You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

clang15下getauxval与ifunc配合触发段错误的修复及Workaround咨询

问题背景

我最初写了一段代码,通过use_embed变量在标准memcpy和自定义my_memcpy之间切换:

#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;
}

之后我想通过getauxval检测系统特性来选择函数版本,写出了下面的代码:

#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
/// 检查当前环境是否支持MTE
static int mte_supported(void)
{
    return (getauxval(AT_HWCAP2) & HWCAP2_MTE);
}
/// 检查当前环境是否支持SVE
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;
}

用clang15编译这段代码时触发了段错误,原因和PLT相关:getauxval@plt段的第一个跳转既不指向_dl_runtime_resolve,也不指向getauxval的读访问点。

我的问题:

  1. 如何修复这段代码?ifunc机制应该预见到这类场景,肯定有对应的解决方案,但我还不清楚。
  2. 如果ifunc没有解决方案,PLT层面有没有可行的workaround?比如LD_BIND_NOW这类方法。

补充信息

clang --version
clang version 15.0.7 (https://github.com/llvm/llvm-project 8dfdcc7b7bf66834a761bd8de445840ef68e4d1a)
Target: x86_64-unknown-linux-gnu
Thread model: posix

cat /proc/version
Linux version 5.14.0-162.6.1.el9_1.x86_64 (mockbuild@dal1-prod-builder001.bld.equ.rockylinux.org) (gcc (GCC) 11.3.1 20220421 (Red Hat 11.3.1-2), GNU ld version 2.35.2-24.el9) #1 SMP PREEMPT_DYNAMIC Fri Nov 18 02:06:38 UTC 2022

解决方案

问题1:修复代码的正确方式

段错误的核心原因是:ifunc的 resolver 函数在程序启动早期执行,此时动态链接器还没完成PLT的延迟绑定初始化,直接调用getauxval这类需要PLT跳转的函数会触发非法访问。

GNU ifunc机制明确要求:resolver函数必须是**无副作用、仅依赖静态信息或直接读取ELF辅助向量(不通过libc包装函数)**的代码。所以不能直接调用getauxval,而是要自己直接读取辅助向量。

修改方法:绕过libc的getauxval包装,直接从进程的辅助向量链表中读取AT_HWCAP和AT_HWCAP2的值。代码如下:

#include <stdio.h>
#include <string.h>
#include <stdint.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

// 直接读取ELF辅助向量,避免调用libc的getauxval
static uint64_t get_auxval(unsigned long type) {
    extern char **environ;
    unsigned long *auxv = (unsigned long *)environ;
    // 跳过环境变量,找到辅助向量起始位置
    while (*auxv++ != 0);
    // 遍历辅助向量
    while (1) {
        unsigned long tag = *auxv++;
        unsigned long val = *auxv++;
        if (tag == 0) break; // AT_NULL结束
        if (tag == type) return val;
    }
    return 0;
}

/// 检查当前环境是否支持MTE
static int mte_supported(void)
{
    return (get_auxval(AT_HWCAP2) & HWCAP2_MTE);
}
/// 检查当前环境是否支持SVE
static int sve_supported(void)
{
    return (get_auxval(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 memcpy; // 修正原代码的错误,最后返回标准memcpy
}
void *memcpy2 (void *, const void *, size_t) __attribute__ ((ifunc ("resolve_memcpy")));
#define memcpy memcpy2

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

关键改动点:

  • 实现自定义的get_auxval,直接遍历辅助向量,不依赖libc的PLT绑定
  • 修正resolver函数的返回逻辑,原代码错误地全返回my_memcpy,最后应回退到标准memcpy

编译命令:clang -o ifunc_memcpy ifunc_memcpy.c,运行即可正常工作。

问题2:PLT层面的workaround

如果确实无法修改resolver代码,有两种临时workaround:

  1. 启用立即绑定:运行程序时设置LD_BIND_NOW=1,强制动态链接器在程序启动前完成所有PLT绑定,这样resolver调用getauxval时PLT已经初始化完成:

    LD_BIND_NOW=1 ./your_program
    
  2. 静态编译:使用-static参数编译,直接把libc的代码链接到程序中,避免PLT延迟绑定:

    clang -static -o ifunc_memcpy_static ifunc_memcpy.c
    

    注意:静态编译会导致程序体积变大,且某些系统特性可能受影响。


内容的提问来源于stack exchange,提问作者calvin

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.26 18:07:32