使用LLVM C++ API操作结构体指针时函数查找段错误问题
LLVM JIT调用结构体指针函数时的段错误问题
我想为特定用途开发一门专用语言,本质是一组可被C代码调用的函数,需要传递C结构体指针。为验证自己对LLVM结构体指针操作的掌握程度,我写了一个给结构体int成员加2的程序,但在JIT查找函数时持续触发段错误,LLVM IR还没修正。
问题代码与环境
结构体定义
struct test_struct { char *b; int *c_dummy; int c; double f ; }; struct test_struct tt{0,0,1,0};
LLVM初始化代码
InitializeNativeTarget(); InitializeNativeTargetAsmPrinter(); InitializeNativeTargetAsmParser(); TheJIT = ExitOnErr(KaleidoscopeJIT::Create()); TheContext = std::make_unique<LLVMContext>(); TheModule = std::make_unique<Module>("my cool jit", *TheContext); TheModule->setDataLayout(TheJIT->getDataLayout()); Builder = std::make_unique<IRBuilder<>>(*TheContext); TheFPM = std::make_unique<legacy::FunctionPassManager>(TheModule.get()); TheFPM->add(createCFGSimplificationPass()); TheFPM->doInitialization(); // create the value 2 to add later. Value *v_const = ConstantInt::get(*TheContext,APInt(32,2,true)); // The return type of the function: Type *r_type = Type::getVoidTy(*TheContext);
函数与IR构建代码
ArrayRef<Type *> params = {Type::getInt8PtrTy(*TheContext),Type::getInt32PtrTy(*TheContext), Type::getInt32Ty(*TheContext),Type::getDoubleTy(*TheContext)}; StructType *st = StructType::create(*TheContext,"s"); st->setBody(params); /* Define function.*/ ArrayRef<Type *> params_func = ArrayRef<Type *>(st); FunctionType *ft = FunctionType::get(r_type,params_func,false); Function *F = Function::Create(ft,Function::ExternalLinkage,"test",*TheModule); // add a function to the module Value *c; BasicBlock *bb = BasicBlock::Create(*TheContext,"entry",F); Builder->SetInsertPoint(bb); Value *mem= Builder->CreateAlloca(st,nullptr); // This should allocate the param to stack. Value *mem_int= Builder->CreateAlloca(Type::getInt32Ty(*TheContext),nullptr); // create a stac allocation for st. for (auto arg = F->arg_begin();arg != F->arg_end();arg++) { // bring st from the argument. Builder->CreateStore(arg,mem); // store rhe struct pointer in the stack. auto tmp_ar = arg; c = Builder->CreateStructGEP(st,mem,2); // take the second element from the pointer. } /* Store the argument in the stack*/ mem_int = Builder->CreateLoad(st,c); // perform add Value *res = Builder->CreateAdd(mem_int,v_const,"add tmp"); // close of. Builder->CreateStore(res, mem_int); Builder->CreateRetVoid(); /// added a basic block and returned void. TheModule->dump(); auto RT = TheJIT->getMainJITDylib().createResourceTracker(); auto TSM = ThreadSafeModule(std::move(TheModule),std::move(TheContext)); ExitOnErr(TheJIT->addModule(std::move(TSM),RT)); F->print(errs()); TheJIT->dumpEE(); do_test();
测试函数
void do_test() { auto fsym = ExitOnErr(TheJIT->lookup("test")); void (*add_func)(struct test_struct *) = (void (*)(struct test_struct *))(intptr_t)fsym.getAddress(); printf("Before func: %d\n", tt.c); add_func(&tt); // this should change t.c printf("After func: %d\n", tt.c); }
编译命令
g++ -g test_ptr.cpp `llvm-config --cxxflags --ldflags --system-libs --libs core orcjit native` -O0 -o test_ptr
错误原因分析
函数参数类型不匹配
C端传递的是struct test_struct*,但LLVM中定义的函数参数是s类型(结构体值,而非指针),这导致调用约定完全不匹配,JIT加载后调用时栈结构混乱,直接触发段错误。内存操作逻辑错误
- 不必要的栈分配:代码中用
CreateAlloca分配结构体栈空间,但实际上应该直接操作传入的结构体指针,无需额外拷贝。 - Load类型错误:
c是结构体成员c的指针(int*),但CreateLoad时传入的类型是结构体st,类型不匹配会导致IR非法,运行时崩溃。
- 不必要的栈分配:代码中用
悬空指针访问
将TheModule和TheContext通过std::move转移给ThreadSafeModule后,原指针F(属于原模块)变为悬空指针,后续调用F->print(errs())会触发未定义行为,也是段错误的潜在诱因。
修正后的代码
修正结构体与函数定义
// 匹配C结构体的名称和布局,确保内存对齐一致 StructType *st = StructType::create(*TheContext, "test_struct"); st->setBody({ Type::getInt8PtrTy(*TheContext), Type::getInt32PtrTy(*TheContext), Type::getInt32Ty(*TheContext), Type::getDoubleTy(*TheContext) }, false); // 默认非packed,和C结构体对齐规则一致 // 函数参数改为结构体指针,匹配C端调用签名 ArrayRef<Type *> params_func = {st->getPointerTo()}; FunctionType *ft = FunctionType::get(r_type, params_func, false); Function *F = Function::Create(ft, Function::ExternalLinkage, "test", *TheModule);
修正IR函数体
BasicBlock *bb = BasicBlock::Create(*TheContext, "entry", F); Builder->SetInsertPoint(bb); // 获取传入的结构体指针参数 Argument *structPtr = &*F->arg_begin(); structPtr->setName("struct_ptr"); // 直接通过指针访问第3个成员(索引从0开始,c对应索引2) Value *cPtr = Builder->CreateStructGEP(st, structPtr, 2, "c_ptr"); // 加载成员当前值 Value *cVal = Builder->CreateLoad(Type::getInt32Ty(*TheContext), cPtr, "c_val"); // 执行加2操作 Value *newCVal = Builder->CreateAdd(cVal, v_const, "new_c_val"); // 将结果存回结构体成员 Builder->CreateStore(newCVal, cPtr); Builder->CreateRetVoid();
修正模块处理逻辑
// 先打印模块内容,再转移模块 TheModule->dump(); F->print(errs()); auto RT = TheJIT->getMainJITDylib().createResourceTracker(); auto TSM = ThreadSafeModule(std::move(TheModule), std::move(TheContext)); ExitOnErr(TheJIT->addModule(std::move(TSM), RT)); TheJIT->dumpEE(); do_test();
关键修正点总结
- 对齐LLVM结构体与C结构体的名称、成员类型和内存布局,确保调用时内存结构一致
- 将LLVM函数参数改为结构体指针,完全匹配C端的函数签名
- 直接操作传入的结构体指针,避免不必要的栈分配和拷贝
- 修正Load/Store的类型匹配错误,保证IR合法性
- 避免在模块转移后访问原模块的悬空指针
内容的提问来源于stack exchange,提问作者Igor de Paula
相关产品推荐
相关产品推荐

