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

Main Menu

Preservation of esi, edi, ebx in general code

Started by satpro, June 09, 2012, 02:20:00 AM

Previous topic - Next topic


Hi all,

Could someone explain if (and why) preserving ESI, EDI, and EBX is necessary--if they are used--in ordinary procedures (excluding WndProc)?  My understanding is that Windows preserves them, so we should, too?  I'm not sure.  What does Windows do with them--exactly?  When I read text about it I only get more confused.  I've tried it both ways with no adverse effects--YET.  Haven't won the lottery, either... helps, though, if you play.

From Kip R. Irvine:

"Always save and restore registers that are modified by a procedure so the calling program can be
sure that none of its own register values will be overwritten. The exception to this rule pertains to
registers used as return values, usually EAX. Do not push and pop them."

I get the eax sentence--that makes sense.  The rest really doesn't.  It sounds like fear-mongering.  In your own code, let's assume you know what's going on  ::) .  I'm guessing you really don't have to preserve those three in ordinary code, and I'd rather not if I don't have to, but everyone always does.  They aren't holding some value that can't be changed, are they?

Just being "safe" and saving registers unnecessarily doesn't really seem efficient or logical, unless there is good reason (such as preserving across a call).  It seems like C/C++.



Kip may be refering to "old-style" DOS code - check the date of the book

for windows programming, we tend to follow the ABI
in functions that may be used in call-back (as in WndProc, etc), preserve EBX, ESI, EDI, and EBP
also - the direction flag should be cleared whenever calling an API function
typically, if we set it (STD), we clear it (CLD) when done
we also generally assume it to be cleared when we start a function
EAX, ECX, EDX are considered fair game to be overwritten
EAX is quite often used to return a result or status

windows API functions follow those same rules

while not all procedures are used in call-back situations, it is considered good practice to write functions so that they may be used that way

i write code in a variety of ways, which may be a little confusing - lol
but - some functions i may write "know" the preservation rules are followed by the calling operation

WinMain is not a call-back function, so the register rules may be ignored (not the direction flag, though)
you can use that to your advantage, too
at the beginning of WinMain, i might zero the EDI register
i know it will be 0 all the way through to ExitProcess   :P


If others call your code they are going to expect it to conform to the ABI (Application Binary Interface) defined for the architecture.

What you do in your own code is up to you, you can use the registers to hold anything you want, and use different registers to pass parameters.

By pushing registers on the stack it makes upstream code development easier as you don't need to be painfully aware of what everything you call is doing, and the side effects different code flows many exhibit.
It's a pity the clowns in Washington DC don't understand the Constitution as well as Edward Snowden


Good question. Ask three members here, and get four different answers. Ask Microsoft, and get no clear answer.
The "preserve in callbacks" seems to be widely accepted, and "in console mode apps do what ever you want", too, but wait for more replies... have a look at The "register gets trashed" trap, too.

I tend to use
WndProc proc uses esi edi ebx hWnd:HWND, uMsg:UINT, wParam:WPARAM, lParam:LPARAM
It's pretty safe imho.


Thanks dedndave, clive, jj2007.

dave--6th edition--believe that?  You really have to sift his books (all editions) to get what you want sometimes.  But there's some good in there, too.
jj-That's pretty much what I do, too.

Thanks again, guys.  I feel better now.  It's been driving me nuts for a while.



Stick to the Intel ABI and you are safe. Windows complies with it and it means your code can live with Windows API functions. You preserve registers on the basis of needs, IF you modify EBX ESI or EDI within a procedure, restore them before you exit the procedure, if not then you don't bother as you arew only adding extra code that is not needed. Also remember the inverse, if you call other procedures, they can also modify EAX ECX and EDX so if you are using any of them and you make a procedure call, preserve and restore them as well.

Don't be lead astray here, if you do not write ABI complaint code it may crash on other Wndows versions.


I'm also a bit confused when it comes to register preservation, mainly because if Windows expects registers to be preserved, wouldn't you need to restore the registers before making an API call?  Right now I'm only doing a push/pop around my entire procedure or using the 'uses' keyword.  I know it hasn't been an issue in anything I've written in the last couple months that I've been working with assembly, but is it a potential issue?


YES !!!!

> Don't be lead astray here, if you do not write ABI complaint code it may crash on other Wndows versions.


as i mentioned earlier...
windows expects the registers to be preserved in functions used during call-back
i.e., WndProc should preserve whatever values the OS has in EBX, ESI, EDI, and EBP

this is not the same as calling an API function
they follow the same rules as we do   :P
if you put something in EBX, ESI, or EDI and call an API function, those registers will be preserved

most if us generally follow these rules when we write functions so that it may be used the same way
for example, nearly all, if not all, of the functions Hutch has written for the masm32 library preserve the registers
if you want to call one of the masm32 functions inside WndProc, you do not need to worry about EBX, ESI, EDI, or EBP


Quote from: hutch-- on June 09, 2012, 11:30:52 AM
Stick to the Intel ABI and you are safe.

Or test yourself:

WndProc proc hWnd:HWND, uMsg:UINT, wParam:WPARAM, lParam:LPARAM
LOCAL rc:RECT, whatever:DWORD
mov esi, 123
mov edi, 456
mov ebx, 789

Hutch, can you post a link to the page where Microsoft explains which kind of callback functions need preservation, and what to do for console mode apps? Thanks :icon14:



No, just look up Intel Application Binary Interface.


Quote from: hutch-- on June 09, 2012, 04:09:16 PM

No, just look up Intel Application Binary Interface.

What does Intel have to do with Microsoft???

Ok, let's launch a little challenge (source and executable attached):

ghWnd dd ?
guMsg dd ?
gwParam dd ?
glParam dd ?

WndProc proc a1, a2, a3, a4
m2m ghWnd, a1
m2m guMsg, a2
m2m gwParam, a3
m2m glParam, a4
mov ebp, 666   ; the number of the devil ######
mov esi, 123
mov edi, 456
mov ebx, 789

Crash it if you can :biggrin:

P.S.: And no, I do not recommend that, and I do not use that in any code of mine.



Q. What does Microsoft have to do with Intel ????
A. Not much but they use the Intel ABI since WinNT3.5.  :P


Unless you can point me to a page where an authoritative source (aka Microsoft) writes, in detail, what exactly Windows does, and why exactly it does NOT crash even if you trash the most precious registers like ebp in the most important callback (aka WndProc), it remains all hearsay and anecdotes of grumpy old men like you & me.

It is one of these grey areas where Microsoft maintains a tiny comparative advantage by not releasing its little secrets, that's all.

For the noobs here: Use ...
WndProc proc uses esi edi ebx hWin:DWORD, uMsg:DWORD, wParam :DWORD, lParam :DWORD
... otherwise Hutch & me & a few others will eat you alive :greensml:



This is actually BAD advice.

WndProc proc uses esi edi ebx hWin:DWORD, uMsg:DWORD, wParam :DWORD, lParam :DWORD
... otherwise Hutch & me & a few others will eat you alive

Taking the lazy way out by using "uses esi edi ebx" saddles your message processing with 6 extra operations (3 pushes and 3 pops) for every message processed, even when its not needed and it limits where you can exit the proc unless you want all of the extra  pops at every exit point. Far better architecture wise to preserve registers on the basis of need within each message processed then exit any way you like.