July 25th, 2006

Issues related to forcing a stub to be created for an imported function

I noted last time that you can concoct situations that force the creation of a stub for an imported function. For example, if you declare a global function pointer variable:

DWORD (WINAPI *g_pGetVersion)() = GetVersion;

then the C compiler is forced to generate the stub and assign the address of the stub to the g_pGetVersion variable. That’s the best it can do, since the loader will patch up only the imported function address table; it won’t patch up anything else in the data segment.

The C++ compiler, on the other hand, can take advantage of some C++ magic and secretly generate a “pseudo global constructor” (I just made up that term so don’t go around using it like it’s official or something) that copies the value from the imported function address table to the g_pGetVersion variable at runtime. Note, however, that since this is happening at runtime, mixed in with all the other global constructors, then the variable might not be set properly if you call it from any code that runs during construction of global objects. Consider the following buggy program made up of two files.

// file1.cpp
#include <windows.h>
EXTERN_C DWORD (WINAPI *g_pGetVersion)();
class Oops {
  public: Oops() { g_pGetVersion(); }
} g_oops;
int __cdecl main(int argc, char **argv)
{
  return 0;
}
// file2.cpp
#include <windows.h>
EXTERN_C DWORD (WINAPI *g_pGetVersion)() = GetVersion;

The rules for C++ construction of global objects is that global objects within a single translation unit are constructed in the order they are declared (and destructed in reverse order), but there is no enforced order for global objects from separate translation units. But notice that there is an order-of-construction dependency here. The construction of the g_oops object requires that the g_pGetVersion object be fully constructed, because it’s going to call through the pointer when the Oops constructor runs.

It so happens that the Microsoft linker constructs global objects in the order in which the corresponding OBJ files are listed in the linker’s command line. (I don’t know whether this is guaranteed behavior or merely an implementation detail, so I wouldn’t rely on it.) Consequently, if you tell the linker to link file1.obj + file2.obj, you will crash because the linker will generate a call to the Oops::Oops() constructor before it gets around to constructing g_pGetVersion. On the other hand, if you list them in the order file2.obj + file1.obj, you will run fine.

Even stranger: If you rename file2.cpp to file2.c, then the program will run fine regardless of what order you give the OBJ files to the linker, because the C compiler will use the stub instead of trying to copy the imported function address at runtime.

But what happens if you mess up and declare a function as dllimport when it isn’t, or vice versa? We’ll look at that next time.

Topics
Other

Author

Raymond has been involved in the evolution of Windows for more than 30 years. In 2003, he began a Web site known as The Old New Thing which has grown in popularity far beyond his wildest imagination, a development which still gives him the heebie-jeebies. The Web site spawned a book, coincidentally also titled The Old New Thing (Addison Wesley 2007). He occasionally appears on the Windows Dev Docs Twitter account to tell stories which convey no useful information.

0 comments

Discussion are closed.