Date: | February 14, 2005 / year-entry #37 |
Tags: | code |
Orig Link: | https://blogs.msdn.microsoft.com/oldnewthing/20050214-00/?p=36463 |
Comments: | 25 |
Summary: | There is a flag to the LoadLibraryEx function called DONT_RESOLVE_DLL_REFERENCES. The documentation says, If this value is used, and the executable module is a DLL, the system does not call DllMain for process and thread initialization and termination. Also, the system does not load additional executable modules that are referenced by the specified module. If... |
There is a flag to
the
In my opinion, the above text that "suggests" the
Look carefully at what the flag does and doesn't do.
The module is loaded into memory, but its initialization
function is not called and no dependent DLLs are loaded.
[Typo fixed, 10am.]
As a result, you cannot run code from this DLL.
(More accurately, if you try, it will crash because the DLL
hasn't initialized itself and none of its imports to
DLLs have been resolved.)
However, unlike the
Clearly,
The
It is common for somebody to call
(Note that code that does this is unsafe anyway, because
the code that originally loaded the DLL might decide to
do a
Even if you used #include <windows.h> typedef HINSTANCE (WINAPI *SXA)(HWND, LPCSTR, LPCSTR, LPCSTR, LPCSTR, int); int __cdecl main(int argc, char* argv[]) { if (argc > 1) // set the time bomb LoadLibraryEx("shell32.dll", NULL, DONT_RESOLVE_DLL_REFERENCES); // victim code runs here HINSTANCE h = LoadLibrary("shell32.dll"); if (h) { SXA f = (SXA)GetProcAddress(h, "ShellExecuteA"); if (f) { f(NULL, NULL, "notepad.exe", NULL, NULL, SW_SHOWNORMAL); } FreeLibrary(h); } } If you run this program with no command line arguments, then everything works just fine: Notepad is launched without incident. However, if you pass a command line argument, this sets the time bomb, and the call to ShellExecuteA crashes in flames because shell32.dll was loaded without having its DLL references resolved.
In other words,
|
Comments (25)
Comments are closed. |
Maybe the dll only exports data, not any functions?
Raymond, you are playing CarTalk puzzler now. WHAT backward compatibility?
I meant: for WHAT purpose was the flag originally used? was it used to mean LOAD_LIBRARY_AS_DATAFILE? Did they then realize the havoc and added the newer flag?
The sentence "The module is loaded into memory, but its initialization function is called …" should read "is NOT called", right?
The sentence "The module is loaded into memory, but its initialization function is called …" should be "is NOT called", right?
Isn’t it possible to write a DLL function without any imports? To do something at least a little useful, it could accept a callback pointer back into a module already loaded.
If you just give the function the pointers to LoadLibrary and GetProcAddress, it can do everything it may ever need. Or maybe not?
Is there any situation where you would ever WANT to load a DLL without calling any of its initialization? Surely there are and have been better ways than, for example, extracting resources (AS_DATAFILE isn’t that new is it?)
This particular flag seems to be way beyond the level of kludge you’d expect to find in a professional API like win32 :)
I’ve actually found this useful for when I’m doing some rather underhanded, hackish things .. but it’s definitely only useful in a few limited situations and only when you’re very sure of what you’re doing.
In particular, I’ve used this when I’m going to be loading and then patching somebody else’s DLL before calling it, and I don’t want to have to carry around a lot of dependencies that are unnecessary for a small section of code I want to use.
(No, I probably wouldn’t do anything like this in production code. But I’ve found it useful when writing some specialized things for my own personal use.)
CN: No, it’s not possible. LDR will refuse to load any image with an empty imports table.
I recall once making an EXE file that imported nothing at all. It seemed perfectly valid – a program that performs some unspecified task that involves no IO using OS functions then returns something.
It would not start. It crashed – on Win2K – at an address where presumably Kernel32.dll should have been mapped.
In my current project, we put resources for each supported language into a separate satellite dll, and load the appropriate dll when the application starts. These dll’s contain resources only.
The LOAD_LIBRARY_AS_DATAFILE flag worked, with one exception; ::CreateDialogParam() would fail if the dialog contained an EDITTEXT control. The problem went away when DONT_RESOLVE_DLL_REFERENCES was used.
This issue was mentioned in passing in the Windows Template Group ( http://groups.yahoo.com/group/wtl/message/6298) so it seems that other people have observed the same behavior.
Are resource only dll’s a legitimate case for using the DONT_RESOLVE_DLL_REFERENCES flag?
I’ve used DONT_RESOLVE_DLL_REFERENCES in much the same manner as Skywing: the DLL was loaded, then the imports section was patched to call functions provided by me. I’ve found this to be pretty useful for emulating plugin API’s for example (and certainly never had any problems with it crashing).
FTR I agree with Dmitry about the sentence change; I don’t recall DONT_RESOLVE_DLL_REFERENCES actually calling the initialization code (since, presumably, the init code would crash and burn if it relied on any of the imports being resolved).
My team maintained this code during Windows XP development and my belief is that gkdada got it right in their second reply: the flag was added and then too late they figured that it was a time bomb. It probably had already shipped so they had to just add yet another flag.
Re: why it sometimes works when other things don’t:
When you use LOAD_LIBRARY_AS_DATAFILE, the PE is mapped as a flag/linear data section. Offsets in the PE headers and data tend to be RVAs (Relative Virtual Addresses), which are offsets from the image header, when mapped as a PE (such that the various image sections have their alignments applied correctly).
So for images mapped as a data file, any code that has to peruse the PE has to understand how to remap the RVAs rather than just taking the HINSTANCE and adding the offset. There’s standard code for it but we have been finding code paths over time that don’t always do it right. Thus why mapping it as an image but not initializing it tended to work for certain situations.
Some people want to "fix" it to make it work "right" but of course this will break compatibility just as badly as removing it.
Re: why can’t you just use it when you know your DLL won’t have code:
Ok, so the DLL you’re loading right now may not have code, or your comfortable not running the code, but what about in the future? What if they add an initialization section just to fix up exported data? What happens when someone else in the process wants to load it in order to run code? Boom.
Just try to avoid this flag. It doesn’t work right and any uses of it are timebombs. They may never go off before the end of time but that doesn’t mean that they’re not timebombs.
The real problem here is the documentation author’s attempt at political correctness. A part of the API like this flag that — I’m quoting Raymond — exists solely for reasons of backwards compatibility should be clearly marked as such:
"[bold]Deprecated.[/bold] This flag is inherently unsafe. (Explanation) (Safe Alternative)." That was a direct quote from the Java API docs.
Such a warning fulfills three purposes:
a) clearly states that using X is a bad idea, and that X is preserved only for reasons of backwards compatibility.
b) explains the reason for a).
c) points to Y, a safe alternative for X.
The quoted example from the docs "it might be better to use Y" is a half-hearted attempt at c). Somehow a) and b) were not important enough to be included in the docs, so they remain hidden in some blog written by some Microsoft employee to be found somewhere on the internet.
I’m quite with Muck. One of the reasons you keep having to support poor design decisions until the end of time is that people keep using them in new code, because they don’t know not to use them. In some cases, the blame for that goes to the user — they didn’t read documentation, just saw an existing use somewhere, or they didn’t read enough of the documentation. In this case, it’s all your (in the plural!) fault — you didn’t provide nearly enough warning not to use it. In fact, "If you are planning only to access data or resources in the DLL…" suggests there are reasons to use DONT_RESOLVE_DLL_REFERENCES other then accessing data or resources. (And, indeed, I would tend to say that even if you only access data or resources, then you should still run DllMain — if it does not have one, then nothing wasted, and if it does, it may well need to run.)
Also, never, ever use the "word" DONT. If you can not use an apostrophie, use DO_NOT. It will either lead to typos, or, worse, lead people to think that DONT is a valid word.
If you want to make an image that hard-imports nothing, then you should just import something from NTDLL. NTDLL is always loaded into every process, unlike KERNEL32. (KERNEL32 is loaded into every *Win32* process. However, NTOSKRNL will map in NTDLL before anything else, so that’s always going to be present. You might as well import from it and make LDR happy. If you still care about Win9x, you can just dummy import one of the RtlExtended* math functions that the Win9x NTDLL implements and not have to worry about any unresolved symbols.)
I use this flag while enumerating a directory containing an unknown number of application specific DLLs that may or may not actually be used during the course of execution depending on the configuration and what the user does. Once I enumerate a DLL, I unload it but save the interesting information away someplace. If the DLL is later used, I load it in the normal fashion.
This allows me to do two things. One, I can retrieve constant data embedded in the DLLs that can be browsed by the user at runtime. This works even if the DLL can’t actually be loaded and fully resolved because some other DLL it depends on hasn’t been installed on the system. This is a benefit to the user and can help with debugging an installation.
Two, so I can *avoid* calling DllMain() on the DLL and any of its dependencies. Why is this desirable? Because I’ve had to use third party DLLs (often implicitly linked) that caused undesirable "side effects" just by executing their DllMain(). It’s nice to contain trouble when possible.
Unfortunately, I discoverd that this flag appears to be ignored by Windows 95 and 98. Fortunately, Windows 95 and 98 are on their way out (for the users of this application at any rate).
Of course, I realize this is a very specific application of the flag. The DLLs are designed to be used by the application and will not be implicitly linked to by other unkown pieces of code. But the flag does have a use.
If DONT_RESOLVE_DLL_REFERENCES became deprecated how would one produce their own psuedo-loader which just patched up the PE import table (which is what I’ve used DONT_RESOLVE_DLL_REFERENCES for in the past)? It seems to me that, rather than deprecating the use of DONT_RESOLVE_DLL_REFERENCES (or worse, dropping support for it), the documentation should be updated to highlight what exactly it could be used for (and then drop hints for the other flags/functions that can be used for other tasks).
Incidentally, the Unix version of LoadLibrary (dlopen) has options of RTLD_NOW and RTLD_LAZY, but it does not have an option of RTLD_NO.
http://www.opengroup.org/onlinepubs/009695399/functions/dlopen.html
All these three flags make sense in certain scenarios.
It is interesting that both Windows and Unix miss one of the three.
md, something being deprecated does not mean that you can’t use it. The flag won’t suddenly disappear. Being deprecated just means that you should have a very good reason to use it despite the clear warning.
Why would a warning bother you? The flag is "fundamentally flawed", as Raymond puts it, whether this fact is clearly indicated in the documentation or not.
Maybe it’s the same reason why for many people, smoking "feels better" if the cigarette box doesn’t say "SMOKING KILLS". Even though it doesn’t actually matter, because the effect is the same.
Check out the following MSDN article for a discussion of what this flag does: http://msdn.microsoft.com/msdnmag/issues/02/03/Loader/
Muck,
Whether it’s flawed or not depends on what the original intended usage for it was. If the goal is to get at data in the DLL, the documentation is correct that you should use LOAD_LIBRARY_AS_DATAFILE. However, if your goal is API replacement, then DONT_RESOLVE_DLL_REFERENCES is the most reliable way of doing that.
Unless someone from MS wants to step in and say what DONT_RESOLVE_DLL_REFERENCES was originally intended for, I’m going to stick with my idea that it was for taking over imports. ;)
Why don’t you clearly mark the api functions that exist only for backwards compatibility in the documentation so that developers writing new software will avoid them? IE you can move all the backwards compatibility functions in a special section of the documentation.
PingBack from http://www.globalmapperforum.com/forums/sdk/2792-sdk-borland-c-builder-2008-a.html#post5216