缘起
最近又遇到了一个程序功能不正常的问题,深入调查后发现与全局变量初始化顺序有非常大的关系,只不过这次更加隐蔽。
之前总结了两篇与全局变量初始化顺序有关的文章,感兴趣的小伙伴儿可以参考《调试实战 | dll 加载失败之全局变量初始化篇》 和《调试实战 | 全局变量初始化顺序探究》。
在排查错误之前先简单介绍一下相关代码。
示例程序
示例程序一共包含4
个工程:LoadDlls, dll1, dll2, dll3
。
主程序
LoadDlls.exe
会加载dll1.dll
dll1.dll
隐式依赖了dll2.dll
,所以dll1.dll
加载的时候会自动加载dll2.dll
dll2.dll
中的全局变量s_culprit
的构造函数会加载dll3.dll
dll3.dll
加载的时候会自动调用dll2.dll
的导出函数RegisterInitCallback()
和RegisterCallback()
下面是每个工程的关键代码
src/common/autorunner.h
该文件是公共头文件,实现了自动注册逻辑
// autorunner.h #pragma once class AutoRunner { public: AutoRunner(void (*func)()) { func(); } }; #define STR_CAT(s1, s2) s1 ## s2 #define NAME_WITH_LINE(name, line) STR_CAT(name, line) #define BEGIN_AUTO_RUN static AutoRunner NAME_WITH_LINE(s_auto_runner_, __LINE__) ([](){ #define END_AUTO_RUN });
LoadDlls
该工程只有一个源文件,用来模拟加载各种插件。对应的源码如下:
// LoadDlls.cpp #include "windows.h" #include <iostream> #include <map> #include <vector> typedef void (*PFN_Init)(); std::map<std::string, HMODULE> LoadPlugins(const char* plugins[]) { std::map<std::string, HMODULE> result; for (int idx = 0; ; ++idx) { constchar* plugin = plugins[idx]; if (plugin == nullptr) { break; } HMODULE module = LoadLibraryA(plugin); if (module == nullptr) { DWORD lastError = GetLastError(); std::cout << "[+] load [" << plugin << "] failed with error " << lastError << std::endl; } else { result[plugin] = module;