Finding the Fault
At the machine level, a running program is nothing but state: the values in the registers, the contents of memory, and the program counter saying what runs next. A bug is a point where that state diverges from what it should be. Debugging is the disciplined art of finding where the divergence first happens.
The core technique is to make state observable. A debugger lets you set a breakpoint (pause execution at a chosen instruction), then inspect the registers and memory to see the actual state, and single-step (execute one instruction at a time) to watch the state change. The strategy is a search: confirm the state is correct at some early point, confirm it is wrong at a later point, and narrow the gap — a kind of binary search in time — until you find the exact instruction that first produces the wrong value.
Machine-level bugs cluster into recognizable families: an off-by-one in a loop bound; integer overflow wrapping a value unexpectedly; reading or writing the wrong memory address; using an uninitialized register or memory cell (whose value is whatever was left there); and getting signed vs. unsigned interpretation wrong. Recognizing the family speeds the hunt.
The overriding principle is to reason from evidence, not assumption. Do not guess what the code "should" be doing — observe what the state actually is at each step, and let the discrepancy point to the fault.
Common pitfall: "debugging by guessing" — changing code because you think you know the cause, without observing the actual state. The reliable method is to inspect real register and memory values at breakpoints and narrow down where the state first goes wrong. Evidence, not intuition, finds the bug.