News:

MASM32 SDK Description, downloads and other helpful links
MASM32.com New Forum Link
masmforum WebSite

Using ESP and EBP

Started by jj2007, January 27, 2008, 11:12:32 PM

Previous topic - Next topic

jj2007

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


MichaelW

#1
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.

eschew obfuscation

jj2007

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?

MichaelW

Yes, if I understand "in the inner loop" correctly, and assuming you also don't access any parameters.


eschew obfuscation

hutch--

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.
Download site for MASM32      New MASM Forum
https://masm32.com          https://masm32.com/board/index.php

jj2007

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

Sarel

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

jj2007

#7
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

dedndave

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

jj2007

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

jj2007

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

RuiLoureiro

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 ---