Although the x86 instruction set is quite complex (it's CISC anyway), and I saw that many people here discourage your attempts to try to understand this, I will say the opposite: you can still understand it, and you can learn how it is so difficult and how Intel managed to extend it several times from 8086 to modern processors.
Instructions
x86s use variable length encoding, so they can consist of several bytes. Each byte must encode different things, and some of them are optional (it is encoded in the operation code, whether these optional fields are used or not).
For example, each opcode can be preceded by 0 to 4 prefix bytes, which are optional. Usually you do not need to worry about them. They are used to change the size of operands or evacuation codes to the "second floor" of the opcode table with advanced instructions of modern processors (MMX, SSE, etc.).
Then there is the actual operation code, which is usually one byte, but can contain up to three bytes for extended instructions. If you use only the basic set of commands, you also do not need to worry about them.
Further, there is the so-called ModR/M byte (sometimes also called mode-reg-reg/mem ), which encodes the addressing modes and types of operands. It is used only by operation codes that have such operands. It has three bit fields:
- The first two bits (left, the most important) encode the addressing mode (4 possible bit combinations).
- The next three bits encode the first register (8 possible bit combinations).
- The last three bits can encode another register or extend the addressing mode, depending on what setting the first two bits are.
After the ModR/M byte, ModR/M may be another optional byte (depending on the addressing mode) called SIB ( S cale I ndex B ase). It is used for more exotic addressing modes to encode the scale factor (1x, 2x, 4x), base address / register, and index register used. It has a similar layout with the ModR/M byte, but the first two bits on the left (the most significant) are used to encode the scale, and the next three and last three bits encode index and base registers, as the name implies.
If any displacement is used, it occurs immediately afterwards. This can be 0, 1, 2, or 4 bytes depending on the addressing mode and execution mode (16 bit / 32-bit / 64-bit).
The latter is always direct data, if any. It can also be 0, 1, 2, or 4 bytes long.
So now that you know the general x86 instruction format, you just need to know what encodings are for all these bytes. And there are some patterns, contrary to common beliefs.
For example, all register encodings follow the neat ACDB scheme. That is, for 8-bit instructions, the lower two bits of the register code encode registers A, C, D, and B, respectively:
00 = A register (battery)
01 = C register (counter)
10 = D register (data)
11 = B case (basic)
I suspect that their 8-bit processors used only these four 8-bit registers encoded this way:
second +---+---+ f | 0 | 1 | 00 = A i +---+---+---+ 01 = C r | 0 | A : C | 10 = D s +---+ - + - + 11 = B t | 1 | D : B | +---+---+---+
Then, on 16-bit processors, they doubled this register bank and added another register-encoded bit to select the bank, thus:
second second 0 00 = AL +----+----+ +----+----+ 0 01 = CL f | 0 | 1 | f | 0 | 1 | 0 10 = DL i +---+----+----+ i +---+----+----+ 0 11 = BL r | 0 | AL : CL | r | 0 | AH : CH | s +---+ - -+ - -+ s +---+ - -+ - -+ 1 00 = AH t | 1 | DL : BL | t | 1 | DH : BH | 1 01 = CH +---+---+-----+ +---+----+----+ 1 10 = DH 0 = BANK L 1 = BANK H 1 11 = BH
But now you can also use both halves of these registers together as full 16-bit registers. This is done with the last bit of the opcode (the least significant bit, the rightmost): if it is 0 , this is an 8-bit instruction. But if this bit is set (that is, the opcode is an odd number), this is a 16-bit instruction. In this mode, two bits encode one of the ACDB registers, as before. Samples remain the same. But now they encode full 16-bit registers. But when the third byte (the highest) is also set, they switch to a whole bank of registers called the index / pointer register, which are: SP (stack pointer), BP (base pointer), SI (source index), DI (destination / data index ) So, the addressing is as follows:
second second 0 00 = AX +----+----+ +----+----+ 0 01 = CX f | 0 | 1 | f | 0 | 1 | 0 10 = DX i +---+----+----+ i +---+----+----+ 0 11 = BX r | 0 | AX : CX | r | 0 | SP : BP | s +---+ - -+ - -+ s +---+ - -+ - -+ 1 00 = SP t | 1 | DX : BX | t | 1 | SI : DI | 1 01 = BP +---+----+----+ +---+----+----+ 1 10 = SI 0 = BANK OF 1 = BANK OF 1 11 = DI GENERAL-PURPOSE POINTER/INDEX REGISTERS REGISTERS
With the introduction of 32-bit CPUs, they doubled those banks again. But the pattern remains the same. Only now odd operation codes mean 32-bit registers and even operation codes, as before, 8-bit registers. I would call the odd opcodes "long" versions, because the 16/32-bit version is used depending on the processor and its current mode of operation. When it works in 16-bit mode, odd ("long") operation codes mean 16-bit registers, but when it works in 32-bit mode, odd ("long") operation codes mean 32-bit registers. It can be flipped over, the prefix of the entire instruction with the prefix 66 (redefining the size of the operand). Even operation codes (“short”) are always 8-bit. Thus, in a 32-bit processor, register codes:
0 00 = EAX 1 00 = ESP 0 01 = ECX 1 01 = EBP 0 10 = EDX 1 10 = ESI 0 11 = EBX 1 11 = EDI
As you can see, the ACDB pattern remains the same. Also, the pattern SP,BP,SI,SI remains the same. It just uses longer registers.
Operation codes also have some patterns. I already described one of them (even and odd = 8-bit "short" compared to 16/32-bit "long" things). Moreover, you can see on this map the opcode that I did once for quick reference and manual assembly / disassembly:
(This is not a complete table yet; some of the code codes are missing. Maybe I will update it someday.)
As you can see, arithmetic and logical instructions are mainly located in the upper half of the table, and their left and right halves correspond to a similar scheme. Instructions for moving data are in the bottom half. All branching commands (conditional branching) are on line 7* . There is also one full line B* reserved for the mov instruction, which is short for loading instantaneous values ​​(constants) into registers. These are all single-byte opcodes immediately followed by an immediate constant, because they encode the destination register in the opcode (they are selected by the column number in this table) in the three least significant bytes (the rightmost ones). They execute the same pattern for encoding registers. And the fourth bit is the short / long selection. You can see that your imul command is in the table, exactly at position 69 (huh ..; J).
For many instructions, the bit before the short / long bit must encode the order of the operands: which of the two registers encoded in the ModR/M byte is the source, and which is the same (this applies to instructions with two register operands).
As for the addressing mode field of the ModR/M byte, here's how to interpret it:
11 is the simplest: it encodes register-to-register transfers. One register is encoded with the next three bits ( reg field), and the other register with three other bits ( R/M field) of this byte.
01 means that after this byte there will be a one-byte offset.
10 means the same, but the offset used is four bytes (on 32-bit CPUs).
00 is the most difficult: it means indirect addressing or a simple offset, depending on the contents of the R/M field.
If a SIB byte is present, it is signaled by a bitmap of 100 in R/M bits. There is also code 101 for 32-bit mode only for movement, which does not use SIB bytes at all.
Here is a brief description of all these addressing modes:
Mod R/M 11 rrr = register-register (one encoded in `R/M` bits, the other one in `reg` bits). 00 rrr = [ register ] (except SP and BP, which are encoded in `SIB` byte) 00 100 = SIB byte present 00 101 = 32-bit displacement only (no `SIB` byte required) 01 rrr = [ rrr + disp8 ] (8-bit displacement after the `ModR/M` byte) 01 100 = SIB + disp8 10 rrr = [ rrr + disp32 ] (except SP, which means that the `SIB` byte is used) 10 100 = SIB + disp32
So, let's now decode your imul :
69 is the operation code. It encodes a version of imul that does not sign - it extends 8-bit operands. Version 6B decrypts them. (They differ in bit 1 in the opcode if someone asked.)
62 is the RegR/M byte. In binary format, it is 0110 0010 or 01 100 010 . The first two bytes ( Mod field) indicate the indirect addressing mode, and the offset will be 8-bit. The next three bits ( reg field) are 100 and encode the SP register (in this case, ESP , since we are in 32-bit mode) as the destination register. The last three bits represent the R/M field, and we have 010 there that encode register D (in this case EDX ) as another (source) register.
Now we expect an 8-bit offset. And here it is: 2f is the offset, positive (+47 in decimal form).
The last part consists of four bytes of the nearest constant required by the imul command. In your case, this is 6c 64 2d 6c , which in little-endian is $6c2d646c .
And what is the way that cookies collapse; -J