Debugging JIT-ed Code With GDB

Background

Without special runtime support, debugging dynamically generated code withGDB (as well as most debuggers) can be quite painful. Debuggers generallyread debug information from the object file of the code, but for JITedcode, there is no such file to look for.

In order to communicate the necessary debug info to GDB, an interface forregistering JITed code with debuggers has been designed and implemented forGDB and LLVM MCJIT. At a high level, whenever MCJIT generates new machine code,it does so in an in-memory object file that contains the debug information inDWARF format. MCJIT then adds this in-memory object file to a global list ofdynamically generated object files and calls a special function(__jit_debug_register_code) marked noinline that GDB knows about. WhenGDB attaches to a process, it puts a breakpoint in this function and loads allof the object files in the global list. When MCJIT calls the registrationfunction, GDB catches the breakpoint signal, loads the new object file fromthe inferior’s memory, and resumes the execution. In this way, GDB can get thenecessary debug information.

GDB Version

In order to debug code JIT-ed by LLVM, you need GDB 7.0 or newer, which isavailable on most modern distributions of Linux. The version of GDB thatApple ships with Xcode has been frozen at 6.3 for a while. LLDB may be abetter option for debugging JIT-ed code on macOS.

Debugging MCJIT-ed code

The emerging MCJIT component of LLVM allows full debugging of JIT-ed code withGDB. This is due to MCJIT’s ability to use the MC emitter to provide fullDWARF debugging information to GDB.

Note that lli has to be passed the -jit-kind=mcjit flag to JIT the code withMCJIT instead of the old JIT.

Example

Consider the following C code (with line numbers added to make the exampleeasier to follow):

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

Here is a sample command line session that shows how to build and run thiscode via lli inside GDB:

  1. $ $BINPATH/clang -cc1 -O0 -g -emit-llvm showdebug.c
  2. $ gdb --quiet --args $BINPATH/lli -jit-kind=mcjit showdebug.ll 5
  3. Reading symbols from $BINPATH/lli...done.
  4. (gdb) b showdebug.c:6
  5. No source file named showdebug.c.
  6. Make breakpoint pending on future shared library load? (y or [n]) y
  7. Breakpoint 1 (showdebug.c:6) pending.
  8. (gdb) r
  9. Starting program: $BINPATH/lli -jit-kind=mcjit showdebug.ll 5
  10. [Thread debugging using libthread_db enabled]
  11.  
  12. Breakpoint 1, compute_factorial (n=5) at showdebug.c:6
  13. 6 int f = n;
  14. (gdb) p n
  15. $1 = 5
  16. (gdb) p f
  17. $2 = 0
  18. (gdb) n
  19. 7 while (--n > 1)
  20. (gdb) p f
  21. $3 = 5
  22. (gdb) b showdebug.c:9
  23. Breakpoint 2 at 0x7ffff7ed404c: file showdebug.c, line 9.
  24. (gdb) c
  25. Continuing.
  26.  
  27. Breakpoint 2, compute_factorial (n=1) at showdebug.c:9
  28. 9 return f;
  29. (gdb) p f
  30. $4 = 120
  31. (gdb) bt
  32. #0 compute_factorial (n=1) at showdebug.c:9
  33. #1 0x00007ffff7ed40a9 in main (argc=2, argv=0x16677e0) at showdebug.c:18
  34. #2 0x3500000001652748 in ?? ()
  35. #3 0x00000000016677e0 in ?? ()
  36. #4 0x0000000000000002 in ?? ()
  37. #5 0x0000000000d953b3 in llvm::MCJIT::runFunction (this=0x16151f0, F=0x1603020, ArgValues=...) at /home/ebenders_test/llvm_svn_rw/lib/ExecutionEngine/MCJIT/MCJIT.cpp:161
  38. #6 0x0000000000dc8872 in llvm::ExecutionEngine::runFunctionAsMain (this=0x16151f0, Fn=0x1603020, argv=..., envp=0x7fffffffe040)
  39. at /home/ebenders_test/llvm_svn_rw/lib/ExecutionEngine/ExecutionEngine.cpp:397
  40. #7 0x000000000059c583 in main (argc=4, argv=0x7fffffffe018, envp=0x7fffffffe040) at /home/ebenders_test/llvm_svn_rw/tools/lli/lli.cpp:324
  41. (gdb) finish
  42. Run till exit from #0 compute_factorial (n=1) at showdebug.c:9
  43. 0x00007ffff7ed40a9 in main (argc=2, argv=0x16677e0) at showdebug.c:18
  44. 18 int result = compute_factorial(firstletter - '0');
  45. Value returned is $5 = 120
  46. (gdb) p result
  47. $6 = 23406408
  48. (gdb) n
  49. 21 return result;
  50. (gdb) p result
  51. $7 = 120
  52. (gdb) c
  53. Continuing.
  54.  
  55. Program exited with code 0170.
  56. (gdb)