p6dis - Disassembler for Intel P6 Microcode
p6dis is a disassembler for Intel P6 family microcode. It processes microcode stored in triplet format (3 microoperations per line) and produces human-readable assembly-like output with labels, branch targets, and flow analysis.
Input files are "hex triad" files (.tri) produced by p6scrambler -d. Each line represents one triad (3 microcode slots) at a specific address.
- -t [type] - Specify CPU type: pentiumpro, pentium2, pentium3, pentiumm, core, core2. Optional - defaults to pentium2 if not given.
- -b [addr] - Set base address (hex format)
- -a - Generate assembly format with .org directives
- -L [arg] - Add label: LABEL=addr or load label file: @file.lbl
- -C [file] - Load constant ROM definitions
- -F [file] - Load floating point ROM (fprom) definitions
- -f [addr] - Forward flow trace: Disassemble only code reachable from [addr]
- -E [addr] - Backward flow trace: Disassemble only code that leads to [addr]
- -e [addr] - Complete flow trace: Find all predecessors of [addr], then show everything those paths can reach
Addresses can be specified as:
- Label name: my_label (must be defined via -L)
- UROM format: UROM_1234
- Hex number: 0x1234 or 1234
Important notice: This set of functions is very very very very inefficient (and no, I am not paying your electricity bill). Someone should rewrite it, but it likely requires starting p6dis from scratch.
Use case: "Show me all code paths starting from this entry point"
p6dis -t pentium2 -f 0x1000 input.tri
Marks and disassembles only microcode reachable by following branches forward from address 0x1000.
Use case: "How did execution reach this point?"
p6dis -t pentium2 -E 0x2000 input.tri
Marks and disassembles only microcode that can lead to address 0x2000 by tracing predecessors backward.
Use case: "Show me all possible execution paths that could end at this point, including their side branches"
p6dis -t pentium2 -e 0x2000 input.tri
Two-phase analysis:
- Find all predecessors that reach 0x2000 (backward)
- From each predecessor, trace forward to show all branches they might take
This is extremely useful for finding all code branches that may terminate at a specific point.
Label file format (.lbl):
# Comments start with # 1234 entry_point 5678 error_handler
Load with: [email protected]
Constant ROM files (.cst) define microcode constant values referenced as CONSTROM.XXX (alias 0x0C) or CONSTROM2.XXX (alias 0x18) in disassembly output.
Format:
index: hexvalue
# Comments start with # 000: FFFFFFFFFFFFFE40 001: 0000000000000001 0EE: DEADBEEF12345678
- Index: 3 hex digits (000-1FF), represents CONSTROM offset
- Value: Up to 32 hex digits, stored raw as a 128-bit number internally - but in practice every constant ROM value seen so far is still just a plain up-to-64-bit number, so 16 hex digits is what you'll actually use
- Whitespace and comments are ignored
- Values can optionally have 0x prefix
- This is the same "IDX: VALUE" loader used for -F below - -C and -F just point it at a different array
Load with: -C constants.cst
When loaded, disassembly will show:
TMP5 = LOAD.SC1.DSZ8 (TMP0, TMP7, CONSTROM.0EE /* 0xDEADBEEF12345678 / -2401053092306725256 */, ...)
Floating Point ROM (fprom) Files
fprom files define the microcode floating-point constant ROM, shown as an inline comment after a plain CONST.
Format is identical to the constant ROM file's "IDX: VALUE" format (see above) - same loader, same index range (000-1FF), values up to 32 hex digits:
Load with: -F fprom.cst
Each fprom entry can hold either a float-ish payload or a short ASCII string packed into the low 32 or 64 bits (used for e.g. the "Intel(R)..." CPU brand string baked into some ROMs); p6dis tries the ASCII interpretation first and only shows it when the bytes are actually printable:
Known issue: the "floatpoint" value is currently computed assuming a classic 80-bit x87 extended layout (64-bit mantissa / 15-bit exponent / sign), which is a placeholder, not confirmed hardware fact. Reverse-engineering of a separate raw ROM readout (which exposes the exponent as its own ~20-bit field alongside the mantissa, rather than packed into a fixed 80-bit word) suggests the real fprom entries are wider than 80 bits and use a biased exponent (bias 0x10041 observed empirically) over a mantissa wider than 64 bits - none of that is wired into p6dis's float decode yet, so treat the printed floatpoint value with suspicion until this is revisited. A small helper tool, readconst2_to_fprom, exists to pack that raw ROM readout format into an -F-loadable file without doing any (potentially wrong) float interpretation itself.
This pipeline generates complete backtraces for every End-Of-Microcode (EOM) flow marker:
Explanation:
002: 5555555555555584F
12F: 02952286C65746E49
TMPx = MOVE (CONST.0B.12F /* 0x2952286C65746E49 / 0 / 'Intel(R)' */, ...)
p6scrambler -d -t pentium2 msrom.hex | p6dis -t pentium2 -
p6scrambler -d -t pentium2 msrom.hex | p6dis -t pentium2 [email protected] -
p6dis -t pentiumpro [email protected] -f entry_point input.tri > output.asm
Advanced: Backtrace All EOM Flow Markers
p6scrambler -d -t pentiumpro msrom.hex | \
p6dis -t pentiumpro - | \
grep EOM | cut -c 6-9 | \
while read addr ; do
# Full backtrace (backward only)
p6scrambler -d -t pentiumpro msrom.hex | \
p6dis -t pentiumpro [email protected] - -E 0x$addr > full/$addr.full.asm
# Complete flow (backward + forward from all predecessors)
p6scrambler -d -t pentiumpro msrom.hex | \
p6dis -t pentiumpro [email protected] - -e 0x$addr > full/$addr.back.asm
echo $addr
done
./p6microcode-tools/p6scrambler -dt pentiumpro msrom-612.hex | ./p6microcode-tools/p6dis -t pentiumpro - | grep EOM | cut -c 6-9 | while read addr ; do ./p6microcode-tools/p6scrambler -dt pentiumpro msrom-612.hex | ./p6microcode-tools/p6dis -t pentiumpro -L @msrom-612.lbl - -E 0x$addr > full/msrom-612-$addr.full.asm ; ./p6microcode-tools/p6scrambler -dt pentiumpro msrom-612.hex | ./p6microcode-tools/p6dis -t pentiumpro -L @msrom-612.lbl - -e 0x$addr > full/msrom-612-$addr.back.asm ; echo $addr ; done