「 摘要:記一次ARM伺服器上面Llvm開源庫崩潰,問題發現到解決的過程。主要內容如下:
1. 問題現象
2. 問題分解思維導圖
3. 工具說明
4. 問題定位
5. 源碼
」
問題現象
本地編寫的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=largePS:
-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); 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( Type::getVoidTy(context), FuncTy_0_args, 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; 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(); 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