我們學習程式設計,都是從“Hello World”開始的,驅動程式也不例外,今天我就寫一個驅動版的“Hello World”來熱熱身,目的希望大家能對驅動程式的基本架構有所瞭解。
驅動程式分為2類,一個是Kernel模式驅動,另一個是Windows模式驅動,2種模式本質是相同,但細節不同,本文介紹的是核心模式驅動和驅動程式的安裝、使用。
驅動程式同普通的EXE,DLL一樣,都屬於PE檔案,而且都有一個入口函數。但EXE中,入口函數是main()/WinMain()和Unicode的wmain()/wWinmain(),DLL的入口函數則可有可無,它是DllMain()。驅動程式也有入口函數,而且是必須的,它是DriverEntry(),再次提示,它是必須的,因為I/O管理器會首先調用驅動程式的DriverEntry(),它的作用就像DllMain()--完成一些初始化工作。DriverEntry()一共有2個參數:1PDRIVER_OBJECT DriverObject,指向驅動程式對象的指標,我們操作驅動程式,全靠它,它是由I/O管理器傳遞進來的;2)PUNICODE_STRING RegistryPath,驅動程式的服務主鍵,這個參數的使用並不多,但要注意,在DriverEntry()返回後,它可能會消失,所以如果需要使用,記住先要儲存下來。DriverEntry()的返回一個NTSTATUS值,它是一個ULONG值,具體的定義,請參見DDK中的NTSTATUS.H標頭檔,裡邊有詳細的定義。
既然要寫驅動版的“Hello World”,就需要確定如何來與驅動程式通訊,常用的共用記憶體,共用事件,IOCTL宏,或者直接用ReadFile()或WriteFile()進行讀寫,在本文裡我就採用一種簡單的、但又很常用的IOCTL宏,它依賴的IRP派遣常式是IRP_MJ_DEVICE_CONTROL,Win32程式使用DeviceIoControl()與驅動進行通訊,根據不同的IOCTL宏,輸出不同的調試資訊。為了簡便,我並沒有使用ReadFile()將資訊讀出來,而是直接用DbgPrint()輸出,所以需要使用DbgView查看,其他調試工具也可以。PS:偷懶!
驅動程式與I/O管理器通訊,使用的是IRP,即I/O請求包。IRP分為2部分:1)IRP首部;2)IRP堆棧。IRP首部資訊如下:
IRP首部:
IO_STATUS_BLOCK IoStatus 包含I/O請求的狀態
PVOID AssociatedIrp.SystemBuffer 如果執行緩衝區I/O,這個指標指向系統緩衝區
PMDL MdlAddress 如果直接I/O,這個指標指向使用者緩衝區的儲存空間描述符表
PVOID UserBuffer I/O緩衝區的使用者空間地址
IRP堆棧:
UCHAR MajorFunction 指示IRP_MJ_XXX派遣常式
UCHAR MinorFunction 同上,一般檔案系統和SCSI驅動程式使用它
union Parameters MajorFunction的等位型別
{
struct Read IRP_MJ_READ的參數
ULONG Length
ULONG Key
LARGE_INTEGER ByteOffset
struct Write IRP_MJ_WRITE的參數
ULONG Length
ULONG Key
LARGE_INTEGER ByteOffset
struct DeviceIoControl IRP_MJ_DEVICE_CONTROL參數
ULONG OutputBufferLength
ULONG InputBufferLength
ULONG IoControlCode
PVOID Type3InputBuffer
}
PDEVICE_OBJECT DeviceObject 請求的目標裝置對象的指標
PFILE_OBJECT FileObject 請求的目標檔案對象的指標,如果有的話
操作IRP。對於不同的IRP函數,操作也是不同的:有的只操作IRP首部;有的只操作IRP堆棧;還有操作IRP整體,下面是一些常用的函數:
IRP整體:
名稱 描述 調用者
IoStartPacket 發送IRP到Start I/O常式 Dispatch
IoCompleteRequest 表示所有的處理完成 DpcForIsr
IoStartNextPacket 發送下一個IRP到Start I/O常式 DpcForIsr
IoCallDriver 發送IRP請求 Dispatch
IoAllocateIrp 請求另外的IRP Dispatch
IoFreeIrp 釋放驅動程式分配的IRP I/O Completion
IRP堆棧:
名稱 描述 調用者
IoGetCurrentIrpStackLocation 得到調用者堆棧的指標 Dispatch
IoMarkIrpPending 為進一步的處理標記調用者I/O堆棧 Dispatch
IoGetNextIrpStackLocation 得到下一個驅動程式的I/O堆棧的指標 Dispatch
IoSetNextIrpStackLocation 將I/O堆棧指標壓入堆棧 Dispatc
在驅動程式,IRP派遣常式起著很重要的作用,每個IRP派遣常式,幾乎都有對應的Win32函數,下面是幾個常用的:
IRP派遣常式:
名稱 描述 調用者
IRP_MJ_CREATE 請求一個控制代碼 CreateFile
IRP_MJ_CLEANUP 在關閉控制代碼時取消懸掛的IRP CloseHandle
IRP_MJ_CLOSE 關閉控制代碼 CloseHandle
IRP_MJ_READ 從裝置得到資料 ReadFile
IRP_MJ_WRITE 傳送資料到裝置 WriteFile
IRP_MJ_DEVICE_CONTROL 控制操作(利用IOCTL宏) DeviceIoControl
IRP_MJ_INTERNAL_DEVICE_CONTROL 控制操作(只能被核心調用) N/A
IRP_MJ_QUERY_INFORMATION 得到檔案的長度 GetFileSize
IRP_MJ_SET_INFORMATION 設定檔案的長度 SetFileSize
IRP_MJ_FLUSH_BUFFERS 寫輸出緩衝區或者丟棄輸入緩衝區 FlushFileBuffers FlushConsoleInputBuffer PurgeComm
IRP_MJ_SHUTDOWN 系統關閉 InitiateSystemShutdown
====================================================================================================
下面開始寫我們的驅動版的“Hello World”,程式很簡單,先介紹一下流程:
1,調用IoCreateDevice()建立一個裝置,並返回一個裝置對象。
2,調用IoCreateSynbolicLink()建立一個符號串連,使Win32程式可以使用驅動程式
3,設定IRP_MJ_DEVICE_CONTROL派遣常式HelloWorldDispatch()和卸載常式HelloWorldUnLoad()。
如果Win32程式使用DeviceIoControl(),則執行HelloWorldDispatch()函數
4,調用IoGetCurrentIrpStackLocation()得到當前調用者的IRP指標
5,取得IO控制碼,完成後使用IoCompleteRequest()完成IRP操作
如果使用ControlService()停止驅動程式,則執行HelloWorldUnLoad()函數
4,調用IoDeleteSymbolicLink()刪除符號串連
5,調用IoDeleteDevice()刪除已建立的裝置
驅動入口DriverEntry()
//建立裝置
IoCreateDevice(DriverObject, //驅動程式對象
0, //擴充裝置的大小,由於不需要,所以置0
&DeviceNameString, //裝置名稱
FILE_DEVICE_UNKNOWN, //裝置類型
0, //指示裝置允許的操作
FALSE, //如果為TRUE,表示只能有一個線程使用該裝置,為FALSE,則沒有限制
&lpDeviceObject); //返回的裝置對象
//建立符號串連
IoCreateSymbolicLink(&DeviceLinkString, //存放符號串連的UNICODE_STRING
&DeviceNameString); //裝置名稱
//派遣常式和卸載常式
DriverObject->MajorFunction[IRP_MJ_DEVICE_CONTROL]=HelloWorldDispatch;吧
DriverObject->DriverUnload=HelloWorldUnLoad;
IRP派遣常式HelloWorldDispatch()
IrpStack=IoGetCurrentIrpStackLocation(pIrp); //得到當前調用者的IRP堆棧
//擷取IO控制碼,並執行指定操作,這裡只是DbgPrint()
IoControlCodes=IrpStack->Parameters.DeviceIoControl.IoControlCode;
switch (IoControlCodes) {
......
IoCompleteRequest(pIrp,IO_NO_INCREMENT); //完成IRP操作
卸載常式HelloWorldUnLoad()
//刪除符號串連和裝置
IoDeleteSymbolicLink(&DeviceLinkString);
IoDeleteDevice(DriverObject->DeviceObject);
====================================================================================================完整代碼:
#ifndef __HELLOWORLD_C__
#define __HELLOWORLD_C__
#define DEBUGMSG
#include "HelloWorld.h"
//驅動入口
NTSTATUS DriverEntry (IN PDRIVER_OBJECT DriverObject,IN PUNICODE_STRING RegistryPath)
{
NTSTATUS ntStatus=STATUS_SUCCESS;
PDEVICE_OBJECT lpDeviceObject=NULL; //指向裝置對象的指標
UNICODE_STRING DeviceNameString; //裝置名稱
UNICODE_STRING DeviceLinkString; //符號串連
//調試資訊
#ifdef DEBUGMSG
DbgPrint("Starting DriverEntry()/n");
#endif
RtlInitUnicodeString(&DeviceNameString,NT_DEVICE_NAME); //初始化Unicode字串
//建立裝置
ntStatus=IoCreateDevice(DriverObject,0,&DeviceNameString,FILE_DEVICE_UNKNOWN,
0,FALSE,&lpDeviceObject);
//使用NT_SUCCESS宏檢測函數調用是否成功
if (!NT_SUCCESS(ntStatus))
{
#ifdef DEBUGMSG
DbgPrint("Error IoCreateDevice()/n");
#endif
goto Error;
}
RtlInitUnicodeString(&DeviceLinkString,DOS_DEVICE_NAME);
//建立符號串連
ntStatus=IoCreateSymbolicLink(&DeviceLinkString,&DeviceNameString);
if (!NT_SUCCESS(ntStatus))
{
#ifdef DEBUGMSG
DbgPrint("Error IoCreateSymbolicLink()/n");
#endif
goto Error;
}
//設定IRP派遣常式和卸載常式
DriverObject->MajorFunction[IRP_MJ_CREATE]=//HelloWorldDispatch;
DriverObject->MajorFunction[IRP_MJ_CLOSE]=//HelloWorldDispatch;
DriverObject->MajorFunction[IRP_MJ_DEVICE_CONTROL]=HelloWorldDispatch;
DriverObject->DriverUnload=HelloWorldUnLoad;
return ntStatus;
Error:
#ifdef DEBUGMSG
DbgPrint("Error DriverEntry()/n");
#endif
return ntStatus;
}
NTSTATUS HelloWorldDispatch (IN PDEVICE_OBJECT DeviceObject,IN PIRP pIrp)
{
NTSTATUS ntStatus=STATUS_SUCCESS;
ULONG IoControlCodes=0; //I/O控制碼
PIO_STACK_LOCATION IrpStack=NULL; //IRP堆棧
//設定IRP狀態
pIrp->IoStatus.Status=STATUS_SUCCESS;
pIrp->IoStatus.Information=0;
#ifdef DEBUGMSG
DbgPrint("Starting HelloWorldDispatch()/n");
#endif
IrpStack=IoGetCurrentIrpStackLocation(pIrp); //得到當前調用者的IRP
switch (IrpStack->MajorFunction)
{
case IRP_MJ_CREATE:
#ifdef DEBUGMSG
DbgPrint("IRP_MJ_CREATE/n");
#endif
break;
case IRP_MJ_CLOSE:
#ifdef DEBUGMSG
DbgPrint("IRP_MJ_CLOSE/n");
#endif
break;
case IRP_MJ_DEVICE_CONTROL:
#ifdef DEBUGMSG
DbgPrint("IRP_MJ_DEVICE_CONTROL/n");
#endif
//取得I/O控制碼
IoControlCodes=IrpStack->Parameters.DeviceIoControl.IoControlCode;
switch (IoControlCodes)
{
//啟動
case START_HELLPWORLD:
DbgPrint("Starting /"Hello World/"/n");
break;
//停止
case STOP_HELLPWORLD:
DbgPrint("Stoping /"Hello World/"/n");
break;
default:
pIrp->IoStatus.Status=STATUS_INVALID_PARAMETER;
break;
}
break;
default:break;
}
ntStatus=pIrp->IoStatus.Status;
IoCompleteRequest(pIrp,IO_NO_INCREMENT);
return ntStatus;
}
VOID HelloWorldUnLoad (IN PDRIVER_OBJECT DriverObject)
{
UNICODE_STRING DeviceLinkString;
PDEVICE_OBJECT DeviceObjectTemp1=NULL;
PDEVICE_OBJECT DeviceObjectTemp2=NULL;
#ifdef DEBUGMSG
DbgPrint("Starting HelloWorldUnLoad()/n");
#endif
RtlInitUnicodeString(&DeviceLinkString,DOS_DEVICE_NAME);
IoDeleteSymbolicLink(&DeviceLinkString); //刪除符號串連
if (DriverObject)
{
DeviceObjectTemp1=DriverObject->DeviceObject;
//刪除裝置
while (DeviceObjectTemp1)
{
DeviceObjectTemp2=DeviceObjectTemp1;
DeviceObjectTemp1=DeviceObjectTemp1->NextDevice;
IoDeleteDevice(DeviceObjectTemp2);
}
}
}
#endif
====================================================================================================
驅動程式的編譯需要使用DDK中的build公用程式,它是一個命令列程式,使用不是很方便。VC知識庫有一篇在VC++ 6.0中編譯驅動的文章,有興趣可以去看看。
1,makefile
編譯驅動程式,首先應該準備一個makefile,這個檔案很簡單,只有一句代碼:
#
# DO NOT EDIT THIS FILE!!! Edit ./sources. if you want to add a new source
# file to this component. This file merely indirects to the real make file
# that is shared by all the driver components of the Windows NT DDK
#
!INCLUDE $(NTMAKEENV)/makefile.def
正如描述的那樣,不要修改這個檔案---它是通用的!
2,sources
準備的第二個檔案就是sources,它描述了一些編譯的細節。針對本文的程式,sources檔案的內容是這樣的:
TARGETNAME=HelloWorld //驅動名稱
TARGETPATH=. //編譯後SYS的路徑
TARGETTYPE=DRIVER //類型為驅動程式
SOURCES= HelloWorld.c //只有一個源檔案
有了這2個檔案後,就可以使用build進行編譯了。進入「開始」菜單/程式/Development Kits/Windows 2000 DDK,
分別有3個CMD程式:1)Checked 64 Bit Build Environment,“Debug”的64位版本;2)Checked Build Environment
“Debug”的32位版本;3)Free Build Environment,“Release”的32位版本。不用說,肯定是使用Free Build Environment。
New or updated MSVC detected. Updating DDK environment....
Setting environment for using Microsoft Visual C++ tools.
Starting dirs creation...Completed.
C:/NTDDK>cd/
C:/>cd HelloWorld
C:/HelloWorld>build
BUILD: Object root set to: ==> objfre
BUILD: /i switch ignored
BUILD: Compile and Link for i386
BUILD: Loading c:/NTDDK/build.dat...
BUILD: Computing Include file dependencies:
BUILD: Examining c:/helloworld directory for files to compile.
c:/helloworld - 1 source files (127 lines)
BUILD: Saving c:/NTDDK/build.dat...
BUILD: Compiling c:/helloworld directory
Compiling - helloworld.c for i386
BUILD: Linking c:/helloworld directory
Linking Executable - i386/helloworld.sys for i386
BUILD: Done
1 file compiled
1 executable built
C:/HelloWorld>
現在C:/HelloWorld/i386目錄下,就有了HelloWorld.sys。
====================================================================================================
驅動程式的安裝如同安裝服務一樣,唯一不同的是,建立服務時,類型是核心驅動,其他跟操作服務沒什麼區別。
安裝驅動程式流程:
1,調用OpenSCManager()開啟服務控制管理員
2,調用CreateService()建立一個服務,服務類型為核心驅動
3,調用OpenService()取得服務控制代碼
啟動服務
4,調用StartService()啟動服務
停止服務
4,調用ControlService()停止服務
刪除服務
4,調用DeleteService()刪除服務
5,調用CloseServiceHandle()關閉服務控制代碼
操作驅動程式流程:
1,調用CreateFile()取得裝置控制代碼
2,調用DeviceIoControl()傳遞I/O控制碼
3,調用CloseHandle()關閉裝置控制代碼
http://www.xfocus.net/tools/200411/882.html
這裡有一個完整的驅動安裝程式,所以我就不寫了,只給出操作驅動程式的代碼
完整代碼:
#define DEBUGMSG
#include <windows.h>
#include <winioctl.h>
#include <stdio.h>
#define DEVICE_HELLO_INDEX 0x860
#define START_HELLPWORLD CTL_CODE(FILE_DEVICE_UNKNOWN,DEVICE_HELLO_INDEX,METHOD_BUFFERED,FILE_ANY_ACCESS)
#define STOP_HELLPWORLD CTL_CODE(FILE_DEVICE_UNKNOWN,DEVICE_HELLO_INDEX+1,METHOD_BUFFERED,FILE_ANY_ACCESS)
#define erron GetLastError()
#define MY_DEVICE_NAME "////.//HelloWorld"
#define MY_DEVICE_START "-start"
#define MY_DEVICE_STOP "-stop"
BOOL DriverControl (TCHAR *Maik);
void Usage (TCHAR *Paramerter);
int main (int argc,TCHAR *argv[])
{
if (argc!=2)
{
Usage(argv[0]);
return 0;
}
if (strcmpi(argv[1],MY_DEVICE_START)==0 || strcmpi(argv[1],MY_DEVICE_STOP)==0)
DriverControl(argv[1]);
else
{
Usage(argv[0]);
return 0;
}
return 0;
}
BOOL DriverControl (TCHAR *Maik)
{
HANDLE hDevice=NULL; //裝置控制代碼
DWORD RetBytes=0;
//獲得裝置控制代碼
hDevice=CreateFile(MY_DEVICE_NAME,GENERIC_READ | GENERIC_WRITE,0,NULL,OPEN_EXISTING,FILE_ATTRIBUTE_NORMAL,NULL);
if (hDevice==INVALID_HANDLE_VALUE)
{
#ifdef DEBUGMSG
printf("CreateFile() GetLastError reports %d/n",erron);
#endif
return FALSE;
}
//啟動
if (strcmpi(Maik,MY_DEVICE_START)==0)
{
//傳遞啟動的I/O控制碼
if (!(DeviceIoControl(hDevice,START_HELLPWORLD,NULL,0,NULL,0,&RetBytes,NULL)))
{
#ifdef DEBUGMSG
printf("DeviceIoControl() GetLastError reports %d/n",erron);
#endif
CloseHandle(hDevice);
return FALSE;
}
}
//停止
if (strcmpi(Maik,MY_DEVICE_STOP)==0)
{
//傳遞停止的I/O控制碼
if (!(DeviceIoControl(hDevice,STOP_HELLPWORLD,NULL,0,NULL,0,&RetBytes,NULL)))
{
#ifdef DEBUGMSG
printf("DeviceIoControl() GetLastError reports %d/n",erron);
#endif
CloseHandle(hDevice);
return FALSE;
}
}
if (hDevice)
CloseHandle(hDevice); //關閉控制代碼
return TRUE;
}
void Usage (TCHAR *Paramerter)
{
fprintf(stderr,"============================================================================/n"
" 驅動版Hello World/n"
"作者:dahubaobao[EST]/n"
"首頁:[url]www.eviloctal.com[/url] or [url]www.ringz.org[/url]/n"
"郵件:[email]dahubaobao@eviloctal.com[/email]/n"
"OICQ:382690/n/n"
"%s -start/t啟動/n"
"%s -stop /t停止/n/n"
"本程式只是用做代碼交流,如有錯誤,還請多多包含!/n"
"============================================================================/n"
,Paramerter,Paramerter);
}
標頭檔HelloWorld.h代碼:
#ifndef __HELLOWORLD_H__
#define __HELLOWORLD_H__
#include <ntddk.h>
#define DEVICE_HELLO_INDEX 0x860
//2個IOCTL宏
#define START_HELLPWORLD CTL_CODE(FILE_DEVICE_UNKNOWN,DEVICE_HELLO_INDEX,METHOD_BUFFERED,FILE_ANY_ACCESS)
#define STOP_HELLPWORLD CTL_CODE(FILE_DEVICE_UNKNOWN,DEVICE_HELLO_INDEX+1,METHOD_BUFFERED,FILE_ANY_ACCESS)
#define NT_DEVICE_NAME L"//Device//HelloWorld" //裝置名稱
#define DOS_DEVICE_NAME L"//DosDevices//HelloWorld" //符號串連
NTSTATUS HelloWorldDispatch (IN PDEVICE_OBJECT DeviceObject,IN PIRP pIrp);
VOID HelloWorldUnLoad (IN PDRIVER_OBJECT DriverObject);
#endif