Why does the Windows Portable Executable (PE) format have separate tables for import names and import addresses?, part 2

Raymond Chen

In the Windows Portable Executable (PE) format, the image import descriptor table describes the functions imported from a specific target DLL.

struct IMAGE_IMPORT_DESCRIPTOR {
    DWORD   OriginalFirstThunk;
    DWORD   TimeDateStamp;
    DWORD   ForwarderChain;
    DWORD   Name;
    DWORD   FirstThunk;
};

The OriginalFirstThunk points to an array of pointer-sized IMAGE_THUNK_DATA structures which describe the functions being imported. The FirstThunk points to an array of pointers, whose initial values are a copy of the values pointed to by OriginalFirstThunk.

Last time, we looked at why the two identical tables can’t be merged: Because the second table can be modified by binding, and then the two copies both contain distinct information that is needed at run time.

Okay, so you can’t merge the two tables, but why not combine them into a single mega-table? Why split it into two mini-tables?

The tables are kept separate because one is read-only and the other is read-write. Splitting them up allows the read-only part to be combined with other read-only data, and the read-write part to be combined with other read-write data. This allows for a reduction in the number of read-write pages. This benefit could end up being small for a single DLL, but if the DLL is used by many processes, the multiplicative effect can be significant.

2 comments

Discussion is closed. Login to edit/delete existing comments.

  • skSdnW 0

    I believe old Borland compilers produce a empty/zero OriginalFirstThunk.

  • Joshua Hudson 0

    As far as I can tell, ForwarderChain is unnecessary and it’s always possible to emit a binary for which it is zero. The documentation suggests it’s an initial index into OriginalFirstThunk, but on testing it I found no reason that OriginalFirstThunk can’t point to the right place to begin with.

Feedback usabilla icon