我们如何直接在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 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
寄存器,系统调用所需的所有参数分别进入ebx
, ecx
, edx
, esi
, edi
和ebp
。 系统调用号码可以通过读取文件/usr/include/asm/unistd_32.h
。 本手册第二部分提供了原型和功能描述,所以在这种情况下write(2)
。 由于内核被允许销毁几乎所有的寄存器,所以我把所有剩余的GPR放在clobber列表中,以及cc
,因为eflags
寄存器也可能改变。 请记住,clobber列表还包含memory
参数,这意味着指令列表中列出的指令参考内存(通过buf
参数)。
AMD64体系结构看起来非常不同,它包含一个名为SYSCALL
的新指令。 它和原来的SYSENTER
指令有很大的不同,而且在用户级应用程序中的使用肯定要容易得多 – 实际上,它实际上和一个正常的CALL
类似,并且将旧的int 0x80
适配到新的SYSCALL
是相当简单的。
在这种情况下,系统调用的编号仍然在寄存器rax
传递,但是用于保存参数的寄存器已经发生了严重的变化,因为现在应该按照以下顺序使用它们: rdi
, rsi
, rdx
, r10
, r8
和r9
。 内核被允许销毁寄存器rcx
和r11
(它们被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的扩展内联汇编语法提供的输入/输出列表,它自动提供执行指令列表所需的适当的移动指令。