调试 Kotlin/Native

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

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.

调试 Kotlin/Native - 图1

Produce binaries with debug info with Kotlin/Native compiler

To produce binaries with the Kotlin/Native compiler, use the -g option on the command line.

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  }

9. 7  data class Point(val x: Int, val y: Int)

11. λ lldb ./program.kexe -o 'b main.kt:5' -o
12. (lldb) target create "./program.kexe"
13. Current executable set to './program.kexe' (x86_64).
14. (lldb) b main.kt:5
15. Breakpoint 1: where = program.kexe`kfun:main(kotlin.Array<kotlin.String>) + 289 at main.kt:5, address = 0x000000000040af11
16. (lldb) r
17. Process 4985 stopped
18. * thread #1, name = 'program.kexe', stop reason = breakpoint 1.1
19. frame #0: program.kexe`kfun:main(kotlin.Array<kotlin.String>) at main.kt:5
20. 2        var x = 1
21. 3        var y = 2
22. 4        var p = Point(x, y)
23. -> 5        println("p = $p")
24. 6    }
25. 7
26. 8    data class Point(val x: Int, val y: Int)

28. Process 4985 launched: './program.kexe' (x86_64)
29. (lldb) fr var
30. (int) x = 1
31. (int) y = 2
32. (ObjHeader *) p = 0x00000000007643d8
33. (lldb) command script import dist/tools/konan_lldb.py
34. (lldb) fr var
35. (int) x = 1
36. (int) y = 2
37. (ObjHeader *) p = [x: ..., y: ...]
38. (lldb) p p
39. (ObjHeader *) $2 = [x: ..., y: ...]
40. (lldb) script lldb.frame.FindVariable("p").GetChildMemberWithName("x").Dereference().GetValue()
41. '1'
42. (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   }

32. Process 80496 launched: './program.kexe' (x86_64)
33. (lldb) expression -- (int32_t)Konan_DebugPrint(a_variable)
34. (a_variable) one is 1(int32_t) $0 = 0
35. (lldb)

Known issues

  • performance of Python bindings.