debugging gdb不显示LLVM的MCJIT生成的JIT代码的源代码级调试信息

cig3rfwq  于 2022-11-14  发布在  其他
关注(0)|答案(1)|浏览(121)

我试图提高我们正在编写的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源代码,看看我是否能从中获得一些见解。

vptzau2j

vptzau2j1#

我可以提供部分答案。
我们将所有JITted代码放在其中的函数没有dbg!元数据,这导致gdb不显示任何源代码级别的信息。
我通过从第一条JITted指令中复制子程序元数据来使它工作。我能够跳过JITted跟踪并看到源代码窗格中的C代码更新。

请注意JITted代码现在也标识为main,这是由于我复制元数据的方式很不好。
(我们现在面临的另一个问题是,当LLVM遇到不同子程序的dbg!指令时,它会因Assert失败而崩溃。这是由于我们跟踪JIT的工作方式,即在指令运行时内联指令。但我认为这超出了这个SO问题的范围)

相关问题