使用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