贡献者: addis
参考一些 x86-64 的例子,Tutorials Point(提供一些 cpu 原理,但用的是 NASM 不是 GAS)。
一个例子:
.global _start
.text
_start:
# write(1, message, 13)
mov $1, %rax # system call 1 is write
mov $1, %rdi # file handle 1 is stdout
mov $message, %rsi # address of string to output
mov $13, %rdx # number of bytes
syscall # invoke operating system to do the write
# exit(0)
mov $60, %rax # system call 60 is exit
xor %rdi, %rdi # we want return code 0
syscall # invoke operating system to exit
message:
.ascii "Hello, world\n"
编译:gcc -c hello.s && ld hello.o && ./a.out
,运行:./hello.x
gcc
使用的 assembler 是 GNU Assembler (GAS)
#
mov $4, %eax
把 register eax
赋值为 4。%
表示 register,$
表示 immediate value,可以用十进制 4
也可以 16 进制 0x4
。
mov (%rdi), %rax
可以把 rdi
作为指针 dereference,然后赋值给 rax
。
xor %寄存器, %寄存器
可以把 寄存器
的值设为 0。xor
是逐 bit 异或,结果存到第一个变量。也可以用 mov $0, %寄存器
,但是这样会读取内存,减慢速度。
$
和 %
往往是可省略的
mov
等效于 movl
,64 位系统上等效于 movq
。
.section .text
用于声明一个 section,text 说明这是执行程序。.section
可以省略。
syscall
一般代替 int 0x80
进行 systemcall,后者已经过时了。
标签:
是一个 label,用于指定代码的某个位置,用于跳转。也可以定义一个函数。
rax, rbx, rcx, rdx
: arithmetic and data manipulation
rsi, rdi, rbp, rsp
: storing data and addressing memory
r8
-r15
: additional arithmetic and data manipulation
r
(register),x
(extended)
rsi
(source index register),rdi
(destination index register),rbp
(base pointer register),rsp
(stack pointer register)
r
换成 e
(extended)表示 32 位
eax
: Accumulator
ebx
: Base
ecx
: Counter
edx
: Data
esi
: Source index
edi
: Destination index
ebp
: Base pointer
esp
: Stack pointer
rdi, rsi, rdx, rcx, r8, r9
xmm0, xmm1, ..., xmm7
rax
,再按普通函数的变量顺序给参数赋值。
syscall
命令。
sys_write
- write to a file descriptor
sys_open
- open a file
sys_close
- close a file descriptor
sys_exit
- exit the process
sys_exit_group
- exit all threads in a process
inc %xxx
把寄存器的值加 1。dec %xxx
寄存器值减 1。
push %xxx
是把寄存器的值 push 到 stack 顶部。pop %xxx
把 stack 顶部的值取回到寄存器。
jnz 标签
是条件跳转。当 ecx
为 0 时就不跳。
add %xxx, %yyy
把两个值相加,存到第一个。
sub %xxx, %yyy
同理,相减。
and %xxx, %yyy
逐 bit 相与。
-g
,就可以用 gdb ./hello.x
来调试程序。
b main
可以在 main 的第一行设置 break point,r
跑程序。
p $xxx
可以查看 register 的值。注意不是 %
disassemble
命令可以看到当前的汇编码。
gcc -S main.c
,但是生成汇编码的可读性比较差(比手写的复杂)。
# A 64-bit Linux application that writes the first 90 Fibonacci numbers.
# gcc -no-pie -g fib.s -o fib.x && ./fib.x
.global main
.text
main:
push %rbx # we have to save this since we use it
mov $10, %ecx # ecx will countdown to 0
xor %rax, %rax # rax will hold the current number
xor %rbx, %rbx # rbx will hold the next number
inc %rbx # rbx is originally 1
print:
# We need to call printf, but we are using printf
# may destroy eax and ecx so we will save these before the call and
# restore them afterwards.
push %rax # caller-save register
push %rcx # caller-save register
mov $format, %rdi # set 1st parameter (format)
mov %rax, %rsi # set 2nd parameter (current_number)
xor %rax, %rax # because printf is varargs
# Stack is already aligned because we pushed three 8 byte registers
call printf # printf(format, current_number)
pop %rcx # restore caller-save register
pop %rax # restore caller-save register
mov %rax, %rdx # save the current number
mov %rbx, %rax # next number is now current
add %rdx, %rbx # get the new next number
dec %ecx # count down
jnz print # if not done counting, do some more
pop %rbx # restore rbx before returning
ret
format:
.asciz "%20ld\n" # null terminated string
x/2 $rsp
可以查看 stack 顶部的值。在程序一开始记录下该值可以知道 stack 第一个元素之前的位置。2
代表打印 2 个 hex,每个 hex 4 字节(32bit)。x
是 examine。
rbx, rbp, r12-r15
call printf
调用标准库中的 printf
函数,函数的参数由以下 register 按照顺序携带