Skip to content
Klarion

Changelog

Every build, and what was wrong with it.

One entry per published build, newest first, each identified by the checksum of the file itself rather than by a version number two builds can share. What was known to be broken at the time is listed beside what was fixed.

  • 1.2.2

    d25174e45 September 2026

    Five bugs that could make a whole function come out wrong, all of them the analysis knowing something the decompiler never heard.

    Fixed

    • **A stripped ARM binary was decoded as ARM even when it is Thumb.** Every piece of evidence the loader weighed lives in the symbol table, so a stripped image had none and the ARM default stood. That is wrong from the first byte: 70 47, which is bx lr and the commonest instruction in Thumb code, disassembles as svclt #0x4770, and pseudocode built on that listing is confident and fictional. Bit 0 of the ELF entry point survives stripping and is the ARM ABI's own statement of which state execution begins in, so it is consulted now — but only when nothing better is available, so it cannot outvote real evidence on an image that still has some. Across 58 stripped Cortex-M firmware images, files containing bad instruction data fell from 51 to 5.
    • **Calls into a shared library showed no name on a recent Linux build.** Those binaries carry two procedure linkage tables: .plt holds the lazy-binding stubs, and an endbr64-guarded stub in .plt.sec is what every call is actually compiled against. Only .plt was named, so the addresses a program really calls were anonymous — sub_3a50() where the name is exit(). Both are named now.
    • **Functions ran past their end into the next one.** Three separate causes, each enough on its own. exit and abort reached through a PLT stub were not recognised as never returning, so the call kept a path after it that does not exist. A local wrapper around one of them was recognised and then lost the mark a moment later, because the rebuild that recognising it triggered took the function's flags fresh and dropped it. And which block ended a function was read from a graph built before any of this was known. A one-block routine could come out carrying thirty-seven blocks of its neighbours.
    • **A tail call was pasted in whole instead of shown as a call.** A jmp to another function is a call that reuses the current frame, and compilers emit it constantly for a wrapper whose last act is one call. The decompiler was following it and inlining the callee, so a two-line function came back as a hundred lines of borrowed registers it never set.
    • **A segment's zero-fill tail was decoded as code.** It is mapped but backed by no bytes in the file and reads as zeroes, and a run of zeroes is a valid instruction on most architectures — so sweeping one manufactured code out of nothing. Instruction fetches there are refused now, while data reads still read zero, which is what a .bss actually holds.
    • Analysis could give different answers at different worker counts in one narrow case, and --arch was accepted and then ignored on ELF.

    Known limitations

    • Eight of the 58 ARM images in that corpus carry no entry-point Thumb bit and are still decoded as ARM. Reading .ARM.attributes covers them and is not done yet.
    • The exactness above is measured for control-flow structure only. Type recovery and byte-exact recompilation are not measured by it.
    • The installer is still not code-signed, so SmartScreen warns on download. The SHA-256 is on the download page; check it before running.

Running an older build?

The eight characters beside each version are the start of that installer’s SHA-256, which the download page publishes in full — so the way to tell which build you have is to check the file you installed from. Inside Klarion, Help › About and klarion-cli --version report the version and the commit it was built from, which is a different identifier and does not appear here.

Latest build