Modern processors do not execute human readable assembly code. They operate exclusively on machine code, which consists of binary encoded instructions defined by the processor instruction set architecture. Assembly language exists only as a textual representation of those instructions. During compilation, this text must be translated into raw machine code that the processor can execute directly. This translation step is handled by the assembler.
This article walks through how assembly code, data, and external references are transformed into machine code and laid out in memory on 32 bit Windows systems using the Portable Executable format.
Sections, Offsets, and Why They Exist
Processors cannot interpret text strings such as hi there or symbolic references like MessageBoxA. Any non instruction data must be stored separately in memory and referenced by address. To support this, compilers organize program output into sections.
Each section is assigned a file offset and a virtual offset. At runtime, the operating system maps these sections into memory at specific addresses relative to the module image base.
Typical sections include:
.text
Contains executable machine code..rdata
Stores read only data such as string literals, constants, and lookup tables..idata
Stores import related structures, including pointers to external API functions.
These section names are conventions, not guarantees. Different compilers may merge or repurpose sections depending on optimization and linker behavior.
Example: Strings and Absolute Addressing
Consider two string literals used by a program:
"info""hi there"
The compiler encodes these strings as raw bytes using ASCII encoding:
"info"becomes69 6E 66 6F 00"hi there"becomes68 69 20 74 68 65 72 65 00
These byte sequences are placed consecutively in a data section, commonly .rdata.
Assume the following:
- Image base address:
0x400000 .rdatasection virtual offset:0x2000
Then the absolute addresses become:
"info"at0x402000"hi there"at0x402005
The processor never sees the string itself, only the address where the data resides.
External Functions and the Import Address Table
API calls such as MessageBoxA are not understood by the processor as symbolic names. The compiler and linker resolve these calls through an Import Address Table.
The Import Address Table stores function pointers to external routines loaded from system libraries. At runtime, the loader fills this table with the actual addresses of the imported functions.
For example:
MessageBoxApointer stored at0x403018- The code performs an indirect call through this address
This allows the program to jump to the correct system function at runtime without hardcoding addresses.
Machine Code Generation
Once data and imports are organized into sections, the compiler translates assembly instructions into machine code using the x86 instruction set encoding rules.
Examples:
push 0
Encoded as6A 00push 0x402005
Encoded as68 05 20 40 00call ds:[0x403018]
Encoded asFF 15 18 30 40 00
Each instruction is converted into one or more bytes and written sequentially into the .text section.
The processor executes these bytes directly, without any awareness of the original assembly text.
Object Files and COFF
At this stage, the compiler output is not yet a runnable executable. Instead, it produces an object file using the Common Object File Format.
A COFF file contains:
- An
IMAGE_FILE_HEADERdescribing the file and section count - An array of
IMAGE_SECTION_HEADERstructures - The raw data for each section
The .text section usually appears first, followed by data sections. The object file records where each section should be placed, but it does not yet define final runtime addresses.
Linking and Final Executable Generation
The linker takes one or more COFF object files and produces a Portable Executable file such as an .exe or .dll.
During linking, the linker:
- Assigns a final image base
- Resolves imports and relocations
- Builds the PE headers
- Aligns sections in memory
Only after this step does the program become a valid executable that the Windows loader can map into memory.
Endianness Considerations
On x86 systems, multi byte values are stored in little endian format. This means the least significant byte is stored at the lowest memory address.
For example:
0x402005is stored as05 20 40 00
Instruction encoding, pointers, and integers all follow this convention. Strings and byte arrays, however, are stored in increasing address order.
Summary
The transformation from source code to executable involves several distinct stages:
- Source code is compiled into assembly.
- Assembly is assembled into machine code.
- Data and imports are placed into structured sections.
- The compiler emits COFF object files.
- The linker produces a final PE executable.
Throughout this process, symbolic constructs are replaced with absolute or relative addresses that the processor can execute directly. Understanding these mechanics is foundational for reverse engineering, exploit development, and low level systems programming.





