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
EDIT: Sorry, my previous answer was wrong for your first question. My x86 is rusty. The best way to check is to debug your program. Here's a similar one (NASM syntax):
You can compile this on Linux as follows:
Now let's use GDB to debug it:
Disassemble
_start
function to see its addressesPut a breakpoint at the start
And run it!
Check ESP starting value
And put a breakpoint on
test
function.Let's continue
Ok, stopped at start of
test
, beforepush
, let's check ESP value and its contentsIt has been decremented by 4 and the call return value pushed there. Let's put another breakpoint after
push
and see what happens.It has decremented ESP by 4 and pushed 1. So now stack looks like this
So resuming: what you were missing is that
CALL
also behaves likePUSH
: it decrementsESP
by 4 before pushing the value into the stack.And for the rest of the questions:
Assemblers usually default to 32 bits and not 16 bits, that's why it is decrementing 4 bytes and not 2. You can force your assembler to use 16-bit instructions instead.
In fact that is the common case, it is called the function/routine entry protocol.
The step by step run I've made should have clarified how to check register and memory/stack values. If you have any further questions about this, let me know.
The
call <address>
is like:push eip
jmp <address>
, so in your case ifesp
is0xffffd840
is ahead ofcall
, the returning address of next instruction is pushed to0xffffd83c
(Because the pseudo "push eip
" will first decrement theesp
to create new top of stack, then it will store the current value ofeip
there (BTW, theeip
already points at next instruction, as the fetch+decode instruction phase ofcall
was finished, so it is actually the value which will be needed forret
).You can also in debugger view memory. And "stack" is just ordinary memory. So if you have
esp
equal to0xffffd840
, you can open for example memory view at0xffffd824
, 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 dopush ax
or usesub/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 +-4esp
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).