Lesson 32 of 48 intermediate

Debugging Registers, Flags, and Memory

Find and fix bugs like a detective — single-step, inspect, and trace your 8086 programs

Open interactive version (quiz + challenge)

Real-world analogy

Debugging is like being a detective at a crime scene (buggy program). Single-stepping is examining evidence one piece at a time. Registers are the suspect's pockets — you check what they are carrying after each step. Memory is the crime scene — you scan it for clues. Flags are the suspect's facial expressions — they reveal hidden truths (was there a carry? did the result go negative?).

What is it?

Debugging 8086 programs means systematically inspecting CPU state — registers, flags, memory, and stack — to find where actual behavior diverges from expected behavior. Single-stepping executes one instruction at a time. Breakpoints skip to the interesting code. Flag analysis reveals arithmetic edge cases. Memory dumps verify data layout. Mastering these techniques turns opaque bugs into clear root causes.

Real-world relevance

In industry, embedded engineers debug microprocessors using JTAG probes that provide single-step and memory inspection on real hardware. Reverse engineers use debuggers (OllyDbg, x64dbg) to analyze unknown binaries instruction by instruction. Boot loader developers debug pre-OS code where no printf exists — only register and memory inspection. These skills are timeless.

Key points

Code example

; Program: Demonstrates common bugs and how to catch them
; Run this in emu8086, single-stepping to see each issue
.MODEL SMALL
.STACK 100h
.DATA
  values DB 200, 150, 100, 50, 25
  vcount EQU 5
  result DW ?
  msg    DB 'Debug Demo$'

.CODE
MAIN PROC
  MOV AX, @DATA
  MOV DS, AX

  ; === Test 1: Unsigned overflow ===
  MOV AL, 200
  ADD AL, 100       ; AL = 300? No! AL = 44 (300-256)
  ; Step here and check: AL=2Ch, CF=1
  ; CF=1 tells you there was a carry out

  ; === Test 2: Signed overflow ===
  MOV AL, 127       ; max positive signed byte
  ADD AL, 1         ; AL = 128 = -128 signed!
  ; Step here: AL=80h, OF=1, SF=1
  ; OF=1 tells you signed overflow occurred

  ; === Test 3: Sum array (correct way) ===
  LEA SI, values
  XOR AX, AX        ; clear sum
  MOV CX, vcount
  JCXZ no_data      ; guard!

sum_loop:
  MOV BL, [SI]      ; load byte
  XOR BH, BH        ; zero-extend to word
  ADD AX, BX        ; add to word-size sum
  INC SI
  LOOP sum_loop
  ; AX should be 200+150+100+50+25 = 525
  ; Watch AX grow each iteration

no_data:
  MOV [result], AX

  ; === Test 4: Verify memory ===
  ; After execution, check memory at 'result'
  ; Should see: 0D 02 (525 = 020Dh, little-endian)

  MOV AH, 4Ch
  INT 21h
MAIN ENDP
END MAIN

Line-by-line walkthrough

  1. 1. Test 1: MOV AL, 200 / ADD AL, 100 — AL is 8 bits, so 300 wraps to 44 (300 mod 256). CF=1 signals the unsigned overflow
  2. 2. Test 2: MOV AL, 127 / ADD AL, 1 — 127 is the max positive signed byte. Adding 1 gives 128 (80h), which is -128 in signed. OF=1 signals signed overflow
  3. 3. JCXZ no_data — guard before the loop. If vcount were 0, this prevents the loop from running 65535 times
  4. 4. XOR BH, BH — zero-extends BL to BX before adding. Without this, random data in BH corrupts the sum
  5. 5. ADD AX, BX — accumulates in a 16-bit register to handle sums over 255 (our sum is 525)
  6. 6. After the loop, result should be 020Dh (525). In memory it appears as 0D 02 due to little-endian byte order
  7. 7. At each step in emu8086, compare your expected register values with actual values. Any discrepancy is a bug

Spot the bug

; Sum should be 150 (50+50+50)
.DATA
  vals DW 50, 50, 50
.CODE
MAIN PROC
  MOV CX, 3
  LEA SI, vals
  XOR AX, AX
add_loop:
  ADD AX, [SI]
  INC SI
  LOOP add_loop
  MOV AH, 4Ch
  INT 21h
MAIN ENDP
END MAIN
Need a hint?
Three bugs: segment initialization, index increment size, and something you need to check with a memory dump.
Show answer
Bug 1: Missing MOV AX, @DATA / MOV DS, AX — DS does not point to .DATA. Bug 2: INC SI moves only 1 byte, but DW elements are 2 bytes — use ADD SI, 2. Bug 3: After fixing, verify with memory dump that vals contains 32 00 32 00 32 00 (50 = 0032h, little-endian).

Explain like I'm 5

Imagine you are baking a cake but it comes out wrong. Debugging is going back and checking each step: Did you use sugar or salt? (inspect registers) Did you set the oven to the right temperature? (check flags) Did you pour the batter into the right pan? (inspect memory) Single-stepping is doing ONE step at a time and checking the result before moving on. A breakpoint is saying 'I know steps 1-5 are fine, let me skip to step 6 and watch from there.'

Fun fact

The 8086 has a special Trap Flag (TF) that enables single-stepping at the hardware level. When TF=1, the CPU automatically generates an INT 1 after every instruction. Debuggers set TF and install an INT 1 handler to gain control after each step. This same mechanism works on modern x86 processors — your favorite debugger still uses TF internally.

Hands-on challenge

Take this buggy program and fix all the errors using single-stepping in emu8086: It should sum an array of 5 words and print the result, but it (1) forgets to initialize DS, (2) uses INC SI instead of ADD SI, 2 for a word array, (3) has an unbalanced stack in the print procedure, and (4) uses JA instead of JG for signed comparison.

More resources

Open interactive version (quiz + challenge) ← Back to course: Microprocessor A–Z