Iczelion 的 Win32Asm VxD 彙編教程 (三) (轉)

worldblog發表於2007-12-12
Iczelion 的 Win32Asm VxD 彙編教程 (三) (轉)[@more@]

虛擬裝置結構

現在大家對vmm和vxd有了一定的瞭解,接下來我們來看一看如何編寫vxd程式碼。首先,你必須具備 95/98 Device Development Kit。Window95 ddk只有MSDN 訂戶才能拿到,但Windows98 ddk卻可以免費從公司取得。儘管 ddk是面向WDM的,但你還是可以用它來開發VxD程式。你可以從 http://www.microsoft.com/hwdev/ddk/install98ddk.htm?Window98 ddk。
你可以下載整個包(大約30M),也可以只下載你感興趣的部分。如果你沒有下載整個軟體包,那麼別忘了下載other.exe
裡面的Window95 ddk documentation。Windows98 ddk 包含了6.11d版的MASM。你需要把它升級為最新版。如果你不知道到哪裡去下載最新的版本,可以去我的asm.cjb/">主頁上查一查。
Window9x DDK包含了一些Masm32包所不具有的重要庫。
你可以在.org/~michael/files/firstvxd.zip">這裡下載這一章的例子。

LE檔案格式

VxD採用線性可檔案格式(LE)。這種檔案格式是為OS/2 2.0版設計的。它同時包含16位和32位程式碼,這點也是VxD程式的需要。回想VxD在Windows3.x的時代,在那時,從Dos啟動Windows,Windows在把機器轉到保護之前需要在真實模式下做一些初始化。真實模式的16位程式碼必須和32位程式碼一起放在可執行檔案中。所以LE檔案格式理所當然的選擇。幸運的,驅動程式不必在真實模式下初始化,所以它們不必使用LE檔案格式。它們用的是PE檔案格式。

在LE檔案中,程式碼和資料被存放在幾類執行屬性不同的中。以下是一些可用的段類

  • LCODE 頁面鎖定的程式碼和資料段 這種段被鎖定在裡。換句話說,這段永遠不會被放到上去,所以你一定要謹慎的使用這種段類以免浪費寶貴的記憶體。那些每時每刻都必須放在記憶體中的程式碼和資料應該放在這個段裡。尤其是那些中斷處理程式。
  • PCODE 可調頁程式碼段 VMM可以對這種段實行調頁處理,在這種段裡的程式碼不必時刻放在記憶體裡,當VMM需要實體記憶體的時候,它就會把這段放到硬碟上去。
  • PDATA 可調頁資料段
  • ICODE 僅用於的初始化段 這種段裡的程式碼僅僅用來進行VxD的初始化。當初始化完成後,VMM就把這段從記憶體中釋放。
  • OCODE 僅用於的程式碼資料段 當你要除錯VxD程式時,就要用到這種段裡的程式碼和資料,例如,它包含要除錯的訊息的處理程式碼。
  • DE 靜態程式碼和資料段 這種段時刻存在於記憶體中,即使VxD已經解除安裝,這種段對某些動態的VxD程式很有用,這些VxD程式需要在某一Windows程式裡不停的載入/解除安裝而又要紀錄上次的環境和狀態。
  • RCODE 真實模式初始化程式碼資料段 這種段包含真實模式初始化需要的16位程式碼和資料。
  • 16ICODE 16ICODE USE16保護模式初始化資料段 這是一個16位的段,它包含VxD要從保護模式複製到V86模式的程式碼。例如,如果你要把一些V86的程式碼複製到一個虛擬機器上時,你想複製的程式碼就要放在這裡。如果你把它放在其他的段裡,編譯程式就會產生錯誤的程式碼,例如,它會產生32位程式碼而不是16位程式碼。
  • MCODE 鎖定的訊息字串 這種段包含了由VMM訊息宏幫助編譯的訊息字串,這有助於你構造你的驅程的國際版本。
這並不意味著你的VxD程式必須包含以上所有的段,你可以選擇你的VxD程式需要的段。例如,如果你的VxD程式不進行真實模式初始化,那麼就不必包含RCODE段。
大多數時候,你要用到LCODE, PCODEPDATA段。作為一個VxD程式編寫者,為你的程式碼和資料選擇合適的段取決於你自己的判斷。總的來說,你應該儘可能多的使用PCODEPDATA因為這樣VMM就可以在需要的時候把段調入調出記憶體。另外,硬體中斷程式及其所用到的服務必須放在 LCODE段裡。
你不能直接地使用這些段類,你要用這些段類來定義段,這些段的定義被存放在模組定義檔案(.def)中。下面是一個標準的模組定義檔案:
VXD FIRSTVXD
SEGMENTS
  _LPTEXT  CLASS 'LCODE'  PRELOAD NONDISCARDABLE
  _LTEXT  CLASS 'LCODE'  PRELOAD NONDISCARDABLE
  _LDATA  CLASS 'LCODE'  PRELOAD NONDISCARDABLE
  _TEXT  CLASS 'LCODE'  PRELOAD NONDISCARDABLE
  _DATA  CLASS 'LCODE'  PRELOAD NONDISCARDABLE
  CONST  CLASS 'LCODE'  PRELOAD NONDISCARDABLE
  _TLS  CLASS 'LCODE'  PRELOAD NONDISCARDABLE
  _BSS  CLASS 'LCODE'  PRELOAD NONDISCARDABLE
  _LMGTABLE  CLASS 'MCODE'  PRELOAD NONDISCARDABLE IOPL
  _LMSGDATA  CLASS 'MCODE'  PRELOAD NONDISCARDABLE IOPL
  _IMSGTABLE  CLASS 'MCODE'  PRELOAD DISCARDABLE IOPL
  _IMSGDATA  CLASS 'MCODE'  PRELOAD DISCARDABLE IOPL
  _ITEXT  CLASS 'ICODE'  DISCARDABLE
  _IDATA  CLASS 'ICODE'  DISCARDABLE
  _PTEXT  CLASS 'PCODE'  NONDISCARDABLE
  _PMSGTABLE  CLASS 'MCODE'  NONDISCARDABLE IOPL
  _PMSGDATA  CLASS 'MCODE'  NONDISCARDABLE IOPL
  _PDATA  CLASS 'PDATA'  NONDISCARDABLE SHARED
  _STEXT  CLASS 'SCODE'  RESNT
  _SDATA  CLASS 'SCODE'  RESIDENT
  _DBOSTART  CLASS 'DBOCODE'  PRELOAD NONDISCARDABLE CONFONG
  _DBOCODE  CLASS 'DBOCODE'  PRELOAD NONDISCARDABLE CONFORMING
  _DBODATA  CLASS 'DBOCODE'  PRELOAD NONDISCARDABLE CONFORMING
  _16ICODE  CLASS '16ICODE'  PRELOAD DISCARDABLE
  _RCODE  CLASS 'RCODE'
EXPORTS
  FIRSTVXD_DDB  @1
第一個宣告定義了VxD的名稱,一個VxD的名稱必須是全部大寫的,我曾經試過用小寫,結果VxD除了把自己載入記憶體外什麼也不幹。
接下來是段的定義,段的定義包括三個部分:段的名稱,段類和要求的段的執行屬性。你可以看到很多段都基於相同的段類,例如,_LPTEXT, _LTEXT, _LDATA都是基於LCODE段類而且屬性也完全一樣。這樣定義段有利於使程式碼更容易理解。如:LCODE可以包含程式碼和資料,對於一個程式設計師來說,如果他能把資料放到_LDATA段裡,把程式碼放到_LTEXT 段裡,就會顯得很容易理解。最後,這兩個段都會被編譯到最後的可執行程式的同一個段內。
一個VxD程式匯出且僅匯出一個標記:它的裝置描述塊(DDB)。DDB實際上是一個結構,它包含了VMM需要知道的所有的VxD資訊。你必須在模組定義檔案中匯出DDB。
在大多數時候,你可以把上面的.DEF檔案用到你的新建的VxD專案中去。你只要把.DEF檔案裡第一行和最後一行的VxD名字改掉就可以了。在一個的VxD專案中,段的定義是不必要的,段的定義主要用於C的VxD專案編寫,但用在彙編裡也是可以的。你會得到一大堆警告的資訊但是它能彙編成功。你也可以刪掉你在你的專案裡沒有用到的段定義從而去掉這些討厭的警告資訊。
vmm.inc包含了許多用於定義你的原始檔中的段的宏:
 
_LTEXT VxD_LOCKED_CODE_SEG _PTEXT VxD_PAGEABLE_CODE_SEG _DBOCODE VxD_DE_ONLY_CODE_SEG _ITEXT VxD_INIT_CODE_SEG _LDATA VxD_LOCKED_DATA_SEG _IDATA VxD_IDATA_SEG _PDATA VxD_PAGEABLE_DATA_SEG _STEXT VxD_STATIC_CODE_SEG _SDATA VxD_STATIC_DATA_SEG _DBODATA VxD_DEBUG_ONLY_DATA_SEG _16ICODE VxD_16BIT_INIT_SEG _RCODE VxD_REAL_INIT_SEG

每個宏都有它相對應的結束宏,例如,如果你要在你的原始檔中定義一個_LTEXT段,你應該這樣寫:

VxD_LOCKED_CODE_SEG

(把你的程式碼寫在這裡)

VxD_LOCKED_CODE_ENDS

VxD結構

現在你瞭解了LE檔案裡的段,我們可以繼續來看一下原始檔。你會發現VxD程式有一個特點,那就是它用了很多的宏。你可以看到在VxD中宏幾乎無處不在,這都成為一個習慣了。這些宏用來隱藏一些底層的細節,也增加了源程式的可移植性。如果你有興趣,你可以看一看像vmm.inc這一類的庫檔案中的這些宏的定義。
下面是VxD原始檔結構:
 
.386p
include vmm.inc

DECLARE_VIRTUAL_DEVICE FIRSTVXD,1,0, FIRSTVXD_Control, UNDEFINED_DEVICE_ID, UNDEFINED_INIT_ORDER

Begin_control_dispatch FIRSTVXD
End_control_dispatch FIRSTVXD

end


這段源程式給人的第一印象就是:它並不像一個彙編源程式。那是因為它用了很多宏。讓我們來分析一下源程式以便你能很快理解它。

.386p
告訴我們要使用包括特權指令的80386指令。你也可以使用.486p或者.586p.
include vmm.inc
你的每個VxD源程式都必須包含imm.inc,因為它包含了你在源程式裡所要用到的宏的定義。你還可以根據需要包含其他的庫檔案。
DECLARE_VIRTUAL_DEVICE FIRSTVXD,1,0, FIRSTVXD_Control, UNDEFINED_DEVICE_ID, UNDEFINED_INIT_ORDER
正如我們剛才說的,VMM透過VxD程式的裝置描述塊(DDB)來獲取它所需要知道的關於VxD的所有資訊。一個裝置描述塊是一個結構,它包含了許多關於VxD的重要資訊,比如VxD的名字,它的裝置ID,它的VxD服務入口(如果有的話),等等。你可以在imm.inc裡查一查這個結構,它被定義為VxD_Desc_Block。你必須在.DEF 檔案裡匯出這個結構。這個結構有22個資料,但是你只用填寫其中的幾個。然後vmm.inc包含的一個宏會為你初始化並填寫這些資料。這個宏叫做DECLARE_VIRTUAL_DEVICE。它的格式如下:

Declare_Virtual_Device  Name, MajorVer, MinorVer, CtrlProc, DeviceID, InitOrder, V86Proc, PMProc, RefData

你可以看到:VxD源程式中的標號是不區分大小寫的,你可以用大寫,小寫或者混合起來用都可以。讓我們來看一下Declare_virtual_device裡的每一個引數。

  • Name  VxD的名字 最多8個字元。它必須是大寫!在系統中的所有VxD程式裡,它們的名字不能重複,每個VxD的名字應該是唯一的。這個宏同時也會根據這個名字產生DDB的名字,產生的辦法就是:在這個名字的後面加上_DDB。所以如果你的VxD的名字是FIRSTVXD, Declare_Virtual_Device這個宏就會把DDB的名字定為FIRSTVXD_DDB。記住,你還要在.DEF檔案裡匯出DDB。所以你必須使DDB的名字和.DEF檔案定義中的相同。
  • MajorVerMinorVer 你的VxD的主要的和次要的版本。
  • CtrlProc 你的VxD程式的裝置控制函式的名字。裝置控制函式是一個接受和處理VxD程式的控制訊息的函式。你可以把裝置控制函式看作Window函式的等價物。既然我們要用Begin_Control_Dispatch這個宏來生成我們的裝置控制函式,那麼我們應該使用一個標準格式的名字,那就是VxD的名字_ControlBegin_Control_Dispatch這個宏把_Control 加到它後面的那個名字上(而我們又通常把VxD的名字寫在它後面)作為裝置控制函式的名字,所以我們就應該把VxD的名字加上_Control作為CtrlProc 引數的值。
  • DeviceID 你的VxD程式的16位唯一識別符號 當且僅當你的VxD程式需要處理以下情況時你需要用到這個ID:
    • 你的VxD程式匯出一些供其他VxD程式使用的VxD服務。因為20H中斷介面用裝置ID來定位/區分VxD程式,所以一個唯一的ID對你的VxD程式是必要的。
    • Your VxD 你的VxD程式要在初始化中斷2FH,1607H時通知真實模式程式它的存在。
    • Some 一些真實模式軟體(TSR)要用中斷2FH,1605H來載入你的VxD程式。
    如果你的VxD程式不需要一個唯一的裝置ID,你可以把這一項設為UNDEFINED_DEVICE_ID ,如果你需要它,你可以去Microsoft要一個。
  • InitOrder 初始化的順序,簡單的說,就是載入的順序。VMM就按照這個次序來載入VxD程式。每個VxD程式都有一個載入次序號,例如:
      VMM_INIT_ORDER  EQU 000000000H
      DEBUG_INIT_ORDER  EQU 000000000H
      DEBUGCMD_INIT_ORDER  EQU 000000000H
      PERF_INIT_ORDER  EQU 000900000H
      APM_INIT_ORDER  EQU 001000000H


    你可以看到:VMM, DEBUGDEBUGCMD是首先載入的VxD程式,然後是PERFAPM。初始化順序值越低的VxD程式越先被載入。如果你的VxD程式在初始化時需要用到其他VxD程式提供的服務,那麼你必須把初始化順序的值設得比你所要的那個VxD程式的大,這樣,當你的VxD程式載入時,你所要的VxD就已經在記憶體中為你準備好了。如果不想去管你的VxD的初始化順序,就把這個引數填寫為UNDEFINED_INIT_ORDER

  • V86ProcPMProc 你的程式可以匯出供V86和保護模式程式使用的,這兩個引數就是用來填寫這些API的地址。記住,VxD程式除了系統虛擬機器外,還要監控一個或多個執行在DOS或者保護模式下的虛擬機器程式。理所當然的,VxD程式要為DOS和保護模式程式提供API支援。如果你不匯出這些API,你可以不填這兩個引數。
  • RefData 輸入輸出監視器(IOS)要用到的參考資料。只有一種情況下你要用到這個引數:當你在為IOS編寫一個層驅動程式時。否則,你可以不填這個引數。
接下來是 Begin_Control_Dispatch宏。
Begin_control_dispatch FIRSTVXD
End_control_dispatch FIRSTVXD
這兩個宏定義了裝置控制函式,當VxD的控制訊息發生時,VMM就呼叫這個函式。你必須填寫裝置控制函式名字的前半部分,在本例中,我們用的是 FIRSTVXD。這個宏會在你輸入的前半部分後加上_Control作為裝置控制函式的名字。這個名字一定要和你在Declare_virtual_device 宏中給引數CtrlProc填的名字一致。裝置控制函式總是放在鎖定段(VxD_LOCKED_CODE_SEG)內的。上面定義的裝置控制函式什麼也不幹。你需要說明你的VxD程式要響應什麼控制訊息,以及處理這個訊息的函式,你可以用Control_Dispatch宏來實現這一點。
Control_Dispatch message, function
例如,如果你的VxD程式只要處理Device_Init 訊息,你的裝置控制程式要這樣寫:
Begin_Control_Dispatch  FIRSTVXD
  Control_Dispatch  Device_Init, OnDeviceInit
End_Control_DispatchFIRSTVXD
OnDeviceInit就是要處理Device_Init訊息的函式的名字。你可以給你的函式取任何你想取的名字。
你可以用end 直接地結束你的VxD源程式。
綜上所述,一個VxD程式至少包含一個裝置控制塊和一個裝置控制函式。你要用Declare_Virtual_Device宏來定義一個裝置控制塊,用Begin_Control_Dispatch宏來定義一個裝置控制程式。你必須在.def檔案中的EXPORTS下面填寫裝置控制塊的名字,從而匯出該裝置控制塊。

編譯VxD

編譯的過程和編譯普通的win32程式一樣。先呼叫ml.exe編譯asm原始檔,然後用link.exe來連線檔案。不同的地方是ml.exe和link.exe後所帶的命令列引數不同。

 ml -coff -c -Cx -DMASM6 -DBLD_COFF -DIS_32  firstvxd.asm

-coff  表明COFF資料格式
-c   只彙編,不呼叫連線程式來連線,這樣我們就可以在呼叫link.exe的時候使用跟多的引數。
-Cx  儲存公共,外部標記。
-D 定義一個文字宏,例如,-DBLD_COFF定義了一個文字宏BLD_COFF,這個宏用來作為編譯的條件。如果你有興趣,你可以在庫檔案中查詢BLD_COFF,自己親眼看看它對彙編過程起什麼作用。上面的命令列定義了三個文字宏:BLD_COFF,IS_32和MASM6。如果你對C熟悉的話,你會知道這些定義相當於完成以下功能:

#define BLD_COFF
#define IS_32
#define MASM6
link -vxd -def:firstvxd.def  firstvxd.obj

-vxd 表明我們要根據obj檔案來生成一個VxD檔案。
-def:<.def file=""> 指定該VxD檔案的模式定義檔案。

我覺得用makefile很方便,如果你不喜歡用makefile,你也可以建立批處理檔案來自動完成編譯過程。我的makefile如下:

NAME=firstvxd

$(NAME).vxd:$(NAME).obj
  link -vxd -def:$(NAME).def $(NAME).obj

$(NAME).obj:$(NAME).asm
  ml -coff -c -Cx  -DMASM6 -DBLD_COFF -DIS_32 $(NAME).asm


來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/10752043/viewspace-991910/,如需轉載,請註明出處,否則將追究法律責任。

相關文章