首页
社区
课程
招聘
[原创]做了个生成DLL导入库的工具(1月28日更新)
发表于: 2014-1-26 22:23 11019

[原创]做了个生成DLL导入库的工具(1月28日更新)

2014-1-26 22:23
11019
题外话:不喜欢这个账号名了想换个,结果注册完“空雪梦见”以后发现一时半会儿死活搞不成正式会员,放弃挣扎了……
使用此工具需要对链接过程中导入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直播授课

上传的附件:
收藏
免费 0
支持
分享
最新回复 (10)
雪    币: 123
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
2
先支持下!
2014-1-27 08:59
0
雪    币: 13186
活跃值: (4246)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
3
你这个号买邀请码就好了。。。
2014-1-27 09:43
0
雪    币: 37
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
4
我觉得你说得非常有道理!

编辑:擦!本来还想删掉那个用这个再发一次,发现pediy论坛不能删自己的帖。
好吧那也不敢再用这个发一次了……
2014-1-27 10:25
0
雪    币: 55
活跃值: (519)
能力值: ( LV6,RANK:80 )
在线值:
发帖
回帖
粉丝
5
说,头发上的水滴是怎么回事。
2014-1-27 11:20
0
雪    币: 319
活跃值: (2439)
能力值: ( LV12,RANK:980 )
在线值:
发帖
回帖
粉丝
6
测试了一下,没有成功。不知是不是我的方法不对。请楼主写个例子测试一下。
2014-1-27 18:19
0
雪    币: 150
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
7
好,比如你有一个这样的doadd.c文件,内容是
__declspec(dllexport) int __stdcall add(int a, int b) { return a + b; }

用gcc编译,
gcc -s -odoadd.dll -shared doadd.c

得到doadd.dll,导出的函数名是 add@8

然后你有一个这样的calladd.c文件,内容是
int __stdcall add(int, int);
#include <stdio.h>
int main() {
  printf("%d\n", add(10, 20));
  return 0;
}

编译的时候add用导入库的方式链接,所以需要一个vc能用的导入库。
此时需要导入的函数名是 add@8,链接的时候需要的符号是 _add@8
写一个这样的xml:
<ImportLibrary>
  <DllName>doadd.dll</DllName>
  <Import>
    <LinkName>__imp__add@8</LinkName>
    <StubName>_add@8</StubName>
    <ImportName>add@8</ImportName>
  </Import>
</ImportLibrary>

保存成doadd.xml,用这个工具
MakeImpLib doadd.xml doadd.lib

得到一个doadd.lib文件,然后用vc编译calladd.c
cl calladd.c doadd.lib

得到调用doadd.dll进行加法计算然后显示结果的calladd.exe

p.s. 甚至能支持汉字函数名哦,
<?xml version="1.0" encoding="UTF-8"?>
<ImportLibrary>
  <DllName>doadd.dll</DllName>
  <Import>
    <LinkName>__imp__add@8</LinkName>
    <StubName>_加法@8</StubName>
    <ImportName>add@8</ImportName>
  </Import>
</ImportLibrary>

然后
int __stdcall 加法(int, int);
#include <stdio.h>
int main() {
  printf("%d\n", 加法(10, 20));
  return 0;
}

在VC2013下能正常XD
2014-1-27 19:12
0
雪    币: 319
活跃值: (2439)
能力值: ( LV12,RANK:980 )
在线值:
发帖
回帖
粉丝
8
谢谢回复。我拿一个现成的DLL来测试,用ImpLib-1.8生成的LIB能正常使用,而用你的工具生成时编译错误。
另外,你的工具不能自动生成XML文件,手工写比较麻烦。
不好意思,挑剔了
2014-1-27 19:58
0
雪    币: 37
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
9
我的工具生成的编译时候出错误……具体是什么错误?
如果是无法解析的外部符号,那么是因为XML里给出的符号不正确。
可以使用 dumpbin.exe /symbols 查看生成的lib文件导出了什么符号。
如果是其他错误,比如说lib文件结构不正确、lib里的object文件结构有问题之类,希望报告一下,我好发现和修复bug。

我最早也是一直用implib sdk的,后来发现它有点局限性,之后决定自己做一个。
虽然他是开源的但是因为源代码实在是很难看懂,都是靠预编译来完成的,而且似乎有很多的重复代码,看起来实在头痛,加上对fasm汇编器的不熟悉,就放弃了对其源代码进行修改达到自己目的的途径,转为自己重新开发一套来满足这样的需求。
2014-1-27 20:34
0
雪    币: 319
活跃值: (2439)
能力值: ( LV12,RANK:980 )
在线值:
发帖
回帖
粉丝
10
你测试一下我上传的附件DLL,应该能重现问题。
上传的附件:
2014-1-27 21:29
0
雪    币: 150
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
11
我用OD看了一下,这个DLL里导出的两个函数都是没有参数的,而且还没有返回值,作为stdcall或者cdecl都行。我当作stdcall处理。

我可以从cpp代码生成exe文件,导入你的dll里的两个函数。
截图质量很差是在想大概可以给看雪省点附件空间(何?)

我想你应该没有误解这个工具的作用吧……它是生成导入库,就是vc生成dll的时候会配给你的那个lib,生成和那个功能一样的lib,供弄丢lib的人(或者网上下的dll没有lib,或者其他编译器生成的dll所以lib不能给vc用,等情况)开发时候使用,不是给exe的导入表添加导入项之类。

哦对了有个问题我还没解释就是为什么输入是xml,因为我发现解析输入文件很麻烦(锤锤锤)文件编码问题,之类之类。所以最后发现xml比较省事。如果需要其他前端的话,可以自己写一个,我的libgenhelper.dll的资源里可以拆出一个readme和两个头文件,然后coffgen.dll里可以拆出前两个头文件依赖的另一个头文件(这个你可以不看),提供的接口我觉得应该是非常简单了,作为生成lib文件用的后端自己配一个前端就好。
现在暂时没有太好的想法要把前端做成什么样,就先暂时用xml……
上传的附件:
2014-1-28 11:01
0
游客
登录 | 注册 方可回帖
返回
//