Newsgroups : Borland : borland.public.delphi.rtl.win32 : 2006 Jul : Bug in DLL ExitProc?

www.cryer.info
Managed Newsgroup Archive

Bug in DLL ExitProc?

Subject:Bug in DLL ExitProc?
Posted by:"William Egge" (beg..@eggcentric.com)
Date:Sun, 30 Jul 2006 15:18:00

I have found that the ExitProc's are not getting called when a Delphi DLL is
unloaded.  Below is my full analysis:


Here is my short library code, The DoExit is not getting called:

============================
library Project2;

uses
  SysUtils,
  Classes, Windows, Dialogs;

{$R *.res}

var
  SaveExit: Pointer;

procedure SayHi;
begin
  ShowMessage('Hi xxx');
end;

exports
  SayHi;

procedure DoExit; far;
begin
  ExitProc:= SaveExit;
  MessageBeep(0);
end;

begin
  ShowMessage('Starting');
  SaveExit:= ExitProc;
  ExitProc:= @DoExit;
end.

=========================

Here is the code which loads and unloads the DLL

====================
procedure TForm1.cxButton1Click(Sender: TObject);
begin
  H:= LoadLibrary('Project2.dll');
end;

procedure TForm1.cxButton2Click(Sender: TObject);
var
  P: TProc;
begin
  //P:= GetProcAddress(H, 'SayHi');
  //P;
  FreeLibrary(H);
end;
====================


I have looked into Delphi's code for calling the exit procs and this is where I
found that they are called:

Unit System.pas
=======================
procedure _Halt0;
var
  P: procedure;
begin
{$IFDEF LINUX}
  if (ExitCode <> 0) and CoreDumpEnabled then
    __raise(SIGABRT);
{$ENDIF}

  if InitContext.DLLInitState = 0 then
    while ExitProc <> nil do
    begin
      @P := ExitProc;
      ExitProc := nil;
      P;
    end;

... snip

=====================


So, DLLInitState must be 0 in order for the exit procs to be called - but when a
DLL is shutting down, DLLInitState = 1


I traced the code to see where DLLInitState was getting set and found it came
from here:
(Look for " ***** RIGHT HERE ****** " about  midway into the routine)

System.pas
==========================
procedure       _StartLib;
asm
        { ->    EAX InitTable   }
        {       EDX Module      }
        {       ECX InitTLS     }
        {       [ESP+4] DllProc }
        {       [EBP+8] HInst   }
        {       [EBP+12] Reason }

        { Push some desperately needed registers }

        PUSH    ECX
        PUSH    ESI
        PUSH    EDI

        { Save the current init context into the stackframe of our caller }

        MOV     ESI,offset InitContext
        LEA     EDI,[EBP- (type TExcFrame) - (type TInitContext)]
        MOV     ECX,(type TInitContext)/4
        REP     MOVSD

        { Setup the current InitContext }

        POP     InitContext.DLLSaveEDI
        POP     InitContext.DLLSaveESI
        MOV     InitContext.DLLSaveEBP,EBP
        MOV     InitContext.DLLSaveEBX,EBX
        MOV     InitContext.InitTable,EAX
        MOV     InitContext.Module,EDX
        LEA     ECX,[EBP- (type TExcFrame) - (type TInitContext)]
        MOV     InitContext.OuterContext,ECX
        XOR     ECX,ECX
        CMP     dword ptr [EBP+12],0
        JNE     @@notShutDown
        MOV     ECX,[EAX].PackageInfoTable.UnitCount
@@notShutDown:
        MOV     InitContext.InitCount,ECX

        MOV     EAX, offset RaiseException
        MOV     RaiseExceptionProc, EAX
        MOV     EAX, offset RTLUnwind
        MOV     RTLUnwindProc, EAX

        CALL    SetExceptionHandler

        MOV     EAX,[EBP+12]
        INC     EAX
        MOV     InitContext.DLLInitState,AL // ***** RIGHT HERE ******
        DEC     EAX

        { Init any needed TLS }

        POP     ECX
        MOV     EDX,[ECX]
        MOV     InitContext.ExitProcessTLS,EDX
        JE      @@skipTLSproc
        CMP     AL,3              // DLL_THREAD_DETACH
        JGE     @@skipTLSproc     // call ExitThreadTLS proc after DLLProc
        CALL    dword ptr [ECX+EAX*4]
@@skipTLSproc:

        { Call any DllProc }

        PUSH    ECX
        MOV     ECX,[ESP+8]
        TEST    ECX,ECX
        JE      @@noDllProc
        MOV     EAX,[EBP+12]
        MOV     EDX,[EBP+16]
        CALL    ECX
@@noDllProc:

        POP     ECX
        MOV     EAX, [EBP+12]
        CMP     AL,3                  // DLL_THREAD_DETACH
        JL      @@afterDLLproc        // don't free TLS on process shutdown
        CALL    dword ptr [ECX+EAX*4]

@@afterDLLProc:

        { Set IsLibrary if there was no exe yet }

        CMP     MainInstance,0
        JNE     @@haveExe
        MOV     IsLibrary,1
        FNSTCW  Default8087CW   // save host exe's FPU preferences

@@haveExe:

        MOV     EAX,[EBP+12]
        DEC     EAX
        JNE     _Halt0
        CALL    InitUnits
        RET     4
end;
===================================


Note that "Reason [EBP+12]" is being incremented before assignment.  Why? I have
no clue.

Recap:  [EBP+12] is 0, and then _StartLib assigns DLLInitState from [EBP+12] and
adds 1, DLLInitState = 1 and then _Halt0 is called and checks if DLLInitState=0,
but it is 1 and so the exit procs are not called.

Windows API for FreeLibrary:
---------------------
Before unmapping a library module, the system enables the DLL to detach from the
process by calling the DLL's DllMain function, if it has one, with the
DLL_PROCESS_DETACH value
----------------------

Note: DLL_PROCESS_DETACH = 0

On more tracing I found that _StartLib is called by _InitLib and..... note the
entry params on _InitLib


SysInit.pas
============

procedure _InitLib;
asm
        { ->    EAX Inittable   }
        {       [EBP+8] Hinst   }
        {       [EBP+12] Reason }
        {       [EBP+16] Resvd  }

        MOV     EDX,offset Module
        CMP     dword ptr [EBP+12],DLL_PROCESS_ATTACH
        JNE     @@notInit

        PUSH    EAX
        PUSH    EDX
        MOV     ModuleIsLib,1
        MOV     ECX,[EBP+8]
        MOV     HInstance,ECX
        MOV     [EDX].TLibModule.Instance,ECX
        MOV     [EDX].TLibModule.CodeInstance,0
        MOV     [EDX].TLibModule.DataInstance,0
        CALL    InitializeModule
        POP     EDX
        POP     EAX

@@notInit:
        PUSH    DllProc
        MOV     ECX,offset TlsProc
        CALL    _StartLib
end;

===================


So, [EBP+12] is the reason. Note the windows entry for DllMain is this:

BOOL WINAPI DllMain(
  HINSTANCE hinstDLL,
  DWORD fdwReason,
  LPVOID lpvReserved
);Reason is one of the following:const  DLL_PROCESS_DETACH = 0;
DLL_PROCESS_ATTACH = 1;  DLL_THREAD_ATTACH  = 2;  DLL_THREAD_DETACH  = 3;
Basically identical to _InitLib.  I cannot find any further code that calls
_InitLib so I am going to assume that somehow _InitLib IS DllMain.


Recap: [EBP+12] is the reason, [EBP+12] is 0 when the DLL is shutting down which
is DLL_PROCESS_DETACH.


So, since the exit procs are only called when DLLInitState = 0 it seams that it
is expecting DLLInitState to be the Reason and so is looking for
DLL_PROCESS_DETACH.


It seems like it is a bug to increment the reason before assigning it to
DLLInitState.  Am I correct?  Do you see anything wrong with modifing the Delphi
code and remove the increment?


-Bill

Replies:

www.cryer.info
Managed Newsgroup Archive