可通过重写__asan_report_error函数自定义asan错误上报,需静态链接asan、严格匹配5参数签名,结合__sanitizer_print_stack_trace获取堆栈;对未插桩代码可用mmap(prot_none)保护内存页并捕获sigsegv,二者均需避免在处理中调用非异步信号安全函数。

用 __asan_report_error 捕获越界访问(ASan 运行时钩子)
AddressSanitizer(ASan)本身不提供可直接调用的“上报接口”,但它的运行时在检测到越界时会调用内部函数 __asan_report_error,并在退出前打印堆栈。你可以通过 LD_PRELOAD 注入自定义符号覆盖它,实现拦截——前提是编译时启用 -fsanitize=address 且链接 ASan 运行时(libasan)。
- 必须用
extern "C"声明,避免 C++ name mangling - 函数签名固定:
void __asan_report_error(void <em>addr, void </em>pc, void <em>bp, void </em>sp, bool is_write, unsigned long access_size) - 不建议在该函数里做复杂操作(如 malloc、std::string 构造),ASan 此时已处于异常上下文,堆可能不可靠
- 实际拦截需配合
objcopy --globalize-symbol=__asan_report_error或使用LD_PRELOAD+ dlsym 获取原函数地址再包装
extern "C" {
void __asan_report_error(void *addr, void *pc, void *bp, void *sp,
bool is_write, unsigned long access_size) {
// 记录 addr、access_size、调用栈(可用 backtrace())
// 注意:不要 throw、不要 new、不要 fprintf(stderr) 以外的 I/O
abort(); // 继续默认行为,或 longjmp 回安全点
}
}
手动插桩:在关键指针访问前加 __builtin_address_sanitizer_check?
没有这个 builtin。ASan 的检查是编译器自动插入的(如对数组下标、指针算术后的 load/store 插入影子内存校验),无法在源码中显式调用“检查函数”。想手动控制,只能:
- 用
__asan_address_is_valid(非公开 API,不保证稳定)判断某地址是否在合法映射范围内,但它不区分“越界读”和“未初始化读”,也不提供访问大小信息 - 更可靠的做法是封装自己的容器(如
safe_vector),在operator[]和at()中做边界断言,并记录__builtin_return_address(0) - 若需轻量级运行时检查,可用
mmap(MAP_ANONYMOUS|MAP_NORESERVE)预留 guard page,再用mincore()或信号 handler(SEGV)捕获非法访问
用 sigaction(SIGSEGV) 捕获非法内存访问
这是最通用、不依赖 ASan 的方式,但无法区分“越界读”和“空指针解引用”或“权限错误”,且部分越界(如 heap 内部越界)不会触发 SIGSEGV。
- 必须用
sigaction而非signal,确保 sa_flags 包含SA_SIGINFO | SA_RESTART - 在 signal handler 中仅允许异步信号安全函数(
write、siglongjmp、atomic操作),不能调用printf、malloc、std::cout - 可通过
siginfo_t->si_addr获取出错地址,结合/proc/self/maps判断是否落在 heap/data/bss 区域附近,辅助推测越界类型 - 若需还原调用栈,必须用
ucontext_t->uc_mcontext.gregs[REG_RIP](x86_64)并配合外部 symbolizer,不能依赖backtrace()
上报逻辑必须绕过 stdio 和堆分配
所有越界监控最终要落盘或发网络,但崩溃瞬间堆和 stdio 缓冲区都不可信。
- 日志写入应使用
open("/dev/shm/crash.log", O_WRONLY|O_APPEND|O_CLOEXEC)+write(),避免 stdio 层缓冲 - 上报地址、栈帧(从
ucontext或__builtin_frame_address提取)、模块基址(/proc/self/maps解析)即可,不用 JSON 或结构化序列化 - 若需进程外上报,推荐写一个最小 socket client(
socket(AF_UNIX)连接守护进程),发送二进制 blob,由守护进程负责落盘/转发
越界监控真正难的不是捕获,而是如何在崩溃上下文中安全地提取、传输信息——任何依赖运行时库或动态分配的操作,都可能让问题变得更糟。
C++免费学习笔记(深入):立即使用
在学习笔记中,你将探索 C++ 的入门与实战技巧!











