gpt4 book ai didi

linux - Intel x86与x64系统调用

转载 作者:行者123 更新时间:2023-12-01 22:23:21 36 4
gpt4 key购买 nike

我正在阅读有关x86和x64之间的汇编差异的信息。

在x86上,系统调用号码放在eax中,然后执行int 80h生成软件中断。

但是在x64上,系统调用号放在rax中,然后执行syscall

有人告诉我syscall比生成软件中断更轻巧,更快。

为什么在x64上它比x86更快,并且我可以使用int 80h在x64上进行系统调用吗?

最佳答案

一般部分

编辑:Linux无关的部分已删除

虽然不是完全错误,但缩小到int 0x80syscall可以简化问题,因为sysenter至少有第三个选择。

使用0x80和eax作为系统调用编号,ebx,ecx,edx,esi,edi和ebp传递参数只是实现系统调用的许多其他选择之一,但是这些寄存器是32位Linux ABI选择的寄存器。

在仔细研究所涉及的技术之前,应该指出,它们都绕过了逃避特权运行过程的问题。

x86架构在此提供的选择的另一个选择是使用 call 门(请参阅:http://en.wikipedia.org/wiki/Call_gate)

所有i386机器上存在的唯一其他可能性是使用软件中断,该中断使ISR(中断服务程序或简称为中断处理程序)以与以前不同的特权级别运行。

(有趣的事实:某些i386操作系统使用无效指令异常进入系统调用内核,因为它实际上比386 CPU上的int指令快。有关可能的系统调用机制的概述,请参见OsDev syscall/sysret and sysenter/sysexit instructions enabling。)

软件中断

触发中断后,究竟发生什么情况取决于切换到ISR是否需要更改特权:

(英特尔®64和IA-32架构软件开发人员手册)

6.4.1 Call and Return Operation for Interrupt or Exception Handling Procedures

...

If the code segment for the handler procedure has the same privilege level as the currently executing program or task, the handler procedure uses the current stack; if the handler executes at a more privileged level, the processor switches to the stack for the handler’s privilege level.

....

If a stack switch does occur, the processor does the following:

  1. Temporarily saves (internally) the current contents of the SS, ESP, EFLAGS, CS, and > EIP registers.

  2. Loads the segment selector and stack pointer for the new stack (that is, the stack for the privilege level being called) from the TSS into the SS and ESP registers and switches to the new stack.

  3. Pushes the temporarily saved SS, ESP, EFLAGS, CS, and EIP values for the interrupted procedure’s stack onto the new stack.

  4. Pushes an error code on the new stack (if appropriate).

  5. Loads the segment selector for the new code segment and the new instruction pointer (from the interrupt gate or trap gate) into the CS and EIP registers, respectively.

  6. If the call is through an interrupt gate, clears the IF flag in the EFLAGS register.

  7. Begins execution of the handler procedure at the new privilege level.



...叹息这似乎有很多事情要做,即使一旦完成,也不会变得更好:

(摘录自上述同一来源:英特尔®64和IA-32架构软件开发人员手册)

When executing a return from an interrupt or exception handler from a different privilege level than the interrupted procedure, the processor performs these actions:

  1. Performs a privilege check.

  2. Restores the CS and EIP registers to their values prior to the interrupt or exception.

  3. Restores the EFLAGS register.

  4. Restores the SS and ESP registers to their values prior to the interrupt or exception, resulting in a stack switch back to the stack of the interrupted procedure.

  5. Resumes execution of the interrupted procedure.



Sysenter

在您的问题中完全没有提到的32位平台上的另一个选项,但是Linux内核仍然使用了 sysenter指令。

(英特尔®64和IA-32体系结构软件开发人员手册第2卷(2A,2B和2C):指令集引用,A-Z)

Description Executes a fast call to a level 0 system procedure or routine. SYSENTER is a companion instruction to SYSEXIT. The instruction is optimized to provide the maximum performance for system calls from user code running at privilege level 3 to operating system or executive procedures running at privilege level 0.



使用此解决方案的一个缺点是,并非所有32位计算机上都存在该解决方案,因此,如果CPU不知道,则仍必须提供 int 0x80方法。

The SYSENTER and SYSEXIT instructions were introduced into the IA-32 architecture in the Pentium II processor. The availability of these instructions on a processor is indicated with the SYSENTER/SYSEXIT present (SEP) feature flag returned to the EDX register by the CPUID instruction. An operating system that qualifies the SEP flag must also qualify the processor family and model to ensure that the SYSENTER/SYSEXIT instructions are actually present



系统调用

最后一种可能性是 syscall指令,几乎允许使用与 sysenter指令相同的功能。两者之所以存在,是因为一个( systenter)由Intel引入,而另一个( syscall)由AMD引入。

特定于Linux

在Linux内核中,可以选择上述三种可能性中的任何一种来实现系统调用。

另请参见 The Definitive Guide to Linux System Calls

如上所述, int 0x80方法是3种选择的实现中的唯一一种,它可以在任何i386 CPU上运行,因此,这是唯一始终可用于32位用户空间的方法。

( syscall是唯一始终可用于64位用户空间和 the only one you should ever use in 64-bit code的代码;可以在不使用 CONFIG_IA32_EMULATION的情况下构建x86-64内核,并且 int 0x80仍调用32位ABI来将指针截断到32位。)

为了允许在所有3个选项之间切换,每个进程运行都被授予对特殊共享对象的访问权限,该共享对象可以访问为正在运行的系统选择的系统调用实现。这是看起来很奇怪的 linux-gate.so.1,当您使用 ldd等时,您可能已经遇到它作为未解析的库。

(arch/x86/vdso/vdso32-setup.c)
 if (vdso32_syscall()) {                                                                               
vsyscall = &vdso32_syscall_start;
vsyscall_len = &vdso32_syscall_end - &vdso32_syscall_start;
} else if (vdso32_sysenter()){
vsyscall = &vdso32_sysenter_start;
vsyscall_len = &vdso32_sysenter_end - &vdso32_sysenter_start;
} else {
vsyscall = &vdso32_int80_start;
vsyscall_len = &vdso32_int80_end - &vdso32_int80_start;
}

要利用它,您要做的就是将所有寄存器的系统调用号加载到eax中,将参数ebx,ecx,edx,esi,edi加载到 int 0x80系统调用实现中,并将 call加载到主例程中。

不幸的是,这并不是那么容易。为了最大程度地减少固定的预定义地址的安全风险,将随机显示 vdso(虚拟动态共享对象)在进程中的位置,因此您必须首先确定正确的位置。

该地址是每个进程的专用地址,并且在启动后将其传递给该进程。

如果您不知道,在Linux中启动时,每个进程都会获得指向其启动后传递的参数的指针,以及指向其正在运行的环境变量的描述的指针,该指针在其堆栈上传递-每个变量都以NULL终止。

除了这些之外,继前面提到的那些之后,又传递了所谓的elf-辅助 vector 的第三块。正确的位置被编码为携带类型标识符 AT_SYSINFO的其中之一。

因此,堆栈布局如下所示(地址向下增长):
  • 参数-0
  • ...
  • 参数-m
  • NULL
  • 环境-0
  • ....
  • 环境-n
  • NULL
  • ...
  • 辅助精灵 vector :AT_SYSINFO
  • ...
  • 辅助精灵 vector :AT_NULL

  • 使用范例

    要找到正确的地址,您将必须首先跳过所有参数和所有环境指针,然后开始扫描 AT_SYSINFO,如下例所示:
    #include <stdio.h>
    #include <elf.h>

    void putc_1 (char c) {
    __asm__ ("movl $0x04, %%eax\n"
    "movl $0x01, %%ebx\n"
    "movl $0x01, %%edx\n"
    "int $0x80"
    :: "c" (&c)
    : "eax", "ebx", "edx");
    }

    void putc_2 (char c, void *addr) {
    __asm__ ("movl $0x04, %%eax\n"
    "movl $0x01, %%ebx\n"
    "movl $0x01, %%edx\n"
    "call *%%esi"
    :: "c" (&c), "S" (addr)
    : "eax", "ebx", "edx");
    }


    int main (int argc, char *argv[]) {

    /* using int 0x80 */
    putc_1 ('1');


    /* rather nasty search for jump address */
    argv += argc + 1; /* skip args */
    while (*argv != NULL) /* skip env */
    ++argv;

    Elf32_auxv_t *aux = (Elf32_auxv_t*) ++argv; /* aux vector start */

    while (aux->a_type != AT_SYSINFO) {
    if (aux->a_type == AT_NULL)
    return 1;
    ++aux;
    }

    putc_2 ('2', (void*) aux->a_un.a_val);

    return 0;
    }

    如您所见,请查看系统上的 /usr/include/asm/unistd_32.h的以下片段:
    #define __NR_restart_syscall 0
    #define __NR_exit 1
    #define __NR_fork 2
    #define __NR_read 3
    #define __NR_write 4
    #define __NR_open 5
    #define __NR_close 6

    我使用的syscall是在eax寄存器中传递的编号为4(写)的那个。
    以filedescriptor(ebx = 1),数据指针(ecx =&c)和size(edx = 1)作为其参数,每个参数都传递到相应的寄存器中。

    简而言之

    使用(最初由AMD发明的) int 0x80指令将在任何英特尔CPU上运行缓慢的 syscall系统调用与(希望)更快的实现进行比较,比较苹果与橘子。

    恕我直言:很有可能应该在这里测试 sysenter指令而不是 int 0x80

    关于linux - Intel x86与x64系统调用,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/15168822/

    36 4 0
    Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
    广告合作:1813099741@qq.com 6ren.com