4

The code below is generated by gcc from a simple scanf program. My question is that

  1. Why these 3 addresses of variables are not consecutive when allocated?
  2. If not, when could I speculate the number of variables generated from stack by watching the clause like add esp, N which is often at the end of a routine? Is it related with calling convention?
  3. In this example, why compiler does not generated the add esp, 20h with it?

C code

#include <stdio.h>
int main() {
  int x;
  printf ("Enter X:\n");
  scanf ("%d", &x);
  printf ("You entered %d...\n", x);
  return 0;
};

asm

main            proc near
var_20          = dword ptr -20h
var_1C          = dword ptr -1Ch
var_4           = dword ptr -4
                push    ebp
                mov     ebp, esp
                and     esp, 0FFFFFFF0h
                sub     esp, 20h
                mov     [esp+20h+var_20], offset aEnterX ; "Enter X:"
                call    _puts
                mov     eax, offset aD  ; "%d"
                lea     edx, [esp+20h+var_4]
                mov     [esp+20h+var_1C], edx
                mov     [esp+20h+var_20], eax
                call    ___isoc99_scanf
                mov     edx, [esp+20h+var_4]
                mov     eax, off set aYouEnteredD___ ; "You entered %d...\n"
                mov     [esp+20h+var_1C], edx
                mov     [esp+20h+var_20], eax
                call    _printf
                mov     eax, 0
                leave
                retn
main            endp

4 Answers 4

5

There is only actually one local variable in your function: x. This variable is located on the stack where you would expect it, at ebp-4. IDA is getting confused because this particular function, instead of pushing variables onto the stack before calling a function, is moving them instead. This tricks IDA into thinking those are local variables when they're actually just the locations at the top of the stack.

mov     [esp+20h+var_1C], edx  <===> push edx
mov     [esp+20h+var_20], eax  <===> push eax

I can't definitively explain why gcc is doing this, but my guess is that you compiled without optimizations. This instruction layout might make debugging easier.

I think you're also confusing calling conventions with local variable cleanup. Every function needs to clean up its own local variable area. Your main() function is doing that with the leave instruction. Calling conventions are related to cleaning up the parameters passed to a function.

3

there is no add esp ,20 because of the leave instruction (LEAVE PROCDURE ) instruction

quoted ftom intel instruction manual

6.5.2 LEAVE Instruction
The LEAVE instruction, which does not have any operands, reverses the action of the previous ENTER instruction. 
The LEAVE instruction copies the contents of the EBP register into the ESP register to release all stack space allocated to the procedure. Then it restores the old value ofthe EBP register from the stack. This simultaneously 
restores the ESP register to its original value. A subsequent RET instruction then can remove any arguments and 
the return address pushed on the stack by the calling program for use by the procedure.

as to the other part in your question i guess it is because the compiler didn't generate any push argument instruction it utilises the top part for moving the args into stack and bottom part for varargs storage

mov [esp],%d
mov [esp+4] , ADDR where to store the input ie ADDR of [esp+1c]
3

There's actually only one stack variable in this function: var_4

In the disassembly above, IDA incorrectly detects arguments passed to _puts(), ___isoc99_scanf(), and _printf() as local stack variables. To see this, let's analyze the following snippet:

mov     [esp+20h+var_20], offset aEnterX ; "Enter X:"
call    _puts

var_20 is defined by IDA at the beginning of this function as -20h, so mov [esp+20h+var_20], offset aEnterX is effectively saying mov [esp+20h+-20h], offset aEnterX, which is the same as mov [esp], offset aEnterX. In other words, the code is just pushing offset aEnterX onto the stack before calling _puts(), and IDA is unfortunately detecting that "alternative push" as a local stack variable.

2

(I deal more with code generated by Visual C++, so some parts of this answer are just educated guesses.)

1.Why these 3 addresses of variables are not consecutive when allocated?

Variable alignment and even presence on the stack is up to the compiler and can differ based on the specific compiler version you use, the optimization options being used, and other factors.

The 3 variables in your example are not all "real" variables. var_4 is the variable corresponding to your x, whereas var_1C and var_20 are simply results of GCC's approach to passing arguments to deeper function calls. When you write scanf("%d", &x);, GCC knows that it will need to pass two 4-byte variables to that function on the stack, so it pre-emptively reserves enough room for them upon entering the function. This way, it doesn't need to push anything onto the stack (which might be problematic if there's no stack space left...), it just needs to mov the arguments into that preallocated space.

That doesn't explain why there's a gap between the two allocations, though. GCC also prefers to align stack allocations to 16 bytes1, and this is where I am guessing it seems that the sizes needed for "real local variables" and "space reserved for deeper function arguments" are aligned independently before being summed into the final value of "reserved stack space".

1You can control this alignment by using -mpreferred-stack-boundary=num.

2.If not, when could I speculate the number of variables generated from stack by watching the clause like add esp, N which is often at the end of a routine? Is it related with calling convention?

As you can see in this example, that instruction does not always get generated. Its counterpart, sub esp, N, is a much better indicator. From that you can make educated guesses about the amount/sizes of local variables.

Calling convention is not related to a function's local variables, it controls the way the arguments are passed into the function and whose responsibility it is to clean them up afterwards.

3.In this example, why compiler does not generated the add esp, 20h with it?

The function in your example begins with push ebp; mov ebp, esp, which saves the original values of both ebp and esp. The leave instruction at the end does the reverse - it restores the saved values of esp and ebp, so there's no need to calculate anything.

The saved ebp is also known as a frame pointer. It is possible to instruct the compiler not to generate it, in which case the original value of esp needs to be restored using that calculation you mentioned.

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.