Skip to content
Tech News
← Back to articles

The NX bit is not just about security

read original get Programming with 64-Bit ARM Assembly Language (Stephen Smith) → more articles
Why This Matters

A debugging story from bare-metal ARM64 hypervisor work on postmarketOS shows how a subtle, seemingly impossible lockup bug traces back to low-level CPU behavior rather than obvious logic errors. The framing — that the NX (no-execute) bit matters beyond security — hints that memory permission bits also affect correctness and performance on ARM, something systems developers routinely overlook. It's a useful reminder of how much modern platform work depends on understanding hardware details.

Key Takeaways
Worth a Look

Programming with 64-Bit ARM Assembly Language (Stephen Smith) — If this MRS/MSR debugging saga made you curious about what's really happening under the hood on AArch64, Stephen Smith's book walks through 64-bit ARM assembly hands-on, from registers and instruction encoding to writing and debugging real code. It's a great companion for anyone poking at bare-metal ARM, hypervisors, or postmarketOS-style low-level projects.

See Programming with 64-Bit ARM Assembly Language (Stephen Smith) on Amazon → Affiliate link — we may earn a commission on purchases, at no extra cost to you. Product picked by AI based on this article; it is not a tested recommendation.

The NX bit is not just about security

September 4, 2026

While I’m taking a short break from low-level programming, here’s a story by a friend of mine, Sonya, about debugging a seemingly impossible bug in ARM code.

This bug hunting saga started several months ago. While developing a bare-metal hypervisor on ARM64 for postmarketOS, I hit a strange bug: if I enabled the CTR_EL0 intercept (which was the whole purpose of the HV, so could not be skipped), the phone would randomly lock itself up. After a few seconds, the watchdog kicked in and reset the system. At first I thought that the boot got slowed down so much that the system simply did not have enough time to boot, but disabling the watchdog did not help either (fortunately, the phone in question had removable battery, and I didn’t have to wait several hours for it to discharge). So, I started digging deeper.

Hypothesis #1: My emulation of MRS is wrong

On Aarch64, so-called Special Function Registers (which CTR_EL0 is one of) are accessed using MRS and MSR machine instructions:

mrs x3 , ctr_el0 msr ctr_el0, x3

These instructions move data between the specified SFR and the specified general-purpose register (in this case, X3 ), with all other registers remaining unchanged. So the failure must have meant that I was either corrupting some of the registers I had to preserve, or not writing the real output register correctly. Thus the first two things I verified were the exception handler trampoline:

trap_from_el1: sub sp , sp , #256 stp x0 , x1 , [ sp ] stp x2 , x3 , [ sp , #16 ] stp x4 , x5 , [ sp , #32 ] stp x28 , x29 , [ sp , #224 ] stp x30 , xzr, [ sp , #240 ] mov x0 , sp bl handle_trap_from_el1 mrs x1 , elr_el2 add x0 , x0 , x1 msr elr_el2, x0 ldp x0 , x1 , [ sp ] ldp x2 , x3 , [ sp , #16 ] ldp x4 , x5 , [ sp , #32 ] ldp x28 , x29 , [ sp , #224 ] ldp x30 , xzr, [ sp , #240 ] add sp , sp , #256 eret

And the stack allocation:

... continue reading