栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 面试经验 > 面试问答

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

面试问答 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

如何通过内联汇编中的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
,以避免名称与
write
libc提供的“
normal”冲突。此答案的其余部分包含
my_write
i386和amd64 的来源。

i386

i386 Linux中的系统调用是使用第128个中断向量实现的,例如,通过调用

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

如果您想了解更多有关此的内容,请查看此链接。它简要介绍了内核和VDSO中应用的技术。另请参阅《(x86)Linux系统调用的权威指南》
-一些系统调用(如

getpid
clock_gettime
如此简单),内核可以导出在用户空间中运行的代码+数据,因此VDSO无需进入内核,因此速度甚至比
sysenter
可能。


使用慢速

int $0x80
调用32位ABI 要容易得多。

// i386 Linux#include <asm/unistd.h>      // compile with -m32 for 32 bit call numbers//#define __NR_write 4ssize_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;}

如您所见,使用

int0x80
API相对简单。系统调用的数量转到
eax
寄存器,而所需的系统调用去所有的参数分别为
ebx
ecx
edx
esi
edi
,和
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
,但现在使用保存参数的寄存器近匹配函数调用约定:
rdi
rsi
rdx
r10
r8
r9
的顺序。(
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 1ssize_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传递约定也不同。



转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/373938.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号