8 动态链接库的载入分析.docx

上传人:b****8 文档编号:11337315 上传时间:2023-02-28 格式:DOCX 页数:18 大小:102.27KB
下载 相关 举报
8 动态链接库的载入分析.docx_第1页
第1页 / 共18页
8 动态链接库的载入分析.docx_第2页
第2页 / 共18页
8 动态链接库的载入分析.docx_第3页
第3页 / 共18页
8 动态链接库的载入分析.docx_第4页
第4页 / 共18页
8 动态链接库的载入分析.docx_第5页
第5页 / 共18页
点击查看更多>>
下载资源
资源描述

8 动态链接库的载入分析.docx

《8 动态链接库的载入分析.docx》由会员分享,可在线阅读,更多相关《8 动态链接库的载入分析.docx(18页珍藏版)》请在冰豆网上搜索。

8 动态链接库的载入分析.docx

8动态链接库的载入分析

第八章動態連結程式庫的載入分析

動態連結程式庫(DLL)一直以來都是Windows的重要基礎,WindowsCE也不例外。

DLL對作業系統十分重要,本節的內容主要是分析loader.c中的程式碼,它負責載入EXE和DLL。

這裏要討論的是關於DLL的部分,如無特別說明,本章中所引用的程式碼,都是來自WindowsCE原始程式碼樹中的[CEROOT]\PRIVATE\WINCEOS\COREOS\NK\KERNEL\loader.c。

8.1loader.c中程式碼的組織結構

loader.c主要透過以下的API函數,來完成NK核心載入EXE和DLL處理程序的工作,這是使用者程式對DLL和EXE程式操作的進入點。

之後再透過一系列的函數呼叫,來完成DLL和EXE的載入和卸載,以及對處理程序和執行緒的其他操作。

由於我們現在要討論的是有關DLL的部分,所以只列出了部分函式。

●Win32LoadLibrarycall

HANDLESC_LoadLibraryW(LPCWSTRlpszFileName)

HINSTANCESC_LoadLibraryExW(LPCWSTRlpLibFileName,HANDLEhFile,DWORDdwFlags)

HANDLESC_LoadDriver(LPCWSTRlpszFileName)

HANDLESC_LoadKernelLibrary(LPCWSTRlpszFileName)

HANDLESC_LoadIntChainHandler(LPCWSTRlpszFileName,LPCWSTRlpszFunctionName,

BYTEbIRQ)

這幾個函數基本上都是負責DLL的載入,它們都呼叫了LoadOneLibraryW這個函數,函數的原型如下:

HANDLELoadOneLibraryW(LPCWSTRlpszFileName,DWORDfLbFlags,WORDwFlags)

這個函數在載入DLL的前後,處理一些必要的細節工作。

更重要的是呼叫了LoadOneLibraryPart2這個函數,載入的重要工作都在該函數的程式碼中完成,在本章的後面會詳細分析它的內容。

●Win32FreeLibrarycall

HANDLESC_FreeIntChainHandler(HANDLEhLib)

BOOLSC_FreeLibrary(HANDLEhInst)

主要是FreeLibrary函數。

這個API函數負責卸載DLL。

它呼叫FreeOneLibrary這個函數,函數原型如下:

BOOLFreeOneLibrary(PMODULEpMod,BOOLbCallEntry)

卸載的過程主要是由FreeOneLibraryPart2負責。

在後面的章節會有具體的分析。

●Win32GetProcAddresscall

LPVOIDSC_GetProcAddressA(HANDLEhInst,LPCSTRlpszProc)

LPVOIDSC_GetProcAddressW(HANDLEhInst,LPCSTRlpszProc)

這兩個函數可以得到處理程序的位址。

8.2modulestructure

這是記錄DLL資訊的重要資料結構之一,每一個程式對應一個module,但一個module可以對應多個處理程序。

在系統中維持一條已載入模組的串列,它是一個單向鏈結串列。

第一個元素是pModList。

程式碼8.1modulestructure

typedefstructModule{

LPVOIDlpSelf;

PMODULEpMod;

LPWSTRlpszModName;

DWORDinuse;

DWORDcalledfunc;

WORDrefcnt[MAX_PROCESSES];

LPVOIDBasePtr;

DWORDDbgFlags;

LPDBGPARAMZonePtr;

ulongstartip;

openexe_toe;

e32_litee32;

o32_lite*o32_ptr;

DWORDbreadcrumb;

DWORDdwNoNotify;

WORDwFlags;

BYTEbTrustLevel;

BYTEbPadding;

PMODULEpmodResource;

DWORDrwLow;

DWORDrwHigh;

}Module;

/*用於驗證,指向自己的指標*/

/*鏈結串列中的下一個模組*/

/*模組名字*/

/*使用狀況的位元向量*/

/*被呼叫的進入點,但不退出*/

/*處理程序的引用計數*/

/*DLL載入基址*/

/*偵錯旗標*/

/*Debugzone指標*/

/*基於0的進入點*/

/*執行檔指標*/

/*e32標頭*/

/*O32串列指標*/

/*每個處理程序對應一個位元,當Notify被禁用時設為1*/

 

/*包含資源的模組*/

/*ROMDLL中可讀寫段的基底位址*/

/*ROMDLL中可讀寫段的高位位址*/

refcnt[MAX_PROCESSES]是每個處理程序的引用計數。

BasePtr是DLL載入的基底位址,這是比較需要注意的部分。

oe、e32、*o32_ptr放在後面介紹。

8.3LoadOneLibraryPart2載入DLL的過程

呼叫

LoadOneLibraryPart2

圖8.1LoadOneLibraryPart2的基本執行步驟

函數原型:

PMODULELoadOneLibraryPart2(LPWSTRlpszFileName,DWORDfLbFlags,WORDwFlags)

參數lpszFileName

指向DLL名字的字串,該名字確定了模組(module)的檔案名稱,而與它儲存在模組庫中的名字無關。

函式庫模組以LIBRARY為關鍵字,在模組定義檔(.def)中定義。

參數hFile

是保留做將來使用的,現在必須是NULL。

參數wFlags

指定載入模組時所要處理的工作。

其值可以為0、DONT_RESOLVE_DLL_REFERENCES、LOAD_LIBRARY_AS_DATAFILE或者是LOAD_LIBRARY_IN_KERNEL。

DONT_RESOLVE_DLL_REFERENCE:

如果使用這個旗標,而且可執行模組本身是一個DLL,則系統並不呼叫DllMain來初始化和結束處理程序和執行緒。

此外,一個DLL可能會引入包含在另一個DLL中的函數,而系統映射一個DLL時也會自動載入,當這個旗標被設置之後,系統就不再自動載入額外的DLL。

載入過程如圖8.1所示。

載入的過程首先設定wFlags的值,然後呼叫pMod=FindModByName(dllname),藉由查找pModList串列,來得到DLL的pMod指標。

對於已經載入的module,增加其引用次數。

由以下的程式碼來完成:

if(!

(pMod->inuse&(1<procnum))){

pMod->inuse|=(1<procnum);

pMod->calledfunc&=~(1<procnum);

}

returnpMod->refcnt[pCurProc->procnum]++;

呼叫該module的處理程序是目前的處理程序。

將目前處理程序對該module的引用計數加一,傳回pMod,便完成了載入的過程。

如果module之前並未載入,則需要一系列的工作:

1)為module配置記憶體

2)初始化module,呼叫InitModule

3)增加引用計數

4)將這個新的pMod插入module鏈結串列中,作為第一個元素。

程式碼如下:

EnterCriticalSection(&ModListcs);

pMod->pMod=pModList;

pModList=pMod;

LeaveCriticalSection(&ModListcs)

8.4DLL載入過程—InitModule的執行

InitModule處理載入一個新的DLL所要做的大部分工作。

函數原型如下:

DWORDInitModule(PMODULEpMod,LPWSTRlpszFileName,LPWSTRlpszDllName,

DWORDfLbFlags,WORDwFlags)

在函數InitModule中,具體執行步驟如下:

1)初始化pMod中的欄位(field)

2)呼叫函數OpenADll,產生執行檔指標(openexe_t)

3)載入module的e32資訊,產生e32標頭資訊(e32_lite)

4)檢查bTrustlevel參數

5)配置記憶體給DLL,取得Module->BasePtr,即載入DLL的基底位址

6)配置記憶體給name和O32物件,讀取這個module的O32資訊

7)改變module的名字

8)重定位映射

9)呼叫函數FindEntryPoint,找到EXE的起始IP(或者DLL的進入點)

整體來說,這個函數就是負責設定pMod各個欄位的初始值、取得可執行檔的指標以及EXE或DLL的進入點。

在這個過程中,將各個步驟、判斷中的錯誤碼,傳回給LoadOneLibraryPart2,用來作為判斷pMod是否建立成功的資訊,以繼續以後的工作。

下面,將對這幾個重要步驟作詳細分析。

8.4.1呼叫OpenADll,產生執行檔指標(openexe_t)

module中的oe是可執行檔的指標,每個程式對應一個module,每個module對應一個可執行檔指標。

oe的型別openexe_t定義如下:

程式碼8.2openexe_tstructure

typedefstructopenexe_t{

union{

inthppfs;

HANDLEhf;

TOCentry*tocptr;

};

BYTEfiletype;

BYTEbIsOID;

WORDpagemode;

DWORDoffset;

union{

Name*lpName;

CEOIDceOid;

};

}openexe_t;

//ppfshandle

//物件儲存指標

//romentrypointer

//檔案類型

//分頁模式

//偏移

這實際上是與檔案處理等有關的結構,描述可執行程式碼的位址、分頁模式、偏移等資訊。

OpenADll呼叫OpenExe,由OpenExe呼叫SafeOpenExe,設定可執行檔指標的工作基本上都在SafeOpenExe中完成。

BOOLSafeOpenExe(LPWSTRlpszName,openexe_t*oeptr,BOOLbIsDLL,BOOLbAllowPaging,OEinfo_t*poeinfo)

SafeOpenExe還執行以下的工作:

1)尋找EXE檔案所在的目錄

2)按照指定路徑尋找檔案

3)在Windows目錄中尋找檔案

4)在根目錄中尋找檔案

在檔案系統中搜尋DLL檔案時,根據搜尋過程中的資訊設定其中各個欄位的值。

並藉由下面的程式碼取得檔案的儲存指標:

oeptr->hf=CreateFileW((LPWSTR)poeinfo->tmpname,GENERIC_READ,FILE_SHARE_READ,0,

OPEN_EXISTING,0,0);

即透過CreateFile開啟檔案。

如果在ROM中有備份,則設定oeptr->tocptr的值。

傳回結果:

如果找到,則傳回1,否則傳回0。

8.4.2設定module的e32標頭資訊

module的成員e32的型別定義如下:

程式碼8.3e32_litestructure

typedefstructe32_lite{

unsignedshorte32_objcnt;

BYTEe32_cevermajor;

BYTEe32_ceverminor;

unsignedlonge32_stackmax;

unsignedlonge32_vbase;

unsignedlonge32_vsize;

unsignedlonge32_sect14rva;

unsignedlonge32_sect14size;

struct infoe32_unit[LITE_EXTRA];

}e32_lite,*LPe32_list;

/*PE32-bit.EXEheader*/

/*記憶體物件個數*/

/*版本資訊*/

/*版本資訊*/

/*堆疊的最大值*/

/*module的虛擬記憶體基底位址*/

/*整個映射的Virtual大小*/

/*section14rva*/

/*section14size*/

/*Arrayofextrainfounits*/

這是與記憶體相關的一組資料,在InitModule中有一段程式碼如下:

//loadO32infoforthemodule

eptr=&pMod->e32;

if(retval=LoadE32(&pMod->oe,eptr,&flags,&entry,(pMod->wFlags&

LOAD_LIBRARY_AS_DATAFILE)?

1:

0,bAllowPaging,&pMod->bTrustLevel)){

returnretval;

}

它藉由呼叫LoadE32函數讀取DLL檔,設定e32標頭的各部分內容,設定堆疊、虛擬記憶體基底位址、映射等正確的值,使接下來DLL的記憶體配置工作能夠順利進行。

8.4.3DLL的記憶體配置與Module->BasePtr的取得

InitModule中的這一部分程式碼的主要作用是設定pMod->BasePtr。

為了方便解釋,圖8.2是簡化的流程圖。

圖8.2DLL配置記憶體的過程

由可執行檔指標pMod->oe.filetype可得到檔案類型。

DLL有兩種映射,一種是普通檔案,需要將其載入到RAM中執行。

為了節省時間,還有另一種方式就是XIP(Executeinplace)方式。

顧名思義,就是可以立即執行,而不用載入RAM中,所以相應pMod->BasePtr的值也有兩種不同的情況。

如果不是XIP(Executeinplace)DLL,則保留足夠的虛擬記憶體位址空間以容納整個映射。

從已經存在的DLL的底部開始由上往下分配。

DLL載入到核心位址空間(kernelspace)或是使用者空間(userspace)也有差別。

對於載入核心位址空間的DLL,必須為其取得連續的實體頁面。

因為核心空間位址和虛擬記憶體位址之間是靜態映射的,這是為了讓核心執行時,不必進行位址的轉換,藉以加快核心的執行。

例如在PlatformBuilder下產生的platform,啟動時載入的features等就是載入核心的DLL。

if(wFlags&LOAD_LIBRARY_IN_KERNEL){

PHYSICAL_ADDRESSpaRet;

//Loadinginthekerneladdressspace

paRet=GetContiguousPages((DWORD)(eptr->e32_vsize+PAGE_SIZE-1)/PAGE_SIZE,

0,0);

if(paRet==INVALID_PHYSICAL_ADDRESS||!

(pMod->BasePtr=

(PVOID)Phys2Virt(paRet))){

returnERROR_OUTOFMEMORY;

}

}

它先向系統要求連續的實體位址頁面,取得實體位址,再將其直接映射到虛擬位址空間,並把虛擬記憶體位址傳給pMod->BasePtr。

有關GetContiguousPages及Phys2Virt的細節請參考其他的原始檔案。

如果不是載入核心位址空間,則必須為其保留位址空間,以避免其他DLL載入同樣的位址空間,而使該DLL的卸載發生問題。

程式碼如下:

}else{

//trytohonortheDll'srelocationbasetopreventrelocation

if((pTOC->ulKernelFlags&KFLAG_HONOR_DLL_BASE)&&(eptr->e32_vbase+

eptr->e32_vsize

pMod->BasePtr=VirtualAlloc((LPVOID)(ProcArray[0].dwVMBase+

eptr->e32_vbase),eptr->e32_vsize,MEM_RESERVE|MEM_IMAGE,

PAGE_NOACCESS);

DEBUGMSG(pMod->BasePtr,(L"LoadingDLL'%s'atthepreferredloadingaddress

%8.8lx\n",lpszFileName,ZeroPtr(pMod->BasePtr)));

}

其中,eptr->e32_vbase是module映射的虛擬記憶體基底位址,eptr->e32_vsize是module的虛擬記憶體大小。

利用VirtualAlloc在目前處理程序的虛擬記憶體位址空間保留一個區域,基底位址是處理程序位址空間基底位址ProcArray[0].dwVMBase+module映射的虛擬記憶體基底位址。

大小受模組影響。

它使用參數MEM_RESERVE|MEM_IMAGE,只保留了處理程序的一部分虛擬記憶體空間,而沒有實際分配實體記憶體。

而且這部分空間不能透過其他記憶體配置的操作如malloc和LocalAlloc使用,也不能被其他DLL佔用。

如果配置基底位址時,發現要使用的區域已經被保留,即已經有其他DLL佔用,造成配置虛擬記憶體失敗,則由上往下配置。

//allocatetop-downifwecan'tloaditinthedll'spreferredloadbase.

if(!

pMod->BasePtr&&!

(pMod->BasePtr=VirtualAlloc((LPVOID)ProcArray[0].

dwVMBase,eptr->e32_vsize,MEM_RESERVE|MEM_TOP_DOWN|MEM_IMAGE,

PAGE_NOACCESS))){

returnERROR_OUTOFMEMORY;

}

}

配置的基底位址是目前處理程序的虛擬記憶體空間基底位址,大小不變,只是由上往下配置。

這樣就得到了pMod->BasePtr。

下面的程式碼修改DLL的載入基址,這是個全域變數。

if(ZeroPtr(pMod->BasePtr)<(DWORD)DllLoadBase)

DllLoadBase=ZeroPtr(pMod->BasePtr);

如果要載入的DLL是XIP的,即表示它是在ROM中,不需載入到處理程序位址空間,如coredll.dll就是XIP型的DLL。

只需在ROM中找到要執行的DLL的基底位址,傳給pMod->BasePtr即可,如以下程式碼所示:

}else{

e32_rom*e32rp=(e32_rom*)pMod->oe.tocptr->ulE32Offset;

pMod->BasePtr=(LPVOID)MapPtrProc(e32rp->e32_vbase,ProcArray);

if((wFlags&LOAD_LIBRARY_IN_KERNEL)&&!

IsKernelVa(pMod->BasePtr)){

returnERROR_BAD_EXE_FORMAT;

}

}

8.4.4name和o32物件的記憶體配置

o32物件是與存取控制有關的物件。

結構定義如下。

typedefstructo32_lite{

unsignedlongo32_vsize;

unsignedlongo32_rva;

unsignedlongo32_realaddr;

unsignedlongo32_access;

unsignedlongo32_flags;

unsignedlongo32_psize;

unsignedlongo32_dataptr;

}o32_lite,*LPo32_lite;

8.4.5重定位映射

非XIP映射需要重新定址(Relocate)。

if(pMod->oe.filetype!

=FT_ROMIMAGE){

//

//Non-XIPimageneedstoberelocated.

//

if((pMod->oe.pagemode==PM_NOPAGING)&&

!

(pMod->wFlags&LOAD_LIBRARY_AS_DATAFILE)&&

!

Relocate(eptr,pMod->o32_ptr,(ulong)pMod->BasePtr,

((wFlags&LOAD_LIBRARY_IN_KERNEL)?

0:

ProcArray[0].dwVMBase))){

returnERROR_OUTOFMEMORY;

}

這裏呼叫Relocate對DLL重新定址,傳遞的參數為:

可執行檔指標eptr,o32標頭資訊pMod->o32_ptr,DLL載入的基底位址pMod->BasePtr,如果DLL載入到核心,則最後一個參數為0,否則傳遞的參數是目前處理程序虛擬記憶體空間的基址。

這裏還需要解釋一下重新定址(Relocate)的過程。

重新定址的過程,是將DLL映射位址定位到目前處理程序的位址空間,即位址0x00000000-0x02000000,從0x01FFFFFF開始由上往下,最頂端是coredll.dll,然後是其他DLL。

如果是XIP映射,即DLL在ROM中。

如果module被載入記憶體的Slot1(DLL高位址區域),或者載入到核心中,則需要記錄為這個module而定址的讀寫區。

程式碼如下:

o32_lite*optr=

展开阅读全文
相关资源
猜你喜欢
相关搜索

当前位置:首页 > 成人教育 > 成考

copyright@ 2008-2022 冰豆网网站版权所有

经营许可证编号:鄂ICP备2022015515号-1