我试图提高我们正在编写的JIT的可调试性。
JIT是一种跟踪JIT,它使用LLVM在运行时使用ExecutionEngine接口(据我所知,它是MCJIT的一个变体,而不是更新的ORC)发出代码。
我们使用LLVM的C++ API在内存中生成LLVM模块,然后最终生成如下所示的执行引擎:
auto MPtr = std::unique_ptr<Module>(M);
string ErrStr;
ExecutionEngine *EE =
EngineBuilder(std::move(MPtr))
.setEngineKind(EngineKind::JIT)
.setMemoryManager(std::unique_ptr<MCJITMemoryManager>(memman))
.setErrorStr(&ErrStr)
.create();
if (EE == nullptr)
errx(EXIT_FAILURE, "Couldn't compile trace: %s", ErrStr.c_str());
...
EE->finalizeObject();
...
// Then later on when we want to execute this code, we call to
// EE->getFunctionAddress(TraceName), where tracename is a function inside
// the module we've just compiled.
(Full此处为系统这一部分的代码)
以这种方式执行代码对我们来说很好。
但是,在gdb中调试JITted代码时,源代码级调试信息不起作用。
我找到了this page in the LLVM docs(和this page in the gdb docs),它描述了当MCJIT在运行时编译代码时,它如何在调用函数__jit_debug_register_code()
之前将DWARF调试信息放入内存缓冲区。据我所知,当附加gdb时,它在后台将断点放置在该符号上,并为新JITted代码加载调试信息。
这听起来很完美。让我们来试试。下面是一些JITted代码的IR:
define i8 @__yk_compiled_trace_0(ptr nocapture %0, ptr %1, i64 %2, ptr %3, ptr %4) local_unnamed_addr {
%6 = load ptr, ptr %0, align 8, !dbg !21
%7 = getelementptr %YkCtrlPointVars, ptr %0, i64 0, i32 1, !dbg !21
%8 = load ptr, ptr %7, align 8, !dbg !21
%9 = getelementptr %YkCtrlPointVars, ptr %0, i64 0, i32 2, !dbg !21
%10 = load ptr, ptr %9, align 8, !dbg !21
...
}
...
!5 = !DIFile(filename: "c/noopts.c", directory: "/home/vext01/research/yk/tests", checksumkind: CSK_MD5, checksum: "21402bb47784fb6db5e1a02382e9c053")
...
!21 = !DILocation(line: 46, column: 5, scope: !22)
!22 = distinct !DILexicalBlock(scope: !23, file: !5, line: 45, column: 17)
...
在这里我们可以看到,前几行附加了指向noopts.c
第46行的调试元数据。当指令指针位于与这些IR行对应的机器代码上时,我希望gdb中会显示此信息。
我已经通过在__jit_debug_register_code()
上放置我自己的断点来验证LLVM正在做正确的事情:
$ YKD_SERIALISE_COMPILATION=1 gdb /tmp/.tmpcaG9yl/noopts
GNU gdb (Debian 10.1-1.7) 10.1.90.20210103-git
...
(gdb) b __jit_debug_register_code
Function "__jit_debug_register_code" not defined.
Make breakpoint pending on future shared library load? (y or [n]) y
Breakpoint 1 (__jit_debug_register_code) pending.
(gdb) run
Starting program: /tmp/.tmpcaG9yl/noopts
DW_FORM_rnglistx index pointing outside of .debug_rnglists offset array [in module /home/vext01/research/yk/tests/../ykcapi/scripts/../../target/debug/deps/libykcapi.so]
[Thread debugging using libthread_db enabled]
Using host libthread_db library "/lib/x86_64-linux-gnu/libthread_db.so.1".
[New Thread 0x7ffff4555700 (LWP 11177)]
[Thread 0x7ffff4555700 (LWP 11177) exited]
Thread 1 "noopts" hit Breakpoint 1, 0x00007ffff5c8d690 in __jit_debug_register_code.localalias () from /home/vext01/research/yk/tests/../ykcapi/scripts/../../target/debug/deps/libykcapi.so
(gdb)
很好,现在让我们在JITted代码的开头放置一个断点,然后切换到split
布局:
(gdb) b __yk_compiled_trace_0
Breakpoint 2 at 0x7ffff4fe8004
(gdb) c
Continuing.
Thread 1 "noopts" hit Breakpoint 2, 0x00007ffff4fe8004 in __yk_compiled_trace_0 ()
(gdb) la split
gdb没有显示源信息,我的问题是为什么?
我的一个理论是函数prolog没有任何调试信息,如果我前进到后面的代码,可能会显示一些源代码。但是,我已经跳过了整个JITted函数,没有显示任何PC值的源代码级信息。
(几周前我使用LLVM的主分支)
编辑@Andrew的建议(谢谢):
(我必须更新到gdb-12.1才能得到main info jit
的东西。还要注意,旧的gdb不接受“on”来打开一个选项,而是需要“1”)
打开这些选项后,我看到的是:
(gdb) set debug jit on
(gdb) b __yk_compiled_trace_0
Function "__yk_compiled_trace_0" not defined.
Make breakpoint pending on future shared library load? (y or [n]) y
Breakpoint 1 (__yk_compiled_trace_0) pending.
(gdb) run
Starting program: /tmp/.tmpcaG9yl/noopts
[jit] jit_inferior_init: called
[Thread debugging using libthread_db enabled]
Using host libthread_db library "/lib/x86_64-linux-gnu/libthread_db.so.1".
[jit] jit_breakpoint_re_set_internal: breakpoint_addr = 0x7ffff5c8d690
...
[jit] jit_read_descriptor: descriptor_addr = 0x7ffff7f789f0
[jit] jit_register_code: symfile_addr = 0x3c8820, symfile_size = 2776
[jit] jit_bfd_try_read_symtab: symfile_addr = 0x3c8820, symfile_size = 2776
[jit] jit_breakpoint_re_set_internal: breakpoint_addr = 0x7ffff5c8d690
(gdb) maint info jit
jit_code_entry address symfile address symfile size
0x00000000003e8270 0x00000000003c8820 2776
我认为这显示了gdb拦截新代码,并在新的JIT代码到达时重新设置其内部断点。
我仍然不确定为什么gdb中没有显示源代码级别的调试信息。今天我将阅读gdb源代码,看看我是否能从中获得一些见解。
1条答案
按热度按时间vptzau2j1#
我可以提供部分答案。
我们将所有JITted代码放在其中的函数没有
dbg!
元数据,这导致gdb不显示任何源代码级别的信息。我通过从第一条JITted指令中复制子程序元数据来使它工作。我能够跳过JITted跟踪并看到源代码窗格中的C代码更新。
请注意JITted代码现在也标识为main,这是由于我复制元数据的方式很不好。
(我们现在面临的另一个问题是,当LLVM遇到不同子程序的
dbg!
指令时,它会因Assert失败而崩溃。这是由于我们跟踪JIT的工作方式,即在指令运行时内联指令。但我认为这超出了这个SO问题的范围)