Like C++ variables, registers are actually available in several sizes:
Curiously, you can write a 64-bit value into rax, then read off the low 32 bits from eax, or the low 16 bitx from ax, or the low 8 bits from al--it's just one register, but they keep on extending it!
|
For example,
mov rcx,0xf00d00d2beefc03; load a big 64-bit constant
mov eax,ecx; pull out low 32 bits (0x2beefc03)
ret
Here's the full list of x86 registers. The 64 bit registers are shown in red. "Scratch" registers any function is allowed to overwrite, and use for anything you want without asking anybody. "Preserved" registers have to be put back ("save" the register) if you use them.
Name |
Notes | Type |
64-bit long |
32-bit int |
16-bit short |
8-bit char |
eax |
Values are returned from
functions in this register. |
scratch |
rax | eax | ax | ah and al |
ecx |
Typical scratch register. Some instructions also use it as a counter. | scratch |
rcx | ecx | cx | ch and cl |
edx |
Scratch register. | scratch |
rdx | edx | dx | dh and dl |
ebx |
Preserved register: don't use it without saving it! | preserved |
rbx | ebx | bx | bh and bl |
esp |
The stack pointer. Points to the top of the stack (details below) | preserved | rsp | esp | sp | spl |
ebp |
Preserved register. Sometimes used to store the old value of the stack pointer, or the "base". | preserved | rbp | ebp | bp | bpl |
esi |
Scratch register. Also used to pass function argument #2 in 64-bit Linux | scratch | rsi | esi | si | sil |
edi |
Scratch register. Function argument #1 in 64-bit Linux | scratch | rdi | edi | di | dil |
r8 |
Scratch register. These
were added in 64-bit mode, so they have numbers, not names. |
scratch | r8 | r8d | r8w | r8b |
r9 |
Scratch register. | scratch | r9 | r9d | r9w | r9b |
r10 |
Scratch register. | scratch | r10 | r10d | r10w | r10b |
r11 |
Scratch register. | scratch | r11 | r11d | r11w | r11b |
r12 |
Preserved
register. You can use it, but you need to save and
restore it. |
preserved | r12 | r12d | r12w | r12b |
r13 |
Preserved register. | preserved | r13 | r13d | r13w | r13b |
r14 |
Preserved register. | preserved | r14 | r14d | r14w | r14b |
r15 |
Preserved register. | preserved | r15 | r15d | r15w | r15b |
Source
Size |
|||||
64 bit rcx |
32 bit ecx |
16 bit cx |
8 bit cl |
Notes |
|
64 bit rax |
mov rax,rcx |
movsxd
rax,ecx |
movsx
rax,cx |
movsx
rax,cl |
Writes to whole register |
32 bit eax |
mov eax,ecx |
mov eax,ecx |
movsx
eax,cx |
movsx
eax,cl |
Top half of destination gets zeroed |
16 bit ax |
mov ax,cx |
mov ax,cx |
mov ax,cx |
movsx
ax,cl |
Only affects low 16 bits, rest unchanged. |
8 bit al |
mov al,cl |
mov al,cl | mov al,cl | mov al,cl |
Only affects low 8 bits, rest unchanged. |
"The Stack" is a frequently-used area of memory designed for functions to use as temporary storage. This is normally where you store values while calling another function: you can't store values in the scratch registers, because the function could change them.
The easiest and most common way to use the stack is with the dedicated "push" and "pop" instructions.
For example,
this loads 3 into rax and returns. It's a kinda roundabout
way to return a 3, but it lets you use rax for something else
until you need it.
push 3 pop rax ret
For a more complicated example, this loads 23 into rax, and then 17 into rcx:
push 17
push 23
pop rax
pop rcx
ret
After the
first "push", the stack just has one value:
17
After the second "push", the stack has two values:
17 23
So the first "pop" picks up the 23, and puts it in rax, leaving
the stack with one value:
17
The second "pop" picks up that value, puts it in rcx, leaving the
stack clean. If the stack was not clean, everything
actually works fine except "ret", which jumps to whatever is on
the top of the stack. Let me say that again:
If you do not pop *exactly*
the same number of times as you push, your program will crash.
Horribly.
So be careful with your pushes and pops!
You can use push and pop to save registers at the start and end of your function. For example, "rbp" is a preserved register, so you need to save its value before you can use it:
push rbp ; save old copy of this register
mov rbp,23
mov rax,rbp
pop rbp ; restore main's copy from the stack
ret
Main might be
storing something important in rbp, and will complain if you just
change it, but as long as you put it back exactly how it was
before you return, main is perfectly happy letting you use it!
Without the push and pop, main will be annoyed that you
messed with its stuff, which in a real program often means a
strange and difficult to debug crash.
If you have multiple registers to save and restore, be sure to pop
them in the *opposite* order they were pushed:
push rbp ; save old copy of this register
push r15
mov rbp,23
mov rax,rbp
pop r15 ; restore main's copy from the stack
pop rbp
ret
One big
advantage to saved registers: you can call other functions, and
know that the registers values won't change (because they'll be
saved). All the scratch registers, by contrast, are likely
to get overwritten by any function you call.
When I'm
writing a long function that calls a bunch of stuff, I tend to
work mostly in saved registers, which I push and pop at the start
and end of my function to keep main from getting annoyed.
You can also save a scratch register, to keep some other function
from messing with it. You do this by pushing your value
before calling a function, then popping it afterwards to bring
your copy back:
mov rax,17; say I want to keep this value while calling a function...
push rax; save rax to the stack
mov rdi,3 ; now call the function
extern print_long
call print_long
pop rax; restore rax afterwards, and safely return 17
ret
(Try this in NetRun now!)
Again, you can
save as many registers as you want, but you need to pop them in
the opposite order--otherwise you've flipped their values around!
For a short function where I only call a few other functions, I tend to work in scratch registers, and save the few things I need before calling other functions.