Lesson 04-Data Transfer Instructions and Stack Operations

Introduction: Data Transfer Is 90% of What Computers Do

Here’s a controversial opinion: the vast majority of computing is just moving data around.

I know, I know — we like to think of computers as these majestic calculation engines, solving differential equations and training neural networks and rendering photorealistic graphics. And they do those things. But if you look at what a CPU actually does at the instruction level — if you profile a typical program and count the instructions — you’ll find that the vast majority are data transfer instructions. MOV. PUSH. POP. LOAD. STORE. Instructions that move data from one place to another without computing anything.

Why? Because computation (ADD, SUB, MUL, etc.) requires both operands to be in registers (or one in a register and one in memory, depending on the instruction). So before you can compute A + B, you need to load A into a register, load B into a register (or into memory if the instruction supports memory operands), compute the result, and then store the result back to memory. Three data transfer instructions for every arithmetic instruction.

In this article, we’re going to dive deep into the data transfer instructions of the x86 architecture. We’ll start with MOV (the workhorse), then talk about LEA (the most misunderstood instruction in x86), then discuss the stack (PUSH, POP) and why it’s fundamental to function calls. We’ll cover MOVZX and MOVSX (zero-extend and sign-extend), and we’ll talk about common bugs that arise from incorrect data transfer.

This is foundational material. If you don’t understand data transfer, you don’t understand assembly language. Let’s get to work.

Data Transfer Is the Most Common Operation: MOV and Its Many Forms

The MOV instruction is, without question, the most frequently used instruction in x86 assembly. If you open a disassembly of any program and count the instructions, MOV will be at the top of the list, often accounting for 30-50% of all instructions.

What Does MOV Do?

MOV copies data from a source operand to a destination operand. I want to emphasize copies — the source operand is not modified. It’s a copy, not a move in the “cut and paste” sense.

(Why is it called MOV and not COPY? Historical accident. The Intel 4004 (1971) and 8008 (1972) used MOV as the mnemonic, and the name stuck. Other architectures use LD/ST (load/store) or MOV or CPY. It’s all the same operation.)

The Many Forms of MOV

The x86 architecture has an extremely flexible MOV instruction. The source and destination can be:

  1. Register to register: MOV EAX, EBX
  2. Immediate to register: MOV EAX, 42
  3. Memory to register: MOV EAX, [EBX]
  4. Register to memory: MOV [EBX], EAX
  5. Immediate to memory: MOV DWORD [EBX], 42
  6. Segment register to register (16-bit only): MOV AX, DS
  7. Register to segment register (16-bit only): MOV DS, AX
  8. Control register to register (privileged): MOV EAX, CR0
  9. Register to control register (privileged): MOV CR0, EAX
  10. Debug register to/from register (privileged): MOV EAX, DR0

That’s… a lot of forms. One instruction, ten different types of operands. This is the hallmark of CISC (Complex Instruction Set Computer) design — a single instruction does many things.

Compare this to a RISC (Reduced Instruction Set Computer) design like ARM or RISC-V, where MOV (or LD/ST) has far fewer forms. In RISC architectures, you typically can’t do immediate-to-memory or memory-to-memory in a single instruction. You have to load the immediate into a register, then store it to memory. Two instructions instead of one.

Examples of MOV in Action

Let me show you MOV in action with a series of examples:

; Register to register
MOV EAX, EBX            ; EAX = EBX (copy EBX into EAX)

; Immediate to register
MOV EAX, 0x12345678     ; EAX = 0x12345678
MOV CX, 42               ; CX = 42 (16-bit immediate)

; Memory to register
MOV EAX, [EBX]           ; Load 32 bits from address in EBX into EAX
MOV CL, [ESI]             ; Load 8 bits from address in ESI into CL

; Register to memory
MOV [EBX], EAX           ; Store EAX to memory at address in EBX
MOV [EDI], CX             ; Store CX to memory at address in EDI

; Immediate to memory
MOV DWORD [EBX], 0x12345678   ; Store 32-bit immediate to memory
MOV WORD [ESI], 42              ; Store 16-bit immediate to memory
MOV BYTE [EDI], 0xFF            ; Store 8-bit immediate to memory

; Segment register (16-bit mode)
MOV AX, DS              ; AX = DS (copy data segment to AX)
MOV DS, AX              ; DS = AX (set data segment from AX)

What About the Operand Size?

When you write MOV EAX, EBX, it’s clear that you’re moving 32 bits (because EAX and EBX are 32-bit registers). But what about MOV [EBX], 42? How many bytes are being stored to memory? 1 byte? 2 bytes? 4 bytes?

In MASM and NASM, you specify the operand size using a size directive:

MOV BYTE [EBX], 42     ; Store 1 byte (8 bits)
MOV WORD [EBX], 42     ; Store 2 bytes (16 bits)
MOV DWORD [EBX], 42    ; Store 4 bytes (32 bits)
MOV QWORD [EBX], 42    ; Store 8 bytes (64 bits, x86-64 only)

In MASM, the syntax is slightly different:

MOV BYTE PTR [EBX], 42  ; Store 1 byte
MOV WORD PTR [EBX], 42  ; Store 2 bytes
MOV DWORD PTR [EBX], 42 ; Store 4 bytes
MOV QWORD PTR [EBX], 42 ; Store 8 bytes

The PTR stands for “pointer” — it’s MASM’s way of saying “treat the memory operand as a pointer to a value of this size.”

If you omit the size directive and the assembler can’t infer the size from the operands, you’ll get an error:

MOV [EBX], 42    ; ERROR: operand size ambiguous!

The assembler doesn’t know whether you want to store 1, 2, or 4 bytes. You must specify the size.

Membership Required

You must be a member to access this content.

View Membership Levels

Already a member? Log in here
Share your love