Inf*_*ite 19 linux x86 gcc x86-64 system-calls
我们如何在x86 Linux中直接使用sysenter/syscall实现系统调用?有人可以提供帮助吗?如果您还可以显示amd64平台的代码,那就更好了.
我知道在x86中,我们可以使用
__asm__(
" movl $1, %eax \n"
" movl $0, %ebx \n"
" call *%gs:0x10 \n"
);
Run Code Online (Sandbox Code Playgroud)
间接路由到sysenter.
但是我们如何使用sysenter/syscall直接编码来发出系统调用呢?
我找到了一些材料http://damocles.blogbus.com/tag/sysenter/.但仍然难以弄明白.
Dan*_*zar 32
我将通过编写一个asm("");使用"D"(1)系统调用写入标准输出的程序来向您展示如何执行系统调用.这是没有实际系统调用的程序的源代码:
#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;
}
Run Code Online (Sandbox Code Playgroud)
您可以看到我将自定义系统调用函数命名为asm()以避免与Hello World!libc提供的"normal"名称冲突.本答案的其余部分包含write()i386和amd64 的来源.
i386 Linux中的系统调用是使用第128个中断向量实现的,例如通过调用my_write汇编代码,事先已经相应地设置了参数.可以执行相同的过程write,但实际执行此指令是通过虚拟映射到每个正在运行的进程的VDSO实现的.由于my_write从未意味着直接替代int 0x80API,因此它永远不会由用户态应用程序直接执行 - 相反,当应用程序需要访问某些内核代码时,它会调用VDSO中的虚拟映射例程(这就是SYSENTER代码中的代码) ),其中包含支持该SYSENTER指令的所有代码.由于指令的实际工作方式,它有很多.
如果您想了解更多相关信息,请查看此链接.它包含了内核和VDSO中应用的技术的相当简要的概述.
// 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;
}
Run Code Online (Sandbox Code Playgroud)
如您所见,使用int 0x80API相对简单.系统调用的数量转到call *%gs:0x10寄存器,而所需的系统调用的所有参数进入分别为SYSENTER,getpid,clock_gettime,sysenter,int $0x80,和int 0x80.可以通过读取文件获得系统调用号eax.手册的第2部分提供了功能的原型和说明,因此在本例中ebx.由于允许内核几乎销毁任何寄存器,因此我将所有剩余的GPR放在clobber列表中,以及ecx由于edx寄存器也可能发生变化.请记住,clobber列表还包含esi参数,这意味着指令列表中列出的指令引用内存(通过edi参数).
在AMD64体系结构上看起来非常不同,它采用了一种名为的新指令ebp.这是从原来的完全不同/usr/include/asm/unistd_32.h的指令,绝对更容易从用户级应用程序来使用-它真的像一个正常的write(2),居然和适应旧memory到新的buf几乎是微不足道的.
在这种情况下,系统调用的数量还通过了在寄存器SYSCALL,而是用来存放参数的寄存器有剧烈变化,因为现在他们应该按以下顺序使用:SYSENTER,CALL,int 0x80,SYSCALL,rax和rdi.内核允许破坏寄存器的内容rsi和rdx(他们用于通过节省其他一些寄存器r10).
// 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;
}
Run Code Online (Sandbox Code Playgroud)
请注意,实际上唯一需要更改的是寄存器名称和用于拨打电话的实际指令.这主要归功于gcc扩展内联汇编语法提供的输入/输出列表,该语法自动提供执行指令列表所需的适当移动指令.
显式寄存器变量
https://gcc.gnu.org/onlinedocs/gcc-8.2.0/gcc/Explicit-Register-Variables.html#Explicit-Reg-Vars)
我相信现在这通常应该是针对寄存器限制的推荐方法,因为:
r8、r9和r10用于系统调用参数的寄存器:How to指定寄存器约束 on the Intel x86_64 register r8 to r15 in GCC inline assembly?S -> rsi例如,在 glibc 2.29 中使用了寄存器变量,请参阅:sysdeps/unix/sysv/linux/x86_64/sysdep.h。
main_reg.c
#define _XOPEN_SOURCE 700
#include <inttypes.h>
#include <sys/types.h>
ssize_t my_write(int fd, const void *buf, size_t size) {
register int64_t rax __asm__ ("rax") = 1;
register int rdi __asm__ ("rdi") = fd;
register const void *rsi __asm__ ("rsi") = buf;
register size_t rdx __asm__ ("rdx") = size;
__asm__ __volatile__ (
"syscall"
: "+r" (rax)
: "r" (rdi), "r" (rsi), "r" (rdx)
: "rcx", "r11", "memory"
);
return rax;
}
void my_exit(int exit_status) {
register int64_t rax __asm__ ("rax") = 60;
register int rdi __asm__ ("rdi") = exit_status;
__asm__ __volatile__ (
"syscall"
: "+r" (rax)
: "r" (rdi)
: "rcx", "r11", "memory"
);
}
void _start(void) {
char msg[] = "hello world\n";
my_exit(my_write(1, msg, sizeof(msg)) != sizeof(msg));
}
Run Code Online (Sandbox Code Playgroud)
编译并运行:
gcc -O3 -std=c99 -ggdb3 -ffreestanding -nostdlib -Wall -Werror \
-pedantic -o main_reg.out main_reg.c
./main.out
echo $?
Run Code Online (Sandbox Code Playgroud)
输出
hello world
0
Run Code Online (Sandbox Code Playgroud)
为了进行比较,以下内容类似于如何在内联汇编中通过 syscall 或 sysenter 调用系统调用?产生等效的组件:
main_constraint.c
#define _XOPEN_SOURCE 700
#include <inttypes.h>
#include <sys/types.h>
ssize_t my_write(int fd, const void *buf, size_t size) {
ssize_t ret;
__asm__ __volatile__ (
"syscall"
: "=a" (ret)
: "0" (1), "D" (fd), "S" (buf), "d" (size)
: "rcx", "r11", "memory"
);
return ret;
}
void my_exit(int exit_status) {
ssize_t ret;
__asm__ __volatile__ (
"syscall"
: "=a" (ret)
: "0" (60), "D" (exit_status)
: "rcx", "r11", "memory"
);
}
void _start(void) {
char msg[] = "hello world\n";
my_exit(my_write(1, msg, sizeof(msg)) != sizeof(msg));
}
Run Code Online (Sandbox Code Playgroud)
两者的拆卸:
objdump -d main_reg.out
Run Code Online (Sandbox Code Playgroud)
几乎相同,这是main_reg.c:
Disassembly of section .text:
0000000000001000 <my_write>:
1000: b8 01 00 00 00 mov $0x1,%eax
1005: 0f 05 syscall
1007: c3 retq
1008: 0f 1f 84 00 00 00 00 nopl 0x0(%rax,%rax,1)
100f: 00
0000000000001010 <my_exit>:
1010: b8 3c 00 00 00 mov $0x3c,%eax
1015: 0f 05 syscall
1017: c3 retq
1018: 0f 1f 84 00 00 00 00 nopl 0x0(%rax,%rax,1)
101f: 00
0000000000001020 <_start>:
1020: c6 44 24 ff 00 movb $0x0,-0x1(%rsp)
1025: bf 01 00 00 00 mov $0x1,%edi
102a: 48 8d 74 24 f3 lea -0xd(%rsp),%rsi
102f: 48 b8 68 65 6c 6c 6f movabs $0x6f77206f6c6c6568,%rax
1036: 20 77 6f
1039: 48 89 44 24 f3 mov %rax,-0xd(%rsp)
103e: ba 0d 00 00 00 mov $0xd,%edx
1043: b8 01 00 00 00 mov $0x1,%eax
1048: c7 44 24 fb 72 6c 64 movl $0xa646c72,-0x5(%rsp)
104f: 0a
1050: 0f 05 syscall
1052: 31 ff xor %edi,%edi
1054: 48 83 f8 0d cmp $0xd,%rax
1058: b8 3c 00 00 00 mov $0x3c,%eax
105d: 40 0f 95 c7 setne %dil
1061: 0f 05 syscall
1063: c3 retq
Run Code Online (Sandbox Code Playgroud)
所以我们看到 GCC 按照需要内联了那些微小的系统调用函数。
my_write和my_exit两者相同,但_startinmain_constraint.c略有不同:
0000000000001020 <_start>:
1020: c6 44 24 ff 00 movb $0x0,-0x1(%rsp)
1025: 48 8d 74 24 f3 lea -0xd(%rsp),%rsi
102a: ba 0d 00 00 00 mov $0xd,%edx
102f: 48 b8 68 65 6c 6c 6f movabs $0x6f77206f6c6c6568,%rax
1036: 20 77 6f
1039: 48 89 44 24 f3 mov %rax,-0xd(%rsp)
103e: b8 01 00 00 00 mov $0x1,%eax
1043: c7 44 24 fb 72 6c 64 movl $0xa646c72,-0x5(%rsp)
104a: 0a
104b: 89 c7 mov %eax,%edi
104d: 0f 05 syscall
104f: 31 ff xor %edi,%edi
1051: 48 83 f8 0d cmp $0xd,%rax
1055: b8 3c 00 00 00 mov $0x3c,%eax
105a: 40 0f 95 c7 setne %dil
105e: 0f 05 syscall
1060: c3 retq
Run Code Online (Sandbox Code Playgroud)
有趣的是,在这种情况下,GCC 通过选择找到了一个稍短的等效编码:
104b: 89 c7 mov %eax,%edi
Run Code Online (Sandbox Code Playgroud)
设置fdto 1,它等于1系统调用号中的 ,而不是更直接的:
1025: bf 01 00 00 00 mov $0x1,%edi
Run Code Online (Sandbox Code Playgroud)
有关调用约定的深入讨论,另请参阅:i386 和 x86-64 上 UNIX & Linux 系统调用(和用户空间函数)的调用约定是什么
在 Ubuntu 18.10、GCC 8.2.0 中测试。
| 归档时间: |
|
| 查看次数: |
12944 次 |
| 最近记录: |