惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

T
The Exploit Database - CXSecurity.com
S
Schneier on Security
Google Online Security Blog
Google Online Security Blog
The Hacker News
The Hacker News
T
Threatpost
C
CERT Recently Published Vulnerability Notes
Help Net Security
Help Net Security
D
Darknet – Hacking Tools, Hacker News & Cyber Security
The Last Watchdog
The Last Watchdog
AI
AI
Exploit-DB.com RSS Feed
Exploit-DB.com RSS Feed
cs.AI updates on arXiv.org
cs.AI updates on arXiv.org
Cyberwarzone
Cyberwarzone
T
Threat Research - Cisco Blogs
G
GRAHAM CLULEY
L
LINUX DO - 热门话题
cs.CV updates on arXiv.org
cs.CV updates on arXiv.org
Spread Privacy
Spread Privacy
Scott Helme
Scott Helme
阮一峰的网络日志
阮一峰的网络日志
V
V2EX
Know Your Adversary
Know Your Adversary
WordPress大学
WordPress大学
AWS News Blog
AWS News Blog
T
Troy Hunt's Blog
Microsoft Azure Blog
Microsoft Azure Blog
钛媒体:引领未来商业与生活新知
钛媒体:引领未来商业与生活新知
Hacker News: Ask HN
Hacker News: Ask HN
小众软件
小众软件
Cisco Talos Blog
Cisco Talos Blog
有赞技术团队
有赞技术团队
H
Heimdal Security Blog
U
Unit 42
cs.CL updates on arXiv.org
cs.CL updates on arXiv.org
大猫的无限游戏
大猫的无限游戏
F
Fortinet All Blogs
C
CXSECURITY Database RSS Feed - CXSecurity.com
S
SegmentFault 最新的问题
Forbes - Security
Forbes - Security
Security Latest
Security Latest
腾讯CDC
Security Archives - TechRepublic
Security Archives - TechRepublic
I
Intezer
Threat Intelligence Blog | Flashpoint
Threat Intelligence Blog | Flashpoint
P
Proofpoint News Feed
A
Arctic Wolf
L
LINUX DO - 最新话题
Engineering at Meta
Engineering at Meta
C
Cisco Blogs
Recent Announcements
Recent Announcements

博客园 - 泉子

算法题:求一个序列S中所有包含T的子序列(distinct sub sequence) Django模板输出Dict所有Value的效率问题 Windows Restart Manager 重启管理器 advapi32.dll kernel32.dll 中的两套注册表API 使用GDI+保存带Alpha通道的图像(续) 使用GDI+保存带Alpha通道的图像 窗口销毁的相关函数与消息,ActiveX控件的销毁 为什么不能将ptr_fun(operator delete)传递给For_Each 两个数之间的最短路径问题 C/C++函数中局部对象的构造与析构时机 四年记——身在中小企业 C++ Const总结 [转载]怎样用VB编写.DLL动态链接库文件 Visual Studio 6.0编译PWLib1.12总结 在Windows上编译PWLib 金山词霸没落了么? SendInput模拟键盘输入的问题 数组变量在取地址时回归:不再是指针 创建用于非Visual C++工具的DLL——VB调用C++ DLL为何必须使用def文件 - 泉子
笔记:LNK2001不代表链接器真的需要链接相关符号
泉子 · 2015-04-24 · via 博客园 - 泉子

环境:VS2008

我们都知道,链接器在生成可执行程序时,会忽略那些没有用到的符号。但是昨天遇到一个链接问题,看起来与这条基本策略并不相符。首先看一个静态链接库的结构:

                lib
                 |
      |---------------------|
    a.cpp                 b.cpp
      |                     |
  |-------|           |-----------|
fun1     fun2       fun3         fun4
  |       ↑___________|
  ↓
GetModuleFileNameEx(psapi.lib)

这个库里只存在两个依赖:b.cpp中的fun3依赖于a.cpp中的fun2,a.cpp中的fun1依赖于psapi.lib中的GetModuleFileNameEx。

我在一个app中使用了fun4,除此之外别无其它,根据开头提到的策略,显然我并不需要链接psapi.lib。但是事实并非如此,链接器提示了错误:

error LNK2001: 无法解析的外部符号 _GetModuleFileNameExW@16

经过反复测试确认,正是fun1所依赖GetModuleFileNameEx导致了链接错误。这看起来很不可思议,fun4对fun1并没有任何依赖关系,链接器为何会报告错误?

就我的经验来说,链接器会使用这项策略肯定是毋庸置疑的,最有可能的,是我们存在某个认知错误。所以我决定验证一下,这里的验证分为两部分:

一、链接器要求解析一个符号是否意味着链接器需要在生成的PE文件中包含相关符号的代码?

遇到LNK2001错误时,我们的第一感觉是链接器需要在生成的程序中包含这个符号,也就是:

1. 如果该符号在静态库中,那么会把该符号相关的代码包含到PE文件中;

2. 如果该符号在动态库中,那么需要把该符号记录到导入表中,以使相关的动态库会在程序启动时加载到进程中并进行地址映射;

但是链接器对一个显然没有依赖关系的函数中的符号提示了LNK2001错误,这让我对之前的“感觉”产生了怀疑:或许链接器要解析一个符号并不意味着它会在生成的可执行程序中包含相关的代码。用一个简单的对比试验就能知道结果:

#include <windows.h>
#include <psapi.h>

#pragma comment(lib, "psapi.lib")

void fun_infile_unuse()
{
    TCHAR buf[MAX_PATH];
    GetModuleFileNameEx(GetCurrentProcess(), NULL, buf, MAX_PATH);
}

void fun_infile_use()
{
    OutputDebugStringA("fun_infile_use\r\n");
}

int _tmain(int argc, _TCHAR* argv[])
{
    fun_infile_use();
    //fun_infile_unuse();

    getchar();
    return 0;
}

首先,在这段程序里,不管有没有调用fun_infile_unuse函数,#pragma comment(lib, "psapi.lib")都是不可缺少的,否则链接器会提示LINK2001错误。

但是,为调用与不调用fun_infile_unuse两种情况分别编译两份程序,用LoadPE查看PE文件的导入表,可以看到:只有调用了fun_infile_unuse的那份程序的导出表中存在psapi.dll的条目,另一份程序的导出表中则没有。分别运行两份程序,用process explorer查看它们加载的库列表,也可以看到:没有调用fun_infile_unuse的那份程序,运行起来并不会去加载psapi.dll。 —— 图就不贴了,有兴趣的自己验证。

显然,如果在不调用fun_infile_unuse的程序中不会存在psapi.dll的导入表项的话,那么,它也应该不会在程序中包含fun_in_fun_unuse的代码。

这证实了我的怀疑。也就是说:链接器需要解析一个符号,并不意味着它真的需要“链接”这个符号。而链接器在真正链接符号生成程序时,确实会遵循“忽略未使用过的符号”的原则。

不过这还没有完,当我们得到这个结论后,也就意味着,链接器在查找符号时,并不是按照调用上的依赖关系来进行遍历的。那么,链接器是按照什么关系来遍历符号的呢?这是下一个问题。

二、链接器依据怎样的关系来确定要“解析”的符号范围?

我不打算对这个问题做严格的推理,只是用几个测试来对比验证我的一个猜想。这些测试用例分别是:

1.        app
           |
        main.cpp
 |---------|---------|
fun1     fun2       main
 |         ↑_________|
 ↓
GetModuleFileNameEx(psapi.lib)
2.        app
           |
 |-----------------|
other.cpp        main.cpp
 |                 |
 |            |---------|
fun1        fun2       main
 |            ↑_________|
 ↓
GetModuleFileNameEx(psapi.lib)
3.   lib            app
      |              |
    a.cpp          main.cpp
      |              |
  |-------|          |
fun1     fun2       main
  |       ↑__________|
  ↓
GetModuleFileNameEx(psapi.lib)
4.              lib                         app
                 |                           |
      |---------------------|                |
    a.cpp                 b.cpp            main.cpp
      |                     |                |
  |-------|           |-----------|          |
fun1     fun2       fun3         fun4       main
  |                               ↑__________|
  ↓
GetModuleFileNameEx(psapi.lib)
5.              lib                         app
                 |                           |
      |---------------------|                |
    a.cpp                 b.cpp            main.cpp
      |                     |                |
  |-------|           |-----------|          |
fun1     fun2       fun3         fun4       main
  |       ↑___________|           ↑__________|
  ↓
GetModuleFileNameEx(psapi.lib)

对上述几个用例的测试结果如下:

用例(编号)  :    1        2        3         4        5

是否LINK2001 :    是       是       是         否       是

注:在我做的真实测试中,还对命名空间的包含关系做了测试,结果发现没有影响,从命名空间的涵义来说,也不应该有影响,所以相关测试没有列进来。

我们都知道,静态链接库是由一组obj组成的,而obj与cpp是一一对应的。所以这里有一个推测:链接器以根据调用关系来搜索符号,但是在处理时是以obj为节点单位的。

1. 对project内的所有cpp(obj),链接器要求解析其中所有使用到的符号。

2. 对外部库,如果使用了其中符号,则定位该符号所在obj,并要求解析该obj中的所有符号。以此类推。

完。

第二部分是不严谨的测试,也不打算进一步测试,因为需要确认的只是第一部分。如果有对第二部分的内容感兴趣的,那我非常期待看到你的结论:yedaoq@126.com