x86 32-bit Registers, Modes, and Instruction Encoding
IA-32 is the 32-bit extension of the original x86 architecture. It is useful to learn as a precisely specified historical and still-maintained programming model, particularly when reading 32-bit object files, firmware-era material, or older operating-system code. This article describes ordinary, authorized program development and inspection. An instruction stream is not self-describing: use a known object file, its symbols, its target mode, and its ABI. Never execute unknown bytes merely to find out what they do.
Register state in 32-bit code
The eight general-purpose registers are EAX, EBX, ECX, EDX, ESI, EDI, EBP, and ESP. Their low 16-bit names are AX through DI, BP, and SP. AX, BX, CX, and DX also expose low and high bytes: AL/AH, BL/BH, CL/CH, and DL/DH. The names describe overlapping views, not separate storage: writing AL changes only eight bits of EAX. In contrast, the x86-64 rule that a 32-bit write clears the upper half of a 64-bit register belongs to 64-bit mode and should not be projected backward onto IA-32.
| State | Typical role | Important qualification |
|---|---|---|
| EAX | integer return value; accumulator in some encodings | It is general purpose unless an instruction or ABI says otherwise. |
| ECX | count register; CL supplies variable shift counts | IA-32 cdecl integer arguments are stack-passed under the target ABI. ECX is an argument register only in an explicitly named non-cdecl convention, such as a compiler's fastcall variant. |
| EDX:EAX | wide multiply/divide result or dividend | That pair is an instruction convention, not a universal return rule. |
| ESI / EDI | index registers; source/destination for string instructions | Usually callee-saved in common 32-bit C ABIs. |
| EBP / ESP | frame and stack pointers | Optimized code can use EBP as a general register. |
| EIP | next instruction pointer | Control transfers change it; ordinary mov does not name it. |
| EFLAGS | condition and control flags | CF, ZF, SF, OF, and PF have distinct meanings. |
cmp a, b computes the flags as though it had formed a - b, without retaining that subtraction result. A following je tests ZF; jb uses unsigned “below” logic; jl uses signed “less” logic. The mnemonic choice is therefore part of the type information a reader must recover. test similarly updates flags after an AND-like operation. Preserve flags only when a documented interface requires it; most C calling conventions classify condition flags as caller-clobbered.
From segments to a flat protected-mode address space
Segmentation is central to x86 history. In 16-bit real mode, a segment value and offset form an address, traditionally explained as segment * 16 + offset; this permits a 20-bit physical address from 16-bit pieces. The 80286 and later protected mode changed segment registers into selectors for descriptor-table entries. Descriptors supply a base, limit, and permissions. This is not the same model as real mode, and protected mode must be set up by privileged system software.
Most 32-bit Unix-like and Windows user programs use a flat model: operating-system descriptors make the conventional code and data segments have base zero and an appropriate limit, so a normal pointer is effectively a 32-bit linear address. CS selects executable code; DS, ES, and SS provide historical defaults for data and stack references; FS and GS can be used for thread- or system-defined data. “Flat” does not mean segment registers vanished, and it does not bypass page protection. Paging then translates linear addresses through tables controlled by the operating system.
Do not confuse this IA-32 practice with x86-64: long mode retains segment selectors but generally treats CS/DS/ES/SS bases differently, while FS and GS have special base use. See the dedicated x86 assembly overview for a comparison, rather than assuming 64-bit conventions in a 32-bit listing.
How an instruction becomes bytes
x86 uses variable-length encodings. A typical instruction contains optional legacy prefixes, one or more opcode bytes, an optional ModR/M byte, an optional SIB byte, displacement bytes, and immediate bytes. This is a conceptual map, not an invitation to hand-decode arbitrary data: instruction length and the meaning of fields depend on earlier opcode and prefix choices. Let an assembler and a debugger or disassembler operating on a file you own do the mechanical work.
The ModR/M byte divides into mod, reg, and r/m fields. Depending on the opcode, reg either names a register operand or extends an opcode group; r/m names a register when mod=11, otherwise it participates in a memory address. The SIB byte, where selected, describes a scaled index: base + index × 1, 2, 4, or 8 plus any displacement. For example, a compiler might express an array element as [ebx + esi*4 + 12]. That notation means an address calculation followed by a load or store when the instruction operand is memory; lea eax, [ebx + esi*4 + 12] performs only the address arithmetic.
| Source form | Conceptual fields | Meaning |
|---|---|---|
mov eax, 7 | opcode + immediate | Place a constant in EAX. |
add eax, ebx | opcode + ModR/M | Register-to-register arithmetic. |
mov eax, [ebp-4] | opcode + ModR/M + displacement | Load from an address relative to EBP. |
mov edx, [ebx+esi*4+8] | opcode + ModR/M + SIB + displacement | Load an indexed element. |
Multi-byte numeric fields are normally little-endian: the least significant byte occupies the lowest address. Thus a four-byte value 0x12345678 is stored as bytes 78 56 34 12. This ordering affects data in memory, displacements, and immediates; it does not reverse the way humans write the hexadecimal number. Byte-oriented character data is simply addressed in increasing byte order.
Object files, relocations, and a safe inspection loop
Assembly source is not yet a runnable program. An assembler makes an object file with sections, symbols, and possibly relocations. A relocation says that a linker must adjust a field when it knows the final address of a named symbol. It is normal to see an unresolved call or address in an ELF32 .o file. Inspect it without linking or running:
nasm -f elf32 sample.asm -o sample.o
readelf -h -S -s -r sample.o
objdump -dr -Mintel sample.o
These commands require NASM and GNU binutils, but no 32-bit C library, linker startup objects, or multilib runtime. They are appropriate for a source file you wrote. Confirm that readelf -h reports ELF32 and an i386 machine target, then correlate objdump -dr output with relocation annotations. A byte sequence alone lacks enough context to establish a function boundary or intent.
; sample.asm: assemble only; no entry point and no system call
BITS 32
section .text
global add_index
add_index:
lea eax, [eax + edx*4 + 8] ; arithmetic address expression
ret
This small example intentionally has no external dependencies. Its comments name the input assumption only for a controlled demonstration; a real exported function needs an ABI declaration and preservation rules. ret is not proof of a safe callable interface by itself.
Floating point and extensions
IA-32 also has x87 floating-point state, an eight-register stack of extended-precision values, and later SIMD extensions such as MMX and SSE. Legacy C compilers may use x87 for scalar floating point; SSE introduced XMM registers and a flatter register model for single- and double-precision operations. Which is used depends on compiler target options, CPU baseline, and ABI. Do not mix instructions casually: x87 state, MMX state, XMM register preservation, alignment, and exception behavior are interface concerns. Modern 32-bit ABIs commonly return scalar floating results in an x87 register, while vector details must be checked against the selected ABI.
Safe exercises
- Write a two-instruction NASM source that returns
EAX + 1, assemble it to ELF32 only, and identify its ELF class withreadelf. - Change an indexed
leascale from 4 to 8. Predict the source-level arithmetic, then verify the assembler’s listing or disassembly of your own object. - For a signed and an unsigned C comparison, compile source you own to a 32-bit assembly listing where your toolchain supports it. Explain why the conditional mnemonics differ; do not execute generated binaries just for this exercise.
Reading checklist. Before explaining one instruction, identify its processor mode, object format, and ABI. Then identify operand widths, whether brackets mean a memory access, and whether a conditional branch is signed or unsigned. Finally, check labels and relocations rather than assigning an external address by appearance. This sequence turns a disassembly into corroborated evidence. It also prevents a common error: treating EIP as a general register or treating a selector value as an ordinary flat pointer. Privileged state and page tables belong to operating-system design, not to ordinary user-mode arithmetic.
dispelled