目录
问题现象
本地编写的demo, 在多线程组装和运行JIT函数的过程中产生崩溃问题。崩溃信息为
/xxx/llvm6.0/llvm/lib/ExecutionEngine/RuntimeDyld/RuntimeDyldELF.cpp:383:
void llvm::RuntimeDyldELF::resolveAArch64Relocation(const llvm::SectionEntry&, uint64_t,
uint64_t, uint32_t, int64_t):
Assertion `static_cast<int64_t>(Result) >= (-2147483647-1) &&
static_cast<int64_t>(Result) <= (4294967295U)' failed.
Llvm库编译方式为: Release+Assert断言版本
问题分解思维导图
工具说明
无
问题定位
问题是否必现?
跑产品集成测试环境,问题是必现的。每一次的崩溃问题堆栈都相同。
问题复现的场景是什么?
跑产品集成测试环境是1000并发,每一个并发线程组装和运行JIT函数100次。
是否可以提取成demo, 降低复杂度?
把JIT相关组装和使用的简化逻辑提取出来编写成demo,并且模拟环境1000并发,组装JIT函数100次的场景。问题得到复现。
崩溃的位置,处理的是什么逻辑?
处理ARM环境下,JIT重定向功能中 .eh_frame段的重定向功能。".eh_frame"段是用于异常处理,包含异常处理过程需要的帧信息。
出现问题的代码,影响的因素有哪些?
".eh_frame段"的重定向功能影响因素是编译器的大小代码模型。
问题原因分析
问题一、当前编译器默认使用小代码模型,在jit的重定位模块resolveAArch64Relocation函数case ELF_AARCH64_PREL32分支中会计算代码段和.eh_frame段的重定位地址之前的差距,相差超过+/- 2G会触发断言,需要使用大代码模型解除该限制。如下图所示
问题二、当编译器使用大代码模型后,.eh_frame段的地址还是使用4bytes地址,是LLVM源码的bug,导致大代码模型无法生效。
解决问题的方案是什么?
问题一的解决方案分两种
<1> 编译的bc文件需要重新定义bc文件中的大代码模型。
clang++ -c -emit-llvm xx.cpp -o xx.bc -mcmodel=large
PS:
-mcmodel=large //是设置编译器使用大代码模型
<2> 如果程序中手动组装JIT函数则需要设置
EngineBuilder类
EngineBuilder engineBuilder(xxx)
engineBuilder.setCodeModel(CodeModel::Large) //是设置编译器使用大代码模型
问题二的解决方案
在生成目标机器码的接口中,添加AArch64分支,通过判断代码模型分别对应使用4/8字节重定位地址。这样使用大代码模型时生成的.eh_frame重定位类型不会有2G的限制,且该修改只针对.eh_frame段。
修改lib/MC/MCObjectFileInfo.cpp文件,如下图所示
源码
程序不保证能够运行,看个样子就行,代码仅仅是示意作用。可以自己本地参考编写。
#include "llvm_header.h"
#include <sstream>
typedef llvm::IRBuilder<> LlvmIRBuilder;
class LlvmEntry {
public:
LlvmEntry() {
m_module = NULL;
m_llvm_main_func = NULL;
m_llvm_context = NULL;
m_execution_engine = NULL;
}
~LlvmEntry() {}
void initialize(const std::string& name,
std::unique_ptr<llvm::Module> mod,
llvm::LLVMContext * context) {
m_llvm_context = context;
m_module = mod.get();
std::string errorStr;
llvm::EngineBuilder engineBuilder(std::move(mod));
engineBuilder.setEngineKind(llvm::EngineKind::JIT);
engineBuilder.setErrorStr(&errorStr);
engineBuilder.setOptLevel(llvm::CodeGenOpt::Aggressive);
// engineBuilder.setCodeModel(CodeModel::Large); // 设置大代码模型
m_execution_engine = engineBuilder.create();
if (NULL == m_execution_engine) {
std::stringstream sstr;
sstr << "[LlvmEntry::initialize] LLVM ExecutionEngine is null"
<< ", because [" << errorStr << "]";
std::cout << sstr.str().c_str() << std::endl;
return;
}
m_builder = new LlvmIRBuilder(*m_llvm_context);
}
void release() {
if (m_llvm_context) {
delete m_llvm_context;
m_llvm_context = NULL;
}
if (m_execution_engine) {
delete m_execution_engine;
m_execution_engine = NULL;
}
if (m_builder) {
delete m_builder;
m_builder = NULL;
}
}
llvm::LLVMContext* getLlvmContext() const {
return m_llvm_context;
}
llvm::Module* getLlvmModule() const {
return m_module;
}
void setLlvmModule(llvm::Module* mod) {
m_module = mod;
}
llvm::ExecutionEngine* getLlvmExecutionEngine() const {
return m_execution_engine;
}
void setLlvmExecutionEngine(llvm::ExecutionEngine* engine) {
m_execution_engine = engine;
}
LlvmIRBuilder* getLlvmBuilder() const {
return m_builder;
}
private:
llvm::LLVMContext* m_llvm_context;
llvm::Module* m_module;
llvm::ExecutionEngine* m_execution_engine;
llvm::Function* m_llvm_main_func;
LlvmIRBuilder* m_builder;
};
#include "llvm_header.h"
#include "llvm_entry.h"
#include <sstream>
#include "llvm/Support/Debug.h"
#include "type.h"
std::unique_ptr<llvm::Module> loadBC(llvm::LLVMContext& context) {
SMDiagnostic Err;
std::unique_ptr<llvm::Module> main_module = parseIRFile("static_lib.bc", Err, context);
if (!main_module) {
Err.print("[parseBitcodeFile] fail ! -> ", errs());
}
return main_module;
}
void createJitFunctionDefine(llvm::IRBuilder<> *ir_builder, llvm::Module* mod, LLVMContext& context, int64_t idx) {
std::vector<Type*>FuncTy_0_args;
FunctionType* FuncTy_0 = FunctionType::get(
/*Result=*/Type::getVoidTy(context),
/*Params=*/FuncTy_0_args,
/*isVarArg=*/false);
std::ostringstream ss;
ss << "my_jit_func" << idx;
llvm::Function* fn = llvm::Function::Create(
FuncTy_0, llvm::GlobalValue::ExternalLinkage, ss.str().c_str(), mod);
if (ir_builder != NULL) {
llvm::BasicBlock* entry_block =
llvm::BasicBlock::Create(context, "entry", fn);
ir_builder->SetInsertPoint(entry_block);
}
fn->addAttribute(~0U, llvm::Attribute::AlwaysInline);
}
void createJitFunctionBody(llvm::IRBuilder<> *ir_builder, llvm::Module* mod, LLVMContext& context) {
llvm::Type *i1T = mod->getTypeByName("struct.IntVal");
llvm::Value* i1 = ir_builder->CreateAlloca(i1T);
llvm::Type *i2T = mod->getTypeByName("struct.IntVal");
llvm::Value* i2 = ir_builder->CreateAlloca(i2T);
llvm::Type *retT = mod->getTypeByName("struct.BoolVal");
llvm::Value* ret = ir_builder->CreateAlloca(retT);
std::vector<Value*> params;
params.push_back(i1);
params.push_back(i2);
params.push_back(ret);
Function* eq_func = mod->getFunction("_Z16intobj_eq_intobjR6IntValS0_R7BoolVal");
ir_builder->CreateCall(eq_func, params);
ir_builder->CreateRetVoid();
}
void* OneThread(void* args) {
typedef void (*jitFuncPtr)();
int64_t thread_id = 1;
llvm::LLVMContext *context = new llvm::LLVMContext();
std::unique_ptr<llvm::Module> mod = loadBC(*context);
std::string module_name = "moudule";
LlvmEntry *entry = new LlvmEntry();
entry->initialize(module_name, std::move(mod), context);
llvm::Module *module = entry->getLlvmModule();
llvm::ExecutionEngine* execution_engine = entry->getLlvmExecutionEngine();
llvm::IRBuilder<> *ir_builder = entry->getLlvmBuilder();
module->setDataLayout(execution_engine->getDataLayout());
createJitFunctionDefine(ir_builder, module, *context, thread_id);
createJitFunctionBody(ir_builder, module, *context);
std::ostringstream ss;
ss << "my_jit_func" << thread_id;
//call main function
void* irFunc = reinterpret_cast<void*>(execution_engine->getFunctionAddress(ss.str().c_str()));
if (irFunc == NULL) {
std::cout << "myJitFunc is Null !" << std::endl;
}
jitFuncPtr jitFunc = reinterpret_cast<jitFuncPtr>(irFunc);
jitFunc();
delete entry;
return NULL;
}
void createThread() {
int NUM_THREADS = 1;
pthread_t tids[NUM_THREADS];
for(int i = 0; i < NUM_THREADS; ++i)
{
int ret = pthread_create(&tids[i], NULL, OneThread, NULL);
if (ret != 0)
{
std::cout << "pthread_create error: error_code=" << ret << std::endl;
}
}
for(int i = 0; i < NUM_THREADS; ++i) {
pthread_join(tids[i], NULL);
}
}
int main(int argc, char**argv) {
llvm::InitializeNativeTarget();
llvm::InitializeNativeTargetAsmPrinter();
llvm::InitializeNativeTargetAsmParser();
llvm::InitializeNativeTargetDisassembler();
//llvm::DebugFlag = true;
createThread();
llvm_shutdown();
std::cout << "main function fininsh !" << std::endl;
return 0;
}
参考资料
//.eh_frame段的描述信息
https://refspecs.linuxbase.org/LSB_3.0.0/LSB-PDA/LSB-PDA/specialsections.html
// 关于编译器mcmodel选项的说明:
// -mcmodel=small [默认值]程序和它的符号必须位于2GB以下的地址空间。
// -mcmodel=large 对地址空间没有任何限制。
https://gcc.gnu.org/onlinedocs/gcc/x86-Options.html
// 查看LLVM最新代码,在PowerPC也解决过类似问题
https://www.mail-archive.com/llvm-bugs@lists.llvm.org/msg22257.html