2017-04-06 25 views
4

我一直在学习汇编,我已经读过四个主要的x86通用寄存器(eax,ebx,ecx和edx),每个寄存器都有一个预期的或建议的目的。例如,eax是累加器寄存器,ecx用作循环的计数器,依此类推。大多数编译器是否尝试使用寄存器来达到建议的目的,或者他们是否忽略寄存器“应该”设置的值,并将值分配给下一个可用的寄存器?如果你忽略了rbp,rsp,rsi和rdi(因为它们没有),所以我们注意到增加了8个通用寄存器,使得gp寄存器的总数为12个一般目的使用),如果包括它们,则为十六。在普通用户程序(即浏览器,文字处理器等,而不需要大量寄存器的加密程序)中,在任何给定时间通常有多少个这样的寄存器正在使用?像Firefox这样的程序一次使用全部12/16个正常寄存器,还是只使用一个子集,因为它们没有足够的变量来填充它们?我会通过分解二进制文件来了解一般情况,但我会感谢来自比我更熟悉的人的回答。编译器通常使用寄存器来实现其“预期”目的吗?

另外,编译器通常使用半gp寄存器(rsi,rdi,rsp ,和rbp)用于通用目的,如果它们目前不用于其非一般应用程序?我很好奇,因为我看到这些寄存器被列为“通用目的”,但即使我可以想到这些寄存器不能用于一般存储的头顶部的实例(例如,您不想存储变量到rbp和rsp,然后将值压入堆栈!)。那么编译器会尽可能地利用这些寄存器吗? x86和x64编译有没有区别,因为x64处理器有更多的寄存器可用,所以没有必要将变量填入任何可用的寄存器中?

+0

所有GP寄存器都是通用的。只有当执行特定的,通常是遗留的指令时,它们才有特殊的含义。例如四元组'rsi','rdi','rbp','rsp'只有后者具有特殊用途,并且由于“call/ret/push/pop”等等。如果你不使用它们(甚至是隐含的),你可以用它作为累加器。这个原则是普遍的,编译器利用它。 –

+0

@MargaretBloom不是像movsb这样的指令用于数组/字符串复制之类的指令所使用的rsi/rdi寄存器吗?另外,变量在通话/回复/推送/弹出指令之间的时间间隔内是否仅为“活动”是常见的情况?看起来这些指令会很普遍,以至于在这些指令之间没有足够的“空间”来适应变量的整个生命周期。 –

+0

我用一些示例发布了一个答案,以说服编译器使用GP尽可能自由地注册:)像'rbp'这样的寄存器和'gtr'类似 - 现在后者确实是一个特定的目的寄存器 –

回答

4

所有GP寄存器都是通用的。
只有当执行特定的,通常是遗留的指令时,它们才有特殊的含义。

例如四联rsirdirbprsp只有后者有一个特殊的目的,这是由于像callretpush等指令。
如果你不使用它们,即使是隐含的(一种不可能的情况),你可以用它作为累加器。

这个原则是普遍的,编译器利用它。

考虑这个人为例子[1]

void maxArray(int* x, int* y, int*z, short* w) { 
    for (int i = 0; i < 65536; i++) 
    { 
     int a = y[i]*z[i]; 
     int b = z[i]*z[i]; 
     int c = y[i]*x[i]-w[i]; 
     int d = w[i]+x[i]-y[i]; 
     int e = y[i+1]*w[i+2]; 
     int f = w[i]*w[i]; 

     x[i] = a*a-b+d; 
     y[i] = b-c*d/f+e; 
     z[i] = (e+f)*2-4*a*d; 
     w[i] = a*b-c*d+e*f; 
    } 
} 

它是由GCC编译成此房源

maxArray(int*, int*, int*, short*): 
     push r13 
     push r12 
     xor  r8d, r8d 
     push rbp 
     push rbx 
     mov  r12, rdx 
.L2: 
     mov  edx, DWORD PTR [rsi+r8*2]  
     mov  ebp, DWORD PTR [r12+r8*2] 
     movsx r11d, WORD PTR [rcx+r8] 
     mov  eax, DWORD PTR [rdi+r8*2] 
     movsx ebx, WORD PTR [rcx+4+r8] 
     mov  r9d, edx 
     mov  r13d, edx 
     imul r9d, ebp 
     imul r13d, eax 
     lea  r10d, [rax+r11] 
     imul ebx, DWORD PTR [rsi+4+r8*2] 
     mov  eax, r9d 
     sub  r10d, edx 
     imul ebp, ebp 
     sub  r13d, r11d 
     imul eax, r9d 
     imul r11d, r11d 
     sub  eax, ebp 
     add  eax, r10d 
     mov  DWORD PTR [rdi+r8*2], eax 
     mov  eax, r13d 
     imul eax, r10d 
     cdq 
     idiv r11d 
     mov  edx, ebp 
     sub  edx, eax 
     mov  eax, edx 
     lea  edx, [0+r9*4] 
     add  eax, ebx 
     mov  DWORD PTR [rsi+r8*2], eax 
     lea  eax, [rbx+r11] 
     imul r9d, ebp 
     imul r11d, ebx 
     add  eax, eax 
     imul edx, r10d 
     add  r9d, r11d 
     imul r10d, r13d 
     sub  eax, edx 
     sub  r9d, r10d 
     mov  DWORD PTR [r12+r8*2], eax 
     mov  WORD PTR [rcx+r8], r9w 
     add  r8, 2 
     cmp  r8, 131072 
     jne  .L2 
     pop  rbx 
     pop  rbp 
     pop  r12 
     pop  r13 
     ret 

你可以看到,大多数GP的寄存器用于(我的天堂不包括它们),包括rbp,rsirdi
寄存器的使用都不限于其规范形式。

注意在这个例子中rsirdi用于加载和读取(都为每个寄存器)的阵列,这是巧合。
这些寄存器用于传递前两个整数/指针参数。

int sum(int a, int b, int c, int d) 
{ 
    return a+b+c+d; 
} 

sum(int, int, int, int): 
     lea  eax, [rdi+rsi] 
     add  eax, edx 
     add  eax, ecx 
     ret 
+0

好吧,这一切都是有道理的。但是,平均程序(尽管是一个复杂的程序,如浏览器)可能会使用那么多的寄存器吗?这个功能是相当人为的 - 就像人们期望在实际程序中看到的那样组装?目前我正在使用手机,所以现在我无法检查自己,但是我会尽可能通过反汇编来查看Firefox。另外,该程序似乎在寄存器的32位和64位注释之间切换(例如,它使用eax,然后是rax)。这是常见的吗? –

+2

@JamesPreston非常常见,大多数整数变量是32位,但指针将是64位。在x86上用尽寄存器是很常见的,在x64上没有那么多,所以典型的寄存器压力显然大于6但低于14 – harold

+0

谢谢@harold。 –

2

最初(与在16位8086中一样),寄存器的功能比以后的x86处理器更有限。只有BX,BP,SI和DI才可用于寻址内存,而使用CISC风格的指令更常见,这些指令使用一条指令完成许多操作。

例如,LOOP指令递减CX,将其与零进行比较,如果仍为正,则跳转。如果您查看为当前系统生成的代码,您不太可能会看到该代码,但是DEC和JNE。后者需要更多的代码空间,但可以使用任何寄存器。

80386和32位模式提升了寻址的大部分限制,允许所有寄存器用作指针。另外,更复杂的指令也不合时宜,我认为这与处理器本身的乱序执行和其他优化技术的增加有关。

因此,大部分情况下,对寄存器的处理方式有所不同。当然,ESP/RSP仍然是堆栈指针。

相关问题