News:

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

Main Menu

Is EBP a protected register?

Started by CCurl, October 17, 2015, 12:02:52 AM

Previous topic - Next topic

CCurl

I am 99% sure the answer is YES, but want to verify with the people who have more experience than I have.

In my code, can I set EBP to a value and assume that it will be preserved when I call library procedures, such as WriteChar and ReadConsole?

gelatine1

As far as I know ebp is used to set up a stack frame (for each function that has parameters or uses local variables) and thus ebp will be changed after a function call. I don't believe the functions restore the value of ebp before returning

jj2007

Quote from: gelatine1 on October 17, 2015, 12:59:14 AMI don't believe the functions restore the value of ebp before returning

Believing is not knowing :biggrin:

include \masm32\MasmBasic\MasmBasic.inc      ; download

.code
MyTest proc uses esi edi ebx hwnd:DWORD, uMsg:DWORD, wParam:DWORD, lParam:DWORD
LOCAL MyLocVar
  mov MyLocVar, rv(GetTickCount)                        ; WinAPI test
  print str$(ebp), 9, "ebp in the proc", 13, 10         ; more WinAPI calls under the hood
  push 123
  push 456
  push 789      ; seriously misaligned stack!
  ret
MyTest endp

Init
  mov esi, 11111111
  mov edi, 22222222
  mov ebx, 33333333
  deb 4, "Before the proc", ebp, esi, edi, ebx
  invoke MyTest, 1, 2, 3, 4
  deb 4, "After the proc", ebp, esi, edi, ebx
EndOfCode


Output:Before the proc
ebp             1638292
esi             11111111
edi             22222222
ebx             33333333
1638260 ebp in the proc

After the proc
ebp             1638292
esi             123
edi             456
ebx             789

dedndave

when you write a PROC, MASM creates a "stack frame", by default
well - it does if there are any arguments on the PROC line, or if there are any LOCAL variables

if you write a PROC like this....

SomeFunc PROC USES EBX ESI EDI Arg1:DWORD,Arg2:DWORD

    LOCAL   dwLocalVar1    :DWORD
    LOCAL   dwLocalVar2    :DWORD

    mov     ebx,87654321h
    mov     esi,Arg1
    mov     edi,Arg2
    mov     dwLocalVar1,12345678h
    mov     dwLocalVar2,87654321h
    mov     eax,dwLocalVar1
    add     eax,ebx
    ret

SomeFunc ENDP


the assembler will generate code that looks like this
notice that StdCall convention is the default for most ASM programs, because windows API functions use it
for C, Basic, Pascal, or other conventions, the code will be a little different

SomeFunc PROC Arg1:DWORD,Arg2:DWORD

    push    ebp                            ;preserve EBP
    mov     ebp,esp                        ;EBP is now the stack frame base pointer
    sub     esp,8                          ;make space for 2 local dwords
    push    ebx
    push    esi
    push    edi

    mov     ebx,87654321h
    mov     esi,[ebp+8]                    ;Arg1
    mov     edi,[ebp+12]                   ;Arg2
    mov     [ebp-4],12345678h              ;dwLocalVar1
    mov     [ebp-8],87654321h              ;dwLocalVar2
    mov     eax,[ebp-4]                    ;dwLocalVar1
    add     eax,ebx

    pop     edi
    pop     esi
    pop     ebx
    leave                                  ;same as MOV ESP,EBP, then POP EBP
    ret     8                              ;return and remove 2 dword arguments from the stack

SomeFunc ENDP


the first part is called a "prologue"
the last part is called an "epilogue"
an epilogue will be generated any place there is a RET, so try to use one exit point   :P

per the windows32 ABI, EBX, EBP, ESI, EDI should be preserved
also, it is assumed that the direction flag is cleared at entry
if you set the direction flag inside a function, you should clear it before exit
EAX, ECX, and EDX are volatile, EAX is generally used to return a status or result
one is no more volatile than the other - most often, they all get trashed

CCurl

Excellent info. So long I can be confident that EBP isn't going to change out from under me when calling routines, I'm good.

Thanks.

jj2007

Perhaps I should drop a word on the "seriously misaligned stack" in my example:
SomeTest proc uses esi edi ebx args:DWORD
  ...
  ret
SomeTest endp


"uses esi edi ebx" means "preserve these three registers", which is required in certain situations by Windows (more precisely, in callback functions).
The stack frame uses ebp on entry, and restores it on exit, providing also the correct stackpointer esp. That is a great feature, especially if you are bad in math, and don't balance the number of push's and pop's. Unfortunately, your "uses esi edi ebx" gets invalid because these regs are popped in the wrong place. This kind of bug is really difficult to chase, so better make an effort to control if your esp is the same on entry and before the ret. If in doubt, use a global variable to store esp on proc entry, and compare it to the stored one on proc exit, i.e. before the ret.

CCurl

I absolutely get it that if I push something on the return stack, and I don't pop it before returning, then my program will fail miserably.

On the other hand though, if you know what you are doing, you can do some pretty trick stuff by pushing a valid code address on the stack and then returning. That kind of thing is pretty common in Forth, but I currently have no need or desire to do it at the machine level.

Forth (that is what I am implementing) has separate stacks for parameters and return addresses, so it can avoid most of this fancy stack juggling fun.

hutch--

The thing you are looking for here is a "balanced" stack which mean that it must be the same AFTER the called procedure has returned. In STDCALL this is done by the procedure, with C call you must correct ESP yourself. As far as manipulating the stack, stick to CALL / RET(N) number as it is balanced in hardware. If you use a normal MASM stack frame it does this for you but once you start writing procedures with no stack frame you will need to do this yourself.

dedndave

i often write my own prologue and epilogue
and, the way i do it, you can leave with an unbalanced stack, here's why...

;these directives turn off the default prologue and epiloge
        OPTION  PROLOGUE:None
        OPTION  EPILOGUE:None

MyFunc PROC Arg1:DWORD,Arg2:DWORD

    push    ebx
    push    esi
    push    edi
    push    ebp
    mov     ebp,esp                  ;notice that EBP holds an unaltered value throughout the routine

    ;here, i can PUSH whatever i like - POP is optional

    leave                            ;the value in EBP is loaded back into ESP, effectively reseting the stack
                                     ;LEAVE also POP's EBP from the stack
    pop     edi
    pop     esi
    pop     ebx
    ret     8                        ;RETurn and discard Arg1 and Arg2

MyFunc ENDP

;these directives restore the default prologue and epilogue for the next PROC
        OPTION  PROLOGUE:PrologueDef
        OPTION  EPILOGUE:EpilogueDef

jj2007

Quote from: CCurl on October 17, 2015, 08:17:25 AMI absolutely get it that if I push something on the return stack, and I don't pop it before returning, then my program will fail miserably.

The nasty thing is, no, with stack frames it won't fail miserably. It will return correctly even if the stack is unbalanced, but the preserved regs (usually esi, edi, ebx) are trashed. And it is difficult to predict how Windows or your own routines react to such trashing, and where... but no worries, if you reach that stage, we can discuss ways to prevent that.

Dave's code is one option, as it preserves the regs before setting the frame. Which means you can write a proc with an unbalanced stack at the end - that works perfectly, if it is by design 8)

But we are indulging in unnecessary details here. In 99.9% of all cases, simply take care that the stack is balanced, fullstop.

QuoteOn the other hand though, if you know what you are doing, you can do some pretty trick stuff by pushing a valid code address on the stack and then returning. That kind of thing is pretty common in Forth, but I currently have no need or desire to do it at the machine level.

Forth (that is what I am implementing) has separate stacks for parameters and return addresses, so it can avoid most of this fancy stack juggling fun.

We've seen such tricks, of course. Speed-wise, they are rarely efficient.

dedndave

Quote from: CCurl on October 17, 2015, 04:56:41 AM
So long I can be confident that EBP isn't going to change out from under me when calling routines, I'm good.

you can pretty much rely on that one
if it were any other way, all kinds of code would crash   :biggrin:

i have disassembled some Forth code, and saw things like this

    pop     edi     ;get the return address off the stack

;body of code

    jmp     edi


it's not all that inefficient, really
as Hutch will tell you, newer processors attempt to "match up" CALL's and RET's (prediction)
ok - you break that prediction algorithm, but the ouch isn't too serious
JMP EDI is pretty fast   :t
but, the processor may try to cache code unnecessarily, up to the next RET instruction

KeepingRealBusy

Quote from: CCurl on October 17, 2015, 04:56:41 AM
Excellent info. So long I can be confident that EBP isn't going to change out from under me when calling routines, I'm good.

Thanks.

Just one moment. Are you putting anything into EBP and expecting it to be present when you return from a library routine? How do you preserve what is in EBP as you enter your function?

Dave.

CCurl

Quote from: KeepingRealBusy on October 18, 2015, 06:49:59 AM
Just one moment. Are you putting anything into EBP and expecting it to be present when you return from a library routine? How do you preserve what is in EBP as you enter your function?

Dave.
Yes, that is absolutely my expectation and understanding. Is it invalid?

Since no outside code is calling my procedures, I believe I can manage EBP however I choose, can't I?

I understand that if I choose to write procedures that some 3rd party code can call, then I will need to follow the rules regarding register preservation.

KeepingRealBusy

If no one is calling your procedures, how does your program ever get into execution?

Dave.

CCurl

Quote from: KeepingRealBusy on October 20, 2015, 02:25:29 PM
If no one is calling your procedures, how does your program ever get into execution?

Dave.
It is a command line program. This is my main function. All the work is done in processLine.

main proc
call bootStrap

mainLoop:
; Display prompt
mov  edx, offset msgPrompt ; string addr in edx
call WriteString

; Wait for commands from console
vmToAbsolute edx, MemLoc_InpBuf
inc edx ; save room for the count BYTE

INVOKE ReadConsole, hStdIn, edx, 100, ADDR bytesRead, 0

mov eax, [bytesRead] ; count includes CR and LF
sub eax, 2 ; remove CR/LF
MemSet_8 MemLoc_InpBuf, al ; store the count
m_fPush 0
m_fPush MemLoc_InpBuf
m_execW code_Count
m_execO I_PLUS
m_execO I_STORE_8

; is the command "bye"?
push edi
mov edi, offset cmdBye
vmToAbsolute esi, MemLoc_InpBuf
call strCmpC
pop edi

cmp  eax, 0 ; strCmpX returns result in EAX
jne  exitMain

call processLine ; Where all the work gets done

mov  edx, offset msgOK ; string addr in edx
call WriteString

jmp MainLoop

exitMain:
mov  edx, offset msgBye ; string addr in edx
call WriteString

; vmToAbsolute edx, MemLoc_InpBuf
; INVOKE ReadConsole, hStdIn, edx, 100, ADDR bytesRead, 0

invoke GetProcessHeap
invoke HeapFree, eax, HEAP_NO_SERIALIZE, theMemory

invoke ExitProcess,0
main endp
end main