← Back to Articles

Debugging Assembly Code

Why Debugging Matters

Assembly code can be challenging to debug because you're working at such a low level. Small mistakes can lead to unexpected behavior. Learning systematic debugging techniques will save you hours of frustration.

Common Assembly Bugs

1. Off-by-One Errors

Loop counters and memory addresses are prone to off-by-one errors:

// Bug: Loops 11 times instead of 10
MOV R0, #0
MOV R1, #10
ADD R0, R0, #1   // Line 3
CMP R0, R1
BLE 3            // Wrong: should be BLT
HALT

2. Wrong Register Usage

Using the wrong register or overwriting needed values:

// Bug: R0 gets overwritten
MOV R0, #5
MOV R1, #10
ADD R2, R0, R1   // R2 = 15
MOV R0, #20      // Oops! Lost original R0
MUL R3, R0, R2   // Wrong calculation

3. Incorrect Branch Targets

Branching to the wrong line number:

MOV R0, #0
CMP R0, #0
BEQ 5            // Bug: Line 5 might not exist!
MOV R1, #10
HALT

4. Flag Misunderstanding

Using the wrong conditional branch:

MOV R0, #5
CMP R0, #10
BGT 5            // Bug: 5 is NOT > 10
MOV R1, #100     // This executes
HALT

Debugging Techniques

1. Use Step Mode

The most powerful debugging tool is the Step button:

  • Execute one instruction at a time
  • Watch registers and flags update after each step
  • Verify each instruction does what you expect
  • Check the Program Counter to see which line executes next

2. Add Temporary Output Instructions

Store intermediate values in memory to track program state:

MOV R0, #5
STR R0, 900      // Save R0 for inspection
MUL R1, R0, R0
STR R1, 901      // Save R1 for inspection
ADD R2, R1, #10
STR R2, 902      // Save R2 for inspection
HALT

3. Divide and Conquer

If your program isn't working, comment out parts to isolate the problem:

// Test first part only
MOV R0, #10
ADD R0, R0, #5
HALT            // Add temporary HALT
// MOV R1, #20  // Comment out rest
// ADD R2, R0, R1

4. Check Your Math

Manually calculate what values should be in registers:

// Expected: R2 = 30
MOV R0, #10     // R0 should be 10
MOV R1, #20     // R1 should be 20
ADD R2, R0, R1  // R2 should be 30
// Use Step mode to verify each value

Using the Compiler's Debug Features

Register Panel

  • Changed registers are highlighted
  • Click any register to edit its value
  • Test "what if" scenarios by modifying values mid-execution

Flags Panel

  • Watch which flags are set after CMP and arithmetic operations
  • Click flags to manually test conditional branches
  • Verify your understanding of how flags work

Memory View

  • Check that STR instructions write to correct addresses
  • Verify LDR instructions read expected values
  • Change start address to view different memory regions
  • Click memory locations to edit values for testing

Program Counter

  • Shows which line will execute next
  • Helps verify branches go to correct locations
  • Useful for tracking loop iterations

Debugging Strategies for Common Problems

Infinite Loops

If your program doesn't halt:

  1. Check loop counters are incrementing/decrementing
  2. Verify loop exit condition is correct
  3. Ensure branch target is inside the loop
// Fixed infinite loop
MOV R0, #0
MOV R1, #5
ADD R0, R0, #1   // Line 3: Make sure this runs!
CMP R0, R1
BLT 3            // Goes back to line 3
HALT

Wrong Results

If calculations are incorrect:

  1. Verify input values are loaded correctly
  2. Check instruction order (dependency issues)
  3. Ensure you're not overwriting needed values
  4. Step through and compare actual vs expected values

Crashes or Unexpected Behavior

  1. Check for invalid memory addresses
  2. Verify branch targets exist
  3. Ensure all registers are initialized before use

Systematic Debugging Process

  1. Reproduce: Make sure you can consistently see the bug
  2. Isolate: Narrow down which part of code has the problem
  3. Understand: Figure out what the code is actually doing
  4. Hypothesize: Form a theory about what's wrong
  5. Test: Use Step mode to verify your hypothesis
  6. Fix: Make the minimal change to fix the bug
  7. Verify: Confirm the bug is gone and nothing else broke

Prevention Tips

  • Comment generously: Explain what each section does
  • Use consistent formatting: Makes errors more visible
  • Test incrementally: Don't write everything before testing
  • Start simple: Test with simple input values first
  • Document register usage: Note what each register contains

Example: Debugging a Factorial Function

// Buggy factorial calculator
MOV R0, #5       // Input: calculate 5!
MOV R1, #1       // Result
MOV R2, #1       // Counter
// Loop at line 4
MUL R1, R1, R2   // result *= counter
ADD R2, R2, #1   // counter++
CMP R2, R0       // Compare counter with input
BLE 4            // Bug: should be BLT!
// Result in R1
HALT

The bug: BLE loops one extra time. When R2 equals R0, it should stop, but BLE (branch if less than or equal) continues. Fix: Change to BLT.

Next Steps

With these debugging techniques, you'll be able to find and fix bugs more efficiently. Continue learning about the compiler's features to leverage all available debugging tools.