Import library incompatability between MSVC and Binutils (link.exe bug)

未关闭
#135 6 条评论 1 个 reaction 已指派 0 人 在 GitHub 查看

还没有人认领这个 Issue。

评估

难度
5/5
预计耗时
一周以上
新手友好度
35/100
Issue 类型
缺陷
描述清晰度
基本清楚
活跃度
冷清
技术栈
c

调研方向

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.

由索引模型根据 Issue 内容生成。

描述

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.

主要语言
C
星标
5k
派生
408
平均合并
4 小时 40 分钟
30 天内合并 PR
2

环境准备

  • 提供 Dockerfile 或 Docker Compose 文件
  • 没有 Pull Request 模板
  • 没有贡献指南

从这里开始

  1. 先读完整个 Issue,再读项目的贡献指南。
  2. 在 Issue 下留言说明你要接手 —— 这能避免两个人做同样的事。
  3. Fork 仓库,在一个分支上完成修改。
  4. 提交 Pull Request,并在描述里引用这个 Issue 编号。

skeeto/w64devkit 的其他 Issue

查看 skeeto/w64devkit 的全部 Issue

相似的 Issue

更多 C Issue

把新 issue 发到你的邮箱

精选适合新手参与的 GitHub issue 摘要。