A DLL linked with /DELAYLOAD is not loaded when the program starts, but
the first time one of its functions is called. Windows’ loader knows nothing of this:
the linker adds a helper function from the C run-time library, and tables that the
helper reads. Data directory 13 points at those tables. Few programs use it; 40 of the
PE files examined for this site had delay imports.
The delay-load descriptor
ImgDelayDescr (IMAGE_DELAYLOAD_DESCRIPTOR in winnt.h), 32 bytes, one for each DLL, ending with one that is all zero:
| Offset | Size | Type | Field | Meaning |
|---|---|---|---|---|
| 0 | 4 | u32 | Attributes (grAttrs) | Bit 0 says how the other fields are written; see Attributes. |
| 4 | 4 | u32 | Name (szName) | The DLL’s name, in ASCII, ending in a zero byte. |
| 8 | 4 | u32 | ModuleHandle (phmod) | Where the helper stores the DLL’s module handle once loaded, in .data. |
| 12 | 4 | u32 | DelayImportAddressTable (pIAT) | The delay import address table; see Tables. |
| 16 | 4 | u32 | DelayImportNameTable (pINT) | The names or ordinals of the functions, laid out like an import lookup table. |
| 20 | 4 | u32 | BoundDelayImportTable (pBoundIAT) | Optional: a copy of the address table with prebound addresses. |
| 24 | 4 | u32 | UnloadDelayImportTable (pUnloadIAT) | Optional: a copy of the original address table, to put back if the DLL is unloaded. |
| 28 | 4 | u32 | TimeDateStamp | The time stamp of the DLL the table was bound to, or 0. |
Attributes
Bit 0 of Attributes is RvaBased (dlattrRva). When it is set, the
other address fields are RVAs, as everywhere else in the file. When it is clear, they
are virtual addresses, the image base added in: the form written by Visual C++ 6. The
other bits are reserved. The explorer reads both forms, turning a virtual address back
into an RVA by taking away the image base.
Tables
The delay import address table has a 4-byte slot for each function, ending with a
zero slot, as an import address
table does. In the file, each slot holds the address of a small stub. Calling the
function for the first time runs the stub, which passes the slot’s address to the
helper, __delayLoadHelper2. The helper loads the DLL, if that has not
been done, finds the function from the name table, writes its address into the slot,
and jumps to it. Later calls go straight to the function.
The name table holds the same 4-byte entries as an import lookup table: bit 31 set for an import by ordinal, otherwise the RVA of a hint/name entry.
The explorer names the delay import address table slots in the disassembly as it
does the ordinary ones, as DLL!function, and lists the functions with the
other imports, marked “(delay-loaded)”.