map a runtime fault to the EXACT IR instruction with `opt -passes=debugify` — and its TRAP. ------------------------------------------------------------ `debugify` stamps a synthetic !DILocation onto EVERY IR instruction, with the line number = the instruction's ORDINAL (1st instr -> line 1, 2nd -> 2, ...). It is NOT the physical line in the .ll text file.
gdb will faithfully say e.g. "debugify_trap.nodbg.ll:15", but if you `list 15` gdb opens the .ll and prints whatever TEXT physically sits on line 15 — usually a blank line or an unrelated instruction. The "15" names the 15th INSTRUCTION (the faulting one), not source line 15. You resolve it by reading the !dbg / !DILocation metadata, not by trusting `list`.
`opt -passes=verify` attaches NO debug info, so a fault in that build gives only "main+offset" with no line at all. We give the function several arithmetic steps before the null store so the faulting instruction gets a satisfyingly large ordinal. Build & trace (see cross_level/README.md for REAL output): clang -O0 -S -emit-llvm debugify_trap.c -o debugify_trap.nodbg.ll # NO -g opt -passes=debugify debugify_trap.nodbg.ll -S -o debugify_trap.dbgfy.ll llc -filetype=obj debugify_trap.dbgfy.ll -o debugify_trap.dbgfy.o clang debugify_trap.dbgfy.o -o debugify_trap_dbgfy gdb ... ./debugify_trap_dbgfy # -> "...nodbg.ll:N" where N is the ordinal ============================================================
int compute(int *p, int a, int b) {
int x = a + b; /* a few real operations, so the store's ordinal is large */
int y = x * a;
int z = y - b;
int w = z + x;
*p = w; /* THE faulting store (p == NULL) */
return w;
}
int main(void) {
int *p = 0;
return compute(p, 3, 4);
}A lab is done when you can say what it showed on your machine — not when you have read it.
It decides which problems come first, which drills open, which labs you see and how the Learn surface is organised. Change it any time from the switch in the top bar, or press Escape to look around first.