JIT で生成されたコードのデバッグ

背景

特別ランタイムサポートがない場合、動的に生成されたコードをデバッグすることは非常に困難になることがあります。通常、デバッガーはディスク上のオブジェクトファイルからデバッグ情報を読み込みますが、JIT で生成されたコードにはそのようなファイルがありません。

必要なデバッグ情報を引き渡すために、GDB は、デバッガーに JIT で生成されたコードを登録するためのインターフェイスを確立しました。LLDB は JITLoaderGDB プラグインにこれを実装しています。JIT 側では、LLVM MCJIT が ELF オブジェクトファイルのインターフェイスを実装しています。

高レベルでは、MCJIT が新しいマシンコードを生成するたびに、DWARF 形式のデバッグ情報を含むメモリ内オブジェクトファイルで行います。次に、MCJIT はこのメモリ内オブジェクトファイルを動的に生成されたオブジェクトファイルのグローバルリストに追加し、デバッガーが認識する特別な関数 __jit_debug_register_code を呼び出します。デバッガーがプロセスにアタッチすると、この関数にブレークポイントを設定して、これと特殊なハンドラーを関連付けます。MCJIT が登録関数を呼び出すと、デバッガーはブレークポイント信号を受信し、ターゲットのメモリから新しいオブジェクトファイルをロードして、実行を再開します。このようにして、純粋なメモリ内オブジェクトファイルのデバッグ情報を得ることができます。

GDB のバージョン

LLVM で JIT 生成されたコードをデバッグするには、最新の Linux ディストリビューションのほとんどで利用可能な GDB 7.0 以降が必要です。Apple が Xcode に搭載する GDB のバージョンはしばらく 6.3 で固定されています。

LLDB のバージョン

リリース 6.0 の回帰により、LLDB はしばらく JIT で生成されたコードのデバッグをサポートしていませんでした。バグは最近メインラインで修正され、今後のリリース 12.0 以降で ELF オブジェクトの JIT 生成されたデバッグが再び可能になる予定です。macOS では、plugin.jit-loader.gdb.enable 設定を使用して機能を明示的に有効にする必要があります。

MCJIT で生成されたコードのデバッグ

LLVM の新たな MCJIT コンポーネントにより、GDB で JIT で生成されたコードを完全にデバッグできます。これは、MCJIT が MC エミッタを使用して GDB に完全な DWARF デバッグ情報を提供できるためです。

lli には、新しい ORC JIT ではなく MCJIT でコードを JIT 生成するための --jit-kind=mcjit フラグを渡す必要があることに注意してください。

次の C コードを考えてみます (行番号は例をわかりやすくするために追加されています)

1   int compute_factorial(int n)
2   {
3       if (n <= 1)
4           return 1;
5
6       int f = n;
7       while (--n > 1)
8           f *= n;
9       return f;
10  }
11
12
13  int main(int argc, char** argv)
14  {
15      if (argc < 2)
16          return -1;
17      char firstletter = argv[1][0];
18      int result = compute_factorial(firstletter - '0');
19
20      // Returned result is clipped at 255...
21      return result;
22  }

次に、LLDB 内の lli を介してこのコードをビルドして実行する方法を示すコマンドラインセッションの例を示します

> export BINPATH=/workspaces/llvm-project/build/bin
> $BINPATH/clang -g -S -emit-llvm --target=x86_64-unknown-unknown-elf showdebug.c
> lldb $BINPATH/lli
(lldb) target create "/workspaces/llvm-project/build/bin/lli"
Current executable set to '/workspaces/llvm-project/build/bin/lli' (x86_64).
(lldb) settings set plugin.jit-loader.gdb.enable on
(lldb) b compute_factorial
Breakpoint 1: no locations (pending).
WARNING:  Unable to resolve breakpoint to any actual locations.
(lldb) run --jit-kind=mcjit showdebug.ll 5
1 location added to breakpoint 1
Process 21340 stopped
* thread #1, name = 'lli', stop reason = breakpoint 1.1
   frame #0: 0x00007ffff7fd0007 JIT(0x45c2cb0)`compute_factorial(n=5) at showdebug.c:3:11
   1    int compute_factorial(int n)
   2    {
-> 3        if (n <= 1)
   4            return 1;
   5        int f = n;
   6        while (--n > 1)
   7            f *= n;
(lldb) p n
(int) $0 = 5
(lldb) b showdebug.c:9
Breakpoint 2: where = JIT(0x45c2cb0)`compute_factorial + 60 at showdebug.c:9:1, address = 0x00007ffff7fd003c
(lldb) c
Process 21340 resuming
Process 21340 stopped
* thread #1, name = 'lli', stop reason = breakpoint 2.1
   frame #0: 0x00007ffff7fd003c JIT(0x45c2cb0)`compute_factorial(n=1) at showdebug.c:9:1
   6        while (--n > 1)
   7            f *= n;
   8        return f;
-> 9    }
   10
   11   int main(int argc, char** argv)
   12   {
(lldb) p f
(int) $1 = 120
(lldb) bt
* thread #1, name = 'lli', stop reason = breakpoint 2.1
* frame #0: 0x00007ffff7fd003c JIT(0x45c2cb0)`compute_factorial(n=1) at showdebug.c:9:1
   frame #1: 0x00007ffff7fd0095 JIT(0x45c2cb0)`main(argc=2, argv=0x00000000046122f0) at showdebug.c:16:18
   frame #2: 0x0000000002a8306e lli`llvm::MCJIT::runFunction(this=0x000000000458ed10, F=0x0000000004589ff8, ArgValues=ArrayRef<llvm::GenericValue> @ 0x00007fffffffc798) at MCJIT.cpp:554:31
   frame #3: 0x00000000029bdb45 lli`llvm::ExecutionEngine::runFunctionAsMain(this=0x000000000458ed10, Fn=0x0000000004589ff8, argv=size=0, envp=0x00007fffffffe140) at ExecutionEngine.cpp:467:10
   frame #4: 0x0000000001f2fc2f lli`main(argc=4, argv=0x00007fffffffe118, envp=0x00007fffffffe140) at lli.cpp:643:18
   frame #5: 0x00007ffff788c09b libc.so.6`__libc_start_main(main=(lli`main at lli.cpp:387), argc=4, argv=0x00007fffffffe118, init=<unavailable>, fini=<unavailable>, rtld_fini=<unavailable>, stack_end=0x00007fffffffe108) at libc-start.c:308:16
   frame #6: 0x0000000001f2dc7a lli`_start + 42
(lldb) finish
Process 21340 stopped
* thread #1, name = 'lli', stop reason = step out
Return value: (int) $2 = 120

   frame #0: 0x00007ffff7fd0095 JIT(0x45c2cb0)`main(argc=2, argv=0x00000000046122f0) at showdebug.c:16:9
   13       if (argc < 2)
   14           return -1;
   15       char firstletter = argv[1][0];
-> 16       int result = compute_factorial(firstletter - '0');
   17
   18       // Returned result is clipped at 255...
   19       return result;
(lldb) p result
(int) $3 = 73670648
(lldb) n
Process 21340 stopped
* thread #1, name = 'lli', stop reason = step over
   frame #0: 0x00007ffff7fd0098 JIT(0x45c2cb0)`main(argc=2, argv=0x00000000046122f0) at showdebug.c:19:12
   16       int result = compute_factorial(firstletter - '0');
   17
   18       // Returned result is clipped at 255...
-> 19       return result;
   20   }
(lldb) p result
(int) $4 = 120
(lldb) expr result=42
(int) $5 = 42
(lldb) p result
(int) $6 = 42
(lldb) c
Process 21340 resuming
Process 21340 exited with status = 42 (0x0000002a)
(lldb) exit