1. 程式人生 > >Windows驅動開發入門

Windows驅動開發入門

對於初學者,DbgView.exe和SRVINSTW.EXE是非常簡單有用的兩個工具,一定要裝上。前者用於檢視日誌資訊,後者用於載入驅動。

一、驅動程式設計的必要性
    在傳統DOS系統下,每個應用程式都有權利讀寫硬體,讀寫I/O埠,控制系統中斷,然而到了Windows系統中,為了保持良好的系統安全性,對應用程式的許可權作出了限制,因為不適當的硬體讀寫會引發整個系統的崩潰。在Windows系統中,將整個程式設計為分層結構,其中,應用程式位於ring3,驅動程式位於ring0,應用程式不能讀寫底層硬體,對於硬體操作必須藉助於底層驅動程式,所以,只要是與硬體系統打交道的Windows程式,必然會涉及到驅動程式的開發和設計。

二、驅動程式的分類和設計工具
    驅動程式是Windows系統的核心,驅動程式的分類與Windows相關,在Windows 9X下,驅動程式的型別為VXD(虛擬裝置驅動程式),在Windows 2000/XP,驅動程式的型別為WDM(Windows驅動程式設計模型),生成的驅動程式設計檔案為.sys格式。
    在Windows9X下,設計驅動程式的工具稱為VTOOLSD,而在Windows 2000/xp下,設計驅動程式的工具為DriverStudio中的DriverWorks,另外的設計驅動程式的工具還有WinDriver,微軟提供的開發工具為Windows DDK。由於所有的驅動設計工具均以DDK作為基本的類或者參照,加上DDK是一個免費軟體,所以在下面主要以DDK為例進行講解,掌握了DDK工具,其他工具也就變得簡單了。

三、Window DDK軟體的安裝與環境設定
    每個Windows系統都有各自的DDK開發工具,在安裝DDK前,請先根據Windows系統的不同,安裝不同的DDK工具。在安裝DDK之前,請先安裝相應的編譯器如Visual C++6.0,本文以Windows 2000下的DDK為例,說明安裝過程,安裝DDK完成後,選擇選單“開始”->“Development Kit”->“Check Build environment”將自動進行各項環境的設定,當然使用者也可選擇“Free Build environment”,二者的區別在於“check”帶有除錯資訊,“Free”則不帶有除錯資訊,一般情況下,在軟體開發階段,選用“check”,而在釋出階段,選用“Free”。
    選擇上述命令後,將進入編譯環境,該環境為“DOS”介面,進入驅動程式所在的目錄,一般情況下,該目錄包含了以下檔案:
makefile 編譯檔案,一般不作更改
sources 規定了其中的原始檔,驅動程式的型別
原始檔 為.c或者.h檔案
    為了編寫方便,我們可以進入DDK提供的例子下面,將makefile和sources拷貝到我們程式所在的目錄下,將sources進行簡單的修改,編譯時,進入相應的目錄,在該目錄下輸入“Build”,系統編譯完成後,將在objchk\i386目錄下生成相應的.sys檔案。

四、驅動程式的編寫
    尋找一個合適的編譯器如EditPlus,當然也可以用VC,只不過需要手動編譯,第一步,找到驅動程式的入口函式DriverEntry(),相當於Main()或者WinMain(),所有的驅動函式入口均從DriverEntry()開始,下面以埠驅動程式為例,說明驅動的編寫。該檔案位於NTDDK\src\general\portio下。
1.建立裝置
    對於裝置驅動程式,首先要建立該裝置,這段程式碼可以放在DriverEntry中,也可放在AddDevice中。
首先,呼叫IoCreateDevice()建立自己的裝置,該函式用法可以參考DDK或者相關示例。在埠操作中,可以將函式寫為:
status = IoCreateDevice (DriverObject,
                    sizeof (LOCAL_DEVICE_INFO),
                    &7,
                    GPD_TYPE,
                    0,
                    FALSE,
                  &deviceObject);
    其次,呼叫函式IoCreateSymbolicLink()建立兩個裝置之間的連線
    status = IoCreateSymbolicLink( &win32DeviceName, &ntDeviceName );

NTSTATUS
DiskFilterAddDevice(
	IN PDRIVER_OBJECT DriverObject,
	IN PDEVICE_OBJECT PhysicalDeviceObject
	)
{

	NTSTATUS                status;
	IO_STATUS_BLOCK         ioStatus;
	PDEVICE_OBJECT          filterDeviceObject;
	PDEVICE_EXTENSION       deviceExtension;
	PIRP                    irp;
	HANDLE              thread_handle;
	static  UCHAR firstdisk = 0;
	ULONG					diskOrderFalg = 0;
	PAGED_CODE();
	DbgPrint("ntdisk:add device.\n");
	DbgPrint("DriverName = %ws\n", DriverObject->HardwareDatabase->Buffer);
	if (PhysicalDeviceObject->DeviceType == FILE_DEVICE_MASS_STORAGE && PhysicalDeviceObject->Characteristics == 0x181)//U盤
	{
		return STATUS_SUCCESS;
	}
	status = IoCreateDevice(DriverObject,
		DEVICE_EXTENSION_SIZE,
		NULL,
		FILE_DEVICE_DISK,
		0,
		FALSE,
		&filterDeviceObject);

	if (!NT_SUCCESS(status))
	{
		return status;
	}

	filterDeviceObject->Flags |= DO_DIRECT_IO;

	deviceExtension = (PDEVICE_EXTENSION)filterDeviceObject->DeviceExtension;

	RtlZeroMemory(deviceExtension, DEVICE_EXTENSION_SIZE);

	deviceExtension->TargetPhysicalDeviceObject = PhysicalDeviceObject;
	deviceExtension->TargetDeviceObject = IoAttachDeviceToDeviceStack(filterDeviceObject, PhysicalDeviceObject);
	if (deviceExtension->TargetDeviceObject == NULL)
	{
		IoDeleteDevice(filterDeviceObject);
		return STATUS_NO_SUCH_DEVICE;
	}

	deviceExtension->DiskNumber = firstdisk++;

	DbgPrint("ntdisk: Attach to device %x,my device %x,return %x,disknumber %d \n", PhysicalDeviceObject, filterDeviceObject, deviceExtension->TargetDeviceObject, deviceExtension->DiskNumber);
	deviceExtension->DeviceObject = filterDeviceObject;
	KeInitializeEvent(&deviceExtension->PagingPathCountEvent, NotificationEvent, TRUE);

	filterDeviceObject->Flags |= DO_POWER_PAGABLE;

	filterDeviceObject->Flags &= ~DO_DEVICE_INITIALIZING;


	g_pDeviceExten[deviceExtension->DiskNumber] = deviceExtension;

	//向scsi發訊號,開啟開關。
	MakeSynchronusIoctl(deviceExtension->TargetDeviceObject, IOCTL_FROM_NTDISK_RW_SWITCH_ON, NULL, 0, NULL, 0);
	return STATUS_SUCCESS;
}


2.初始化所用的資源
    在驅動程式中,總需要訪問I/O埠、系統中斷、記憶體地址以及DMA,使用這些資源之前,需要獲取資源並且初始化,一種簡單的方法是直接指定,如中斷10,DMA3等,這種方法雖然簡單,但靈活性差,任何硬體資源的改變均需在驅動程式中作出修改;另一種較為科學的方法就是讓驅動程式訪問登錄檔,從登錄檔中訪問硬體資源,然後進行初始化。
在驅動程式中,訪問登錄檔的函式為RtlQueryRegistryValues(),該函式的用法較為複雜,可參考相關資料,建議在驅動程式開始開發時,直接給資源賦值,等驅動程式除錯成功後再加入訪問登錄檔程式碼。
3.註冊驅動程式的各個處理函式
    DriverObject->MajorFunction[IRP_MJ_CREATE]          = GpdDispatch;
    DriverObject->MajorFunction[IRP_MJ_CLOSE]          = GpdDispatch;
    DriverObject->MajorFunction[IRP_MJ_DEVICE_CONTROL] = GpdDispatch;
    DriverObject->DriverUnload                          = GpdUnload;
    DriverObject->MajorFunction[IRP_MJ_PNP]            = GpdDispatchPnp;
    DriverObject->MajorFunction[IRP_MJ_POWER]          = GpdDispatchPower;
    DriverObject->MajorFunction[IRP_MJ_SYSTEM_CONTROL]= SystemControl;
    DriverObject->DriverExtension->AddDevice          = GpdAddDevice;
    處理函式的註冊方法有點類似Windows下應用程式設計的訊息處理函式,註冊完成後,當處理相應的IRP時,自動呼叫相應的函式模組。
NTSTATUS
DriverEntry(
	IN PDRIVER_OBJECT DriverObject,
	IN PUNICODE_STRING RegistryPath
	)
{


	ULONG i;
	HANDLE hThread;
	NTSTATUS ntStatus;

	for (i = 0; i <= IRP_MJ_MAXIMUM_FUNCTION; i++)
	{
		//if (i != IRP_MJ_POWER)
		{
			DriverObject->MajorFunction[i] =
				DiskFilterPassThrough;
		}
	}
	DriverObject->MajorFunction[IRP_MJ_CREATE] = DiskFilterCreate;
	DriverObject->MajorFunction[IRP_MJ_READ] = DiskFilterReadWrite4;
	DriverObject->MajorFunction[IRP_MJ_WRITE] = DiskFilterReadWrite4;
	DriverObject->MajorFunction[IRP_MJ_DEVICE_CONTROL] = DiskFilterDeviceControl;
	DriverObject->MajorFunction[IRP_MJ_SHUTDOWN] = DiskFilterShutdownFlush;

	//DriverObject->MajorFunction[IRP_MJ_FLUSH_BUFFERS] =  DiskFilterShutdownFlush;
	//DriverObject->MajorFunction[IRP_MJ_POWER] =          DiskFilterDispatchPower;
	//DriverObject->MajorFunction[IRP_MJ_PNP]             = DiskFilterDispatchPnp;

	DriverObject->DriverExtension->AddDevice = DiskFilterAddDevice;
	DriverObject->DriverUnload = DiskFilterUnload;

	return(STATUS_SUCCESS);

} // DriverEntry


五、驅動程式與應用程式間的資訊互動
    驅動程式用以訪問底層硬體,應用程式實現人機互動,驅動程式和應用程式之間需要實現相應的資訊互動,一方面,應用程式通過對驅動程式傳送相應的指令,實現硬體控制的動作指令,另一方面,驅動程式將硬體讀寫的狀態、從硬體上獲得的資料傳送給驅動程式,實現應用程式與驅動程式間的互動函式包括以下API函式;相應的API函式能夠激發驅動程式的訊息。
介面API函式          驅動程式的中IRP
CreateFile            IRP_MJ_CREATE
CloseHandle         IRP_MJ_CLOSE
ReadFile              IRP_MJ_READ
WriteFile              IRP_MJ_WRITE
DeviceIoControl    IRP_MJ_DEVICE_CONTROL
    在應用程式中,使用者可以呼叫上述函式操作驅動程式,其中CreateFile( )用於開啟驅動程式,在使用完驅動程式之後,可以用CloseHandle()關閉驅動程式,ReadFile( )用於從驅動程式中讀取資料,WriteFile()用以往驅動程式中寫入資料,在函式中,最重要的是DeviceIoControl(),通過定義各種ITL_CODE來實現應用程式與驅動程式間的通訊函式,並可以傳遞各種引數和資料。

六、驅動程式的安裝
使用SRVINSTW.EXE--->

1.手動安裝方法
    生成的驅動程式為sys字尾,一般放於Windows\System32\Drivers目錄下,如果進行手動安裝,可以將生成好的驅動程式拷貝到該目錄中,然後修改登錄檔,對於登錄檔的修改,可以進入登錄檔修改程式進行修改,也可編寫登錄檔程式進行修改,以下為一註冊驅動程式的登錄檔檔案示例。
REGEDIT4
[HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\Device]
"Type"=dword:00000001
"Start"=dword:00000001
"ErrorControl"=dword:00000001
"DisplayName"="Device"
"Group"="port"
"Tag"=dword:00000001

[HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\Device\Parameters]
"IRQ Line"=dword:00000003
    直接在資源管理器下雙擊reg檔案,在彈出的視窗上選擇“是”將直接修改登錄檔,完成後,重新啟動Windows系統,將呼叫驅動程式。
2.編寫安裝檔案INF
    INF檔案含有安裝一個WDM裝置驅動程式需要的所有必需的資訊,包括賦值的檔案列表,要建立的登錄檔項等,Windows為大多數型別的裝置提供了一個標準的安裝程式。INF檔案是一個文字檔案,由節組成,每一節從括在方括號中的節的名稱開始,後面是節的內容,每一行可以是簡單的一項,或者設定一個一個值。具體的INF檔案編寫可以參考現成的示例。
DDK安裝完成後,其中存在工具GenINF,可以按照該向導進行INF檔案的編寫。
3.利用API函式程式設計實現驅動程式的安裝
    利用API函式實現登錄檔的安裝,其實是利用訪問登錄檔的API函式訪問修改登錄檔,實現驅動程式的安裝。這種方法完全可以嵌入到我們的應用程式中,以下提供了安裝驅動程式的API程式碼。主要的API函式包括RegCreateKeyEx(),RegSetValueEx(),RegQueryValueEx(),RegCloseKey() 。

七、驅動程式的除錯
由於驅動程式的所有資訊不能直接輸出到螢幕上,所以驅動程式的除錯較一般應用程式要難得多,在除錯時,可以利用應用程式中的DeviceIoControl()獲取驅動程式的狀態,也可藉助除錯工具SoftIce,比較方便的工具是SysInternals公司的DebugView,如果驅動程式中帶有除錯語句資訊DbgPrint(),可以直接將該函式提供的資訊顯示到螢幕上。

使用windbg+虛擬機器除錯 || windbg+真機   通過串列埠除錯。