LLVM函数Pass替换read为klee_make_symbolic的报错与崩溃问题
解决LLVM Pass替换read为klee_make_symbolic的两个核心问题
我来帮你拆解你遇到的这两个问题——它们分别对应LLVM IR的作用域规则和KLEE符号化函数的调用细节,咱们一步步解决:
第一个问题:"Referring to an argument in another function"报错
这个错误的根源很明确:你错误引用了read函数的形参,而非当前调用指令的实参。
LLVM中,每个函数的Argument对象属于该函数的作用域,你不能直接把read函数定义里的getArg(1)(比如buffer形参)拿来给klee_make_symbolic用——这相当于跨函数引用局部变量,LLVM的验证器会直接报错终止编译。
正确的做法是从**read的调用指令(CallInst)**中提取实参:
// 错误示例:引用了read函数的形参 Function* readFunc = M.getFunction("read"); Value* buffer = readFunc->getArg(1); // 正确示例:从当前CallInst中取实参 CallInst* ci = dyn_cast<CallInst>(&I); Value* buffer = ci->getArgOperand(1); // 取read调用的第二个实参(buffer)
第二个问题:插桩后程序崩溃,手动替换却正常
虽然IR看起来无异常,但崩溃大概率是类型不匹配或符号化参数的细节处理不到位导致的,以下是排查和修复要点:
1. 严格匹配klee_make_symbolic的函数签名
klee_make_symbolic的C原型是:
void klee_make_symbolic(void *addr, size_t nbytes, const char *name);
对应LLVM IR的类型要求:
- 第一个参数:必须是
i8*(void*的LLVM等价类型) - 第二个参数:必须是
i64(64位系统下size_t的类型) - 第三个参数:必须是指向全局字符串常量的
i8*
如果你直接传递read的原始参数,很可能出现类型不兼容:
- read的buffer可能是
i32*/i64*等其他指针类型,需要转成i8* - read的size参数可能是
i32,需要零扩展为i64
2. 正确创建符号名称的全局字符串
KLEE要求第三个参数是一个有效的字符串常量指针,如果你随便传一个局部指针或未初始化的值,会直接导致崩溃。必须创建全局字符串常量并做bitcast转换:
// 创建符号名称的全局字符串 std::string symName = "sym_buffer_" + std::to_string(ci->getDebugLoc().getLine()); Constant* symStr = ConstantDataArray::getString(M.getContext(), symName); GlobalVariable* symStrGV = new GlobalVariable( M, symStr->getType(), true, GlobalValue::PrivateLinkage, symStr, ".str" ); Value* symStrPtr = ConstantExpr::getBitCast(symStrGV, Type::getInt8PtrTy(M.getContext()));
3. 完整的正确Pass代码片段
结合以上要点,修复后的核心代码如下:
bool runOnFunction(Function &F) override { bool modified = false; Module &M = *F.getParent(); // 声明klee_make_symbolic函数 FunctionType* kleeFuncType = FunctionType::get( Type::getVoidTy(M.getContext()), {Type::getInt8PtrTy(M.getContext()), Type::getInt64Ty(M.getContext()), Type::getInt8PtrTy(M.getContext())}, false ); FunctionCallee kleeFunc = M.getOrInsertFunction("klee_make_symbolic", kleeFuncType); // 遍历所有指令 for (auto &BB : F) { for (auto I = BB.begin(); I != BB.end();) { CallInst* ci = dyn_cast<CallInst>(&*I); ++I; // 先递增迭代器,避免删除指令后失效 if (!ci) continue; Function* callee = ci->getCalledFunction(); if (!callee || callee->getName() != "read") continue; // 提取read的实参 Value* buffer = ci->getArgOperand(1); Value* size = ci->getArgOperand(2); // 转换buffer为i8* Value* bufferI8 = CastInst::CreatePointerCast( buffer, Type::getInt8PtrTy(M.getContext()), "buffer_i8", ci ); // 转换size为i64(如果是i32的话) Value* sizeI64 = size; if (size->getType()->isIntegerTy(32)) { sizeI64 = CastInst::CreateZExt( size, Type::getInt64Ty(M.getContext()), "size_i64", ci ); } // 创建符号名称字符串 std::string symName = "sym_" + F.getName().str() + "_" + std::to_string(ci->getDebugLoc().getLine()); Constant* symStr = ConstantDataArray::getString(M.getContext(), symName); GlobalVariable* symStrGV = new GlobalVariable( M, symStr->getType(), true, GlobalValue::PrivateLinkage, symStr, ".sym_str" ); Value* symStrPtr = ConstantExpr::getBitCast(symStrGV, Type::getInt8PtrTy(M.getContext())); // 插入klee_make_symbolic调用 CallInst::Create(kleeFunc, {bufferI8, sizeI64, symStrPtr}, "", ci); // 删除原read调用 ci->eraseFromParent(); modified = true; } } return modified; }
4. 额外排查步骤
如果还是崩溃,可以做这些检查:
- 对比手动替换的IR和Pass生成的IR,逐行检查参数类型、字符串常量定义是否完全一致
- 给KLEE添加
-debug参数,查看崩溃时的调用栈和详细日志,定位具体出错环节 - 确认原read调用已经被完全删除,避免程序先执行read再符号化导致内存状态混乱
内容的提问来源于stack exchange,提问作者funnypig run
相关产品推荐
相关产品推荐

