Jam*_*ton 4 compiler-construction x86 assembly compilation x86-64
我一直在学习汇编,并且我读到四个主要的 x86 通用寄存器(eax、ebx、ecx 和 edx)每个都有一个预期或建议的用途。例如,eax 是累加器寄存器,ecx 用作循环的计数器,等等。大多数编译器是否尝试将寄存器用于建议的目的,或者他们是否忽略了寄存器“应该”的用途而只是将值分配给下一个可用的寄存器?
此外,在查看 x64 寄存器时,我注意到添加了额外的 8 个通用寄存器,如果忽略 rbp、rsp、rsi 和 rdi(因为它们具有非通用用途),gp 寄存器的总数将达到 12 ),如果包括它们,则为十六个。在普通用户程序(即浏览器、文字处理器等,而不是需要大量寄存器的密码程序)中,在任何给定时间通常使用多少这些寄存器?像 Firefox 这样的程序一次使用所有 12/16 个普通寄存器是常见的,还是因为它们没有足够的变量来填充它们而只使用一个子集?我将通过反汇编二进制文件来自己研究这个问题,看看一般情况是什么,但我希望能得到比我更有见识的人的回答。
此外,如果当前未用于非通用应用程序,编译器是否通常将 semi-gp 寄存器(rsi、rdi、rsp 和 rbp)用于通用用途?我很好奇,因为我看到这些寄存器被列为“通用”,但即使我能想到这些寄存器不能用于通用存储的实例(例如,你不想存储rbp 和 rsp 变量,然后将值推入堆栈!)。那么编译器会尽量使用这些寄存器吗?x86 和 x64 编译之间是否有区别,因为 x64 处理器有更多可用寄存器,因此没有必要将变量填充到任何可用寄存器中?
所有 GP 寄存器都是通用的。
只有在执行特定的(通常是遗留的)指令时,它们才具有特殊含义。
例如,四联rsi,rdi,rbp,rsp只有后者有一个特殊的目的,这是由于指令等call,ret,push等。
如果您不使用它们,即使是隐式使用(不可否认,这是一种不太可能的情况),您可以将其用作累加器。
这个原则是通用的,编译器会利用它。
考虑这个人为的例子[ 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;
}
}
Run Code Online (Sandbox Code Playgroud)
它由 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
Run Code Online (Sandbox Code Playgroud)
可以看到大部分的GP寄存器都用到了(我没统计过),包括rbp,rsi和rdi。
寄存器的使用不限于它们的规范形式。
注意在这个例子中rsi,rdi用于加载和读取(对于每个寄存器)一个数组,这是一个巧合。
这些寄存器用于传递前两个整数/指针参数。
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
Run Code Online (Sandbox Code Playgroud)