On Sunday 05 January 2025 13:46:28 LIU Hao wrote:
> 在 2025-01-05 01:46, Pali Rohár 写道:
> > But the mentioned comment applies also for the main application EXE
> > module? Or only for DLL libraries? Because I have feeling that only for
> > DLL libraries...
> 
> It's called both in 'dll_dllmain.cpp' and 'exe_common.inl'. The latter
> implements all the four variants of narrow/wide `main`/`WinMain` startup
> routines.

I tried to understand what is the logic behind and so I wrote my notes
with state logic what is happening in each phase for DLL and EXE:

Startup logic of DLL (DLL_PROCESS_ATTACH) is:
- call _pRawDllMain
  --> if failed then return failure to system
- wait for native_startup_lock
- check that native_startup_state is uninitialized
  --> if is not uninitialized then crash the whole application
- set native_startup_state to initializing
- do process initialization
  --> if failed then return failure to system
- set native_startup_state to initialized
- release native_startup_lock
- call __dyn_tls_init_callback with DLL_THREAD_ATTACH
- increase proc_attached
- call DllMain
  --> if failed then:
      - call Shutdown logic of DLL (DLL_PROCESS_DETACH)
      - return failure to system
- return success to system

Shutdown logic of DLL (DLL_PROCESS_DETACH) is:
- if proc_attached is less than one then return failure to system
- call DllMain and ignore failure
- decrease proc_attached
- wait for native_startup_lock
- check that native_startup_state is initialized
  --> if is not initialized then crash the whole application
- do process deinitialization
- set native_startup_state to uninitialized
- release native_startup_lock
- call _pRawDllMain
  --> if failed then return failure to system
- return success to system

New thread creation logic in DLL (DLL_THREAD_ATTACH) is:
- call _pRawDllMain
  --> if failed then return failure to system
- do thread initialization
  --> if failed then return failure to system
- call DllMain
  --> if failed then return failure to system
- return success to system

Thread exiting logic in DLL (DLL_THREAD_DETACH) is:
- call DllMain and ignore failure
- do thread deinitialization
  --> if failed then return failure to system
- call _pRawDllMain
  --> if failed then return failure to system
- return success to system

Startup logic of EXE is:
- set has_cctor to false
- wait for native_startup_lock
- check that native_startup_state is not initializing
  --> if is initializing then crash the whole application
- if native_startup_state is uninitialized
  - do process initialization
  - set native_startup_state to initialized
- else (if native_startup_state is initialized)
  - set has_cctor to true
- release native_startup_lock
- call __dyn_tls_init_callback with DLL_THREAD_ATTACH
- register __dyn_tls_dtor_callback into atexit hook
- invoke main and remember its return value
- continue to shutdown logic

Shutdown logic of EXE is:
- if not managed app
  - call exit() with return value from main
  --> exit() does not return
- if not has_cctor
  - call _cexit()
- do uninitialization
- return to system with value from main

Note that native_startup_lock, native_startup_state and proc_attached
are global per-module variables. And has_cctor is the local variable.

And native_startup_lock is type of lock which can be taken more times by
the same execution unit which has same stack pointer (userspace thread
or fiber in windows terminology), such lock type is sometimes called
recursive.

So if I understood the code logic correctly then any success-duplicate
or success-concurrent initialization of DLL is fatal error which
terminates the process. It is because for DLL the native_startup_lock is
the barrier which prevents duplicate thread execution and
native_startup_state after barrier is either in initializing state
(if some initialization hook in the same execution unit calls the entry
point again) or in initialized state (if successful initialization
possible by other thread already happen). So for successful path it is
always in non-uninitialized state if there is either duplicate or
parallel execution. Therefore if managed/mixed application calls entry
point of DLL with DLL_PROCESS_ATTACH multiple times then it is an fatal
error without recovery possibility.

Startup logic of EXE is quite different. It has that "has_cctor" state
and for some unknown reasons it allows duplicate entry point execution.
If duplicate execution happens then has_cctor is set to true and any
initialization except the thread-local callbacks are skipped.

What is suspicious is that on duplicate EXE entry point execution, the
main() function is also called more times, but there is no mark from
startup code which pass to main() function that duplicate execution
happened. The "has_cctor" variable is local and not exported at all.
Also this variable is not bound to "managed application" check at all.
So if I understood the logic correctly then this code path is valid also
for native applications too. Manage application check is only in
shutdown logic, which use different way how to terminate the process.

> 
> > But IIRC DLL library has no way to cause execution of main EXE
> > application entry point. LoadLibrary does not call entry point for EXE
> > binaries if for some reason dll library hook in some DllMain do that.
> > 
> > And I'm not aware of any WinAPI function which is doing to call EXE
> > entry point. The only known option is when process starts execution.
> > And I highly double that CLR/.net is going to explicitly execute entry
> > point of EXE application file for some case.
> > 
> > It is just a big mystery for me what is the code path which can trigger
> > this for EXE applications.
> 
> Those comments seem to say that error handling functions for managed modules
> may reenter startup code, which sounds plausible if those functions wish to
> display some nice dialog boxes. However I am currently out of ideas now;
> haven't studied managed code at all.

I think that either comments are quite misleading, or not fully correct
or my logic and its explanation above is wrong. Because for the managed
DLL the startup code cannot be reentered. But for EXE it can.


_______________________________________________
Mingw-w64-public mailing list
Mingw-w64-public@lists.sourceforge.net
https://lists.sourceforge.net/lists/listinfo/mingw-w64-public

Reply via email to