| # Debugging JIT-ed Code | |
| ## Background | |
| Without special runtime support, debugging dynamically generated code can be | |
| quite painful. Debuggers generally read debug information from object files on | |
| disk, but for JITed code there is no such file to look for. | |
| In order to hand over the necessary debug info, [GDB established an | |
| interface](https://sourceware.org/gdb/onlinedocs/gdb/JIT-Interface.html) | |
| for registering JITed code with debuggers. LLDB implements it in the | |
| JITLoaderGDB plugin. On the JIT side, LLVM MCJIT does implement the interface | |
| for ELF object files. | |
| At a high level, whenever MCJIT generates new machine code, it does so in an | |
| in-memory object file that contains the debug information in DWARF format. | |
| MCJIT then adds this in-memory object file to a global list of dynamically | |
| generated object files and calls a special function | |
| `__jit_debug_register_code` that the debugger knows about. When the debugger | |
| attaches to a process, it puts a breakpoint in this function and associates a | |
| special handler with it. Once MCJIT calls the registration function, the | |
| debugger catches the breakpoint signal, loads the new object file from the | |
| inferior's memory and resumes execution. This way it can obtain debug | |
| information for pure in-memory object files. | |
| ## GDB Version | |
| In order to debug code JIT-ed by LLVM, you need GDB 7.0 or newer, which is | |
| available on most modern distributions of Linux. The version of GDB that | |
| Apple ships with Xcode has been frozen at 6.3 for a while. | |
| ## LLDB Version | |
| Due to a regression in release 6.0, LLDB didn't support JITed code debugging for | |
| a while. The bug was fixed in mainline recently, so that debugging JITed ELF | |
| objects should be possible again from the upcoming release 12.0 on. On macOS the | |
| feature must be enabled explicitly using the `plugin.jit-loader.gdb.enable` | |
| setting. | |
| ## Debugging MCJIT-ed code | |
| The emerging MCJIT component of LLVM allows full debugging of JIT-ed code with | |
| GDB. This is due to MCJIT's ability to use the MC emitter to provide full | |
| DWARF debugging information to GDB. | |
| Note that lli has to be passed the `--jit-kind=mcjit` flag to JIT the code | |
| with MCJIT instead of the newer ORC JIT. | |
| ### Example | |
| Consider the following C code (with line numbers added to make the example | |
| easier to follow): | |
| % FIXME: | |
| % Sphinx has the ability to automatically number these lines by adding | |
| % :linenos: on the line immediately following the `.. code-block:: c`, but | |
| % it looks like garbage; the line numbers don't even line up with the | |
| % lines. Is this a Sphinx bug, or is it a CSS problem? | |
| ```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 } | |
| ``` | |
| Here is a sample command line session that shows how to build and run this | |
| code via `lli` inside LLDB: | |
| ```bash | |
| > 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 | |
| ``` |