This is an archive of the discontinued LLVM Phabricator instance.

Adds support for GOT relocations to i386/ELF backend
ClosedPublic

Authored by jain98 on Dec 18 2022, 8:09 PM.

Details

Summary

This CR adds support for GOT relocations to the JITLink i386/ELF backend.

Diff Detail

Event Timeline

jain98 created this revision.Dec 18 2022, 8:09 PM
Herald added a project: Restricted Project. · View Herald TranscriptDec 18 2022, 8:09 PM
jain98 requested review of this revision.Dec 18 2022, 8:09 PM
Herald added a project: Restricted Project. · View Herald TranscriptDec 18 2022, 8:09 PM
jain98 retitled this revision from Adds support for GOT relocations to i386 backend to Adds support for GOT relocations to i386/ELF backend.Dec 18 2022, 8:10 PM
jain98 edited the summary of this revision. (Show Details)
jain98 updated this revision to Diff 483855.Dec 18 2022, 8:13 PM

Fixed a binary path issue in tests

lhames accepted this revision.Dec 23 2022, 7:58 PM

This looks great, and it's a big step forward for the i386 backend. (Side note: I had no idea how steep the learning curve on this one would be -- nice work getting through it!)

LGTM. :)

llvm/test/ExecutionEngine/JITLink/i386/ELF_external_to_absolute_conversion.s
28–32

This sequence is trying to access the non-existent GOT-entry, which is why it crashes at runtime.

I think the assembly can be reduced to the i386 equivalent of the x86-64 sequence:

        .text
        .globl  main                            
        .p2align        4, 0x90
        .type   main,@function
main:                    
.L0$pb:
        movl    $_GLOBAL_OFFSET_TABLE_-.L0$pb, %eax
        movl    $foo@GOTOFF, %eax
        xorl    %eax, %eax
        retl
        .size   main, .-main

The '$' symbol at the start of the expression means that we're treating the result of the expression as an immediate, not an address to be loaded from. The immediate values that we move into %eax will be zeros, but since %eax isn't used anyway we can disregard that.

This revision is now accepted and ready to land.Dec 23 2022, 7:58 PM
This revision was automatically updated to reflect the committed changes.
jain98 added inline comments.Dec 24 2022, 6:38 PM
llvm/test/ExecutionEngine/JITLink/i386/ELF_external_to_absolute_conversion.s
28–32

This sequence is trying to access the non-existent GOT-entry, which is why it crashes at runtime.

I didn't quite get that. I think, you're referring to this instruction -

movl    a@GOTOFF(%eax), %eax

I thought this instruction was using a's offset from the GOT symbol - not accessing any GOT entry? Could you point to the specific instruction that you were referring to in your comment?

I do think I understand why the seg fault might be happening though. Essentially a@GOTOFF(%eax) ends up being a@GOTOFF(0x00000000)? Which is likely out of the JITLink process' memory. If that is indeed the case, I have another question. The assembly that I used from the test was actually generated by Clang. Is it just that Clang uses a different linker that can actually handle the code that it's producing without seg-faulting?

lhames added inline comments.Jan 9 2023, 8:48 PM
llvm/test/ExecutionEngine/JITLink/i386/ELF_external_to_absolute_conversion.s
28–32

I thought this instruction was using a's offset from the GOT symbol - not accessing any GOT entry? Could you point to the specific instruction that you were referring to in your comment?

Oh you're right -- GOTOFF references the symbol, not the GOT entry for the symbol. Sorry for the confusion!

In that case you're absolutely right -- this should work even if there's no GOT synthesized. It looks like the problem is that the R_386_GOTPC relocation has an addend embedded in it, but it's not being included in the Edge construction:

ELF_external_to_absolute_conversion.i386.o:     file format elf32-i386


Disassembly of section .text:

00000000 <main>:
   0:   55                      push   %ebp
   1:   89 e5                   mov    %esp,%ebp
   3:   50                      push   %eax
   4:   e8 00 00 00 00          call   9 <main+0x9>
   9:   58                      pop    %eax
   a:   81 c0 03 00 00 00       add    $0x3,%eax.       <-- 0x00000003 addend embedded here at fixup location.
  10:   c7 45 fc 00 00 00 00    movl   $0x0,-0x4(%ebp)
  17:   8b 80 00 00 00 00       mov    0x0(%eax),%eax
  1d:   83 c4 04                add    $0x4,%esp
  20:   5d                      pop    %ebp
  21:   c3                      ret

Edge from debugging output:

0xb7ab600c (block + 0x0000000c), addend = +0x00000000, kind = Delta32, target = _GLOBAL_OFFSET_TABLE_

You just need to read that addend out when constructing the edge. E.g. here's the MachO_x86_64 backend doing something similar: https://github.com/llvm/llvm-project/blob/5a58b19f9c93f3ac51bcde318508131ae78aa10c/llvm/lib/ExecutionEngine/JITLink/MachO_x86_64.cpp#L342

The missing addend is causing the variable reference to point to the page _before_ the data section, which is usually outside allocated memory -- that's triggering the crash, not the missing GOT.

Once you fix that I think your original test case should just work -- sorry for the bad advice above!

jain98 marked 2 inline comments as done.Jan 13 2023, 6:28 PM
jain98 added inline comments.
llvm/test/ExecutionEngine/JITLink/i386/ELF_external_to_absolute_conversion.s
28–32

Thanks for the detailed explanation Lang! The solution and the problem make sense for the most part. I'm confused about one thing though. What is causing the embedded addend to be present? In the testcase we're looking at what we need is the address of the GOT in %eax.

I'll try to clarify my question.

Disassembly of section .text:

00000000 <main>:
   0:   55                      push   %ebp
   1:   89 e5                   mov    %esp,%ebp
   3:   50                      push   %eax
   4:   e8 00 00 00 00          call   9 <main+0x9>
   9:   58                      pop    %eax   // After this instruction %eax will contain the address 9
   a:   81 c0 03 00 00 00       add    $0x3,%eax.       // This is the fixup location and we should be adding the (GOT-9) to %eax here, no?
  10:   c7 45 fc 00 00 00 00    movl   $0x0,-0x4(%ebp)
  17:   8b 80 00 00 00 00       mov    0x0(%eax),%eax
  1d:   83 c4 04                add    $0x4,%esp
  20:   5d                      pop    %ebp
  21:   c3                      ret

Looking at a few other objects it looks like the embedded addend has something to do with the byte number, of an instruction, where the fixup location starts, but it still doesn't make sense to me, why that is the case.

Additionally, is there a way I can access this "byte number of the start of fixup location" using jitlink-check?

lhames added inline comments.Jan 13 2023, 7:02 PM
llvm/test/ExecutionEngine/JITLink/i386/ELF_external_to_absolute_conversion.s
28–32

No worries. :)

What is causing the embedded addend to be present? In the testcase we're looking at what we need is the address of the GOT in %eax...

Agreed. The addend ends up being a small but crucial part of that calculation.

Taking the example above, the address loaded into %eax by the pop will be the address of the pop instruction itself (the call will have placed the address of the next address on the stack, and the next instruction is the pop itself).

Now in the add instruction you need to add some delta D to %eax to cause it to be equal to GOT. The usual linker calculation is the delta from _the address being fixed up_ (i.e. the address of the operand field of the add) to the target (the _GLOBAL_OFFSET_TABLE_ symbol), but in this case the address being fixed up isn't the same as the address in %eax (the address of the pop) -- it's off by exactly 3 bytes: 1 byte for the pop instruction, and two bytes for the opcode field of the add.

So the addend of 3 is ELF trying to help us out and make the linker math simple: D = GOT - Address of Fixup + Addend, with Addend implicitly capturing the smaller delta between the value stored in %eax and Address of Fixup.

Additionally, is there a way I can access this "byte number of the start of fixup location" using jitlink-check?

You can, but you don't usually want to -- you would read the fixed-up operand value using decode_operand, then compare it to the expected value. You'll have to find the decode_operand index by trial and error, but it should end up looking something like this:

        calll   .L0$pb
.L0$pb:
        popl    %eax
        .globl GOTPC
GOTPC:
        addl    $_GLOBAL_OFFSET_TABLE_+(.Ltmp0-.L0$pb), %eax

#jitlink-check: decode_operand(GOTPC, <idx>) = _GLOBAL_OFFSET_TABLE_ - (GOTPC + 2)

where GOTPC + 2 gives you the fixup location -- two bytes into the add.

jain98 marked 2 inline comments as done.Jan 13 2023, 7:20 PM
jain98 added inline comments.
llvm/test/ExecutionEngine/JITLink/i386/ELF_external_to_absolute_conversion.s
28–32

Thanks, I think that makes sense! It seems like the addend is present because of the way the R_386_GOTPC relocation is calculated (GOT - fixup location), that the compiler is giving us the remaining delta to arrive at the correct GOT address. I'll have a patch out for this shortly. Thanks for talking through this :).