我一直在学习汇编,并且我读到四个主要的x86通用寄存器(eax,ebx,ecx和edx)都有一个预期或建议的目的。例如,eax 是累加器寄存器,ecx 用作循环的计数器,依此类推。大多数编译器是否尝试将寄存器用于建议的目的,或者他们是否忽略了寄存器“应该”用于什么,而只是将值分配给下一个可用的寄存器?
此外,在查看x64寄存器时,我注意到额外添加了8个通用寄存器,如果忽略rbp、rsp、rsi和rdi(因为它们具有非通用用途),则gp寄存器的总数将达到12个,如果包括它们,则为16个。在普通用户程序(即浏览器、文字处理器等,而不是需要大量寄存器的密码程序)中,在任何给定时间,通常使用多少寄存器?对于像Firefox这样的程序来说,一次使用所有12/16个普通寄存器是很常见的,还是因为没有足够的变量来填充它们,所以它们只使用一个子集?我将通过分解二进制文件来了解这一点,以了解一般情况,但我希望有比我更有知识的人提供答案。
此外,如果半通用寄存器(rsi、rdi、rsp和rbp)目前没有用于非通用应用,编译器通常会将它们用于通用用途吗?我很好奇,因为我看到这些寄存器被列为“通用”,但即使是我也能想到这些寄存器不能用于一般存储的实例(例如,您不会希望将变量存储到rbp和rsp,然后将值压入堆栈!).那么编译器会尽可能地利用这些寄存器吗?x86和x64编译之间有区别吗,因为x64处理器有更多可用的寄存器,所以没有必要将变量填充到任何可用的寄存器中?
最初(如在16位8086中),寄存器的功能比后来的x86处理器更加有限。只有BX、BP、SI和DI可用于寻址存储器,更常见的是使用CISC式的指令,用一条指令完成许多操作。
例如,LOOP 指令递减 CX,将其与零进行比较,如果 CX 仍为正,则跳转。如果你看一下为当前系统生成的代码,你不太可能看到这一点,但DEC和JNE。后者需要更多的代码空间,但允许您使用任何寄存器。
80386 和 32 位模式解除了寻址方面的大部分限制,允许将所有寄存器用作指针。此外,更复杂的指令已经过时了,我认为这与处理器本身的无序执行和其他优化技术的增加有关。
因此,在大多数情况下,几乎没有理由区别对待寄存器。当然,ESP/RSP 仍然是堆栈指针。
所有GP寄存器都是通用的。
只有当执行特定的(通常是遗留的)指令时,它们才具有特殊意义。
例如,四元组<code>rsi,<code>rdi,<code>rbp,<code>rsp,只有后者有一个特殊的用途,这是由于<code>call</code>、<code<ret</code>、<code>push</code>等指令造成的。<br>如果不使用它们,即使是隐式使用(不太可能出现的情况),也可以将其用作累加器。
这个原则是通用的,编译器会利用它。
考虑这个人为的例子[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寄存器都用上了(我没数过),包括< code>rbp、< code>rsi和< code>rdi。< br >寄存器的使用都不局限于它们的规范形式。
注意:在此示例中,rsi
和 rdi
用于加载和读取(两者都针对每个寄存器)数组,这是巧合。
这些寄存器用于传递前两个整数/指针参数。
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
在x86-64中,如果某些通用寄存器比其他寄存器更受欢迎,某些指令会执行得更快吗? 例如,会比执行得更快吗?我可以想象后者需要一个REX前缀,这会使指令获取速度变慢? 使用代替怎么样?或呢?其他操作?更小的寄存器,如vs?vs? AMD vs Intel?更新的处理器?较旧的处理器?指令的组合? 澄清:某些通用登记册是否应该优先于其他登记册,它们是哪些?
2.1 通用 CPU 寄存器 CPU 的寄存器能够对少量的数据进行快速的存取访问。在 x86 指令集里,一个 CPU 有 八个通用寄存器:EAX, EDX, ECX, ESI, EDI, EBP, ESP 和 EBX。还有很多别的寄存器,遇 到的时候具体讲解。这八个通用寄存器各有不同的用途,了解它们的作用对于我们设计调试 器是至关重要的。让我们先简略的看一看每个寄存器和功能。最后我们将通过一个简单
问题内容: 普通CPU(例如Android设备)是基于寄存器的计算机。Java虚拟机是基于堆栈的计算机。但是基于堆栈的计算机是否依赖于基于寄存器的计算机工作?由于基于堆栈的计算机不是OS,因此不能单独运行吗?除了JVM,是否有任何基于堆栈的计算机示例?有人说1个操作数,2个操作数;你为什么需要这个? 问题答案: JVM在任何地方都没有提到寄存器的存在。从它的角度来看,内存仅存在于少数几个地方,例如
在Google的Python类中 Python是一种动态的解释(字节码编译)语言 我知道什么是解释器,也知道什么是字节码,但两者加在一起似乎不合适。在阅读了一些之后,我变得更清楚了,基本上Python源代码在被解释之前是自动编译的;但是出现了一些新的问题。 使用Python解释器时,不会发生编译吗?如果有,什么时候?例如,如果您只是在命令行中键入代码,并且每次按enter键时它都会运行,那么编译器
考虑以下x86程序集: 序列结束时,rax的值与输入时的值相同,但从CPU的角度来看,其值取决于从内存加载到rcx的值。特别是,在该加载和两个异或指令完成之前,不会开始后续使用rax。 有什么方法可以比两个异或序列更有效地实现这种效果,例如,使用单个单uop单周期延迟指令?如果某个常量值需要在序列之前设置一次(例如,有一个零寄存器),则可以。
我想你们大多数人都知道在Java语言中是一个保留的关键字,但实际上并没有被使用。你们可能也知道是一个Java虚拟机(JVM)操作码。我认为Java、Scala和静态编程语言的所有复杂的控制流结构都是在JVM级别上使用和、、等的某种组合来实现的。 查看JVM规范https://docs.oracle.com/javase/specs/jvms/se7/html/jvms-6.html#jvms-6.