跳转到内容

x86 汇编/与 Linux 交互

来自维基教科书,开放世界中的开放书籍

系统调用

[编辑 | 编辑源代码]

系统调用是用户程序和 Linux 内核之间的接口。它们用于让内核执行各种系统任务,例如文件访问、进程管理和网络。在 C 编程语言中,您通常会调用一个包装函数来执行所有必需的步骤,甚至使用高级功能,例如标准 IO 库。

在 Linux 上,有几种方法可以发出系统调用。此页面将重点介绍通过使用 int $0x80syscall 调用软件中断来发出系统调用。这是一种在纯汇编程序中发出系统调用的简单直观的方法。

发出系统调用

[编辑 | 编辑源代码]

为了通过中断发出系统调用,您必须通过将所有必需的信息复制到 GPRs 中来传递它们给内核。

每个系统调用都有一个固定的编号。Linux 永久保证向后兼容性,因此一旦一个编号被分配给一个系统调用,它就不会再改变。永远。

Warning int $0x80syscall 的编号不同!

您可以通过将编号写入 eax/rax 寄存器来指定系统调用。

大多数系统调用都采用参数来执行其任务。这些参数通过在发出实际调用之前将它们写入相应的寄存器来传递。每个参数索引都有一个特定的寄存器。请查看小节中的表格,因为 int $0x80syscall 之间的映射不同。参数按它们在相应 C 包装函数的函数签名中出现的顺序传递。您可以在每个 Linux ABI 文档中找到系统调用函数及其签名,例如参考手册(键入 man 2 open 查看 open 系统调用的签名)。

在一切设置正确后,您可以使用 int $0x80syscall 调用中断,内核将执行任务。

系统调用的返回值/错误值将写入 eax/rax

内核使用自己的堆栈来执行操作。用户堆栈不会以任何方式被触碰。

通过中断

[编辑 | 编辑源代码]

在 Linux x86 和 Linux x86_64 系统上,您可以通过使用 int 指令调用中断 $0x80 来发出系统调用。参数通过设置如下通用寄存器来传递

使用 int $0x80 发出系统调用的寄存器映射
系统调用编号 第一个参数 第二个参数 第三个参数 第四个参数 第五个参数 第六个参数 结果
eax ebx ecx edx esi edi ebp eax

系统调用编号在 Linux 生成的文件 $build/‌arch/‌x86/‌include/‌generated/‌uapi/‌asm/‌unistd_32.h$build/‌usr/‌include/‌asm/‌unistd_32.h 中描述。后者也可能存在于您的 Linux 系统中,只需省略 $build

在使用 int $0x80 发出系统调用期间,所有寄存器都会被保留,除了 eax,返回值将存储在那里。

通过专用系统调用指令

[编辑 | 编辑源代码]

x86_64 架构引入了一条专用指令来发出系统调用。它不访问中断描述符表,并且速度更快。参数通过设置如下 GPRs 来传递

使用 syscall 发出系统调用的寄存器映射
系统调用编号 第一个参数 第二个参数 第三个参数 第四个参数 第五个参数 第六个参数 结果
rax rdi rsi rdx r10 r8 r9 rax

系统调用编号在 Linux 生成的文件 $build/‌usr/‌include/‌asm/‌unistd_64.h 中描述。此文件也可能存在于您的 Linux 系统中,只需省略 $build

在使用 syscall 发出系统调用期间,所有寄存器都会被保留,除了 rcxr11(以及返回值 rax)。

为了实现最大兼容性,在 64 位平台上,Linux 会剪裁使用中断方法的系统调用的输入输出。这意味着,例如,您不能在使用 int $0x80 方法的 x86-64 平台上传递或接收(完整的)64 位地址指针,因为所有参数和结果的高 32 位将被清零。这通常与 syscall 的一般偏好一致,因为它比中断更快。

库调用

[编辑 | 编辑源代码]

在 x86-64 Linux 的 C 库函数调用中,参数 6 在 r9 上传递,后续参数在堆栈上传递(以相反顺序)。

库调用寄存器映射
第一个参数 第二个参数 第三个参数 第四个参数 第五个参数 第六个参数
rdi rsi rdx rcx r8 r9

调用者可以预期在 rax 寄存器中找到子例程的返回值。

为了总结和澄清信息,让我们来看一个非常简单的例子:hello world 程序。它将使用 write 系统调用将文本 "Hello World" 写入标准输出,并使用 _exit 系统调用退出程序。

系统调用签名

ssize_t write(int fd, const void *buf, size_t count);
void _exit(int status);

这是在下面汇编中实现的 C 程序

#include <unistd.h>

int main(int argc, char *argv[])
{
    write(1, "Hello World\n", 12); /* write "Hello World" to stdout */
    _exit(0);                      /* exit with error code 0 (no error) */
}

两个示例的开头都一样:一个存储在数据段中的字符串和作为全局符号的 _start

.data
msg: .ascii "Hello World\n"

.text
.global _start

int $0x80

[编辑 | 编辑源代码]

$build/usr/include/asm/unistd_32.h 中定义的那样,write_exit 的系统调用号为

#define __NR_exit 1
#define __NR_write 4

参数的传递方式与在 C 程序中一样,使用正确的寄存器。设置好一切后,使用 int $0x80 进行系统调用。

_start:
    movl $4, %eax   ; use the `write` [interrupt-flavor] system call
    movl $1, %ebx   ; write to stdout
    movl $msg, %ecx ; use string "Hello World"
    movl $12, %edx  ; write 12 characters
    int $0x80       ; make system call
    
    movl $1, %eax   ; use the `_exit` [interrupt-flavor] system call
    movl $0, %ebx   ; error code 0
    int $0x80       ; make system call

系统调用

[编辑 | 编辑源代码]

$build/usr/include/asm/unistd_64.h 中,系统调用号定义如下

#define __NR_write 1
#define __NR_exit 60

参数的传递方式与 int $0x80 示例中相同,只是寄存器的顺序不同。系统调用使用 syscall 完成。

_start:
    movq $1, %rax   ; use the `write` [fast] syscall
    movq $1, %rdi   ; write to stdout
    movq $msg, %rsi ; use string "Hello World"
    movq $12, %rdx  ; write 12 characters
    syscall         ; make syscall
    
    movq $60, %rax  ; use the `_exit` [fast] syscall
    movq $0, %rdi   ; error code 0
    syscall         ; make syscall

库调用

[编辑 | 编辑源代码]

这是一个示例库函数的 C 原型。

Window XCreateWindow(display, parent, x, y, width, height, border_width, depth,
                       class, visual, valuemask, attributes)

参数的传递方式与 int $0x80 示例中相同,只是寄存器的顺序不同。

库函数在源文件开头声明(库路径在编译链接时声明)。

extern XCreateWindow
		mov rdi, [xserver_pdisplay]
		mov rsi, [xwin_parent]
		mov rdx, [xwin_x]
		mov rcx, [xwin_y]
		mov r8, [xwin_width]
		mov r9, [xwin_height]
		mov rax, attributes
		push rax				; ARG 12
		sub rax, rax
		mov eax, [xwin_valuemask]
		push rax				; ARG 11
		mov rax, [xwin_visual]
		push rax				; ARG 10
		mov rax, [xwin_class]
		push rax				; ARG 9
		mov rax, [xwin_depth]
		push rax				; ARG 8
		mov rax, [xwin_border_width]
		push rax				; ARG 7
		call XCreateWindow
		mov [xwin_window], rax

注意函数的最后几个参数,它们以相反的顺序被压入堆栈。

华夏公益教科书