Understanding RIP relative relocations

Viewed 238

I am trying to understand the relocations for the object file generated by this simple program:

int answer = 42;

int compute()
{
    return answer;
}

int main()
{
    return compute();
}

I compile it with simply gcc -c main.cpp.

Then, examining objdump -DCrw -M intel main.o, we see among other things:

Disassembly of section .text:

0000000000000000 <compute()>:
   0:   f3 0f 1e fa             endbr64 
   4:   55                      push   rbp
   5:   48 89 e5                mov    rbp,rsp
   8:   8b 05 00 00 00 00       mov    eax,DWORD PTR [rip+0x0]        # e <compute()+0xe>   a: R_X86_64_PC32    answer-0x4
   e:   5d                      pop    rbp
   f:   c3                      ret    

(...)

Disassembly of section .data:

0000000000000000 <answer>:
   0:   2a 00                   sub    al,BYTE PTR [rax]
    ...

We can also look at the relocations as reported by readelf -rW main.o:

Relocation section '.rela.text' at offset 0x250 contains 2 entries:
    Offset             Info             Type               Symbol's Value  Symbol's Name + Addend
000000000000000a  0000000900000002 R_X86_64_PC32          0000000000000000 answer - 4
(...)

Let's look at the relocation for the use of answer in compute():

To return answer from the function, we have this instruction to put the value of answer into eax:

8:   8b 05 00 00 00 00       mov    eax,DWORD PTR [rip+0x0]

I.e. "copy whatever is at rip + an offset we don't know yet so let's just put 0s for it for now into eax".

The relocation for that instruction is

    Offset             Info             Type               Symbol's Value  Symbol's Name + Addend
000000000000000a  0000000900000002 R_X86_64_PC32          0000000000000000 answer - 4

The offset value 0xa means that there's a relocation to be done at 0xa, or the third byte of the mov instruction above which starts on 0x8. This is the placeholder 00 00 00 00 we saw above. The relocation type is R_X86_64_PC32, which according to the System V Application Binary Interface means "S+A-P", where:

  • S = the value of the symbol whose index resides in the relocation entry
  • A = the addend used to compute the value of the relocatable field
  • P = the place (section offset or address) of the storage unit being relocated (computed using r_offset).

The value of the symbol (the value at 0xa) is 0. A is the addend -4 on that line.

I am not certain what P is, is it the address of the .data section?

And where does the -4 come from?

This calculation seems to end up as "4 bytes less the address of the .data section". How does that represent the distance from RIP (0xe) to wherever answer ends up in the final executable?

If I look at the final executable though (gcc -o main main.o, objdump -DCrw -M intel main), I see our mov instruction has been relocated:

1131:       8b 05 d9 2e 00 00       mov    eax,DWORD PTR [rip+0x2ed9]

The 00 00 00 00 placeholder has been replaced with d9 2e 00 00. rip is now at 0x1137, so rip+0x2ed9 is 0x4010. And sure enough, at 0x4010 we find the answer:

Disassembly of section .data:
(...)
0000000000004010 <answer>:
    4010:       2a 00                   sub    al,BYTE PTR [rax]

So everything works out fine in the end, I just don't understand exactly how it happens.

0 Answers
Related