【線上問題定位】Llvm庫ARM環境崩潰問題

2021-03-02 技術茶館

 摘要:記一次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=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); 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

相關焦點

  • ARM棧回溯——從理論到實踐,開發IDA-arm-unwind-plugin
    本文參考:官方文檔,發現看不懂的就去讀文檔:https://developer.arm.com/documentation/ihi0038/b/https://github.com/llvm/llvm-project/blob/master/llvm/tools/llvm-readobj/ARMEHABIPrinter.hhttps://github.com/bminor/binutils-gdb
  • 通過LLVM 在 Android 上運行 Swift 代碼
    適配 Android現在最大的問題是 SwiftCore 庫缺失。現在蘋果已經為 iOS,OS X 和 Watch OS 都提供了一個。但是,很明顯,並沒有提供 Android 版本。但是,不是所有 Swift 代碼都要求 SwiftCore 庫,跟不是所有 C++ 代碼都要求 STL 一樣。
  • 關於LLVM,這些東西你必須要知道!
    二、安裝編譯LLVM這裡使用clang作為前端:1.直接從官網下載:http://releases.llvm.org/download.html2.svn獲取svn co http://llvm.org/svn/llvm-project/llvm/trunk llvmcd llvm/toolssvn co http://llvm.org/svn
  • STM32F4-浮點DSP庫的MDK開發環境的設置
    在網上看了一下關於DSP的庫的開發環境設置,寫的有些亂,現在來整理一下,做一下MARK吧!當然也不一定需要編譯通過,先做編譯是為了後面添加浮點庫做準備,這樣發現問題和知道哪裡出問題。4.
  • LLVM 初探
    llvm-dis < hello.bc | less10.PrintFunctions/printFunctions.cpp 內容如下:#include "llvm/Pass.h"#include "llvm/IR/Function.h"#include "llvm/Support/raw_ostream.h"#include "llvm/IR/LegacyPassManager.h"#include
  • 線上服務CPU100%問題快速定位實戰
    功能問題,通過日誌,單步調試相對比較好定位。
  • LLVM 7.0.0 發布,提升性能分析能力 - OSCHINA - 中文開源技術交流...
    LLVM 7.0.0 發布了,LLVM 是 Low Level Virtual Machine (低級虛擬機)的簡稱,這個庫提供了與編譯器相關的支持
  • arm_pwn環境搭建及初探
    >$ sudo apt-get install qemu-use-binfmt qemu-user-binfmt:i386libc環境安裝(安裝後libc環境存放在/usr/aarch64-linux-gnu目錄下):$ sudo apt install libc6-arm64-cross正常啟動:$ qemu-aarch64 -L /usr
  • gcc、arm-linux-gcc和arm-elf-gcc的關係?
    uClinux發行版提供了環境能夠讓你選擇使用uC-libc或是uClibc編譯。對於m68000和Coldfire平臺來說,選擇uC-libc還是稍微好一點,因為它支持共享庫,而共享庫是這些cpu經常使用的 libc。uClibc也幾乎和所有的平臺都能很好的工作。
  • 什麼是 LLVM?Swift, Rust, Clang 等語言背後的支持
    新的語言,還有對現有語言的提升,在整個編程環境中正大行其道。Mozilla 的 Rust、Apple 的 Swift、Jetbrains 的 Kotlin,以及許多其它的語言都給開發者在速度、安全性、便利性、可移植性還有能力這些方面提供了新的選擇。為什麼現在正當時呢?一個大因素就是那些用來構建語言的新工具,特別是編譯器。
  • 線上問題排查利器Arthas
    當然,上線前如何充分測試,線上出問題之後如何快速止血這些不在本文介紹範圍之內,本文主要介紹如何快速定位問題。線上問題複雜多樣,CPU飆升、RT突增、內存不足、性能下降等等,如何才能在最短時間內定位到問題呢?下面就來安利這款線上問題定位神器:Arthas。Arthas是一款阿里開源的Java診斷工具,對業務代碼無侵入,功能強悍,安裝簡單,上手快,深受Java開發者喜愛。
  • Java 問題定位方法和工具
    「線下不會有問題的」、 「代碼不可能有問題,是系統原因吧」、「能在線上遠程 debug 麼?」
  • 自定義 LLVM PASS 實現 函數耗時插樁統計
    echo "ninja build llvm ..."-11.0.0/llvm/lib/Transforms 目錄將 Hello 目錄拷貝一份,並重命名為 FunctionCallTime在 llvm-project-llvmorg-11.0.0/llvm/lib/Transforms/CMakeLists.txt 後面追加一行 add_subdirectory(FunctionCallTime)在 llvm-project-llvmorg
  • OpenCV ffmpeg移植到ARM平臺
    前一篇寫了如何在移植OpenCV庫到ARM平臺上,本來我只是用到OpenCV的打開USB攝像頭獲取圖像到IplImage結構中,這用到了V4L2的底層函數。然後進行視頻圖像的幀處理。那麼如何用OpenCV讀寫ARM板上的視頻文件,並進行視頻處理呢?這該又將如何移植呢?
  • 用chardect庫解決網頁亂碼問題
    二、代碼實戰2.1 定位關鍵詞及其連結F12鍵盤打開開發者工具,我們定位關鍵詞及其對應的html標籤。在這裡我們使用pyquery庫定位 class屬性為'keyword'的td。這就是我們今天要克服的問題-html編碼問題。遇到這種問題問題,我們可能會先在html標籤中查找charset字符集。一般charset值有utf-8、gbk、gb2312、ascii等。
  • 嵌入式ARM-Linux平臺上的編譯、配置和運行使用
    本文介紹了嵌入式ARM-Linux上的常用應用程式wpa_supplicant(以及wpa_supplicant依賴的libnl和openssl)的編譯、配置和運行使用,iw、hostapd等應用的編譯和使用
  • 什麼是 LLVM?Swift, Rust, Clang 等語言背後的原力
    而 Kotlin,名義上是一種 JVM 語言,正在開發一種名為 Kotlin Native 的語言版本,它使用 LLVM 來編譯成機器原生代碼。LLVM 定義在它的核心,LLVM 是一個以編程方式創建機器原生代碼的庫。開發人員使用該 API 以一種稱為中間代理或 IR 的格式生成指令。
  • GMAT換庫規律揭秘,換庫常見問題匯總!
    2020年GMAT考試共換庫26次,想不想知道這26次換庫有什麼規律呢?跟著轉運少女一起來尋找吧!所以我們可以總結出,在GMAT換庫7天以內報名是安全的,換庫時間大概為10天左右。例如最近的換庫時間為12月29日,所以在1月5日之前報名為安全期,下次換庫時間在1月8日左右。Q1:什麼時候報名最好?A:最好把考試時間定在換庫之後的4-5天,因為這時候換庫機經已經很豐富了,但也要保障可以搶到考位。