6.3.7 调试 Kotlin/Native
原文链接: https://kotlinlang.org/docs/native-debugging.html
6.3.7 调试 Kotlin/Native
Kotlin/Native 编译器可以生成带调试信息的二进制文件,也可以创建用于符号化崩溃报告的调试符号文件。
调试信息与 DWARF 2 规范兼容,因此 LLDB 和 GDB 等现代调试器工具可以:
注意: 支持 DWARF 2 规范意味着调试器工具会把 Kotlin 识别为 C89,因为在 DWARF 5 规范之前,规范中还没有用于 Kotlin 语言类型的标识符。
在 IntelliJ IDEA、Android Studio 或 Xcode 中调试时,带调试信息的二进制文件会自动生成(除非构建配置有所不同)。
你可以手动启用调试,并通过以下方式生成包含调试信息的二进制文件:
- 使用 Gradle 任务。要获得调试二进制文件,请使用
linkDebug* Gradle 任务,例如:
1
| ./gradlew linkDebugFrameworkNative
|
这些任务会因二进制类型(例如 linkDebugSharedNative)或你的目标(例如 linkDebugExecutableMacosArm64)而有所不同。
- 使用命令行编译器。在命令行中,使用
-g 选项编译你的 Kotlin/Native 二进制文件:
1
| kotlinc-native hello.kt -g -o terminator
|
然后启动你的调试器工具。例如:
调试器会输出:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
| $ cat - > hello.kt
fun main(args: Array<String>) {
println("Hello world")
println("I need your clothes, your boots and your motorcycle")
}
$ dist/bin/konanc -g hello.kt -o terminator
KtFile: hello.kt
$ lldb terminator.kexe
(lldb) target create "terminator.kexe"
Current executable set to 'terminator.kexe' (x86_64).
(lldb) b kfun:main(kotlin.Array<kotlin.String>)
Breakpoint 1: where = terminator.kexe`kfun:main(kotlin.Array<kotlin.String>) + 4 at hello.kt:2, address = 0x00000001000012e4
(lldb) r
Process 28473 launched: '/Users/minamoto/ws/.git-trees/debugger-fixes/terminator.kexe' (x86_64)
Process 28473 stopped
* thread #1, queue = 'com.apple.main-thread', stop reason = breakpoint 1.1
frame #0: 0x00000001000012e4 terminator.kexe`kfun:main(kotlin.Array<kotlin.String>) at hello.kt:2
1 fun main(args: Array<String>) {
-> 2 println("Hello world")
3 println("I need your clothes, your boots and your motorcycle")
4 }
(lldb) n
Hello world
Process 28473 stopped
* thread #1, queue = 'com.apple.main-thread', stop reason = step over
frame #0: 0x00000001000012f0 terminator.kexe`kfun:main(kotlin.Array<kotlin.String>) at hello.kt:3
1 fun main(args: Array<String>) {
2 println("Hello world")
-> 3 println("I need your clothes, your boots and your motorcycle")
4 }
(lldb)
|
设置断点
现代调试器提供了几种设置断点的方式。下面按工具分别说明:
LLDB
1
2
| (lldb) b -n kfun:main(kotlin.Array<kotlin.String>)
Breakpoint 4: where = terminator.kexe`kfun:main(kotlin.Array<kotlin.String>) + 4 at hello.kt:2, address = 0x00000001000012e4
|
-n 是可选的,默认就会应用。
1
2
| (lldb) b -f hello.kt -l 1
Breakpoint 1: where = terminator.kexe`kfun:main(kotlin.Array<kotlin.String>) + 4 at hello.kt:2, address = 0x00000001000012e4
|
1
2
| (lldb) b -a 0x00000001000012e4
Breakpoint 2: address = 0x00000001000012e4
|
- 按正则表达式。在调试生成的产物(例如名称中包含
# 符号的 lambda)时,你可能会发现它很有用:
1
2
3
| (lldb) b -r main\(
3: regex = 'main\(', locations = 1
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
1
2
3
| (gdb) rbreak main(
Breakpoint 1 at 0x1000109b4
struct ktype:kotlin.Unit &kfun:main(kotlin.Array<kotlin.String>);
|
- 按名称是_不_可行的,因为
: 是按位置设置断点的分隔符:
1
2
3
4
| (gdb) b kfun:main(kotlin.Array<kotlin.String>)
No source file named kfun.
Make breakpoint pending on future shared library load? (y or [n]) y
Breakpoint 1 (kfun:main(kotlin.Array<kotlin.String>)) pending
|
1
2
| (gdb) b hello.kt:1
Breakpoint 2 at 0x100001704: file /Users/minamoto/ws/.git-trees/hello.kt, line 1.
|
1
2
3
| (gdb) b *0x100001704
Note: breakpoint 2 also set at pc 0x100001704.
Breakpoint 3 at 0x100001704: file /Users/minamoto/ws/.git-trees/hello.kt, line 2.
|
使用单步执行
单步执行函数的方式与 C/C++ 程序基本相同。
检查变量
对 var 变量的变量检查对原始类型和非原始类型都可以直接使用:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
| $ cat -n main.kt
1 fun main(args: Array<String>) {
2 var x = 1
3 var y = 2
4 var p = Point(x, y)
5 println("p = $p")
6 }
7
8 data class Point(val x: Int, val y: Int)
$ lldb ./program.kexe -o 'b main.kt:5' -o
(lldb) target create "./program.kexe"
Current executable set to './program.kexe' (x86_64).
(lldb) b main.kt:5
Breakpoint 1: where = program.kexe`kfun:main(kotlin.Array<kotlin.String>) + 289 at main.kt:5
(lldb) r
Process 4985 stopped
* thread #1, name = 'program.kexe', stop reason = breakpoint 1.1
frame #0: program.kexe`kfun:main(kotlin.Array<kotlin.String>) at main.kt:5
2 var x = 1
3 var y = 2
4 var p = Point(x, y)
-> 5 println("p = $p")
6 }
7
8 data class Point(val x: Int, val y: Int)
Process 4985 launched: './program.kexe' (x86_64)
(lldb) fr var
(int) x = 1
(int) y = 2
(ObjHeader *) p = Point(x=1, y=2)
(lldb) v p->x
(int32_t) p->x = 1
|
调试 iOS 应用
调试 iOS 应用有时需要详细分析崩溃报告。崩溃报告通常需要符号化(symbolication),即把内存地址转换为可读的源码位置的过程。
要符号化 Kotlin 代码中的地址(例如与 Kotlin 代码对应的堆栈跟踪元素),你需要一个特殊的调试符号(.dSYM)文件。该文件把崩溃报告中的内存地址与源码中的实际位置(例如函数或行号)对应起来。
在 Apple 平台上,Kotlin/Native 编译器会默认为 release(优化过的)二进制文件生成 .dSYM 文件。在 Xcode 中构建时,IDE 会在标准位置查找 .dSYM 文件,并自动用它们进行符号化。对于由 IntelliJ IDEA 模板创建的项目,Xcode 会自动检测 .dSYM 文件。
在其他平台上,你可以使用 -Xadd-light-debug 编译器选项把调试信息加入生成的二进制文件(这会增大文件体积):
Kotlin
1
2
3
4
5
6
7
| kotlin {
targets.withType<org.jetbrains.kotlin.gradle.plugin.mpp.KotlinNativeTarget> {
binaries.all {
freeCompilerArgs += "-Xadd-light-debug=enable"
}
}
}
|
Groovy
1
2
3
4
5
6
7
| kotlin {
targets.withType(org.jetbrains.kotlin.gradle.plugin.mpp.KotlinNativeTarget) {
binaries.all {
freeCompilerArgs += "-Xadd-light-debug=enable"
}
}
}
|
关于崩溃报告的更多信息,请参阅 Apple 文档。
已知问题
- Python 绑定的性能。
- 不支持在调试器工具中进行表达式求值,目前也没有实现它的计划。
接下来做什么?
了解如何为 iOS 代码中未处理的 Kotlin 异常改进崩溃报告