Now I have yet another question. :)
When I implement a procedure like this:
EncodeBuffer proc
LOCAL Buffer[512]:Byte
xor ecx, ecx
lea eax, Buffer
mov edx, SIZEOF Buffer
call memset
mov esp, ebp <-- Epilogue
pop ebp
retn
EncodeBuffer endp
It looks a bit strange to me. reason is, apparently by using the "local" keyword, the assembler automatically inserts the prolog code:
push ebp
mov evp, esp
sub esp, localsize
However, I had to manually insert the epilogue code myself, as seen in the above procedure. Is this correct, or am I missing something? It looks a bit strange to me, when reading the source code, that I don't have the push instructions matching the pop instructions(even though in the final exe it will be correct again).
deleted
if you use RETN, you have to write your own epilogue (or exit without one)
if you use RET, the assembler creates the epilogue code for you, as it created the prologue code
your PROC has no parameters and no USES
so - were it not for the LOCAL variable, it would not create a prologue/epilogue at all
because you have a LOCAL, it creates a prologue that sets up the stack frame
and it will create an epilogue anywhere it sees RET
also.....
these 2 instructions
mov esp,ebp
pop ebp
may be replaced by
leave
which is what the assembler uses in most epilogues
Quote from: dedndave on October 14, 2013, 02:07:26 AM
and it will create an epilogue anywhere it sees RET
Which can lead to bloated code:
.486 ; create 32 bit code
.model flat, stdcall ; 32 bit memory model
option casemap :none ; case sensitive
.code
MyTest proc uses esi edi ebx arg
LOCAL buffer[260]:BYTE
.if arg==1
ret
.elseif arg==2
ret
.elseif arg==3
ret
.elseif arg==4
ret
.else
ret
.endif
MyTest endp
start: int 3
invoke MyTest, 123
ret
end startBranches 1...3 through the eyes of Olly:
00401000 Ú$ 55 push ebp ; RetIsAMacro.00401000(guessed Arg1)
00401001 ³. 8BEC mov ebp, esp
00401003 ³. 81EC 04010000 sub esp, 104
00401009 ³. 56 push esi
0040100A ³. 57 push edi
0040100B ³. 53 push ebx
0040100C ³. 837D 08 01 cmp dword ptr [ebp+8], 1
00401010 ³. 75 0B jne short 0040101D
00401012 ³. 5B pop ebx
00401013 ³. 5F pop edi
00401014 ³. 5E pop esi
00401015 ³. 8BE5 mov esp, ebp
00401017 ³. 5D pop ebp
00401018 ³. C2 0400 retn 4
0040101B ³. EB 3C jmp short <ModuleEntryPoi
0040101D ³> 837D 08 02 cmp dword ptr [ebp+8], 2
00401021 ³. 75 0B jne short 0040102E
00401023 ³. 5B pop ebx
00401024 ³. 5F pop edi
00401025 ³. 5E pop esi
00401026 ³. 8BE5 mov esp, ebp
00401028 ³. 5D pop ebp
00401029 ³. C2 0400 retn 4
0040102C ³. EB 2B jmp short <ModuleEntryPoi
0040102E ³> 837D 08 03 cmp dword ptr [ebp+8], 3
00401032 ³. 75 0B jne short 0040103F
00401034 ³. 5B pop ebx
00401035 ³. 5F pop edi
00401036 ³. 5E pop esi
00401037 ³. 8BE5 mov esp, ebp
00401039 ³. 5D pop ebp
0040103A ³. C2 0400 retn 4
Instead of n*ret, it's often better to use
n*jmp @Bye, with
@Bye: ret
are you using JwAsm ?
because i thought MASM used LEAVE
Yes, that was JWasm - good observation :t
Thanks for the explanations. :t
Quote from: dedndave on October 14, 2013, 02:07:26 AM
also.....
these 2 instructions
mov esp,ebp
pop ebp
may be replaced by
leave
which is what the assembler uses in most epilogues
Yeah, when I debugged it, I could see that a leave instruction was generated. I was not using it myself though, because I read somewhere that enter/leave are slower than using the manual assignment and as a consequence those instructions are not really usefull. Compilers also seem not to use them (gcc, Visual C).
The mnemonic ENTER is slow but LEAVE is not, thats why MASM uses it.
Hi sys64738,
here is a qote AMD Software Optimization Guide, p. 89:
Quote
The LEAVE instruction is a single-byte instruction and saves 2 bytes of code space over the traditional epilogue. Replacing the traditional sequence with LEAVE also preserves decode bandwidth.
Gunther
Quote from: sys64738 on October 14, 2013, 12:55:06 AM
Now I have yet another question. :)
When I implement a procedure like this:
EncodeBuffer proc
LOCAL Buffer[512]:Byte
xor ecx, ecx
lea eax, Buffer
mov edx, SIZEOF Buffer
call memset
mov esp, ebp <-- Epilogue
pop ebp
retn
EncodeBuffer endp
It looks a bit strange to me. reason is, apparently by using the "local" keyword, the assembler automatically inserts the prolog code:
push ebp
mov evp, esp
sub esp, localsize
However, I had to manually insert the epilogue code myself, as seen in the above procedure. Is this correct, or am I missing something? It looks a bit strange to me, when reading the source code, that I don't have the push instructions matching the pop instructions(even though in the final exe it will be correct again).
Hi,
you dont need to use ebp.
we may use esp, but
in this case, you need to define
your own LOCAL variables
Use
OPTION PROLOGUE:NONE
OPTION EPILOGUE:NONE
Proc1 proc var1:DWORD, ..., varN:DWORD
...
ret 4*N
Proc1 endp
OPTION PROLOGUE:PrologueDef
OPTION EPILOGUE:EpilogueDef
Quote from: sys64738 on October 14, 2013, 12:55:06 AMapparently by using the "local" keyword, the assembler automatically inserts the prolog code:
push ebp
mov evp, esp
sub esp, localsize
LOCAL adds the sub esp, localsize; the push ebp will be inserted if you have arguments OR local variables.
Work out what you are trying to achieve with the procedure design you are after. If you need LOCAL variables you generally do better using the normal MASM PROC/ENDP as MASM sets up the normal EBP based stack storage. If you are writing a short and fast leaf procedure you have a standard notation for avoiding a stack frame but you then need to know how to write a procedure without a stack frame. It becomes a little complex when you use PUSH / POP for preserving registers as you must correct the address of each stack argument to account for the address change of the ESP based stack.
In performance terms the longer a procedure gets, the less important that stack frame overhead is. You can in fact write stack frame free procedures with LOCAL variables by manually coding them and external function calls but the stack corrections become extremely complex and you gain nothing from doing so.
i think using ESP is slow compared to EBP - and clumsy, as Hutch mentioned - no advantage, there
it is often faster to create your own stack frame variables with PUSH, initializing them as you go
another advantage is that you can push EBX, ESI, EDI before the stack frame is created
the assembler pushes them afterward - which makes no sense
Hi
Writing procedures without using ebp
-------------------------------------------
It is only an example:
Proc1 proto :DWORD,:DWORD,:DWORD
OPTION PROLOGUE:NONE
OPTION EPILOGUE:NONE
Proc1 proc varX:DWORD, pProcX:DWORD, pTblX:DWORD
push ebx
push esi
push edi
;
mov ecx, 9
mov esi, [esp+24] ; pTblX
;
@@: push ecx
mov ebx, [esp+20] ; varX
shl ecx, 4
add ecx, esi
...
call dword ptr [esp+24] ; pProcX
...
pop ecx
sub ecx, 1
jns short @B
...
pop edi
pop esi
pop ebx
ret 12
Proc1 endp
OPTION PROLOGUE:PrologueDef
OPTION EPILOGUE:EpilogueDef
But as Hutch wrote,
«In performance terms the longer a procedure gets,
the less important that stack frame overhead is.
You can in fact write stack frame free procedures
with LOCAL variables by manually coding them and external
function calls but the stack corrections
become extremely complex and you gain nothing from doing so.»
----------------------------------------------------------------------------------
Explaining
When we want to get pProcX we start at
push ecx ; -> is +0 (means [esp+0])
and we go upward
next is push edi ; -> is +4
next is push esi ; -> is +8
next is push ebx ; -> is +12
next is proc (!) ; -> is +16
next is varX ---> [esp+20]
next is pProcX ---> [esp+24]
Note that pTblX is also at [esp+24]
(we start at «push edi»)
As we see, it is a simple arithmetic question
See also the old Avoiding stack frames (again!) (http://www.masmforum.com/board/index.php?topic=11766.msg88926#msg88926) thread.