关于LEA指令的问题

逆向工程 艾达 拆卸 x86 C
2021-06-10 09:41:20

我正在学习逆向工程。所以我正在编写一些程序并尝试理解它们的程序集。我偶然发现了一个奇怪的案例,我认为我无法单独解决它。

这是c代码:

 #include <stdio.h>

int main(){

char *texto = "O numero e %d\n";
int i = 10;

while(i){
    printf(texto, i--);
}

return 0;
}

IDA 生产的程序集如下:

mov     eax, [esp+28]
lea     edx, [eax-1] ; The part i don't understand
mov     [esp+28], edx
mov     [esp+4], eax
mov     eax, [esp+18h]
mov     [esp], eax      ; char *
call    _printf

我能理解的是它将旧值存储在 eax 中并推送到堆栈(我故意没有打开优化)然后推送格式。虽然这发生在中间i--,但我无法理解它是如何工作的。所以它获取eax-1和存储的地址,edx然后将它存储在i,但eax不保存地址而是一个值。

提前致谢。

2个回答

您所看到的是编译器喜欢使用的效率技巧。

在内部,CPU 不会区分数字和地址——32 位整数和指针是一回事。(或 64 位,如果您使用较新的架构,但由于您的寄存器名称以 开头e,则您使用的是 32 位)。

lea指令加载其操作数的地址,而不是操作数本身。在 C 语言中,您可以将 [eax-1] 视为 *(eax-1),并为其lea添加一个&运算符,lea edx, [eax-1]就像edx = &(*(eax-1)). eax-1当然是一样的。

编译器可以使用指令序列mov edx, eax; sub edx, 1mov edx, eax; dec edx. 那么,它为什么使用lea指令呢?

答案是,从历史上看,解析地址lea是使用专用地址总线硬件完成的,绕过了 ALU。此外,当同一个寄存器在后续操作中被使用两次时,流水线有其问题。这意味着,在较旧的处理器上,使用lea比替代方案快几个周期,并且在编译器中实现并不难,所以这就是编译器传统上所做的。

在新处理器上,“lea使用单独的硬件”的区别不再(必然)出现,并且流水线比以前更智能,所以我怀疑现在这有什么不同。但它仍然在编译器中,不会从它们中删除,因为没有充分的理由。

根据英特尔的说法,LEA 指令是:

该指令计算第二个操作数(源操作数)的有效地址并将其存储在第一个操作数(目标操作数)中。源操作数是由处理器寻址模式之一指定的内存地址(偏移量部分);目标操作数是一个通用寄存器。

因此,lea edx, [eax-1]计算 的地址[eax-1],即 eax-1,因为 [] 意味着操作数应作为地址处理。之后,地址将存储在edx.