[PATCH v3 1/4] bfd,ld,dlltool: Emit delay-load import data into its own section

Jan Beulich jbeulich@suse.com
Fri Jun 6 08:40:50 GMT 2025


On 06.05.2025 08:50, Jeremy Drake wrote:
> From: LIU Hao <lh_mouse@126.com>
> 
> A delay-import symbol (of a function) is resolved when a call to it is made.
> The delay loader may overwrite the `__imp_` pointer to the actual function
> after it has been resolved, which requires the pointer itself be in a
> writeable section.
> 
> Previously it was placed in the ordinary Import Address Table (IAT), which
> is emitted into the `.idata` section, which had been changed to read-only
> in db00f6c3aceabbf03acdb69e74b59b2d2b043cd7, which caused segmentation
> faults when functions from delay-import library were called.  This is
> PR 32675.
> 
> This commit makes DLLTOOL emit delay-import IAT into `.didat`, as specified
> by Microsoft. Most of the code is copied from `.idata`, except that this
> section is writeable.  As a side-effect of this, PR 14339 is also fixed.
> 
> Using this DEF:
> 
>    ```
>    ; ws2_32.def
>    LIBRARY "WS2_32.DLL"
>    EXPORTS
>      WSAGetLastError
>    ```
> 
> and this C program:
> 
>    ```
>    // delay.c
>    #define WIN32_LEAN_AND_MEAN 1
>    #include <windows.h>
>    #include <stdio.h>
> 
>    /////////////////////////////////////////////////////////
>    // User code
>    /////////////////////////////////////////////////////////
> 
>    DWORD WINAPI WSAGetLastError(void);
>    extern PVOID __imp_WSAGetLastError;
> 
>    int
>    main(void)
>      {
>        fprintf(stderr, "before delay load, __imp_WSAGetLastError = %p\n", __imp_WSAGetLastError);
>        SetLastError(123);
>        fprintf(stderr, "WSAGetLastError() = %d\n", WSAGetLastError());
>        fprintf(stderr, "after delay load, __imp_WSAGetLastError = %p\n", __imp_WSAGetLastError);
>        __imp_WSAGetLastError = (PVOID) 1234567;
>        fprintf(stderr, "after plain write, __imp_WSAGetLastError = %p\n", __imp_WSAGetLastError);
>      }
> 
>    /////////////////////////////////////////////////////////
>    // Overridden `__delayLoadHelper2` facility
>    /////////////////////////////////////////////////////////
> 
>    extern char __ImageBase[];
>    PVOID WINAPI ResolveDelayLoadedAPI(PVOID ParentModuleBase, LPCVOID DelayloadDescriptor,
>                                       PVOID FailureDllHook, PVOID FailureSystemHook,
>                                       FARPROC* ThunkAddress, ULONG Flags);
>    FARPROC WINAPI DelayLoadFailureHook(LPCSTR name, LPCSTR function);
> 
>    FARPROC WINAPI __delayLoadHelper2(LPCVOID pidd, FARPROC* ppfnIATEntry)
>    {
>      return ResolveDelayLoadedAPI(&__ImageBase, pidd, NULL, (PVOID) DelayLoadFailureHook,
>                                   ppfnIATEntry, 0);
>    }
>    ```
> 
> This program used to crash:
> 
>    ```
>    $ dlltool -nn -d ws2_32.def -y delay_ws2_32.a
>    $ gcc -g delay.c delay_ws2_32.a -o delay.exe
>    $ ./delay.exe
>    before delay load, __imp_WSAGetLastError = 00007FF6937215C6
>    Segmentation fault
>    ```
> 
> After this commit, it loads and calls `WSAGetLastError()` properly, and
> `__imp_WSAGetLastError` is writeable:
> 
>    ```
>    $ dlltool -nn -d ws2_32.def -y delay_ws2_32.a
>    $ gcc -g delay.c delay_ws2_32.a -o delay.exe
>    $ ./delay.exe
>    before delay load, __imp_WSAGetLastError = 00007FF76E2215C6
>    WSAGetLastError() = 123
>    after delay load, __imp_WSAGetLastError = 00007FFF191FA720
>    after plain write, __imp_WSAGetLastError = 000000000012D687
>    ```
> 
> Reference: https://learn.microsoft.com/en-us/windows/win32/secbp/pe-metadata#import-handling
> Co-authored-by: Jeremy Drake <sourceware-bugzilla@jdrake.com>
> Signed-off-by: LIU Hao <lh_mouse@126.com>
> Signed-off-by: Jeremy Drake <sourceware-bugzilla@jdrake.com>

I was meaning to commit this today, but obviously(?) ran it through the testsuite
first. This introduces regressions for 3 testcases for both Cygwin and MinGW. Did
you not observe these, or did you simply not run the testsuite for either of
these (pretty common when it comes to PE/COFF) targets?

The failures also appear to point out a problem with the code changes themselves,
i.e. it's not just the testcase expectations that need adjustment. For
ld-pe/secrel_64.d and ld-pe/secidx_64.d I don't see why suddenly a .didat section
would be present in the output. Neither test looks to be using delayload
functionality. For ld-pe/tlssec64.d the issue is likely the same, even if it
expresses itself differently in the expected vs actual output delta.

Overall this also reminds me of another shortcoming of the series: There's no
new testcase being added, making sure the new functionality won#t regress.

Jan


More information about the Binutils mailing list