英特尔的x86 VS x64的系统调用 [英] Intel x86 vs x64 system call

查看:268
本文介绍了英特尔的x86 VS x64的系统调用的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我在阅​​读有关x86和x64之间的装配的差异。

在x86上,系统调用号放在 EAX ,那么 INT 80H 执行生成软件中断。

但在x64,系统调用号放在 RAX ,那么系统调用执行

我听说系统调用比产生软件中断更轻,速度更快。

为什么它是在64比86快了,我可以做一个系统调用使用的x64 INT 80H


解决方案

常规部分

编辑:Linux的不相干的部分去掉

虽然不是完全错误缩小到 INT 0x80的系统调用简单化的问题与SYSENTER至少有第3选项。

使用0x80的和EAX为系统调用号,EBX,ECX,EDX,ESI,EDI和EBP传递参数只是众多可能的其他选择来实现一系统调用。

在服用的技术细看参与之前,应该说,他们逃离周围的特权监狱的每个进程都运行的问题所有的社交圈。

另一个选择在这里psented的那些$ P $由x86架构提供的将是使用调用门(见: http://en.wikipedia.org/wiki/Call_gate

在所有的i386机器唯一的其他可能性present使用软件中断,这使得ISR在不同的权限级别比之前运行。

软件中断

什么一旦中断被触发究竟发生取决于是否切换到ISR需要权限更改与否:

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


  

6.4.1呼叫和中断返回操作或异常处理程序


  
  

...


  
  

如果在处理过程中的code段有
  相同特权级别作为当前正在执行的程序或任务,所述
  处理过程使用当前栈;如果处理程序执行在
  更多的特权级别,处理器切换到栈
  处理程序的权限级别。


  
  

...


  
  

如果一个堆叠交换机确实发生,处理器执行以下操作:


  
  

      
  1. 暂时保存(内部)的SS,ESP,EFLAGS,CS的当前内容和> EIP寄存器。


  2.   
  3. 加载的段选择和堆栈指针为新的叠层(即,堆叠的特权级别之中
      从TSS到SS和ESP寄存器的称呼)并切换到新的堆栈。


  4.   
  5. 推的中断程序的堆栈的临时保存SS,ESP,EFLAGS,CS,EIP和价值观上
       新的堆栈。


  6.   
  7. 推新的堆栈上的错误code(如适用)。


  8.   
  9. 装载新的code段和新指令指针的段选择(从中断门
       或陷阱门)到CS与EIP寄存器分别。


  10.   
  11. 如果呼叫是通过一个中断门,在EFLAGS寄存器清除IF标志。


  12.   
  13. 在新的特权级别开始的处理程序的执行。


  14.   

......感叹这似乎是有很多事情要做,甚至一度就完成了它没有得到太大改善:

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


  

在执行从中断或异常处理程序从一回
  对特权级别中断的过程中,
  处理器执行以下操作:


  
  

      
  1. 执行特权检查。


  2.   
  3. 中断或异常CS与EIP寄存器的值之前恢复。


  4.   
  5. 在恢复EFLAGS注册。


  6.   
  7. 恢复在SS和ESP寄存器之前中断或异常的值,导致一个堆叠交换机回
      中断程序的堆栈。


  8.   
  9. 重新开始中断程序的执行。


  10.   

SYSENTER

在你的问题没有提及在所有的,但尽管如此,Linux内核使用的32位平台上的另一个选项是 SYSENTER 指令。

(英特尔®64和IA-32架构软件开发人员手册第2卷(2A,2B和放大器; 2C):指令集参考,A-Z)


  

说明执行快速调用0级系统过程或
  常规。 SYSENTER是一个同伴指令SYSEXIT。该
  指令被优化,以提供对系统中的最大性能
  从用户code。在特权级3运行的操作系统调用
  或者在运行0级特权执行程序。


使用这种方案的一个缺点是,它不是在所有32位机器present,因此 INT 0x80的方法仍具有在被提供情况下,CPU不知道这件事。


  

的SYSENTER和SYSEXIT指令被引入的IA-32
  架构在奔腾II处理器。这些可用性
  处理器上指示的标志是SYSENTER / SYSEXIT
  present(SEP)功能标志由CPUID返回EDX寄存器
  指令。一个操作系统有资格的SEP标志还必须
  限定处理器系列和型号,以保证
  SYSENTER / SYSEXIT指令实际上是present


系统调用

最后一个可能,系统调用指令,pretty多允许相同的功能的 SYSENTER 指令。两者的存在,是由于一( systenter )是由英特尔公司推出的事实,而其他(系统调用)由AMD推出。

Linux特有

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

正如上文已经说过的, INT 0x80的法是3中选择实现的只有一个,那可以在任何i386的CPU上运行,所以这是唯一的一个是始终可用。

要允许所有3个选择每一道工序的运行被赋予访问一个特殊的共享对象可以访问到被选为运行系统的系统调用的实现之间切换。这是奇怪的 Linux的gate.so.1 您已经可以使用的时候都遇到过因为悬而未决库 LDD 或等

(弓/ 86 / VDSO / vdso32-setup.c中)

 如果(vdso32_syscall()){
        vsyscall =安培; vdso32_syscall_start;
        vsyscall_len =安培; vdso32_syscall_end - &安培; vdso32_syscall_start;
    }否则如果(vdso32_sysenter()){
        vsyscall =安培; vdso32_sysenter_start;
        vsyscall_len =安培; vdso32_sysenter_end - &安培; vdso32_sysenter_start;
    }其他{
        vsyscall =安培; vdso32_int80_start;
        vsyscall_len =安培; vdso32_int80_end - &安培; vdso32_int80_start;
    }

要利用一切你所要做的就是加载EAX所有寄存器的系统调用号,EBX,ECX,EDX,ESI参数,EDI与 INT 0x80的系统调用实现和呼叫主程序。

不幸的是它不是那么容易,尽量减少一个固定的$ P $的安全风险pdefined解决在该 VDSO 将在过程中是可见的位置是随机的,所以你必须首先弄清楚正确的位置。

此地址单独给每个进程传递给它,一旦它开始。

如果你不知道,在Linux下启动时,每一道工序得到指针传递的参数,一旦它开始和指向它在它的堆栈传递运行的环境变量的描述 - 他们每个人终止由NULL。

此外这些所谓的小精灵 - 辅助矢量的第三块以下之前提到的那些被传递。正确位置为连接codeD中的这些携带类型标识符有一个 AT_SYSINFO

所以栈布局看起来是这样的:


  • 参数0

  • ...

  • 参数-M

  • NULL

  • 环境0

  • ...

  • 环境-N

  • NULL

  • ...

  • 的辅助精灵矢量: AT_SYSINFO

  • ...

  • 的辅助精灵矢量: AT_NULL

使用示例

要找到你必须先跳过所有参数和所有的环境指针,然后开始扫描 AT_SYSINFO ,如下面的例子中,正确的地址:

 的#include<&stdio.h中GT;
#包括LT&;&elf.h中GT;无效putc_1(焦三){
  __asm​​__(MOVL $ 0x04的EAX %% \\ n
           MOVL $为0x01,EBX %% \\ n
           MOVL $为0x01,EDX %% \\ n
           INT 0x80的$
           ::C(和C)
           :EAX,EBX,EDX);
}无效putc_2(焦C,无效*地址){
  __asm​​__(MOVL $ 0x04的EAX %% \\ n
           MOVL $为0x01,EBX %% \\ n
           MOVL $为0x01,EDX %% \\ n
           呼叫* %% ESI
           ::C(和C),S(地址)
           :EAX,EBX,EDX);
}
INT主(INT ARGC,CHAR *的argv []){  / *使用INT 0x80的* /
  putc_1('1');
  / *为跳转地址*而讨厌搜索/
  的argv + = ARGC + 1; / *跳过ARGS * /
  而(* argv的!= NULL)/ *跳过ENV * /
    ++ argv的;  Elf32_auxv_t * AUX =(Elf32_auxv_t *)++ argv的; / *辅助载体开始* /  而(AUX->!a_type = AT_SYSINFO){
    如果(AUX-> a_type == AT_NULL)
      返回1;
    ++辅助;
  }  putc_2('2',(无效*)AUX-> a_un.a_val);  返回0;
}

正如你将采取一起来看看 /usr/include/asm/unistd_32.h 的片断我的系统上看到的:

 的#define __NR_restart_syscall 0
#定义__NR_exit 1
#定义__NR_fork 2
#定义__NR_read 3
#定义__NR_write 4
#定义__NR_open 5
#定义__NR_close 6

我使用的系统调用是一个4号(写)作为EAX寄存器传递。
以文件描述符(EBX = 1),数据指针(ECX =和C),大小(EDX = 1)作为它的参数,每个相应寄存器传递

为了把长话短说

一个假想运行缓慢 INT 0x80的系统调用上的任何的使用英特尔CPU一个(希望)更快速的实现了(真正按照发明的比较AMD)系统调用指令比较苹果和橘子。

恕我直言:最有可能的 SYSENTER 指令而不是 INT 0x80的应该是这里的测试。

I'm reading about the difference in assembly between x86 and x64.

On x86, the system call number is placed in eax, then int 80h is executed to generate a software interrupt.

But on x64, the system call number is placed in rax, then syscall is executed.

I'm told that syscall is lighter and faster than generating a software interrupt.

Why it is faster on x64 than x86, and can I make a system call on x64 using int 80h?

解决方案

General part

EDIT: Linux irrelevant parts removed

While not totally wrong narrowing down to int 0x80 and syscall oversimplifies the question as with sysenter there is at least a 3rd option.

Using 0x80 and eax for syscall number, ebx, ecx, edx, esi, edi, and ebp to pass parameters is just one of many possible other choices to implement a system call.

Before taking a closer look at the techniques involved, it should be stated that they all circle around the problem of escaping the privilege prison every process runs in.

Another choice to the ones presented here offered by the x86 architecture would have been the use of a call gate (see: http://en.wikipedia.org/wiki/Call_gate)

The only other possibility present on all i386 machines is using a software interrupt, which allows the ISR to run in a different privilege level than before.

Software Interrupt

What exactly happens once an interrupt is triggered depends on whether switching to the ISR requires a privilege change or not:

(Intel® 64 and IA-32 Architectures Software Developer’s Manual)

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.

... sigh this seems to be a lot to do and even once we're done it doesn't get too much better:

(excerpt taken from the same source as mentioned above: Intel® 64 and IA-32 Architectures Software Developer’s Manual)

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

Another option on the 32-bit platform not mentioned in your question at all, but nevertheless utilized by the Linux kernel is the sysenter instruction.

(Intel® 64 and IA-32 Architectures Software Developer’s Manual Volume 2 (2A, 2B & 2C): Instruction Set Reference, 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.

One disadvantage of using this solution is, that it is not present on all 32-bit machines, so the int 0x80 method still has to be provided in case the CPU doesn't know about it.

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

The last possibility, the syscall instruction, pretty much allows for the same functionality as the sysenter instruction. The existence of both is due to the fact that one (systenter) was introduced by Intel while the other (syscall) was introduced by AMD.

Linux specific

In the Linux kernel any of the three possibilities mentioned above may be chosen to realize a system call.

As already stated above, the int 0x80 method is the only one of the 3 chosen implementations, that can run on any i386 CPU so this is the only one that is always available.

To allow to switch between all 3 choices every process run is given access to a special shared object that gives access to the system call implementation chosen for the running system. This is the strange looking linux-gate.so.1 you already might have encountered as unresolved library when using ldd or the like.

(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;                                           
    }   

To utilize it all you have to do is load all your registers system call number in eax, parameters in ebx, ecx, edx, esi, edi as with int 0x80 system call implementation and call the main routine.

Unfortunately it is not all that easy, as to minimize the security risk of a fixed predefined address the location at which the vdso will be visible in a process is randomized, so you will have to figure out the correct location first.

This address individual to each process is passed to it, once it is started.

In case you didn't know, when started in Linux, every process gets pointers to the parameters passed once it was started and pointers to a description of the environment variables it is running under passed on its stack - each of them terminated by NULL.

Additionally to these a third block of so called elf-auxiliary-vectors gets passed following the ones mentioned before. The correct location is encoded in one of these carrying the type-identifier AT_SYSINFO.

So stack layout looks like this:

  • parameter-0
  • ...
  • parameter-m
  • NULL
  • environment-0
  • ....
  • environment-n
  • NULL
  • ...
  • auxilliary elf vector: AT_SYSINFO
  • ...
  • auxilliary elf vector: AT_NULL

Usage example

To find the correct address you will have to first skip all arguments and all environment pointers and then start scanning for AT_SYSINFO as shown in the example below:

#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;
}

As you will see by taking a look at the following snippet of /usr/include/asm/unistd_32.h on my system:

#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

The syscall I used is the one numbered 4 (write) as passed in the eax register. Taking filedescriptor (ebx = 1), data-pointer (ecx = &c) and size (edx = 1) as its arguments, each passed in the corresponding register.

To put a long story short

Comparing a supposedly slow running int 0x80 system call on any Intel CPU with a (hopefully) much faster implementation using the (genuinely invented by AMD) syscall instruction is comparing apples to oranges.

IMHO: Most probably the sysenter instruction instead of int 0x80 should be to the test here.

这篇关于英特尔的x86 VS x64的系统调用的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆