Clang15编译代码出现段错误的原因分析与修复方案问询
x86平台Clang 15/17编译含ifunc的代码触发段错误
以下代码在x86平台使用Clang 15/17编译运行时会出现段错误:
#include <stdbool.h> #include <stddef.h> #include <stdint.h> #include <string.h> #include <sys/auxv.h> // Provide default macro definitions in case that they are not defined on current linux distro. // For example, TiFlash compiled on older linux kernels may also be used in newer ones. // These values should be stable for Linux: only false negative is expected when running on // older kernels, but it is acceptable as `google/cpu_features` is also doing so. #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 inline bool mte_supported(void) { return (getauxval(AT_HWCAP2) & HWCAP2_MTE) != 0; } /// check if SVE is supported in current environment static inline bool sve_supported(void) { return (getauxval(AT_HWCAP) & HWCAP_SVE) != 0; } #define STRINGIFY_IMPL(X) #X #define STRINGIFY(X) STRINGIFY_IMPL(X) /** * \brief * Symbol is defined as hidden visibility. Therefore, implementations here are only to override routines with TiFlash * binary itself. This is because dependencies like `ld.so`, `libgcc_s.so`, etc will need essential routines like * `memcpy` to finish the early loading procedure. Therefore, declare such symbols as visible indirect function will * create cyclic dependency. It shall be good enough to override symbols within TiFlash, as most heavy computation works * are happening in the main binary. * \param NAME: exported symbol name * \param SVE: preferred implementation when SVE is available * \param MTE: preferred implementation when MTE is available * \param ASIMD: preferred implementation for generic aarch64 targets (ASIMD is required by default for Armv8 and above) */ #define DISPATCH(NAME, SVE, MTE, ASIMD) \ extern typeof(ASIMD) __tiflash_##NAME __attribute__((ifunc(STRINGIFY(__tiflash_##NAME##_resolver)))); \ extern typeof(ASIMD) NAME __attribute__((visibility("hidden"), alias(STRINGIFY(__tiflash_##NAME)))); \ _Pragma("GCC diagnostic push") \ _Pragma("GCC diagnostic ignored \"-Wunused-function\"") static typeof(ASIMD) * __tiflash_##NAME##_resolver(void) \ { \ if (sve_supported()) \ { \ return SVE; \ } \ if (mte_supported()) \ { \ return MTE; \ } \ return ASIMD; \ } \ _Pragma("GCC diagnostic pop") #undef memcpy DISPATCH(memcpy, memcpy, memcpy, memcpy) int main() { char source[] = "once upon a daydream...", dest[4]; memcpy(dest, source, sizeof dest); return 0; }
已做的测试与观察
- Clang 13及GCC编译运行无段错误;
- Clang 15/17编译运行触发段错误;
- 设置
LD_BIND_NOW=1运行时无段错误; - 移除
extern typeof(ASIMD)相关代码后可正常运行,确认问题与ifunc机制相关。
调试细节:Clang15/17中getauxval通过PLT解析(调用getauxval@plt),而GCC或Clang13中直接调用。查看getauxval@plt的汇编后,预期其GOT表项应为PLT后续指令地址或getauxval真实地址,但实际调试显示为0x1936,完全不符合预期。
待解答问题
0x1936的来源是什么?推测与ifunc或动态库加载顺序有关,但难以定位根源;- 如何修复代码?因
LD_BIND_NOW=1在ARM平台无效。
补充内容1:测试GCC文档示例未达预期
按GCC文档编写如下测试代码,未输出预期的"aaaaa":
#include <stdio.h> 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) { return my_memcpy; } void *memcpy (void *, const void *, size_t) __attribute__ ((ifunc ("resolve_memcpy"))); extern void *memcpy (void *, const void *, size_t); int main() { char source[] = "once upon a daydream...", dest[4]; memcpy(dest, source, sizeof dest); return 0; }
补充内容2:能否移除#define memcpy memcpy2?
现有代码通过宏替换将memcpy指向自定义的ifunc符号memcpy2,询问是否可以移除该宏:
#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; }
核心需求
- 能否从C库导入
memcpy并命名为memcpy_origin,同时重定义非宏类型的memcpy,使其可调用memcpy_origin或自定义my_memcpy? - 能否在动态库加载阶段,决定是使用C库的
memcpy还是自定义实现?
内容的提问来源于stack exchange,提问作者calvin
相关产品推荐
相关产品推荐

