NASM assembler

GAS : GNU assembler
NASM(Intel syntax) vs GAS(AT&T syntax)

ELF system
GNU assembler
NASM tutorial
Another NASM tutorial
X86 calling convention

1 structure of a NASM program

2 寄存器

整形寄存器

  • 64bits
    R0 R1 R2 R3 R4 R5 R6 R7 R8 R9 R10 R11 R12 R13 R14 R15
    RAX RCX RDX RBX RSP RBP RSI RDI

  • 32bits
    R0D R1D R2D R3D R4D R5D R6D R7D R8D R9D R10D R11D R12D R13D R14D R15D
    EAX ECX EDX EBX ESP EBP ESI EDI

  • 16bits
    R0W R1W R2W R3W R4W R5W R6W R7W R8W R9W R10W R11W R12W R13W R14W R15W
    AX CX DX BX SP BP SI DI

  • 8bits
    R0B R1B R2B R3B R4B R5B R6B R7B R8B R9B R10B R11B R12B R13B R14B R15B
    AL CL DL BL SPL BPL SIL DIL

浮点寄存器

16 XMM寄存器, 每一个寄存器128位宽度:

XMM0 … XMM15

3 内存寻址

1
2
3
4
5
[number]
[reg]
[reg + number]
[reg + reg * scale]
[reg + reg * scale + number]

[reg + reg scale + number] 完整版
reg为基址(base)
number为偏移(displacement)
reg
scale是索引(index)

4 立即数

  • 10进制
    200
    200d
    0d200

  • 16进制
    0c8h
    0xc8

  • 8进制
    210q
    0q210

  • 2进制
    11001000b
    0b1100_1000

5 运算指令

  • mul乘法指令
    格式 mul [操作数]

乘法指令的第一个操作数一定要放在eax寄存器之间,第二个操作数可以放在其他寄存器之中,运算结果存在eax寄存器中

  • div除法指令同理

    6 分支跳转

  • cmp
  • je =
  • jne !=
  • jl <
  • jnl ! <
  • jg >
  • jng !>
  • jle <=

7 调用约定

参数传递

从左到右,传递尽可能多的参数到寄存器中
整形 rdi rsi rdx rcx r8 r9
浮点 xmm0 xmm1 xmm2 xmm3 xmm4 xmm5 xmm6 xmm7

多余的参数以从右往左的顺序推入堆栈

返回地址 [rsp]
第一个参数[rsp + 8]

rsp对齐

The stack pointer rsp must be aligned to a 16-byte boundary before making a call. Fine, but the process of making a call pushes the return address (8 bytes) on the stack, so when a function gets control, rsp is not aligned. You have to make that extra space yourself, by pushing something or subtracting 8 from rsp.

清理

caller clean up

callee save

rbp rbx r12 r13 r14 r15

8 section

  • data
  • text

  • bss

  • undefined section

9 entry point

NASM默认入口是_start

1
2
3
GLOBAL _start
section text
_start:

可以用ld -o out a.o -e _main改变入口函数
what-is-global-start-in-assembly-language

10 使用syscall

  • 将调用的syscall号码放在eax寄存器中
  • 将参数按照顺序传入ebx ecx, etc
  • 发出中断
  • 结果在eax中
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
section .text
global _start

_start:
mov edx, 5 ; 要写的信息的长度
mov ecx, msg ; 要写的信息
mov ebx, 1 ; 文件描述符号 1为标准输出
mov eax, 4 ; 系统调用号
int 0x80

mov eax,1 ; sys_exit
mov ebx,0 ; Exit with return code of 0 (no error)
int 80h;

msg:
db "hello"

使用c库函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
section .text
global main
extern printf

main:
push rbp;
mov rbp, rsp;
sub rsp, 16;
mov dword [rbp - 16], 2;
movsx rax, dword [rbp - 16];
mov rdi, message;
mov rsi, rax;
call printf;
add rsp, 16;
mov eax, 0;
pop rbp;
ret;

section .data
message db "Register = %0d", 10, 0

11 mov

mov指令无法在memory和memory之间移动
需要加入寄存器作为中介

不是16字节对齐会引起segment fault

divide two numbers in nasm

12 fpu

fpu register

https://stackoverflow.com/questions/22710272/difference-in-floating-point-arithmetics-between-x86-and-x64

13 type specifier

mov指令用于

  • 两个寄存器之间 (移动内容确定 因为寄存器大小确定)
  • 寄存器和内存地址之间 (移动内容不确定 因为内存地址指向的大小不定)
    如果不用type specifier, 移动内容大小为寄存器大小

    1
    2
    mov eax, [ebp - 4]; 4 bytes
    movsx eax, WORD [ebp - 4]; 2 bytes, signed extended
  • 寄存器和立即数 (移动内容不确定 因为立即数大小不确定)

  • 内存地址和立即数 (移动内容不确定 因为内存地址指向的大小和立即数大小不定)

14 named memory

db (define byte) 声明byte大小的内存
dd (define double word) 声明double word大小的内存
dw (define word) 声明word大小的内存

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
section .text
global main
extern printf

main:
push rbp;
mov rbp, rsp;
sub rsp, 16;
mov dword [rbp - 16], 2;
movsx rax, dword [rbp - 16];
mov rbx, 2;
mov rdi, message + 14;
mov rsi, rax;
call printf;
add rsp, 16;
mov eax, 0;
pop rbp;
ret;

section .data
message db "Register = %0d","Register = %0d", 10, 0