32-bit x86 code holds absolute addresses: a mov eax, 0x1000CCD8 or a
call dword ptr [0x10009030] has the address written into the instruction,
worked out for the image loaded at its
ImageBase. When the image
is loaded somewhere else, because the address is taken or because ASLR moves it, every
such address must be adjusted. The base relocation table, at data directory 5 and
usually in the .reloc section, lists where they are. A file with the
RELOCS_STRIPPED flag has none and can only load at its image base.
Blocks
The table is a run of blocks, one for each 4 KB page that has addresses to fix. Each block starts on a 4-byte boundary:
| Offset | Size | Type | Field | Meaning |
|---|---|---|---|---|
| 0 | 4 | u32 | VirtualAddress | The RVA of the page. |
| 4 | 4 | u32 | SizeOfBlock | The size of the block in bytes, these 8 included. |
| 8 | 2 each | u16 | Entries | (SizeOfBlock − 8) ÷ 2 entries. The top 4 bits of each are its type, and the low 12 bits its offset in the page. |
To apply an entry, the loader works out the difference between the address the image was loaded at and its ImageBase, and adds it to the value at VirtualAddress + offset. A block whose entries come to an odd number ends with a type 0 entry, padding, to keep the next block on a 4-byte boundary.
0000B400 00 10 00 00 C4 00 00 00 01 30 38 32 7B 32 E8 32
0xE000 (file 0xB400): page 0x1000, 196 bytes, 94 entries. 0x3001 is a HIGHLOW at 0x1001; then 0x1238, 0x127B and 0x12E8.The first entry fixes the 4 bytes at RVA 0x1001, which hold
0x1000CCD8: they are the operand of the DLL’s first instruction,
B8 D8 CC 00 10, mov eax, 0x1000CCD8, at
0x10001000. If the DLL loads at 0x20000000 instead, the
loader adds 0x10000000 and the operand becomes 0x2000CCD8.
That file has ten blocks, one for each page from 0x1000 to
0xA000, with 1,038 entries in all.
Types
| Type | Name | What it fixes |
|---|---|---|
| 0 | ABSOLUTE | Nothing: padding, skipped. |
| 1 | HIGH | The high 16 bits of a 32-bit address, in a 16-bit field. |
| 2 | LOW | The low 16 bits of a 32-bit address, in a 16-bit field. |
| 3 | HIGHLOW | A whole 32-bit address. The usual type in PE32 files. |
| 4 | HIGHADJ | The high 16 bits, adjusted for the low 16 bits held in the next entry, which is a parameter rather than an entry of its own. |
| 5 | MIPS_JMPADDR, ARM_MOV32, RISCV_HIGH20 | Depends on the machine: a MIPS jump, an ARM MOVW/MOVT pair, or a RISC-V high 20 bits. |
| 6 | – | Reserved. |
| 7 | THUMB_MOV32, RISCV_LOW12I | A Thumb-2 MOVW/MOVT pair (32-bit ARM Windows), or a RISC-V low 12 bits in an I-type instruction. |
| 8 | RISCV_LOW12S, LOONGARCH32_MARK_LA, LOONGARCH64_MARK_LA | A RISC-V low 12 bits in an S-type instruction, or a LoongArch address load. |
| 9 | MIPS_JMPADDR16 | A MIPS16 jump. winnt.h also names it IA64_IMM64. |
| 10 | DIR64 | A whole 64-bit address, the usual type in PE32+ files, not used in PE32. |
The explorer names types 5, 7, 8 and 9 by the file’s machine, and for HIGHLOW and
DIR64 shows the value each entry patches, as in “HIGHLOW at 0x1001 ·
patches 0x1000CCD8”.