Dll的链接使用细节
关于Dll
Dll。Exe 都是PE格式的二进制文件。
Dll相当于Linux操作系统下的so文件
1 基地址(Base Address)和相对地址(RelativeVirtual Address)
基地址(BaseAddress)和相对地址(Relative Virtual Address)是PE文件的概念。当PE文件被装载的时候。进程空间的起始地址就是基地址,这个值是PE文件里的Image Base的值。
在exe文件里,Image Base 的值是0x40 0000; 在Dll中,ImageBase的值是0x 1000 0000.
RVL是相对于基地址的偏移量。由于基地址可能被其它PE占用。所以会发生基地址的重定向。因此在PE中用到的地址都是RVL。
2 关于lib
Dll中生成的lib文件不包括代码和数据,它用来描写叙述dll的导出符号,在程序链接的时候用来找到Dll中的变量以及函数,它里面包括程序链接Dll时候所须要的导入符号以及“桩代码”。所以它起到一个黏合,胶水的作用。
3 导出函数以及符号到Dll中的方法
<1> 使用 declspec(dllexport)
<2> 使用def(模块定义)文件
4 导入Dll
在程序中使用dll用两种方法
<1>隐式载入
使用h文件。lib文件,dll文件。
这种话。lib文件起到了链接程序的作用。
<2>显式载入
使用LoadLibrary函数。GetProcAddress函数,FreeLibaray函数来显式载入。使用这样的方式的话比較麻烦。它首先须要定义一个函数指针。然后用GetProcAddress函数来依据函数的名字或者序号来返回函数的地址,最后还要释放库。
由于编译器可能对函数的名字进行了修饰,加上了各种前缀和后缀,所以在使用名字进行查找的时候可能会出现找不到的情形。
使用函数的序号呢。由于函数的序号可能会由于Dll的升级而产生了变化,这种话。使用序号来查找的也会出现难以预料的错误。
5 符号导出表
符号导出表提供了一个符号名和符号地址之间的映射关系,能够通过符号名来查找符号地址。
typedef struct_IMAGE_EXPORT_DIRECTORY {
DWORD Characteristics; //not use,always 0
DWORD TimeDateStamp; //file generatetime
WORD MajorVersion; //notuse,always 0
WORD MinorVersion; //notuse,always 0
DWORD Name; //model’sreal name
DWORD Base; //base
DWORD NumberOfFunctions; //maximumof order
DWORD NumberOfNames; //numberof Names
DWORD AddressOfFunctions; // RVA from base of image
DWORD AddressOfNames; // RVA from base of image
DWORD AddressOfNameOrdinals; // RVA from base of image
}IMAGE_EXPORT_DIRECTORY, *PIMAGE_EXPORT_DIRECTORY;
用dumpbin查看dll的信息,当中有这样一段例如以下所看到的:
Section contains the following exports forVCDll.dll
00000000 characteristics
52C0392A time date stamp Sun Dec 29 23:00:58 2013
0.00 version
1 ordinal base
3 number of functions
3 number of names
ordinal hint RVA name
1 0 0001113B ?mul@@YGHHH@Z
2 1 0001111D ?sub@@YAHHH@Z
3 2 000110C3 add
我们能够看到这个描写叙述和IMAGE_EXPORT_DIRECTORY是相应的。
Number of functions :
函数序号的最大值(所以不一定代表函数的个数)。假设你在def文件里指定一个序号为5的函数。那么即使编号为4的函数不存在。这时候Number of functions 依旧等于5
Number of Names :
AddressOfFunctions:
它指向EAT(Export Address Table),里面存放各个函数的RVA。
AddressOfNames:
它指向函数名字表。它是依照ASCII编码来排序的。
AddressOfNameOrdinals:
这个是函数名字和序号的对比表。
<1>序号的长处:
早期的计算机内存非常小。假设用函数名字表来保存函数时非常奢侈的事情,由于把几百个函数名字加在到内存中会占用不少的内存,为了解决问题就採取了序号的方法,每个序号相应一个函数,直接依据序号来找到相应的函数地址。
<2>序号的缺陷:
Dll在更新的时候,可能会导致函数的序号发生变化,所以假设可能会造成载入函数的错误。
<3>序号的必要性:
我们找到函数时给据序号来找的,所以序号是必须存在的,相反函数名字不一定是须要的,由于我们依据函数名字来找函数的地址的时候。先要找到函数名字相应的序号,然后再依据序号来查找地址,这也是AddressOfNameOrdinals存在的原因了。
<4>依据序号查找函数
函数相应的序号– Base的值得到索引值,依据这个索引值在EAT中查找相对偏移量地址,就得到了函数的地址了。
以上两图对照说明了NumberOfFunctions的值不一定等于NUmberOfNames。同一时候寻找函数的地址是依据序号来查找的。
6 关于exp文件
链接器在创建Dll的时候与创建静态链接一样,是两个过程。首先是链接器扫描全部的目标文件而且搜集全部的符号信息并创建导出表,为了方便链接器把导出表信息放到一个暂时目标文件里的.edata 段中,这个目标文件就是exp文件。
这个exp是个标准的PE/COFF目标文件,仅仅只是后缀时exp而不是obj。
第二遍时候。将exp当做普通目标文件与其它obj文件链接在一起而且输出为Dll,此时exp文件里的.edata段就会输出到Dll文件里而且成为导出表。
7 导出重定向
将导出符号重定位到还有一个Dll中。假设我们想重定向某个函数。能够使用模块定义文件,
如:
EXPORTS
xxfunc = xx.dll.Oldfunc
8 导入表
假设在程序中使用了来自某个Dll的函数和变量,这样的行为就叫做符号导入。当PE文件被载入的时候,windows载入器的一个任务就是将全部须要导入的函数和符号的地址确定。以实现动态链接的过程。
在PE中导入表是一个IMAGE_IMPORT_DESCRIPTOR的结构体数组。数组中每个成员相应一个Dll。
typedef struct_IMAGE_IMPORT_DESCRIPTOR {
union {
DWORD Characteristics; // 0 for terminating null import descriptor
DWORD OriginalFirstThunk; // RVA to original unbound IAT (PIMAGE_THUNK_DATA)
} DUMMYUNIONNAME;
DWORD TimeDateStamp; // 0 if not bound,
// -1 if bound, and real date\time stamp
// inIMAGE_DIRECTORY_ENTRY_BOUND_IMPORT (new BIND)
// O.W. date/time stamp of DLL bound to (Old BIND) DWORD ForwarderChain; // -1 if no forwarders
DWORD Name;
DWORD FirstThunk; // RVA to IAT (if bound this IAT has actual addresses)
}IMAGE_IMPORT_DESCRIPTOR;
<1> FirstThunk
当中FirstThunk指向一个IAT(ImportAddress Table),它是导入表中最重要的的结构,IAT的每个元素相应一个被导入的符号。在动态链接刚完毕映射还没有进行重定位以及符号解析的时候。IAT中元素表示的相应的导入的符号的序号或者符号名。在windows的动态链接器完毕该模块的链接时候。元素值被动态链接器改写成该符号的 真正地址。
假设IAT的元素的最高位被置为1,那么剩下的31位就表示序号值,假设不是1,那么元素的值就是指向IMAGE_IMPORT_BY_NAME的RVA,
typedef struct_IMAGE_IMPORT_BY_NAME {
WORD Hint;
BYTE Name[1];
}IMAGE_IMPORT_BY_NAME, *PIMAGE_IMPORT_BY_NAME;
当中Hint表示导入符号的最有可能的序号值。Name[1]表示的是符号名。
当用符号名字去导入时,链接器会依据Hint的值在目标导出表中找出符号的位置,假设没有找到就使用二分查找法去进行符号的查找。
<2> OriginalFirstThunk
OriginalFirstThunk指向一个INT(Import Name Table)这个数组跟IAT一样,里面的数值也一样。
9 延迟加载
在VisualC++6.0版之前。在执行时载入 DLL 的唯一办法是使用 LoadLibrary 和 GetProcAddress 函数;当使用操作系统的可执行文件或 DLL 被载入之后,操作系统才载入 DLL。从 Visual C++ 6.0 開始,与 DLL 静态链接时。链接器提供了一些选项,将 DLL 的载入延迟到程序调用该 DLL 中的函数时才进行。
当链接一个支持延迟载入的dll的时候,链接器会产生与普通Dll导入很类似的数据,可是操作系统会忽略这些数据,知道Dll中的API第一次被调用的时候。链接器中加入的特殊的桩代码就会启动。这个桩代码负责对Dll的装载工作,它调用GetProcAddress来找到函数的地址。
10 导入函数的调用
导入函数的声明declspec(dllimport)xx func(xx );用来声明函数是外部模块的编译器在产生lib库的时候。对同一个函数来说,产生了两个符号定义,针对函数func来说,一个符号是func,还有一个是_imp_func。当中func指向桩代码,_imp_func指向func函数在IAT中的的位置。当通过delspec(dllimport) xx func(xx);的时候。编译器会在编译的时候会在改导入函数前面加上_imp_,以确保跟导入库中的_imp_func函数正确链接,假设没有的话就会产生一个正常的func符号,以便跟导入库中的func符号定义相连接。
当把一个函数声明为declspec(dllimport)xx func(xx)的时候。编译器会知道函数是外部导入的。它就会产生一个 CALL [XXX]的间接跳转指令。
假设不加declspec(dllimport)xx func(xx)的话,编译器就不会区分模块内自定义还是外部导入的。它统一的产生直接调用指令。可是在连链接的时候会把外部函数导向一段桩代码,
桩代码再把控制权交给IAT中的真正地址。
CALL 0x0040100C
….
0x0040100C
JMP DWORD PTR[XXX]
只从此能够看出假设有了declspec(dllimport)的声明。会降低一条跳转指令,所以函数的运行效率会更高点。
附加:_declspec(import)在C ++导出类中的使用。
在C++导出类中假设不适用_declspec(import)的话那么就会导致类中的静态变量不能解析。所以假设类中有静态变量的话就一定使用_declspec(import)来声明函数。
Dll的链接使用细节的更多相关文章
- 结对项目——图形界面实现与dll动态链接
先来一发软件截图~~~ 生成题目的界面 测评界面 第三块本来准备做一个文件历史记录的界面,但是由于时间不够,暂时还没做完. 图形界面的设计与实现 由于对传统的对话框风格不太满意,所以这次作业的图形界面 ...
- 静态链接库(lib)、动态链接库(dll)与动态链接库的导入库(lib)
静态链接库与动态链接库相对应.动态链接库的导入库不同于以上两种库. 1.静态链接库(lib) 程序编译一般需经编辑.编译.连接.加载和运行几个步骤.在我们的应用中,有一些公共代码是需要反复使用 ...
- 原创 C++应用程序在Windows下的编译、链接:第一部分 概述
本文是对C++应用程序在Windows下的编译.链接的深入理解和分析,文章的目录如下: 我们先看第一章概述部分. 1概述 1.1编译工具简介 cl.exe是windows平台下的编译器,link.ex ...
- C++应用程序在Windows下的编译、链接(一)概述
C++应用程序在Windows下的编译.链接(一)概述 本文是对C++应用程序在Windows下的编译.链接的深入理解和分析,文章的目录如下: 我们先看第一章概述部分. 1概述 1.1编译工具简介 c ...
- Python使用Ctypes与C/C++ DLL文件通信过程介绍及实例分析
项目中可能会经常用到第三方库,主要是出于程序效率考虑和节约开发时间避免重复造轮子.无论第三方库开源与否,编程语言是否与当前项目一致,我们最终的目的是在当前编程环境中调用库中的方法并得到结果或者借助库中 ...
- 动态符号链接的细节 与 linux程序的加载过程
转: http://hi.baidu.com/clivestudio/item/4341015363058d3d32e0a952 值得玩味的一篇分析程序链接.装载.动态链接细节的好文档 导读: by ...
- Windows核心编程 第十九章 DLL基础
第1 9章 D L L基础 这章是介绍基本dll,我就记录一些简单应用,dll的坑点以及扩展后面两章会说,到时候在总结. 自从M i c r o s o f t公司推出第一个版本的Wi n d o w ...
- R3抹掉加载的DLL
R3抹掉加载的DLL 原理类似于获取Kernel32.dll加载地址,知道这个东西也是在看获取Kernel32.dll地址的时候在网上搜索学习资料,无意中看到的这个东西.这个挺有用,结合着HiJack ...
- C#程序实现动态调用DLL的研究(转)
摘 要:在<csdn开发高手>2004年第03期中的<化功大法——将DLL嵌入EXE>一文,介绍了如何把一个动态链接库作为一个资源嵌入到可执行文件,在可执行文件运行时,自动从资 ...
随机推荐
- 移动端meta几个值的设置以及含义
<!-- 为移动设备添加 viewport --> <meta name="viewport" content="width=device-width, ...
- sql创建外键
建立外键关系:先建主表再见从表:主表:create table zhu(code int parimary key,name varchar(20)) ;从表:create table cong(co ...
- 图片压缩优化kraken
https://kraken.io/web-interface 测试过,可以节省10%左右的大小,图片清晰度不受影响.
- jQuery插件--根据数据加载的进度动画案例
css: *{ margin:; padding:; } @media screen and (min-width:320px){ html{font-size:12px;}} @media scre ...
- nslookup---域名查询
nslookup命令是常用域名查询工具,就是查DNS信息用的命令. nslookup4有两种工作模式,即“交互模式”和“非交互模式”. 在“交互模式”下,用户可以向域名服务器查询各类主机.域名的信息, ...
- Android学习笔记进阶16之BitmapShader
<1>简介 具体的看一下博文:Android学习笔记进阶15之Shader渲染 public BitmapShader(Bitmap bitmap,Shader.TileMode ti ...
- Onvif开发之Linux下gsoap的使用及移植
一直以来都是在CSDN上面学习别人的东西,很多次想写点什么但是又无从写起.由于公司项目需要,最近一段时间在研究onvif,在网上找了很多资料,发现资料是非常多,但是很少有比较全的资料,或者资料太多无从 ...
- Spark源代码分析之中的一个:Job提交执行总流程概述
Spark是一个基于内存的分布式计算框架.执行在其上的应用程序,依照Action被划分为一个个Job.而Job提交执行的总流程.大致分为两个阶段: 1.Stage划分与提交 (1)Job依照RDD之间 ...
- js面向对象2--原型
一.原型和原型对象 函数的原型prototype:函数才有prototype,prototype是一个对象,指向了当前构造函数的引用地址. 所有对象都有__proto__属性, 所有的__proto ...
- Makefile的问题
注意包含路径的变量,可能会带有空格而使得运行失败 直接定义:DEBUG = false后面引用需要使用 $(DEBUG) 或者set DEBUG $(DEBUG)之后就可以直接引用了