Newsgroups : Borland : borland.public.delphi.rtl.win32 : 2006 Jul : 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