国产探花免费观看_亚洲丰满少妇自慰呻吟_97日韩有码在线_资源在线日韩欧美_一区二区精品毛片,辰东完美世界有声小说,欢乐颂第一季,yy玄幻小说排行榜完本

首頁 > 學(xué)院 > 開發(fā)設(shè)計 > 正文

利用DelayLoad來優(yōu)化應(yīng)用程序的性能,攔截API

2019-11-17 05:35:05
字體:
供稿:網(wǎng)友
    在 1998年12月的MSJ出版刊物中, Jeffrey和我寫了關(guān)于 在 vc6中使用DelayLoad 功能的專欄.最終結(jié)果,是證實了它是多么cool.但是,不幸的是,還有很多人不了解DelayLoad,他們以為這個新特點是 最新版本的WINNT才有的.
    在開始的時候,讓我重申一遍:DelayLoad不是最新的操作系統(tǒng)帶的特有功能,它可以在任何win32系統(tǒng)中起作用.我將寫一個簡單例子來說明. DelayLoadPRofile, 實現(xiàn)了一個很小功能,很多程序都可以得益于它.


預(yù)覽:
    通常的,當調(diào)用一個dll中的函數(shù)時,連接器會將dll和函數(shù)加入你的可執(zhí)行文件.最后,所有引用的函數(shù)會放在imports段中.  當加載該程序的時候,win32程序加載器會掃描所有imports段的每個dll.加載,和重新定位imports段的所有函數(shù),將信息寫入 引入地址表(ImportAddress Table, IAT).簡單說來,IAT就是一個函數(shù)指針的表.調(diào)用該 引入函數(shù)的時候,就到IAT中去找.  那么,DelayLoad的機理是什么呢?當你為一個Dll進行"DelayLoad"的時候,連接器不將原來的值放入imports段,相反,它為每個DelayLoad的引入函數(shù)的名稱和地址,生成一個小的根區(qū), 備份下來。第一次引用的時候,它調(diào)用LoadLibrary加載Dll,然后,它調(diào)用GetProcAddress取得該函數(shù)的地址。最后,改寫自己在IAT的值,以便以后的程序可以直接調(diào)用.
    上面的是簡化的步驟.實際上,根區(qū)是一小段代碼,它以靜態(tài)的方式連接到可執(zhí)行文件中.代碼在delayimp.lib中,必須被 連接程序引用.并且,該代碼要足夠智能,當一個函數(shù)第一次被引用的時候,要調(diào)用LoadLibrary,以后調(diào)用就不用引用了.  和引用Dll相比,DelayLoad不會加太多的時間和空間,這種方式 調(diào)用LoadLibrary只會引起稍微一點點的性能損失.每次程序啟動,在針對引入表的函數(shù)地址定位的時候,依次對DelayLoad引入的調(diào)用GetProcAddress,相對于Win32加載器來說,所損失的性能也可以忽略不記.
    然而,DelayLoad帶來的好處也是不可比擬的.例如:假如你的程序從來沒有 從Delay調(diào)用引入的函數(shù),Dll的第一次是不會被加載的。有時候,這個情況的出現(xiàn)頻率出乎你想象。假如,你的程序中,包含打印的代碼,毫無疑問,即使用戶沒有使用打印功能,你的程序也一定要加載winspool.drv。在這種情況下,使用DelayLoad,你就不必加載和初始化Winspool.drv.
    另外一個好處就是:DelayLoad可以避免調(diào)用某些目標平臺不存在的API。例如,假如你的程序需要調(diào)用AnimateWindow,這個API在Win2000和Win98中存在,但是在Win95和WinNT4中,就不存在,假如你用常規(guī)的方式調(diào)用AnimateWindow,那么,你的程序?qū)⒉荒茉僭缙诘钠脚_中運行。然而,你可以用DelayLoad進行對AnimateWindow的加載檢查。這樣,你就不必改寫你的代碼為LoadLibrary和GetProcAddress的方式了。
    DelayLoad是很輕易使用的。當你決定哪個dll你想使用DelayLoad,只需要簡單的增加/DELAYLOAD:DLLNAME。其中,DLLNAME是相關(guān)的DLL文件名。你還需要增加DELAYIMP.LIB到連接庫中,你也需要原來的LIB,例如,SHELL32.LIB。把全部放到一塊,連接的命令就如下: SHELL32.LIB /DELAYLOAD:SHELL32.DLL DELAYIMP.LIB  很不幸,Visual Studio 6.0 IDE 不提供一個簡單的方法去實現(xiàn)一個Dll的DelayLoad。所以,你必須手工加入:/DELAYLOAD:XXX 命令行到 "Project settings"->"Link"->"Project Options"中。
   
什么時候需要DelayLoad:
    當你有小的工程,它調(diào)用了多個dll,就是一個好的DelayLoad候選例子。然而,工程可能在以后由于其它開發(fā)者的加入而變大,很輕易丟失調(diào)用dll的跟蹤。我通常用sdk中的depends.exe。一個只有少數(shù)函數(shù)要引入的dll就是一個好的開始。
    然而,我想找到一個簡單的,自動的方法來跟蹤。于是,出來了DelayLoadProfile程序。它是一個exe,可以監(jiān)視你的exe文件對dll的調(diào)用,直到你的exe結(jié)束。它打印出dll被調(diào)用的情況的匯總,包括多少個dll被調(diào)用,每個dll有多少個函數(shù)被引入。
    我在這里強調(diào):DelayLoadProfile只是針對exe有效,當它涵蓋你的程序所關(guān)聯(lián)的所有dll的時候,有時會造成一點點復(fù)雜。DelayLoadProfile只給你哪個dll可以用DelayLoad開關(guān)的暗示,你最好在不確定的時候,使用原來的處理方法。

DelayLoadProfile:具體描述
    其實DelayLoadProfile的原理很簡單:重定向 exe中,IAT的函數(shù)的指針到一段根區(qū)。根區(qū)簡單的標志一下,引入的函數(shù)被調(diào)用了。然后,跳入原來的Win32加載提供的IAT地址。只是,難的是如何實現(xiàn)。
    第一,你必須決定,要在哪里運行你的代碼,實現(xiàn)對exe的IAT入口的更改,把他們指定到那段根區(qū)去。這些都是在進程外完成。這樣可以避免你的代碼牽涉到目的exe進程中。這個可以用遍歷所有的數(shù)據(jù)結(jié)構(gòu),定位和修改IAT結(jié)構(gòu)的方法。我在這里利用了很多ReadProcessMemory調(diào)用。

    接著的艱苦工作是要在和目的exe相同的進程空間里完成。幾乎是很瑣碎的工作:遍歷所有的數(shù)據(jù)結(jié)構(gòu),建立根區(qū),從定向IAT入口,然后在完成的時候,匯總結(jié)果。然而,為了完成進程空間的工作,在exe進程運行的時候,一些 DelayLoadProfile代碼必須被加載到目的exe的進程空間。這個是我要做的。
    當確認到需要在目的進程中,加載我的代碼的時候,下個問題就是如何把我的代碼加入到目的進程中。其中一個選擇就是,要求用戶連接我的DelayLoadProfile庫,這個會造成用戶的很大量的對他們源代碼工程,或者Makefile的更改,所以,我不能采用,現(xiàn)在需要一個完全自動化的方法。
    在這點上,我想到了加載程序,然后,插入我的DelayLoadProfiledll進去,一個技術(shù)就是用CreateRemoteThread,在目標進程,創(chuàng)建一個LoadLibrary的線程。我放棄了這個,因為,win9x中,不提供CreateRemoteThread.
    很久以前的MSJ讀者可能記得我5年前寫的一個叫APISPY32的程序。它加載一個進程,插入一個dll來記錄API的調(diào)用。那個有點像我今天的DelayLoadProfile工作。然而,我在Win200中,調(diào)用那個dll失敗。有一點點問題。我覺得現(xiàn)在是時候要重讀那段代碼,并且改正那個錯誤了。
   
繼續(xù)深入:
    重新溫習(xí)一下,DelayLoadProfile包含2部分,一,是進程加載功能,它會注射一個dll到你的進程的地址空間。然后,那個dll掃描你的所有的exe IAT,重新定向他們到dll創(chuàng)建的根區(qū)中。當你的程序完成后,注射的dll會掃描所有的根區(qū),統(tǒng)計出多少dll和函數(shù)它調(diào)用的。假如你曾經(jīng)用過APIMON的相關(guān)部件,你將認出類似的技術(shù)細節(jié)。
    完成所有的工作,包括 監(jiān)視 一個程序的引入的dll,叫DelayLoadProfileDLL.(看Figure 1).它用到DLL_PROCESS_ATTACH和DLL_PROCESS_DETACH來初始化2個主要的工作。
    當DllMain獲得DLL_PROCESS_ATTACH的消息的時候,DelayLoadProfileDLL調(diào)用PrepareToProfile(),在PrepareToProfile中,代碼加載目的EXE的IAT,對于每個它發(fā)現(xiàn)的引用的DLL,代碼還檢測是否安全的重定向IAT。通過IsModuleOKToHook函數(shù)來檢測,大多數(shù)情況下,是安全的,因此,PrepareToProfile包括了RedirectIAT函數(shù)。
    RedirectIAT是比較復(fù)雜的函數(shù)。假如你理解了 winini.h中的引入相關(guān)數(shù)據(jù)結(jié)構(gòu),你將得到很大的幫助。首先,函數(shù)定位IAT和相關(guān)的引入名字表,然后,計算有多少個IAT入口,掃描所有的IAT,查找NULL的指針。得到了數(shù)目后,程序?qū)?chuàng)建一個DLPD_IAT_STUB根區(qū),每個根區(qū)對應(yīng)一個IAT入口。
    最后,代碼重新掃描IAT,獲取每個IAT入口的地址,用根區(qū)的一個包含JMP指令的地址,替換IAT入口。它還掃描下一個IATDLPD_IAT_STUB根區(qū)。我在后面將還會繼續(xù)解析。
    在重定向IAT入口的根區(qū)中,有2個值得提起:1,IAT經(jīng)常被放到EXE的只讀段,通常,嘗試改寫只讀段,會引起訪問違規(guī),幸運的,VirtualProtect答應(yīng)你更改一個目的地址的屬性。現(xiàn)在必須更改iat的屬性為讀/寫。完成后,代碼要恢復(fù)IAT段原來的屬性。
    另外一個要注重的地方,就是在重定向IAT的時候,有數(shù)據(jù)引入的問題。雖然程序員們很少這樣做,但是,很輕易用增加的代碼去導(dǎo)入數(shù)據(jù)。vc++運行庫DLL(MSVCRT.DLL)有數(shù)據(jù)導(dǎo)出。假如重定向一個數(shù)據(jù)IAT的入口,會導(dǎo)致問題。
    那么,如何判定一個IAT是數(shù)據(jù)呢?一個商業(yè)的軟件,應(yīng)該用準確的算法來判定一個IAT入口的類型。但是,我在這里用了一個快捷方法。就是IsBadWritePtr。假如IAT包含的指針是可寫的,那么,很可能是一個數(shù)據(jù)指針。假如是只讀的,那么,應(yīng)該是一段代碼。這個測試合適嗎?不,但是,它對DelayLoadProfile是足夠了。
    現(xiàn)在看一下根區(qū),在DelayLoadProfileDLL.h中定義的DLPD_IAT_STUB結(jié)構(gòu)包含著代碼和數(shù)據(jù)。簡單來說,就是如下:
  CALL DelayLoadProfileDLL_UpdateCount
  JMP xxxxxxxx //original IAT 地址
  DWord count
  DWORD pssNameOrOrdinal

    當exe調(diào)用其中一個重定向的函數(shù)時,控制權(quán)被轉(zhuǎn)到根區(qū)的CALL指令中,調(diào)用DelayLoadProfileDLL.CPP

發(fā)表評論 共有條評論
用戶名: 密碼:
驗證碼: 匿名發(fā)表
主站蜘蛛池模板: 江门市| 汾西县| 高密市| 垫江县| 阿克苏市| 汾西县| 丰都县| 前郭尔| 宜昌市| 哈密市| 高阳县| 通河县| 肇庆市| 体育| 修水县| 陆丰市| 禹州市| 和龙市| 旌德县| 三都| 盐津县| 远安县| 武威市| 永兴县| 云南省| 天津市| 清镇市| 石屏县| 汕头市| 水富县| 阳泉市| 牡丹江市| 江北区| 凤庆县| 鹿邑县| 铜山县| 特克斯县| 惠来县| 陆川县| 阿拉善左旗| 谷城县|