If you are diving into vulnerability research, reverse engineering, or low-level programming, understanding how the CPU handles data sizes is critical. The x86-64 architecture has a unique quirk: it allows you to manipulate the main 64-bit registers in smaller chunks (8-bit, 16-bit, and 32-bit).
But what happens to the rest of the register when you only change a small part of it?
In this tutorial, we will use DDD (Data Display Debugger) to step through a simple assembly program. We will observe exactly how the RAX register behaves when we write bytes, words, doublewords, and quadwords into it.
The Program
;first_with_dnum.asm
section .data
bNum db 0x41 ; 'A' (ASCII 65)
wNum dw 0x1337 ; Hex value for clarity
dNum dd 0xDEADBEEF ; 32-bit doubleword value
qNum1 dq 0x1122334455667788 ; 64-bit quadword
qNum2 dq 0x0 ; Destination buffer initialized to 0
qNum3 dq 7.13 ; Floating-point value
section .bss
section .text
global main
main:
push rbp
mov rbp, rsp
; --- 1. Working with Bytes (8-bit) ---
mov rax, -1 ; Fill rax with 1s (0xFFFFFFFFFFFFFFFF)
mov al, byte [bNum] ; Does NOT clear upper bits of rax
xor rax, rax ; Clear the rax register completely
mov al, byte [bNum] ; Now rax has the correct clean value
; --- 2. Working with Words (16-bit) ---
mov rax, -1 ; Fill rax with 1s
mov ax, word [wNum] ; Does NOT clear upper bits of rax
xor rax, rax ; Clear the rax register
mov ax, word [wNum] ; Now rax has the correct value
; --- 3. Working with Doublewords (32-bit) ---
mov rax, -1 ; Fill rax with 1s
mov eax, dword [dNum] ; AUTOMATICALLY clears upper 32 bits of rax!
; Notice: No 'xor rax, rax' is needed here because 32-bit writes zero-extend.
; --- 4. Working with Quadwords (64-bit) ---
mov rax, -1 ; Fill rax with 1s
mov rax, qword [qNum1] ; Completely overwrites rax (64-bit to 64-bit)
mov qword [qNum2], rax ; Store register back to memory variable qNum2
mov rax, 123456 ; Load an immediate value into rax
; --- 5. Floating-Point Instruction ---
movsd xmm0, [qNum3] ; Move scalar double-precision float to XMM register
; Epilogue and Return
mov rsp, rbp ; Restore stack pointer
pop rbp ; Restore base pointer
ret ; Retrun from main
The Setup
Part 1: Compilation & Debugging Setup
Before opening DDD, the program is compiled using the provided Makefile:
- nasm -f elf64 -g first.asm -o first.o -l first.lst: Assembles the source code into a 64-bit ELF object file. The -g flag is crucial because it embeds debugging symbols (line numbers and variable names) so GDB and DDD can read them.
- gcc -o first first.o -no-pie: Links the object file into an executable named first. -no-pie (Position Independent Executa[.ble disabled) keeps memory addresses fixed and easier to track.
make file:
cat makefile
first: first.o
gcc -o first first.o -no-pie
first.o: first.asm
nasm -f elf64 -g first.asm -o first.o -l first.lst
nasm -f elf64 -g first.asm -o first.o -l first.lst
gcc -o first first.o -no-pie
main function:ddd ./first
(gdb) break mainSet break point to understand how Register use in program
Open the Register window (Status -> Register) and click Run.
Step-by-Step Analysis
1. The Prologue: Why isn't RAX changing?
When the program first stops at line 16 (main: push rbp), you will see RAX holds a memory address (e.g., 0x401110).
If you click Step to execute lines 16 and 17 (push rbp and mov rbp, rsp), you will notice the yellow pointer moves, but RAX remains exactly the same.
- Why? These lines are the function prologue setting up the stack frame. We haven't reached the instructions that actually modify the data registers yet.
2. Working with Bytes (8-bit)
mov rax, -1.RAX becomes 0xFFFFFFFFFFFFFFFF. The -1 fills the entire 64-bit register with 1s.mov al, byte [bNum].RAX becomes 0xFFFFFFFFFFFFFF41.bNum contains 0x41 ('A'). The AL register is the lowest 8 bits of RAX. Notice that the upper 56 bits are still FF. Writing to an 8-bit register does NOT clear the rest of the register. To clean it, we use xor rax, rax (line 22) to zero it out before writing to al again.3. Working with Words (16-bit)
Step to lines 26 and 27 (mov rax, -1 followed by mov ax, word [wNum]).
RAXbecomes0xFFFFFFFFFFFF1337.- The Lesson: Just like the 8-bit operation, writing
0x1337into the 16-bitAXregister leaves the upper 48 bits ofRAXcompletely untouched.
4. Working with Doublewords (32-bit) - The Plot Twist
mov rax, -1) and execute it. RAX is 0xFFFFFFFFFFFFFFFF again.Now, execute line 33: mov eax, dword [dNum].
RAXbecomes0x00000000DEADBEEF.- The Lesson: This is a fundamental rule of x86-64 architecture. A write to a 32-bit register (like
EAX) automatically zero-extends and clears the upper 32 bits of the 64-bit register. Notice how the upper 32 bits are now00000000instead ofFFFFFFFF. You don't need anxorinstruction here!

5. Working with Quadwords (64-bit)
Step to line 38: mov rax, qword [qNum1].
RAXbecomes0x1122334455667788.- The Lesson: A 64-bit write completely overwrites the entire register.
Step 6: Epilogue and Exit
mov rsp, rbp(Restore stack pointer)pop rbp(Restore base pointer)ret(Return from main)- The program will exit. The GDB console will display:
[Inferior 1 (process XXXX) exited with code 0100](Note:
0100 here is octal, which equals 64 in decimal, which is the exit code for 0 in Linux).Answering Common Questions
As you step through the rest of the code, you might encounter a few confusing moments. Here is what is actually happening:
mov rax, 123456), but the register shows 0x1e240. Why?0x1e240 from hex to decimal, it equals exactly 123456. The CPU is doing its job perfectly!RAX into qNum2 (line 39), but the value in the register window still looks different. Did it work?RAX into the memory address of qNum2, but RAX itself hasn't changed. To see the memory change, you would need to open DDD's "Data" window or inspect the memory address directly.qNum3? I don't see it anywhere.movsd xmm0, [qNum3] moves a floating-point number (7.13) from memory into the XMM0 register. qNum3 is still safely in memory. Because XMM0 is a floating-point/SIMD register, it is not shown in the default "Integer registers" view. To see it, check the "All registers" box at the bottom of the DDD Register window.Conclusion
Understanding how registers are overwritten is crucial. If you are analyzing a binary (or writing an exploit), forgetting that a 32-bit write zero-extends can completely change your understanding of the program's state.
By using DDD to visually map the CPU's state to the assembly code, you can quickly master these low-level architectural quirks.
















