After tracing through the RTL I found on a Process DLL startup the
following happens during the DLLMain call:
function LoadResourceModule(ModuleName: PChar): LongWord;
var
FileName: array[0..260] of Char;
Key: LongWord;
LocaleName, LocaleOverride: array[0..4] of Char;
Size: Integer;
P: PChar;
{ Overhead Code Omitted........... }
begin
{ Get the calling applications name }
GetModuleFileName(0, FileName, SizeOf(FileName)); // Get host
appliation name
{ Look in the Registry for Locale information for the HOST application
}
{ Overhead Code Omitted........... }
if LocaleOverride[0] <> #0 then
Result := LoadLibraryEx(FileName, 0, LOAD_LIBRARY_AS_DATAFILE);
// Then look for a potential language/country translation
Result := LoadLibraryEx(FileName, 0, LOAD_LIBRARY_AS_DATAFILE);
if Result = 0 then
begin
// Finally look for a language only translation
Result := LoadLibraryEx(FileName, 0,
LOAD_LIBRARY_AS_DATAFILE);
{ Overhead Code Omitted........... }
And during a PROCESS or THREAD DETACH
{ Overhead Code Omitted........... }
UnregisterModule(Module);
if Module.ResInstance <> Module.Instance then
FreeLibrary(Module.ResInstance);
So in the entry code the DLL is looking for various user and/or system
defined locale information and loaded or freed the required DLLs if
necessary.
In my little test case the last 2 LoadLibraryEx were called but the
files were not found so no other libraries were loaded.
Now a little searching around on MSDN turned up this tidbit, and I quote
(except for the capital letters)
"WARNING: On attach, the body of your DLL entry-point function should
perform only simple initialization tasks, such as setting up thread
local storage (TLS), creating objects, and opening files. YOUR MUST NOT
CALL LOADLIBRARY IN THE ENTRY-POINT FUNCTION, because you may create
dependency loops in the DLL load order. This can result in a DLL being
used before the system has executed its initialization code. Similarly,
you must not call the FreeLibrary function in the entry-point function
on detach, because this can result in a DLL being used after the system
has executed its termination code.
Calling Win32 functions other than TLS, object-creation, and file
functions may result in problems that are difficult to diagnose. For
example, calling User, Shell, COM, RPC, and Windows Sockets functions
(or any functions that call these functions) can cause access violation
errors, because their DLLs call LoadLibrary to load other system
components. While it is acceptable to create synchronization objects in
DllMain, you should not perform synchronization in DllMain (or a
function called by DllMain) because all calls to DllMain are serialized.
Waiting on synchronization objects in DllMain can cause a deadlock.
To provide more complex initialization, create an initialization routine
for the DLL. You can require applications to call the initialization
routine before calling any other routines in the DLL. Otherwise, you can
have the initialization routine create a named mutex, and have each
routine in the DLL call the initialization routine if the mutex does not
exist. "
This fits the randomness of the DLL crashing problem to a tee.
I contend that especially in networked environments that the possibility
of Explorer (or other programs) have a higher chance of needing to load
international or user specified locale. And if Explorer launches a new
process which requires a locale library to be loaded and then
immediately launchs a new thread for that process that a potential race
condition can be created if the system serializes the DLL like this:
NewProcess (Which loads the Delphi Hook which in turn loads a local
dll in it entry code)
NewThread ( Which loads the Delphi Hook thinks the LocalDLL is
loaded)
LocalDLL
And the NewThread tries to access something in the LocalDLL before the
LocalDLL initialization is called, CRASH!
During the PROCESS shut down the same may be true, there is a lot of
tests before calling FreeLibray and I have not looked at it close
enough.
By adding DisableThreadLibraryCalls the race condition is impossible.
Any comments? I admit I don't understand the Locale aspect of this very
well. One thing I am not sure of is the LoadLibrary calls do use the
LOAD_LIBRARY_AS_DATAFILE flag, but they do still need to initialize
themselves correct?
Jim Kueneman
But these libraries are loaded with LOAD_LIBRARY_AS_DATAFILE parameter which
tells to not resolve imported functions etc, it just map the DLL into process
address space and uses as a data file.
I think that problem with stability of Delphi hook DLLs is somewhere else. At
first, you have to use Delphi 5 compiler, you need to create shared data area
for hook handle and you shouldn't use any threadvar in the DLL code.
--
Petr Vones (Team JEDI) - http://delphi-jedi.org
"Petr Vones (Team JEDI)" wrote:
>
> > "WARNING: On attach, the body of your DLL entry-point function should
> > perform only simple initialization tasks, such as setting up thread
> I think that problem with stability of Delphi hook DLLs is somewhere else. At
> first, you have to use Delphi 5 compiler, you need to create shared data area
> for hook handle and you shouldn't use any threadvar in the DLL code.
>
Wrong! I dug out an old simple CBT hook (using memory mapped files)
that got this whole saga started months ago. I could make it crash
explorer EVERY time using dialup networking and "logging into network"
option. I tried it again and within seconds it crashed Explorer
(actutally a system dll) just as I remembered. Add the
DisableThreadLibraryCalls an it would not crash. To test my theory I
pulled out TASM 5.0 and modifed the SysInit.pas file:
WAS:
procedure InitializeModule;
var
FileName: array[0..260] of Char;
begin
GetModuleFileName(HInstance, FileName, SizeOf(FileName));
{ This call reads the registry and calls LoadLibraryEx which }
{ M$ says is a no no in the DLLInit function. }
{ It loads any local language DLL other than the default assigned }
{ to the thread. }
Module.ResInstance := LoadResourceModule(FileName);
if Module.ResInstance = 0 then
Module.ResInstance := Module.Instance;
RegisterModule(@Module);
end;
IS:
procedure InitializeModule;
var
FileName: array[0..260] of Char;
begin
{ GetModuleFileName(HInstance, FileName, SizeOf(FileName));
Module.ResInstance := LoadResourceModule(FileName);
if Module.ResInstance = 0 then
}
{ Just use the Language assigned by the system.}
Module.ResInstance := Module.Instance;
RegisterModule(@Module);
end;
I recompiled the SysInit.dcu and recompiled the CBT hook. Loaded up
Win98 logged in an tried and tried to make it crash. It worked
PERFECTLY!!!!!!
I need to go in and piece out if it is the Registry access or the
LoadLibraryEx calls but either way I think I have found a major problem
with the Hook DLL RTL.
Jim Kueneman
> I think that problem with stability of Delphi hook DLLs is somewhere else. At
> first, you have to use Delphi 5 compiler, you need to create shared data area
> for hook handle and you shouldn't use any threadvar in the DLL code.
My hook DLL uses neither of the above. It stores the hook in the
registry and it does not do anything but sending a message to my
program.
--
Robert Marquardt (Team JEDI) http://delphi-jedi.org
Yup, good catch ! Although both (registry access and LoadLibraryEx) aren't
explicitly disallowed there are known problems with them in DLL start up
code. Microsoft is not able to describe what is exactly safe to call in the
DLLEntryPoint for ages.
"Petr Vones (Team JEDI)" wrote:
Petr,
This ended up only make the hook harder to crash, not impossible. I
have spent a lot of time stepping through the RTL and running test
programs and have come to the following conclusions.
1) I know every line of the Library RTL code by heart!
2) I understand the Windows Exception mechinism more than I ever wanted
to know.
3) Delphi does WAY to much in response to a DLLEntry call. A lot
against Microsofts recomendations. If any call can in some way require
another DLL to load DON'T call it.
4) The Initialization/Finalization sections of all units in the library
USES section are called in response to the Library entry Attach and
Detach calls. So don't add any code there that violates 3.
5) By not using SysUtils in the uses clause the hook is thousands of
times more stable than with it included. The fundamental reason from
what I can tell is that the Delphi compiler creates a table of indexes
for resources (including strings). Any unit that uses resource strings,
in what way I am not exactly sure, the compiler generates code that
calls two function when the unit is initialized (again during a process
or thread attach). One reads this compiler generated table the other
calls this function in the system.pas
function LoadResString(ResStringRec: PResStringRec): string;
for each resource which loads the string resources. I tried removing
all the initialization / finalization code from the SysUtils.dcu so all
that was left was the above two calls which are compiler generated. It
still was easily crashable. The only way to get the extra stabilty was
to remove SysUtils from USES section. Note here during this test I am
not using ANYTHING from the unit, it is just in the USES section. So I
can only conclude that it has something to do with loading these
resources. Also reading these strings requires memory allocation which
is wrapped in CriticalSection if IsMultiThread is true, BUT during
initialization your code to set IsMultiThread to true has not run so
during this initialization the DLL is NOT thread safe! I though I had
the solution so I changed the Entry code so the first thing it did was
set IsMultiThread to true. It still crashed.
6) At times Win98 does NOT unload the DLL from the address space of some
processes when the hook is removed. So be real careful how you implement
the hook as the global info the hook is using can have been destroyed
when the hook is removed. Case in point is tapisrv.exe when connected to
you ISP, (I know this thanks to Petr free tools!)
7) There is just too much happening during the loading of the DLL to
initialize the inner working of Delphi.
8) The only thing I can see to do is to have a SysInit.dcu that is a raw
DLLEntry point and the DLL must be written at the lowest windows API
level possible, ie only units that contain type definitions can be used
so only SysInit, System, Windows, and Messages can be used. A lot of
the things that Delphi does automaticlly will be lost. This will be real
confusing for the average Delphi user and will take a lot of knowledge
of the working of Delphi to know if what you are doing is going to work
if the entry code is not executing a lot of the normal startup
routines.
9) Keep using DisableThreadLibraryCalls to create *almost* stable hook
DLLs that work with the exception with a few programs.
Jim Kueneman