-
-
Notifications
You must be signed in to change notification settings - Fork 1.6k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Support DLL delay-loading on Windows #13436
Support DLL delay-loading on Windows #13436
Conversation
$image_base = __ImageBase : IMAGE_DOS_HEADER | ||
end | ||
|
||
private macro p_from_rva(rva) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There is just one caveat: for some reason LibC.image_base
cannot be accessed from any LLVM module scope other than the top-level one, otherwise MSVC raises an internal linker error, and the only way out is to pass --single-module
. So this cannot be a class method of Crystal::System::DelayLoad
, and neither can __delayLoadHelper2
itself
This PR adds support for the
/DELAYLOAD
MSVC linker flag, and additionally makes almost all DLLs delay-loaded when-Dpreview_dll
is used to build a load-time dynamically linked executable.When a program is linked against a DLL import library, by default the startup code automatically calls
LoadLibrary
andGetProcAddress
behind the scenes to perform the actual linking, and terminates withSTATUS_DLL_NOT_FOUND = 0xC0000135
if a DLL cannot be found. This happens before the entry point gets called, so Crystal's runtime cannot intercept the error at all, and the parent process receives no information other than the exit code. With delayed loading, the DLLs and symbols are only loaded the first time they are used, and__delayLoadHelper2
becomes the linker function, which must be defined separately.One way to obtain this linker function is to link against
delayimp.lib
from Visual C++ for a sensible default implementation with support for several user-defined hooks, and the other is to port the reference implementation to Crystal. This PR does the latter, which gives us complete control over the link process, in particular the DLL search order and error reporting. At the momentfun __delayLoadHelper2
is a verbatim port of the fileMicrosoft Visual Studio\2022\Community\VC\Tools\MSVC\14.35.32215\include\delayhlp.cpp
, except all the hooks are omitted. In the future we could enhance this function to deliver a better experience when dealing with DLLs on Windows.When
-Dpreview_dll
is specified, if both the host and the target use MSVC, the compiler will now look up all the import libraries passed to the linker, and append a/DELAYLOAD
linker flag for every imported DLL, reusing the logic fromCrystal::Loader
. No loader is actually created, and the compiler itself never loads any dynamic libraries this way. For example, runningdumpbin /dependents
on an empty source code compiled with-Dpreview_dll
gives:The commented out
FormatMessageA
stub indelay_load.cr
gives:This behavior can be disabled if
-Dno_win32_delay_load
is also provided; the linker function remains available, so individual DLLs can still be delay-loaded if the respective/DELAYLOAD
flag is provided explicitly.A side effect of this change is that a missing DLL is no longer an error as long as the executable doesn't call any function from that DLL, but the most useful benefit is not having to link the entire
user32.dll
upfront whenever OpenSSL is used (they useMessageBoxW
for certain errors).