I can't keep the stack in order. How do I do?

My problem is as follows. When the execution reaches the code below the stack contains 6 dwords from top. I want to copy the third and the fifth dword from top in the Gprmc process.

The "call REF_2" pushes one dword on the stack and the "pop eax" restore the stack order. Then the reference point is pushed on the stack. Now i have 7 dwords on the stack.

The "call dword ptr" pushes the return address on the stack. Now there are 8 dwords on the stack.

In the Gprmc process ebp is pushed on the stack i.e. 9 dwords on the stack.

Now I want to copy the original fifth dword (which now should be the eighth) and the third (which now should be the sixth).

When returning from the process the stack must be as it was on the line after REF_2: so the reference point can be poped to eax again.

Well, it doesn't work. Is my reasoning wrong. Are there any short cuts to keep the stack in order or how do one do. Can sombody help.

This is the code.

call REF_2
pop eax
sub eax,REF_2 ;Reference point
push eax ;Store reference point
call dword ptr[eax+_GPRMC] ;call Gprmc process
pop eax
jmp dword ptr [eax+_NMEA]

Gprmc proc

mov ecx,[esp+(7*4)]
mov esi,[esp+(5*4)]

Gprmc endp
Posted on 2003-01-28 16:42:01 by minor28

two things are somewhat confusing. First, you write:
In the Gprmc process ebp is pushed on the stack

I don't think so. The Gprmc procedure has no parameters and no locals, so MASM won't set up a stack frame, and EBP won't be pushed.

Then, you write:
Now I want to copy the original fifth dword (which now should be the eighth) and the third (which now should be the sixth).

At the start of your post, you counted the DWORDs from top to bottom. Whatever number of DWORDs are being pushed after entering the procedure, the "original fifth dword" from the top will remain the fifth dword from the top, and can not suddenly become the eigth dword (from the top). Or did you mean something else?

Anyway, here is my attempt at finding the correct figures for the Gprmc procedure. I hope it is not too weird :-)

Regards, Frank

[COLOR=blue]; x = 0[/COLOR]
; Stack:
; First DWORD from top ; [esp + (5+x)*4]
; Second DWORD from top ; [esp + (4+x)*4]
; Third DWORD from top ; [esp + (3+x)*4] ; to be accessed later
; Fourth DWORD from top ; [esp + (2+x)*4]
; Fifth DWORD from top ; [esp + (1+x)*4] ; to be accessed later
; Sixth DWORD from top ; [esp + (0+x)*4]

call REF_2 ; push address of REF_2, then jump to REF_2
[COLOR=blue]; x = 1 because of the CALL[/COLOR]
pop eax ; pop address of REF_2
[COLOR=blue]; x = 0 because of the POP[/COLOR]
sub eax,REF_2 ; EAX = (address of REF_2) - (address of REF_2) = 0
push eax ; push 0 onto stack
[COLOR=blue]; x = 1 because of the PUSH[/COLOR]
call dword ptr[eax+_GPRMC]; push return address, jump to procedure
[COLOR=blue]; x = 1 because of CALL followed by RET[/COLOR]
pop eax ; pop 0 from stack
[COLOR=blue]; x = 0 because of the POP[/COLOR]
jmp dword ptr [eax+_NMEA]; jump to procedure
ret ; this line will never be executed

Gprmc proc
[COLOR=blue]; x = x + 1 because we arrive here by a CALL[/COLOR]
[COLOR=blue]; the procedure has neither parameters nor local variables
; therefore no stack frame will be created (i.e., EBP isn't PUSHed)[/COLOR]
; the original third DWORD from top would be [esp + (3+x)*4] = [esp + (3+2)*4]
; the original fifth DWORD from top would be [esp + (1+x)*4] = [esp + (1+2)*4]
mov ecx,[esp+(7*4)]
mov esi,[esp+(5*4)]

Gprmc endp
Posted on 2003-01-28 18:35:11 by Frank

By default, PROC will generate a standard stack frame (pushing EBP onto the stack). I don't know how to nullify that in MASM. So your ESP offsets are off by one DWORD.

tenkey, that's a strong assertion with important implications -- did you actually test it? My MASM32 installation behaves, and has always behaved, differently. The following code:

.model flat,stdcall
option casemap:none
include \masm32\include\windows.inc
include \masm32\include\kernel32.inc
includelib \masm32\lib\kernel32.lib


dummy proc
dummy endp

call dummy
invoke ExitProcess, 0
end start

results in an executable that disassembles into:


00401000 fn_00401000:
00401000 C3 ret
00401001 start:
00401001 E8FAFFFFFF call fn_00401000
00401006 6A00 push 0
00401008 FF1500204000 call dword ptr [ExitProcess]

Because the dummy proc has neither parameters nor locals, MASM generates no stack frame, EBP doesn't get pushed, and ESP offsets are not off by one.

Regards, Frank
Posted on 2003-01-29 17:28:13 by Frank
I stand corrected.
Posted on 2003-01-29 19:03:21 by tenkey
Thanks for your answer

Actually there is one local so ebp is pushed and esp is decreased by 4. I found the right positions by testing. Nevertheless my main purpose with the thread was to inquire if there are som tricks or helptool to keep the stack in order though I understand that you do the same reasoning as I do.
Posted on 2003-01-30 00:51:31 by minor28
There are two tricks that I am aware of. One trick is to use MASM's automatic stack frames which do an excellent job without any effort on the programmer's side.

The other trick is to split the offset from ESP into its components. A procedure could look like this:

option prologue:none
option epilogue:none

myproc proc param1:DWORD, param2:DWORD, param3:DWORD

; Stack:
; 3rd procedure parameter = [esp + (3+0)*4]
; 2nd procedure parameter = [esp + (2+0)*4]
; 1st procedure parameter = [esp + (1+0)*4]
; return address = [esp + (0+0)*4]

mov eax, [esp + (2+0)*4] ; load the 2nd parameter into EAX
push ecx ; push something, just to change ESP
mov eax, [esp + (2+1)*4] ; load the 2nd parameter into EAX
pop ecx ; change ESP once more
mov eax, [esp + (2+0)*4] ; load the 2nd parameter into EAX

ret 3*4 ; clear 3 DWORD parameters from the stack on return

myproc endp

The source code reveals immediately that we are doing the same thing in each of three lines: we are loading the second ("2") parameter into EAX, taking changes to ESP into account ("+0" or "+1"). The notation is much clearer and less error-prone than switching from "mov eax, " to "mov eax, " and back.

I noticed the method for the first time in code by buliaNaza (see, e.g., here ), but I'm sure others have used it before. It is straightforward, self-documenting, and does not require extra tools like macros or equates. Most importantly, however, mental acrobatics becomes a thing of the past ("if I push ECX now, then my former third DWORD above the initial stack pointer will become the fourth DWORD above the current stack pointer" -- what???).

You see, it is the little things that make me happy :-)

Regards, Frank
Posted on 2003-01-30 10:29:34 by Frank