Connect Source to Live Machine State
Both branches are in the file
Section titled “Both branches are in the file”The disassembly of classify contains instructions for adding two and subtracting two. It has to: either path might be needed. Seeing an addition in the executable doesn’t establish that this run executed it, any more than seeing an else in the source establishes that the condition was false.
We can now watch one execution. Keep the original decision.c from the first debugging lesson; no new program is needed. This lesson builds on frame selection, the instruction lesson’s registers, and the rule that a pointer is useful only while it refers to valid storage.
Put the views at the same stop
Section titled “Put the views at the same stop”First leave any previous GDB session with quit. In the shell, rebuild the unchanged baseline and check its identity:
cd ~/underdunn-course/exercises/debugginggcc -std=c11 -Wall -Wextra -Wpedantic -O0 -g decision.c -o decisionsha256sum decision.c decisiongdb -q ./decisionThe saved GDB settings select Intel instruction notation automatically. Predict the default argument and which source branch will run. GDB input:
break classifyrunprint valueprint &valuex/1dw &valueprint value should show 12. &value asks for the address of the local argument storage in this stopped frame. The examine command, x, reads memory: /1dw requests one decimal, four-byte word. On this supported build, that memory should also contain 12.
The address will vary between runs. More importantly, this observation is valid because we’re inside the active function with the variable in scope. It is not permission to keep reading that address after the function returns. Accessible memory and a live C object are different claims.
We’ve now connected a source expression to storage in this run. GDB used debugging information to make that connection; it did not recover the author’s intent from an arbitrary number.
Execute one instruction, not one line
Section titled “Execute one instruction, not one line”Inspect the function and the next instruction:
disassemble classifyx/i $pcstepix/i $pcdisassemble shows the function’s instructions. x/i decodes one instruction at the supplied address. $pc is GDB’s program-counter name, identifying the instruction that will execute next at this stop.
Before issuing stepi, use the displayed instruction to predict whether execution will continue sequentially or take a branch. Then compare the new instruction location with the prediction. A branch destination can move backward or forward, so “the address changed” is not an explanation of the instruction’s effect.
stepi executes one machine instruction. step, from the previous lesson, advances by source location and can execute several instructions before stopping. A C comparison doesn’t become exactly one debugger step merely because it occupies one line of your file.
Continue with stepi until you’ve observed the decision’s path and the arithmetic instruction implementing it. Stop before the function’s ret instruction, which returns to the caller. Keep this bounded to classify; we’re not single-stepping through the library’s printing implementation. Check the actual branch and arithmetic instructions from your build rather than copying an address from another run.
The GDB machine-stepping reference distinguishes stepi from nexti, which steps over a call at instruction level. For this short function, stepi is enough.
Observe the return at the right time
Section titled “Observe the return at the right time”While still inside classify, predict its return value. Then execute the rest of the function:
finishinfo registers eaxcontinueExpect GDB to report a returned value of 14, and eax to show hexadecimal 0xe and decimal 14 immediately after the return. The final program output should agree.
Why inspect eax here? The calling convention you studied earlier uses it for this int result. That is a claim about the call boundary, not a promise that the register always represents the C variable value. Another instruction can overwrite it later. Reading every register and looking for a familiar number would lose the relationship we came here to establish.
Practice: take the other branch
Section titled “Practice: take the other branch”Use run low at the existing function breakpoint. Predict the argument, compare it with valid local memory, then follow the decision again. Observe the return register at the same boundary as before.
Common failures and recovery
Section titled “Common failures and recovery”- Addresses differ: that’s expected. Resolve the function by name and use the current
$pcor&value, not an address copied from an earlier session. - A variable is unavailable or optimized away: rebuild the exact
-O0 -gbaseline and load that executable. More optimized programs need a less literal source-to-state model. - You already executed the return: don’t use
finishin the caller and expect another observation ofclassify’s return. Restart at the function breakpoint. - Memory cannot be read: check the frame and lifetime. Restart inside
classify; do not disable protections or try nearby addresses.
Check your understanding
Section titled “Check your understanding”Knowledge check
Question 1 of 1
Practice complete
You've checked the key ideas. Revisit any explanation you need, then continue when it makes sense.