Reading — step 1 of 5
Learn
Registers and Arithmetic
Registers are the CPU's scratch space: 16 general-purpose slots, each 64 bits wide, built into the processor itself. Memory is far away (in CPU terms); registers are right there. Nearly every instruction reads or writes one, so fluency with registers is fluency with assembly.
One register, four names
The 64-bit general-purpose registers are rax, rbx, rcx, rdx, rsi, rdi, rbp, rsp, and r8–r15. Each can also be addressed as a narrower slice of itself:
rax(64-bit) →eax(low 32) →ax(low 16) →al(low 8)r8(64-bit) →r8d(low 32) →r8w(low 16) →r8b(low 8)
These are views of the same storage, not separate registers — and that's where the classic width trap lives:
- Writing the 32-bit view zeroes the upper 32 bits: after
mov eax, 5, all ofraxis exactly 5. That's why compilers prefermov eax, 5overmov rax, 5— same result, smaller instruction. - Writing the 16-bit or 8-bit view leaves the upper bits unchanged: after
mov al, '7', bits 8–63 ofraxstill hold whatever was there before. Use all ofraxas a number afterwards and that leftover garbage comes along for the ride.
Arithmetic: destination first
NASM is Intel syntax: the destination comes first, and it doubles as the left-hand input:
mov rax, 7 ; rax = 7
add rax, 3 ; rax = rax + 3 -> 10
sub rax, 4 ; rax = rax - 4 -> 6
imul rax, 2 ; rax = rax * 2 -> 12 (signed multiply)
inc rax ; rax += 1 -> 13
dec rax ; rax -= 1 -> 12
xor rax, rax ; rax = 0
xor rax, rax is the idiomatic zero: anything XOR itself is 0, it encodes shorter than mov rax, 0, and CPUs recognize it as a fresh start (no dependency on the register's old value). If you have seen AT&T syntax (addl $3, %eax — source first), note that operand order here is the mirror image; reading it backwards is a rite-of-passage bug.
The stack gives you quick save/restore:
push rax ; rsp -= 8, then [rsp] = rax
pop rbx ; rbx = [rsp], then rsp += 8
Touching memory: brackets and sizes
Square brackets mean "the memory at this address":
mov rax, [counter] ; load 8 bytes from counter into rax
mov [counter], rbx ; store rbx (8 bytes) at counter
mov byte [digit], '7' ; store exactly ONE byte
That byte keyword is not decoration. In mov [digit], '7', neither operand pins down a size — digit is just an address, '7' is just a number — so NASM refuses to guess and errors with operation size not specified. When one operand is a register, the register's width implies the size; for immediate-to-memory stores you must write byte, word, dword, or qword yourself.
Characters are numbers wearing costumes
Assembly has no character type. The terminal receives bytes and renders them: byte 0x37 (decimal 55) is drawn as 7, byte 0x39 as 9, byte 10 as a line break. NASM's '7' is just a convenient way to write 0x37.
So these are two different programs:
mov byte [digit], '9' ; stores 0x39 — the visible character 9
mov byte [digit], 9 ; stores 0x09 — a horizontal TAB
Drop the quotes and you print an invisible control character. The output looks almost right in a terminal — and is byte-for-byte wrong.
Your exercise
The starter's data line is digit db 0, 10: two adjacent bytes — a placeholder, then a newline. The code stores '7' into the first byte and then writes 2 bytes starting at digit, so the digit and the newline go out in one write. Change the stored character so the program prints 9 followed by a newline — exactly the bytes 0x39 0x0A. The mistake the grader catches: writing mov byte [digit], 9 without quotes, which prints a tab instead of 9 — the code "looks right" and the diff still fails. Keep the quotes: '9'. And leave rdx at 2, or you'll drop the newline the test expects.
Discussion
Ask a question, share an insight, or help someone who’s stuck.
Sign in to post a comment or reply.
Loading…