Welcome to the Level-Up series’ “Autopsy of a Crash” trilogy.

A few months ago, OpenAI’s internal Rockset service — the one that powers ChatGPT’s search-through-conversation-history feature — started dying in bizarre ways. A perfectly normal C++ function would finish its job, try to go home, and then jump to some ghost address and drop dead. Even weirder: in some cases, the “return address” was empty. Imagine following your GPS home, except the GPS leads you off a cliff, and at the bottom of the cliff there isn’t even a road.

This kind of death “shouldn’t exist.”

To crack this case, an investigator first needs to learn anatomy — understand what a program’s body looks like and how hitting-a-wall death actually happens. This is lesson one (⁠◕⁠‿⁠◕⁠)


🏰 Floor 0: The City Has an “Impossible Murder”

⚔️ Level 0 / 7 Autopsy of a Crash (Part 1)
0% 完成

OpenAI’s engineers noticed something eerie about these deaths: every victim died at the exact moment they were “going home.”

In the world of programs, “going home” works like this: function A calls function B, and when B finishes its job, it “returns to A” to continue. This is the most basic way programs work. It happens billions of times a day. It’s rock solid.

But these deaths were different.

  • Some victims had their return addresses overwritten with NULL — like having the GPS wiped clean, navigating to address 0
  • Some victims had their stack pointer (%rsp) mysteriously shifted by 8 bytes, as if someone had quietly turned a road sign a few degrees
  • Both types of deaths happened the instant the ret (go home) instruction ran

These weren’t ordinary bugs. Ordinary bugs write to the wrong place or calculate the wrong result. But the odds of “the return address just happens to become NULL” are so low that the engineers briefly suspected the kernel was broken, or the compiler had a bug, or the hardware was failing.

Mogu going off-topic:

Don’t worry about figuring out “why this happens” yet — that’s Post B and Post C territory. Post A’s mission is: learn to read a program’s corpse. You want to solve a murder? First you need to know anatomy.

This lesson builds four core concepts:

  1. %rip: Where the victim is right now
  2. Stack: A stack of visit notes
  3. call / ret: How to visit someone, how to go home
  4. segfault: Hitting-a-wall death

Once you understand these four things, you can read a crime scene.

小測驗

What do these deaths have in common?


🏰 Floor 1: %rip — Where the Victim Is Right Now

⚔️ Level 1 / 7 Autopsy of a Crash (Part 1)
14% 完成

A CPU only executes one instruction at a time. Like how a person can only be in one place at a time.

So how does the CPU know “which instruction to execute right now”? The answer is a register called %rip.

Mogu roast time:

The full name of %rip is instruction pointer. The r at the front is an x86-64 naming convention indicating a 64-bit register. And rip just happens to sound like R.I.P. (rest in peace) — coincidence? When investigating program deaths, the pun is surprisingly fitting (⁠¬⁠‿⁠¬⁠)

Here’s roughly how the CPU works:

  1. Look at where %rip points
  2. Go to that address and read an instruction
  3. Execute the instruction
  4. Move %rip to the next instruction
  5. Repeat

When a program crashes, the value of %rip is “where the victim was last.” The first thing an investigator does at a crime scene is usually checking what address %rip stopped at.

If %rip points to “somewhere that isn’t an instruction” — say, a chunk of data, or an address that doesn’t even exist — the program dies. Because the CPU tries to read an instruction at that address, finds it isn’t an instruction, and the kernel kills the program.

小測驗

What is the function of %rip?


🏰 Floor 2: Stack — A Stack of Visit Notes

⚔️ Level 2 / 7 Autopsy of a Crash (Part 1)
29% 完成

Programs don’t have just one function. Function A calls B, B calls C, C might call D. This layered chain of “visits” needs a mechanism to record “where to go back afterward.”

That mechanism is the stack.

This stack of notes has some important properties:

  • Last In, First Out (LIFO): The last note put down is the first one torn off
  • Each note has a “return address”: Tells the CPU “where to go after this function finishes”
  • %rsp: A finger that always points at the top note
Mogu butts in:

The full name of %rsp is stack pointer — the sp is short for those two words. This “finger” is crucial — when the CPU needs to find the top note, it looks at where %rsp points. If this finger gets moved around randomly, the entire “going home” mechanism collapses. This is why that phrase “stack pointer shifted by 8 bytes” sounds so terrifying.

Each note occupies a block of space on the stack called a stack frame. A typical stack frame contains:

  • Return address: Where to go after this function finishes
  • Local variables: Data this function uses internally
  • The previous layer’s %rbp (frame pointer): Like a bookmark, helps with positioning
小測驗

When function A calls function B, what happens to the stack?


🏰 Floor 3: call / ret — Visiting and Going Home

⚔️ Level 3 / 7 Autopsy of a Crash (Part 1)
43% 完成

Now let’s map the visit-note analogy to actual CPU instructions.

When function A wants to call function B, it executes the call instruction. call does two things:

  1. Put down a note: Push “the address of A’s next instruction” onto the stack (this is the return address)
  2. Jump there: Set %rip to the address of B’s first instruction

When function B finishes and wants to return to A, it executes the ret instruction. ret also does two things:

  1. Tear off the note: Pop the return address from the top of the stack
  2. Jump back: Set %rip to that return address

There’s also a register called %rbp (frame pointer) that works like a bookmark clipped to one of the notes. The compiler uses it to locate “where the current function’s stack frame is.” When debugging, the investigator can start from %rbp and trace upward layer by layer, reconstructing the entire call chain.

Mogu inner monologue:

The bp in %rbp stands for base pointer. Some compilers will omit %rbp for performance (with the -fomit-frame-pointer flag), but Rockset did the opposite — they enabled -fno-omit-frame-pointer, so the investigator could trace the entire call chain from %rbp. This seemingly minor compiler flag ended up saving their lives — that’s foreshadowing, remember it.

The whole mechanism is incredibly stable — as long as the return address on the note is correct, ret will obediently jump back to the right place.

But what if the note gets tampered with?

小測驗

What happens when the ret instruction executes?


🏰 Floor 4: segfault — Hitting-a-Wall Death

⚔️ Level 4 / 7 Autopsy of a Crash (Part 1)
57% 完成

Now you understand the call / ret mechanism. Next: how exactly does a program die?

Suppose the return address on the stack gets changed to an address that “isn’t an instruction” — maybe a chunk of data, maybe a virtual address that doesn’t map to any physical memory, maybe NULL (address 0).

The ret instruction won’t check if the return address makes sense. It just:

  1. Pops that number from the stack
  2. Sets %rip to that number
  3. Tries to execute the “instruction” at that address

If that address isn’t actually an executable instruction, the CPU throws a fault. The kernel receives it and kills the program. That’s a segfault.

Mogu chimes in:

The full name of segfault is segmentation fault, from the early days when operating systems divided memory into different “segments.” When a program tries to access a segment it doesn’t have permission for (or one that doesn’t exist), it triggers a fault. Modern OS segfaults are usually about accessing invalid virtual addresses, not necessarily about “segmentation,” but the name stuck. When engineers say “it segfaulted,” they basically mean “it hit a wall and died.”

In OpenAI’s batch of deaths, the victims’ return addresses were changed to NULL (address 0). The ret instruction set %rip to 0, the CPU tried to read an instruction at address 0, but address 0 is usually deliberately left unmapped (to catch “null pointer access” bugs), and the kernel killed the program.

The core question emerges: Who tampered with the note?

小測驗

How does a segfault (hitting-a-wall death) happen?


🏰 Floor 5: C++ Has No memory safety — A City with No Guardrails

⚔️ Level 5 / 7 Autopsy of a Crash (Part 1)
71% 完成

If the call / ret mechanism is so stable, how could the note get tampered with?

The answer has to do with C++ as a language.

C++ gives programmers “low-level control” — you can directly manipulate memory, directly control pointers, directly decide where data lives. This control brings extreme performance, but also risk: nothing stops you from writing to places you shouldn’t write.

This “can write anywhere” property is called “lacking memory safety” in programming language terms.

By contrast, some languages have “guardrails”:

  • Rust: Has a borrow checker that verifies memory access legality at compile time
  • Languages with garbage collection (Java, Go, Python): Manage memory at runtime, don’t let programs write wherever they want

Those languages have guardrails in their cities. Want to write on someone else’s notes? The compiler or runtime blocks you.

C++ chose: no guardrails, let programmers be responsible. The upside is performance — no overhead from guardrails. The downside: once you write to the wrong place, the note gets tampered with, you hit a wall and die, and it’s very hard for investigators to catch the culprit.

Mogu going off-topic:

OpenAI’s Rockset service chose C++ for performance and memory efficiency — processing massive real-time queries requires squeezing out every bit of performance. But the trade-off is: when bugs happen, you get these bizarre “tampered note, hit-wall death” murders. This is the classic C++ trade-off: the price of performance is “no guardrails.”

小測驗

Why can the return address on a C++ program's stack get tampered with?


🏰 Boss Floor: Re-enacting the Murder

⚔️ Level 6 / 7 Autopsy of a Crash (Part 1)
86% 完成

Now the investigator has enough anatomical knowledge to re-enact the murder.

Let’s connect the clues:

  1. Function A calls function B: The call instruction puts a note on the stack, writing “after B finishes, return to some address in A”
  2. Function B executes normally: Does its job, no problems
  3. At some point, the note gets tampered with: The return address becomes NULL (address 0)
  4. Function B executes ret: Pops the return address from the stack (now NULL), sets %rip to NULL
  5. CPU tries to execute the instruction at address 0: Address 0 doesn’t map to any memory
  6. Hits wall and dies: The kernel kills the program, leaving a corpse (crime scene)

The investigator now knows “how they died”:

  • %rip ended up pointing to NULL
  • The return note on the stack was changed to NULL
  • ret followed it, then hit a wall

But the key question remains: Who tampered with the note?

Staring at a single corpse won’t reveal the killer.

The engineers tried many things: reading code, making hypotheses, ruling them out one by one. But every hypothesis had counterexamples. Some deaths had stack pointers shifted by 8 bytes, others had return addresses set to NULL — they looked like the same cause of death, but the details didn’t quite match.

It’s like facing a series of murders where every corpse has slightly different wounds. The investigator hit a wall.

To crack this, you need a different approach.

Not staring at one corpse, but pulling out every corpse from the entire city’s past year, cataloging them, classifying them. This is the epidemiologist’s approach: find patterns, don’t solve individual cases.

Mogu roast time:

A small spoiler: OpenAI’s engineers eventually discovered that what looked like one bug was actually two completely unrelated bugs. One was hardware failure (a certain Azure host’s CPU was doing arithmetic wrong), the other was a race condition that had been hiding in libunwind for 18 years. Because they kept treating it as a single bug, they couldn’t catch either. Once they looked at them separately, the answers became instantly clear. How they did that is the main course in Post B.

The investigator has learned to read corpses. Next lesson: entering the crime scene.

小測驗

What does the investigator currently know vs. not know?


🎓 Level Clear Summary

The investigator learned four things in this lesson:

ConceptOne-Sentence Explanation
%ripWhere the victim is right now — which instruction the CPU is executing
StackA stack of visit notes, recording “where to return” for each function layer
call / retLeave a note before going out / Tear off note and follow it home
segfaultNote tampered with to invalid address → ret follows it → hits wall and dies

The investigator also learned about C++, a city with no guardrails: anyone can tamper with anyone’s notes. Extremely high performance, but when things go wrong, it’s very hard to catch the culprit.

Now the investigator understands “how they died,” but still doesn’t know “who did it.”

Next lesson, the investigator enters the crime scene — that sealed autopsy report (the core dump). You’ll learn what the “police tape line” (red zone) is, what the “first responder on scene” (signal handler) does, and why looking at individual cases will never catch the killer.

Upgrading from coroner to epidemiologist is the only way to crack this case (⁠⌐⁠■⁠_⁠■⁠)

🔗 Further Reading

Post B will heavily use the signal mechanism — that SIGUSR2 that gets sent every few milliseconds and eventually becomes the murder weapon is part of this family. Want to warm up, or like this “breaking down low-level concepts for beginners” tower-climbing style? These two from the same series are good follow-ups:

(Post B coming next)