LLVM基础学习:使用GDB调试一个out-of-tree的 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