getauxval can't cowork with ifunc

ghz 8months ago ⋅ 148 views

This question is derived from here, however, it is still a totally new question.

After the mentioned question, I have the following code. It can select between memcpy and my_memcpy by use_embed.

#include <stdio.h>
#include <string.h>
static int use_embed = 1;
void *my_memcpy (void *dst, const void *src, size_t len)
{
    printf("aaaaa\n");
    return dst;
}
static void * (*resolve_memcpy (void))(void *, const void *, size_t)
{
    if(use_embed == 1) {
        return memcpy;
    } else {
        return my_memcpy;
    }
}
void *memcpy2 (void *, const void *, size_t) __attribute__ ((ifunc ("resolve_memcpy")));
#define memcpy memcpy2
// extern void *memcpy (void *, const void *, size_t);
int main() {
    int source = 5;
    int dest = 6;
    memcpy(&dest, &source, sizeof(source));
    printf("%d %d\n", use_embed, dest);
    return 0;
}

However, I want to use getauxval to decide which version to be used, so I have the following code

#include <stdio.h>
#include <string.h>
#include <sys/auxv.h>

#ifndef HWCAP2_MTE
#define HWCAP2_MTE (1 << 18)
#endif
#ifndef HWCAP_SVE
#define HWCAP_SVE (1 << 22)
#endif
#ifndef AT_HWCAP2
#define AT_HWCAP2 26
#endif
#ifndef AT_HWCAP
#define AT_HWCAP 16
#endif
/// check if MTE is supported in current environment
static int mte_supported(void)
{
    return (getauxval(AT_HWCAP2) & HWCAP2_MTE);
}
/// check if SVE is supported in current environment
static int sve_supported(void)
{
    return (getauxval(AT_HWCAP) & HWCAP_SVE);
}

void *my_memcpy (void *dst, const void *src, size_t len)
{
    printf("aaaaa\n");
    return dst;
}
static void * (*resolve_memcpy (void))(void *, const void *, size_t)
{
    if(mte_supported() != 0) {
        return my_memcpy;
    }
    if(sve_supported() != 0) {
        return my_memcpy;
    }
    return my_memcpy;
}
void *memcpy2 (void *, const void *, size_t) __attribute__ ((ifunc ("resolve_memcpy")));
#define memcpy memcpy2
// extern void *memcpy (void *, const void *, size_t);
int main() {
    int source = 5;
    int dest = 6;
    memcpy2(&dest, &source, sizeof(source));
    printf("%d\n", dest);
    return 0;
}

It segfaults when built with clang15, for the same reason(related to PLT) mentioned in my previous question. To elaborate, the first jump in the PLT section for getauxval@plt points to neither _dl_runtime_resolve nor read access point to getauxval.

My question is:

  1. How to fix the code? I mean ifunc should have foreseen this problem. It must have already provide some solutions which I don't know.
  2. If there is no solution by ifunc, do we have some workarounds on the PLT level? Like LD_BIND_NOW or something.

Answers

The issue you're encountering arises from the fact that the ifunc mechanism relies on runtime resolution, which involves jumping through the PLT (Procedure Linkage Table). When resolving the ifunc, the dynamic linker needs to execute code to determine which function to use, and if this resolution process itself requires calls to external functions (such as getauxval), it can lead to a circular dependency and result in a segmentation fault.

To fix the code and avoid the segmentation fault, you can use a different mechanism for selecting the appropriate memcpy implementation based on runtime conditions. One approach is to use function pointers directly, without relying on ifunc. Here's how you can refactor your code to achieve this:

#include <stdio.h>
#include <string.h>
#include <sys/auxv.h>

#ifndef HWCAP2_MTE
#define HWCAP2_MTE (1 << 18)
#endif
#ifndef HWCAP_SVE
#define HWCAP_SVE (1 << 22)
#endif
#ifndef AT_HWCAP2
#define AT_HWCAP2 26
#endif
#ifndef AT_HWCAP
#define AT_HWCAP 16
#endif

void *my_memcpy (void *dst, const void *src, size_t len)
{
    printf("aaaaa\n");
    return memcpy(dst, src, len); // Use libc memcpy
}

void *(*resolve_memcpy(void))(void *, const void *, size_t)
{
    if (mte_supported() != 0 || sve_supported() != 0) {
        return my_memcpy;
    } else {
        return memcpy; // Use libc memcpy
    }
}

void *memcpy2(void *dst, const void *src, size_t len)
{
    void *(*selected_memcpy)(void *, const void *, size_t) = resolve_memcpy();
    return selected_memcpy(dst, src, len);
}

int main() {
    int source = 5;
    int dest = 6;
    memcpy2(&dest, &source, sizeof(source));
    printf("%d\n", dest);
    return 0;
}

In this version, memcpy2 directly calls resolve_memcpy to determine which memcpy implementation to use and then assigns the appropriate function pointer to selected_memcpy. Finally, selected_memcpy is called with the provided arguments.

This approach avoids the use of ifunc and relies on function pointers for runtime selection, which should prevent the segmentation fault caused by circular dependencies in the PLT.