本文将主要讲述如何在汇编语言代码中调用Linux的系统调用。
一、32位系统的系统调用
在32位x86 Linux系统中,可用的系统调用定义在/usr/include/asm/unistd_32.h
头文件中。
每个系统调用都对应一个编号 以及 若干个参数。如果想使用汇编语言调用系统调用,那么在调用之前,需要将系统调用编号存到%eax
,将参数依次存到%ebx, %ecx, %edx, %esi, %edi, %ebp
中,然后再执行int $0x80
指令即可。
每个系统调用的编号和参数列表可以参考:https://chromium.googlesource.com/chromiumos/docs/+/master/constants/syscalls.md#x86-32_bit
下面的汇编代码通过write
系统调用往屏幕上打印出了Hello
。
.section .data
output:
.string "Hello\n"
.text
...
movl $4, %eax # write对应编号为4
movl $1, %ebx # 第1个参数文件描述符为1,表示stdout
leal output(%rip), %ecx # 第2个参数,字符串指针
movl $6, %edx # 第3个参数,写入的字节数
int $0x80
movq $1, %eax # exit系统调用
xor %edi, %edi # 第1个参数,0,即exit(0)
int $0x80
二、64位系统的系统调用
在64位x86_64 Linux系统中,可用的系统调用定义在/usr/include/asm/unistd_64.h
头文件中。
每个系统调用都对应一个编号 以及 若干个参数。如果想使用汇编语言调用系统调用,那么在调用之前,需要将系统调用编号存到%rax
,将参数依次存到%rdi, %rsi, %rdx, %r10, %r8, %r9
中,然后再执行syscall
指令即可。
每个系统调用的编号和参数列表可以参考:
https://chromium.googlesource.com/chromiumos/docs/+/master/constants/syscalls.md#x86_64-64_bit
下面的汇编代码通过write
系统调用往屏幕上打印出了Hello
。
.section .rodata
msg:
.string "Hello\n"
.text
.globl _start
_start:
pushq %rax # for stack alignment
movq $1, %rax
movq $1, %rdi
leaq msg(%rip), %rsi
movq $6, %rdx
syscall
# exit(0)
movq $60, %rax
xor %rdi, %rdi
syscall
需要注意,上述代码中第一行是为了实现栈对齐,关于栈对齐请参考x86_64汇编之四:函数调用、调用约定。
如何运行上述代码?
as -o a.o a.s
ld -o a a.o
./a
三、调用C函数
除了直接使用Linux提供的系统调用接口之外,我们还可以在汇编代码中直接使用C函数,例如直接调用call printf
来调用printf
函数。参数传递的规范遵循System V AMD64调用约定。
.section .rodata
msg:
.ascii "Hello, %d\n"
.text
.globl main
main:
pushq %rbp # for stack alignment
leaq msg(%rip), %rdi # 1st param
movq $1, %rsi # 2nd param
movb $0, %al # variable length param function:
# number of vector registers
callq printf
# exit(0)
movq $0, %rdi
callq exit
注意上述的movb $0, %al
:因为printf
是可变参数函数,因此根据System V AMD64调用约定需要在%al
寄存器中设置它使用的向量寄存器个数。
汇编并链接上述代码:
as -o a.o a.s
gcc -o a a.o # 由于调用了C标准库的函数,所以使用gcc进行链接
./a
最终打印出Hello, 1
。