GetFileVersionInfo 获取文件版本信息错误原因分析
来源:互联网 发布:nvh分析软件 编辑:程序博客网 时间:2024/05/18 01:45
概述
这两天遇到一个奇怪的问题:在一个进程中,通过GetFileVersionInfo去获取一个绝对路径文件的版本号时, 实际路径对应文件根本不存在,却获取到了版本号信息。在仔细分析GetFileVersionInfo内部实现后,真相终于大白,为了以后能更好的使用这个API,故把分析过程记录下来。
问题背景
一进程在判断是否需要打补丁时,需要去读取文件的版本号,读取路径是从一个库中配置的。在测试的库中,配置的条件是判断C:\Windows\system32\gdiplus.dll的版本号小于某个补丁,就说明用户机器上面需要安装该补丁。
该进程运行后,分析到库的条件时,就会通过GetFileVersionIno获取C:\Windows\system32\gdiplus.dll模块的版本号来进行判断, 实际上在C:\Windows\system32\目录下面是不存在gdiplus.dll文件,但是GetFileVersionInfo这个函数却返回了成功,而且可以通过VerQueryValue能够查询出版本号, 从而导致扫描出的结果不符合预期。
为什么GetFileVersionInfo获取不存在路径的版本号信息会出错呢?
查找原因
查看msdn,GetFileVersionInfo:
GetFileVersionInfo function
Retrieves version information for the specified file.
Syntax
BOOL WINAPI GetFileVersionInfo(
_In_ LPCTSTR lptstrFilename,
_Reserved_ DWORD dwHandle,
_In_ DWORD dwLen,
_Out_ LPVOID lpData
);
Parameters
lptstrFilename [in]
Type: LPCTSTR
The name of the file. If a full path is not specified, the function uses the search sequence specified by the LoadLibrary function.
从msdn 来看,如果输入不是全路径,则会通过LoadLibrary去搜索标识的文件名。
进一步查看GetFileVersionInfo内部实现:
BOOLAPIENTRYGetFileVersionInfoA( LPSTR lpstrFilename, DWORD dwHandle, DWORD dwLen, LPVOID lpData ){ UNICODE_STRING FileName; ANSI_STRING AnsiString; NTSTATUS Status; BOOL bStatus; RtlInitAnsiString(&AnsiString, lpstrFilename); Status = RtlAnsiStringToUnicodeString(&FileName, &AnsiString, TRUE); if (!NT_SUCCESS(Status)) { SetLastError(Status); return FALSE; } <span style="color:#ff6666;"> bStatus = GetFileVersionInfoW(FileName.Buffer, dwHandle, dwLen, lpData);</span> RtlFreeUnicodeString(&FileName); return bStatus;}
GetFileVersionInfoW的实现:
BOOLAPIENTRYGetFileVersionInfoW( LPWSTR lpwstrFilename, DWORD dwHandle, DWORD dwLen, LPVOID lpData ){ VERHEAD *pVerHead; VERHEAD16 *pVerHead16; HANDLE hMod; HANDLE hVerRes; HANDLE h; UINT dwTemp; BOOL bTruncate, rc; UNREFERENCED_PARAMETER(dwHandle); // Check minimum size to prevent access violations if (dwLen < sizeof(((VERHEAD*)lpData)->wTotLen)) { SetLastError(ERROR_INSUFFICIENT_BUFFER); return (FALSE); } dwTemp = SetErrorMode(SEM_FAILCRITICALERRORS); <span style="color:#ff0000;">hMod = LoadLibraryEx(lpwstrFilename, NULL, LOAD_LIBRARY_AS_DATAFILE);</span> SetErrorMode(dwTemp); if (hMod == NULL) { // Allow 16bit stuff __try { dwTemp = MyExtractVersionResource16W( lpwstrFilename, &hVerRes ); } __except( EXCEPTION_EXECUTE_HANDLER ) { dwTemp = 0 ; } if (!dwTemp) return (FALSE); if (!(pVerHead16 = GlobalLock(hVerRes))) { SetLastError(ERROR_INVALID_DATA); GlobalFree(hVerRes); return (FALSE); } __try { dwTemp = (DWORD)pVerHead16->wTotLen; if ((dwTemp * 3) > dwLen) { // // We are forced to truncate. // dwTemp = dwLen/3; bTruncate = TRUE; } else { bTruncate = FALSE; } // Now mem copy only the real size of the resource. (We alloced // extra space for unicode) memcpy((PVOID)lpData, (PVOID)pVerHead16, dwTemp); if (bTruncate) { // If we truncated above, then we must set the new // size of the block so that we don't overtraverse. ((VERHEAD16*)lpData)->wTotLen = (WORD)dwTemp; } rc = TRUE; } __except( EXCEPTION_EXECUTE_HANDLER ) { rc = FALSE; } GlobalUnlock(hVerRes); GlobalFree(hVerRes); return rc; } <span style="color:#ff0000;"> if (((hVerRes = FindResource(hMod, MAKEINTRESOURCE(VS_VERSION_INFO), VS_FILE_INFO)) == NULL) || ((pVerHead = LoadResource(hMod, hVerRes)) == NULL)) { rc = FALSE;</span> } else { __try { dwTemp = (DWORD)pVerHead->wTotLen; if (((dwTemp * 2) + sizeof(VER2_SIG)) > dwLen) { // We are forced to truncate. // // dwLen = UnicodeBuffer + AnsiBuffer. // // if we try to "memcpy" with "(dwLen/3) * 2" size, pVerHead // might not have such a big data... // dwTemp = (dwLen / 2) - sizeof(VER2_SIG); bTruncate = TRUE; } else { bTruncate = FALSE; } // Now mem copy only the real size of the resource. (We alloced // extra space for ansi) memcpy((PVOID)lpData, (PVOID)pVerHead, dwTemp); // Store a sig between the raw data and the ANSI translation area so we know // how much space we have available in VerQuery for ANSI translation. *((PDWORD)((ULONG_PTR)lpData + dwTemp)) = VER2_SIG; if (bTruncate) { // If we truncated above, then we must set the new // size of the block so that we don't overtraverse. ((VERHEAD*)lpData)->wTotLen = (WORD)dwTemp; } rc = TRUE; } __except( EXCEPTION_EXECUTE_HANDLER ) { rc = FALSE; } } <span style="color:#ff0000;">FreeLibrary(hMod);</span> return (rc);}
从win2k源码来看,
GetFileVersionInfoW先是检测输入BuffSize是否足够大, 然后直接通过LoadLibraryEx把lpwstrFilename作为DATAFILE进行加载; 如果加载成功则通过FindResource查找其模块资源,然后取出资源数据。
到了这里,我们应该可以得出结论,GetFileVersionInfo依赖于LoadLibraryEx函数的执行结果。对于Dll的加载,最重要的是要搞清其搜索路径;到哪里去搞清?当然是问问微软了!MSDN上面有一篇专门讲DLL搜索顺序的文章(Dynamic-Link Library Search Order http://msdn.microsoft.com/en-us/library/ms682586(v=vs.85).aspx)。
按照msdn的说法,应用程序可以通过完整路径,使用重定向,或者manifest来控制加载哪个dll, 否则对于输入是相对路径文件名,其搜索路径如下:
在安全DLL搜索模式开启的情况下,搜索顺序是:
1、应用程序EXE所在的路径。
2、系统目录。
3、16位系统目录
4、Windows目录
5、当前目录
6、PATH环境变量指定的目录
如果安全DLL搜索模式不支持或者被禁用,那么搜索顺序是:
1、应用程序EXE所在的路径。
2、当前目录
3、系统目录。
4、16位系统目录
5、Windows目录
6、PATH环境变量指定的目录
安全模式是由HKEY_LOCAL_MACHINE\System\CurrentControlSet\Control\Session Manager\SafeDllSearchMode这个注册表值控制。默认情况下是开启状态。对于Win XP默认是关闭,可以通过建立这样的注册表值来开启。
关于安全DLL搜索路径可以参考(Dynamic-Link Library Security
http://msdn.microsoft.com/en-us/library/ff919712(v=vs.85).aspx)
根据前面的分析: 该场景下,我们传递的是一个路径C:\Windows\system32\gdiplus.dll, 只有可能是使用了dll重定向,加载到了重定向目录的dll.
直接写测试程序,加载C:\Windows\System32\gdiplus.dll,而该目录下面并不存在GdiPlus.dll; 按常理应该加载失败;但它却直接读取的是C:\Windows\winsxs下面的文件
那如果C:\Windows\System32\目录下存在gdiplus.dll呢,拷贝一个模块到该目录,重命名为 gdiplus.dll, 它仍然是加载的C:\Windows\winsxs目录下面的gdiplus.dll:
再次测试了把gdiplus.dll放在C:\windows目录下,加载C:\Windows\gdiplus.dll, 能够成功加载到 C:\Windows\gdiplus.dll,
把gdiplud.dll放到应用程序的exe同目录,加载D:\Data\testLoad\Debug\gdiplus.dll成功加载到了D:\Data\testLoad\Debug\gdiplus.dll的模块。但是直接加载gdiplus.dll,则加载到了 C:\Windows\winsxs目录下面的gdiplus.dll。
总结下测试结果:
1. 采用full path加载dll, 如果是加载C:\Windows\system32目录下面,则会重定向去加载C:\Windows\winsxs目录下面的gdiplus.dll,其他情况都能正确加载到给定路径的文件。
2. 采用相对文件名gdiplus.dll加载, 尽管exe目录下面有gdiplus.dll,也会加载到C:\Windows\winsxs目录下面的gdiplus.dll.
也就是说,GetFileVersionInfo能够获取不存在的路径C:\Windows\System32\gdiplus.dll的版本号是因为触发了DLL加载的重定向机制,实际获取的是C:\Windows\winsxs目录下面对应文件的信息。
这个被称为side by side Assembly, msdn有一篇文章介绍(side-by-side component http://msdn.microsoft.com/en-us/library/dd408052(v=vs.85).aspx),这个东东的作用就是为了解决 以前windows上的“Dll 地狱”(参考附录参考链接) 问题才产生的新的DLL管理解决方案。大家知道,Dll是动态加载共享库,同一个Dll可能被多个程序所使用,而所谓“Dll 地狱”就是当不同程序依赖的Dll相同,但版本不同时,由于系统不能分辨到底哪个是哪个,所以加载错了Dll版本,然后就挂了。于是盖茨就吸取了教训,搞了一个程序集清单的东东,每个程序都要有一个清单,这个清单存在和自己应用程序同名的.manifest文件中,里面列出其所需要的所有依赖,这儿所列出的依赖可不是简单地靠文件明来区分的,而是根据一种叫做“强文件名”的东西区分的。
具体细节这里就不详细描述了, 直接参考链接就可以了。
总结
1. GetFileVersionInfo内部实现是通过LoadLibrary加载对应模块,并获取其Resource的信息来获取文件信息。
2. LoadLibrary加载DLL时会受到DLL重定向的影响,也就是说,如果确实需要判断系统盘\System32\目录的会重定向的文件的版本时,不能通过GetFileVersionInfo去获取版本信息,因为获取的是重定向路径的dll版本信息。只能采用其他方式获取。
附录
Dynamic-Link Library Search Order :
http://msdn.microsoft.com/en-us/library/ms682586(v=vs.85).aspx)。
side-by-side component :
http://msdn.microsoft.com/en-us/library/dd408052(v=vs.85).aspx
DLL HELL及解决办法:
http://wenku.baidu.com/link?url=zqy12i40ZhJOAmyWqBKWGy-nOiDtKQeWC6FdeLAt-Rc6ifRg44HP7iYOkyHNAeU5ZeZ_poGpvv5n7W_VSAeYvu_HsqL-4M6mJqzNMdemcnW
Supported Microsoft Side-by-side Assemblies
http://msdn.microsoft.com/en-us/library/aa376609(v=vs.85).aspx
Activation Contexts
http://msdn.microsoft.com/en-us/library/windows/desktop/aa374153(v=vs.85).aspx
- GetFileVersionInfo 获取文件版本信息错误原因分析
- 获取EXE版本信息 GetFileVersionInfo
- 利用WinAPI(GetFileVersionInfo)获取文件版本信息
- 利用WinAPI(GetFileVersionInfo)获取文件版本信息
- 如何使用API函数GetFileVersionInfo,获得版本信息
- 如何使用API函数GetFileVersionInfo,获得版本信息
- 获取文件版本信息
- 获取文件版本信息
- 获取文件版本信息
- 获取文件的版本信息
- c#获取文件版本信息
- win32获取文件版本信息
- C++获取文件版本信息
- GetFileVersionInfo
- Delphi 获取文件的版本等信息
- WINAPI 编程获取文件版本信息
- 从资源文件中获取版本信息
- 获取一个文件的版本信息
- jquery 学习插件
- 五, 监控oracle的等待事件(http://blog.csdn.net/47522341/article/details/4370491?reload)
- 谷歌互联网气球开始测试 面向巴西地区
- shader 3 rendering path
- 浅谈 js中parseInt函数的解析
- GetFileVersionInfo 获取文件版本信息错误原因分析
- 从相册,相机拍照,并对图片进行剪裁
- Python 学习之类(Class)
- libcurl 接口调用方式
- 应用 Valgrind 发现 Linux 程序的内存问题
- 浅谈MVC
- 队列的实现:链式队列
- jquery,Struts,json 登录验证实例
- 普林斯顿公开课 算法4-4:优先级队列的应用