Debugging

Currently the Kotlin/Native compiler produces debug info compatible with the DWARF 2 specification, so modern debugger tools can perform the following operations:

  • breakpoints
  • stepping
  • inspection of type information
  • variable inspection

Producing binaries with debug info with Kotlin/Native compiler

To produce binaries with the Kotlin/Native compiler it’s sufficient to use the -g option on the command line.
Example:

  1. 0:b-debugger-fixes:minamoto@unit-703(0)# cat - > hello.kt
  2. fun main(args: Array<String>) {
  3. println("Hello world")
  4. println("I need your clothes, your boots and your motocycle")
  5. }
  6. 0:b-debugger-fixes:minamoto@unit-703(0)# dist/bin/konanc -g hello.kt -o terminator
  7. KtFile: hello.kt
  8. 0:b-debugger-fixes:minamoto@unit-703(0)# lldb terminator.kexe
  9. (lldb) target create "terminator.kexe"
  10. Current executable set to 'terminator.kexe' (x86_64).
  11. (lldb) b kfun:main(kotlin.Array<kotlin.String>)
  12. Breakpoint 1: where = terminator.kexe`kfun:main(kotlin.Array<kotlin.String>) + 4 at hello.kt:2, address = 0x00000001000012e4
  13. (lldb) r
  14. Process 28473 launched: '/Users/minamoto/ws/.git-trees/debugger-fixes/terminator.kexe' (x86_64)
  15. Process 28473 stopped
  16. * thread #1, queue = 'com.apple.main-thread', stop reason = breakpoint 1.1
  17. frame #0: 0x00000001000012e4 terminator.kexe`kfun:main(kotlin.Array<kotlin.String>) at hello.kt:2
  18. 1 fun main(args: Array<String>) {
  19. -> 2 println("Hello world")
  20. 3 println("I need your clothes, your boots and your motocycle")
  21. 4 }
  22. (lldb) n
  23. Hello world
  24. Process 28473 stopped
  25. * thread #1, queue = 'com.apple.main-thread', stop reason = step over
  26. frame #0: 0x00000001000012f0 terminator.kexe`kfun:main(kotlin.Array<kotlin.String>) at hello.kt:3
  27. 1 fun main(args: Array<String>) {
  28. 2 println("Hello world")
  29. -> 3 println("I need your clothes, your boots and your motocycle")
  30. 4 }
  31. (lldb)

Breakpoints

Modern debuggers provide several ways to set a breakpoint, see below for a tool-by-tool breakdown:

lldb

  • by name
  1. (lldb) b -n kfun:main(kotlin.Array<kotlin.String>)
  2. Breakpoint 4: where = terminator.kexe`kfun:main(kotlin.Array<kotlin.String>) + 4 at hello.kt:2, address = 0x00000001000012e4

-n is optional, this flag is applied by default

  • by location (filename, line number)
  1. (lldb) b -f hello.kt -l 1
  2. Breakpoint 1: where = terminator.kexe`kfun:main(kotlin.Array<kotlin.String>) + 4 at hello.kt:2, address = 0x00000001000012e4
  • by address
  1. (lldb) b -a 0x00000001000012e4
  2. Breakpoint 2: address = 0x00000001000012e4
  • by regex, you might find it useful for debugging generated artifacts, like lambda etc. (where used # symbol in name).
  1. 3: regex = 'main\(', locations = 1
  2. 3.1: where = terminator.kexe`kfun:main(kotlin.Array<kotlin.String>) + 4 at hello.kt:2, address = terminator.kexe[0x00000001000012e4], unresolved, hit count = 0

gdb

  • by regex
  1. (gdb) rbreak main(
  2. Breakpoint 1 at 0x1000109b4
  3. struct ktype:kotlin.Unit &kfun:main(kotlin.Array<kotlin.String>);
  • by name unusable, because : is a separator for the breakpoint by location
  1. (gdb) b kfun:main(kotlin.Array<kotlin.String>)
  2. No source file named kfun.
  3. Make breakpoint pending on future shared library load? (y or [n]) y
  4. Breakpoint 1 (kfun:main(kotlin.Array<kotlin.String>)) pending
  • by location
  1. (gdb) b hello.kt:1
  2. Breakpoint 2 at 0x100001704: file /Users/minamoto/ws/.git-trees/hello.kt, line 1.
  • by address
  1. (gdb) b *0x100001704
  2. Note: breakpoint 2 also set at pc 0x100001704.
  3. Breakpoint 3 at 0x100001704: file /Users/minamoto/ws/.git-trees/hello.kt, line 2.

Stepping

Stepping functions works mostly the same way as for C/C++ programs

Variable inspection

Variable inspections for var variables works out of the box for primitive types. For non-primitive types there are custom pretty printers for lldb in konan_lldb.py:

  1. λ cat main.kt | nl
  2. 1 fun main(args: Array<String>) {
  3. 2 var x = 1
  4. 3 var y = 2
  5. 4 var p = Point(x, y)
  6. 5 println("p = $p")
  7. 6 }
  8. 7 data class Point(val x: Int, val y: Int)
  9. λ lldb ./program.kexe -o 'b main.kt:5' -o
  10. (lldb) target create "./program.kexe"
  11. Current executable set to './program.kexe' (x86_64).
  12. (lldb) b main.kt:5
  13. Breakpoint 1: where = program.kexe`kfun:main(kotlin.Array<kotlin.String>) + 289 at main.kt:5, address = 0x000000000040af11
  14. (lldb) r
  15. Process 4985 stopped
  16. * thread #1, name = 'program.kexe', stop reason = breakpoint 1.1
  17. frame #0: program.kexe`kfun:main(kotlin.Array<kotlin.String>) at main.kt:5
  18. 2 var x = 1
  19. 3 var y = 2
  20. 4 var p = Point(x, y)
  21. -> 5 println("p = $p")
  22. 6 }
  23. 7
  24. 8 data class Point(val x: Int, val y: Int)
  25. Process 4985 launched: './program.kexe' (x86_64)
  26. (lldb) fr var
  27. (int) x = 1
  28. (int) y = 2
  29. (ObjHeader *) p = 0x00000000007643d8
  30. (lldb) command script import dist/tools/konan_lldb.py
  31. (lldb) fr var
  32. (int) x = 1
  33. (int) y = 2
  34. (ObjHeader *) p = Point(x=1, y=2)
  35. (lldb) p p
  36. (ObjHeader *) $2 = Point(x=1, y=2)
  37. (lldb)

Getting representation of the object variable (var) could also be done using the built-in runtime function Konan_DebugPrint (this approach also works for gdb, using a module of command syntax):

  1. 0:b-debugger-fixes:minamoto@unit-703(0)# cat ../debugger-plugin/1.kt | nl -p
  2. 1 fun foo(a:String, b:Int) = a + b
  3. 2 fun one() = 1
  4. 3 fun main(arg:Array<String>) {
  5. 4 var a_variable = foo("(a_variable) one is ", 1)
  6. 5 var b_variable = foo("(b_variable) two is ", 2)
  7. 6 var c_variable = foo("(c_variable) two is ", 3)
  8. 7 var d_variable = foo("(d_variable) two is ", 4)
  9. 8 println(a_variable)
  10. 9 println(b_variable)
  11. 10 println(c_variable)
  12. 11 println(d_variable)
  13. 12 }
  14. 0:b-debugger-fixes:minamoto@unit-703(0)# lldb ./program.kexe -o 'b -f 1.kt -l 9' -o r
  15. (lldb) target create "./program.kexe"
  16. Current executable set to './program.kexe' (x86_64).
  17. (lldb) b -f 1.kt -l 9
  18. Breakpoint 1: where = program.kexe`kfun:main(kotlin.Array<kotlin.String>) + 463 at 1.kt:9, address = 0x0000000100000dbf
  19. (lldb) r
  20. (a_variable) one is 1
  21. Process 80496 stopped
  22. * thread #1, queue = 'com.apple.main-thread', stop reason = breakpoint 1.1
  23. frame #0: 0x0000000100000dbf program.kexe`kfun:main(kotlin.Array<kotlin.String>) at 1.kt:9
  24. 6 var c_variable = foo("(c_variable) two is ", 3)
  25. 7 var d_variable = foo("(d_variable) two is ", 4)
  26. 8 println(a_variable)
  27. -> 9 println(b_variable)
  28. 10 println(c_variable)
  29. 11 println(d_variable)
  30. 12 }
  31. Process 80496 launched: './program.kexe' (x86_64)
  32. (lldb) expression -- Konan_DebugPrint(a_variable)
  33. (a_variable) one is 1(KInt) $0 = 0
  34. (lldb)

Known issues

  • performance of Python bindings.

Note: Supporting the DWARF 2 specification means that the debugger tool recognizes Kotlin as C89, because before the DWARF 5 specification, there is no identifier for the Kotlin language type in specification.