1

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

 7 months ago
source link: https://bianchengnan.gitee.io//articles/troubleshoot-LNK2001-unresovled-external-symbol-error/
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

最近,在加班的过程中遇到一个链接错误 —— 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 可以非常方便的查看未经过编译器处理的函数名


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK