6. GNU IFunc support
GNU IFunc functionality enables a developer to provide multiple implementations for a function and a resolver function that selects at runtime which implementation to use. It is typically used for selecting the most optimized implementation for a given CPU / runtime. The resolver function is called once at the start of the application runtime and then the selected implementation is fixed. Resolver function is always called eagerly (at program start) instead of lazily (when first needed).
Example:
__attribute__((ifunc("foo_resolver")))
int foo(int);
int foo_impl(int u) {
return u;
}
int (*foo_resolver())(int) {
return foo_impl;
}
int bar(int u) {
int v = foo(u); // Calls the function that is returned by foo_resolver
}
6.1. Static linking
eld generates a PLT slot for each ifunc symbol. Each PLT slot has a corresponding GOTPLT slot. This model is very similar to how preemptible dynamic symbols are handled. However, instead of doing symbol resolution at runtime to fill the GOTPLT slot, the runtime calls the corresponding resolver function to fill the GOTPLT slot.
For the case of static executables, libc plays the role of runtime and takes on a task that is typically unusual for a libc – resolve symbols and patch the binary at runtime with the resolution information.
eld emits R_<TARGET>_IRELATIVE relocations in .rel[a].plt section, and __rel[a]_iplt_start and
__rela_iplt_end symbols that store the start and end addresses of .rel[a].plt section.
The tiny-loader in the libc process the R_<TARGET>_IRELATIVE relocations by
iterating over the [__rel[a]_iplt_start, __rel[a]_iplt_end) range.
Relocation section '.rela.plt' at offset 0x190 contains 6 entries:
Offset Info Type Symbol's Value
0000000000490000 0000000000000408 R_<TARGET>_IRELATIVE 4358c0
0000000000490008 0000000000000408 R_<TARGET>_IRELATIVE 40db20
The symbol value of IRELATIVE relocations contains the IFunc resolver address, and
the relocation offset points to the GOTPLT slot for the IFunc symbol. For each relocation,
the tiny-loader in the libc calls the IFunc resolver and stores the result in the GOTPLT slot.
Note that there is no lazy binding here.
6.1.1. Direct reference to an IFunc symbol
Direct references to an IFunc symbol are resolved to the PLT slot of the IFunc symbol.
// global variable!
// Absolute relocation, for example, `R_AARCH64_ABS64`, `R_RISCV_64`.
int (*foo_gp)(int) = foo;
eld will resolve the absolute relocation to the address of PLT[foo].
6.2. GOT references to an IFunc symbol
GOT references to an IFunc symbol are resolved to the GOTPLT slot of the IFunc symbol when there are no direct references to the IFunc symbol. When there is a direct reference to the IFunc symbol, then the GOT references to the IFunc symbols gets resolved to the GOT slot of the IFunc symbol. It is to ensure pointer equality. Let’s understand this in detail:
Case 1: PIC code and no direct reference
// foo is an ifunc symbol!
int main() {
// adrp x0, :got:foo ; R_AARCH64_GOT_PAGE
// ldr x0, [x0, :got_lo12:foo] ; R_AARCH64_LD64_GOT_LO12_NC
int (*foo_lp)(int) = foo;
}
Here eld will resolve adrp + ldr relocation pair to the address of GOTPLT[foo].
Hence, ldr will load the address that is stored in the GOTPLT[foo]. Hence,
foo_lp will store the address of the resolved function. With this design, there
is no indirection penalty for calls to foo_lp.
Case 2: PIC code and direct reference
// foo is an ifunc symbol!
// R_AARCH64_ABS64
int (*foo_gp)(int) = foo;
int main() {
// adrp x0, :got:foo ; R_AARCH64_GOT_PAGE
// ldr x0, [x0, :got_lo12:foo] ; R_AARCH64_LD64_GOT_LO12_NC
int (*foo_lp)(int) = foo;
}
Here eld will resolve ABS64 to the address of PLT[foo]. With this, calls to foo_gp will
work as expected. Note that we cannot resolve ABS64 to the address of
the resolved function because at link time we cannot know the resolved function.
Now, if we resolve adrp + ldr relocation pair as before, then foo_lp will store the address of
the resolved function. This is a problem because foo_gp and foo_lp, both pointers to the same
function foo, have different values.
To resolve this, whenever their is a direct reference to an ifunc symbol foo,
eld creates an additional GOT slot for foo, and fill that with the address of
the PLT[foo], and resolve all GOT references of foo to the GOT[foo] instead of the GOTPLT[foo].
With this design, foo_lp will store the address of PLT[foo], the same as foo_gp. Hence,
no pointer inequality issue.
6.3. IFunc behaviour across all relocations
To describe IFunc behavior for all relocations, we categorize the relocations into the following categories:
Absolute / PC-relative data relocations
GOT-related data relocations
Control flow relocations
Regular GOT-related instruction relocations
Regular absolute / PC-relative address-forming relocations
Address forming relocations used to compute both GOT and non-GOT addresses.
Absolute / PC-relative load/store relocations
Regular in the relocation category name here means that the relocations are non-TLS non-call relocations.
Any relocation against an IFunc symbol that does not fall into one of the above categories is invalid and produces a warning.
The relocations which are not supported by GNU for IFunc symbols are annotated with NotSupportedInGNULDForIFunc. The GNU toolchain that is used for verifying this is: aarch64-none-linux-gnu-gcc (Arm GNU Toolchain 15.2.Rel1 (Build arm-15.86)) 15.2.1 20251203, and riscv64-unknown-linux-ld (GNU ld (GNU Binutils) 2.43.1).
6.3.1. Absolute / PC-relative data relocations
Resolves to PLT[IFuncSymbol]. Sets HasDirectReference[IFuncSymbol] to true.
R_AARCH64_ABS{16, 32, 64}
R_RISCV_{32, 64}
R_ARM_ABS32
R_ARM_ABS32_NOI
R_ARM_TARGET1 (treated as R_ARM_ABS32)
The below relocations are currently unsupported by eld. For IFunc symbols, these should be resolved to the PLT[IFuncSymbol] as well.
R_AARCH64_PREL{16, 32, 64} (NotSupportedInGNULDForIFunc)
R_AARCH64_PLT32
R_RISCV_PLT32
R_ARM_ABS8
R_ARM_ABS16
R_ARM_PREL{16, 32, 64}
6.3.3. Control flow relocations
Resolves to PLT[IFuncSymbol].
R_AARCH64_TSTBR14
R_AARCH64_CONDBR19
R_AARCH64_JUMP26
R_AARCH64_CALL26
R_RISCV_CALL
R_RISCV_CALL_PLT
R_RISCV_JAL
R_RISCV_BRANCH
R_RISCV_RVC_BRANCH
R_RISCV_RVC_JUMP
R_ARM_CALL
R_ARM_JUMP24
R_ARM_PC24
R_ARM_PLT32
R_ARM_THM_CALL
R_ARM_THM_JUMP24
The below relocations should not be used with IFunc functions because these relocations are used with thumb instructions that does not support interworking (correctly transferring control between thumb-state and ARM-state) and PLT slot is typically always in the ARM state.
R_ARM_THM_JUMP6 (UNSUPPORTED) (NotSupportedInGNULDForIFunc)
R_ARM_THM_JUMP19
R_ARM_THM_JUMP11
R_ARM_THM_JUMP8
6.3.5. Regular absolute / PC-relative address-forming relocations
Resolves to PLT[IFuncSymbol]. Sets HasDirectReference[IFuncSymbol] to true.
R_AARCH64_ADR_PREL_LO21 (NotSupportedInGNULDForIFunc)
R_AARCH64_ADR_PREL_PG_HI21{_NC}
R_AARCH64_ADD_ABS_LO12_NC
R_RISCV_HI20
R_RISCV_PCREL_HI20
R_RISCV_LO12_I
R_RISCV_LO12_S
R_ARM_REL32
R_ARM_PREL31
R_ARM_MOVW_ABS_NC
R_ARM_MOVT_ABS
R_ARM_MOVW_PREL_NC
R_ARM_MOVT_PREL
R_ARM_THM_MOVW_ABS_NC
R_ARM_THM_MOVT_ABS
R_ARM_THM_MOVW_PREL_NC
R_ARM_THM_MOVT_PREL
The below relocations are resolved to the IFunc symbol:
R_AARCH64_MOVW_UABS_G{0,1,2,3}{_NC} (NotSupportedInGNULDForIFunc)
R_AARCH64_SABS_G{0,1,2,3} (NotSupportedInGNULDForIFunc)
[!IMPORTANT] FIXME: We should perhaps resolve these relocations to the PLT[IFuncSymbol] instead of the IFuncSymbol for ensuring pointer equality.
The below relocations are currently unsupported in eld:
R_AARCH64_MOVW_PREL_G{0, 1, 2, 3}{_NC}
6.3.6. Address forming relocations used to compute both GOT and non-GOT addresses
These are the relocations that can be used to form both GOT and non-GOT addresses.
The behavior of these relocations depend upon the corresponding Hi-relocation pair.
R_RISCV_PCREL_LO12_I
R_RISCV_PCREL_LO12_S
There is no relevant AArch64 relocation here.
6.3.7. Absolute / PC-relative load/store relocations
These relocations do not make sense with IFunc symbols. Loading a value at a function address is invalid behavior, and so is storing a value at a function address.
LD_PREL_LO19 (NotSupportedInGNULDForIFunc)
LDST{8, 16, 32, 64, 128}_ABS_LO12_NC (NotSupportedInGNULDForIFunc)
[!IMPORTANT] FIXME: It should be an error to use these relocations with IFunc symbols.