I am testing whether one can use esp and ebp without changing the stack frame etc., through using global variables for storing these two registers. Fine so far, but in the line marked with *** something unexpected happened, and I wonder if anybody can give me a clue? Normally, changing eSp should not affext the use of a local variable, because they are relative to eBp... where is the error in my logic?
Thanxalot, jj
.nolist
include \masm32\include\masm32rt.inc
EspTest PROTO:DWORD
.data?
gvEsp dd ?
gvEbp dd ?
gv1 dd ?
gv2 dd ?
gv3 dd ?
gva1 dd ?
.code
start:
print cat$(chr$(13, 10, "Stack/BP="), str$(esp), chr$("/"), str$(ebp), chr$(13,10))
invoke EspTest, 12345
print cat$(chr$(13, 10, "Stack/BP="), str$(esp), chr$("/"), str$(ebp), chr$(13,10))
invoke ExitProcess,0
EspTest proc Arg1
Local Var1:DWORD, Var2:DWORD, Var3:DWORD
mov gvEsp, esp
mov gvEbp, ebp
mov Var1, 1001 ; MOV DWORD PTR SS:[EBP-4],1001
mov Var2, 1002 ; MOV DWORD PTR SS:[EBP-8],1002
mov Var3, 1003 ; MOV DWORD PTR SS:[EBP-C],1003
print chr$("Expected:",13, 10, "Var1/Var2/Var3/Arg1", 13,10)
print cat$(str$(Var1), chr$("/"), str$(Var2), chr$("/"), str$(Var3), chr$("/"), str$(Arg1))
sub esp, 4
print chr$(13,10, "sub esp, 4", 13,10)
print cat$(str$(Var1), chr$("/"), str$(Var2), chr$("/"), str$(Var3), chr$("/"), str$(Arg1))
add esp, 4
print chr$(13,10, "add esp, 4", 13,10)
print cat$(str$(Var1), chr$("/"), str$(Var2), chr$("/"), str$(Var3), chr$("/"), str$(Arg1))
add esp, 4 ; increasing esp beyond initial value...
m2m gv1, Var1
m2m gv2, Var2
m2m gv3, Var3 ; ... means that here, all Vars are correct except Var3==1002! ************************
m2m gva1, Arg1 ; stack corruption?
COMMENT @ 4 m2m's above:
00401231 |. FF75 FC PUSH DWORD PTR SS:[EBP-4] push Var1 (local)
00401234 |. 8F05 E8364000 POP DWORD PTR DS:[4036E8] pop gv1 (global)
0040123A |. FF75 F8 PUSH DWORD PTR SS:[EBP-8]
0040123D |. 8F05 EC364000 POP DWORD PTR DS:[4036EC]
00401243 |. FF75 F4 PUSH DWORD PTR SS:[EBP-C]
00401246 |. 8F05 F0364000 POP DWORD PTR DS:[4036F0]
0040124C |. FF75 08 PUSH DWORD PTR SS:[EBP+8]
0040124F |. 8F05 F4364000 POP DWORD PTR DS:[4036F4] @
print chr$(13,10, "add esp, 4, local", 13,10)
print cat$(str$(Var1), chr$("/"), str$(Var2), chr$("/"), str$(Var3), chr$("/"), str$(Arg1))
print chr$(13,10, "add esp, 4, GLOBAL", 13,10)
print cat$(str$(gv1), chr$("/"), str$(gv2), chr$("/"), str$(gv3), chr$("/"), str$(gva1))
mov Var1, 1001
mov Var2, 1002
mov Var3, 1003
sub esp, 400 ; then decreasing?
print chr$(13,10, "add & sub esp, 400", 13,10)
print cat$(str$(Var1), chr$("/"), str$(Var2), chr$("/"), str$(Var3), chr$("/"), str$(Arg1))
print chr$(13,10, "global vals inside add & sub esp, 400 - note values 2 & 3:", 13,10)
print cat$(str$(gv1), chr$("/"), str$(gv2), chr$("/"), str$(gv3), chr$("/"), str$(gva1))
add esp, 400 ; increasing plus calls is not so good ;-)
print chr$(13,10, "add esp, 4", 13,10)
print cat$(str$(Var1), chr$("/"), str$(Var2), chr$("/"), str$(Var3), chr$("/"), str$(Arg1))
mov esp, gvEsp
print chr$(13,10, "mov esp, gvEsp", 13,10)
print cat$(str$(Var1), chr$("/"), str$(Var2), chr$("/"), str$(Var3), chr$("/"), str$(Arg1))
mov Var1, 1001
mov Var2, 1002
mov Var3, 1003
print chr$(13,10, 13,10, "now fumbling with base pointer", 13,10)
print cat$(str$(Var1), chr$("/"), str$(Var2), chr$("/"), str$(Var3), chr$("/"), str$(Arg1))
sub ebp, 4
print chr$(13,10, "sub ebp, 4", 13,10)
print cat$(str$(Var1), chr$("/"), str$(Var2), chr$("/"), str$(Var3), chr$("/"), str$(Arg1))
add ebp, 4
print chr$(13,10, "add ebp, 4", 13,10)
print cat$(str$(Var1), chr$("/"), str$(Var2), chr$("/"), str$(Var3), chr$("/"), str$(Arg1))
add ebp, 4
print chr$(13,10, "add ebp, 4", 13,10)
print cat$(str$(Var1), chr$("/"), str$(Var2), chr$("/"), str$(Var3), chr$("/"), str$(Arg1))
mov ebp, gvEbp
print chr$(13,10, "mov ebp, gvEbp", 13,10)
print cat$(str$(Var1), chr$("/"), str$(Var2), chr$("/"), str$(Var3), chr$("/"), str$(Arg1))
mov esp, gvEsp
mov ebp, gvEbp
ret
EspTest endp
end start
Locals are addressed relative to EBP, but they are stored on the stack immediately above the portion of the stack that is active in the procedure. The contents of the stack after the locals have been allocated:
Arg1
Return address
Preserved EBP
Locals
Active stack
The first add esp, 4 corrects for the sub esp, 4, restoring ESP to the the value it had initially, and after the second add esp, 4 the next value pushed overwrites a local.
Thanks, Michael. Actually, one minute after I posted this I realised that the m2m would write into that sensible area called stack... but I had shut down my pc already, and it was after midnight...
Replacing m2m with mrm helps, but "print" uses the stack, too.
The purpose of this exercise was to check whether one can safely use two extra registers. So what I learnt is:
- esp and ebp can safely be stored in global variables and restored from there;
- yes you can use esp as long as you don't use the stack with m2m=push/pop or through calling another procedure, including API's, print, chr$ and whatever comes along as apparently simple tools but uses the stack;
- yes you can use ebp as long as you don't use any local variables in the inner loop.
Correct?
Yes, if I understand "in the inner loop" correctly, and assuming you also don't access any parameters.
jj,
You can routinely use EBP if you handle the stack addresses directly but it can be a bit tricky if you push a set of registers as you have to correct the ESP position for each push.
just for example if you remove a stack frame then push 3 registers the the first arg address in ESP shifts feom ESP+4 to ESP+16.
With a procedure written without a stack frae you must also balance the stack on exit (if its a STDCALL procedure) with RET (byte count) equal to the byte count of arguments passed on the stack to the procedure.
If for example you push 3 DWORD argumwents to the procedure you balance the stack with RET 12.
This boils down to:
- using eSp is fine if you don't use the stack in the inner loop between save/restore eSp
- using eBp is fine if you don't use locals and arguments in the inner loop between save/restore eBp
And that even works without fumbling with PROLOGUE & EPILOGUE :P
Thanks to both of you. Here is a handy snippet that I use in projects with many procedures.
.data?
EspGlob dd ?
TestWithManyLocalVars proc
LOCAL MyCt:DWORD
LOCAL lvi:LVITEM
LOCAL lvCt:DWORD, lvPos:DWORD
LOCAL LocBuf[BufLen]:BYTE, pBuf:DWORD
call ClearLocals
...
ClearLocals proc ; first instruction after LOCALS - eax will be zero on exit
pop EspGlob ; save the return address - now the stack is identical to the calling procedure
mov eax,ecx ; save ecx
mov ecx,ebp ; base page of calling procedure
sub ecx,esp ; ebp - esp = No. of bytes in locals
mov esp,ebp ; discard existing locals
shr ecx,2 ; divide by four
@@: push 0 ; dwords on stack
loop @B
xchg eax,ecx ; restore ecx - eax is now zero
push EspGlob ; restore the return address
ret
ClearLocals endp
Thanks for the information. I was running out of registers and first used EBP without a problem. Then I used ESP. Unfortunately there were a few pushes and pops. :U
Just for fun:
include \masm32\include\masm32rt.inc
.code
MyTest proc arg1:DWORD
print arg1, 13, 10
xor esp, esp ; <<<<<<<<<<<<<<<<<<<<
ret
MyTest endp
start:
invoke MyTest, chr$("Masm32 is great")
inkey "Isn't it?"
exit
end start
it should crash hard - lol
unless there is some magical function address at cs:00000000, like ExitProcess
ohhhhhhhhhhh
nevermind
the epilogue does a LEAVE, which resets the stack pointer :P
i have gotten used to writing most stuff with no prologue/epilogue
Here are two more "teasers":
include \masm32\include\masm32rt.inc
.data
Src db "Just a pretty useless string for demonstrating the use of the stackpointer in an unusual context"
.data?
Dest1 db 1000 dup(?)
Dest2 db 1000 dup(?)
.code
PushCopy proc src, dest, count
mov esi, src
mov esp, dest
add esi, count
add esp, count
pop eax
std
.Repeat
lodsd
push eax
.Until esp<=dest
cld
ret
PushCopy endp
PopCopy proc src, dest, count
mov esp, src
mov edi, dest
mov ecx, count
add ecx, edi
.Repeat
pop eax
stosd
.Until edi>=ecx
ret
PopCopy endp
start:
invoke PushCopy, offset Src, offset Dest1, sizeof Src
print offset Dest1, 13, 10
invoke PopCopy, offset Src, offset Dest2, sizeof Src
inkey offset Dest2
exit
end start
Wow, I had no idea that popping was so fast...:
Intel(R) Celeron(R) M CPU 420 @ 1.60GHz (SSE3)
test with 50 bytes
44 cycles for PopCopy
77 cycles for MbCopy
83 cycles for MemCopy
88 cycles for RtlMoveMemory
test with 200 bytes
138 cycles for PopCopy
178 cycles for MbCopy
185 cycles for MemCopy
177 cycles for RtlMoveMemory
test with 800 bytes
553 cycles for PopCopy
644 cycles for MbCopy
644 cycles for MemCopy
638 cycles for RtlMoveMemory
test with 3200 bytes
2177 cycles for PopCopy
2385 cycles for MbCopy
2385 cycles for MemCopy
2382 cycles for RtlMoveMemory
Intel(R) Pentium(R) 4 CPU 3.00GHz (SSE3)
test with 50 bytes
56 cycles for PopCopy
120 cycles for MbCopy
127 cycles for MemCopy
128 cycles for RtlMoveMemory
test with 200 bytes
220 cycles for PopCopy
240 cycles for MbCopy
248 cycles for MemCopy
225 cycles for RtlMoveMemory
test with 800 bytes
701 cycles for PopCopy
697 cycles for MbCopy
716 cycles for MemCopy
706 cycles for RtlMoveMemory
test with 3200 bytes
2640 cycles for PopCopy
1864 cycles for MbCopy
1834 cycles for MemCopy
1762 cycles for RtlMoveMemory
--- ok ---
try POPFD, though :P
http://www.masm32.com/board/index.php?topic=15639.msg128430#msg128430