The MASM Forum

General => The Campus => Topic started by: gbreuer on November 25, 2013, 12:09:11 AM

Title: ebp-handling at function-entry
Post by: gbreuer on November 25, 2013, 12:09:11 AM
Hi

(Sorry for my english)

I use masm with RadASM. When i define a function, then after disassembling i see that at the beginning of my function:

push ebp
mov ebp,esp

How can i avoid this ?  I want to program the stack by myself.

btw: i am new to assembler




Title: Re: ebp-handling at function-entry
Post by: Gunther on November 25, 2013, 01:07:42 AM
Hi gbreuer,

Quote from: gbreuer on November 25, 2013, 12:09:11 AM
Hi

(Sorry for my english)

I use masm with RadASM. When i define a function, then after disassembling i see that at the beginning of my function:

push ebp
mov ebp,esp

How can i avoid this ?  I want to program the stack by myself.

btw: i am new to assembler

That's a frame function, which can call other functions, declare local variables etc. Without this prologue it would be a leaf function. You will find more details here. (http://www.agner.org/optimize/calling_conventions.pdf) And welcome to the forum.

Gunther
Title: Re: ebp-handling at function-entry
Post by: qWord on November 25, 2013, 01:51:16 AM
Quote from: gbreuer on November 25, 2013, 12:09:11 AMHow can i avoid this ?  I want to program the stack by myself.
OPTION PROLOGUE:NONE
OPTION EPILOGUE:NONE
Title: Re: ebp-handling at function-entry
Post by: dedndave on November 25, 2013, 03:22:44 AM
        OPTION  PROLOGUE:None
        OPTION  EPILOGUE:None

DelGdiObj PROC  lphGdiObject:LPVOID

    mov     edx,[esp+4]
    mov     eax,[edx]
    .if eax
        and dword ptr [edx],0
        INVOKE  DeleteObject,eax
    .endif
    ret     4  ;with the prologue/epilogue disabled, you must pop 4 bytes for each passed dword parm

DelGdiObj ENDP

        OPTION  PROLOGUE:PrologueDef
        OPTION  EPILOGUE:EpilogueDef
Title: Re: ebp-handling at function-entry
Post by: jj2007 on November 25, 2013, 03:35:16 AM
You don't even need the prologue/epilogue thing:
include \masm32\include\masm32rt.inc

.code
AppName db "Masm32:", 0

start:
  push MB_OK
  push offset AppName
  push chr$("Hello World")
  call MyBox

  push MB_OK
  push offset AppName
  push chr$("Another box")
  call MyOtherBox
  exit

MyBox proc ; arg1, arg2, arg3
  invoke MessageBox, 0, [esp+3*4], [esp+3*4], [esp+3*4]
  retn 3*DWORD
MyBox endp

MyOtherBox:
  invoke MessageBox, 0, [esp+3*4], [esp+3*4], [esp+3*4]
  retn 3*DWORD

end start


I doubt that you will do yourself a favour using frameless procs. It is rarely faster, rarely more compact, but frequently a source of very obscure bugs that will cost you sleepless nights.

However, in case you cannot suppress your masochistic tendencies, Hutch has left an easter egg for you: \Masm32\examples\exampl07\slickhuh\slickhuh.asm  :lol:
Title: Re: ebp-handling at function-entry
Post by: dedndave on November 25, 2013, 04:40:56 AM
there are times when it is nice   :t
Title: Re: ebp-handling at function-entry
Post by: hutch-- on November 25, 2013, 05:23:26 AM
The advantage of a procedure with no stack frame is limited to very short leaf procedures that are hit at very high interval rates. In this context you save some overhead but with anything much larger or anything that calls another procedure, the complexity is very high and for no gain.

Basically you have to manually keep track of the register ESP and any PUSH or POP instruction changes ESP.

Now with a procedure call that has arguments passed to it on the stack in the normal manner it has the return address at the stack memory address [esp], the first user argument is at [esp+4], second at [esp+8] etc ....

If you have an argument at [esp+4] then use two PUSH instructions, you must add 4 * 2 to the stack address so [esp+4] becomes [esp+12] or to help you keep track of it the alternative notation [esp+4][8]. NOTE also that you must balance the stack manually on exit if you are using the STDCALL convention, the RET(n) instruction takes a numeric argument so if you have 2 * 4 byte stack arguments you use RET 8 on exit to balance the stack. If this sounds complicated, you are right.  :icon_mrgreen:
Title: Re: ebp-handling at function-entry
Post by: dedndave on November 25, 2013, 05:41:08 AM
that's not the only advantage of creating your own stack frame

MyFunc PROC USES EBX lpString:LPSTR

    LOCAL   dwSomeVal   :DWORD

;body code

    ret

MyFunc ENDP


the assembler generates something like this...

MyFunc PROC lpString:LPSTR

    push    ebp
    mov     ebp,esp
    sub     esp,4       ;or ADD ESP,-4; LEA ESP,[ESP-4] might be better, as no flags are affected
    push    ebx

;body code

;the stack must be balanced before exit so that EBX is properly restored

    pop     ebx
    leave
    ret     4

MyFunc ENDP


however, if you take charge of the stack frame....

        OPTION  PROLOGUE:None
        OPTION  EPILOGUE:None

MyFunc PROC lpString:LPSTR

    push    ebx
    push    ebp
    mov     ebp,esp
    lea     esp,[esp-4]      ;flags not affected (though we rarely use current flag state)

;body code

;the stack is balanced by LEAVE before EBX is restored
;i.e., you can exit with an imbalanced stack

    leave
    pop     ebx
    ret     4

MyFunc ENDP

        OPTION  PROLOGUE:PrologueDef
        OPTION  EPILOGUE:EpilogueDef


one example of this being particularly useful...
your PROC creates a variable-sized (string perhaps) buffer on the stack
no need to restore the original ESP, as LEAVE takes care of everything
Title: Re: ebp-handling at function-entry
Post by: Vortex on November 25, 2013, 05:43:13 AM
Here is another one :

include     SimpleFunc.inc

.data

str1        db 'Function test',0

.code

start:

   _invoke  StdOut,ADDR str1

    invoke  ExitProcess,0

StdOut:

    sub     esp,2*4
    invoke  GetStdHandle,STD_OUTPUT_HANDLE
    mov     DWORD PTR [esp+4],eax
    invoke  lstrlen,DWORD PTR [esp+12]
    mov     edx,esp
   
    invoke  WriteFile,DWORD PTR [esp+20],\
            DWORD PTR [esp+24],\
            eax,edx,0
           
    add     esp,2*4
    ret     4

END start
Title: Re: ebp-handling at function-entry
Post by: dedndave on November 25, 2013, 05:58:35 AM
and - if you want to use names for parms and locals....
        OPTION  PROLOGUE:None
        OPTION  EPILOGUE:None

MyFunc PROC lpString:LPSTR

;----------------------------------------

_lpString  TEXTEQU <dword ptr [EBP+12]>      ;lpString PARM alias
;                             [EBP+8]         return address
;                             [EBP+4]         preserved EBX
;                             [EBP]           preserved EBP
_dwSomeVal TEXTEQU <dword ptr [EBP-4]>       ;dwSomeVal LOCAL alias

;----------------------------------------

    push    ebx
    push    ebp
    mov     ebp,esp
    lea     esp,[esp-4]      ;flags not affected (though we rarely use current flag state)

;body code

;the stack is balanced by LEAVE before EBX is restored
;i.e., you can exit with an imbalanced stack

    leave
    pop     ebx
    ret     4

MyFunc ENDP

        OPTION  PROLOGUE:PrologueDef
        OPTION  EPILOGUE:EpilogueDef


it's easy to update the TEXTEQU list when changes are made   :t
Title: Re: ebp-handling at function-entry
Post by: dedndave on November 25, 2013, 06:53:09 AM
one more advantage of maintaining your own stack frame
is that you can initialize local variables, as they are calculated
for example.....
    push    ebx
    push    esi
    push    edi
    push    ebp                                    ;[EBP]    = saved EBP contents
    mov     esi,cg.v.rcDiv.top                     ;ESI = window height
    mov     ebp,esp
    dec     esi                                    ;ESI = window height-1
    xor     ecx,ecx
    movzx   eax,byte ptr dwGridState
    mov     ebx,36
    inc     ecx
    .if esi>=265
        mov     cl,35
        .if eax
            mov     bx,605h
        .endif
    .elseif esi>=83
        mov     cl,7
        .if eax
            mov     bx,601h
        .endif
    .elseif esi>=59
        mov     cl,5
        .if al==2
            mov     bx,401h
        .endif
    .elseif esi>=43
        mov     cl,5
        .if al==2
            mov     bl,1
        .endif
    .endif
    mov     eax,esi
    xor     edx,edx
    push    ecx                                    ;[EBP-4]  = loop count+1
    push    esi                                    ;[EBP-8]  = Y position high dword
    push    edx                                    ;[EBP-12] = Y position low dword
    div     ecx
    push    eax                                    ;[EBP-16] = Y step high dword
    xor     eax,eax
    div     ecx
    shl     edx,1
    cmp     edx,ecx
    sbb     eax,-1
    push    eax                                    ;[EBP-20] = Y step low dword
    push    ebx                                    ;[EBP-24] = grid line count reset
;
;


LOCAL's can be ordered "as they are created" on the stack
this is probably more efficient than a bunch of MOV [EBP-xx],reg32 instructions
Title: Re: ebp-handling at function-entry
Post by: gbreuer on November 25, 2013, 09:24:25 AM
Thanks, for this detailed answers.

The reason for my question was, that i explore the principles of local "variables" via stack.
Then i found during debugging the "epilog"-code and was not sure, whether the assembler confuse my experiments by adding code ;-)

Now i know, that this code is common and i can immediately work with ebp, as soon the function is started.


Title: Re: ebp-handling at function-entry
Post by: dedndave on November 25, 2013, 09:38:35 AM
what you may want to know...
if you use no PROC line arguements (parameters) and no LOCAL's, EBP is not used
you can preserve it, and use it as a working register

the windows ABI.....
EBX, ESI, EDI, and EBP should be preserved across calls
and - the direction flag should remain cleared (if you set it, clear it when done)
EAX, ECX, and EDX may be destroyed across calls

those are the rules for windows API functions
we usually try to follow the same rules when writing PROC's

i may write a PROC that preserves EBX, ESI, EDI, EBP
then, inside that PROC, call "low-level" support PROC's that do not preserve them - that's ok, too
Title: Re: ebp-handling at function-entry
Post by: hutch-- on November 25, 2013, 10:59:20 AM
Here is a simple test piece that demonstrates how ESP behaves in a no stack frame proc. It also shows ESP before and after the call to show that the stack is balanced.


IF 0  ; ¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤
                      Build this template with "CONSOLE ASSEMBLE AND LINK"
ENDIF ; ¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤

    include \masm32\include\masm32rt.inc

    testproc PROTO arg1:DWORD,arg2:DWORD

    .data?
      value dd ?

    .data
      item dd 0

    .code

start:
   
; ¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤

    call main
    inkey
    exit

; ¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤

main proc

    LOCAL var1    :DWORD

    print ustr$(esp)," stack before call",13,10,13,10   ; stack BEFORE procedure call

    invoke testproc,499, 501
    mov var1, eax

    print ustr$(esp)," stack after call",13,10,13,10    ; stack AFTER procedure call

    print ustr$(var1)," Calculation result",13,10,13,10

    ret

main endp

; ¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤

OPTION PROLOGUE:NONE
OPTION EPILOGUE:NONE

testproc proc arg1:DWORD,arg2:DWORD

    push ebx                    ; add 4 to ESP
    push esi                    ; add another 4 to ESP
    push edi                    ; add another 4 to ESP
    push ebp                    ; add another 4 to ESP

  ; --------------------------------------
  ; you now have 7 32 bit registers to use
  ; EAX ECX EDX EBX EBP ESI EDI
  ; [ESP] is treated as a memory operand
  ; --------------------------------------

  ; [esp+4] = arg1
  ; [esp+8] = arg2
  ; the appended [16] is the extra bytes for the 4 PUSH instructions

    mov eax, [esp+4][16]        ; load arg1 into EAX
    mov ecx, [esp+8][16]        ; load arg2 into ECX

    add ecx, eax                ; do something simple in the test proc

    mov eax, ecx                ; place the return value if needed in EAX

  ; ------------------------------------------------
  ; restore the contents of the 4 required registers
  ; ------------------------------------------------
    pop ebp
    pop edi
    pop esi
    pop ebx

    retn 8                      ; balance the stack with 8 bytes

testproc endp

OPTION PROLOGUE:PrologueDef
OPTION EPILOGUE:EpilogueDef

; ¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤

end start
Title: Re: ebp-handling at function-entry
Post by: gbreuer on November 27, 2013, 05:20:28 AM
Hi

here a little test-function. I call it via invoke.

As we discussed above, the assembler inserts at its beginning

push ebp
mov ebp,esp

I want a local variable (named here "stringvar") that is 3 Bytes long (inclusive the 0-Byte).

The first 4 Bytes of the stack are taken by the "push ebp" above. So i subtract 7 from
ebp to skip this 4 Bytes and then to make space for the needed 3 bytes for "stringvar":
Now the offset for "stringvar" is calculated.

Before this i move the stackpointer to make space for "stringvar".

But this only works, when i move the stackpointer about 12 Bytes. Otherwise i get an
empty MessageBox. (Even the button is empty).

I followed this step by step via ollydbg. But i found no explanation.

What i am doing wrong ?




functionxyz proc testarg:dword
                 
    ; here the assembler later inserts automatically "push ebp" and "mov ebp,esp"
                 
    STACK_ADDER equ 12 ; only 12 works. lower or higher results in an empty messagebox below
   
    ; 7 for skipping the 4 bytes of the stack and then the 3 needed bytes for stringvar
      stringvar equ  ebp-7 
     
      sub esp,STACK_ADDER  ; reserve space on stack


      mov byte ptr [stringvar],   'o'
      mov byte ptr [stringvar+1], 'k'
      mov byte ptr [stringvar+2], 0
           

      invoke MessageBox,0,addr [stringvar],offset mbt_title,MB_OK



    mov eax,0affeh ; return-test-value                             
                           
    add esp,STACK_ADDER ; restore stack
 
 
    ret                       

functionxyz endp


Title: Re: ebp-handling at function-entry
Post by: dedndave on November 27, 2013, 05:27:55 AM
ok - don't mix "the assembler handles the stack frame" with "the programmer handles it" in the same PROC
use one method or the other

in this case, just create a LOCAL or LOCAL's

MyFunc  PROC  dwParm:DWORD

    LOCAL   dwVal1      :DWORD  ;1 dword
    LOCAL   dwVal2      :DWORD  ;1 dword
    LOCAL   abBytes[4]  :BYTE   ;4 bytes
Title: Re: ebp-handling at function-entry
Post by: jj2007 on November 27, 2013, 05:44:15 AM
Quote from: gbreuer on November 27, 2013, 05:20:28 AM
But this only works, when i move the stackpointer about 12 Bytes. Otherwise i get an
empty MessageBox. (Even the button is empty).

I followed this step by step via ollydbg. But i found no explanation.

In Win32 code, the stack must be DWORD-aligned. Otherwise, you will see such crashes all the time.
Title: Re: ebp-handling at function-entry
Post by: gbreuer on November 27, 2013, 08:30:22 AM
Thanks for the hints. I have to learn more. At the moment i will use LOCAL in such cases.

But there is a problem with "LOCAL": How can i make it compatible to underscores in variablenames?

I got a syntax-error for "ebp" as long as i used a name like this: local a_b ...

Then i removed the underscore and it worked.



Title: Re: ebp-handling at function-entry
Post by: jj2007 on November 27, 2013, 08:37:14 AM
LOCAL a_b works fine with ML 6.14 ... 10.0 and JWasm.

Do you get Error A2056: Symbol already defined: a_b ?
Can you post a complete example?
Title: Re: ebp-handling at function-entry
Post by: gbreuer on November 27, 2013, 08:56:37 AM
I got this error:

error A2008: syntax error : ebp

But i found the problem: The length of variablenames, that are used with LOCAL
is limited. It has nothing to do with the underscore. I shortened the name, and it works now.