Writing an x86 disassembler in Kotlin/Native is making me lose my mind (KT-90596)

Hey everyone,

I’m currently writing a standalone, bare-metal x86-64 machine code disassembler and binary reverse engineering engine completely from scratch in pure Kotlin/Native (targeting Windows mingwX64).

I chose Kotlin/Native because it’s a fantastic alternative to C or Rust. But right now, the language design is fighting me on two very basic systems-programming tasks. I’ve hit so much syntactic friction that I had to open a YouTrack ticket to track it: KT-90596.

Here is the code I’m forced to write right now just to decode a block of bytes and print a standard hexdump/assembly line:

kotlin

@OptIn(kotlinx.cinterop.ExperimentalForeignApi::class)
fun decodeBlock(buffer: ByteArray, offset: Int) {
    // 1. Friction: Messy block closures just to pin a standard array
    buffer.usePinned { pinned ->
        // 2. Friction: Forced to use raw C-pointers for simple byte math
        val opcode = pinned.addressOf(offset).reinterpret<ByteVar>().toInt() and 0xFF
        
        // 3. Friction: Forced to use POSIX printf because Kotlin lacks native string alignment
        platform.posix.printf("0x%08X: %02X \n", offset, opcode)
    }
}

Use code with caution.

why i think this is a nightmare for low-level dev:

  1. No Built-in Native String Formatter: Because java.lang.String.format doesn’t exist on native targets, I have to abandon Kotlin style completely and drop into raw Windows C-runtime tools (platform.posix.printf) just to pad and align basic hexadecimal memory addresses.
  2. Boilerplate Overload for Array Pinning: x86 instructions are variable-length. To step through a binary, I need to constantly look at pointer offsets. Forcing endless nested usePinned blocks and @OptIn annotations for routine byte reading completely ruins readability.

Whenever someone brings this up, the usual answer is: “Just use an external library like kotlinx-io!”

But for reverse engineering utilities or security tools, that is a non-starter. Portability is everything. I need a tight, single-file, zero-dependency executable. I shouldn’t have to pull in a heavy Gradle multiplatform build structure just to format text or read a byte offset.

If you set up an LLVM environment using something like Martin Stördsjö’s llvm-mingw toolchain, it bundles its entire C++ Standard Template Library (libc++) right out of the box. You get native, clean access to formatting and memory mapping. Kotlin/Native should treat its systems developers the same way.

If you’ve ever felt the pain of writing messy C-interop blocks or wished we had a lightweight, type-safe multiplatform String.format engine built directly into the standard library, please check out the ticket and drop an upvote: https://youtrack.jetbrains.com/issue/KT-90596/Language-DesignNative-Provide-a-core-native-string-formatting-engine-and-reduce-boilerplate-for-low-level-memory-interop i would like to hear feedback from engineers

Maybe I’m misunderstanding the problem, but I don’t see why:

fun decodeBlock(buffer: UByteArray, offset: Int) {
    val opcode = buffer[offset]
    println("0x${offset.toHexString(HexFormat.UpperCase)}: ${opcode.toHexString(HexFormat.UpperCase)}")
}

fun main() {
    val buffer = UByteArray(2048)
    buffer[0xCA] = 0xFEu
    decodeBlock(buffer, 0xCA) // 0x000000CA: FE
}

isn’t enough
You can access bytes from a ByteArray just fine (I’m using UByteArray since it befits your domain better, but either of them work), and you can use toHexString and HexFormat to create hex strings.
In Kotlin, we tend to use template strings instead of printf, but it depends on the situation.

1 Like

Thats not my point padding znd layout still fail for me also its syntactic noise Hexformat is great for conversion but it doesnt solve the padding and layout An x86_64 disassembler is variable length array indexing is not what a disassemnler is supposed to be c interpp is messy syntax and zero copy is hard to do without using cintreop and posix

This is not the language you are looking for this task

2 Likes