dll(Dynamic Link Library),動(dòng)態(tài)鏈接庫,它和exe基本上一樣,只不過它的pe文件頭中的符號(hào)表標(biāo)明該文件是dll而不是exe。
dll文件的后綴名不僅是dll還可以是ocx和cpl。
dll設(shè)計(jì)的目的不同于共享對(duì)象,它關(guān)注的是軟件工程中的模塊化設(shè)計(jì)思想,想要做到高內(nèi)聚低耦合,它方便了軟件的升級(jí)和維護(hù)。
exe文件里面也有基地址和RVA(Relative Virtual Address,相對(duì)虛擬地址)。
對(duì)于dll來說它的基地址有一個(gè)默認(rèn)值,如果這個(gè)默認(rèn)值被其他模塊占用了,它就找個(gè)別的地方裝載。
dll中有些數(shù)據(jù)是共享的,有些是進(jìn)程私有的。
按照這個(gè)區(qū)別劃分的話,就可以把數(shù)據(jù)段分成兩種。
不過由于各進(jìn)程共同訪問公共數(shù)據(jù)段,如果某一進(jìn)程惡意破壞數(shù)據(jù),其他進(jìn)程也會(huì)受到影響,這存在一定的安全隱患。
ELF中的符號(hào)默認(rèn)都是可以導(dǎo)入導(dǎo)出的,所謂導(dǎo)出就是指可以被別的模塊調(diào)用,導(dǎo)入就不解釋了,而DLL中的符號(hào)需要指定才能導(dǎo)入導(dǎo)出。
__declspec(dllexport)用來指定導(dǎo)出符號(hào),__declspec(dllimport)用來指定導(dǎo)入符號(hào)。
另外,還可以用def文件中的IMPORT和EXPORTS段來聲明符號(hào)的導(dǎo)入和導(dǎo)出。
如果是C語言的符號(hào)規(guī)范,你必須在符號(hào)的定義之前加上external
“C”。
就是前面提到的def文件了,它的好處有如下幾點(diǎn):
1、能夠控制導(dǎo)出符號(hào)的符號(hào)名。原來__cdecl、__stdcall、__fastcall都是msvc中的函數(shù)規(guī)范啊。編譯器會(huì)對(duì)源碼中的符號(hào)進(jìn)行修飾,經(jīng)過修飾的符號(hào)變得和環(huán)境中的符號(hào)不兼容,不便于維護(hù)和使用,于是采用def文件對(duì)導(dǎo)出符號(hào)進(jìn)行重命名。
2、它可以控制一些鏈接的過程。它還可以控制輸出文件名、段的屬性、堆棧大小、版本號(hào)等。
即,運(yùn)行時(shí)加載。
Windows提供了3個(gè)API:
1、LoadLibrary裝載一個(gè)DLL進(jìn)進(jìn)程地址空間。
2、GetProAddress查找某個(gè)符號(hào)的地址。
3、FreeLibrary用來卸載某個(gè)已經(jīng)加載的模塊。
Windows下的PE文件的導(dǎo)出符號(hào)全部集中在導(dǎo)出表中,供其他PE文件調(diào)用,它提供的是一種符號(hào)與地址的映射關(guān)系。
導(dǎo)出表是個(gè)DataDirectory的結(jié)構(gòu)體數(shù)組,名字叫做IMAGE_EXPORT_DIRECTORY,被定義在Winnt.h中。
DataDirectory中最后三項(xiàng)EAT(Export Address Table)、Name
Table、Name
Ordinal Table分別代表導(dǎo)出地址表、符號(hào)名表、名字序號(hào)對(duì)應(yīng)表。
導(dǎo)出地址表存放的是個(gè)符號(hào)的相對(duì)虛擬地址。
符號(hào)名表存放的是導(dǎo)出符號(hào)的名字。
名字序號(hào)對(duì)應(yīng)表存放的是函數(shù)的序號(hào)和函數(shù)名的對(duì)應(yīng)關(guān)系。
函數(shù)序號(hào)存在的意義在于節(jié)省空間和查找方便,壞處是函數(shù)變化了序號(hào)也要跟著變化。導(dǎo)出函數(shù)一定有序號(hào)但可以沒有函數(shù)名。
在創(chuàng)建DLL時(shí)會(huì)產(chǎn)生一個(gè)EXP,EXP中的.edata存放的是導(dǎo)出表。EXP會(huì)與其他目標(biāo)文件一樣一起鏈接生成DLL并且成為導(dǎo)出表。
來自于DLL和其他可執(zhí)行文件中的符號(hào)會(huì)存儲(chǔ)在導(dǎo)入表中。
導(dǎo)入表是一個(gè)IMAGE_EXPORT_DIRECTORY結(jié)構(gòu)體數(shù)組。
IMAGE_EXPORT_DIRECTORY中的FirstThunk指向?qū)氲刂窋?shù)組(Import
Address Table,IAT),每個(gè)IAT對(duì)應(yīng)一個(gè)被導(dǎo)入的符號(hào)。
延遲載入:DLL也支持延遲裝載,它是通過特殊的樁代碼實(shí)現(xiàn)的。
PE DLL的代碼段并不是地址無關(guān)的。
PE采用了重定基地址的方法來解決模塊裝載時(shí)進(jìn)程空間中地址沖突的問題。
__declspec(dllimport)的作用是使編譯器能夠區(qū)分函數(shù)是從外部導(dǎo)入的還是模塊內(nèi)部定義的。
同一個(gè)導(dǎo)出函數(shù)會(huì)產(chǎn)生兩個(gè)符號(hào)的定義,一個(gè)指向該函數(shù)的樁代碼,一個(gè)指向該函數(shù)在IAT中的位置。
用__declspec(dllimport)來聲明導(dǎo)入函數(shù)時(shí)會(huì)在導(dǎo)入函數(shù)前面加上__imp__以確保跟庫中的函數(shù)符號(hào)正確鏈接。
DLL本身的代碼段和數(shù)據(jù)段并不是地址無關(guān)的。
一旦DLL的基址被占用,它就必須被重定位,這需要時(shí)間開銷。
雖然在DLL中采用二分查找法進(jìn)行符號(hào)字符串的比較和查找,但是由于符號(hào)眾多,因此這也是一項(xiàng)非常耗時(shí)的工作。
以上是影響DLL性能的兩個(gè)問題。
在裝載DLL時(shí)發(fā)生了地址沖突,就必須對(duì)每個(gè)絕對(duì)地址的引用都進(jìn)行重定位。
重定位的過程很簡單就是在原來的地址基礎(chǔ)上加上一個(gè)偏移量,但是這是對(duì)所有需要重定位的絕對(duì)地址而言的。
PE文件的重定位信息都放在reloc段中。
一般來講exe文件是不會(huì)發(fā)生重定位的,因?yàn)樗偸潜坏谝粋€(gè)裝載。
而DLL則是動(dòng)態(tài)裝載所以它的裝載地址可能被占用。
DLL文件中代碼段的訪問比ELF更加快速,是一種空間換時(shí)間的優(yōu)化策略,因?yàn)樗總€(gè)進(jìn)程都有一個(gè)副本。
確切地說它是裝載時(shí)重定位基址。
DLL的裝載和地址順序是一樣的。
DLL的基地址是可以手動(dòng)指定的,不然老是重定位多麻煩啊,VC提供這種功能。
一個(gè)導(dǎo)出的函數(shù)符號(hào)可以沒有函數(shù)名,但是絕對(duì)不能沒有序號(hào)。
序號(hào)表示導(dǎo)出函數(shù)在導(dǎo)出表中的位置。
內(nèi)部使用的函數(shù)一般只有序號(hào)沒有函數(shù)名。
Windows API雖然函數(shù)名是不變的,但是序號(hào)總是在變化的。
序號(hào)可以通過def文件指定。
凡是涉及到PE文件的查找的地方用的都是二分查找法。
由于無論如何導(dǎo)入導(dǎo)出的符號(hào)關(guān)系都會(huì)被重新解析,而且每次解析完畢后它們被裝載的內(nèi)存地址都相同,那么這個(gè)解析過程就是浪費(fèi)。
針對(duì)這種浪費(fèi)采取的優(yōu)化策略就叫做DLL綁定,具體方法是把這些導(dǎo)入的符號(hào)保存在模塊的導(dǎo)入表中每次只需查表即可。
INT(Import Name Table),導(dǎo)入名稱表,把符號(hào)運(yùn)行時(shí)的目標(biāo)地址寫到INT中。
DLL更新和重定基址可能導(dǎo)致DLL綁定地址失效。
針對(duì)DLL更新,Windows會(huì)核對(duì)裝載的DLL與綁定時(shí)的DLL版本是否相同,還有該DLL是否發(fā)生過重定基址,如果都沒有那就直接查表。
DLL綁定過程可以發(fā)生在安裝的時(shí)候,可能改變可執(zhí)行文件本身從而導(dǎo)致可執(zhí)行文件的校驗(yàn)和變化。這對(duì)于一些經(jīng)過加密的和數(shù)字簽名的程序來說可能會(huì)有問題。
Linux下的共享庫絕大多數(shù)都是用C語言寫的,這是因?yàn)镃++編寫的庫比C語言編寫的庫要復(fù)雜得多,而且由于C++沒有二進(jìn)制級(jí)別的規(guī)定只有語法級(jí)別的,所以也為共享庫的更新帶來了不便。
C++帶來的麻煩主要表現(xiàn)在以下幾個(gè)方面:
1、內(nèi)存釋放過程復(fù)雜,不好把握。因?yàn)椴煌腄LL和EXE使用不同的堆。
2、所謂的更新只不過是簡單的覆蓋。
3、正是由于舊版DLL被覆蓋,如果新版程序運(yùn)行出錯(cuò),舊版程序也運(yùn)行出錯(cuò),那就悲劇了。
為了解決程序開發(fā)中遇到的兼容性問題,微軟推出了組件對(duì)象模型(COM,Component
Object Model)。
P300列出了幾點(diǎn)用C++編寫DLL時(shí)應(yīng)該遵循的原則。
早期Windows中的DLL文件使用范圍大,更新也頻繁,而且還缺乏版本控制機(jī)制,所以DLL不兼容的情況在早期Windows下特別嚴(yán)重,史稱DLL噩夢(mèng)。
導(dǎo)致DLL
HELL發(fā)生的3個(gè)原因見P301中所說明的。
解決DLL
HELL的方法:
1、由于DLL HELL是由DLL引起的,即動(dòng)態(tài)鏈接引起的,所以最徹底的解決辦法是不使用動(dòng)態(tài)鏈接,而使用靜態(tài)鏈接。
2、避免DLL覆蓋。這個(gè)可以通過Windows的文件保護(hù)機(jī)制實(shí)現(xiàn)。
3、避免DLL沖突。它主要是針對(duì)不同應(yīng)用程序依賴相同DLL的不同版本的問題,解決辦法是讓每個(gè)應(yīng)用程序。
.NET下的程序集包括兩種類型應(yīng)用程序集和庫程序集,前者是指EXE可執(zhí)行文件,后者是指DLL動(dòng)態(tài)鏈接庫。
由于程序集包括一個(gè)或多個(gè)文件所需要一個(gè)清單來描述,這個(gè)清單叫做Manifest文件,它就是描述了程序及的各種屬性信息,其本質(zhì)是一個(gè)XML文件。
在Windows
XP以前的版本中Manifest就是個(gè)擺設(shè),這以后的Windows版本在執(zhí)行可執(zhí)行文件時(shí)首先要讀取Manifest內(nèi)容獲取所需的DLL文件列表,然后Windows再根據(jù)DLL的Manifest文件去掉用DLL。
CRT(C Run-Time Library):C語言運(yùn)行庫。