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 2312.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 / ret | syscall / 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 -
如有错误请指正。
发表回复