文件系统驱动编程基础篇之五——注册表与Inf

时间:2022-09-04 13:47:02

本文摘自:http://wenku.baidu.com/view/c83d9c51f01dc281e53af0e0


一、前略

本系列文章为业余编程爱好者而写,仅仅作为初学者的一个借鉴,真正的精华存在于参考资料*中。知识的积累将经历从薄到厚,再从厚到薄的反复过程,为了打下牢固的基础,请读者务必在阅读本文的基础上花费必要的时间完成参考资料。

 

参考资料*:

1.《Programming the Microsoft Windowsdriver model》第一版(当前阶段主要阅读资料,阅读第二章一小节、第二章三小节、第三章五小节、第十二章)

2.《Registry Keysfor Drivers

3.《Windows上获得IP地址的四种方法

4.《DeviceInformation Sets

5.《Using a DeviceInterface

6.《Using DeviceInstallation Functions

7.《From the Lab:Mapping USB devices via LNK files

8.《Getting afile handle of a USB volume from its vid/pid/serial number

9.《Fill Levelfield in DEVICE_NODE structure

10.《Tracing USBDevice artefacts on Windows XP operating system for forensic purpose

11.《INF ModelsSection》关于硬件ID的命名规则部分

12. devids.txt

 

阅读基础:不限。

 

本章目的:了解注册表在驱动编程的重要作用,阅读并学会编写简单的Inf。

 

二、注册表的配置

注册表以树形方式存储配置信息,树节点称为键(key),键可以包含子键(subkey)和称为值(value)的数据项。

 

一)需要关注的几种键(注:硬件键、类键、设备接口类应是所列位置下的子键):

1、硬件键*(设备键):HKLM\SYSTEM\CurrentControlSet\Enum\enumerator\deviceID 值Service指向服务键,值Class,ClassGUID等指向类键

2、类键(软件键、驱动键):HKLM\System\CurrentControlSet\Control\Class 即设备创建类,区分了驱动程序的类别,如GUID_DEVCLASS_1394,GUID_DEVCLASS_CDROM

3、服务键:\REGISTRY\MACHINE\SYSTEM\CurrentControlSet\Services\DriverName 键名取自驱动程序.sys的主名字,DriverEntry的第二个参数指向该键,如Raid卡驱动的fasttx2k.sys为fasttx2k,又如Fastfat文件系统驱动的为\Registry\Machine\System\CurrentControlSet\Services\Fastfat

4、硬件配置文件:HKLM\SYSTEM\CurrentControlSet\HardwareProfiles

5、设备接口类(IoRegisterDeviceInterface注册):HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\DeviceClasses后有详述

6、硬件描述:(1)HKLM\HARDWARE\DESCRIPTION\System 登记bios、videobios版本等,Identifier键表示运行的机型,如AT/AT COMPATIBLE,FUJITSU FMR-

(2)HKLM\HARDWARE\DEVICEMAP\SERIALCOMM (PARALLELPORTS等)

7、文件系统:HKLM\SYSTEM\CurrentControlSet\Control\FileSystem\,兼容模式键Win31FileSystem,代码页恒定FatDisableCodePageInvariance(跟长文件名有关)

8、查询网卡IP地址的一个方法:HKLM\SOFTWARE\Microsoft\Windows NT\CurrentVersion\NetworkCards\9,以本机为例,GLOBAL??视图里的符号连接为\\.\{420FB3CF-4468-4C6B-99E5-EB2FEBAA22D1},即\\.\Device\NTPNP_PCI021(图中ServiceName即服务键,Parameters\Tcpip下包含IP地址等信息。CurrentVersion还包括了很多当前的硬件信息。而通过HKLM\SYSTEM\CurrentControlSet\Enum\PCI\VEN_11AB&DEV_4364&SUBSYS_00BA11AB&REV_14\4&2531c6b0&0&00E4这个网卡硬件键找到的服务键为yukonwxp,仅包含驱动信息,不包含IP地址信息)

9、查询串口的一个方法:HKLM \HARDWARE\DEVICEMAP\SERIALCOMM

 

*(资料1第二章一小节)Enum键下的第一级子键与系统中的各种总线枚举器相对应。\Enum\USB子键中包含了所有以前用过和现在存在的USB设备的描述。在USB42例子中,我将阐述怎样把设备的硬件ID(vendor 0574,product 102A)转换成键名(Vid_0574&Pid_102A),以及如何使有该ID的设备实例被表示为下一层的子键7&2。7&2就是该设备的硬件(或实例)键名。(实际在第十二章“设备标识符”部分详述)

文件系统驱动编程基础篇之五——注册表与Inf

文件系统驱动编程基础篇之五——注册表与Inf

二)第3点中的服务键的写法与其他键有所不同,它以\REGISTRY打头,这是内核模式下根键的规定写法。 

User-mode Handle

Corresponding Object Name

HKEY_LOCAL_MACHINE

\Registry\Machine

HKEY_USERS

\Registry\User

HKEY_CLASSES_ROOT

No kernel-mode equivalent

HKEY_CURRENT_USER

No simple kernel-mode equivalent, but see Registry Run-Time Library Routines

 

三)服务的启动类型,如Start为3表示按需启动,scm在基础篇四已经有所论述了。

启动类型

注释

SERVICE_AUTO_START
0x00000002

A service started automatically by the service control manager(scm) during system startup. For more information, seeAutomatically Starting Services.

SERVICE_BOOT_START
0x00000000

A device driver started by the system loader. This value is valid only for driver services.

SERVICE_DEMAND_START
0x00000003

A service started by the service control manager when a process calls the StartService function. For more information, seeStarting Services on Demand.

SERVICE_DISABLED
0x00000004

A service that cannot be started. Attempts to start the service result in the error code ERROR_SERVICE_DISABLED.

SERVICE_SYSTEM_START
0x00000001

A device driver started by the IoInitSystem function. This value is valid only for driver services.

 

四)Chap6\pnpevent示例驱动为例的具体键值,硬件键的命名问题详看资料1第十二章。

 

三、内核模式下注册表的访问

请先阅读资料1第三章五小节,本小节仅仅补充两个有删节的示例。

一)IoOpenDeviceRegistryKey与ZwSetValueKey的示例:

     PLOCAL_DEVICE_INFO pLDI;

     PIO_STACK_LOCATION pIrpStack;

     NTSTATUS Status;

 

     HANDLE handle;

     UNICODE_STRING ValueName;

     ULONG Value =0x101; // this is the value we're setting the key to.

 

     PAGED_CODE();

 

     pIrp->IoStatus.Information = 0;

     pLDI = (PLOCAL_DEVICE_INFO)pDO->DeviceExtension;   // Get local infostruct

 

     Status = IoOpenDeviceRegistryKey(pLDI->NextLowerDriver/*pdo*/,PLUGPLAY_REGKEY_DEVICE,KEY_READ, &handle);

 

     if (NT_SUCCESS(Status)) {

         RtlInitUnicodeString(&ValueName,L"Value");

         Status = ZwSetValueKey(handle, &ValueName,0,REG_DWORD, &Value,sizeof(ULONG));

         if (NT_SUCCESS(Status)) {

              ZwClose(handle);

         }else {

              KdPrint(("writereg failed")); // handle error.

         }

       }

 

IoOpenDeviceRegistryKey的参数DevInstKeyType指示打开哪个注册表键,Msdn里的说明有些让人困惑。实践上的结果为:PLUGPLAY_REGKEY_DEVICE(打开硬件键的Device Parameters子键)、PLUGPLAY_REGKEY_DRIVER(打开类键)、PLUGPLAY_REGKEY_CURRENT_HWPROFILE(打开配置文件键)。

 

二)RtlQueryRegistryValues与RtlWriteRegistryValue的示例:

NTSTATUS SerialGetConfigDefaults(

                            IN PSERIAL_FIRMWARE_DATA    DriverDefaultsPtr,

                            IN PUNICODE_STRING          RegistryPath// 服务键

                            )

{

     NTSTATUS Status= STATUS_SUCCESS;    // return value

 

     //

     // We use this to query into the registry for defaults

     //

 

     RTL_QUERY_REGISTRY_TABLE paramTable[9];//注册表查询用到8个值,最后一个为0表示结束

 

     PWCHAR  path;

     ULONG   zero           = 0;

     ULONG   DbgDefault     = 0;//SER_DBG_DEFAULT;

     ULONG   DetectDefault  = 0;

     ULONG   notThereDefault = SERIAL_UNINITIALIZED_DEFAULT;

 

     PAGED_CODE();

 

     //

     // Since the registry path parameter is a"counted" UNICODE string, it

     // might not be zero terminated.  For a very short time allocate memory

     // to hold the registry path zero terminated so that we canuse it to

     // delve into the registry.

     //

     // NOTE NOTE!!!! This is not an architected way of breakinginto

     // a driver.  Ithappens to work for this driver because the author

     // likes to do things this way.

     //

 

     path = ExAllocatePool(PagedPool,RegistryPath->Length+sizeof(WCHAR));

 

     if (!path) {

         Status = STATUS_INSUFFICIENT_RESOURCES;

         return (Status);

     }

 

     RtlZeroMemory (DriverDefaultsPtr,sizeof(SERIAL_FIRMWARE_DATA));

     RtlZeroMemory (&paramTable[0],sizeof(paramTable));

     RtlZeroMemory (path,RegistryPath->Length+sizeof(WCHAR));

     RtlMoveMemory (path,RegistryPath->Buffer,RegistryPath->Length);

 

     paramTable[0].Flags         = RTL_QUERY_REGISTRY_DIRECT;

     paramTable[0].Name          = L"BreakOnEntry";

     paramTable[0].EntryContext  = &DriverDefaultsPtr->ShouldBreakOnEntry;// 保存查询结果

     paramTable[0].DefaultType   = REG_DWORD;

     paramTable[0].DefaultData   = &zero;

     paramTable[0].DefaultLength= sizeof(ULONG);

。。。

     paramTable[7].Flags         = RTL_QUERY_REGISTRY_DIRECT;

     paramTable[7].Name          = L"UartRemovalDetect";

     paramTable[7].EntryContext  = &DriverDefaultsPtr->UartRemovalDetect;// 保存查询结果

     paramTable[7].DefaultType   = REG_DWORD;

     paramTable[7].DefaultData   = &DetectDefault;

     paramTable[7].DefaultLength= sizeof(ULONG);

 

     Status = RtlQueryRegistryValues(RTL_REGISTRY_ABSOLUTE |RTL_REGISTRY_OPTIONAL,

         path, //HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\Serial

         &paramTable[0],// 以全0的结尾表结束,这样一次就可以查询多个值了

         NULL,

         NULL);

 

     if (!NT_SUCCESS(Status)) {

         DriverDefaultsPtr->ShouldBreakOnEntry   = 0;

         DriverDefaultsPtr->DebugLevel           = 0;

         DriverDefaultsPtr->UartRemovalDetect    = 0;

     }

 

     //

     // Check to see if there was a forcefifo or an rxfifo size.

     // If there isn't then write out values so that they could

     // be adjusted later.

     //

 

     if (DriverDefaultsPtr->ForceFifoEnableDefault ==notThereDefault){

 

         DriverDefaultsPtr->ForceFifoEnableDefault=SERIAL_FORCE_FIFO_DEFAULT;

         RtlWriteRegistryValue(

              RTL_REGISTRY_ABSOLUTE,

              path,

              L"ForceFifoEnable",

              REG_DWORD,

              &DriverDefaultsPtr->ForceFifoEnableDefault,

              sizeof(ULONG)

              );

     }

。。。

     //

     // We don't need that path anymore.

     //

 

     if (path) {

         ExFreePool(path);

     }

 

     //

     //  Set the defaultsfor other values

     //

     DriverDefaultsPtr->PermitSystemWideShare=FALSE;

 

     return (Status);

}

 

四、设备接口

设备创建类(DeviceSetup Classes)和设备接口类(DeviceInterface Classes)是驱动编程中经常碰到的两类键。设备创建类区分了驱动的类别,它的标准类定义于devguid.h,我们将在下一节详述。Winodow2000以前的驱动需要自定义设备对象的名字,同时提供和该名字关联的符号链接(如C:,COM1)以使应用程序可以访问该设备,使用了设备接口类可以避免这种显式的命名。它在接口类键下创建了设备实例子键(##?…),子键下产生了符号链接名(#{…}。同设备创建类一样,也存在预定义的接口类,如usbiodef.h定义了GUID_DEVINTERFACE_USB_HUB,mountmgr.h定义了MOUNTDEV_MOUNTED_DEVICE_GUID。


文件系统驱动编程基础篇之五——注册表与Inf

文件系统驱动编程基础篇之五——注册表与Inf

 

{2eb07ea0-7e70-11d0-a5d6-28db04c10000}(STATIC_KSCATEGORY_DATATRANSFORM):

SetupDiEnumDeviceInterfaces'devindex = 0, interfacedata.Reserved = 1436720

    Microsoft Kernel Acoustic EchoCanceller|Plug and Play Software Device Enumerator, 2232

SetupDiEnumDeviceInterfaces'devindex = 1, interfacedata.Reserved = 1436224

    Microsoft Kernel GS WavetableSynthesizer|Plug and Play Software Device Enumerator, 2232

SetupDiEnumDeviceInterfaces'devindex = 2, interfacedata.Reserved = 1454616

    Microsoft Kernel DLS Synthesizer|Plug andPlay Software Device Enumerator, 2232

SetupDiEnumDeviceInterfaces'devindex = 3, interfacedata.Reserved = 1454648

Microsoft Kernel DRM Audio Descrambler|Plug and Play Software DeviceEnumerator, 2232

* 键下存放相应内容的值,请自行查看注册表。

 

调用IoRegisterDeviceInterface函数,功能或过滤驱动程序的AddDevice函数可以注册一个或多个设备接口,SymbolicLinkName参数返回了如\??\Root#SYSTEM#0000#{2eb07ea0-7e70-11d0-a5d6-28db04c10000}\{8c07dd50-7a8d-11d2-8f8c-00c04fbf8fef}&dmusic的符号链接串,将来它可以被其他组件用来打开设备句柄。

设备接口在注册后需调用IoSetDeviceInterfaceState函数开放接口,并等到驱动完成IRP_MN_START_DEVICE后,其他组件才可以访问该设备。I/O管理器将查询注册表,如果Linked值为0 且引用计数ReferenceCount值不大于0,说明还未有设备引用该接口,它将创建上述符号链接,否则只是简单的增加引用计数。以后,驱动程序会执行一个功能相反的调用禁止该接口(用FALSE做参数调用IoSetDeviceInterfaceState)。最后,I/O管理器删除符号连接对象,但它保留了注册表项,即这个名字将总与设备的这个实例关联;但符号连接对象与硬件一同到来或消失。注意,该符号链接是创建于该设备的PDO上的,所以PDO的安全描述符将最终控制设备的访问权限,并非应用程序获得该符号链接就一定获准访问该设备。

示例代码Chap6\pnpevent\sys创建了两个设备接口,其中一个是由Generic.sys的InitializeGenericExtension 调用RegisterInterface(pdx,&GUID_GENERIC_POWER)注册了一个电源管理接口,符号链接是\??\ROOT#SAMPLE#0001#{894a7461-a033-11d2-821e-444553540000},另一个就是GUID_INTERFACE_PNPEVENT,符号链接为??\Root#SAMPLE#0000#{6a061783-e697-11d2-81b5-00c04fa330a6}。

在用户模式下,设备接口也有着重要的应用,为此系统提供了SetupDixxx函数——它是设备安装函数集(Device InstallationFunctions)的一部分——简化了设备接口的使用。为了解该部分内容,请先阅读资料1第二章三小节及示例代码Chap2\InterfaceEnum。

 

一)枚举设备接口

根据示例Chap2\InterfaceEnum,我们可以总结出枚举设备接口的一个可行步骤:

 

函数

注释

1

SetupDiGetClassDevs

returns a device information set that contains all devices of a specified class. 返回设备集

2

SetupDiEnumDeviceInterfaces

returns a context structure for a device interface element of a device information set. Each call returns information about one device interface; the function can be called repeatedly to get information about several interfaces exposed by one or more devices. 返回“设备接口数据”结构

3

SetupDiGetDeviceInterfaceDetail

returns details about a particular device interface. 一般分两次调用来获得“设备信息数据”

SetupDiOpenDeviceInterfaceRegKey*

opens the registry subkey that is used by applications and drivers to store information specific to a device interface instance and returns a handle to the key. 设备接口类下的符号链接下的Device Parameters子键

4

SetupDiGetDeviceRegistryProperty

retrieves the specified Plug and Play device property. 设备在硬件键的以及相关的属性(如枚举器等)

5

通过设备信息集完成你感兴趣的工作

注:关键的三步:SetupDiGetClassDevs -> SetupDiEnumDeviceInterfaces -> SetupDiGetDeviceInterfaceDetail

* 设备硬件键下的Device Parameters可以用SetupDiOpenDevRegKey来访问

 

此外可能用到函数还有SetupDiCreateDeviceInfoList、SetupDiDestroyDeviceInfoList、SetupDiCreateDeviceInterface 、SetupDiOpenDeviceInterface等。

示例通过两重循环枚举了系统中设备接口类包含的所有设备,输出结果为:

{2accfe60-c130-11d2-b082-00a0c91efb8b}(StoragePortClassGuid):

    AH4453JX IDE Controller

Win XP Promise FastTrak TX4000/S150 TX Series (tm) Controller

 

如果掌握了设备接口的基础知识,SetupDixxx函数并不显得难以使用,但我们希望大家不要就此满足,对于SetupDixxx涉及到的设备信息集(DeviceInformation Sets),我们有必要了解得更深入一些。

 

 

Msdn对SetupDixxx函数的描述里,多次提及了设备信息集,但是没有明确说明它的具体结构,尽管我们不难猜测到是相当复杂的。从原理上说,设备信息集可看作是设备信息元组成的链表,可以用SetupDiGetClassDevs(...DIGCF_INTERFACEDEVICE...)来获取符合定义条件的设备信息集。每个信息元代表着一个设备,包含了设备实例的句柄(Devnode,PnP管理器为了管理系统里所有的设备,使用它构造了分层的设备树)和关联该设备的设备接口链表。

当调用SetupDiEnumDeviceInterfaces来枚举这个设备信息集包含的设备接口时,如果DeviceInfoData参数为NULL,将会遍历所有的信息元。对于每个信息元,都将发生如下的操作:从本信息元的设备接口链表中筛选出满足InterfaceClassGuid参数的节点组成临时链表B,如果MemberIndex参数小于B的节点总数,则说明满足条件的节点就在B中,从中取出返回即可。否则MemberIndex= MemberIndex – B节点总数,继续遍历下一个信息元。举例来说,MemberIndex = 2(C++一般以0为起点,因此实际上是查找第3个),信息元1的临时链表有2个节点,信息元2的临时链表有3个节点,则我们可以在信息元2找到该节点。

如果DeviceInfoData不为空,则根据它定位到合适的信息元,从中取出满足MemberIndex要求的设备接口。

 

二)枚举设备

既然知道了如何枚举设备接口,枚举设备就是一件轻而易举的事情了。利用先前获得的设备信息集,使用SetupDiEnumDeviceInfo函数来遍历每一个设备。

 

三)本站的《用SetupAPI结合注册表获取USB优盘序列号

因为该文缺少原理上的说明,所以我们将之作为设备接口类的一个例子来学习。mountmgr.h里有如下一段话:

//

//Devices that wish to be mounted should report this GUID in

//IoRegisterDeviceInterface.

//

 

DEFINE_GUID(MOUNTDEV_MOUNTED_DEVICE_GUID,0x53f5630d, 0xb6bf, 0x11d0, 0x94, 0xf2, 0x00, 0xa0, 0xc9, 0x1e, 0xfb, 0x8b);

 

意即需要绑定的设备都应该使用这个GUID来注册设备接口,U盘当然也不例外,因此代码中调用SetupDiGetClassDevs建立了设备信息集后,可以使用这个GUID来枚举设备。枚举循环中,SetupDiGetDeviceInterfaceDetail获取设备路径,构造出该设备的硬件键,用硬件键下的“ParentIdPrefix”值,与最初取得的HKLM\SYSTEM\MountedDevices键下的“\DosDevices\U盘符:”值比较,如果相等,则说明此设备就是想找的U盘。

 

1.  LpUSBKeyData-> \??\STORAGE#RemovableMedia#7&1633246c&0&RM#{53f5630d-b6bf-11d0-94f2-00a0c91efb8b}

2.  截取后的LpUSBKeyData-> 7&1633246c&0

3.设备路径-> \\?\usbstor#disk&ven_kingston&prod_datatraveler_2.0&rev_pmap#5b8504002c22&0#{53f56307-b6bf-11d0-94f2-00a0c91efb8b}

4.截取后的设备路径->usbstor#disk&ven_kingston&prod_datatraveler_2.0&rev_pmap#5b8504002c22&0

5.硬件键-> SYSTEM\CurrentControlSet\Enum\usbstor\disk&ven_kingston&prod_datatraveler_2.0&rev_pmap\5b8504002c22&0

6.硬件键下的ParentIdPrefix-> 7&1633246c&0

7.lpPathTemp-> disk&ven_kingston&prod_datatraveler_2.0&rev_pmap#5b8504002c22&0#{53f56307-b6bf-11d0-94f2-00a0c91efb8b}

8.lpPos1-> 5b8504002c22&0#{53f56307-b6bf-11d0-94f2-00a0c91efb8b}

9.lpPos2-> #{53f56307-b6bf-11d0-94f2-00a0c91efb8b}

10.截取后的lpPathTemp->5b8504002c22&0

11.ID-> 5B8504002C22

 

ID号藏于何处呢,原来包含在设备路径(来自SP_DEVICE_INTERFACE_DETAIL_DATA结构)里。第7步起即为从设备路径中取出ID的字符串处理过程。

这个例子之所以有价值,是因为它用设备路径构造出了硬件键——将设备路径截头去尾,替换掉#,再连上硬件键的固有开始部分。设备路径代表了什么呢,原来是一个符号链接:

 

接下来思考一下能否改进这个程序。大家可以看到,获取了设备路径后,不再使用SetupDixxx,操作从流水线转换到了手工作坊,代码也开始变得复杂起来,放弃的原因可能是SetupDiGetDeviceRegistryProperty无法获取ParentIdPrefix,而在用户模式下似乎也没有其他更好的标志了。有兴趣的读者可以资料6、10为线索,寻找是否存在更有效的方法。

 

五、Inf文件

资料1第十二章已经清晰的描述了Inf文件,笔者仅仅打算叙述一下测试示例驱动时遇到的问题。比较有意思的一个是,用DriverStudio的EzDriverInstaller安装Inf,提示“…Inf找不到 [ClassInstall32] 段…”,这是因为缺少必需的设备创建类,虽然我们可以在Inf里补全该段,但既然示例驱动都使用了这个设备创建类,我们也可以编写一个文本格式的注册表文件setup.reg注册一下,内容为:

Windows RegistryEditor Version 5.00

 

[HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Class\{894A7460-A033-11d2-821E-444553540000}]

@="WDM BookSamples"

"Class"="Sample"

"EnumPropPages32"="samclass.dll"

"Icon"="-5"

 

导入注册表后,再将samclass.dll复制到system32目录下即可。

另外一个问题是使用Inf安装了驱动后,运行测试程序会提示驱动启动失败,原因是示例驱动使用了generic.sys,将它复制到system2\driver下即可。

在前面的章节,我们已经接触了sources、MOF,此次涉及了Inf,虽然语法各异,但对于我们来说,应该明确的是它们的出现是为了完整的表达自身模型的需要。对于资料1未详述的问题,可以查阅Msdn,如需要了解Inf中某节xxx的语法格式,可以输入Inf xxx来查询,又如文件复制、注册表配置的具体路径也可以查阅Msdn获知,再如Inf经常出现的点式名字,如DriverInstall.NT,DriverInstall.NT.Services,DriverInstall.nt.hw等,它们的区别在Msdn中也可以找到。

留给初学者的作业是完全理解显卡的Inf文件,找出显卡支持的分辨率声明位置,并尝试使用DDK的GenInf编写一个简单的Inf。

 

六、结语

注册表的许多操作都以API的形式被固定下来,虽然我们没有必要了解它们的内部实现,但有些特别重要的键仍然值得我们加以关注。Inf为驱动程序的正确安装与工作提供了很多有用的信息。硬件键名的形式,取决于不同类别设备的标识符的定义方式。

现在我们仍然不知道,在文件系统驱动加载之前,能否在内核模式下正常的访问注册表,你能够给出正确的答案来吗?

本篇的参考完成时间为不超过三星期。