This change implements ifunc support using additional synthetic sections for the parts of the plt, got and relocations that ifunc uses. This has involved more changes than I would have liked so I think it is worth getting some feedback on the design before going much further.
The background is that there are several problems with the Ifunc implementation
in lld that I think we can use SyntheticSections to fix. These problems are:
- The IRELATIVE relocations must be processed last by the dynamic loader as
the IFunc resolver may call other functions with PLT entries (https://sourceware.org/bugzilla/show_bug.cgi?id=13302)
- lld does not generate IRELATIVE relocations in rel.dyn with --pie [*] when the address of an IFunc resolver function is taken. These must also be processed last by the dynamic loader.
- ARM (and Power) places all R_ARM_IRELATIVE relocations in rel.dyn and ld.so won't accept R_ARM_RELATIVE in .rel.plt.
The patch here addresses the first and the third but not the second, although I believe it could be extended to do so. The existing tests have been updated because they only need the .plt for Ifuncs so no PLT header is needed. At least one more test case for each Target is needed for the case when the .plt does need a header and uses Ifunc.
I've not done any testing with linker scripts yet, as the new synthesised sections are assigned to the same output section as the .plt, .got, .rela.dyn and .rela.plt I'm not expecting any problems.
Review Questions:
- Is this the right general approach? I've outlined some alternatives below.
- Do we want to support taking the address of ifunc resolvers, an alternative is to give a not supported error message.
The outline of the design is to use SyntheticSections for the Ifunc related parts of the plt, plt.got (.got for ARM) and rela.plt (rela.dyn for ARM). In psuedo
linker script notation using the synthetic section names we have for AArch64, x86_32 and x86_64:
`.plt : { PltSection IpltSection }
.got.plt : { GotPltSection IgotPltSection }
.rel.plt : { RelaPlt RelaIPlt }
.rel.dyn : { RelaDyn }`
For ARM we have:
`.plt : { PltSection IpltSection }
.got : { GotSection IgotPltSection }
.rel.plt : { RelaPlt }
.rel.dyn : { RelaDyn RelaIPlt }`
Implications of the design:
- When looking up the Plt, Got or Relocation we need to know whether the Symbol is in the standard or Ifunc section and redirect appropriately.
- We cannot assume that there is one InputSection for the PLT, GOT and Relocations.
- We don't need a header in the Iplt section so for static linking when there is only the IpltSection we omit the Plt header completely. My understanding is that there isn't much use of Ifunc outside of glibc and it could be a reasonable position to say we support ifunc to statically link with glibc and for straight forward user-defined cases. The gcc bug https://gcc.gnu.org/bugzilla/show_bug.cgi?id=70082 has some useful information.
Some thoughts and questions:
Do we need a new Synthetic Section for the IPltSection?
The Plt section always accesses the PltGot and is relocated by the RelaPlt. An alternative would be to have addEntry() and addIfuncEntry() to merge the logic in IPlt to the Plt. I'm thinking that this might be less code overall but that code is likely to be messier.
Do we need a new Synthetic IgotPlt section?
For AArch64 and x86_64 the IgotPlt should write each entry using Target->writeGotPlt() for ARM we need to write each entry using getGotVA() if we were to use on of the existing gotPlt or Got sections we'd need to add the logic in there.
Do we need the separate RelaIPlt relocation sections?
The separate sections solve the ordering problem and avoids some of the if statements needed for ARM.
Do we need to alter the behaviour of getPltVA() and getGotPltVA()?
An alternative is to add getIpltVA() and getIGotPltVA() but then the calling code needs to know which function to call.
- Example when compiled with gcc -fpie --pie will produce an R_X86_64_IRELATIVE relocation in both .rel.dyn and .rel.plt. LLD only produces the R_X86_64_IRELATIVE relocation for .rel.plt and as a result misses the call through the function pointer.
#include <stdio.h> void alt2(void) { printf("alt2\n"); } static void (*resolve (void)) (void) { return alt2; } void fct(void) __attribute__ ((ifunc ("resolve"))); void (*g)(void) = fct; int main () { g (); fct(); return 0; }