Iczelion 的 Win32Asm VxD 汇编教程 (三) (转)

Iczelion 的 Win32Asm VxD 汇编教程 (三) (转)[@more@]

虚拟设备驱动程序结构

现在大家对vmm和vxd有了一定的了解,接下来我们来看一看如何编写vxd代码。首先,你必须具备 windows 95/98 Device driver Development Kit。Window95 ddk只有MSDN 订户才能拿到,但Windows98 ddk却可以免费从 microsoft公司取得。尽管 windows 98 ddk是面向WDM的,但你还是可以用它来开发VxD程序。你可以从  http://www.microsoft.com/hwdev/ddk/install98ddk.htm? 下载Window98 ddk。
你可以下载整个 软件包(大约30M),也可以只下载你感兴趣的部分。如果你没有下载整个软件包,那么别忘了下载 other.exe
里面的Window95 ddk documentation。Windows98 ddk 包含了6.11d版的MASM。你需要把它升级为最新版。如果你不知道到哪里去下载最新的版本,可以去我的 win32asm.cjb .NET/">主页上查一查。
Window9x DDK包含了一些Masm32包所不具有的重要库 文件
你可以在 shell.org/~michael/files/firstvxd.zip">这里下载这一章的例子。

LE文件格式

VxD采用线性可 执行文件格式(LE)。这种文件格式是为OS/2 2.0版设计的。它同时包含16位和32位代码,这点也是VxD程序的需要。回想VxD在Windows3.x的时代,在那时,从Dos启动Windows,Windows在把机器转到保护 模式之前需要在实模式下做一些初始化。实模式的16位代码必须和32位代码一起放在可执行文件中。所以LE文件格式理所当然的选择。幸运的, windows NT驱动程序不必在实模式下初始化,所以它们不必使用LE文件格式。它们用的是PE文件格式。

在LE文件中,代码和数据被存放在几类运行属性不同的中。以下是一些可用的段类

  • LCODE 页面锁定的代码和数据段 这种段被锁定在内存里。换句话说,这段永远不会被放到硬盘上去,所以你一定要谨慎的使用这种段类以免浪费宝贵的内存。那些每时每刻都必须放在内存中的代码和数据应该放在这个段里。尤其是那些硬件中断处理程序。
  • PCODE 可调页代码段 VMM可以对这种段实行调页处理,在这种段里的代码不必时刻放在内存里,当VMM需要物理内存的时候,它就会把这段放到硬盘上去。
  • PDATA 可调页数据段
  • ICODE 仅用于的初始化段 这种段里的代码仅仅用来进行VxD的初始化。当初始化完成后,VMM就把这段从内存中释放。
  • dbOCODE 仅用于调试的代码数据段 当你要调试VxD程序时,就要用到这种段里的代码和数据,例如,它包含要调试的消息的处理代码。
  • SCODE 静态代码和数据段 这种段时刻存在于内存中,即使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'  RESideNT
  _SDATA  CLASS 'SCODE'  RESIDENT
  _DBOSTART  CLASS 'DBOCODE'  PRELOAD NONDISCARDABLE CONFORmiNG
  _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_DEbug_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
告诉 编译器我们要使用包括 cpu特权指令的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,这两个参数就是用来填写这些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来连接 object文件。不同的地方是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/10752019/viewspace-980587/,如需转载,请注明出处,否则将追究法律责任。

转载于:http://blog.itpub.net/10752019/viewspace-980587/

Win32 programs run in protected mode which is available since 80286. But 80286 is now history. So we only have to concern ourselves with 80386 and its descendants. Windows runs each Win32 program in separated virtual space. That means each Win32 program will have its own 4 GB address space. However, this doesn't mean every win32 program has 4GB of physical memory, only that the program can address any address in that range. Windows will do anything necessary to make the memory the program references valid. Of course, the program must adhere to the rules set by Windows, else it will cause the dreaded General Protection Fault. Each program is alone in its address space. This is in contrast to the situation in Win16. All Win16 programs can *see* each other. Not so under Win32. This feature helps reduce the chance of one program writing over other program's code/data. Memory model is also drastically different from the old days of the 16-bit world. Under Win32, we need not be concerned with memory model or segments anymore! There's only one memory model: Flat memory model. There's no more 64K segments. The memory is a large continuous space of 4 GB. That also means you don't have to play with segment registers. You can use any segment register to address any point in the memory space. That's a GREAT help to programmers. This is what makes Win32 assembly programming as easy as C. When you program under Win32, you must know some important rules. One such rule is that, Windows uses esi, edi, ebp and ebx internally and it doesn't expect the values in those registers to change. So remember this rule first: if you use any of those four registers in your callback function, don't ever forget to restore them before returning control to Windows. A callback function is your own function which is called by Windows. The obvious example is the windows procedure. This doesn't mean that you cannot use those four registers, you can. Just be sure to restore them back before passing control back to Windows.
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值