Question

Calling DLL functions involves the linker generating a stub, unless the function was declared __declspec(dllimport) in which case the stub can be bypassed in favor of an indirect call straight to the import table which is slightly more efficient, e.g.

__declspec(dllimport) void ExitProcess(int);
ExitProcess(0);

generates

call qword ptr [__imp_ExitProcess]

where __imp_ExitProcess resolves to a location in the import table in the executable.

I'm trying to figure out exactly how __imp_ExitProcess is resolved. It occurs as a symbol in kernel32.lib to be sure, but that symbol has storage class IMAGE_SYM_CLASS_EXTERNAL, section number zero and value zero, which amounts to just saying 'this will be defined somewhere else' without actually ever defining it.

Does the __imp_ prefix have special meaning, i.e. does the linker notice that prefix and take it as an instruction to resolve the symbol to the import table entry for the DLL function whose name has that prefix removed? Or is something else going on?

Was it helpful?

Solution 3

Members of kernel32.lib are not normal object files but special placeholders. From the PE/COFF spec:

Traditional import libraries, that is, libraries that describe the exports from one image for use by another, typically follow the layout described in section 7, “Archive (Library) File Format.” The primary difference is that import library members contain pseudo-object files instead of real ones, in which each member includes the section contributions that are required to build the import tables that are described in section 6.4, “The .idata Section.” The linker generates this archive while building the exporting application.

The section contributions for an import can be inferred from a small set of information. The linker can either generate the complete, verbose information into the import library for each member at the time of the library’s creation or write only the canonical information to the library and let the application that later uses it generate the necessary data on the fly.

[...] a short import library is written as follows:

  Archive member header   
  Import header
  Null-terminated import name string
  Null-terminated DLL name string

This is sufficient information to accurately reconstruct the entire contents of the member at the time of its use.

In kernel32.lib on my machine, __imp_ExitProcess is mentioned in the first and second linker members (symbol list) and point to the specific pseudo-object describing the import:

Archive member name at 8: /               
50107C36 time/date Thu Jul 26 01:07:34 2012
         uid
         gid
       0 mode
   106CA size
correct header end

    2515 public symbols

    [...]

    3C874 ExitProcess
    3C874 __imp_ExitProcess

    [...]

Archive member name at 3C874: KERNEL32.dll/   
50107639 time/date Thu Jul 26 00:42:01 2012
         uid
         gid
       0 mode
      2D size
correct header end

  Version      : 0
  Machine      : 8664 (x64)
  TimeDateStamp: 50107639 Thu Jul 26 00:42:01 2012
  SizeOfData   : 00000019
  DLL name     : KERNEL32.dll
  Symbol name  : ExitProcess
  Type         : code
  Name type    : name
  Hint         : 371
  Name         : ExitProcess

So, as you can see, the data in .lib explicitly says that it refers to an import name ExitProcess from the DLL KERNEL32.dll. The linker can use this to build necessary metadata in the import section.

Now, the above only discussed how the __imp_ExitProcess symbol is resolved. I'm not 100% sure but I think if a symbol (e.g. ExitProcess) has been resolved to such an import stub, and it does not start with __imp_, then the linker has to generate a jump stub (for code symbols), or an indirect access (for data accesses) to the IAT slot.

OTHER TIPS

The linker adds to your program a function like:

void (*__imp_ExitProcess)(int) = ...;

void ExitProcess(int n)
{
    return (*__imp_ExitProcess)(n);
}

where __imp_ExitProcess points to the "real" ExitProcess in KERNEL32.DLL.

Declaring this in your code:

__declspec(dllimport) void ExitProcess(int);

is equivalent to:

extern void (*__imp_ExitProcess)(int);

#define ExitProcess (*__imp_ExitProcess)

except that __declspec(dllimport) is handled by the compiler, not by the preprocessor.

Just came up with a test that gives a data point. The following program:

__declspec(dllimport) void ExitProcess(int);

void __imp_ExitProcess(int x) {
}

int main() {
    ExitProcess(0);
}

crashes when compiled with the Microsoft compiler and run (but runs okay if the empty function is renamed to anything else); thus, it seems the linker behaves as though the __imp_ name is special, at least in the sense that a linker that behaves that way will generate a correct executable in all cases where the Microsoft linker does so, unless I'm missing something.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top