Import library incompatability between MSVC and Binutils (link.exe bug)
Nobody has claimed this yet.
- Dominant language
- C
- Stars
- 4.8k
- Forks
- 381
- PR merge metrics
- No merged PRs in 30d
Description
I've discovered a subtle import library incompatibility between MSVC and Binutils which goes back at least a quarter century. It's definitely a bug in MSVC link.exe (at least x86 and x64), but possibly also a bug in Binutils. I haven't gotten to the bottom of it, and probably will not any time soon, but I want to document it publicly for the future, in case anyone else wants to keep digging.
Summary: Linking only Binutils-generated import libraries with link.exe will produce a PE image with a non-terminating import directory. It may crash on load — both Windows and Wine — depending on how the loader was written, and what happens to follow the directory in the image. I can reproduce the issue at least as far back as VS6 (1998). It's likely gone unnoticed so long because virtually nobody does this except for the occasional weirdo like me.
The set up is trivial. First a library definition, lower.def:
LIBRARY lower
EXPORTS
func
Then a program that uses it, upper.c:
__declspec(dllimport) void func(void);
void mainCRTStartup(void) { func(); }
If I use MSVC lib.exe to generate the import library, everything is fine:
$ lib /nologo /def:lower.def
$ cl /nologo upper.c /link /subsystem:console lower.lib
However, if I use Binutils dlltool instead:
$ dlltool -d lower.def -l lower.lib
$ cl /nologo upper.c /link /subsystem:console lower.lib
Then upper.exe is an invalid PE image. dumpbin /imports notices:
Section contains the following imports:
...
<Invalid RVA>
402130 Import Address Table
40214C Import Name Table
0 time date stamp
75660001 Index of first forwarder reference
Note the "Invalid RVA". It's observable with objdump -p as well, though it doesn't notice enough to produce an error message:
The Import Tables (interpreted .rdata section contents)
vma: Hint Time Forward DLL First
Table Stamp Chain Name Thunk
00002104 00002118 00000000 00000000 0000212c 00002000
DLL Name: lower.dll
vma: Hint/Ord Member-Name Bound-To
2120 1 func
00002118 00002120 00000000 75660001 0000636e 00002104
The five fields in the last line should have been all zero. Per the PE specification:
The last directory entry is empty (filled with null values), which indicates the end of the directory table.
The values shown are instead pieces of the first import lookup table, and then the idata string table, which immediately follow the import directory table. objdump notices garbage and stops reading the directory table.
It's not just dlltool: ld --out-implib=... has the same result.
Linking at least one non-Binutils import library, in any position, fixes the problem. With all the implicitly-linked libraries present, this is nearly always the case — except for toolchain-hacking weirdos — so the bug almost never manifests in practice.
I suspect maybe Bintuils' import libraries are not quite formatted properly, which confuses link.exe. It's still a linker bug, because it shouldn't quietly link a broken PE image. Since it's unlikely I could get Microsoft to fix link.exe, perhaps it could be addressed in Bintuils. This is where things get more complicated. The import library format is undocumented — so who knows what's "correct" or not — and Bintuils' import libraries are especially messy, making them difficult to pick through by hand. Figuring this out would be the next step.
Contributor guide
No contributing guide indexed for this repository
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Start with lower.def and upper.c, then reproduce the two builds using MSVC lib.exe, Binutils dlltool, and ld --out-implib. Inspect the resulting PE imports with dumpbin /imports and objdump -p, focusing on the nonzero final import-directory fields. Done would require determining whether the incompatibility belongs in Binutils, MSVC link.exe, or both, and identifying a concrete correction.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- c
- Domain
- compilers, devtools
- Issue type
- Bug
- Difficulty
- 5/5
- Estimated time
- Over a week
- Activity status
- Quiet
- Clarity
- Mostly clear
- Newbie friendliness
- 35/100