News:

MASM32 SDK Description, downloads and other helpful links
MASM32.com New Forum Link
masmforum WebSite

Windows API does not preserve esi, edi, ebx

Started by jj2007, October 02, 2008, 11:06:17 AM

Previous topic - Next topic

jj2007

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

zooba

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


jj2007

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.

u

@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.
Please use a smaller graphic in your signature.