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

如何为Linux静态独立无LibC程序启用编译器Sanitizer?

在无LibC的Freestanding程序中使用Clang Sanitizer的问题

我正在开发一款Linux平台的静态独立(freestanding)无LibC/无标准库程序,希望借助C编译器的内存、地址及未定义行为Sanitizer优化代码,但尝试以下编译命令后无法正常运行:

clang -static -ffreestanding -nostdlib -fno-omit-frame-pointer -fsanitize=undefined -g -o program program.c

编译链接成功,但运行时因调用__ubsan_handle_type_mismatch_v1@plt等函数出现段错误,错误发生在自定义内存分配器中:

Program received signal SIGSEGV, Segmentation fault.
0x0000000000000000 in ?? ()
(gdb) up
(gdb) disas
Dump of assembler code for function lone_reallocate:
...
   0x00000000002116d0 <+160>:   bl      0x206470 <__ubsan_handle_pointer_overflow@plt>
=> 0x00000000002116d4 <+164>:   b       0x2116d8 <lone_reallocate+168>
   0x00000000002116d8 <+168>:   ldur    x8, [x29, #-72]
...

推测是缺少LibC导致Sanitizer相关函数缺失,添加-static-libsan选项后又出现大量未定义符号错误:

error: undefined symbol: __aarch64_cas8_acq_rel
error: undefined symbol: pthread_self
error: undefined symbol: dl_iterate_phdr
error: undefined symbol: abort
...

问题:

  1. 如何解决该问题?是否需要自行实现这些缺失的函数?
  2. 若需要自行实现,该如何实现?
  3. 操作系统和内核能否使用这些Sanitizer?若可以,其实现方法是否适用于我的场景?

解决方案与说明

1. 核心问题原因

Clang的Sanitizer(UBSan、ASan等)依赖大量LibC提供的基础功能,包括线程操作、动态链接器接口、原子操作、进程终止函数等。在-ffreestanding -nostdlib的环境下,这些依赖无法被满足,直接使用Sanitizer会导致要么运行时缺失处理函数(段错误),要么链接时缺失底层依赖符号。

2. 可行的解决路径

路径一:最小化实现Sanitizer依赖的核心函数

你需要自行实现Sanitizer要求的那些未定义符号,具体分为几类:

  • 原子操作函数(如__aarch64_cas8_acq_rel):这类是架构相关的原子指令封装,直接用内联汇编实现。比如ARM64下的CAS操作:

    #include <stdint.h>
    
    uint64_t __aarch64_cas8_acq_rel(uint64_t *ptr, uint64_t expected, uint64_t desired) {
        uint64_t result;
        __asm__ __volatile__(
            "casal %w[res], %w[des], [%[ptr]]"
            : [res] "=r" (result), [ptr] "+r" (ptr)
            : [des] "r" (desired), "[ptr]" (expected)
            : "memory"
        );
        return result;
    }
    
  • 线程相关函数(如pthread_self):如果你的程序是单线程的,直接返回一个固定值即可:

    typedef unsigned long pthread_t;
    pthread_t pthread_self(void) {
        return 1; // 单线程场景下固定返回一个非0值
    }
    
  • 动态链接器接口(如dl_iterate_phdr):Sanitizer用这个来遍历程序的段信息,你可以实现一个简化版本,只返回0表示没有更多段:

    int dl_iterate_phdr(int (*callback)(void *, void *, size_t), void *data) {
        return 0; // 告知Sanitizer没有更多段需要遍历
    }
    
  • 进程终止函数(如abort):实现一个简单的终止逻辑,调用Linux系统调用直接退出:

    void abort(void) {
        // Linux下ARM64的exit系统调用号是93
        __asm__ __volatile__(
            "mov x8, #93\n"
            "mov x0, #1\n"
            "svc #0"
        );
        while(1); // 防止系统调用失败后的执行
    }
    

将这些实现放在一个单独的文件(比如sanitizer_stubs.c)中,编译时和你的主程序一起链接:

clang -static -ffreestanding -nostdlib -fno-omit-frame-pointer -fsanitize=undefined -g -o program program.c sanitizer_stubs.c

路径二:使用Sanitizer的"陷阱模式"

UBSan支持通过-fsanitize-undefined-trap-on-error选项,将未定义行为的检查转换为陷阱指令(如ud2),而不是调用LibC中的处理函数。这种方式不需要依赖Sanitizer的运行时库,只会在触发未定义行为时直接崩溃,适合快速定位问题:

clang -static -ffreestanding -nostdlib -fno-omit-frame-pointer -fsanitize=undefined -fsanitize-undefined-trap-on-error -g -o program program.c

注意:这种方式只能检测问题并触发崩溃,不会给出详细的错误信息,适合初步排查。

3. 内核与Sanitizer的适配情况

Linux内核确实可以使用部分Sanitizer(如KASAN、UBSAN),但内核的实现是完全定制化的:

  • 内核有自己的原子操作、内存管理、线程(进程)模型,不需要依赖LibC。
  • 内核版Sanitizer是直接集成到内核源码树中的,使用内核自身的基础设施来实现检查和错误报告。

这种"基于自身基础设施实现Sanitizer依赖"的思路可以参考,但内核的Sanitizer代码不能直接复用——你的程序是用户态,而内核是内核态,运行环境和系统调用接口完全不同。


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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.03 20:00:32