Windows的程式建立和映像裝入

Just4life發表於2013-04-28

關於Windows的程式建立和映像裝入的過程,“Microsoft Windows Internals 4e”一書的第六章中有頗為詳細的說明。本文就以此為依據,夾譯、夾敘、夾議地作一介紹。書中說,建立程式的過程分成六個階段,發生於作業系統的三個部分中,那就是:Windows客戶端即某個應用程式的包括Kernel32.dll在內的動態連線庫,Windows的“執行體”、即核心(確切地說是核心的上層),以及Windows子系統的服務程式Csrss中。這六個階段是:

1.         開啟目標映像檔案。

2.         建立Windows的“執行體程式物件”,也就是核心中的“程式控制塊”資料結構。

3.         建立該程式的初始(第一個)執行緒,包括其堆疊、上下文、以及“執行體執行緒物件”,即核心中的“執行緒控制塊”資料結構。

4.         將新建程式通知Windows子系統。

5.         啟動初始執行緒地執行(除非因為引數中的CREATE_SUSPENDED標誌位為1而一建立便被掛起)。

6.         在新程式和執行緒的上下文中完成使用者空間的初始化,包括裝入所需的DLL,然後開始目標程式的執行。

下面分階段敘述。

第一階段:開啟目標映像檔案

在Win32位API中,建立程式是由CreateProcess()完成的。這實際上是個巨集定義,根據不同的情況定義成CreateProcessA()或CreateProcessW()之一,這兩個函式都在kernel32.dll中(可以用工具depends觀察)。兩個函式的區別僅在於字串的表達,前者採用ASCII字元,而後者採用“寬字元”、即Unicode。實際上Windows的內部都採用寬字元,所以前者只是把字串轉換成寬字元格式,然後呼叫後者。

可以在Windows上執行的可執行軟體有好幾類,處理的方法自然就不一樣:

l         Windows的32位.exe映像,直接執行。

l         Windows的16位.exe映像,啟動ntvdm.exe,以原有命令列作為引數。

l         DOS的.exe、.com、或.pif映像,啟動ntvdm.exe,以原有命令列作為引數。

l         DOS的.bat或.cmd批命令檔案(指令碼),啟動cmd.exe,以原有命令列作為引數。

l         POSIX可執行映像,啟動posix.exe,以原有命令列作為引數。

l         OS/2可執行映像,啟動os2.exe,以原有命令列作為引數。

這裡面最重要的當然是32位的.exe映像,而最後兩類現在已經很少見了。從對於除32位.exe以外的各種映像的處理,讀者不妨對比一下Wine對.exe映像的處理,看看這裡有著什麼樣的相似性。

當然,我們在這裡只關心32位.exe映像。對於這一類映像,CreateProcess()首先開啟映像檔案,再為其(分配)建立一個“Section”、即記憶體區間。建立記憶體區間的目的當然是要把映像檔案影射到這個區間,不過此時還不忙著對映,還要看看。看什麼呢?首先是看已經開啟的目標檔案是否一個合格的.exe映像(萬一是DLL映像?)。還要看的事就有點出乎讀者意外了,看的是在“登錄檔”中的這個路徑:

HKLM\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Image File Execution Options

       用depends可以看到,ntdll.dll中有個函式LdrQueryImageFileExecutionOptions(),就是專門幹這個事的。

如果上述路徑下有以目標映像檔案的檔名和副檔名為“鍵”的表項,例如“image.exe”,而表項中又有名為“Debugger”的值,那麼這個值(一個字串)就替換了原來的目標檔名,變成新的目標映像名,並重新執行上述的第一階段操作。這樣做的目的當然是為除錯程式提供方便,但是我們不妨設想:如果黑客或某個木馬程式設法在登錄檔中加上了一個表項?這時候使用者以為是啟動了程式A,而實際啟動的卻是B!。

第二階段:建立核心中的程式物件

我們知道,Linux上的每個程式(執行緒)都有一個“程式控制塊”、即task_struct資料結構,與具體程式/執行緒有關的絕大部分資訊都集中儲存在這個資料結構中。而Windows則有所不同。首先,Windows的程式和執行緒各有不同的“物件”、即資料結構,從概念上把執行緒和程式分離開來。執行緒是具體的(執行)上下文,是CPU排程的單位和目標,而程式只是若干共享地址空間和特性(如排程優先順序)的執行緒的集合。於是,程式有程式的資料結構,執行緒有執行緒的資料結構。這就好像是對一組task_struct資料結構“提取公因子”所形成的結果,這個舉措是很好理解的。進一步,Windows又把本可集中儲存的的程式資料結構也拆分成好幾個物件,有的在核心中,有的則在使用者空間。

核心中與程式有關的物件有:

l         EPROCESS。即struct _EPROCESS,在“Internals”書中也稱為“Process Block”。它代表著Windows的一個程式,‘E’表示“Executive”,微軟把Windows核心中的上層稱為“Executive”、以區別於下層的裝置驅動和記憶體管理等成分、一般翻譯成“執行體”。“Executive”也有“管理”、“執行”的意思(所以CEO就是“總裁”)。

l         KPROCESS。這是EPROCESS內部的一個成分,其名稱就叫“Pcb”。

l         W32PROCESS。下面將要講到,在使用者空間有個“Windows子系統”的服務程式csrss。這個服務程式為系統中的每個Windows應用程式都維持著一個資料結構,其中包含了一些與視窗和圖形介面有關的資訊。而對於視窗和圖形介面的操作原來也是由csrss在“客戶”程式的請求下實現的。但是,為了提高效率,後來把這部分功能移到了核心中。與此相應,有關資料結構的一部分也需要移到核心中,就成了W32PROCESS。

既然KPROCESS是EPROCESS一部分,實際上核心中與程式有關的物件實際上只有兩種,就是EPROCESS和W32PROCESS。不過這裡沒有包括“開啟物件表”,那也是每個程式都有的(Linux核心中的“開啟檔案表”也在程式控制塊的外面)。

使用者空間與程式有關的物件有:

l         如上所述,把W32PROCESS資料結構移入核心以後,csrss仍需要為每個Windows程式保持一些別的資訊,所以csrss內部仍有按程式的相應資料結構。

l         PEB(Process Environment Block)、即“程式環境塊”。PEB中記錄著程式的執行引數、映像裝入地址等等資訊。PEB在使用者空間中的位置是固定的,總是在0x7ffdf000。在Windows中,使用者空間和系統空間的分界線是2GB、即0x80000000,所以PEB在靠近使用者空間頂端的地方。

“Internals”書中並未給出有關資料結構的定義,但是通過Debug手段給出了EPROCESS的內部結構:

   +0x000            Pcb                   : _KPROCESS

   +0x06c            ProcessLock            : _EX_PUSH_LOCK

   +0x070            CreateTime              : _LARGE_INTEGER

   +0x078            ExitTime                : _LARGE_INTEGER

   +0x080            RundownProtect         : _EX_RUNDOWN_REF

   +0x084            UniqueProcessId          : Ptr32Void

   +0x088            ActiveProcessLinks              : _LIST_ENTRY

   +0x090            QuotaUsage             : [3]  Uint4B

   +0x09c            QuotaPeak              : [3]  Uint4B

   +0x0a8            CommitCharge          : Uint4B

   +0x0ac            PeakVirtualSize            : Uint4B

   +0x0b0            VirtualSize                : Uint4B

   +0x0b4            SessionProcessLinks     : _LIST_ENTRY

   +0x0bc            DebugPort              : Ptr32Void

   +0x0c0            ExceptionPort            : Ptr32Void

   +0x0c4            ObjectTable              : Ptr32_HANDLE_TABLE

   +0x0c8            Token                 : _EX_FAST_REF

   +0x0cc            WorkingSetLock         : _FAST_MUTEX

   +0x0ec            WorkingSetPage          : Uint4B

   +0x0f0             AddressCreationLock    : _FAST_MUTEX

   +0x110            HyperSpaceLock         : Uint4B

   +0x114            ForkInProgress           : Ptr32_ETHREAD

   +0x118            HardwareTrigger                : Uint4B

可見,EPROCESS的第一個成分是Pcb,其型別是_KPROCESS、即KPROCESS,這是一個大小為0x6c的資料結構。書中也給出了它的內部結構。

“Undocumented Windows 2000 Secrets”一書也以Debug手段給出了這個資料結構的內部結構,但是列出的結構與此有很大的不同,也許是因為版本的關係。從所列的內容看,似乎“Secrets”一書倒是正確的,因為那裡所列的EPROCESS結構中有關於虛存的成分Vm,是一個大小為0x50的資料結構,而這裡沒有,但是虛存(地址空間)顯然是程式的主要資源,所以EPROCESS資料結構中理應有它的位置。由此看來,“Secrets”一書所述更接近於桌面和伺服器系統的現實,而“Internals”書中所列可能更接近於不帶MMU的嵌入式系統。而且,“Secrets”一書還在附錄C中給出了通過逆向工程手段得到的EPROCESS和PEB等資料結構的定義(程式碼),這當然是很有價值的。

那麼,如果確有不同版本的EPROCESS,這會有什麼影響呢?首先,使用者空間的應用程式不能直接訪問核心中的EPROCESS資料結構,所以具體的EPROCESS資料結構屬於核心的內部實現,只要核心中的各種成分、各個環節都配套成龍,“自圓其說”,就沒有什麼問題,這跟Linux核心中一些條件編譯和裁剪的效果是類似的。可是,另一方面,對於可以動態裝入的.sys模組,如果在模組中需要訪問這些資料結構,那就可能有問題了,因為.sys模組都是以二進位制映像的形式提供的,不像在Linux中那樣可以由原始碼重新編譯。怎麼辦呢?我們可以到Windows的DDK中去找找答案。

在DDK的.h檔案中,有函式IoGetCurrentProcess()的申明:

NTKERNELAPI

PEPROCESS

IoGetCurrentProcess(

    VOID

);

這個函式是核心為.sys模組提供的支撐函式,相當於由Linux核心匯出的函式。其返回值型別是PEPROCESS,就是指向EPROCESS資料結構的指標。顯然,這跟Linux核心中的current相似,呼叫的目的是獲取當前程式的EPROCESS資料結構(指標)。但是,DDK的.h檔案中卻並未給出EPROCESS資料結構的定義,所以呼叫這個函式所得到的僅僅是個指標,實際上與void*並無區別。這意味著在.sys模組中是不允許直接訪問其內部成分的。那麼,.sys模組如何使用這個指標呢?下面就是一個例子,還是在DDK中:

NTKERNELAPI

VOID

MmProbeAndLockProcessPages (

    IN OUT PMDL MemoryDescriptorList,

    IN PEPROCESS Process,

    IN KPROCESSOR_MODE AccessMode,

    IN LOCK_OPERATION Operation

    );

這個函式的作用是鎖定某個程式的某些儲存頁面(不讓換出),其輸入引數之一就是指向該程式的EPROCESS結構的指標。當然,這個函式也是由核心提供的(屬於我們所說的裝置驅動介面)。所以指標的提供者和使用者都是核心,只要這二者配套即可,.sys模組在這裡只不過是傳遞了一下,所以也不會有問題。

假定proc是指向程式控制塊的指標,並且程式控制塊中有個成份X,是個整數,那麼在Linux的動態安裝模組中可以直接用“proc->X”訪問這個成分,但是在Windows的.sys模組中則只能通過類似於get_X()、set_X()一類的支撐函式訪問這個成分。將資料結構的內容跟對於這些內容的操作(method)相分離,正是“物件”與“資料結構”的區別所在。而將資料結構的內容“封裝”起來,也正是微軟所需要的,因為它不願意公開這些資料結構。

對於相容核心的開發,這意味著我們不必拘泥於採用與Windows完全一致的EPROCESS資料結構(儘管“Secrets”的附錄C已經給出了它的定義),一些內部的操作和處理也不必完全與之相同,而只要與DDK所規定的介面相符就可以了。

瞭解了有關的程式物件,我們可以言歸正傳了。

所謂建立核心中的程式物件,實際上就是建立以EPROCESS為核心、為基礎的相關資料結構,這就是系統呼叫NtCreateProcess()要做的事情,主要包括:

l         分配並設定EPROCESS資料結構。

l         其他相關的資料結構的設定,例如“開啟物件表”。

l         為目標程式建立初始的地址空間。

l         對目標程式的“核心程式塊”KPROCESS進行初始化。

l         將系統DLL的映像對映到目標程式的(使用者)地址空間。

l         將目標程式的映像對映到其自身的使用者空間。

l         設定好目標程式的“程式環境塊”PEB。

l         對映其他需要對映到使用者空間的資料結構,例如與“當地語言支援”、即NLS有關的資料結構。

l         完成EPROCESS建立,將其掛入程式佇列(注意受排程的是執行緒佇列而不是程式佇列)。

這裡將系統DLL、實際上是ntdll.dll、對映到目標程式的使用者空間是很關鍵的。這是因為,除別的、主流的功能和作用外,ntdll.dll同時也起著相當於Linux中ELF“直譯器”的作用,也擔負著為目標映像建立動態連線的任務。

值得注意的是,NtCreateProcess()與CreateProcess()不同。CreateProcess()建立一個程式並使其(初始執行緒)執行,除非在建立時就指定要將其掛起。而NtCreateProcess(),則只是在核心中建立該程式的EPROCESS資料結構併為其建立起地址空間。這只是個空殼架子,因為沒有執行緒就談不上執行,排程的目標是執行緒而不是執行緒。而且,對NtCreateProcess()的呼叫還有個條件,那就是目標映像已經被對映到一個儲存區間(Section)中。

第三階段:建立初始執行緒

如上所述,程式只是個空架子,實際的執行實體是裡面的執行緒。所以下一步就是建立目標程式的初始執行緒,即其第一個執行緒。

與EPROCESS相對應,執行緒的資料結構是ETHREAD,並且其第一個成分是資料結構KTHREAD,稱為TCB。同樣,“Internals”和“Secrets”兩本書中所列的ETHREAD內部結構有所不同,後者的附錄C中給出了通過逆向工程得到的ETHREAD資料結構定義。

同樣,從Windows DDK中申明的一些函式也可以看出,.sys模組只是傳遞ETHREAD指標或KTHREAD指標(由於KTHREAD是ETHREAD中的第一個成分,二者實際上是一回事),而不會直接訪問它的具體成分。

PKTHREAD NTAPI KeGetCurrentThread();

NTKERNELAPI  KPRIORITY

KeQueryPriorityThread (IN PKTHREAD Thread);

NTKERNELAPI  LONG

KeSetBasePriorityThread (IN PKTHREAD Thread, IN LONG Increment);

NTKERNELAPI  PDEVICE_OBJECT

IoGetDeviceToVerify(IN PETHREAD Thread);

此外,就像程式有“程式環境塊”PEB一樣,執行緒也有“執行緒環境塊”TEB,KTHREAD結構中有個指標指向其存在於使用者空間的TEB。前面講過,PEB在使用者空間的位置是固定的,PEB下方就是TEB,程式中有幾個執行緒就有幾個TEB,每個TEB佔一個4KB的頁面。

這個階段的操作是通過系統呼叫NtCreateThread()完成的,主要包括:

l         建立和設定目標執行緒的ETHREAD資料結構,並處理好與EPROCESS的關係(例如程式塊中的執行緒計數等等)。

l         在目標程式的使用者空間建立並設定目標執行緒的TEB。

l         將目標執行緒在使用者空間的起始地址設定成指向Kernel32.dll中的BaseProcessStart()或BaseThreadStart(),前者用於程式中的第一個執行緒,後者用於隨後的執行緒。使用者程式在呼叫NtCreateThread()時也要提供一個使用者級的起始函式(地址),BaseProcessStart()和BaseThreadStart()在完成初始化時會呼叫這個起始函式。ETHREAD資料結構中有兩個成份,分別用來存放這兩個地址。

l         設定目標執行緒的KTHREAD資料結構併為其分配堆疊。特別地,將其上下文中的斷點(返回點)設定成指向核心中的一段程式KiThreadStartup,使得該執行緒一旦被排程執行時就從這裡開始執行。

l         系統中可能登記了一些每當建立執行緒時就應加以呼叫的“通知”函式,呼叫這些函式。

第四階段:通知Windows子系統

Windows、確切地說是Windows NT、當初的設計目標是支援三種不同系統的應用軟體。第一種是Windows本身的應用軟體,即所謂“Native”Windows軟體,這是微軟開發Windows NT的真正目的。第二種是OS/2的應用軟體,這是因為當時微軟與IBM還有合作關係。第三種是與Unix應用軟體相似、符合POSIX標準的軟體,那是因為當時美國的軍方採購有這樣的要求。不過實際上微軟對後兩種應用的支援從一開始就是半心半意的,後來翅膀長硬了,就更不必勉為其難了。但是,儘管如此,當初在設計的時候還是考慮了對不同“平臺”的支援,即在同一個核心的基礎上配以不同的外圍軟體,形成不同的應用軟體執行環境,微軟稱之為“子系統(Subsystem)”。於是,在Windows核心上就有了所謂“Windows子系統”、“OS/2子系統”、和“POSIX子系統”。當然,時至今日,實際上只剩下Windows子系統了。

那麼,所謂子系統是怎樣構成的呢?“Internals”書中闡明瞭Windows子系統的構成,說這是由下列幾個要素構成的。

一、子系統程式csrss.exe。包括了對下列成分和功能的支援:

l         控制檯(字元型)視窗的操作。面向控制檯/終端的應用本身不支援視窗操作(例如視窗的移動、大化/小化、遮蓋等等),但是又需要在視窗中執行,所以需要有額外的支援。

l         程式和執行緒的管理。例如彈出一個對話窗,說某個程式沒有響應,讓使用者選擇是否結束該程式的執行,等等。每個Windows程式/執行緒再建立/退出時都要向csrss.exe程式發出通知。

l         DOS軟體和16位Windows軟體在(32位)Windows上的執行。

l         其它。包括對當地語言(輸入法)的支援。

這個程式之所以叫csrss,是“C/S Run-time SubSystem”的意思,csrss是Windows子系統的服務程式。其實三個子系統都是C/S結構,但是OS/2子系統的服務程式稱為os2ss,POSIX子系統的服務程式稱為Psxss。之所以如此,據“Internals”說,是因為最初時三個子系統的服務程式是合在一起的,就叫csrss,後來才把那兩個子系統移了出來另立門戶,但剩下的還繼續叫csrss。

二、核心中的圖形裝置驅動、即Win32k.sys模組。其功能包括:

l         視窗管理,控制著視窗的顯示和各種螢幕輸出(例如游標),還擔負著從鍵盤、滑鼠等裝置接收輸入並將它們分發給各個具體應用的任務。

l         為應用軟體提供一個圖形函式庫。

三、若干“系統DLL”,如Kernel32.dll、Advapi32.dll、User32.dll、以及Gdi32.dll。

上述的第二個要素Win32k.sys原先也是和csrss.exe合在一起的,這部分功能也由服務程式在使用者空間提供。應用程式通過程式間通訊向csrss發出圖形操作請求,由csrss完成有關的圖形操作。但是後來發現頻繁的程式間通訊和排程成了瓶頸,所以就把這一部分功能剝離出來,移進了核心,這就是Win32k.sys。這一來,對於一般的32位Windows應用而言,留給csrss、或者說必須要通過csrss辦的事就很少了。但是,儘管如此,在建立WIndows程式時還是要通知csrss,因為它擔負著對所有WIndows程式的管理。另一方面,csrss在接到通知以後就會在螢幕上顯示那個沙漏狀的游標,如果這是個有視窗的程式的話。

注意這裡向csrss發出通知的是父程式、即呼叫CreateProcess()的程式,而不是新建立出來的程式,它還沒有開始執行。

至此CreateProcess()的操作已經完成,從CreateProcess()返回就退出了kernel32.dll,回到了應用程式或更高層的DLL中。這四個階段都是立足於父程式的使用者空間,在整個過程中進行了多次系統呼叫,每次系統呼叫完成後都回到使用者空間中。例如,在第二階段中就呼叫了NtCreateProcess(),第三階段中就呼叫了NtCreateThread(),而整個建立程式的過程包括了許多次系統呼叫(有些系統呼叫屬於細節,所以上面並未提及)。

其實Linux的程式建立也不是一次系統呼叫就可完成的,典型的過程就包括fock()、execve()等系統呼叫,但是在Windows上就更多了。這跟Windows的整個系統呼叫介面的設計有關。以使用者空間的記憶體分配為例,Linux的系統呼叫brk()只有一個引數,那就是區間的長度,但是Windows的系統呼叫NtAllocateVirtualMemory()卻有6個引數,其第一個引數是ProcessHandle,這是標誌著一個已開啟程式物件的Handle。這說明什麼呢?這說明Linux程式只能為自己分配空間,而Windows程式卻可以為別的程式分配空間。或者說,在儲存空間的分配上Linux程式是“自力更生”的,而Windows程式卻可以“包辦代替”。

這對於系統設計的影響可能遠超讀者的想像。就拿為子程式的第一個執行緒分配使用者空間堆疊而言,既然Linux程式(執行緒)只能為自己分配空間,而使用者空間堆疊又必須在進入使用者空間執行之前就已存在,那就只好在核心中完成使用者空間堆疊的分配。相比之下,Windows程式可以為別的程式分配空間,於是就可以由父程式在使用者空間中為子程式完成這些操作。這樣,有些事情Linux只能在核心中做,而Windows可以在使用者空間做。有些人稱Windows為“微核心”,這或許也是個原因。而WindowsCreateProcess()中包含著更多的系統呼叫,也就很好理解了。

現在,雖然父程式已經從庫函式CreateProcess()中返回了,子程式的執行卻還未開始,它的執行還要經歷下面的第五和第六兩個階段。

第五階段:啟動初始執行緒

新建立的執行緒未必是可以被立即排程執行的,因為使用者可能在建立時把標誌位CREATE_ SUSPENDED設成了1。如果那樣的話,就需要等待別的程式通過系統呼叫恢復其執行資格以後才可以被排程執行。否則現在已經可以被排程執行了。至於什麼時候才會被排程執行,則就要看優先順序等等條件了。而一旦受排程執行,那就是以新建程式的身份在執行、與CreateProcess()的呼叫者無關了。

如前所述,當程式的第一個執行緒首次受排程執行時,由於執行緒(系統空間)堆疊的設定,首先執行的是KiThreadStartup。這段程式把目標執行緒的IRQL從DPC級降低到APC級,然後呼叫核心函式PspUserThreadStartup()。

最後,PspUserThreadStartup()將使用者空間ntdll.dll中的函式LdrInitializeThunk()作為APC函式掛入APC佇列,再企圖“返回到”使用者空間。Windows的APC跟Linux的signal機制頗為相似,相當於使用者空間的“中斷服務”。所以,在返回使用者空間的前夕,就會檢查APC函式的存在並加以執行(如果存在的話)。

於是,此時的CPU將兩次進入使用者空間。第一次是因為APC請求的存在而進入使用者空間,執行APC函式LdrInitializeThunk(),執行完畢以後仍回到系統空間。然後,第二次進入使用者空間才是“返回”使用者空間。返回到使用者空間的什麼地方呢?前面已經講到,返回到Kernel32.dll中的BaseProcessStart()或BaseThreadStart(),對於程式中的第一個執行緒是BaseProcessStart()。至於使用者程式所提供的(執行緒)入口,則是作為引數(函式指標)提供給BaseProcessStart()或BaseThreadStart()的,這兩個函式都會使用這指標呼叫由使用者提供的入口函式。

第六階段:使用者空間的初始化和DLL的連線

使用者空間的初始化和DLL的連線是由LdrInitializeThunk()作為APC函式的執行來完成的。

在應用軟體與動態連線庫的連線這一點上,我們已經看到,不管是Linux、Windows、還是Wine,都是一致的,那就是在使用者空間完成:

l         Linux的.so模組連線由“直譯器”在使用者空間完成。“直譯器”相當於一個不需要事先連線的動態庫,因為它的入口是固定的。“直譯器”的映像是由核心裝入使用者空間的。

l         Windows的DLL連線由ntdll.dll中的LdrInitializeThunk()在使用者空間完成。在此之前ntdll.dll與應用軟體尚未連線,但是已經被對映到了使用者空間。函式LdrInitializeThunk()在映像中的位置是系統初始化時就預先確定並記錄在案的,所以在進入這個函式之前也不需要連線。

l         Wine的動態庫連線分兩種情況。一種是ELF格式的.so模組,另一種是PE格式的DLL。二者的連線都是在使用者空間完成的,前者仍由ELF直譯器ld-linux.so.2完成,後者則由工具軟體wine-kthread完成。後者的具體呼叫路徑是:

main() > wine_init() > __wine_process_init() > __wine_kernel_init() >

wine_switch_to_stack() > start_process() > LdrInitializeThunk()

這在“Wine的二進位制映像裝入和啟動”那篇漫談中已經講到過了。注意這裡最終完成DLL連線的函式也叫LdrInitializeThunk(),顯然Wine的作者對於Windows的這一套是清楚的。

通過以上的敘述,我們可以看到Windows的程式建立過程與Linux有較大的不同,但是裝入PE映像和實現DLL連線的過程卻與Linux的對應過程相似,只是把“直譯器”整合到了“系統DLL”裡面,並且是作為APC函式執行的,其他就沒有太大的區別了。但是,如果跟Wine的PE映像裝入過程相比,則顯然Wine的過程(見“Wine的二進位制映像裝入和啟動”)是比較複雜、效率也比較低的。

相關文章