关于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的链接使用细节的更多相关文章

  1. 结对项目——图形界面实现与dll动态链接

    先来一发软件截图~~~ 生成题目的界面 测评界面 第三块本来准备做一个文件历史记录的界面,但是由于时间不够,暂时还没做完. 图形界面的设计与实现 由于对传统的对话框风格不太满意,所以这次作业的图形界面 ...

  2. 静态链接库(lib)、动态链接库(dll)与动态链接库的导入库(lib)

    静态链接库与动态链接库相对应.动态链接库的导入库不同于以上两种库. 1.静态链接库(lib)     程序编译一般需经编辑.编译.连接.加载和运行几个步骤.在我们的应用中,有一些公共代码是需要反复使用 ...

  3. 原创 C++应用程序在Windows下的编译、链接:第一部分 概述

    本文是对C++应用程序在Windows下的编译.链接的深入理解和分析,文章的目录如下: 我们先看第一章概述部分. 1概述 1.1编译工具简介 cl.exe是windows平台下的编译器,link.ex ...

  4. C++应用程序在Windows下的编译、链接(一)概述

    C++应用程序在Windows下的编译.链接(一)概述 本文是对C++应用程序在Windows下的编译.链接的深入理解和分析,文章的目录如下: 我们先看第一章概述部分. 1概述 1.1编译工具简介 c ...

  5. Python使用Ctypes与C/C++ DLL文件通信过程介绍及实例分析

    项目中可能会经常用到第三方库,主要是出于程序效率考虑和节约开发时间避免重复造轮子.无论第三方库开源与否,编程语言是否与当前项目一致,我们最终的目的是在当前编程环境中调用库中的方法并得到结果或者借助库中 ...

  6. 动态符号链接的细节 与 linux程序的加载过程

    转: http://hi.baidu.com/clivestudio/item/4341015363058d3d32e0a952 值得玩味的一篇分析程序链接.装载.动态链接细节的好文档 导读: by ...

  7. Windows核心编程 第十九章 DLL基础

    第1 9章 D L L基础 这章是介绍基本dll,我就记录一些简单应用,dll的坑点以及扩展后面两章会说,到时候在总结. 自从M i c r o s o f t公司推出第一个版本的Wi n d o w ...

  8. R3抹掉加载的DLL

    R3抹掉加载的DLL 原理类似于获取Kernel32.dll加载地址,知道这个东西也是在看获取Kernel32.dll地址的时候在网上搜索学习资料,无意中看到的这个东西.这个挺有用,结合着HiJack ...

  9. C#程序实现动态调用DLL的研究(转)

    摘 要:在<csdn开发高手>2004年第03期中的<化功大法——将DLL嵌入EXE>一文,介绍了如何把一个动态链接库作为一个资源嵌入到可执行文件,在可执行文件运行时,自动从资 ...

随机推荐

  1. 利用日志文件恢复MYSQL数据库

    利用日志文件恢复MYSQL数据库 650) this.width=650;" onclick='window.open("http://blog.51cto.com/viewpic ...

  2. Android studio文件名颜色分别表示含义

    这其实是主要和版本控制工具有关,含义如下: 绿色,已经加入控制暂未提交红色,未加入版本控制蓝色,加入,已提交,有改动白色,加入,已提交,无改动 灰色:版本控制已忽略文件

  3. qrcode length overflow 生成二维码网址长度溢出解决办法

    QRCode.js is javascript library for making QRCode. QRCode.js supports Cross-browser with HTML5 Canva ...

  4. ipcalcIP地址计算

    ipcalc命令是一个简单的ip地址计算器,可以完成简单的IP地址计算任务. 语法 ipcalc(选项) 选项 -b:由给定的IP地址和网络掩码计算出广播地址: -h:显示给定UP地址所对应的主机名: ...

  5. 如何优雅的写UI——(3)添加MFC选项卡

    窗体创建完成,接下来我们讲讲控件的使用 首先在CFormView窗体下选项卡的成员变量,这里我选择MFC下的选项卡类库:CMFCTabCtrl class CtabView : public CFor ...

  6. src/MD2.c:31:20: 错误:Python.h:没有那个文件或目录

    一.前言 在CentOS 上安装fabric时出现问题,首先已安装pip, 用pip执行以下命令pip install 出现以下问题 [niy@niy-computer /]$ sudo pip in ...

  7. [React] Compound Component (React.Children.map & React.cloneElement)

    Imaging you are building a Tabs component. If looks like: <Tabs> <TabList> <Tab> o ...

  8. hdu 1233 还是畅通project (克鲁斯卡尔裸题)

    还是畅通project                                              Time Limit: 4000/2000 MS (Java/Others)    M ...

  9. Qt 5.11的QChar、QString、QTextBoundaryFinder和双向文本算法现在完全兼容Unicode 10

    本文翻译自:Qt 5.11 released 原文作者: Qt公司CTO兼Qt开源项目维护官Lars Knoll翻译校审:Richard.Hongfei.Haipeng 5月22日,我们提发布了Qt ...

  10. Loadrunner经典测试实例

    Loadrunner经典测试实例