Projet_SETI_RISC-V/riscv-gnu-toolchain/newlib/winsup/cygwin/how-startup-shutdown-works.txt

165 lines
7.8 KiB
Text
Raw Permalink Normal View History

2023-03-06 14:48:14 +01:00
Contributed by Dave Korn
How the C runtime handles startup and termination.
--------------------------------------------------
This file documents the processes involved in starting up and shutting down
a Cygwin executable. The responsibility is divided between code that is
statically linked into each Cygwin-based DLL or executable as part of the
C runtime, and code in the Cygwin DLL itself that co-operates with it. The
runtime library code lives in the winsup/cygwin/lib directory, and a little
of it is in winsup/cygwin/include/cygwin/cygwin_dll.h
Process overall startup sequence.
=================================
Overall process startup (and indeed termination) is under the control of the
underlying Windows OS. The details of the Win32 CreateProcess API and the
underlying NT Native API ZwCreateProcess calls are far more complex (and
unknown, since proprietary) than we need go into here; the important details
are that the process address space is first created, then an initial thread
is spawned that performs DLL initialisation, calling the DllMain functions of
all statically-linked DLLs in load order. This thread is also serialised under
the Windows OS global loader lock, and DllMain functions are very limited in
what they can do as a consequence; to help deal with this, cygwin wraps the
user's DllMain function and defers calling it until runtime. Once the DLLs
have been initialised, the initial thread then performs C runtime setup and
calls into the executable's main() function.
Entry sequence for Cygwin-based DLLs.
=====================================
In the compiler's LINK_SPEC, a -e option sets the entry point (what Windows
regards as DllMain) to __cygwin_dll_entry@12. This is defined in
include/cygwin/cygwin_dll.h. The user's DllMain function, if any, is called
from within this function - directly in the case of thread attach/detach
notifications and process detach, but indirectly at process attach time via
cygwin_attach_dll in lib/cygwin_attach_dll.c, which calls the CRT common code
_cygwin_crt0_common and then hands off to the Cygwin DLL at dll_dllcrt0. The
CRT common code doesn't call the user DllMain at once; it caches a pointer to
it in the 'main' member of the DLL's per_process struct.
__cygwin_dll_entry@12 -> cygwin_attach_dll -> (_cygwin_crt0_common)
-> dll_dllcrt0 -> (DllMain?maybe?)
dll_dllcrt0 is in dll_init.cc sets up exception handler, ensures cygwin DLL is
at least partially initialised, allocates a new entry for the DLL chain, and
either calls the 'main' function (via dll::init) before returning to the OS
loader, or defers doing so until dll_crt0_1 runs dlls.dll_list::init() during
the application's startup sequence, depending on whether Cygwin DLL was fully
initialised yet or not. In general statically linked DLLs will defer, while
dlopen'd DLLs will run at once. The Cygwin DLL runs the dependent DLL's ctors
immediately prior to making the call, whether immediate or deferred.
Entry sequence for Cygwin-based executables.
============================================
The entry point is the windows standard entrypoint, WinMainCRTStartup, aliased
to mainCRTStartup, defined in crt0.c. It aligns the stack, sets the x87 fpu
cw, and hands off to cygwin_crt0 in lib/cygwin_crt0.c, which calls the CRT
common init code in _cygwin_crt0_common and heads off into the DLL, never to
return from _dll_crt0.
mainCRTStartup -> cygwin_crt0 -> (_cygwin_crt0_common) -> _dll_crt0
-> dll_crt0_1 -> (n*DllMain?maybe?) -> main -> (__main) -> cygwin_exit
This is a wrapper that does some fork-related stack sorting out then hands off
to dll_crt0_1, which completes all Cygwin DLL initialisation, runs any
deferred DllMain calls, and jumps into the application, returning via the
termination routines.
Post-entry construction.
========================
The compiler automatically inserts a hidden call to __main at the start of the
user's main() function. During startup, DLL constructors are run in dll:init()
immediately prior to calling that DLL's DllMain function (not in a forkee,
though; once is enough). In __main, all statically-loaded DLL ctors are now
complete, so we queue an atexit call to dll_global_dtors, then run the
application's ctors and queue an atexit call to do_global_dtors.
Process overall termination sequence.
=====================================
The program termination sequence can begin in one of the following ways:
- by returning from main()
- by calling exit(), _Exit() or _exit()
- by calling abort()
(this can be implicit, such as when an unhandled C++ exception is thrown,
or when an SEH exception is raised and not trapped, or an unhandled signal
terminates the program).
Unload sequence for Cygwin-based DLLS.
======================================
_cygwin_dll_entry@12 -> (DllMain) -> cygwin_detach_dll -> dll_list::detach
-> (remove_dll_atexit) -> (dll::run_dtors)
When a DLL is unloaded, whether as a result of dlclose() calling FreeLibrary(),
or when then entire process is terminating, the OS arranges to call the DLL's
DllMain function with a DLL_PROCESS_DETACH notification. As during the entry
sequence, this is also wrapped by _cygwin_dll_entry(), although there is in
this case no need to defer calling the user's DllMain hook; it is called at
once. If no error is indicated, the dll is then detached from Cygwin's
internal tracking list, and any atexit functions it has registered are run and
cancelled from the atexit list. Finally any static destructors are run.
Exit sequence for Cygwin-based executables.
============================================
This diagram illustrates the code paths, listed above, by which the main
executable can terminate:
+-------------->-- exception handling --->----------------------------+
| |
+-------------->--------- abort --------->--- stdio cleanup ----------+
| |
+-------------->-- direct or via _Exit -->-------------------+ |
| | |
+-------------->----------+ | |
| V stdio cleanup, V V
main -> dll_crt0_1 -> cygwin_exit -> exit -> atexit funcs -> _exit -> do_exit
-> pinfo::exit -> ExitProcess -> END.
Returning from main() transfers control back to dll_crt0_1(), which passes the
return value to cygwin_exit(); this is the same as calling exit(), which is
an export name alias for cygwin_exit() anyway. cygwin_exit() calls the real
exit() function in newlib, which runs the atexit functions and shuts down
stdio before exiting via _exit(), which immediately passes the exit status
through to do_exit(). If exiting via abort(), stdio is cleaned up, but no
atexit functions are run.
All the termination sequences end up in do_exit(), which takes care of POSIXy
stuff like process group and child signalling, tty disconnection, etc. This
finally passes control to pinfo::exit(), which takes care of indicating the
correct overall exit status and then gives control to the OS process shutdown
routine, ExitProcess().
During ExitProcess(), all the statically-linked DLLs in the application are
terminated, by calling their DllMain functions with the DLL_PROCESS_DETACH
notification.
Static object destruction.
==========================
Static object destruction for any statically-linked DLLs, or any dlopen()ed
DLLs that have still not been dlclose()d by termination time, is handled in
dll_global_dtors(). As the description above makes clear, this relies on the
atexit functions being run, and so only takes place during a graceful exit,
and not in the case of termination via _exit(), _Exit(), abort() or through an
unhandled signal or exception. The destructors are run before stdio has been
terminated, and in reverse of DLL load order.