如何调用内联汇编通过SYSENTER系统调用?(How to invoke a system cal

2019-10-17 10:17发布

我们怎样才能实现直接使用SYSENTER /系统调用在Linux的x86的系统调用? 任何人能提供帮助? 它甚至会更好,如果你也能显示AMD64平台的代码。

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

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

路由到间接SYSENTER。

但如何才能代码直接使用SYSENTER /系统调用发出一个系统调用?

我发现一些材料http://damocles.blogbus.com/tag/sysenter/ 。 但仍难以弄清楚。

Answer 1:

我要告诉你如何通过编写写一个程序执行系统调用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为了避免与“正常”的名字冲突write ,由libc中提供。 这个答案的其余部分包含的源my_write i386和amd64。

I386

在i386的Linux系统调用使用的是128的中断向量,例如通过调用执行int 0x80在你的汇编代码,将其具有的相应参数事前,当然。 这是可能通过做同样的SYSENTER ,但实际上执行该指令被虚拟地映射到每个正在运行的进程VDSO实现。 由于SYSENTER从来就直接取代了的int 0x80 API,它从来没有直接通过用户模式应用程序执行的-相反,当一个应用程序需要访问一些内核代码,它调用VDSO的虚拟地映射程序(这是什么样call *%gs:0x10在你的代码是),它包含了所有的代码支持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寄存器,而所需的系统调用的所有参数进入分别ebxecxedxesiediebp 。 系统调用号可以通过读取文件获得/usr/include/asm/unistd_32.h 。 原型和功能的描述是在手动的第二部分提供,所以在这种情况下, write(2) 由于内核允许几乎摧毁任何寄存器的,我把所有剩余的GPRS撞名单上,还有cc ,因为eflags寄存器也可能发生改变。 请记住,在撞列表还包含了memory参数,这意味着在指令列表中引用的内存(通过列出的指令buf参数)。

AMD64

事情看起来在AMD64架构,它带来了一个所谓的新指令非常不同的SYSCALL 。 这是从原来的完全不同的SYSENTER指令,肯定更容易从用户级应用程序来使用-它真的就像一个正常的CALL ,竟然和适应老int 0x80SYSCALL是非常微不足道的。

在这种情况下,系统调用的数量还通过了在寄存器rax ,而是用来存放参数的寄存器有剧烈变化,因为现在他们应该按以下顺序使用: rdirsirdxr10r8r9 。 内核允许破坏内容的寄存器rcxr11 (他们用于通过节省其他一些寄存器的SYSCALL )。

#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的扩展内联组件语法,其自动地提供了所需用于执行指令列表适当移动指令所提供的输入/输出表。



Answer 2:

明确的寄存器变量

只是为了完整性,我想提供使用例如GCC明确的寄存器变量 。

该机制具有以下优点:

  • 它可以代表所有的寄存器,包括r8r9r10这是用于系统调用的参数: 如何指定登记在Intel x86_64的寄存器R8限制在GCC内联汇编R15?
  • 我认为,此语法比使用单字母助记符如更易读S -> rsi

寄存器变量用于例如在glibc的2.29,见: sysdeps/unix/sysv/linux/x86_64/sysdep.h

还要注意的是其他archs如ARM已经完全放弃了单字母助记符,和寄存器变量是做到这一点的唯一方法似乎看到例如: 如何设定一个独立的寄存器在ARM GCC内联汇编约束?

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)
        : "cc", "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)
        : "cc", "rcx", "r11", "memory"
    );
}

void _start(void) {
    char msg[] = "hello world\n";
    my_exit(my_write(1, msg, sizeof(msg)) != sizeof(msg));
}

GitHub的上游 。

编译并运行:

gcc -O3 -std=c99 -ggdb3 -ffreestanding -nostdlib -Wall -Werror \
  -pedantic -o main_reg.out main_reg.c
./main.out
echo $?

产量

hello world
0

为了便于比较,下面类似于如何调用内联汇编通过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)
        : "cc", "rcx", "r11", "memory"
    );
    return ret;
}

void my_exit(int exit_status) {
    ssize_t ret;
    __asm__ __volatile__ (
        "syscall"
        : "=a" (ret)
        : "0" (60), "D" (exit_status)
        : "cc", "rcx", "r11", "memory"
    );
}

void _start(void) {
    char msg[] = "hello world\n";
    my_exit(my_write(1, msg, sizeof(msg)) != sizeof(msg));
}

GitHub的上游 。

以两者的拆卸:

objdump -d main_reg.out

几乎是相同的,这里是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   

所以我们看到,GCC内联为将所需的这些微小的系统调用函数。

my_writemy_exit是两个相同的,但_startmain_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 

有趣的是,观察到在这种情况下,GCC发现稍短相当于编码通过挑选:

    104b:   89 c7                   mov    %eax,%edi

设置fd1 ,它等于1 ,从系统调用数目,而不是更直接的:

    1025:   bf 01 00 00 00          mov    $0x1,%edi    

对于调用约定的深入讨论,参见: 什么是UNIX和Linux系统调用约定调用在i386和x86-64

经测试在Ubuntu 18.10,GCC 8.2.0。



文章来源: How to invoke a system call via sysenter in inline assembly?