ubsan是llvm生态中专用于运行时检测未定义行为的工具,通过编译时插桩在潜在ub指令处插入检查逻辑,如整数溢出、空指针解引用、未对齐访问等,报错直接定位源码行,仅适用于开发测试阶段。

UBSan 是 LLVM 检测未定义行为的主力运行时工具
LLVM 本身不直接“检测”未定义行为(UB),它把这件事交给 compiler-rt 提供的 sanitizer 运行时库。其中 UndefinedBehaviorSanitizer(UBSan)是专为捕获 UB 设计的,编译时加 -fsanitize=undefined 就能启用。
它不是静态分析,也不是靠猜测——而是在生成的机器码里插桩,在每条可能触发 UB 的指令前后插入检查逻辑。比如 a + b 运算前,会先判断是否会导致有符号溢出;解引用指针前,会检查是否为空或未对齐。
常见错误现象包括:程序在不同环境表现不一致、优化后崩溃但 -O0 下正常、core dump 栈帧跳变严重——这些都可能是 UB 在作祟,而 UBSan 能让问题当场暴露。
- 必须用 Clang 或支持 UBSan 的 GCC(≥7.0)编译,GCC 对部分检查项支持不全
- 不能和 AddressSanitizer 同时开启全部检查项(如
alignment和address冲突),需显式排除:-fsanitize=undefined -fno-sanitize=alignment - 默认只启用一部分检查(如
signed-integer-overflow,shift,builtin),要开全得手动列:-fsanitize=undefined,null,return,signed-integer-overflow,vla-bound - 性能开销明显(通常 2–5 倍),仅用于开发/测试阶段,切勿上线
哪些 UB 能被 UBSan 实际捕获
UBSan 不是万能的,它只覆盖那些“可插桩、可判定”的运行时 UB 场景。LLVM IR 层的 poison 或 undef 值传播类问题,UBSan 无法感知——那是编译器优化阶段的事,得靠 Alive2 或 llvm-opt-report 分析。
它能抓到的典型问题包括:
-
signed-integer-overflow:如INT_MAX + 1,但对unsigned默认不报(C 标准定义为回绕) -
null:解引用空指针,如*ptr且ptr == nullptr -
alignment:访问未对齐地址,比如int*强转自char*偏移为 1 -
shift:左移负数、或移位量 ≥ 类型位宽,如1 (<code>int为 32 位时) -
builtin:调用__builtin_unreachable()后继续执行,或__builtin_assume(0)
注意:divide 检查只覆盖整数除零和 INT_MIN / -1,浮点除零不在此列(那是 IEEE 754 行为,非 UB)。
为什么加了 -fsanitize=undefined 还没报错
最常被忽略的一点:UBSan 默认**不检查未定义行为的间接后果**。比如某处整数溢出后,产生一个非法数组下标,再导致越界读——UBSan 只拦第一关(溢出),不会拦第二关(越界)。后者得靠 AddressSanitizer。
另一个现实限制是:UBSan 插桩依赖编译器生成的 IR 级别信息。如果代码被内联、被 LTO 全局优化掉,或者用了 __attribute__((no_sanitize("undefined"))) 掩盖,检查就会失效。
- 确保编译和链接都带
-fsanitize=undefined,否则运行时找不到 sanitizer 符号 - 禁用 LTO(
-flto)或改用-flto=thin,否则部分检查可能被优化穿透 - 避免在关键路径上用
__attribute__关闭检查,容易漏掉真实问题 - 检查
UBSAN_OPTIONS环境变量是否误设了abort_on_error=0或silence_deadly=1
比 UBSan 更早发现 UB 的方式:Clang 静态分析
UBSan 是运行时兜底,而 Clang 静态分析器(clang --analyze)能在不跑程序的情况下,沿控制流和数据流推演潜在 UB。它擅长发现空指针解引用、内存泄漏、未初始化变量读取等,但对整数溢出这类算术问题覆盖有限。
两者不是替代关系,而是互补:静态分析快、覆盖面广但有误报;UBSan 准、无误报但慢、只能覆盖可执行路径。
实际建议:
- CI 流程中先跑
clang --analyze扫一遍,快速拦截高危模式 - 单元测试和 fuzzing 阶段用 UBSan 编译,配合
libFuzzer输入变异,逼出深层 UB - 对历史遗留模块,优先打开
-fsanitize=undefined,null,signed-integer-overflow,再逐步加严
真正难缠的 UB 往往藏在边界条件组合里——比如年份累加 + 位移运算 + 指针算术三者嵌套。这时候光靠一种工具不够,得让静态分析、UBSan、ASan 在不同环节各守一关。











