当前位置: 首页 > 面试题库 >

如何通过内联汇编中的sysenter调用系统调用?

詹钊
2023-03-14
问题内容

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

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

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

间接路由到sysenter。

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

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


问题答案:

首先, 您不能asm("");为此安全地使用GNU C Basic语法(没有输入/输出/智能限制)。您需要扩展asm来告知编译器您修改的寄存器。请参阅GNUC手册中的inline asm和inline-assembly标签wiki,以获得指向其他指南的链接,以详细了解"D"(1)作为asm()语句一部分的含义。

我将向您展示如何通过编写HelloWorld!使用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,以避免名称与writelibc提供的“
normal”冲突。此答案的其余部分包含my_writei386和amd64 的来源。

i386

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

如果您想了解更多有关此的内容,请查看此链接。它简要介绍了内核和VDSO中应用的技术。另请参阅《(x86)Linux系统调用的权威指南》
-一些系统调用(如getpidclock_gettime如此简单),内核可以导出在用户空间中运行的代码+数据,因此VDSO无需进入内核,因此速度甚至比sysenter可能。

使用慢速int $0x80调用32位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 0x80API相对简单。系统调用的数量转到eax寄存器,而所需的系统调用去所有的参数分别为ebxecxedxesiedi,和ebp。可以通过读取文件获得系统调用号码/usr/include/asm/unistd_32.h

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

内核保存/恢复了所有寄存器(EAX除外),因此我们可以将它们用作嵌入式asm的仅输入操作数。

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

amd64

在AMD64架构上,情况看起来有所不同,该架构采用了一种称为的新指令SYSCALL。它与原始SYSENTER指令有很大的不同,并且在用户界面应用程序中使用起来肯定更容易-CALL实际上,它类似于正常的,并且使旧版本适应int0x80新版本SYSCALL非常简单。(除了使用RCX和R11而不是内核堆栈来保存用户空间RIP和RFLAGS,以便内核知道返回的位置)。

在这种情况下,系统调用的数量还通过了在寄存器rax,但现在使用保存参数的寄存器近匹配函数调用约定:rdirsirdxr10r8r9的顺序。(syscall它本身销毁了它,rcx所以r10使用它代替rcx,而让libc包装器函数仅使用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;
}

(请参见在Godbolt上编译)

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

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

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



 类似资料:
  • 问题内容: 我正在尝试使用内联汇编…我已阅读此页面http://www.codeproject.com/KB/cpp/edujini_inline_asm.aspx,但是我无法理解传递给函数的参数。 我正在写一个C编写示例。这是我的函数标头: 这是我的汇编代码: 我该怎么做才能将该代码传递给C函数…我正在做这样的事情: 那是因为我没有输出变量,那么我该如何处理呢?另外,使用此代码: 如何将代码内联

  • 问题内容: 说,我想在gcc的内联汇编中调用具有以下签名的函数。我怎样才能做到这一点? 问题答案: 通常,您会想要做类似的事情 也就是说,您根本不需要在嵌入式asm中进行函数调用。这样,您不必担心调用约定或堆栈框架管理的细节。

  • 问题内容: 有谁知道在哪里可以找到汇编语言的Linux系统调用摘要表或备忘单?我通过 int 0x80 指令调用Linux系统调用,我需要不时引用 哪个寄存器包含什么值 。 谢谢。 问题答案: 这是一个非常好的在线参考,其中包含文档和内核源代码的链接。

  • 本文向大家介绍C++系统调用是什么,你用过哪些系统调用相关面试题,主要包含被问及C++系统调用是什么,你用过哪些系统调用时的应答技巧和注意事项,需要的朋友参考一下 参考回答: 1)概念: 在计算机中,系统调用(英语:system call),又称为系统呼叫,指运行在使用者空间的程序向操作系统内核请求需要更高权限运行的服务。系统调用提供了用户程序与操作系统之间的接口(即系统调用是用户程序和内核交互的

  • 本章描述 Linux 内核中的系统调用概念。 系统调用概念简介 - 介绍 Linux 内核中的系统调用概念 Linux 内核如何处理系统调用 - 介绍 Linux 内核如何处理来自于用户空间应用的系统调用。 vsyscall and vDSO - 介绍 vsyscall 和 vDSO 概念。 Linux 内核如何运行程序 - 介绍一个程序的启动过程。 open 系统调用的实现 - 介绍 open

  • 问题内容: 我正在开发一个需要与Video4Linux抽象交互的应用程序。该应用程序使用mono框架以C#开发。 我面临的问题是我无法P /调用系统调用。或者,更准确地说,我可以P /调用它,但是它崩溃严重。 extern声明如下: 到目前为止,一切都很好。 使用的实际例程如下: 以上所有代码似乎都不错。该类用于按照标头规范计算I / O请求代码(基本上,它遵循处声明的宏)。 该参数是一个结构,声