Skip to content

0.9.1 --- the second register comes back from every call - #20

Merged
Sunrisepeak merged 1 commit into
mainfrom
second-register-is-returned
Sep 14, 2026
Merged

0.9.1 --- the second register comes back from every call#20
Sunrisepeak merged 1 commit into
mainfrom
second-register-is-returned

Conversation

@Sunrisepeak

Copy link
Copy Markdown
Member

Every program built with --release over openkal-macos faulted before main.

Found through lsp-mcpp: the release server, and a minimal import std; std::println program over openkal-llvm-runtime 0.9.4, both stop in the C library's initializer on macos-14:

* thread #1, stop reason = EXC_BAD_ACCESS (code=1, address=0x0)
  * frame #0: kal_fs_preopen + 288        ldrb w11, [x10], #0x1    ; x10 = 0
    frame #1: okm_table_init + 608
    frame #2: __okm_libc_init + 400
    frame #3: dyld ... findAndRunAllInitializers

This kernel returns a second value in x1 from every system call (rdx on x86_64) and writes it whether or not the call has one; duplicate() and pipe_pair() already read it. The generic sys() declared x1 an input only, so an optimizing compiler assumed it survived the call. The release table(), disassembled:

adrp  x1, "/"@PAGE ; add x1, x1, "/"@PAGEOFF    ; path argument of openat
svc   #0x80                                     ; the kernel clears x1
...
stp   x1, x9, [x8]                              ; t[1] = { x1, 1 }  -> name == NULL

kal_fs_preopen(1) then copied the name from address zero. The dev profile keeps nothing in a register across a call, which is why it never showed.

  • src/sys.h: x1 is an output of sys() as well as an input on arm64, and rdx on x86_64. Compiled with -O2, table() now keeps "/" in a callee-saved register across the call.
  • -fno-builtin beside -fno-exceptions -fno-rtti -fno-stack-protector: the same release build turned okm::length into a call to strlen, a C library symbol this package does not reference by design (openkal-windows 0.7.1 made the same change for the same reason).
  • CI: this package's own tests and the "objects reference nothing of a C library" check run in the dev and release profiles; conformance_process_task enumerates every preopen and is what would have failed.
  • 0.9.1.

Every source compiles with -O2 for arm64 and x86_64 macOS, and the arm64 object has no undefined strlen. The release rows of this CI are the measurement on the machine itself.

This kernel returns a second value in x1 (rdx on x86_64) from every system call,
and the wrapper declared that register an input only. An optimizing compiler
therefore kept the first argument there across the call: the preopen table
stored the address of "/" from x1 after openat had cleared it, and every program
built with --release faulted in kal_fs_preopen before main. The register is now
an output as well. -fno-builtin keeps a counting loop from becoming strlen, and
CI runs this package's tests and the independence check in both profiles.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant