[PATCH v3] RISC-V: Fix IFUNC resolver cannot access gp pointer

Yangyu Chen cyy@cyyself.name
Tue Jan 14 16:36:39 GMT 2025



On 1/8/25 03:12, Palmer Dabbelt wrote:
> On Tue, 07 Jan 2025 09:00:10 PST (-0800), jrtc27@jrtc27.com wrote:
>> On 7 Jan 2025, at 16:38, Florian Weimer <fweimer@redhat.com> wrote:
>>>
>>> * Palmer Dabbelt:
>>>
>>>> On Mon, 06 Jan 2025 18:32:54 PST (-0800), cyy@cyyself.name wrote:
>>>>> Ping again. It has already been 1 month after v3 patch was submitted.
>>>>>
>>>>> I think this patch is critical for RISC-V GCC Function multi- 
>>>>> versioning
>>>>> to work correctly, as GCC 15, which has this feature, is going to be
>>>>> released.
>>>>
>>>> IIUC the conclusion here was to avoid global symbol resolution in
>>>> IFUNCs and instead use the pre-resolved function hwprobe VDSO function
>>>> pointer passed in via a register.  There was a long discussion on this
>>>> during the hwprobe/IFUNC support patches, and IIRC one during the FMV
>>>> patches -- though I remember accepting those and now I'm not sure why,
>>>> so maybe I'm just forgetting something here?
>>>
>>> As far as I understand it, at least in some cases, the gp register needs
>>> to be set up for the main program even for local static/hidden symbol
>>> access.  Unfortunately, the ABI does not provide a completely reliable
>>> way for the dynamic linker to discover the gp value the main program
>>> requires, which leads to the code we have now.  I think we should have
>>> something that is more declarative than running a piece of assembler
>>> code at the right time (the existing code runs too late, after IFUNC
>>> resolver), and which does not depend on dynamic symbol resolution.
>>
>> And, notably, source code that makes no use of global variables can
>> still end up implicitly using them, e.g. for switch statements, which
>> you could imagine being used for hwprobe-using code in the case of keys
>> that have enum values, like misaligned scalar perf. Both GCC and Clang
>> optimise to using a lookup table for switch statements over an enum
>> with that many cases, which means a global access that could become
>> GP-relative with the right (wrong?) linker and memory layout. See
>> https://godbolt.org/z/KEbof5z7h for a minimal example of that, which is
>> completely valid and sensible (modulo bugs from lack of testing) code
>> that you can write today with hwprobe and that is broken if the lookup
>> table is laid out by GNU ld to be close enough to __global_pointer$.
> 
> Here's the code, in case the godbolt short link goes away:
> 
>     typedef unsigned long size_t;
>     #define NULL (void *)0
>     struct riscv_hwprobe {
>         signed long long int key;
>         unsigned long long int value;
>     };
>     typedef int (*__riscv_hwprobe_t) (struct riscv_hwprobe *__pairs, 
> size_t __pair_count,
>                       size_t __cpu_count, unsigned long int *__cpus,
>                       unsigned int __flags);
>     #define RISCV_HWPROBE_KEY_MISALIGNED_SCALAR_PERF    9
>     #define        RISCV_HWPROBE_MISALIGNED_SCALAR_UNKNOWN        0
>     #define        RISCV_HWPROBE_MISALIGNED_SCALAR_EMULATED    1
>     #define        RISCV_HWPROBE_MISALIGNED_SCALAR_SLOW        2
>     #define        RISCV_HWPROBE_MISALIGNED_SCALAR_FAST        3
>     #define        RISCV_HWPROBE_MISALIGNED_SCALAR_UNSUPPORTED    4
>     #define __used __attribute__((used))
>     #define __unused __attribute__((unused))
>     #define    DEFINE_UIFUNC(qual, ret_type, name, args)            \
>         static ret_type (*name##_resolver(unsigned long, 
> __riscv_hwprobe_t,    \
>         unsigned long, unsigned long, unsigned long, unsigned long,    \
>         unsigned long, unsigned long))args __used;            \
>         qual ret_type name args __attribute__((ifunc(#name 
> "_resolver")));    \
>         static ret_type (*name##_resolver(unsigned long hwcap 
> __unused,    \
>         __riscv_hwprobe_t hwprobe __unused, unsigned long _arg3 
> __unused,    \
>         unsigned long _arg4 __unused, unsigned long _arg5 __unused,    \
>         unsigned long _arg6 __unused, unsigned long _arg7 __unused,    \
>         unsigned long _arg8 __unused))args
>     void foo_unknown(void) {}
>     void foo_emulated(void) {}
>     void foo_slow(void) {}
>     void foo_fast(void) {}
>     void foo_unsupported(void) {}
>     DEFINE_UIFUNC(, void, foo, (void))
>     {
>         struct riscv_hwprobe pair;
>         pair.key = RISCV_HWPROBE_KEY_MISALIGNED_SCALAR_PERF;
>         if (hwprobe(&pair, 1, 0, NULL, 0) != 0)
>             goto unknown;
>         switch (pair.value) {
>         unknown:
>         default:
>         case RISCV_HWPROBE_MISALIGNED_SCALAR_UNKNOWN:
>             return foo_unknown;        case 
> RISCV_HWPROBE_MISALIGNED_SCALAR_EMULATED:
>             return foo_emulated;
>         case RISCV_HWPROBE_MISALIGNED_SCALAR_SLOW:
>             return foo_slow;
>         case RISCV_HWPROBE_MISALIGNED_SCALAR_FAST:
>             return foo_fast;
>         case RISCV_HWPROBE_MISALIGNED_SCALAR_UNSUPPORTED:
>             return foo_unsupported;
>         }
>     }
> 
> At least in GCC the docs for `-fjump-table` say to turn it off if you're in
> code that can't handle getting symbols references added, so if the rules 
> are to
> avoid resolving symbols in IFUNC resolvers then that's just user error 
> (though
> kind of a clunky one).  So I think GP is really just a red herring, here --
> sure it's one way this can break, but we went through months of other 
> reasons
> this can break when we decided not to allow them.
> 
> That said, I think there's an even bigger issue here: if we're expecting 
> users
> to return function pointers from the IFUNC resolver, then how are they 
> supposed
> to get those function pointers pre-relocation?  Even if it was easy to 
> get the
> compiler to only use PC-relative references, those would still just be 
> wrong.

I think this is a feature of IFUNC that should not reference any symbol 
that requires relocation. Luckily, the current C-API design and GCC 
implementation of FMV have already prevented this. Also, any 
user-provided IFUNC resolver should be aware of this. This is not an 
architecture-specific issue related to RISC-V.

Thanks,
Yangyu Chen

> 
> So unless I'm just lost here, I think we've got something else wrong?
> 
>> Jess



More information about the Libc-alpha mailing list