Lesson 07-Sequential Structure Programming and Debugging Techniques

Introduction: The Humble Sequential Program

Let me start with a confession: when I first learned assembly language, I thought sequential programming was… well, boring. I mean, it’s just doing one thing after another, right? How hard could it be?

Oh, how wrong I was.

Sequential programming in assembly — what high-level language programmers call “straight-line code” — is where you’ll make your first mistakes, pull your hair out debugging, and eventually develop the methodical thinking that separates assembly programmers from everyone else. There’s a reason why seasoned assembly developers write sequential code differently than beginners. It’s not just about getting the right answer — it’s about preserving registers, planning your instruction sequence, debugging efficiently, and avoiding the dozens of subtle pitfalls that await you.

In this article — the seventh in our series — we’re going to master sequential programming. We’ll talk about register allocation strategies, multi-step arithmetic without high-level operators, debugging with DEBUG.exe and WinDbg, and common bugs that will make you question your life choices. We’ll also build a complete calculator program that evaluates math expressions.

Buckle up. This is where assembly programming starts to feel real.

What Is Sequential Structure? (It’s Just… Doing Things One After Another. But There Are Pitfalls.)

Let’s get the definition out of the way: sequential structure means your program executes instructions one after another, in the order they appear in your source code. No jumps, no loops, no function calls (okay, maybe function calls, but you get the idea). Just a straight line of code from top to bottom.

In high-level languages, sequential code looks like this:

// C code: sequential structure
int a = 5;
int b = 10;
int c = a + b;
printf("%d\n", c);

In assembly, the same sequential code looks like this:

.DATA
    a DD 5
    b DD 10
    c DD 0
    fmt DB '%d', 10, 0

.CODE
main PROC
    ; int a = 5 (already initialized in .data)
    
    ; int b = 10 (already initialized in .data)
    
    ; int c = a + b
    MOV EAX, a          ; EAX = a (5)
    ADD EAX, b          ; EAX = EAX + b (5 + 10 = 15)
    MOV c, EAX          ; c = EAX (15)
    
    ; printf("%d\n", c)
    PUSH c
    PUSH OFFSET fmt
    CALL printf
    ADD ESP, 8
    
    ; Exit
    PUSH 0
    CALL exit
main ENDP
END main

Seems simple, right? And it is — until you start running out of registers, overwriting values you still need, or wondering why your “simple” arithmetic is giving you wrong answers.

The Three Pitfalls of Sequential Assembly Programming

Here are the three most common pitfalls that trip up beginners:

Pitfall #1: Running Out of Registers

In high-level languages, you can use as many variables as you want. The compiler figures out register allocation and spills to the stack when necessary. In assembly, you have a fixed set of registers (EAX, EBX, ECX, EDX, ESI, EDI, EBP, ESP in 32-bit), and once they’re all in use, you’re in trouble.

; BAD: Running out of registers
MOV EAX, var1        ; EAX = var1
MOV EBX, var2        ; EBX = var2
MOV ECX, var3        ; ECX = var3
MOV EDX, var4        ; EDX = var4
MOV ESI, var5        ; ESI = var5
MOV EDI, var6        ; EDI = var6
; ... now what? You've used all general-purpose registers!
; You need var1 again, but it's been modified...

The solution? Plan your register usage before you write a single instruction. More on this in Section 2.

Pitfall #2: The “Modified Register” Bug

This is the most common bug in sequential assembly programming. You load a value into a register, use it for something, then accidentally modify the register before you’re done with the original value.

; BAD: Modified register bug
MOV EAX, a            ; EAX = a
ADD EAX, b            ; EAX = a + b
; ... 20 lines later ...
MOV EAX, c            ; Oops! Overwrote EAX before we were done with (a + b)
; Now (a + b) is lost forever

The solution? Either complete all operations with a value before reusing the register, or save the value to memory/stack before reusing the register.

Pitfall #3: The “Flags Are Volatile” Surprise

In high-level languages, you can do this:

if (a > b) {
    // do something
}
if (a > b) {   // Check again — still true
    // do something else
}

In assembly, the flags (EFLAGS register) are volatile. Every arithmetic, logic, and comparison instruction modifies the flags. So this assembly code is buggy:

; BAD: Flags are volatile
MOV EAX, a
CMP EAX, b          ; Sets ZF, SF, CF, OF based on (a - b)
JG .doSomething      ; Jump if a > b
.doSomething:

; ... some code that modifies flags ...
ADD ECX, 1          ; Modifies flags! (Actually, ADD modifies OF, SF, ZF, AF, CF, PF)

; Now this is WRONG:
JG .doSomethingElse ; This uses the CURRENT flags (from ADD), not the previous CMP!

The solution? If you need to check a condition multiple times, either:

  1. Save the result of the comparison in a register, or
  2. Re-do the comparison before each conditional jump.

Sequential Structure Is the Foundation of Everything

Despite these pitfalls, sequential structure is the foundation of all programming. Every loop body is sequential. Every function body is sequential. Every interrupt handler is sequential. Master sequential programming, and you’ve mastered the building blocks of everything else.

Membership Required

You must be a member to access this content.

View Membership Levels

Already a member? Log in here

Share your love