Step 1 of 5 · Reading · ~4 min
Learn
First Steps
Registers and Arithmetic
By the end of this lesson you can do real arithmetic on the CPU: turn digit characters into numbers, add them, split a result with a division, and turn the pieces back into characters your program can print.
Registers are the CPU's scratch space: 16 general-purpose slots, each 64 bits wide, built into the processor. Memory is far away in CPU terms; registers are right there, and nearly every instruction reads or writes one.
One register, several names
The 64-bit registers are rax, rbx, rcx, rdx, rsi, rdi, rbp, rsp and r8 to r15. Each can be addressed as a narrower slice of itself:
rax(64 bits) ->eax(low 32) ->ax(low 16) ->al(low 8)r8(64 bits) ->r8d(low 32) ->r8w(low 16) ->r8b(low 8)
These are views of the same storage, and that is where the classic width trap lives:
- Writing the 32-bit view zeroes the upper 32 bits. After
mov eax, 5, all ofraxis exactly 5. - Writing the 16-bit or 8-bit view leaves the upper bits alone. After
mov al, 7, bits 8-63 ofraxstill hold whatever was there before.
So if you load one byte with mov al, [x] and then use all of rax as a number, leftover garbage comes along. To load a byte as a clean number, use movzx rax, byte [x]: "move with zero extension" copies the byte and fills the rest with zeros.
Arithmetic: destination first
NASM uses Intel syntax: the destination comes first and doubles as the left-hand input.
mov rax, 7 ; rax = 7
add rax, 3 ; rax = 10
sub rax, 4 ; rax = 6
imul rax, 5 ; rax = 30 (signed multiply)
inc rax ; rax = 31
dec rax ; rax = 30
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 the CPU treats it as a fresh start.
Division has fixed registers
div takes one operand, the divisor, and the dividend is implied: the 128-bit number rdx:rax, with rdx as the high half. It leaves the quotient in rax and the remainder in rdx.
Splitting 135 seconds into minutes and seconds with a divisor of 60:
mov rax, 135
xor edx, edx ; high half of the dividend must be 0
mov rbx, 60
div rbx ; rax = 2 (minutes), rdx = 15 (seconds)
The trap is the xor edx, edx line. Leave stale bits in rdx and the CPU divides a gigantic number. If rdx is at least the divisor, the quotient cannot fit in rax and the process dies with a divide error (SIGFPE); if it is smaller, you silently get a wrong quotient and remainder. Both are easy to hit: after a read syscall, rdx still holds the count you passed in.
Characters are numbers wearing costumes
Assembly has no character type. A terminal receives bytes and draws them: byte 0x41 is drawn as A, byte 10 as a line break. NASM's 'A' is just a convenient way to write 0x41. The digits '0' to '9' are the consecutive codes 0x30 to 0x39, which gives you the whole conversion rule: a digit character minus the code of '0' is its value, and a value plus the code of '0' is its character.
Upper to lower case works the same way, since 'a' is 32 above 'A':
mov al, 'Q'
add al, 32 ; al = 'q'
Quotes matter. mov bl, 'Q' stores 0x51, while mov bl, 51 stores 0x33, which is the character 3. Leave the quotes off a digit and you store a control character: mov byte [x], 9 stores a TAB. The output looks almost right in a terminal and is wrong byte for byte.
Storing into memory
Square brackets mean "the memory at this address". When one operand is a register its width sets the size (mov [x], al stores one byte). When you store an immediate there is nothing to infer from, so you name the size: mov byte [x], 'Q'. Without byte, NASM stops with operation size not specified.
Common mistakes
- Adding characters instead of values.
'3' + '7'is 0x33 + 0x37 = 106, not 10. Convert first. - Skipping
xor edx, edxbeforediv. Wrong digits, or a crash with SIGFPE. - Forgetting to turn a number back into a character. Storing the value 1 prints an invisible control byte, not
1. - Mixing up quotient and remainder. After
div,raxis the quotient andrdxthe remainder.
Your exercise
The starter reads three bytes (two digits and a newline) and prints a two-byte result template followed by a newline. Your job is the arithmetic in between, and the tests include sums that need a carry into the tens place as well as ones that do not.
Discussion
Ask a question, share an insight, or help someone who’s stuck.
Sign in to post a comment or reply.
Loading…