Place/bit Number | i | ... | 4 | 3 | 2 | 1 | 0 |
Decimal: Base-10 |
10i |
... |
10000 |
1000 |
100 |
10 |
1 |
Binary: Base-2 |
2i | ... |
16 = 24 |
8 = 23 |
4 = 22 |
2 |
1 |
Hex: Base-16 |
16i | ... |
65536 = 2 |
4096 = 163 | 256 = 162 |
16 |
1 |
Base-n |
ni | ... |
n4 |
n3 | n2 | n |
1 = n0 |
Name |
Purpose |
C++ |
Assembly |
AND |
Turn bits off, extract bits. |
x=x&0xf0; |
and rax,0xf0 (Try this in NetRun now!) |
OR |
Turn bits on, combine fields. |
x=x|0xf0; |
or rax,0xf0 (Try this in NetRun now!) |
NOT |
Invert all bits. |
x=~x; |
not rax (Try this in NetRun now!) |
XOR |
Invert selected bits, encryption. |
x=x^0xf0; |
xor rax,0xf0 (Try this in NetRun now!) |
left shift |
Multiply by 2n. Reassemble bits. |
x=x<<2; |
shl rax,2 (Try this in NetRun now!) |
right shift (unsigned) |
Divide unsigned by 2n. Brings in zero bits. |
unsigned x; x=x>>2; |
shr rax,2 (Try this in NetRun now!) |
right shift (signed) |
Divide signed by 2n. Brings in copies of the sign bit. |
int x; x=x>>2; |
sar rax,2 (Try this in NetRun now!) |
Name | C++ | Asm Register | Asm Memory | Asm Data | Bits | Bytes (8 bits) |
Hex Digits (4 bits) |
Unsigned Range | Signed Range | |
Bit | n/a | n/a | n/a | n/a | 1 | less than 1 | less than 1 | 0..1 | -1..0 | |
Byte | char | al | BYTE | db | 8 | 1 | 2 | 255 | -128 .. 127 | |
short | short | ax | WORD | dw | 16 | 2 | 4 | 65535 | -32768 .. +32767 | |
32-bit int | int | eax | DWORD | dd | 32 | 4 | 8 | >4 billion | -2G .. +2G | |
64-bit int | "long" (or "long long") | rax | QWORD | dq | 64 | 8 | 16 | >16 quadrillion | -8Q .. +8Q |
int value=1; /* value to test, starts at first (lowest) bit */
int bit=0;
while (value!=0) {
value=value+value; /* moves over by one bit (value=value<<1 would work too) */
bit++;
}
return bit;
two's complement signed integers. The sign bit. To flip the sign, flip all the bits and then add one.
Or, why this code works, and returns 73:
const char commands[]={
0xb0,73, /* load a value to return */
0xc3 /* return from the current function */
};
int foo(void) {
typedef int (*fnptr)(void); // pointer to a function returning an int
fnptr f=(fnptr)commands; // typecast the command array to a function
return f(); // call the new function!
}
Here's the full list of x86 registers. The new 64 bit registers are shown in red.
Notes |
64-bit | 32-bit |
16-bit |
8-bit |
Values are returned from functions in this register. Multiply instructions put the low bits of the result here too. |
rax |
eax |
ax |
ah and al |
Typical scratch register. Some instructions use it as a counter (such as bit shifts). |
rcx |
ecx |
cx |
ch and cl |
Scratch register. Multiply instructions put the high bits of the result here, and divide demands that this contain zero. |
rdx |
edx |
dx |
dh and dl |
Preserved register: don't use it without saving it! |
rbx |
ebx |
bx |
bh and bl |
The stack pointer. Points to the top of the stack |
rsp |
esp |
sp |
spl |
Preserved register. Sometimes used to store the old value of the stack pointer, or the "base". |
rbp |
ebp |
bp |
bpl |
Scratch register. Also used to pass function argument #2 (on 64-bit Linux). |
rsi |
esi |
si |
sil |
Scratch register. Function argument #1 (on 64-bit Linux). | rdi |
edi |
di |
dil |
Scratch register. These were added in 64-bit mode, so the names are slightly different. |
r8 |
r8d |
r8w |
r8b |
Scratch register. |
r9 |
r9d |
r9w |
r9b |
Scratch register. |
r10 |
r10d |
r10w |
r10b |
Scratch register. | r11 |
r11d |
r11w |
r11b |
Preserved register. |
r12 |
r12d |
r12w |
r12b |
Preserved register. | r13 | r13d | r13w | r13b |
Preserved register. | r14 | r14d | r14w | r14b |
Preserved register. | r15 | r15d | r15w | r15b |
Instruction |
Useful to... |
Flags (see below) |
jmp |
Always jump |
None |
ja | Unsigned > (signed "x>n || x<0") |
CF=0 and ZF=0 |
jae |
Unsigned >= (signed "x>=n || x<0") |
CF=0 |
jb |
Unsigned < (signed "x<n && x>=0") |
CF=1 |
jbe |
Unsigned <= (signed "x<=n && x>=0") |
CF=1 or ZF=1 |
jc |
Unsigned overflow checking |
CF=1 |
jecxz |
Compare ecx with 0 |
ecx=0 |
je or jz |
Equality |
ZF=1 |
jg |
Signed > |
ZF=0 and SF=OF |
jge |
Signed >= |
SF=OF |
jl |
Signed < |
SF!=OF |
jle |
Signed <= |
ZF=1 or SF!=OF |
jne or jnz |
Inequality |
ZF=0 |
jo |
Signed overflow checking |
OF=1 |
jp or jpe |
Parity check (even) |
PF=1 |
jpo |
Parity check (odd) |
PF=0 |
js |
Jump if negative |
SF=1 |
Every C flow-control construct can be written using just "if" and
"goto", which usually map one-to-one to a compare-and-jump sequence in assembly.
Normal C |
Expanded C |
if (A) { ... } |
if (!A) goto END; { ... } END: |
if (!A) { ... } |
if (A) goto END; { ... } END: |
if (A&&B) { ... } |
if (!A) goto END; if (!B) goto END; { ... } END: |
if (A||B) { ... } |
if (A) goto STUFF; if (B) goto STUFF; goto END; STUFF: { ... } END: |
while (A) { ... } |
goto TEST; START: { ... } TEST: if (A) goto START; |
do { ... } while (A) |
START: { ... } if (A) goto START; |
for (i=0;i<n;i++) { ... } |
i=0; /* Version A */ goto TEST; START: { ... } i++; TEST: if (i<n) goto START; |
for (i=0;i<n;i++) { ... } |
i=0; /* Version B */ START: if (i>=n) goto END; { ... } i++; goto START; END: |
switch (x) { case 0: ...; break; case 1: ...; break; case 2: ...; break; |
jmp [table + rax*8] label0: ... jmp end label1: ... jmp end label2: ... jmp end end: table: dq label0, label1, label2 |
mov DWORD[myInt],7 ; overwrite our int
mov eax,DWORD[myInt] ; copy the modified int into eax
ret
section .data
myInt:
dd 2 ; "data DWORD" containing this value
Dynamic allocation with malloc (call "free" to deallocate the space when you're done):
mov rdi, 4; malloc's first (and only) parameter: number of bytes to allocate
extern malloc
call malloc
; on return, rax points to our newly-allocated memory
mov DWORD [rax],7; write constant into memory
mov eax,DWORD [rax]; read it back from memory
ret
Dynamic allocation on the stack (be sure to hand the space back afterwards!):
sub rsp,8 ; I claim the next eight bytes in the name of... me!
mov DWORD [rsp],1492 ; store an integer into our stack space
mov eax,DWORD [rsp] ; read integer from where we stored it
add rsp,8 ; Hand back the stack space
ret
The stack must always be 8 byte aligned, which is why I'm grabbing 8
bytes, although I only need 4 bytes. Actually, if you call any
functions that do floating point work, the stack actually needs to be
16 byte alignedonce you're inside the function!
To make an assembly function visible from outside, from your assembly say:
global myfunc
myfunc:
You may need to add underscores, or capital letters, or some other crud depending on the system.
To call this from C++, declare a function prototype:
extern "C" int myfunc(int someParameter);
Your function will find its parameters in registers (64-bit mode) or
on the stack (32-bit mode). The exact registers depend on the
machine's calling convention:
64-bit Linux, Mac | 64-bit Windows | 32-bit | |
Return value |
rax |
rax |
eax |
First parameter | rdi | rcx | DWORD[rsp+4] |
Second parameter | rsi | rdx | DWORD[rsp+8] |
Third parameter | rdx | r8 | DWORD[rsp+12] |
More parameters | see docs |
You can also intermix assembly language inside your C++ code using
"inline assembly". The syntax is nice on Windows: __asm { mov
eax,13 }, but hideous and dyslexic on Linux or Mac.