如何在内联程序集(x86 / amd64 linux)中通过sysenter调用系统调用?

我们如何直接在x86 Linux中使用sysenter / syscall实现系统调用? 任何人都可以提供帮助吗? 如果你也可以显示amd64平台的代码,那将会更好。

我知道在x86中,我们可以使用

__asm__( " movl $1, %eax \n" " movl $0, %ebx \n" " call *%gs:0x10 \n" ); 

间接路由到sysenter。

但是我们如何直接使用sysenter / syscall来编写系统调用?

我find一些材料http://damocles.blogbus.com/tag/sysenter/ 。 但仍然觉得很难弄清楚。

我将向您展示如何通过编写一个编写Hello World!的程序来执行系统调用Hello World! 通过使用write()系统调用到标准输出。 下面是程序的来源,没有实际的系统调用:

 #include <sys/types.h> ssize_t my_write(int fd, const void *buf, size_t size); int main(void) { const char hello[] = "Hello world!\n"; my_write(1, hello, sizeof(hello)); return 0; } 

您可以看到,我将自定义系统调用函数命名为my_write ,以避免与由libc提供的“正常” write冲突。 这个答案的其余部分包含i386和amd64的my_write的来源。

I386

i386 Linux中的系统调用是通过使用第128个中断向量来实现的,例如通过在汇编代码中调用int 0x80 ,事先设置相应的参数。 通过SYSENTER可以做同样的事情,但实际执行这个指令是由VDSO实际上映射到每个正在运行的进程来实现的。 由于SYSENTER从来不是直接替代int 0x80 API,它绝不会被用户级应用程序直接执行 – 相反,当应用程序需要访问某些内核代码时,它会调用VDSO中的虚拟映射例程(这就是call *%gs:0x10代码是for),它包含所有支持SYSENTER指令的代码。 由于指令的实际工作原理,其中有很多。

如果你想了解更多关于这个,请看这个链接 。 它简要介绍了在内核和VDSO中应用的技术。

 #define __NR_write 4 ssize_t my_write(int fd, const void *buf, size_t size) { ssize_t ret; asm volatile ( "int $0x80" : "=a" (ret) : "0"(__NR_write), "b"(fd), "c"(buf), "d"(size) : "cc", "edi", "esi", "memory" ); return ret; } 

正如你所看到的,使用int 0x80 API相对简单。 系统调用的号码进入eax寄存器,系统调用所需的所有参数分别进入ebxecxedxesiediebp 。 系统调用号码可以通过读取文件/usr/include/asm/unistd_32.h 。 本手册第二部分提供了原型和功能描述,所以在这种情况下write(2) 。 由于内核被允许销毁几乎所有的寄存器,所以我把所有剩余的GPR放在clobber列表中,以及cc ,因为eflags寄存器也可能改变。 请记住,clobber列表还包含memory参数,这意味着指令列表中列出的指令参考内存(通过buf参数)。

AMD64

AMD64体系结构看起来非常不同,它包含一个名为SYSCALL的新指令。 它和原来的SYSENTER指令有很大的不同,而且在用户级应用程序中的使用肯定要容易得多 – 实际上,它实际上和一个正常的CALL类似,并且将旧的int 0x80适配到新的SYSCALL是相当简单的。

在这种情况下,系统调用的编号仍然在寄存器rax传递,但是用于保存参数的寄存器已经发生了严重的变化,因为现在应该按照以下顺序使用它们: rdirsirdxr10r8r9 。 内核被允许销毁寄存器rcxr11 (它们被rcx用于保存一些其他的寄存器)。

 #define __NR_write 1 ssize_t my_write(int fd, const void *buf, size_t size) { ssize_t ret; asm volatile ( "syscall" : "=a" (ret) : "0"(__NR_write), "D"(fd), "S"(buf), "d"(size) : "cc", "rcx", "r11", "memory" ); return ret; } 

请注意,实际上唯一需要改变的是注册名称和用于拨打电话的实际指令。 这主要得益于gcc的扩展内联汇编语法提供的输入/输出列表,它自动提供执行指令列表所需的适当的移动指令。