x86-64汇编中函数调用和系统调用

1、函数调用

函数调用是指应用程序中从一个函数(调用方即caller)中调用另一个函数(被调用方即callee)。使用call和ret指令对,在同一特权级内完成跳转和返回的控制权转移。

函数调用以及参数传递参照x86-64函数调用中栈和栈帧文章中的函数示例。

1.1 函数调用参数传递

函数调用时前六个参数使用寄存器,超过6个参数后使用栈,参数需要倒序入栈。

寄存器描述
rdi第一个参数
rsi第二个参数
rdx第三个参数
rcx第四个参数
r8第五个参数
r9第六个参数

2、系统调用

系统调用是跨越用户态与内核态边界的受控入口,在x86-64系统中使用专门的syscall 和 sysret指令对。syscall指令是由操作系统提供的全局可用的函数,会触发一个受控的模式切换,将CPU从用户态提升到内核态。sysret指令将CPU从内核态降回用户态。

2.1 系统调用编号

系统调用由一个数字引用即系统调用编号(系统调用的唯一标识),该数字必须存储在RAX寄存器中。

x86-64 Linux系统中所有的系统调用编号都在/usr/include/x86_64-linux-gnu/asm/unistd_64.h头文件中,例如查找exit函数的编号:

$ cat /usr/include/x86_64-linux-gnu/asm/unistd_64.h | grep exit
#define __NR_exit 60
#define __NR_exit_group 231

2.2 系统调用参数传递

在使用系统调用前,需要确定函数需要的参数,以确保可以正常工作。

系统调用参数转递时需要使用的寄存器:

寄存器描述
rax系统调用编号
rdi第一个参数
rsi第二个参数
rdx第三个参数
r10第四个参数
r8第五个参数
r9第六个参数

可以通过man man 命令来查看(以下内容截取了DESCRIPTION部分):

$ man man
       The table below shows the section numbers of the manual followed by the types of pages they contain.
       1   Executable programs or shell commands                                                                        
       2   System calls (functions provided by the kernel)                                                              
       3   Library calls (functions within program libraries)                                                           
       4   Special files (usually found in /dev)                                                                        
       5   File formats and conventions, e.g. /etc/passwd                                                               
       6   Games                                                                                                        
       7   Miscellaneous (including macro packages and conventions), e.g. man(7), groff(7), man-pages(7)                
       8   System administration commands (usually only for root)                                                       
       9   Kernel routines [Non standard]  

可以看出节编号2表示是系统调用。

访问特定的系统调用函数的man页面,可以使用-s 2 参数:

$ man -s 2 exit

NAME
       _exit, _Exit - terminate the calling process

LIBRARY
       Standard C library (libc, -lc)

SYNOPSIS
       #include <unistd.h>

       [[noreturn]] void _exit(int status);

       #include <stdlib.h>

       [[noreturn]] void _Exit(int status);

可以看出exit函数需要一个整型参数(status 用于指定退出状态码)。

2.3 系统调用示例

系统调用exit函数示例(test_exit.s 文件汇编风格:AT&T):

.global _start
.section .text
_start:
    # 设置系统调用编号
    mov $60, %rax
    # 设置第一个参数
    xor %rdi, %rdi
    syscall

编译、链接并运行:

$ as -o test_exit.o test_exit.s
$ ld -o test_exit.o test_exit
$ ./test_exit
# 打印程序执行退出后的状态码
$ echo $?
0

三、两者对比

函数调用系统调用
指令call / retsyscall / sysret
参数传递rdi : 第一个参数
rsi : 第二个参数
rdx : 第三个参数
rcx : 第四个参数
r8 : 第五个参数
r9 : 第六个参数
+栈(超过6个时)
rdi : 第一个参数
rsi : 第二个参数
rdx : 第三个参数
r10 : 第四个参数
r8 : 第五个参数
r9 : 第六个参数
rax : 系统调用编号
使用的栈用户程序栈(共享)内核栈

注意:参数传递中,函数调用和系统调用的第四个参数分别是:

函数调用:rcx

系统调用:r10

使用man -s 2 syscall命令可以查看各个系统架构支持的系统调用时参数传递使用的寄存器(仅列出了部分系统)。

$ man -s 2 syscall
       The second table shows the registers used to pass the system call arguments.
       Arch/ABI      arg1  arg2  arg3  arg4  arg5  arg6  arg7  Notes
       ──────────────────────────────────────────────────────────────
       x86-64        rdi   rsi   rdx   r10   r8    r9    -
       x32           rdi   rsi   rdx   r10   r8    r9    -       

如有错误请指正。

评论

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注