Kotlin Help

Debugging Kotlin/Native

The Kotlin/Native compiler can generate binaries with debug information, as well as create debug symbol files for symbolicating crash reports.

The debug information is compatible with the DWARF 2 specification, so modern debugger tools, like LLDB and GDB can:

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:

    ./gradlew linkDebugFrameworkNative

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

  • Using the command-line compiler. In the command line, compile your Kotlin/Native binary with the -g option:

    kotlinc-native hello.kt -g -o terminator

Then launch your debugger tool. For example:

lldb terminator.kexe

The debugger outputs:

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 motorcycle") } 0:b-debugger-fixes:minamoto@unit-703(0)# dist/bin/konanc -g hello.kt -o terminator KtFile: hello.kt 0:b-debugger-fixes:minamoto@unit-703(0)# 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)

Set breakpoints

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

LLDB

  • By name:

    (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.

  • By location (filename, line number):

    (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:

    (lldb) b -a 0x00000001000012e4 Breakpoint 2: address = 0x00000001000012e4
  • By regex. You might find it useful for debugging generated artifacts, like a lambda (with the # symbol in the name):

    (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

  • By regex:

    (gdb) rbreak main( Breakpoint 1 at 0x1000109b4 struct ktype:kotlin.Unit &kfun:main(kotlin.Array<kotlin.String>);
  • By name is not possible because : is a separator for the breakpoint by location:

    (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 location:

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

    (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.

Use stepping

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

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:

λ cat main.kt | nl 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 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, address = 0x000000000040af11 (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 = 0x00000000007643d8 (lldb) command script import dist/tools/konan_lldb.py (lldb) fr var (int) x = 1 (int) y = 2 (ObjHeader *) p = [x: ..., y: ...] (lldb) p p (ObjHeader *) $2 = [x: ..., y: ...] (lldb) script lldb.frame.FindVariable("p").GetChildMemberWithName("x").Dereference().GetValue() '1' (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-specific syntax):

0:b-debugger-fixes:minamoto@unit-703(0)# cat ../debugger-plugin/1.kt | nl -p 1 fun foo(a:String, b:Int) = a + b 2 fun one() = 1 3 fun main(arg:Array<String>) { 4 var a_variable = foo("(a_variable) one is ", 1) 5 var b_variable = foo("(b_variable) two is ", 2) 6 var c_variable = foo("(c_variable) two is ", 3) 7 var d_variable = foo("(d_variable) two is ", 4) 8 println(a_variable) 9 println(b_variable) 10 println(c_variable) 11 println(d_variable) 12 } 0:b-debugger-fixes:minamoto@unit-703(0)# lldb ./program.kexe -o 'b -f 1.kt -l 9' -o r (lldb) target create "./program.kexe" Current executable set to './program.kexe' (x86_64). (lldb) b -f 1.kt -l 9 Breakpoint 1: where = program.kexe`kfun:main(kotlin.Array<kotlin.String>) + 463 at 1.kt:9, address = 0x0000000100000dbf (lldb) r (a_variable) one is 1 Process 80496 stopped * thread #1, queue = 'com.apple.main-thread', stop reason = breakpoint 1.1 frame #0: 0x0000000100000dbf program.kexe`kfun:main(kotlin.Array<kotlin.String>) at 1.kt:9 6 var c_variable = foo("(c_variable) two is ", 3) 7 var d_variable = foo("(d_variable) two is ", 4) 8 println(a_variable) -> 9 println(b_variable) 10 println(c_variable) 11 println(d_variable) 12 } 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:

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

For more information about crash reports, see the Apple documentation.

Known issues

  • Performance of Python bindings.

  • Expression evaluation in debugger tools is not supported, and currently there are no plans for implementing it.

05 August 2025