[原创]做了个生成DLL导入库的工具(1月28日更新)
发表于:
2014-1-26 22:23
11019
[原创]做了个生成DLL导入库的工具(1月28日更新)
题外话:不喜欢这个账号名了想换个,结果注册完“空雪梦见”以后发现一时半会儿死活搞不成正式会员,放弃挣扎了……
使用此工具需要对链接过程中导入DLL中的函数会遇到的几种符号有基本常识,举个例子就是要知道 Sleep、_Sleep@4、__imp__Sleep@4 是什么。
目的 正常情况下编译器生成DLL的时候都能顺便给你出一个LIB,这个LIB在编译的时候作为导入库使用。但是我发现一些情况下只拿到DLL没拿到LIB,或者拿到的LIB和当前编译器不兼容无法使用,这样的时候就需要制作一个LIB,否则就只能GetProcAddress,感觉很麻烦。
.
现状 制作LIB的方法确实已经烂大街了,最经典的是从DEF文件用LIB.EXE(或者LINK.EXE /LIB)工具生成导入库的。这种方法用确实可以用,但是有个问题,像Windows API那样的,链接的时候不是找__imp__Sleep@4就是找_Sleep@4(根据有没有__declspec(dllimport)而定),但是DLL里导出的是Sleep。你要不按照序号导入要不就链接失败。这种做LIB的方法根本不认你在DEF里写的 xxx = xxx 那个等号的样子。囧了个囧。
还有个方法是自己写个空白的DLL,里面导出这些函数,然后写个DEF,链接以后能得到一个LIB。感觉这个方法很山寨啊,一点都不高端。而且说不出的总觉得它是不是哪里会不够灵活……
.
相关工作 我一直在网上找了这么久找到的救星,传说中的ImpLib SDK,利用FASM的预处理器,直接从代码生成一个Binary文件,改个名就是LIB了。看似完全满足我的需求,确实也一时解决我的需求,但是用着用着发现它的不足:DLL文件名好像不支持很长的那种?明明文件名还短它就已经开始抱怨了,出不来LIB了,我觉得DLL文件名这种东西,长一点很正常吧?叹
再就是我以前做过的某种事情,利用NASM汇编器生成OBJ文件,达到重命名链接符号的目的。原理不能更简单,就是JMP。同样是方法很山寨,然后要怎么把它塞入LIB文件,塞入GCC的LIB还好,塞入VC的LIB总之我是没成功。虽然可以单独生成一个LIB,链接的时候和导入库一块儿,但是……更山寨了!
然后,然后就是我没再找到能干这种事的东西了。
.
设计与实现 最早我很乐观,学一下COFF文件结构,分析一下导入库文件,模仿做一个。但是事实上,做出现在这个工具,花了我整整半个月 时间。Oh my god,而且在1月16、17日还出现过一次写了一个下午代码傍晚要去吃饭的时候从椅子上站起来伸个懒腰,直接眼前一黑整个人扑地上了(这体制……)。嘛不过总之,是做出来了。
最早从COFF文件入手,分析了一下VC搞出来的LIB,发现它就是 .idata$2 塞了个IMAGE_IMPORT_DESCRIPTOR,然后 .idata$3 塞了个空白的描述符(代表导入描述符数组的结束),在 .idata$4 和 .idata$5 里塞着OriginalFirstThunk和FirstThunk指向的东西,.idata$6就存放DLL的文件名和PE加载器加载的时候查找函数要用的IMPORT_BY_NAME。因为链接的时候会按照Section名的$后的数字进行排序,所以很容易就组成了数组。
现在想想,我简直太乐观了……
对VC生成的LIB一分析,后面有十分诡异的Obj文件,一看说明,是特别的导入格式。最终我没有采用这种方式……
然后我参考GCC生成的LIB,做了好久,一直没有成功。到某天脑子一抽,用VC去链接这个GCC生成的LIB,呸,这不是明摆着失败了吗,囧了个囧。
之后参考ImpLib SDK,我也照着做,感觉都快要成功了,弄了一堆OBJ文件然后用LINK.EXE /LIB来生成LIB文件,DumpBin出来的信息和ImpLib SDK生成的LIB几乎完全一致!但是,它的可以用我的不能用。呸
直到某天早上起来,又脑子一抽,把ImpLib SDK生成的LIB里的每个Object都拆出来,再用LINK.EXE /LIB弄回去,嘿小样儿这回你也不能用了吧(殴
于是我大概知道问题出在LIB里的Member name了。
再然后我做出来的LIB也能用了。
.
经验
LIB文件里各个成员的顺序,按照 导入描述符——空白描述符——导入函数1——导入函数2——……——空白Thunk 的顺序下来。其实是不是这个顺序我没测试过,因为最初不知道Member Name必须一样的时候,我就已经尝试过顺序了。到后来成功了也就没测试过其他顺序。 .
Object文件中符号表里,Section的定义要在它里面的符号出现之前,特别是COMDAT的Section(不是COMDAT的还没遇上过它出错,没测试)。不然链接的时候报错。 .
LIB里的Object文件似乎起始位置(文件指针)都是偶数,遇上奇数的会加上0x0a补成偶数。这个在微软给出的参考资料里貌似没说明(或者我没找到)?但是我看它自己(ImpLib SDK也)是这么做的 .
FirstLinkMember虽然文档中说可以不要,但是我弄个空的FirstLinkMember进去它就什么符号也不给我出来了。是我实验的时候其他地方错了吗?还是说其实它本来就不能省略的? .
FirstLinkMember里Offset表要递增排序,SecondLinkMember里Offset表要递增排序,字符串表也要递增排序。没有递增排序会怎么样?没实验但是有一点,ImpLib SDK生成的FirstLinkMember貌似Offset表没有递增排序诶!
.
参考资料 关于这东西我写的博文,末尾有静态链接的版本下载
http://blog.sora.info.tm/?p=372
.
COFF和LIB的格式
http://msdn.microsoft.com/library/windows/hardware/gg463119.aspx
.
链接时导入表的构成
http://www.microsoft.com/msj/0498/hood0498.aspx
.
ImpLib SDK工具
http://sourceforge.net/projects/implib
.
.
附加说明 DLL文件的资源里我都附上了相应的头文件。虽然本来想说如果要二次开发可以直接引用,但是想想看弄清楚那些个函数到底是干嘛用的可能要很多时间,弄清它们的调用顺序可能又要很多时间,……好吧其实我没什么项目框架构建经验或者开发经验,还是学生来着……
开发环境是Visual Studio 2013 Express,ATL 7.1(从Windows Driver Kit里来的)
编译工具集用了支持Windows XP的设置。
这个工具生成的LIB是给X86架构用的,X64下貌似没有这种修饰名的问题,没有的话直接从DEF生成LIB就能用了。
.
关于源代码 暂时还不想放出,可以吗……
.
来个应用场景。
Windows API里有时会遇到输入流要求送一个IStream*进去的情况。此时经常会有调用SHCreateStreamOnFileEx或者CreateStreamOnHGlobal。我当年在公司实习的时候,就看到很多创建一个HGLOBAL然后把手头一块内存拷过去然后创建Stream的代码。实际上shwapi.dll里是有SHCreateMemStream的但是在Windows Vista之前,甚至连shwapi.h里都没有,要调用的话只能LoadLibrary然后GetProcAddress。
为了能直接通过导入表方式导入这个函数,就可以使用这个工具。
写一个如下的XML:
<ImportLibrary>
<DllName>Shlwapi.dll</DllName>
<Import>
<LinkName>__imp__SHCreateMemStream@8</LinkName>
<Ordinal>12</Ordinal>
</Import>
</ImportLibrary>
保存成shcms.xml,然后
MakeImpLib.exe shcms.xml shcms.lib
就得到了一个shcms.lib,链接的时候链上去就可以了。
Import节点里支持这几种子节点:
LinkName:必须要,指向IAT里函数地址的符号
StubName:可选,导出的函数(不是函数指针)符号,没有__declspec(dllimport)的时候可以链接
Ordinal:DLL中导出函数的序号
ImportName:DLL中导出函数的名称,和Ordinal两者任选一个,同时存在则优先ImportName
然后就只要写个函数声明,带__declspec(dllimport)的,就可以链接了(必要时候别忘了extern "C")
GCC生成的C++的DLL拿给VC用大概也是一种应用场景,但是GCC的修饰名转换到VC的修饰名到底有什么规则我搞不清楚,于是也就作罢了。大概等到链接的时候出错了从出错信息里获取修饰名是一种办法?……
1月27日傍晚更新:
头文件加了点注释,DLL资源里打包了Readme进去
1月28日上午更新:
可以支持同时使用Ordinal和Name的方式导入,实际上导入还是靠Name,Ordinal作为hint,具体看不带参数时makeimplib的输出
ImpGen和LibGenHelper的接口添加了新方法
[培训]内核驱动高级班,冲击BAT一流互联网大厂工作,每周日13:00-18:00直播授课
上传的附件: