关于 macOS 下的系统调用
Selisya
2020年12月28日 23:32

最开始我打算写篇文章讲讲 64 位 nasm 汇编,但是写到一半感觉有一些过分细节的内容不适合放到一篇文章里,所以决定把这个主题单独拿出来作为一篇文章。 本来应该是这样的,但是现在专栏功能的代码块似乎是坏掉了,所以在代码块好起来之前应该不会再写编程方面的文章了。

在编程中做系统调用是一件稀松平常的事情,最简单的退出程序,到输入输出,到文件操作等等,这些动作都需要通过系统调用来完成。

具体到调用方法上来说,我们可以查看 System V ABI 这份文档,直接在网上搜就能找到,差不多长这个样子

这份文档介绍了 x86 ABI 的各方面内容,但是我们关注的内容在最后附录部分,也就是 Appendix A 的 A.2.1 Calling Conventions,这里介绍了进行系统调用的方法。

简单来说有这么几个要点:

  • 通过整数寄存器进行参数传递,参数的顺序是 rdi, rsi, rdx, rcx, r8, r9;

  • 在 rax 中设定功能调用号

  • 参数设定好了之后使用 syscall 指令进行系统调用,同时这个指令不会保护寄存器 rcx 和 r11;

  • 系统调用的返回值会保存在 rax 中。


现在我们来看一个例子。

在 Apple Open Source 中有一份叫 syscalls.master 的文件,直接在网上搜「syscalls.master」就可以找到这个文件,下载下来转成 pdf 差不多是这个样子的:

里面给出的信息我们具体看两个地方,

第一个框 Number 代表系统调用号,第二个框 Name and Args 给出了函数名和参数表,那对于这一行

我们就知道用于输出的 write 系统调用,它的调用号是 4,有三个参数 fd,cbuf,nbyte,然后去查一查 Linux 的手册就知道 fd 是 file descriptor(值为 1 的时候代表 stdout),cbuf 是字符串地址,nbyte 是字符串长度。

现在我们可以写出这样的代码:

如果有 DOS 编程经验的话对上面这些东西应该会比较熟悉,因为 DOS 下的系统功能调用跟上面的代码是非常相似的,例如在 DOS 下要输出一个字符的话,需要设定 al = 02H,dl = 要输出的字符,然后使用 int 21H 指令进行系统调用。

按理说这段代码就应该可以在屏幕上输出「hi there」这一行字符串了。

不过事实上并不是,在 Linux 下可能是可以的,在 macOS 下还需要一些额外的操作。


还是在 Apple Open Source,有一个叫 syscall_sw.h 的文件,在这个文件中有这么一段

也就是说 macOS 下有几类系统调用,而它们都通过同一条 syscall 指令陷入内核。为了区分各类系统调用,macOS 会让 rax 中低 32 位里的高位表示这个调用所属类别,而低位作为调用号,高 32 位不做使用。

我们要使用的类别是 SYSCALL_CLASS_UNIX,就是标号 2 的类别,通过标号 1、3 我们会发现 SYSCALL_CLASS_UNIX 需要左移 SYSCALL_CLASS_SHIFT,也就是 24 位这么多,所以实际上调用号应该写成 0x02000004 才对。

其他的系统调用也同理,需要在原先的调用号的基础上加上 0x02000000 才能让代码正常工作。