驅動版Hello World

來源:互聯網
上載者:User

我們學習程式設計,都是從“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

 

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.