除錯 JIT 編譯的程式碼¶
背景¶
如果沒有特殊的執行階段支援,除錯動態產生的程式碼可能會相當痛苦。除錯器通常從磁碟上的物件檔案中讀取除錯資訊,但對於 JIT 編譯的程式碼來說,沒有這樣的檔案可以查找。
為了遞交必要的除錯資訊,GDB 建立了一個介面,用於向除錯器註冊 JIT 編譯的程式碼。LLDB 在 JITLoaderGDB 外掛中實作了它。在 JIT 端,LLVM MCJIT 確實為 ELF 物件檔案實作了該介面。
在高層次上,每當 MCJIT 產生新的機器碼時,它都會在一個記憶體中的物件檔案中執行此操作,該檔案包含 DWARF 格式的除錯資訊。然後,MCJIT 將此記憶體中的物件檔案添加到動態產生的物件檔案的全域清單中,並呼叫除錯器知道的特殊函數 __jit_debug_register_code
。當除錯器附加到一個行程時,它會在這個函數中設置一個斷點,並將一個特殊的處理程式與之關聯。一旦 MCJIT 呼叫註冊函數,除錯器就會捕捉到斷點訊號,從受控程式的記憶體中載入新的物件檔案,並繼續執行。這樣它就可以獲得純記憶體中物件檔案的除錯資訊。
GDB 版本¶
為了除錯由 LLVM JIT 編譯的程式碼,您需要 GDB 7.0 或更新版本,它在大多数現代 Linux 發行版中都有提供。Apple 隨 Xcode 提供的 GDB 版本已經凍結在 6.3 一段時間了。
LLDB 版本¶
由於 6.0 版中的回歸,LLDB 有段時間不支援 JIT 編譯的程式碼除錯。這個錯誤最近在主線中已經被修復,因此從即將發布的 12.0 版開始,應該可以再次除錯 JIT 編譯的 ELF 物件。在 macOS 上,必須使用 plugin.jit-loader.gdb.enable
設定明確啟用此功能。
除錯 MCJIT 編譯的程式碼¶
LLVM 中新興的 MCJIT 元件允許使用 GDB 對 JIT 編譯的程式碼進行完整除錯。這是因為 MCJIT 能夠使用 MC 發射器向 GDB 提供完整的 DWARF 除錯資訊。
請注意,必須將 --jit-kind=mcjit
旗標傳遞給 lli,才能使用 MCJIT 而不是較新的 ORC JIT 來 JIT 編譯程式碼。
範例¶
考慮以下 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