Skip to content

feat: debugging K/N page revamp #4962

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 9 commits into from
Aug 5, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 0 additions & 1 deletion docs/kr.tree
Original file line number Diff line number Diff line change
Expand Up @@ -200,7 +200,6 @@
<toc-element toc-title="Migration guide" topic="native-migration-guide.md"/>
</toc-element>
<toc-element topic="native-debugging.md"/>
<toc-element topic="native-ios-symbolication.md"/>
<toc-element toc-title="Reference and tips">
<toc-element toc-title="Target support" topic="native-target-support.md"/>
<toc-element toc-title="Improving compilation time" topic="native-improving-compilation-time.md"/>
Expand Down
222 changes: 149 additions & 73 deletions docs/topics/native/native-debugging.md
Original file line number Diff line number Diff line change
@@ -1,25 +1,54 @@
[//]: # (title: Debugging 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.
The Kotlin/Native compiler can generate binaries with debug information, as well as create debug symbol files for [symbolicating
crash reports](#debug-ios-applications).

The debug information is compatible with the [DWARF 2](https://dwarfstd.org/download.html) specification, so modern debugger
tools, like LLDB and GDB can:

* [Set breakpoints](#set-breakpoints)
* [Use stepping](#use-stepping)
* [Inspect variable and type information](#inspect-variables)

> 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 the specification.
>
{style="note"}

## Produce binaries with debug info with Kotlin/Native compiler
## Generate binaries with debug information

When debugging in IntelliJ IDEA, Android Studio, or Xcode, binaries with debug information are generated automatically
(unless the build is configured differently).

You can enable debugging manually and produce binaries that include debug information by:

* **Using Gradle tasks**. To get debug binaries, use `linkDebug*` Gradle tasks, for example:

```bash
./gradlew linkDebugFrameworkNative
```

The tasks differ depending on the binary type (for example, `linkDebugSharedNative`) or your target (for example, `linkDebugExecutableMacosX64`).

To produce binaries with the Kotlin/Native compiler, use the ``-g`` option on the command line.
* **Using the command-line compiler**. In the command line, compile your Kotlin/Native binary with the `-g` option:

```bash
kotlinc-native hello.kt -g -o terminator
```

Then launch your debugger tool. For example:

```bash
lldb terminator.kexe
```

The debugger outputs:

```bash
0:b-debugger-fixes:minamoto@unit-703(0)# cat - > hello.kt
fun main(args: Array<String>) {
println("Hello world")
println("I need your clothes, your boots and your motocycle")
println("I need your clothes, your boots and your motorcycle")
}
0:b-debugger-fixes:minamoto@unit-703(0)# dist/bin/konanc -g hello.kt -o terminator
KtFile: hello.kt
Expand All @@ -35,7 +64,7 @@ Process 28473 stopped
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 motocycle")
3 println("I need your clothes, your boots and your motorcycle")
4 }
(lldb) n
Hello world
Expand All @@ -44,89 +73,90 @@ Process 28473 stopped
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 motocycle")
-> 3 println("I need your clothes, your boots and your motorcycle")
4 }
(lldb)
```

## Breakpoints
## Set breakpoints

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

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

### lldb
* By name:

- by name
```bash
(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
```

```bash
(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` is optional, it's applied by default.

_``-n`` is optional, this flag is applied by default_
- by location (filename, line number)
* By location (filename, line number):

```bash
(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
```
```bash
(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
```

- by address
* By address:

```bash
(lldb) b -a 0x00000001000012e4
Breakpoint 2: address = 0x00000001000012e4
```
```bash
(lldb) b -a 0x00000001000012e4
Breakpoint 2: address = 0x00000001000012e4
```

- by regex, you might find it useful for debugging generated artifacts, like lambda etc. (where used ``#`` symbol in name).
* By regex. You might find it useful for debugging generated artifacts, like a lambda (with the `#` symbol in the name):

```bash
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
```
```bash
(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
### GDB

- by regex
* By regex:

```bash
(gdb) rbreak main(
Breakpoint 1 at 0x1000109b4
struct ktype:kotlin.Unit &kfun:main(kotlin.Array<kotlin.String>);
```
```bash
(gdb) rbreak main(
Breakpoint 1 at 0x1000109b4
struct ktype:kotlin.Unit &kfun:main(kotlin.Array<kotlin.String>);
```

- by name __unusable__, because ``:`` is a separator for the breakpoint by location

```bash
(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
```
* By name is _not_ possible because `:` is a separator for the breakpoint by location:

- by location
```bash
(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
```

```bash
(gdb) b hello.kt:1
Breakpoint 2 at 0x100001704: file /Users/minamoto/ws/.git-trees/hello.kt, line 1.
```
* By location:

- by address
```bash
(gdb) b hello.kt:1
Breakpoint 2 at 0x100001704: file /Users/minamoto/ws/.git-trees/hello.kt, line 1.
```

```bash
(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.
```
* By address:

## Stepping
```bash
(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.
```

Stepping functions works mostly the same way as for C/C++ programs.
## Use stepping

## Variable inspection
Stepping through functions works mostly the same way as for C/C++ programs.

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`:
## Inspect variables

Variable inspection 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`:

```bash
λ cat main.kt | nl
Expand Down Expand Up @@ -173,9 +203,8 @@ Process 4985 launched: './program.kexe' (x86_64)
(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):
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-specific syntax):

```bash
0:b-debugger-fixes:minamoto@unit-703(0)# cat ../debugger-plugin/1.kt | nl -p
Expand Down Expand Up @@ -213,9 +242,56 @@ Process 80496 launched: './program.kexe' (x86_64)
(lldb) expression -- (int32_t)Konan_DebugPrint(a_variable)
(a_variable) one is 1(int32_t) $0 = 0
(lldb)
```

## Debug iOS applications

Debugging iOS applications sometimes involves analyzing crash reports in detail. Crash reports typically require
symbolication, the process of translating memory addresses into readable source code locations.

To symbolicate addresses in Kotlin code (for example, for stack trace elements corresponding to Kotlin code), you need a
special debug symbol (`.dSYM`) file. This file maps memory addresses in crash reports with actual locations in the source
code, such as functions or line numbers.

The Kotlin/Native compiler generates `.dSYM` files for release (optimized) binaries on Apple platforms by default.
When building in Xcode, the IDE looks for `.dSYM` files in standard locations and uses them automatically for symbolication.
Xcode automatically detects `.dSYM` files in projects created from IntelliJ IDEA templates.

On other platforms, you can add debug information into the produced binaries (which increases their size)
using the `-Xadd-light-debug` compiler option:

<tabs group="build-script">
<tab title="Kotlin" group-key="kotlin">

```kotlin
kotlin {
targets.withType<org.jetbrains.kotlin.gradle.plugin.mpp.KotlinNativeTarget> {
binaries.all {
freeCompilerArgs += "-Xadd-light-debug=enable"
}
}
}
```

</tab>
<tab title="Groovy" group-key="groovy">

```groovy
kotlin {
targets.withType(org.jetbrains.kotlin.gradle.plugin.mpp.KotlinNativeTarget) {
binaries.all {
freeCompilerArgs += "-Xadd-light-debug=enable"
}
}
}
```

</tab>
</tabs>

For more information about crash reports, see the [Apple documentation](https://developer.apple.com/documentation/xcode/diagnosing-issues-using-crash-reports-and-device-logs).

## Known issues

- performance of Python bindings.
* Performance of Python bindings.
* Expression evaluation in debugger tools is not supported, and currently there are no plans for implementing it.
90 changes: 0 additions & 90 deletions docs/topics/native/native-ios-symbolication.md

This file was deleted.

Loading
Loading