LLVM pass如何生成空指针校验IR,避免解引用空指针打印参数出错
LLVM Pass指针参数空校验IR生成实现
现存开发场景
- 开发目标:实现自动打印函数参数值的LLVM Pass,当前支持的类型包括整数类型、指向整数的多层指针类型(兼容char类型,不改动现有业务逻辑)
- 已落地的非空场景逻辑:
- LLVM会为每个函数参数通过
alloca分配栈空间,通过对应StoreInst将传入的参数值写入栈地址;Pass依托这一初始化流程定位待处理的参数 - 自研
getIntegerType、getPointerToIntegerType两个辅助函数,可快速匹配对应位宽、对应间接层级的整数、指针类型 - 对指针类型参数,通过IRBuilder逐层生成
LoadInst完成多层解引用,拿到最终整数值后,调用自定义__print_argument_value函数完成打印
- LLVM会为每个函数参数通过
- 现存问题:上述指针解引用逻辑未做空指针防护,遇到空指针入参时直接触发非法访问崩溃
- 已完成的前置调研:参考普通C语言空指针判断生成的原生LLVM IR,明确核心逻辑为通过
icmp ne指令将指针与空常量比较,结果为真时跳转至分支块执行打印逻辑,为假时直接跳过;但尚未掌握Pass中生成这类带条件分支IR的API组合方式,也不确定PHI节点在当前场景的适用性 - 开发者背景:日常以Java开发为主,C/C++编写经验相对不足
可直接落地的IR生成步骤
1. 提前创建流程所需的三个基本块
在目标函数内创建三个独立的BasicBlock,分别对应三个流程节点:
- 校验决策块:承接原有执行流,完成空指针比较、跳转决策
- 非空执行块:承载原有解引用、调用打印函数的逻辑
- 流程汇合块:承接两个分支的跳转,作为原有后续指令流的执行入口
操作提示:需要将原有的、位于空校验插入点之后的所有指令,整体迁移到流程汇合块中,断开原有执行流的线性顺序,避免逻辑错乱
2. 生成空指针比较指令
将IRBuilder的插入点定位到校验决策块,完成比较指令生成:
- 取待校验的指针值(即原本要做解引用操作的指针对应的
Value*句柄) - 生成对应类型的空指针常量:通过
ConstantPointerNull::get(cast<PointerType>(待校验指针的类型))获取,保证指针类型匹配 - 生成不等比较指令:调用
irb.CreateICmpNE(待校验指针, 对应空指针常量, "null_check"),得到i1类型的布尔结果,代表"指针值不等于空"
3. 生成条件跳转逻辑
基于上一步得到的比较结果,调用irb.CreateCondBr生成跳转逻辑:
- 比较结果为真(指针非空)时,跳转至非空执行块
- 比较结果为假(指针为空)时,直接跳转至流程汇合块
4. 迁移原有打印逻辑、补全执行流
- 将之前已验证可正常运行的、多层解引用+调用
__print_argument_value的所有IR生成代码,移动到非空执行块的插入点位置生成 - 非空执行块的所有逻辑生成完成后,在块末尾插入一条无条件跳转指令,跳转回流程汇合块,保证执行流不会中断
PHI节点适用性说明
当前场景完全不需要插入PHI节点:
- PHI节点的核心作用是在分支汇合块中,根据不同来源的跳转路径,选择对应路径计算得到的变量值,供后续代码使用
- 当前逻辑中,非空执行块运行的是有副作用的打印函数调用,不向汇合块传递任何需要后续复用的计算值;两个分支跳转回汇合块后,执行的是完全一致的原有业务逻辑,不存在需要按分支选择变量值的场景,因此无使用PHI的必要
常见踩坑注意事项
- 空指针常量必须与待校验指针的类型完全匹配,不能混用不同地址空间、不同间接层级的空指针值,否则会触发LLVM IR校验失败
- 所有分支路径必须有明确的跳转终点,不能遗漏非空执行块到汇合块的无条件跳转,否则会生成不可达的孤立块,触发Pass校验错误
- 如果业务需要对多层指针做全链路空校验,只需要在每一层
Load得到下一级指针值后,重复一遍上述空校验+分支的生成逻辑即可
内容的提问来源于stack exchange,提问作者Vivin Paliath
相关产品推荐
相关产品推荐

