6

i'm learning to reverse engineer. So i'm coding some programs and try to understand their assembly. I stumbled upon a curious case and i think i can't solve it alone.

Here's the c code:

 #include <stdio.h>

int main(){

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

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

return 0;
}

The assembly produced by IDA is the following:

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

What i could understand is that it stores the old value in eax and pushes to stack(I purposedly didn't turn on optimizations) and then it pushes the format. While that happens in the middle it does the i--, but i can't understand how it's working. So it get's the address of eax-1 and stores in edx and then stores it in i, but eaxdoesn't hold an address but a value.

Thanks in advance.

1
  • lea edx, [blah-1] is essentially mov edx, blah-1 (the latter is obviously not a valid command) Commented Mar 30, 2016 at 3:47

2 Answers 2

11

What you're seeing is an efficiency trick that compilers like to use.

Internally, the CPU doesn't make a difference between numbers and addresses - 32 bit integers and pointers are the same thing. (Or 64 bit, if you're using newer architecture, but as your register names start with e, you're using 32 bit).

The lea instruction loads the address of its operand, instead of the operand itself. In C terms, you could look at [eax-1] as *(eax-1), and lea adds a & operator to that, so lea edx, [eax-1] is like edx = &(*(eax-1)). Which is the same as eax-1 of course.

The compiler could have done exactly the same using the instruction sequence mov edx, eax; sub edx, 1 or mov edx, eax; dec edx. So, why did it use the lea instruction?

The answer is that, historically, resolving addresses in lea was done using dedicated address bus hardware and bypassed the ALU. Also, pipelining had its issues when the same register was used twice in subsequent operations. Which means, on older processors, using lea was a few cycles faster than the alternatives, and it's not hard to implement in the compiler, so this is what compilers traditionally did.

On new processors, the distinction "lea uses separate hardware" isn't (neccesarily) made any more, and pipelining is a lot more intelligent than it used to be, so i doubt it's make any difference these days. But it's still in the compilers, and won't get removed from them because there's just no good reason to.

5
  • Understanding the &(*(eax-1)) eas the problem. Thanks for the complete and in-depth answer! Commented Mar 29, 2016 at 14:12
  • 1
    Even today it reduces the code size. Is that not a win? Commented Mar 29, 2016 at 14:23
  • @JanDvorak: Wouldn't storing EAX to [ESP+4], and then using "dec eax" and storing EAX to [ESP+28] save even more code? I know modern CPUs are slower on "inc" and "dec" than "add", but I think those are still single-byte instructions are they not?
    – supercat
    Commented Mar 29, 2016 at 15:46
  • 3
    @Jan: Modern compilers tend inline aggressively at the slightest provocation, so a minuscule savings here and there will go unnoticed among the hyper-bloat. Also, CPUs have so many ALUs nowadays that hardly anyone will notice that lea doesn't use any. However, lea leaves the flags alone, it can reduce register pressure (always an issue in x86 mode!), and it can shorten dependency chains. What's not to like? ;-)
    – DarthGizka
    Commented Mar 29, 2016 at 15:47
  • Actually, it makes a lot of difference still. In this case the generation is silly because the constant is 1, but in most cases it is better than the mov / add pair or even move / add / add triplet that would otherwise be used.
    – Joshua
    Commented Mar 29, 2016 at 20:06
1

According to Intel the LEA instruction is:

This instruction computes the effective address of the second operand (the source operand) and stores it in the first operand (destination operand). The source operand is a memory address (offset part) specified with one of the processors addressing modes; the destination operand is a general-purpose register.

So, the lea edx, [eax-1] computes the address of [eax-1], which is eax-1, because [] means that the operand should be handled as an address. After it the address will be stored in edx.

1
  • Im always messing up thinking registers have addresses.. Now i understand it subtracts 1. Thanks! Commented Mar 29, 2016 at 12:06

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.