调试实战 | 解决另外一个链接错误

缘起

最近,在加班的过程中遇到一个链接错误 —— fatal error LNK1120: 1 unresolved externals。这种错误是老朋友了,对我这种常年写 bug 的老手来说,完全不是事儿,轻松+愉快。

根据以下的排查思路基本上能解决大多数链接错误:

既然报了链接错误,说明编译已经通过了,问题基本出现在库文件上。

有可能是找不到库文件(缺少库,或者库文件搜索路径不对),可以先确认工程配置是否正确或者使用 /verbose:lib 查看链接过程。

也可能是库文件不对(没包含对应的导出符号),可以通过 dumpbin /exports error.lib > error.txt 查看 lib 库中的导出符号。按照以上步骤排查基本上可以解决绝大多数链接错误。

好的,让我们一起来实战一下吧。

说明: 实际项目需要保密,本文的截图是我在本地用测试工程做的。

确保 lib 存在并且路径正确

通过查看工程设置可以得知,依赖的库文件是 TestLNK1120Dll.lib,附加库目录中也添加了这个 lib 所在的路径。肉眼看上去没问题。为了保险(之前遇到过更诡异的错误,配置看上去都对,但是实际的值不对),还是通过 /verbose:lib 选项看一下链接过程。

在对应工程上,右键 -> 属性 -> Configuration Property -> Linker -> Command Line,在 Additional Options 下面输入 /verbose:lib 即可。

link-option-verbose-lib

设置好后,重新编译。可以看到链接时整个库查找过程,如下图。

verbose-lib-link-process

至此,可以确定库文件路径配置没错,那大概率是库文件中的符号与程序中的符号不匹配导致的。

说明: 如果找不到 .lib 文件,报错应该类似下面这样:

fatal error LNK1104: cannot open file 'TestLNK1120Dll.lib'

查看 lib 文件中的导出符号

使用 dumpbin 可以查看 lib 库中的所有导出符号,命令如下:

dumpbin /exports d:\test\TestLNK1120.lib > d:\TestLNK1120.txt

注意: 以上命令需要在 dumpbin.exe 所在目录下执行,或者启动 Developer Command Prompt for VS xxx

然后根据 vs 编译错误提示中的符号在 TestLNK1120.txt 中搜索,应该是没有匹配项目。

搜索报错的符号

确实可以在 TestLNK1120.txt 中根据函数名 GetStaticData 搜到相关记录,但是根据完整的符号名称搜不到。

TestLNK1120.txt 文件中与 GetStaticData 相关的内容是:

?GetStaticData@@YAAAV?$basic_string@DU?$char_traits@D@std@@V?$allocator@D@2@@std@@XZ (class std::basic_string<char,struct std::char_traits<char>,class std::allocator<char> > & __cdecl GetStaticData(void))

vs 报错提示是:

TestLNK1120.obj : error LNK2001: unresolved external symbol "class std::basic_string<char,struct std::char_traits<char>,class std::allocator<char> > __cdecl GetStaticData(void)" (?GetStaticData@@YA?AV?$basic_string@DU?$char_traits@D@std@@V?$allocator@D@2@@std@@XZ)

让我们对比一下这两个结果哪里不一样。

对比

别看上面的输出很多很乱,其实我们最需要关心的是经过名字改编的符号名。

vs 中经过名字改编后的符号名是

?GetStaticData@@YA?AV?$basic_string@DU?$char_traits@D@std@@V?$allocator@D@2@@std@@XZ

lib 库中经过名字改编后的符号名是

?GetStaticData@@YAAAV?$basic_string@DU?$char_traits@D@std@@V?$allocator@D@2@@std@@XZ

如果仔细看,确实能发现这两个输出结果是不一样的,但是经过编译器处理的符号名非常不适合人类阅读。如果能看到函数原型就太好了。

其实,上面的输出结果既包含了函数原型,又包含了经过名字改编后的符号名。

vs 中的函数名:

class std::basic_string<char,struct std::char_traits<char>,class std::allocator<char> > __cdecl GetStaticData(void)

lib 库中的函数名:

class std::basic_string<char,struct std::char_traits<char>,class std::allocator<char> > & __cdecl GetStaticData(void)

可以发现,lib 库中提供的函数的返回值是引用类型的,而 vs 中函数的返回值是不带引用的。

至此就破案了!

undname 工具

如果只有经过名字改编后的符号名,可以通过微软提供的 undname.exe 工具来翻译成人类友好的名称。

compare-undname

示例工程

可以到 gitee 上下载示例工程。

总结

dumpbin.exe 可以查看 lib 库中的符号信息

undname.exe 可以非常方便的查看未经过编译器处理的函数名

BianChengNan wechat
扫描左侧二维码关注公众号,扫描右侧二维码加我个人微信:)
0%