探索库文件:隐藏函数和头文件生成器

在软件开发过程中,经常需要了解一个库文件(.lib)导出了哪些函数,以及如何验证这些函数的语法是否与提供的头文件一致。有时,库文件可能包含一些在头文件中未列出的隐藏函数。本文介绍的工具可以帮助开发者解决这些问题。

在Visual Studio中,可能会在依赖查看器(Depends)中看到一些奇怪的字符代替C++函数名称,这种现象称为“名称修饰”(Name Mangling)。C++编译器为了在名称中包含类型信息,会对C++程序中的符号名称进行编码,以便链接器能够检查确切的函数并确保类型安全的链接。而C编译器不需要这样做,因为在整个可执行文件链接中只能存在一个同名函数。然而,在C++中,不同类中可能存在多个同名函数,因此C++需要名称修饰来解析函数名称。

本文介绍的工具正是利用了编译器的这一特性。工具的作用是收集库文件中的修饰名称,并将其解码为未修饰名称。未修饰形式就是在普通头文件中看到的形式。

名称修饰

Microsoft Visual C++编译器以一种特定的方式对C++程序中的符号名称进行编码。这种编码方式并未公开,是编译器的一个秘密。但在某些情况下,可能需要未修饰的名称。为此,Microsoft提供了一些API来解码,这些API包含在“Debug Help Library”(DbgHelp.DLL)中。

C++修饰的符号以'?'字符开始。在处理LIB文件时,如果收集以'?'开始并以NULL字符结束的符号,就可以得到完整的修饰名称。将这个名称传递给“Debug Help Library”中的UnDecorateSymbolName函数,就可以得到未修饰的名称。例如,如果输入:

?MyFunc@@YAHD@Z

将得到:

int __cdecl MyFunc(char)

C符号与C++符号不同,它总是在导出函数前加上"__imp__"标记,并且不提供参数类型说明,只提供参数字节数。例如:

int __stdcall func(int a, double b)

编码后看起来像这样:

__imp__func@12

因此,在C库中,不可能从LIB文件中提取头文件。这里只能检索函数名称,即通过收集"__imp__"标记到'@'字符。这就是工具所做的。

核心代码

下面是一个封装了LIB文件符号提取的类CLibContent的代码示例。当传入LIB文件路径时,它会提取并存储修饰和未修饰的名称到一个字符串数组中,以便后续检索。

HANDLE hLibFile = CreateFile( "C:\\Libfiles\\adme.lib", GENERIC_READ, FILE_SHARE_READ|FILE_SHARE_WRITE, NULL, OPEN_EXISTING, 0, NULL); for (i = 0; i < dwFileSize; i++) { SetFilePointer(hLibFile, i, 0, FILE_BEGIN); nRet = ReadFile(hLibFile, &szChar, 32, &dwBytes, NULL); if (!nRet) return GetLastError(); if ((szChar[0] == '?') || (bCollect)) { // the symbol is C++ type collect it szBuff[dwBuffPos] = szChar[0]; dwBuffPos++; bCollect = true; } else if (!memcmp(szChar, C_STYLE, 7)) { // check if it is C style (ie. "__imp__") dwBytes = strlen(szChar); nRet = ExtractCSymbol(szChar, dwBytes); // if it is succ then 0 else non zero if (!nRet) // if failed then pass on to next char i += dwBytes; // if passed skip the string } if ((szChar[0] == 0) && (bCollect)) { // finish and pass the buffer to decode szBuff[dwBuffPos] = szChar[0]; dwBuffPos++; nRet = ExtractCppSymbol(szBuff, dwBuffPos); dwBuffPos = 0; bCollect = false; } }

以下函数用于解码CPP风格的修饰名称:

int CLibContent::ExtractCppSymbol(char *szDecoratedName, DWORD dwLen) { char szFunc[512]; int nRet; nRet = UnDecorateSymbolName(szDecoratedName, szFunc, 512, UNDNAME_COMPLETE|UNDNAME_32_BIT_DECODE); if (!nRet) return GetLastError(); // failure if (!memcmp(szDecoratedName, szFunc, nRet)) return ERROR_INVALID_DATA; // since it is not decoded, this could be some junk if (!memcmp("`string'", szFunc, nRet)) // this could be imported functions and constants return ERROR_INVALID_DATA; // so we don't need this // storing strings in an array and returning nothing else return m_cMemStore.Add(szDecoratedName, szFunc); }

以下函数用于解码C风格的修饰名称:

int CLibContent::ExtractCSymbol(char *szDecoratedName, DWORD dwLen) { char szFunc[512]; DWORD dwBuffPos; int i = 0; dwBuffPos = 0; if (memcmp(szDecoratedName, C_STYLE, 7)) return ERROR_INVALID_DATA; i += strlen(C_STYLE); for (; i < dwLen; i++) { szFunc[dwBuffPos] = szDecoratedName[i]; if (szFunc[dwBuffPos] == '@') { szFunc[dwBuffPos] = 0; // storing strings in an array nothing else m_cMemStore.Add(szDecoratedName, szFunc); return ERROR_SUCCESS; } dwBuffPos++; } return ERROR_INVALID_DATA; }

注意事项

在开发过程中,尝试了BSC SDK(Browser Toolkit)。这是一个有用的SDK,当想要浏览由VC++生成的BSC(浏览器信息)文件时。VC++ IDE使用这个文件来指出函数或变量定义,当右键点击并点击“Go to Definition of ...”菜单项时。使用这个SDK,可以获取项目中使用的符号的详细信息。这个工具包中有一个函数,可以从BSC文件中获取特定修饰名称的未修饰符号。类似于UnDecorateSymbolName函数。

限制

在考虑LIB文件时,似乎很简单,所有的都在同一类别中。事实并非如此。每个编译器和每个版本的编译器可能会产生不同类型的LIB文件和名称修饰。在这个背景下,这个工具可能/可能不会在VC++ 6.0之外的编译器生成的LIB文件上工作。

在导出的头文件中,属于类的函数不会以类的形式出现。它将更像是一个完整的函数。另外,C函数的参数无法检索或导出到头文件中。

如果想了解VC++修饰格式,请查看这个网站。它提供了VC++ 6.0的名称修饰格式。需要注意的是,这个格式可能会在没有任何通知的情况下更改。也不确定这个工具是否会在VC++ 7.0的LIB文件上工作。如果有人可以测试并告诉,那将很有帮助。在由eVC++ 3.0、eVC++ 4.0和VC++ 6.0编译器生成的LIB文件上测试了这个工具。

沪ICP备2024098111号-1
上海秋旦网络科技中心:上海市奉贤区金大公路8218号1幢 联系电话:17898875485