• LLVM基础学习:使用GDB调试一个outoftree的 LLVM Pass


    使用GDB调试一个out-of-tree的 LLVM Pass

    时间:20220611,版本:V0.1

    作者:robotech_erx

    1.Introduction

    环境:

    Ubuntu 20.04 桌面版

    LLVM 13.0.1 github下载的pre-build版本。Release配置的,没有调试符号。

    GDB  9.2  (Ubuntu 9.2-0ubuntu1~20.04.1,系统自带的版本)

    LLVM里编写Pass的时候,可以放到llvm源码目录下,也可以放到源码目录之外单独编译,即所谓的out-of-tree编译。Out-of-tree的方式能够保持LLVM自己的源码整洁。本文介绍怎样调试一个out-of-tree的Pass。

    如果直接以Debug方式编译LLVM,需要的内存和硬盘空间都很恐怖。所以这里的LLVM是release编译的(从 github下载的pre-build版本),没有源码。但是Pass的代码是Debug的,能够单步调试。

    测试的Pass代码来自于github上的llvm-tutor项目。

    2.Release的opt加载debug的pass

    能。至少在13.0.1这个版本里是可以的。

    一开始,没有试一下就搜索,看到网上有文章说是不能加载的(https://www.leadroyal.cn/p/682/)。看下原因是因为LLVM_ABI_BREAKING_CHECKS的原因,Debug和Release的采用了不同的配置。

    官网上的解释是:LLVM_ABI_BREAKING_CHECKS:STRING

    Used to decide if LLVM should be built with ABI breaking checks or not. Allowed values are WITH_ASSERTS (default), FORCE_ON and FORCE_OFF. WITH_ASSERTS turns on ABI breaking checks in an assertion enabled build. FORCE_ON (FORCE_OFF) turns them on (off) irrespective of whether normal (NDEBUG-based) assertions are enabled or not. A version of LLVM built with ABI breaking checks is not ABI compatible with a version built without it.

    Release编译的版本里这个配置是FORCE_OFF,而debug的是打开的,所以release的 opt加载debug的opt会有问题。(貌似跟这个配置相关的还有另一个:LLVM_DISABLE_ABI_BREAKING_CHECKS_ENFORCING,相关信息请自行搜索)

    原以为使用编译Pass的时候修改这个配置跟llvm opt 一样就可以了。但是发现不设置这个也能成功加载。

    使用Debug配置编译llvm tutor的HelloWorld pass:

    $export LLVM_DIR=/home/jack/worktable/llvm1301
    $mkdir build
    $cd build
    $cmake -DLT_LLVM_INSTALL_DIR=$LLVM_DIR ../HelloWorld/
    $make
    $cmake -DLT_LLVM_INSTALL_DIR=$LLVM_DIR -DCMAKE_BUILD_TYPE=debug ../HelloWorld/

    使用objdump 查看:已经有了信息:

    $ objdump -h libHelloWorld.so
    
    libHelloWorld.so:     file format elf64-x86-64
    
    Sections:
    Idx Name         Size      VMA        LMA       File off  Algn
      省略....
     26 .bss        00000090  0000000000019860  0000000000019860  00018850  2**5     ALLOC
     27 .comment      0000002b  0000000000000000  0000000000000000  00018850  2**0    CONTENTS, READONLY
     28 .debug_aranges    00000ee0  0000000000000000  0000000000000000  0001887b  2**0    CONTENTS, READONLY, DEBUGGING, OCTETS
     29 .debug_info     000e78dd  0000000000000000  0000000000000000  0001975b  2**0   CONTENTS, READONLY, DEBUGGING, OCTETS
     30 .debug_abbrev     00002027  0000000000000000  0000000000000000  00101038  2**0    CONTENTS, READONLY, DEBUGGING, OCTETS
     31 .debug_line     000037fb  0000000000000000  0000000000000000  0010305f  2**0     CONTENTS, READONLY, DEBUGGING, OCTETS
     32 .debug_str     002a6b1d  0000000000000000  0000000000000000  0010685a  2**0  CONTENTS, READONLY, DEBUGGING, OCTETS
     33 .debug_ranges     00000f00  0000000000000000  0000000000000000  003ad377  2**0   CONTENTS, READONLY, DEBUGGING, OCTETS

    注意debug开头的各个section,调试信息都写进去了。GDB加载一下,也显示符号成功读取。使用opt加载运行:

    $LLVM_DIR/bin/opt  -enable-new-pm=0    -load ./libHelloWorld.so -legacy-hello-world  input_for_hello.ll -o /dev/null

    成功显示运行结果。

    Opt是没有调试信息的release版本:

    jack@jack-VirtualBox:~/worktable/llvm1301/bin$ gdb opt
    
    ...
    
    GEF for linux ready, type `gef' to start, `gef config' to configure
    
    96 commands loaded for GDB 9.2 using Python engine 3.8
    
    Reading symbols from opt...
    
    (No debugging symbols found in opt)

    所以release版的opt是能够加载debug的pass的。接下来就好办了。

    3.断点调试

    官网文档上(https://llvm.org/docs/WritingAnLLVMPass.html)说的是可以再llvm::PassManager::run函数上下断点,然后加载pass。但是官网文档可能有点旧了,这个函数不存在了:

    gef➤  break llvm::PassManager::run
    
    Function "llvm::PassManager::run" not defined.

    查找相关函数:

    gef➤  info functions PassManager::run
    
    All functions matching regular expression "PassManager::run":
    
    Non-debugging symbols:
    
    0x0000000001637ac0  (anonymous namespace)::CGPassManager::runOnModule(llvm::Module&)
    0x00000000016f3b00  llvm::LPPassManager::runOnFunction(llvm::Function&)
    0x000000000175dca0  llvm::RGPassManager::runOnFunction(llvm::Function&)
    0x0000000001d7f6f0  llvm::FPPassManager::runOnFunction(llvm::Function&)
    0x0000000001d86610  llvm::legacy::FunctionPassManager::run(llvm::Function&)
    0x0000000001d868d0  llvm::FPPassManager::runOnModule(llvm::Module&)
    0x0000000001d86c30  llvm::legacy::PassManager::run(llvm::Module&)

    貌似现在这个函数现在是llvm::legacy::PassManager::run

    gef➤  b llvm::legacy::PassManager::run

    Breakpoint 1 at 0x1d86c30

    但其实GDB是能够在未加载的文件(so)上下断点的,可以直接在pass代码中的函数上下断点的。

    GDB查看所有的符号:

    gef➤  info functions
    
    All defined functions:
    
    File /home/jack/worktable/llvm-tutor-main/HelloWorld/HelloWorld.cpp:
    
    73: llvm::PassPluginLibraryInfo getHelloWorldPluginInfo();
    92: llvm::PassPluginLibraryInfo llvmGetPassPluginInfo();
    54: static bool (anonymous namespace)::HelloWorld::isRequired();
    46: static llvm::PreservedAnalyses (anonymous namespace)::HelloWorld::run(llvm::Function&, llvm::FunctionAnalysisManager&);
    60: static void (anonymous namespace)::LegacyHelloWorld::LegacyHelloWorld();
    62: static bool (anonymous namespace)::LegacyHelloWorld::runOnFunction(llvm::Function&);
    58: static void (anonymous namespace)::LegacyHelloWorld::~LegacyHelloWorld();
    37: static void (anonymous namespace)::visitor(llvm::Function&);
    static void _GLOBAL__sub_I_HelloWorld.cpp(void);
    static void __static_initialization_and_destruction_0(int, int);
    
    (符号非常多,只截取HelloWorld.cpp的)

    可以在在LegacyHelloWorld::runOnFunction上下断点,在GDB里set confirm on打开未加载符号的询问,默认关闭了:

    gef➤  b LegacyHelloWorld::runOnFunction
    Function "LegacyHelloWorld::runOnFunction" not defined.
    gef➤  set confirm on  
    gef➤  b LegacyHelloWorld::runOnFunction
    Function "LegacyHelloWorld::runOnFunction" not defined.
    Make breakpoint pending on future shared library load? (y or [n]) y
    Breakpoint 1 (LegacyHelloWorld::LegacyHelloWorld) pending.
    
    gef➤  run -enable-new-pm=0    -load /home/jack/worktable/llvm-tutor-main/build/libHelloWorld.so -legacy-hello-world  /home/jack/worktable/llvm-tutor-main/build/input_for_hello.ll -o /dev/null

    后面就是单步的调试了。

    参考:

    https://www.leadroyal.cn/p/682/

    https://llvm.org/docs/WritingAnLLVMPass.html#using-gdb-with-dynamically-loaded-passes

  • 相关阅读:
    Excel表格函数逻辑排错
    MobaXterm体验最好的SSH客户端
    Excel中的常用函数
    Shell 知识点2020
    Linux 知识点2020
    Python知识点2020
    es6 模版字符串
    es6对象定义简写
    es6解构赋值
    ES6 let const关键字
  • 原文地址:https://www.cnblogs.com/robotech/p/16367989.html
Copyright © 2020-2023  润新知