News:

Masm32 SDK description, downloads and other helpful links
Message to All Guests

Main Menu

Procedure prolog/epilogue

Started by sys64738, October 14, 2013, 12:55:06 AM

Previous topic - Next topic

sys64738

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

nidud

#1
deleted

dedndave

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

jj2007

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 start


Branches 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

dedndave

are you using JwAsm ?
because i thought MASM used LEAVE

jj2007

Yes, that was JWasm - good observation :t

sys64738

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

hutch--

The mnemonic ENTER is slow but LEAVE is not, thats why MASM uses it.

Gunther

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
You have to know the facts before you can distort them.

RuiLoureiro

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

jj2007

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.

hutch--

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.

dedndave

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

RuiLoureiro

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

jj2007