AddressSanitizer(ASan)可快速捕获越界读写,因其采用编译期插桩与影子内存机制,触发时立即崩溃并输出带源码行号的调用栈;需编译时加-fsanitize=address -g -O1,禁用于生产环境。

用 AddressSanitizer 快速捕获越界读写
AddressSanitizer(ASan)是 Clang 和 GCC 内置的内存错误检测器,能实时发现数组越界、栈/堆缓冲区溢出、释放后使用等行为,且无需修改代码。它通过插桩+影子内存映射实现,开销约 2×,但开发阶段足够实用。
编译时加 -fsanitize=address -g 即可启用:
g++ -fsanitize=address -g -O1 main.cpp -o main
运行时一旦触发越界,会直接打印带调用栈的错误信息,例如:
ERROR: AddressSanitizer: heap-buffer-overflow on address 0x60200000001c at pc 0x0000004012ab bp 0x7ffccf9a8e90 sp 0x7ffccf9a8e88
- 必须保留
-g,否则无法显示源码行号 -
-O1是推荐优化等级;-O2及以上可能因优化导致部分越界被隐藏或误报 - 不支持静态链接
libasan的场景(如某些嵌入式交叉编译),需确认工具链是否启用 ASan 支持
在 Release 模式下嵌入轻量级越界检查逻辑
ASan 不能用于生产环境,若需上线后监控,得靠手动防护。核心思路是:对关键动态数组(如 std::vector、裸 new[])封装访问操作,插入边界断言或日志上报。
立即学习“C++免费学习笔记(深入)”;
例如重载 operator[] 的安全 wrapper:
template<typename T>
class SafeArray {
T* data_;
size_t size_;
public:
SafeArray(size_t n) : data_(new T[n]), size_(n) {}
~SafeArray() { delete[] data_; }
T& operator[](size_t i) {
if (i >= size_) {
report_out_of_bounds("SafeArray::operator[]", i, size_);
__builtin_trap(); // 或 throw / abort
}
return data_[i];
}
};
- 避免在循环内频繁检查——可只在 debug build 中启用
if (i >= size_),用#ifdef DEBUG控制 -
report_out_of_bounds应设计为异步、无锁、不分配内存(比如写入预分配 ring buffer + signal-safe syscall 如write) - 不要试图拦截所有指针算术(如
p + 10),成本高且不可靠;聚焦明确容器和已知大小的缓冲区
利用硬件特性捕获非法地址访问(Linux x86_64)
Linux 下可通过 mmap 配合 PROT_NONE 页面,在已知缓冲区前后设置“警戒页”(guard page)。一旦越界踩到这些页,会触发 SEGV_MAPERR 信号,可在 signal(SIGSEGV, ...) 中捕获并记录上下文。
典型做法:
void* buf = mmap(nullptr, size + 2*4096, ..., MAP_PRIVATE|MAP_ANONYMOUS, -1, 0); mprotect((char*)buf + 4096, size, PROT_READ|PROT_WRITE); // 中间可读写 mprotect(buf, 4096, PROT_NONE); // 前警戒页 mprotect((char*)buf + 4096 + size, 4096, PROT_NONE); // 后警戒页
- 仅对大块内存有效(小数组不值得,且易受内存碎片影响)
- 必须确保访问前未被其他
mmap占用该地址范围;可用MAP_FIXED_NOREPLACE(Linux 5.17+)提升可靠性 -
SIGSEGV处理函数中禁止调用malloc、printf等非 async-signal-safe 函数;推荐用write直接写文件或 socket
为什么不能依赖 try/catch 捕获越界异常
C++ 标准规定,数组越界、空指针解引用、野指针访问等均属未定义行为(UB),**不会抛出 std::exception 子类**。任何试图用 try/catch(...) 拦截这类错误的做法都是徒劳的。
-
std::out_of_range仅由at()方法主动抛出,不是硬件层面的保护机制 - 编译器可能因 UB 删除整段逻辑(如循环优化),导致“没报错却结果错”
- 即使程序看似“正常运行”,越界也可能悄悄破坏相邻对象、vtable 或 malloc 元数据,后续才崩溃——这时堆栈已失真
真正的越界监控永远发生在编译期(静态分析)、加载期(ASan 插桩)或运行期(内存保护页、手动检查),而不是异常处理层。
文章来自机圈观察员网,发布者:,转载请注明出处:https://www.jqgcy.com/jiquanzatan/124078.html