1
votes

Right now I am trying to learn the assembly language on x86 systems. Therefore I am readying the book "Programming from the Ground Up". (Available for free at http://download.savannah.gnu.org/releases/pgubook/)

On page 53, the way the computer's stack works is explained:

The computer’s stack lives at the very top addresses of memory. You can push values onto the top of the stack through an instruction called pushl. [...] Well, we say it’s the top, but the "top" of the stack is actually the bottom of the stack’s memory. [...] In memory the stack starts at the top of memory and grows downward due to architectural considerations. Therefore, when we refer to the "top of the stack" remember it’s at the bottom of the stack’s memory.

That part I get. Let's say the stack's memory starts at address 0 and ends at address 11 (inclusively). That means there are currently three words (4 bytes a piece) on the stack. According to my understanding, the word that is on "top" of the stack currently occupies the addresses 8, 9, 10, and 11. (Since one word has 4 bytes and therefore occupies four storage locations in main memory). However, the book now says the following:

The stack register, %esp, always contains a pointer to the current top of the stack.

Okay, in my example the %esp register would hold the address 8. It points to the word that is currently on top of the stack. But...

Every time we push something onto the stack with pushl, %esp gets subtracted by 4 so that it points to the new top of the stack (remember, each word is four bytes long, and the stack grows downward).

What? Isn't it exactly the other way around? If I push another 4-byte-sized machine word onto the stack, this word will occupy the main memory addresses 12 to 15. Like they said: The stack grows downward. Now the %esp register points to the word that is currently on top of the stack. It starts at address 12. Before we pushed another word onto the stack, the address that was stored in %esp was 8. So %esp has clearly been added 4, not subtracted. Where do they get the subtraction from? What did I miss? I am very confused...

Help is very appreciated ;)

3
"Grow downwards" simply means subtraction. 12 - 4 = 8, ESP gets a lower value then you push something on the stack. Just stand on your head and everything looks normal.Hans Passant

3 Answers

3
votes

If I push another 4-byte-sized maschine word onto the stack, this word will occupy the main memory addresses 12 to 15. Like they said: The stack grows downward.

Downward means toward lower addresses, so pushing another value on the stack means subtracting 4 and writing the value to the new location. So %esp becomes 4.

  +--------+
8 |12345678| <- top of stack before push
  +--------+
4 |11223344| <- top of stack
  +--------+
0 |00000000|
  +--------+
0
votes

Usually the "top of addresses of memory" refers to the highest addresses. For example, your stack may start at 0x00105000; if you add a word, you move esp to 0x00104ffc (i.e. you grow downward in memory addresses). See e.g. here for a nice diagram.

0
votes

If you enter your function and the stack is at 0x100. Typically the stack grows downward, toward zero, from high addresses to low addresses. so if you then push one 4 byte item the stack "top" is now at 0xFC. Push another, 0xF8, and so on.

The other thing that is getting in the way here is ebp vs esp.

Generally a processor has a stack pointer, either a special or sometimes general purpose register that has specific instructions tied to it, push, pop, and perhaps stack pointer relative addressing loads and stores. Also it is not uncommon for compilers to consume a general purpose (or special purpose) register, different from the stack pointer, for a stack frame. Why? to make reading and debugging the compiler generated code a little easier.

Upon entry of the function for example you might make a copy of the stack pointer into this other register, and for the duration of the function that register doesnt move, this allows you to do relative addressing to that register to find local variables as well as function parameters (if this compiler and processor tend to pass parameters on the stack). So throughout the function you can access any one of these local variables with the same offset. If you didnt use a stack frame then, if, for optimization reasons (conservation of the stack memory) the stack pointer were dynamic within the function, then the relative offsets from the stack pointer to local variables and stack based function parameters would also be dynamic and harder to read and debug. (while saving stack space and returning a register for other uses, both optimizations).

In any case, functions that call functions need to have the stack pointer at the "top of stack" when the nested function is called so that that function can enjoy the stack as any other function and assume that it can use/destroy anything beyond the end of stack for its own purposes.