We all have learned that esi, edi and ebx are protected registers. I stumbled over an exception and thought I should share this experience.
First, I call a procedure that retrieves the RTF codes for the current selection in a RichEdit control.
mov edi, SelXXL$ ; pointer to an XXL$ for storing the selection
mov esi, edi ; for testing
mov ebx, edi ; only
call GetRtfStream ; the procedure
Inside this proc, the three registers are intact, of course. Now I pass a pointer to callback procedure and invoke SendMessage; the latter will use this pointer to invoke StreamToXXL:
GetRtfStream proc ; buffer is edi
LOCAL editstream:EDITSTREAM
mbDebug 1, "GetRtfStream:", edi, esi, ebx ; here, the registers are still intact
mov editstream.pfnCallback, StreamToXXL
invoke SendMessage, hEdit, EM_STREAMOUT, SF_RTF, addr editstream
ret
GetRtfStream endp
And, voilà, the ugly surprise in the callback: The three protected registers are no longer there...!
OPTION PROLOGUE:NONE
OPTION EPILOGUE:NONE
StreamToXXL proc cookie:DWORD,pBuffer:DWORD, NumBytes:DWORD, pBytesWritten:DWORD
.if edi!=SelXXL$
mbDebug 1, "StreamToXXL:", edi, esi, ebx ; all registers are gone!!!!
mov edi, SelXXL$ ; workaround: take a global pointer
.endif
invoke RtlMoveMemory, edi, [esp+12], [esp+12] ; dest, source, count
ret 4*4
StreamToXXL endp
OPTION PROLOGUE:PrologueDef
OPTION EPILOGUE:EpilogueDef
Immediately after the call, the regs show up again - which means RichEdit preserved them but not inside its "own" callback, i.e. StreamToXXL...
call GetRtfStream ; the procedure
mbDebug 1, "Call to GetRtfStream:", edi, esi, ebx, SelXXL$
; great, the regs are OK again
The register preservation contract does not extend to callbacks. It is guaranteed that those registers will have the same values after the function returns as they did immediately before calling the function. Within the function they take on a meaning defined by that function - it has no idea what they represented beforehand.
It would be completely unworkable to require that any general purpose register have the same value at every point in the callstack, and if it was required then you wouldn't have been able to have changed it in your code before calling the function.
It is, however, a good "gotcha" to note.
Cheers,
Zooba :U
http://www.masm32.com/board/index.php?topic=4205.msg41584#msg41584 :wink
Quote from: lingo on October 02, 2008, 02:49:33 PM
http://www.masm32.com/board/index.php?topic=4205.msg41584#msg41584 :wink
Thanks, Lingo. Since there is often only a tiny stub before reaching the GetMessage loop, this implies that in 99% of our code we must preserve these registers.
@jj2007, preserve the regs only in callback procs. And they are <1% of the code, not 99%. You misread lingo's post.
Simply put a "uses ebx esi edi" in every WndProc or callback, and you're ready. You can trash those registers everywhere; expect every API to trash eax ecx edx.
The reason to preserve the regs is that in Win2k the DispatchMessage and other API that call user-specified callbacks store some of their data in ebx/esi/edi. (i.e for a wee bit faster enumeration of messages). This only broke compatibility with apps that didn't conform to the Win32 ABI. The ABI dictates which registers a C/C++ compiler must preserve.
int SomeProc(int param1,int param2){
int local1,local2;
// preserve ebx,esi,edi
push(ebx,esi,edi);
// trash all registers
eax = param1+param2;
ecx = param1 & param2;
edx = param1 - param2;
ebx = param1 * param2;
esi = param1+7;
edi = param2+3;
// now, we'll be calling a proc, so eax,ecx,edx will be lost. Put their values into memory
local1 = eax;
local2 = ecx+edx;
SomeOtherProc(...); // notice that ebx,esi,edi are trashed with some computed values here, and after SomeOtherProc returns, the compiler expects ebx,esi,edi to not have changed their values
// expect eax,ecx,edx to have been garbled. Expect values of ebx,esi,edi to persist
// trash some more
eax = local1+local2;
ecx = local2;
edx = esi + 2;
eax = eax+ecx+edx+ebx+edi;
// restore ebx,esi,edi
pop(ebx,esi,edi);
}
So, trash-away freely and easily, as long as you mind the CALLBACK procs.