Previously, I showed a portion of the code generated when compiling a variation of “Hello, world!” using the x64-targeting compiler from Visual Studio 2005.
I observed that some instructions, like JMP and JNE had offsets directly encoded into the instructions, whereas others like LEA, CALL, and CMP, didn’t.
The reason behind this is that at compile time we know the relative positions that the JMP and JNE are targeting, because they are within the same function, and we are generating the code at that time, so we know exactly where they will end up.
In theory, when compiling without /Gy (which if you’ll recall instructed the compiler to put each function in its own .text section), we could emit the displacements for any code that was being generated during the same compile and going into the same .text section. We don’t, but in theory we could.
To refresh your memory, here is what the generated code for main looked like:
main PROC ; COMDAT
; Line 10
$LN5:
00000 48 83 ec 28 sub rsp, 40 ; 00000028H
; Line 11
00004 48 8d 0d 00 00
00 00 lea rcx, OFFSET FLAT:$SG2147
0000b e8 00 00 00 00 call puts
; Line 12
00010 83 3d 00 00 00
00 00 cmp DWORD PTR i, 0
00017 75 09 jne SHORT $LN2@main
; Line 13
00019 e8 00 00 00 00 call pass
0001e eb 07 jmp SHORT $LN3@main
; Line 14
00020 eb 05 jmp SHORT $LN1@main
$LN2@main:
; Line 15
00022 e8 00 00 00 00 call fail
$LN1@main:
$LN3@main:
; Line 17
00027 48 83 c4 28 add rsp, 40 ; 00000028H
0002b c3 ret 0
main ENDP
Looking first at LEA, it’s loading the address of a symbol called $SG2147, which appears in our .cod listing and represents the “Hello, world!” string:
$SG2147 DB 'Hello, world!', 00H
The LEA instruction is currently encoded with a relative displacement of zero. The way that gets “fixed up” is by the linker coming back and patching the instruction once it knows where both the LEA instruction and $SG2147 are going to end up in the generated image.
How does the linker know what needs to be “fixed up”?
Well, the compiler emits this information when it emits the code. Specifically, it emits relocations, often called fix-ups, which the linker reads and uses to patch the code while it generates the final image.
By doing:
dumpbin /disasm /relocations /section:.text /symbols hello.obj > hello.obj.d
we can examine the relocations generated for the .text sections of hello.obj.
Here is what the relocations for main look like:
RELOCATIONS #C
Symbol Symbol
Offset Type Applied To Index Name
-------- ---------------- ----------------- -------- ------
00000007 REL32 00000000 A $SG2147
0000000C REL32 00000000 16 puts
00000012 REL32_1 00000000 5 i
0000001A REL32 00000000 F pass
00000023 REL32 00000000 1C fail
Note that #C refers to the section that these relocations were generated for. The offset specified is the code offset from the beginning of the section to which this relocation will be applied. The relocation type, in this case always REL32 or REL32_1, refers to how the final value that is patched into the machine code is going to be calculated. The “Applied To” column simply repeats the information that is currently encoded into the instruction, and is displayed for convenience. This is not in the relocation table stored in the object file. In this case it is always zero, and we will ignore this column. The symbol index is an index into the symbol table is stored in the object file. The symbol name is displayed for convenience only. Like the “Applied To” information, it is not stored in the relocation table.
So what does this table tell the linker?
Well, in each of the REL32 cases, it tells the linker to emit a displacement relative to the current instruction pointer and the final location of the symbol that is referred to in the relocation. Well, that cleared things up, or maybe not…let’s look at an example.
In the case of the LEA of $SG2147, assuming that $SG2147 ends up 0x0001EFC5 bytes after this instruction in the final image, it would replace the zeros at offset 7 with that displacement (0x0001EFC5). Note that in the case of REL32, these displacements are signed 32-bit values, so if $SG2147 ended up before the code in the image, we would have a negative displacement.
Recall from the last installment that by “current instruction pointer” or “after this instruction” we really mean the value the instruction pointer has after fetching this instruction, so the address of the first byte after the LEA, not the address of the LEA, or the address at the beginning of the displacement.
So what about REL32_1?
Well, that is a variant of REL32. This other relocation type simplifies the job of the linker by letting it know how many bytes follow the fixed-up data, but are still a part of the same instruction. Otherwise, the linker would need to know where the start of the instruction is, and would need to decode the instruction and determine how long the instruction is.
In this case, the REL32_1 is being applied to the cmp instruction, which is comparing a value against zero, which is encoded as the final byte of the instruction. Because of this one byte zero that follows the fixed-up displacement in the instruction, we need to use the REL32_1 relocation type. The relocations that the linker supports are documented in the Microsoft Portable Executable and Common Object File Format Specification.
I intend on coming back to this simple program to illustrate more aspects of the object file format and the x64 ABI, so stay tuned.