-1

I'd like to learn some assembly and now I have a question where ESP shifts the intialized integer to during the function call of the main function.

The C-Code:

#include<stdio.h>


int main() {
    int hallo = 5;
}

When I compile this file with GCC and decompile it with the command objdump -M intel -D a.exe | grep -A20 main.

Then it looks like this:

00401460 <_main>:
  401460:       55                      push   ebp
  401461:       89 e5                   mov    ebp,esp
  401463:       83 e4 f0                and    esp,0xfffffff0
  401466:       83 ec 10                sub    esp,0x10
  401469:       e8 42 05 00 00          call   4019b0 <___main>
  40146e:       c7 44 24 0c 05 00 00    mov    DWORD PTR [esp+0xc],0x5
  401475:       00
  401476:       b8 00 00 00 00          mov    eax,0x0
  40147b:       c9                      leave
  40147c:       c3                      ret
  40147d:       90                      nop
  40147e:       90                      nop
  40147f:       90                      nop

00401480 <__setargv>:
  401480:       55                      push   ebp
  401481:       89 e5                   mov    ebp,esp
  401483:       57                      push   edi
  401484:       56                      push   esi
  401485:       53                      push   ebx
--
004019b0 <___main>:
  4019b0:       a1 28 70 40 00          mov    eax,ds:0x407028
  4019b5:       85 c0                   test   eax,eax
  4019b7:       74 07                   je     4019c0 <___main+0x10>
  4019b9:       f3 c3                   repz ret
  4019bb:       90                      nop
  4019bc:       8d 74 26 00             lea    esi,[esi+eiz*1+0x0]
  4019c0:       c7 05 28 70 40 00 01    mov    DWORD PTR ds:0x407028,0x1
  4019c7:       00 00 00
  4019ca:       eb 94                   jmp    401960 <___do_global_ctors>
  4019cc:       90                      nop
  4019cd:       90                      nop
  4019ce:       90                      nop
  4019cf:       90                      nop

004019d0 <.text>:
  4019d0:       83 ec 1c                sub    esp,0x1c
  4019d3:       8b 44 24 24             mov    eax,DWORD PTR [esp+0x24]
  4019d7:       83 f8 03                cmp    eax,0x3
  4019da:       74 14                   je     4019f0 <.text+0x20>
  4019dc:       85 c0                   test   eax,eax

I expect that the last assembly-command is mov DWORD PTR [esp+0xF],0x5, because the Stack is growing from top to bottom and because of Little Endian, the ESP must be positioned on [esp+0xF] to fill up the next 4 Bytes (integer) to Position [esp+0xc].

1201ProgramAlarm
  • 32,384
  • 7
  • 42
  • 56
  • PS: I am using a 64 bit CPU – Lars Lafleur Jul 01 '19 at 07:03
  • The local variable might've been optimized away. – Neijwiert Jul 01 '19 at 07:04
  • Are you asking for a detailed walkthrough of gcc's code generator? What kind of answer are you looking for? – melpomene Jul 01 '19 at 07:06
  • I don't need a detailed walkthrough of gcc's code generator. If the reason is a speficiation of gcc that it's the way it is but than my question would be why it breaks with little Endian... – Lars Lafleur Jul 01 '19 at 07:10
  • The code has nothing to do with endianness and I'm confused why you think it does. – melpomene Jul 01 '19 at 07:11
  • The address that `mov` takes is the first byte of the four bytes to set . moving four bytes to address 100C writes the bytes 100C,100D,100E,100F. this is the same regardless of endianness or stack direction – M.M Jul 01 '19 at 07:14
  • Because I thought little Endian would fill up the 16-Byte-Block in reversed order. So the 0x05 would be storted in the lowest part of the block: https://stackoverflow.com/questions/18470053/relation-between-endianness-and-stack-growth-direction/45424788. So actually i would expect the 5 stored at [esp + 0]. – Lars Lafleur Jul 01 '19 at 07:14
  • What 16 byte block? we're talking about 4 bytes here. And the answer you link to is somewhere between rubbish and irrelevant. No system stack I've ever heard of would push or pop a 4-byte integer in 4 char-size steps. The code you link doesn't push or pop the `0x5` at all, it writes 4 bytes at once to a location in the stack. `mov` means to write bytes, not push. – M.M Jul 01 '19 at 07:17
  • I thought the command "sub esp, 0x10" reserves 16 Byte? – Lars Lafleur Jul 01 '19 at 07:19
  • `exp` is not a register name. This is obviously not real `objdump` output. Please copy/paste instead of typing, especially things you don't understand and are asking about. Anywhere, what's the label on that block that calls `___main`? Note that it's not `_main` or `main`, it's some internal CRT startup function called `___main` with three underscores. Unless that's a typo, too. Although that doesn't actually matter for your question. Unless that *is* the asm for `main` itself, with MinGW inserting a call to a startup function into `main`. – Peter Cordes Jul 01 '19 at 07:20
  • 1
    It does, but that's nothing to do with endianness . "little endian" means that bytes C,D,E,F are 5,0,0,0 respectively. "big endian" would mean they were 0,0,0,5. – M.M Jul 01 '19 at 07:20
  • @PeterCordes could you comment/answer on the [other question](https://stackoverflow.com/questions/18470053/relation-between-endianness-and-stack-growth-direction/45424788) OP linked? I'm no expert on the topic but the top answer seems like hogwash – M.M Jul 01 '19 at 07:21
  • @M.M: lol, yes that answer is total nonsense; I left a comment. Will post a quick answer. – Peter Cordes Jul 01 '19 at 07:30
  • OK, I edited my Question. Now the full Assembly-Code is posted...sorry. – Lars Lafleur Jul 01 '19 at 07:32
  • 3
    "esp should be positioned on esp+0xF" makes no sense... esp is esp, not esp+anything. `mov DWORD PTR [esp+0xc],0x5` mean: go to where ESP points, go forward by `0xC`, then write four bytes to that location and the next 3 bytes (in increasing order). The endianness tells you whether the bytes are 5,0,0,0 or 0,0,0,5 ; and the stack direction is irrelevant – M.M Jul 01 '19 at 07:38
  • Yeah, its true that "esp should be positioned on esp+0xF" makes no sense....But is the integer 5 (= 4 byte) now stored between [esp+0xC] and [esp+0xF] or between [esp+0xC] and [esp+0x9]? I Would say it ts stored between [esp+0xC] and [esp+0x9]...Thank you so far. – Lars Lafleur Jul 01 '19 at 07:48
  • it's stored between esp+0xC and esp+0xF . That's how addresses work, they increase in order. – M.M Jul 01 '19 at 07:49
  • Ah ok. So the stack grows to lower addresses but when i write a chunk of 4 Bytes in an addres that it is always written in increasing order? – Lars Lafleur Jul 01 '19 at 07:53
  • @M.M: I posted an answer on [Relation between endianness and stack-growth direction](//stackoverflow.com/a/56832003) – Peter Cordes Jul 01 '19 at 08:03
  • 1
    BTW: the assembly code you show here is 32 bit code, even if your CPU is a 64 bit. – Jabberwocky Jul 01 '19 at 08:40

1 Answers1

4

No, a dword store to [esp+0xF] would write the 4 bytes at [esp + 0x0f .. 0x12], which isn't even dword-aligned.

If you were confused by Relation between endianness and stack-growth direction and the old answers there, that's understandable; they were completely wrong so I posted a correct one.

The address of a dword is always the lowest address of any of its component bytes. (This applies to big- and little-endian systems).

Reserving 16 bytes and storing to [esp+0xc] stores to the highest-address 4 bytes of those 16.

The dword at [esp+0xc] is (in order from LSB to MSB) the bytes at addresses ESP +0xc, +0xd, +0xe, and +0xf.

For a (hypothetical) big-endian x86 it would the same bytes, but that order would be MSB to LSB. The address of the dword would still be [esp+0xc].

None of this has any connection to push doing esp-=4 instead of esp+=4. Systems with upwards-growing stacks still use the lowest byte address within a word / dword as the address of that multi-byte integer. Just like in C, the address of an array or struct is the address of the first element. In fact that's why C addresses work that way.


Since the stack grows downward, it makes some sense that gcc would choose to put a local there, right below the saved EBP value, and leave the rest of the space unused as padding for stack alignment before a call (to the CRT helper function ___main).

Even though the 32-bit Windows ABI doesn't require 16-byte stack alignment, gcc chooses to do it anyway (default of -mpreferred-stack-boundary=4 : 2^4 = 16)


And BTW, obviously all this noise goes away if you compile with optimization enabled. Then main can just ret. Or maybe still has to call ___main, but can optimize away the local.

You could reduce the noise but still make GCC init a local by making it volatile and compiling with -O3. Or pass its address to another non-inline function.


Why does ESP point to [esp+0xc]?

Huh? That doesn't even make sense. ESP points at [esp]. You're actually asking why GCC chose to use the addressing mode [esp+0xc] rather than some other displacement.

1201ProgramAlarm
  • 32,384
  • 7
  • 42
  • 56
Peter Cordes
  • 328,167
  • 45
  • 605
  • 847
  • But aren't the two Statements "No, a dword store to [esp+0xF] would write the 4 bytes at [esp + 0x0f .. 0x12], which isn't even dword-aligned." and "Reserving 16 bytes and storing to [esp+0xc] stores to the highest-address 4 bytes of those 16. Since the stack grows downward, it makes some sense that gcc would choose to put a local there, right below the saved EBP value..." contradictory? If [esp+0xF] would write the 4 bytes at [esp + 0x0f .. 0x12], then [esp+0xc] would write the 4 bytes at [esp + 0xc .. 0x9]??? – Lars Lafleur Jul 01 '19 at 08:37
  • 1
    @LarsLafleur you seem confused about what "the stack grows downwards" means. The address after esp+0xC is esp+0xD , that is a fact of how memory addressing is defined. The stack direction is NOTHING WHATSOEVER to do with that. – M.M Jul 01 '19 at 08:49
  • I would completly agree to you. So esp+0xC goes upwards, no matter what stack direction is. But then " [esp+0xF] would write the 4 bytes at [esp + 0x0f .. 0x12]" couldn't be true, because [esp+0xF] would write the 4 bytes at [esp + 0xF .. 0x12], wouldn't it? – Lars Lafleur Jul 01 '19 at 09:08
  • 1
    But I think i have understood why little Edian is irrelevant, because we write the 4 bytes from [esp+0xc] to [esp+0xF] (because thats memory addressing is defined) and within this 4 bytes, the 5 is stored as little Endian...right??? – Lars Lafleur Jul 01 '19 at 09:15
  • @LarsLafleur Huh? You wrote "but then 'THING' couldn't be true because SAME THING", , not sure what your question is. – M.M Jul 01 '19 at 09:17
  • Sorry, that was an misunderstanding. I didn't read the answer correctly. – Lars Lafleur Jul 01 '19 at 09:19
  • @LarsLafleur yes, little endian refers to the significance of bytes used to store an integer in that space – M.M Jul 01 '19 at 10:09