Search code examples
assemblyx86stackpushcall

In x86 assembly, is ESP decremented twice after a call and then push, before data is saved on the stack?


Long story short, I'm studying a book titled "The 8088 and 8086 Microprocessors" by Singh and Triebel, to learn old assembly for those specific CPUs. Now, the computer I'm practicing on is my main computer, which I recently built, so the registers are bigger.

That said, the book (which I find extremely helpful) says that the call label operand causes the address of the instruction following the call to be placed on the stack, and THEN SP is decremented by 2 (ESP, and decremented by 4 on my CPU). In some code I'm studying, a call operand is immediately followed by a push. When the CPU encounters a push, the book states that SP is decremented by two (again, ESP is decremented by 4 on my CPU).

; ESP=0xffffd840 right now
call iprint
mov eax, 0Ah

iprint:
push eax ; say eax contains 1

Now, say ESP=0xffffd840 before the call. The address of EIP is saved on the stack (the address of the instruction that follows the CALL operand). Then ESP is decremented by 4. At this point, ESP=0xffffd83c. Then the push operand is encountered. Going by what the book says, the stack pointer is decremented first, and then the contents of the register are pushed onto the stack. So now ESP=0xffffd838 and 1 is pushed onto the stack.

If it helps:
Stack addr  Contents
********** ********
0xffffd840  address of mov eax, 0Ah
0xffffd83c  ?
0xffffd838  1

Now, my question is, is 0xffffd83c skipped? According to the book, ESP is decremented after saving the next instruction after the call, and then before data is put on the stack from the push, it's decremented again.

I've been debugging a similar scenario for a while now, paying close attention to the values of the registers, but I just can't tell if the debugger adheres to what the book says (decrementing before doing an operation, or after).

Is this because in some cases a parameter is given after RET in a subroutine, causing the stack pointer to increment? If the stack pointer is indeed decremented twice before having data put on it, this is the only reason I can see.

Could someone please confirm or explain this if I have this wrong?

Thank you


Solution

  • The call <address> is like: push eip jmp <address>, so in your case if esp is 0xffffd840 is ahead of call, the returning address of next instruction is pushed to 0xffffd83c (Because the pseudo "push eip" will first decrement the esp to create new top of stack, then it will store the current value of eip there (BTW, the eip already points at next instruction, as the fetch+decode instruction phase of call was finished, so it is actually the value which will be needed for ret).

    You can also in debugger view memory. And "stack" is just ordinary memory. So if you have esp equal to 0xffffd840, you can open for example memory view at 0xffffd824, and you will see 32 bytes of stack memory, with the 28 bytes not-used-yet and last 4 bytes being current "top of the stack".

    I'm using group of 4 bytes everywhere, as that's the native size of CPU "word" (dword in x86 terminology, word is 16 bit only) in 32b protected mode. IIRC you can still enforce the CPU to do push ax or use sub/add esp,immediate to even move it by single byte, but usually it involves performance penalties, and in 64b modes several calling conventions even require 16 byte alignment, so I would recommend to stick with +-4 esp operations in 32b mode.

    But if your book is about 8086, you may want to use dosbox to emulate the old DOS 16 bit environment, to save you some platform specific problems in the beginning. Although maybe you should instead find some 32/64 bit recent book for your OS, as the 32b protected mode on x86 is much easier to learn (only the graphics output is not as straightforward as it was back in DOS era, but if you will mix your asm files with C++ "loader", which would for example initialize some window surface as ARGB memory array, you can pass that pointer down to the asm routines and toy around with pixels, in the same simple way, how the old 320x200 "mode 13h" in DOS worked. Even easier (no palette and no 64k segment limits).