Tech News
← Back to articles

My application programmer instincts failed when debugging assembler

read original related products more articles

I’ve had a smidge of extra time with my recent unemployment, so to stay sharp and learn a few new things I followed Seiya Nuta’s guide to building an Operating System in 1,000 Lines.

I’m not an OS programmer, my life is normally spent at high-level application programming. (The closest I come to the CPU is the week I spent trying to internalize the flow of those crazy speculative execution hacks.) Assembler is easy enough to write, that wasn’t the problem. The problem was when I encountered problems. My years of debugging application-level code has led to a pile of instincts that just failed me when debugging assembler-level bugs.

These are the three places I had the biggest problems debugging.

Big error #1 – I forgot a ret in a naked assembler function#

I forgot the ret in a naked assembler function. It didn’t return to its caller.

What happened next is both fun and obvious—but only when you know that you missed a ret .

That function—let’s call it the first function—didn’t return to its caller, so execution just went to the next function in the file. The input arguments were whatever happened to be in the a0 and a1 registers. And when that second function returned, it used the caller information that was still available in the ra register, and it returned to where the first function was called from.

This is something that just doesn’t happen in application programming, which meant that I had a heck of a time debugging it.

It was even harder to debug because those two functions were related. They were next to each other in the file, of course they were related. I saw that the second function was doing strange stuff, and I was expecting it to be called around that time, so I focused on that error.

My application-programmer brain went like this: Why was it failing? It was sometimes being called with junk parameters, and it was being called more often than it should be. Why? Look at the caller. Why? Investigate the calling site. Investigate any loops. Move up the calling tree. Repeat. Repeat. Repeat. Which sent me nowhere near the problem. Everything went nowhere until I read the compiled assembler and started manually tracing execution.

... continue reading