nasm官方文档:https://www.nasm.us/xdoc/2.14.03rc2/nasmdoc.pdf
当遇到问题时,可以在文档中查询,文档是英文的,需要一定的英语阅读能力。
下面是一个输出的例子。
; hello.asm
section .data
msg db 'hello, world!',0xa
len equ $-msg
section .text
global _start
_start:
mov rax, 4
mov rbx, 1
mov rcx, msg
mov rdx, len
int 0x80
mov rax, 1
int 0x80
在ubuntu 64位的机器上编译运行:
nasm -f elf64 -o hello.o hello.asm
ld -o hello hello.o
./hello
这个时候会在控制台中看到输出结果hello, world!
程序分析:
section是程序段的意思,首先定义了.data数据段,在data数据段中,定义了2个变量msg和len。
.text段中,_start作为程序的入口点,当然可以换成其他的名字。
之后,使用了mov指令,对寄存器rax,rbx,rcx,rdx等赋值,最后执行int 0x80,进行系统调用。
我们可以使用系统调用,就是系统函数。rax保存系统调用号,4是System_write的系统调用号,可以在文件/usr/include/x86_64-linux-gnu/asm/unistd_32.h查看系统调用号。不同的系统位置可能不同。
使用man 2 write查看write的用法,找到write函数的原型:
ssize_t write(int fd, const void *buf, size_t count);
fd是文件描述符,buf是指向要输出内容的指针,而count是输出的数量,按字节计数。
mov rbx, 1
mov rcx, msg
mov rdx, len
rbx,rcx,rdx保存系统调用的参数,即fd=1,buf=msg,count=len
当参数更多时,使用下面的寄存器保存系统调用的参数:rbx,rcx,rdx,rsi,rdi,rbp
int 0x80指令进行系统调用。
不过,现在可以使用syscall指令来替换int 0x80,这时候,规则有了变化,rax依然保存系统调用号,而系统调用的参数使用下面的寄存器,用户模式的系统调用依次传递的寄存器为:rdi,rsi,rdx,rcx,r8和r9;而内核接口的系统调用依次传递的寄存器为:rdi,rsi,rdx,r10,r8和r9。这里只有第4个参数不同。我们使用的是用户模式的系统调用,因此,上面的hello.asm代码可以修改为下面这样:
; hello2.asm
seciont .data
msg db 'hello, world',0xa
len equ $-msg
section .text
global _start
_start:
mov rax, 1
mov rdi, 1
mov rsi, msg
mov rdx, len
syscall
mov rax, 60
syscall
这里发生了一些变化,当使用syscall进行系统调用时,需要使用64位的系统调用号,在文件/usr/include/x86_64-linux-gnu/asm/unistd_64.h查看64位的系统调用号。
write的系统调用号是1,而exit的系统调用号是60.