hi,
How do i go about using ESP & EBP in my code ?
What are the things i have to look out for ?
how can i find out if ESP & EBP are already being used (assuming am not using them manually) ?
they are used for procedures so its a delicate proccess i guess.
would appreciate any guidlines,..tips....tricks.. : )
thank-you
-
I often write small apps for solving math problems. Since those never require the creation of a window, I also never bother to use full blown procs creating stack frames. If some code needs to be repeated, I generally write it as a subroutine using data from registers or from memory as in the old days. Thus, if I occasionally would like to have an extra register, I would use EBP without any concern about saving or restoring it because it must have been saved by the OS and restored when exiting from my program.
However, toying with the ESP register is a totaly different animal. In theory, you could save it in memory and restore it later. But, while you may be using it for some other task, you can't push anything on the stack without risking a page fault aborting your app. Although possible, using the ESP register for some other purpose may be more troublesome than finding some other alternative.
If your app requires the creation of stack frames because of passed parameters or local variables, you should not risk using the EBP register either for other tasks.
Raymond
Rainstorm,
They are always being used. You can use them if you are careful but in general, I would not if I were you. There really is no need and the usage of those two registers are really not best for a beginner.
Paul
Rainstorm,
ESP and EBP are used in standard MASM procedures so if you need or want to learn how they work the trick is to start writing procedures that do not use a stack frame where you control the use of the regsters directly. The gains in not using a stack frame is an extra register in EBP to use in code and slightly lower overhead in the call/ret of a procedure but this only matters if the procedure is very short.
If you like to live dangerously you can also use ESP in some instances but you must know what you are doing to try stuff like this.
Hi.
thanks all,........for the replies.
raymond wrote..
QuoteHowever, toying with the ESP register is a totaly different animal. In theory, you could save it in memory and restore it later.
so if i want to save ESP or EBP, I cannot use push/pop to do it,..I have to save it to a variable, is that right ? - Also don't know exactly what you mean by ..'write it as a subroutine'
QuoteESP and EBP are used in standard MASM procedures so if you need or want to learn how they work the trick is to start writing procedures that do not use a stack frame where you control the use of the regsters directly.
Is there a good example of this ? that shows the general method involved & what I have to look out for ? - writing such a procedure..that is.
i guess it would be worth being able to have EBP available & learn how to at least have EBP free if need be.
have one more Question
when i do something like
movzx ebx, byte ptr [esi+1] is it faster than something like
movzx ebx, byte ptr [variable+1] (where esi is the address of the 'variable') - though the first one uses a register it stll is accesing the same memory - right ?
So if the 1st instruction is faster, then why so ?
This is an example of some code that i just tried, using EBP & it assembles proper & seems to run as expected. Also would be interested in any comments on the code. basically it just does a case insensitive comparison & it doesn't call any procs.
.data
stringd db ": where the Stars shine;..all night",0
strings db "stars",0
total_matches dd 0
ch_tbl \
db 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15
db 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31
db 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47
db 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63
db 64, 97, 98, 99,100,101,102,103,104,105,106,107,108,109,110,111
db 112,113,114,115,116,117,118,119,120,121,122, 91, 92, 93, 94, 95
db 96, 97, 98, 99,100,101,102,103,104,105,106,107,108,109,110,111
db 112,113,114,115,116,117,118,119,120,121,122,123,124,125,126,127
db 128,129,130,131,132,133,134,135,136,137,138,139,140,141,142,143
db 144,145,146,147,148,149,150,151,152,153,154,155,156,156,158,159
db 160,161,162,163,164,165,166,167,168,169,170,171,172,173,173,175
db 176,177,178,179,180,181,182,183,184,185,186,187,188,189,190,191
db 192,193,194,195,196,197,198,199,200,201,202,203,204,205,206,207
db 208,209,210,211,212,213,214,215,216,217,218,219,220,221,222,223
db 224,225,226,227,228,229,230,231,232,233,234,235,236,237,238,239
db 240,241,242,243,244,245,246,247,248,249,250,251,252,253,254,255
; -------------------------------------------------------------------------------------------
.code
start:
mov esi, offset stringd ; address of data string in esi
sub esi, 1
colon_check:
add esi, 1 ; move data pointer forward
cmp byte ptr [esi], 58 ; check for ':' in data string
jne colon_check
space_check:
add esi, 1
cmp byte ptr [esi], 32 ; check for space in data string
jne space_check
mov edx, offset ch_tbl
reset_:
mov edi, offset strings ; (resets search string to start)
search_:
add esi, 1
cmp byte ptr [esi], 0 ; check for 0 terminator in data string
je exit_
movzx ebx, byte ptr [edi] ; move search byte into ebx
movzx eax, byte ptr [esi] ; mov data byte into eax
movzx ecx, byte ptr [edx+ebx] ; replace by matching index in table (makes lower case)
cmp cl, byte ptr [edx+eax] ; check if data & search chars match
jne search_
xor ecx, ecx
sub ecx,1
match_:
add ecx, 1
cmp byte ptr [edi+ecx], 0 ; check for 0 terminator in search string
je match_found
movzx ebx, byte ptr [edi+ecx]
movzx ebp, byte ptr [esi+ecx]
movzx eax, byte ptr [edx+ebx]
cmp al, byte ptr [edx+ebp] ;compare chars from corresponding index in the table
je match_
jmp reset_
match_found:
add total_matches, 1
jmp reset_ ; jmp back, reset the search index
exit_:
print "Total Matches --- "
print ustr$(total_matches),13,10
inkey
exit
end start
Quoteso if i want to save ESP or EBP, I cannot use push/pop to do it,..I have to save it to a variable, is that right ?
That is right for ESP. It would also be right for EBP after you use ESP for something else. However, you could use the stack for saving EBP
before you use ESP for something else or
don't use ESP at all for something else.
QuoteAlso don't know exactly what you mean by ..'write it as a subroutine'
Here's an example where I would qualify my dw2a code as a subroutine (no proc, no stack frame, no passed parameters, no local variables, no endp):
.data
textbuf db 24 dup(?)
.code
dw2a:
mov ecx,10
pushd 0 ;for later use as terminating 0
@@:
xor edx,edx
div ecx
add dl,"0" ;convert to ascii
push edx ;save each digit on stack
.if eax != 0
jmp @B ;continue conversion
.endif
push edi
lea edi,textbuf
@@:
pop eax ;retrieve each ascii character from the stack
stosb
or al,al
jnz @B ;continue until terminating 0 is retrieved
pop edi
ret
start:
mov eax,4321
mul eax
call dw2a ;go convert the content of EAX to ascii for display
invoke MessageBox,0,ADDR buffer,0,MB_OK
invoke ExitProcess,0
end start
Raymond
You can free register EDX and use it about "add total_matches" replacement too... :lol
; mov edx, offset ch_tbl
reset_:
mov edi, offset strings ; (resets search string to start)
search_:
add esi, 1
cmp byte ptr [esi], 0 ; check for 0 terminator in data string
je exit_
movzx ebx, byte ptr [edi] ; move search byte into ebx
movzx eax, byte ptr [esi] ; mov data byte into eax
; movzx ecx, byte ptr [edx+ebx] ; replace by matching index in table (makes lower case)
; cmp cl, byte ptr [edx+eax] ; check if data & search chars match
movzx ecx, byte ptr [ch_tbl+ebx] ; replace by matching index in table (makes lower case)
cmp cl, byte ptr [ch_tbl+eax] ; check if data & search chars match
jne search_
;xor ecx, ecx
;sub ecx,1
or ecx, -1
match_:
add ecx, 1
cmp byte ptr [edi+ecx], 0 ; check for 0 terminator in search string
je match_found
movzx ebx, byte ptr [edi+ecx]
movzx ebp, byte ptr [esi+ecx]
; movzx eax, byte ptr [edx+ebx]
; cmp al, byte ptr [edx+ebp] ;compare chars from corresponding index in the table
movzx eax, byte ptr [ch_tbl+ebx]
cmp al, byte ptr [ch_tbl+ebp] ;compare chars from corresponding index in the table
je match_
jmp reset_
match_found:
add total_matches, 1
You can see how Hutch used ESP register too:
; «««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««
OPTION PROLOGUE:NONE
OPTION EPILOGUE:NONE
align 16
finstr proc spos:DWORD,psrc:DWORD,patn:DWORD
; ÷-÷-÷-÷-÷-÷-÷-÷-÷-÷-÷-÷-÷-÷-÷-÷-÷-÷-÷-÷-÷-÷-÷-÷-÷-÷-÷
; simplified faster InString algorithm.
; ÷-÷-÷-÷-÷-÷-÷-÷-÷-÷-÷-÷-÷-÷-÷-÷-÷-÷-÷-÷-÷-÷-÷-÷-÷-÷-÷
; arguments
; ---------
; 1 spos = 1 based index start offset in source address
; 2 psrc = the source address to be searched
; 3 patn = the address of the pattern to be searched for
; ÷-÷-÷-÷-÷-÷-÷-÷-÷-÷-÷-÷-÷-÷-÷-÷-÷-÷-÷-÷-÷-÷-÷-÷-÷-÷-÷
; return values
; -------------
; on match, 1 based index offset from start of source.
; no match returns zero.
; ÷-÷-÷-÷-÷-÷-÷-÷-÷-÷-÷-÷-÷-÷-÷-÷-÷-÷-÷-÷-÷-÷-÷-÷-÷-÷-÷
; NOTE: The "spos" can be set past the end of the
; source buffer which will generate an unhandled
; exception. To address this potential problem with
; sequential searches, the return value should be
; incremented by ONE BYTE at a time which ensures
; that the starting position "spos" never goes past
; the end of the source buffer.
; ÷-÷-÷-÷-÷-÷-÷-÷-÷-÷-÷-÷-÷-÷-÷-÷-÷-÷-÷-÷-÷-÷-÷-÷-÷-÷-÷
.data?
align 16
_ebx dd ?
_esi dd ?
_edi dd ?
_ebp dd ?
_esp dd ?
.code
mov _ebx, ebx
mov _esi, esi
mov _edi, edi
mov _ebp, ebp
mov _esp, esp
mov edi, [esp+12] ; patn
movzx eax, BYTE PTR [edi] ; set 1st patn byte in AL
mov esi, [esp+8] ; psrc
;;; add esi, [esp+4] ; spos
sub esi, 4 ; correct for 1 to 0 based index
; and dec by 1 for following loop
mov esp, 80808080h
mov ebp, 1
; ÷-÷-÷-÷-÷-÷-÷-÷-÷-÷-÷-÷-÷-÷-÷-÷-÷-÷-÷-÷-÷
scanloop:
REPEAT 16
; ----------------------------------------------
; test if the next 4 bytes contain an ascii zero
; ----------------------------------------------
add esi, 4
mov edx, [esi]
lea ecx, [edx-01010101h]
not edx
and ecx, esp ; modify ECX
test ecx, edx ; test with no modify
jnz no_match
; ----------------------------------------
; test each byte for a patn 1st char match
; ----------------------------------------
movzx ebx, BYTE PTR [esi]
sub ebx, eax
jz si0
movzx ecx, BYTE PTR [esi+1]
sub ecx, eax
jz si1
movzx edx, BYTE PTR [esi+2]
sub edx, eax
jz si2
movzx ecx, BYTE PTR [esi+3]
sub ecx, eax
jz si3
ENDM
jmp scanloop
; ÷-÷-÷-÷-÷-÷-÷-÷-÷-÷-÷-÷-÷-÷-÷-÷-÷-÷-÷-÷-÷
si3:
add esi, ebp
si2:
add esi, ebp
si1:
add esi, ebp
si0: ; set index
xor ebx, ebx
; ÷-÷-÷-÷-÷-÷-÷-÷-÷-÷-÷-÷-÷-÷-÷-÷-÷-÷-÷-÷-÷
matchloop:
REPEAT 8
add ebx, ebp
movzx edx, BYTE PTR [edi+ebx]
test edx, edx ; test for patn terminator
jz match ; text match if patn terminator found
movzx ecx, BYTE PTR [esi+ebx]
cmp ecx, edx ; test for match
jne scanloop
add ebx, ebp
movzx edx, BYTE PTR [edi+ebx]
test edx, edx ; test for patn terminator
jz match ; text match if patn terminator found
movzx ecx, BYTE PTR [esi+ebx]
cmp ecx, edx ; test for match
jne scanloop
ENDM
jmp matchloop
; ÷-÷-÷-÷-÷-÷-÷-÷-÷-÷-÷-÷-÷-÷-÷-÷-÷-÷-÷-÷-÷
match:
mov esp, _esp
sub esi, [esp+8] ; psrc
; lea eax, [esi+1]
mov eax, esi
jmp cleanup
no_match:
mov esp, _esp
xor eax, eax
cleanup:
mov ebx, _ebx
mov esi, _esi
mov edi, _edi
mov ebp, _ebp
ret 12
finstr endp
OPTION PROLOGUE:PrologueDef
OPTION EPILOGUE:EpilogueDef
Funny e.. :lol
never chang ebp,which stores the former address of esp,in a subfunction, if you forget to restore it, your program will crash immediately,but your can chang the esp as you like to,and you neednt have to restore it ,which is restored automaticly in LEAVE,
on the whole,ebp is used for local variables in subroutines,and esp ,beside its pop and push uses ,it is alse used to get the variable which are pushed into the stack and passed to subfunctions
Crosscross,
Most of us know the deal with writing stack frame free procedures and exactly the reason why you do so is to get the extra register EBP but it means you must know what you are doing with the stack pointer ESP as well. Tracking the current location of ESP takes a little practice when you use push or pop but its not that hard to do with practice. With a "leaf" procedure you can in fact write locals to ESP below the current location [esp-8] etc ... but it becomes increasingly more complicated if you try to nest prcedures written in this manner.
Hi
Raymond, thanks for the info & the example, they were very helpful.
lingo,
QuoteYou can free register EDX and use it about "add total_matches" replacement too...
actually had a question related to that in my earlier post
Quotewhen i do something like movzx ebx, byte ptr [esi+1] is it faster than something like
movzx ebx, byte ptr [variable+1] ? (where esi is the address of the 'variable') - though the first one uses a register it stll is accesing the same memory - right ?
So if the 1st instruction is faster, then why so ?
thank-you,.all.
-
Quote
when i do something like movzx ebx, byte ptr [esi+1] is it faster than something like
movzx ebx, byte ptr [variable+1] ? (where esi is the address of the 'variable') - though the first one uses a register it stll is accesing the same memory - right ?
So if the 1st instruction is faster, then why so
Tradeoffs:
movzx ebx, byte ptr [esi+1] is tying up an extra register esi
-versus-
movzx ebx, byte ptr [variable+1] which doesn't affect esi
movzx ebx, byte ptr [esi+1] can point to any memory
-versus-
movzx ebx, byte ptr [variable+1] is used with memory declared in .data or .data?
Haven't done any tests to determine which is faster.
Things that can be determined:
Both will have to access the byte at the effective address
At some point the esi version had to load esi with the offset of the variable, so an extra instruction
The esi version is shorter (by 3 bytes), which may allow more instructions to fit in a 16 byte area
The variable version already has the effective address encoded in the instruction
The esi version does the addition at run time and then is used to generate an effective address
The variable version already has the addition to the address done at assembly time
Dsouza,
that info gave me a much better perspective, appreciate the feedback.
would be interested to know which is quicker though.
Also, it seems that, movzx ebx, byte ptr [variable+1] doesn't need the 'byte ptr' part (when the varaiable name is used unlike a reg. like esi) & can be written as movzx ebx, [variable+1] probably because the declaration of the size of the variable in the .Data section, hints to the assembler that a byte should be moved into ebx. - correct me if am wrong
Quotewhen i do something like movzx ebx, byte ptr [esi+1] is it faster than something like
movzx ebx, byte ptr [variable+1]
Both forms should generally execute in about the same number of cycles. AFAIK the effective address calculations affected the cycle counts only for the very early processors.
Quote
Also, it seems that, movzx ebx, byte ptr [variable+1] doesn't need the 'byte ptr' part (when the varaiable name is used unlike a reg. like esi) & can be written as movzx ebx, [variable+1] probably because the declaration of the size of the variable in the .Data section, hints to the assembler that a byte should be moved into ebx.
Not needing byte ptr is only if the data size of variable matches the size of data accessed.
It does no harm including it, and it makes the intended data size wanted perfectly clear.
Bonus, if you switch code to the esi version it will already be setup.
.data
variableb db 1,2,3,4
variablew dw 1,2,3,4
variabled dd 1,2,3,4
.code
start:
movzx ebx, byte ptr [variableb + 1]
movzx ebx, word ptr [variableb + 1] ; need word ptr
nop
movzx ebx, byte ptr [variablew + 1] ; need byte ptr
movzx ebx, word ptr [variablew + 1]
nop
movzx ebx, byte ptr [variabled + 1] ; need byte ptr
movzx ebx, word ptr [variabled + 1] ; need word ptr
nop
Raymond,
There is a problem with your example. The first pop into eax will be the value that was in edi and not one of the characters you pushed using edx.
Do this instead:
.data
textbuf db 24 dup(?)
.code
dw2a:
push edi
mov ecx,10
pushd 0 ;for later use as terminating 0
@@:
xor edx,edx
div ecx
add dl,"0" ;convert to ascii
push edx ;save each digit on stack
.if eax != 0
jmp @B ;continue conversion
.endif
lea edi,textbuf
@@:
pop eax ;retrieve each ascii character from the stack
stosb
or al,al
jnz @B ;continue until terminating 0 is retrieved
pop edi
ret
start:
mov eax,4321
mul eax
call dw2a ;go convert the content of EAX to ascii for display
invoke MessageBox,0,ADDR buffer,0,MB_OK
invoke ExitProcess,0
end start
Paul
Thanks for the correction Paul. That's what I had initialy but, for some stupid unknown reason, I shifted the "push edi" to the wrong place when I posted the code. :red
Raymond
MichaelW wrote..
QuoteBoth forms should generally execute in about the same number of cycles. AFAIK the effective address calculations affected the cycle counts only for the very early processors
Thanks for the confirmation. - mostly all the code i've seen usually uses the registers, so I got the impression that, that usage was mainly because of a speed factor, although..I didn't understand why it would be so.