如何通过内联汇编中的syscall或sysenter调用系统调用? [英] How to invoke a system call via syscall or sysenter in inline assembly?

查看:174
本文介绍了如何通过内联汇编中的syscall或sysenter调用系统调用?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我们如何直接在x86 Linux中使用sysenter/syscall来实现系统调用?有人可以提供帮助吗?如果您还可以显示amd64平台的代码,那就更好了.

How can we implement the system call using sysenter/syscall directly in x86 Linux? Can anybody provide help? It would be even better if you can also show the code for amd64 platform.

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

I know in x86, we can use

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

间接路由到sysenter.

to route to sysenter indirectly.

但是我们如何直接使用sysenter/syscall进行编码以发出系统调用呢?

But how can we code using sysenter/syscall directly to issue a system call?

我找到了一些资料 http://damocles.blogbus.com/tag/sysenter/.但是仍然很难弄清楚.

I find some material http://damocles.blogbus.com/tag/sysenter/ . But still find it difficult to figure out.

推荐答案

首先,您不能为此安全地使用GNU C Basic asm("");语法(没有输入/输出/集群)约束).您需要扩展asm来告诉编译器您修改的寄存器.请参阅GNU C手册中的内联asm inline-assembly标签Wiki 链接到其他指南,以详细了解"D"(1)之类的含义. asm()语句的一部分.

First of all, you can't safely use GNU C Basic asm(""); syntax for this (without input/output/clobber constraints). You need Extended asm to tell the compiler about registers you modify. See the inline asm in the GNU C manual and the inline-assembly tag wiki for links to other guides for details on what things like "D"(1) means as part of an asm() statement.

我将向您展示如何通过使用write()系统调用编写将Hello World!写入标准输出的程序来执行系统调用.这是程序的源代码,没有实现实际的系统调用:

I'm going to show you how to execute system calls by writing a program that writes Hello World! to standard output by using the write() system call. Here's the source of the program without an implementation of the actual system call :

#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来源.

You can see that I named my custom system call function as my_write in order to avoid name clashes with the "normal" write, provided by libc. The rest of this answer contains the source of my_write for i386 and amd64.

i386 Linux中的系统调用是使用第128个中断向量实现的,例如当然,可以通过在汇编代码中调用int 0x80来预先设置相应的参数.可以通过SYSENTER执行相同的操作,但是实际上通过虚拟映射到每个正在运行的进程的VDSO来执行该指令.由于SYSENTER从来都不是直接替代int 0x80 API的意思,因此它永远不会由用户级应用程序直接执行-相反,当应用程序需要访问某些内核代码时,它将调用VDSO中的虚拟映射例程(即代码中的call *%gs:0x10用于),其中包含所有支持SYSENTER指令的代码.由于指令的实际工作方式,因此有很多.

System calls in i386 Linux are implemented using the 128th interrupt vector, e.g. by calling int 0x80 in your assembly code, having set the parameters accordingly beforehand, of course. It is possible to do the same via SYSENTER, but actually executing this instruction is achieved by the VDSO virtually mapped to each running process. Since SYSENTER was never meant as a direct replacement of the int 0x80 API, it's never directly executed by userland applications - instead, when an application needs to access some kernel code, it calls the virtually mapped routine in the VDSO (that's what the call *%gs:0x10 in your code is for), which contains all the code supporting the SYSENTER instruction. There's quite a lot of it because of how the instruction actually works.

如果您想了解更多有关此的信息,请查看此链接.它包含内核和VDSO中应用的技术的简要概述.另请参见

If you want to read more about this, have a look at this link. It contains a fairly brief overview of the techniques applied in the kernel and the VDSO. See also The Definitive Guide to (x86) Linux System Calls - some system calls like getpid and clock_gettime are so simple the kernel can export code + data that runs in user-space so the VDSO never needs to enter the kernel, making it much faster even than sysenter could be.

使用速度较慢的int $0x80调用32位ABI要容易得多.

It's much easier to use the slower int $0x80 to invoke the 32-bit ABI.

// i386 Linux
#include <asm/unistd.h>      // compile with -m32 for 32 bit call numbers
//#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)
        : "memory"    // the kernel dereferences pointer args
    );
    return ret;
}

如您所见,使用int 0x80 API相对简单. syscall的编号进入eax寄存器,而syscall所需的所有参数分别进入ebxecxedxesiediebp.可以通过读取文件/usr/include/asm/unistd_32.h获得系统呼叫号码.

As you can see, using the int 0x80 API is relatively simple. The number of the syscall goes to the eax register, while all the parameters needed for the syscall go into respectively ebx, ecx, edx, esi, edi, and ebp. System call numbers can be obtained by reading the file /usr/include/asm/unistd_32.h.

功能的原型和说明在手册的第二部分中提供,因此在这种情况下为write(2).

Prototypes and descriptions of the functions are available in the 2nd section of the manual, so in this case write(2).

内核保存/恢复所有寄存器(EAX除外),因此我们可以将它们用作内联asm的仅输入操作数.请参阅是UNIX&的调用约定Linux系统在i386和x86-64上调用

The kernel saves/restores all the registers (except EAX) so we can use them as input-only operands to the inline asm. See What are the calling conventions for UNIX & Linux system calls on i386 and x86-64

请记住,Clobber列表还包含memory参数,这意味着指令列表中列出的指令(通过buf参数)引用了内存. (对嵌入式asm的指针输入并不意味着指向的内存也是输入.请参见

Keep in mind that the clobber list also contains the memory parameter, which means that the instruction listed in the instruction list references memory (via the buf parameter). (A pointer input to inline asm does not imply that the pointed-to memory is also an input. See How can I indicate that the memory *pointed* to by an inline ASM argument may be used?)

AMD64体系结构看起来很不一样,该体系结构采用了称为SYSCALL的新指令.它与原始的SYSENTER指令有很大不同,并且绝对易于在用户界面应用程序中使用-实际上,它实际上类似于普通的CALL,并且使旧的int 0x80适应新的SYSCALL几乎是微不足道的. . (除了它使用RCX和R11而不是内核堆栈来保存用户空间RIP和RFLAGS,以便内核知道返回的位置.)

Things look different on the AMD64 architecture which sports a new instruction called SYSCALL. It is very different from the original SYSENTER instruction, and definitely much easier to use from userland applications - it really resembles a normal CALL, actually, and adapting the old int 0x80 to the new SYSCALL is pretty much trivial. (Except it uses RCX and R11 instead of the kernel stack to save the user-space RIP and RFLAGS so the kernel knows where to return).

在这种情况下,系统调用的编号仍在寄存器rax中传递,但是用于保存参数的寄存器现在几乎与函数调用约定匹配:rdirsirdxr10r8r9的顺序. (syscall本身会破坏rcx

In this case, the number of the system call is still passed in the register rax, but the registers used to hold the arguments now nearly match the function calling convention: rdi, rsi, rdx, r10, r8 and r9 in that order. (syscall itself destroys rcx so r10 is used instead of rcx, letting libc wrapper functions just use mov r10, rcx / syscall.)

// x86-64 Linux
#include <asm/unistd.h>      // compile without -m32 for 64 bit call numbers
// #define __NR_write 1
ssize_t my_write(int fd, const void *buf, size_t size)
{
    ssize_t ret;
    asm volatile
    (
        "syscall"
        : "=a" (ret)
        //                 EDI      RSI       RDX
        : "0"(__NR_write), "D"(fd), "S"(buf), "d"(size)
        : "rcx", "r11", "memory"
    );
    return ret;
}

(请参见请注意,实际上实际上唯一需要更改的是寄存器名称以及用于进行调用的实际指令.这主要归功于gcc扩展的内联汇编语法提供的输入/输出列表,它自动提供执行指令列表所需的适当移动指令.

Do notice how practically the only thing that needed changing were the register names, and the actual instruction used for making the call. This is mostly thanks to the input/output lists provided by gcc's extended inline assembly syntax, which automagically provides appropriate move instructions needed for executing the instruction list.

"0"(callnum)匹配约束可以写为"a",因为操作数0("=a"(ret)输出)只有一个寄存器可供选择.我们知道它将选择EAX.使用更清晰的内容.

The "0"(callnum) matching constraint could be written as "a" because operand 0 (the "=a"(ret) output) only has one register to pick from; we know it will pick EAX. Use whichever you find more clear.

请注意,非Linux操作系统(如MacOS)使用不同的电话号码.甚至32位的arg传递约定也不同.

Note that non-Linux OSes, like MacOS, use different call numbers. And even different arg-passing conventions for 32-bit.

这篇关于如何通过内联汇编中的syscall或sysenter调用系统调用?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆