广东技术师范学院后院社区's Archiver

qqqqqq 发表于 2007-7-25 14:10

vxd技术交流(我菜鸟)

虚拟设备驱动程序初步
由 Ryo 翻译,发表于 [url]http://asm.yeah.net[/url],英文版本来自 [Iczelion's Win32 Assembly Homepage] 在本教程里,我假定读者对诸如虚8086模式,调页,GDT,LDT,IDT之类的INTEL 80x86保护模式的操作比较熟悉。如果你不了解这些,那你要先在 [url]http://developer.intel.com/design/pentium/manuals/[/url]阅读INTEL的文档。
内容:
Windows95是一个运行在最高级特权,第0层级别的多线程操作系统。所有的应用程序都运行在最低级特权,第3层级别上。这样就限制了应用程序对系统的操作。它们不能使用cpu特权指令,不能直接访问I/O端口,等等。你对gdi32,kernal32和user32这三个大的系统组件一定很熟悉。你肯定会认为这样重要的代码段一定是在第0层级别下运行的。但是实际上,它们和其他的应用程序一样,是在第三层级别下运行的。这就是说它们并不比Windows计算器,或者扫雷游戏有更多的权限。系统的控制实权掌握在虚拟级管理器(VMM) 和虚拟设备驱动程序(VxD)手中。
这一切都是由dos引起的。在Window 3.x的时代,在市场上有很多成功的dos软件。Windows 3.x必须同时运行普通的Windows程序和dos程序,否则,它就会失去市场。
这个局面是很难处理的,因为dos程序和Windows程序有本质的不同。dos程序认为它们拥有系统的一切:键盘,cpu,内存,硬盘等等。dos程序不知道怎样和其他程序合作,而Windows程序(从那时候起)是可靠的多任务合作系统。也就是每个Windows程序都必须通过GetMessage或PeekMessage来和其他程序进行交流。
解决办法就是,在一个8086虚拟机上运行所有的dos程序,而在另一个叫做系统虚拟机的虚拟机上运行其他所有的Windows程序。Windows负责把cpu运算时间轮流的分给每个虚拟机。这样,在Windows 3.x里。Windows程序之间用的是合作多任务,而虚拟机之间用的是优先级多任务。
什么是一个虚拟机?一个虚拟机是被软件创建的一个假象。一个虚拟机和在它上面运行的程序交互,就像这个程序是在真正的机器上运行一样。这样,一个程序不知道也不关心自己是否是在虚拟机上运行。只要虚拟机准确的像一个真的机器一样响应程序,我们就可以把它当成一个真正的机器。
你可以把虚拟机这种实机器和软件之间的接口看作一种API。这种不寻常的API由中断,BIOS调用和I/O端口组成。如果Windows能够以某种方法完美的模拟这个API,那么在虚拟机上运行的程序就会表现的和它们在实际器上运行时完全一样。
这就是为什么会出现VMM和VxD的原因。为了协调和监视虚拟机(VMs),Windows需要一个程序来分配任务。这个程序就是虚拟机管理器(VMM)。
虚拟机管理器
VMM是一个32位的保护模式程序。它的主要任务是建立和维护一个支持虚拟机的框架。例如,它要创建,运行和结束一个虚拟机。VMM是众多的系统VxD程序之一,它被放在你的系统目录下的VMM32.VxD文件中。VMM本身是一个VxD程序,但它被当作一个监视其他VxD程序的监视器。让我们来看一下Windows95的启动次序:
1.        加载io.sys。
2.        执行config.sys和autoexec.bat。
3.        调用win.com。
4.        win.com运行VMM32.VxD,VMM32.VxD实际上是个简单的dos的exe文件。
5.        VMM32.VxD用xms驱动程序把VMM加载到扩展内存。
6.        VMM初始化自身及其它的默认VxD。
7.        VMM把机器转入到保护模式并创建系统虚拟机。
8.        最后被加载的虚拟外壳设备在系统虚拟机上通过运行krnl386.exe来启动Windows。
9.        krnl386.exe加载所有的文件,最后是Windows95外壳。
正如你所看到的,VMM是第一个被加载到内存的VxD程序。它创建系统虚拟机并初始化其他的VxD程序。它也为这些VxD程序提供许多服务。
VMM和VxD的操作模式和真正的程序不同。在大多数时候,它们是潜伏的。当应用程序在系统中运行时,这些VxD程序没有被激活。当某些需要它们处理的中断/错误/事件发生时,它们才被唤醒。
VMM是不可重入的。这意味着VxD程序必须使它们的访问和VMM服务同步。在有些情况下调用VMM服务是不安全的,比如VMM正在处理一个硬件中断。在这段时间内,VMM是不允许重进入的。作为一个VxD编写者,你必须对你的所作所为极度的小心。记住,你是在最高特权级别,第0层级别,如果你代码有错的话,谁也管不到。
虚拟设备驱动程序
虚拟设备驱动程序被简称为VxD。x 代表各种设备的名字,如虚拟键盘驱动程序(vkd),虚拟鼠标驱动程序(vmd)等等。VxD程序是硬件成功初始化的途径。记得dos程序认为它们拥有系统的一切,当它们在虚拟机中运行时,Windows需要给它们一个实机器的替身。VxD程序就是这些替身。VxD程序通常虚拟一些硬件设备,所以,例如当一个dos程序认为它在同键盘通讯时,实际是虚拟键盘驱动程序在和dos程序通讯。一个VxD程序通常控制真正的硬件设备并对该设备在各个虚拟机之间的共享进行管理。
尽管如此,并不是说每个VxD程序必须和一个硬件设备相连。虽然VxD程序是用来虚拟硬件设备的,但是我们也可以把VxD程序看作是在第0级别的dll。例如,如果你需要做一些只有在第0级别才能做的工作,你就可以编一个VxD程序来为你完成这个工作。这样,由于此VxD程序并没有虚拟任何设备,你就可以把它仅仅看作是你的程序的扩展。
在我们更深入的讨论VxD和创建我们的VxD程序之前,让我先说一些有关于VxD的事情。
•        VxD程序是Windows 9x特有的,它在Windows NT下不能运行。所以如果你的程序是依靠VxD的,它就不能被移植到Windows NT平台上去。
•        VxD是系统中权力最大的实体。由于它们可以对系统作任何事情,所以它们是极度危险的。一个恶意的/错误的VxD程序可以毁掉整个系统。对于恶意的/错误的VxD程序没有任何的保护措施。
•        通常的,不用VxD也有很多办法能达到你的目的。在采用VxD的解决办法之前一定要三思。如果用其他的可以在第三层级别实施的办法,就使用这个办法。
Windows 95下有两种VxD:
•        静态VxD
•        动态VxD
静态VxD是那些从系统启动就被加载,在系统关闭之前一直存在于内存中的VxD程序。这种VxD可以追溯至Windows 3.x的时代。动态VxD时只有Windows 9x下才有的。动态VxD程序可以在需要的时候被加载/卸载。这些程序大多数都是用来控制设置管理器和输入输出监视器加载的即插即用设备的。你可以在你的win32应用程序里加载或卸载动态VxD程序。
VxD程序之间的通讯
VxD程序,包括VMM,通过以下三种途径在相互之间进行通讯:
•        控制消息
•        服务API
•        回调
控制消息: 当有VMM感兴趣的事件发生时,它就向系统中所有载入的VxD程序发送控制消息。控制消息就像是第三层级别的Windows应用程序的消息。每个VxD程序都有一个接受和处理控制消息的函数,叫做设备控制函数。系统控制消息总共有50多个。控制消息不多的原因是系统中通常加载了很多VxD程序,而每个VxD程序在收到一个控制消息时都要进行处理。如果控制消息太多,就会导致系统停滞。所以控制消息只包括那些与虚拟机有关的重要消息,如:一个虚拟机被创建,被销毁等等。作为对系统控制消息的附加,一个VxD程序可以定义自己的控制消息,这些消息可以用来和那些能响应这些消息的VxD程序通讯。
服务函数: 一个VxD程序,包括VMM在内,通常要导出一系列的被别的VxD程序调用的公共函数,这些函数被称为VxD服务。调用这些服务的机制和在第三层级别运行的的应用程序有很大的不同:每个导出VxD服务的VxD程序必须有一个唯一的ID,你可以从Microsoft得到一个这样的ID。这个ID是一个包含了一个VxD唯一的身份验证的16位的数字,例如:
UNDEFINED_DEVICE_ID    EQU 00000H
VMM_DEVICE_ID          EQU 00001H
DEBUG_DEVICE_ID        EQU 00002H
VPICD_DEVICE_ID        EQU 00003H
VDMAD_DEVICE_ID        EQU 00004H
VTD_DEVICE_ID          EQU 00005H
你可以看到VMM的ID是1,VPICD的ID是3,等等。VMM用这些ID来找到导出所需VxD服务的VxD程序。当一个VxD程序导出VxD服务时,它把所有服务的地址存在一个表里面。所以,你还需要通过服务分支表里面服务的索引来找到你所要的服务。例如,如果你要调用第一个服务,GetVersion服务,你就要指定0(这个索引是从0开始的)。调用VxD服务的实机制包括中断20h,你的代码产生一个中断20h,并带有一个双字的值,这个值包含了设备ID和服务索引。例如,如果你要调用一个VxD程序导出的VxD服务,假设VxD程序设备ID是000DH,服务号码是1,那么代码应该是:
int  20h
dd  000D0001h
跟在中断20H后的双字的高字包含设备ID。低字是在服务列表中的索引。
当20H中断执行时,VMM得到了控制权,并马上检测跟着的双字。然后它提出设备ID用来找到VxD程序,用服务索引来定位在那个VxD程序中的所要求的服务的地址。
你可以看到这个操作时很费时的。VMM必须浪费很多时间来定位VxD程序和所要服务的地址,所以VMM作了个小小的弊 。当中断20H操作成功后,VMM抓取链接。这就是说,VMM用直接的服务调用来替代20H中断和它后面的双字。所以上面的20H中断代码片断就被改变成:
call dword ptr [VxD_Service_Address]
这个把戏是成功的,因为int 20h+dword加一个双字用6个字节,正好和call dword ptr结构相等。所以接下来的服务调用是快速而有效的。这个方法具有直接性,简洁性。在好的一方面,它减轻了VMM和VxD载入器的工作量,因为它们不用定位VxD中所有的服务,那些没有执行过的服务将会保持原样。再不那么好的一方面,一旦一个静态VxD程序导出的服务被调用,那么就不可能把这个静态的VxD程序卸载了。由于VMM把调用锁定到VxD服务的实际地址上,如果提供这个服务的VxD程序从内存中被卸载了,其他VxD程序调用这个服务时就会很快的因为调用无效的内存地址而导致系统崩溃。没有办法来消除抓取的链接。这个问题的结论是动态VxD不适合作为服务提供者。
回调: 回调或者回调函数是在VxD程序中给其他的VxD程序调用的函数,不要把回调函数和VxD服务搞混淆了。回调函数不像服务那样是公共的,它们是私有函数,VxD在特定的情况下把它们的地址送给其他的VxD程序。例如,当一个VxD程序在处理一个硬件中断时,由于VMM是不可重入的,这个VxD程序不能使用VxD服务,否则会引起页面错误(重入VMM)。这个VxD程序可以把它自己的一个回调函数的地址给VMM,这样VMM就可以在能忍受页面错误时调用这个函数。回调函数的想法不是VxD独有的。许多Windows API都在用。最好的例子也许是窗口函数,你把窗口函数的地址填在WINDCLASS或WINDCLASSEX结构里并把它当作函数来调用RegisterClass或者RegisterClassEx。当有这个窗口的消息传来时,Windows就会调用你的窗口函数。另一个例子是窗口接管函数。你的程序把接管函数的地址送给Windows,这样当你感兴趣的事件发生时,Windows就会调用你的接管函数。
上述三种方法是VxD之间通讯的,我们还要讲对V86,保护模式和Win32应用程序的接口。在下一章里,我们要学习VxD对Win32应用程序的接口。
虚拟机管理器

由 Ryo 翻译,发表于 [url]http://asm.yeah.net[/url],英文版本来自 [Iczelion's Win32 Assembly Homepage]

虚拟机管理器(VMM)是Windows 95的实际操作系统,它建立和维护一个管理虚拟机的框架,同时为其他vxd程序提供许多重要的服务。其中三种重要的服务是:
•        内存管理
•        中断处理
•        线程调度
内存管理
VMM使用Intel 80386或更新的处理器的内存调页能力来为系统虚拟机创建一个32位的虚地址空间。它把这个地址空间分为四个不同的部分:
•        V86区 地址从0H到10FFEFH,这个区属于当前执行的虚拟机。
•        应用程序私有区地址从4MB到2GB。这是Win32应用程序运行的空间。每个Win32的进程都有它自己的2GB(要减去4MB)。
•        应用程序共享区地址从2GB到3GB。这个区域是在虚拟机内的所有 应用程序共享的。系统DLL(user32,kernel32,gid32)都驻存在这里。所有的Win16程序也放在这里,因为它们行为都是不规范的的:它们对内存中的其他Win16程序进行读写。只有在这个区域里,Win16程序才可以看到其他所有的Win16程序。内存映射文件和分配给DPMI的内存也被存放在这里。
•        系统共享区地址从3GB到4GB。这里是VMM和VXM存放的地方。
VMM为VxD程序提供三种VxD服务:
•        页面内存服务 这种服务分配/管理页面大小为4KB的内存。这是提供的最低级的服务,其他所有的服务都是建立在页面内存服务上的。
•        堆内存服务 管理小的内存块。这种高级别的内存管理服务建立在页面内存服务的基础上。
•        表服务管理可用来实行链结表的固定大小的内存块。
处理中断
在保护模式下,中断指向中断描述表(IDT)。VMM通过VxD的帮助监视虚拟机的IDT。通常VMM处理IDT内几乎所有的中断入口。它进行第一级的中断处理:保存被中断程序的状态,把控制传送到第二级的中断处理,第二级的中断处理通常由各种VxD程序来进行实际的处理。当第二级中断处理程序完成了它的工作之后,它把控制转交给重分派程序,由重分派程序来恢复被中断程序的状态并从先前被中断的地方继续执行。
上面的描述太过简单。由于被中断的虚拟机的时间片可能已过,重分派也许不会马上执行。VxD程序通过VMM服务如:Set_PM_Int或Hook_V86_Int_Chain来安装中断处理。VxD程序不应该直接改动IDT中的中断入口(除非你很确切的知道将发生的后果)。
线程调度
VMM使用两个调度器组件来在虚拟机之间实现有优先级的多线程处理:
•        主调度器
•        时间片管理器或副调度器
主调度器的任务是选择有最高优先级的线程来执行。这种选择在VMM处理一个中断(如计时器中断)时进行。选择的结果决定了当VMM从中断服务返回时由哪一个线程/虚拟机获得控制权。主调度器工作的结果是确定的,一个线程要么获得控制权,要么没有,只有一个线程可以得到控制权。VMM和其他的VxD可以通过VMM服务来调整线程的执行优先级。例如,当一个硬中断发生时,VMM就会增加中断处理的执行优先级以便让它在尽可能短的时间内有更高的机会被调用。
副调度器通过主调度器提供的服务来给享有最高优先级的线程分配cpu时间。副调度器给每个线程一个时间片。当一个线程执行到它的时间片完结时,副调度器就增加下一个线程的优先级,这样它就会被主调度器选中并执行。
你可以从Walter Oney's Systems Programming for Windows 95和Windows 95 DDK文档里面得到关于这个问题的细节。
虚拟设备驱动程序结构
由 Ryo 翻译,发表于 [url]http://asm.yeah.net[/url],英文版本来自 [Iczelion's Win32 Assembly Homepage]

现在大家对vmm和vxd有了一定的了解,接下来我们来看一看如何编写vxd代码。首先,你必须具备Windows 95/98 Device Driver Development Kit。Window95 ddk只有MSDN 订户才能拿到,但Windows98 ddk却可以免费从Microsoft公司取得。尽管Windows 98 ddk是面向WDM的,但你还是可以用它来开发VxD程序。你可以从 [url]http://www.microsoft.com/hwdev/ddk/install98ddk.htm?[/url]下载Window98 ddk。
你可以下载整个软件包(大约30M),也可以只下载你感兴趣的部分。如果你没有下载整个软件包,那么别忘了下载other.exe
里面的Window95 ddk documentation。Windows98 ddk 包含了6.11d版的MASM。你需要把它升级为最新版。如果你不知道到哪里去下载最新的版本,可以去我的主页上查一查。
Window9x DDK包含了一些Masm32包所不具有的重要库文件。
你可以在这里下载这一章的例子。
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, PCODE和PDATA段。作为一个VxD程序编写者,为你的代码和数据选择合适的段取决于你自己的判断。总的来说,你应该尽可能多的使用PCODE和PDATA因为这样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的名字,产生的办法就是:在这个名字的后面加上
_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的名字,产生的办法就是:在这个名字的后面加上
  
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<text> 定义一个文本宏,例如,-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

页: [1]

Powered by Discuz! Archiver 6.1.0  © 2001-2007 Comsenz Inc.