WDM驅動程式入門-Hello WDM

來源:互聯網
上載者:User
***************************************************************/

//一定要的標頭檔,聲明了函數模組和變數:
#include "HelloWDM.h"

/***************************************************************
函數名稱:DriverEntry()
功能描述:WDM程式入口
***************************************************************/
//extern "C"是必須的,表示“用C連結”。如果你的檔案名稱是HelloWDM.c的話,這句可以省略。
extern "C"
NTSTATUS DriverEntry(    IN PDRIVER_OBJECT DriverObject,
                        IN PUNICODE_STRING RegistryPath)
{
    //指定“添加裝置”訊息由函數“HelloWDMAddDevice()”來處理:
    DriverObject->DriverExtension->AddDevice = HelloWDMAddDevice;
    //指定“隨插即用”訊息由函數“HelloWDMPnp()”來處理:
    DriverObject->MajorFunction[IRP_MJ_PNP] = HelloWDMPnp;

    //返回一個NTSTATUS值STATUS_SUCCESS。幾乎所有的驅動程式常式都必須返回一個NTSTATUS值,這些值在NTSTATUS.H DDK標頭檔中有詳細的定義。
    return STATUS_SUCCESS;
}

/***************************************************************
函數名稱:HelloWDMAddDevice()
功能描述:處理“添加裝置”訊息
***************************************************************/
NTSTATUS HelloWDMAddDevice(IN PDRIVER_OBJECT DriverObject,
                           IN PDEVICE_OBJECT PhysicalDeviceObject)
{
    //定義一個NTSTATUS類型的傳回值:
    NTSTATUS status;
    //定義一個功能裝置對象(Functional Device Object):
    PDEVICE_OBJECT fdo;

    //建立我們的功能裝置對象,並儲存到fdo中:
    status = IoCreateDevice(
        DriverObject,                //驅動程式對象
        sizeof(DEVICE_EXTENSION),    //要求的裝置擴充的大小
        NULL,                        //裝置名稱,這裡為NULL
        FILE_DEVICE_UNKNOWN,        //裝置的類型,在標準標頭檔WDM.H或NTDDK.H中列出的FILE_DEVICE_xxx值之一
        0,                            //各種常量用OR組合在一起,指示可刪除介質、唯讀等。
        FALSE,                        //如果一次只有一個線程可以訪問該裝置,為TRUE,否則為FALSE
        &fdo);                        //返回的裝置對象

    //NT_SUCCESS宏用於測試IoCreateDevice核心是否成功完成。不要忘記檢查對核心的所有調用是否成功。NT_ERROR宏不等同於!NT_SUCCESS,最好使用!NT_SUCCESS,因為除了錯誤外,它還截獲警告資訊。
    if( !NT_SUCCESS(status))
        return status;

    //建立一個裝置擴充項物件dx,用於儲存指向fdo的指標:
    PDEVICE_EXTENSION dx = (PDEVICE_EXTENSION)fdo->DeviceExtension;
    dx->fdo = fdo;

    //用IoAttachDeviceToDeviceStack函數把HelloWDM裝置掛接到裝置棧:
    dx->NextStackDevice = IoAttachDeviceToDeviceStack(fdo, PhysicalDeviceObject);

    //設定fdo的flags。有兩個“位”是必須改變的,一個是必須清除DO_DEVICE_INITIALIZING標誌,如果在DriverEntry常式中調用IoCreateDevice(),就不需要清除這個標誌位。還有一個是必須設定DO_BUFFER_IO標誌位:
    fdo->Flags |= DO_BUFFERED_IO | DO_POWER_PAGABLE;
    fdo->Flags &= ~DO_DEVICE_INITIALIZING;

    //傳回值:
    return STATUS_SUCCESS;
}

/***************************************************************
函數名稱:HelloWDMPnp()
功能描述:處理“隨插即用”訊息
***************************************************************/
NTSTATUS HelloWDMPnp(IN PDEVICE_OBJECT fdo,
                        IN PIRP Irp)
{
    //建立一個裝置擴充項物件dx,用於儲存指向fdo的指標:
    PDEVICE_EXTENSION dx=(PDEVICE_EXTENSION)fdo->DeviceExtension;

    //首先要通過函數IoGetCurrentIrpStackLocation()得到當前的IRP,並由此得到Minor Function:
    PIO_STACK_LOCATION IrpStack = IoGetCurrentIrpStackLocation(Irp);
    ULONG MinorFunction = IrpStack->MinorFunction;

    //然後把這個Minor Function傳遞給下一個裝置棧:
    IoSkipCurrentIrpStackLocation(Irp);
    NTSTATUS status = IoCallDriver( dx->NextStackDevice, Irp);

    //處理“隨插即用”次功能代碼:
    //當Minor Function等於IRP_MN_REMOVE_DEVICE時,說明有裝置被拔出或卸下,這時要取消資源分派並刪除裝置:
    if( MinorFunction==IRP_MN_REMOVE_DEVICE)
    {
        //取消裝置介面:
        IoSetDeviceInterfaceState(&dx->ifSymLinkName, FALSE);
        RtlFreeUnicodeString(&dx->ifSymLinkName);
       
        //調用IoDetachDevice()把fdo從裝置棧中脫開:
        if (dx->NextStackDevice)
            IoDetachDevice(dx->NextStackDevice);
        //刪除fdo:
        IoDeleteDevice(fdo);
    }

    //傳回值:
    return status;
}

 

/***************************************************************
程式名稱:Hello World for WDM
檔案名稱:HelloWDM.h
作者:羅聰
日期:2002-8-16
***************************************************************/

//標頭檔,只是聲明一些函數和變數,比較簡單就不多說了,請讀者自行研究:

#ifdef __cplusplus

extern "C"
{
#endif

#include "ntddk.h"

#ifdef __cplusplus
}
#endif

typedef struct _DEVICE_EXTENSION
{
    PDEVICE_OBJECT    fdo;
    PDEVICE_OBJECT    NextStackDevice;
    UNICODE_STRING    ifSymLinkName;

} DEVICE_EXTENSION, *PDEVICE_EXTENSION;

NTSTATUS HelloWDMAddDevice(IN PDRIVER_OBJECT DriverObject,
                           IN PDEVICE_OBJECT PhysicalDeviceObject);

NTSTATUS HelloWDMPnp(IN PDEVICE_OBJECT fdo,
                        IN PIRP Irp);

 

    好了,第一個WDM版的“Hello World”就介紹到這裡,雖然實際上它什麼都沒有做,但是由於它包含了完整的架構,所以對於初學者來說還是很有參考價值的。至於怎麼編譯及安裝,留待下次再講,敬請留意。(不是我想賣關子啊,這些步驟實在是很麻煩的,要另外寫一篇文章才說得清楚哦!) 

WDM驅動程式入門(2)——驅動程式的小秘密
好啦,辛辛苦苦終於寫完了程式,讓我們編譯運行吧!按下Ctrl+F5(嘿嘿,讓我們先假設你習慣用VC來寫程式),我等啊等……疑?怎麼毫無動靜的?再看看Output視窗,哇!有幾百個錯誤啊!!不禁頭大——這是怎麼回事呢?

原來,WDM程式編譯出來的並不是我們常見的.exe,而是.sys檔案,在未經設定編譯環境之前,是不能直接用VC來編譯的(這就是為什麼會有幾百個錯誤了)。這種類型的檔案你可以在WINNT/System32/Drivers裡面找到很多。其實驅動程式也是一種PE檔案,它同樣由DOS MZ header開頭,也有完整的DOS stub和PE header,同樣擁有Import table和Export table——hoho……那跟普通的PE檔案有什麼不一樣呢?偉大的領袖毛主席教育我們,實踐是檢驗真理的唯一標準。那麼就讓我們先來做個小剖析,加深對.sys檔案的認識吧!(如果你對.sys的內部細節沒有興趣的話,可以略過不看。^_^)

首先祭出Delphi裡附帶的tdump.exe程式(別問我為什麼用這個,這隻是純粹的習慣問題)。讓我們鍵入:
C:/WINNT/System32/Drivers>tdump ccport.sys -em -ee
參數-em是列出Import table,-ee是列出Export table。斷行符號之後,螢幕列出一大堆東西:

C:/WINNT/SYSTEM32/DRIVERS>tdump ccport.sys -em -ee
Turbo Dump  Version 5.0.16.12 Copyright ? 1988, 2000 Inprise Corporation
                    Display of File CCPORT.SYS

IMPORT:     NTOSKRNL.EXE={hint:011Fh}.’memcpy’
IMPORT:     NTOSKRNL.EXE={hint:003Dh}.’IoDeleteDevice’
IMPORT:     NTOSKRNL.EXE={hint:0030h}.’IoAttachDeviceToDeviceStack’
IMPORT:     NTOSKRNL.EXE={hint:008Eh}.’KeSetEvent’
IMPORT:     NTOSKRNL.EXE={hint:0068h}.’IofCallDriver’
IMPORT:     NTOSKRNL.EXE={hint:0095h}.’KeWaitForSingleObject’
IMPORT:     NTOSKRNL.EXE={hint:0074h}.’KeInitializeEvent’
IMPORT:     NTOSKRNL.EXE={hint:003Fh}.’IoDetachDevice’
IMPORT:     NTOSKRNL.EXE={hint:00D3h}.’RtlFreeUnicodeString’
IMPORT:     NTOSKRNL.EXE={hint:0077h}.’KeInitializeSpinLock’
IMPORT:     NTOSKRNL.EXE={hint:0129h}.’strcpy’
IMPORT:     NTOSKRNL.EXE={hint:0121h}.’memset’
IMPORT:     NTOSKRNL.EXE={hint:003Ch}.’IoCreateUnprotectedSymbolicLink’
IMPORT:     NTOSKRNL.EXE={hint:0038h}.’IoCreateDevice’
IMPORT:     NTOSKRNL.EXE={hint:00C2h}.’RtlAnsiStringToUnicodeString’
IMPORT:     NTOSKRNL.EXE={hint:0069h}.’IofCompleteRequest’
IMPORT:     NTOSKRNL.EXE={hint:0124h}.’sprintf’
IMPORT:     NTOSKRNL.EXE={hint:003Eh}.’IoDeleteSymbolicLink’
IMPORT:     NTOSKRNL.EXE={hint:0042h}.’IoFreeIrp’
IMPORT:     NTOSKRNL.EXE={hint:004Dh}.’IoInitializeIrp’
IMPORT:     NTOSKRNL.EXE={hint:002Dh}.’IoAllocateIrp’
IMPORT:     NTOSKRNL.EXE={hint:0027h}.’InterlockedExchange’
IMPORT:     NTOSKRNL.EXE={hint:0025h}.’InterlockedCompareExchange’
IMPORT:     NTOSKRNL.EXE={hint:0035h}.’IoCancelIrp’
IMPORT:     NTOSKRNL.EXE={hint:012Ah}.’strlen’
IMPORT:     NTOSKRNL.EXE={hint:0126h}.’strcat’
IMPORT:     NTOSKRNL.EXE={hint:0114h}.’atoi’
IMPORT:     NTOSKRNL.EXE={hint:0128h}.’strcmp’
IMPORT:     NTOSKRNL.EXE={hint:0034h}.’IoBuildSynchronousFsdRequest’
IMPORT:     NTOSKRNL.EXE={hint:00D5h}.’RtlInitAnsiString’
IMPORT:          HAL.DLL={hint:0006h}.’KfAcquireSpinLock’
IMPORT:          HAL.DLL={hint:0009h}.’KfReleaseSpinLock’

EXPORT ord:0001=’Vcomm_DriverControl’
 

我們可以很清楚地看到,它主要調用了NTOSKRNL.EXE和HAL.DLL檔案(實際上你會發現,幾乎所有的WDM驅動程式都會調用NTOSKRNL.EXE檔案,從它的名字你可以看出為什麼了吧?),並且輸出了一個函數“Vcomm_DriverControl”。這表明,其實.sys跟.exe檔案一樣,都是一種PE檔案來的。不同的是,.sys檔案Import的通常是NTOSKRNL.EXE,而.exe檔案Import的通常是KERNEL32.DLL和USER32.DLL。

知道了這些有什麼用呢?實際上,由於.sys通常不調用KERNEL32.DLL和USER32.DLL,所以你是不能在裝置驅動程式裡面調用任何C、C++和Win32函數的,而且也不能用C++關鍵字new和delete等(可以用malloc和free來代替),而必須使用大量的核心功能。為了讀者的方便,下面我列出一些常見的驅動程式可用的核心功能:

Ex…        執行支援
Hal…        硬體抽象層(僅NT/Windows 2000)
Io…        I/O管理器(包括隨插即用函數)
Ke…        核心
Ks…        核心流IRP管理函數
Mm…        記憶體管理器
Ob…        對象管理器
Po…        電源管理
Ps…        進程結構
Rtl…        執行階段程式庫
Se…        安全引用監視
Zw…        其他函數
 

最後讓我們再來看看,寫裝置驅動程式時必須注意的一些問題:

1、核心宏
如果查看DDK標頭檔,會發現有幾個核心功能是以宏的方式實現的。這種宏中有幾個宏的定義是相當糟糕的。例如,我們看到RemoveHeadList的定義如下:
#define RemoveHeadList(ListHead)
        (ListHead)->Flink;
        {RemoveEntryList((ListHead)->Flink)}
 

如果以以下方式調用RemoveHeadList,則將編譯錯誤的代碼:
if(SomethingInList)
        Entry = RemoveHeadList(list);
 

使這個調用安全的唯一方法是使用花括弧:
if(SomethingInList)
    {
        Entry = RemoveHeadList(list);
    }
 

所以我們切勿為了貪圖一時的方便,而使用不太規範的寫法,最好是在所有的if、for和while等語句中使用花括弧。

2、驅動程式函數名稱
跟C/C++的main()函數一樣,裝置驅動程式也有一個必須存在,而且只能以DriverEntry()為名稱的入口函數。然而,除此之外,我們可以使用任何名字來給其他函數命名——只要你自己記得就行了,當然,最好符合某些特定的規範啦,例如匈牙利命名法……

3、安裝時的問題
·在Windows98中驅動程式可執行檔必須是8.3檔案名稱。(別問我為什麼,我也不知道,我只能建議你去問比爾該死)
·如果INF檔案中含有非法節的詳細資料,Windows將不使用這個INF檔案。

呼~~講了那麼多,先去喝口水吧。其實本節羅羅嗦嗦講了一大堆,跟實際的編程卻並沒有太大的關係,不過為了將來養成良好的編程習慣,還是應該遵守一下的,對嗎?好了,下一節我將詳細講解如何編譯、安裝驅動程式,敬請留意。

WDM驅動程式入門(3)——安裝步驟

8 DDK和2000 DDK兩種,它們工作起來是大同小異的,不過有些驅動程式只能在2000 DDK中使用。由於Win98註定是一種即將被淘汰的作業系統了,所以我也不打算介紹如何在98 DDK中進行編譯,以下的所有內容都是針對2000 DDK的。

·準備工作

1、確定你已經安裝了Visual C++

2、安裝2000 DDK

3、安裝2000 DDK成功後,在“開始 ”->“程式 ”裡應該有“Development Kits ”->“Windows 2000 DDK ”的項目。

(注意一定要先安裝好VC,然後才安裝DDK,這個順序決不能顛倒!!)

4、保證DDKROOT環境變數設定為Windows 2000 DDK的基目錄,如果不是的話,請在控制台“系統 ”屬性的“進階 ”標籤環境變數編輯器中設定好這個環境變數。

·編寫必需的檔案

編譯WDM程式的時候,有兩個檔案是必須要有的,它們是:

1、makefile

(這個是什麼啊?你可能會問。)對於比較年輕的程式員來說,有可能沒有見過這個檔案吧。其實在VC這些IDE出現之前,我們都必須使用makefile來確定項目中哪些檔案需要重新編譯,現在的IDE都把這個工作自動做好了。(Well……其實這樣也好。)

我們要做的工作很簡單,就是提供這樣一個檔案,它的內容是:

## 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
正如它所述,不要編輯這個檔案。事實上每個WDM程式所需要的makefile的內容都是一樣的,也就是說,我們只需要簡單地copy一個makefile到新的項目中就可以了。(呵呵,是不是很方便呢?)

2、Sources

TARGETNAME=HelloWDMTARGETTYPE=DRIVERDRIVERTYPE=WDMTARGETPATH=OBJINCLUDES=$(BASEDIR)/inc;/ $(BASEDIR)/inc/ddk;/TARGETLIBS=$(BASEDIR)/lib/*/free/usbd.lib/SOURCES=HelloWDM.cpp/

這個檔案指定了驅動程式目標名是HelloWDM.sys,是一個WDM驅動程式,產生的檔案存放在OBJ目錄中。值得注意的是,“=”前後不能有空格,否則編譯的時候會出錯。

·開始編譯

娃哈哈,前面羅羅嗦嗦講了一大堆,現在終於到重點了。WDM程式的編譯過程比較特殊,它不是在VC裡面按F7來編譯的(儘管你可以通過設定來達到這一目的),而是通過一個DDK工具 + 生產力build來完成。下面我們來講講具體步驟:

1、“Debug”版的產生

首先,我們假設你的原始碼放在D:/HelloWDM裡面。請跟著以下步驟:

“開始”->“程式”->“Development Kits”->“Windows 2000 DDK”->“Checked Build Environment”

螢幕將顯示:(有“斷行符號”的那行是需要讀者你親自打進去的)

New or updated MSVC detected. Updating DDK environment….Setting environment for using Microsoft Visual C++ tools.Starting dirs creation…Completed.D:/NTDDK>cd/HelloWDM (斷行符號)D:/HelloWDM>build (斷行符號)
如果原始碼沒有錯誤的話,產生的HelloWDM.sys將存放在objchk/i386目錄中。

2、“Release”版的產生

請跟著以下步驟:

“開始”->“程式”->“Development Kits”->“Windows 2000 DDK”->“Free Build Environment”

隨後的步驟跟“Debug”版相同,不同的是產生的HelloWDM.sys將存放在objfre/i386目錄中。

·安裝

如果前面的編譯過程沒有錯誤的話,現在我們應該已經得到了一個HelloWDM.sys檔案,假設它是放在D:/HelloWDM/objfre/i386中。

我們還要幹什麼呢?…………對啦,就是安裝它!不然辛辛苦苦編譯出來有什麼用?

安裝WDM驅動程式可以用兩種方法,一種是利用註冊表,還有一種是利用INF檔案。我們一般是採用INF檔案(這是微軟推薦的)。INF檔案可以在 WINNT/INF 目錄中找到很多。為了順利安裝,我在這裡先給出 HelloWDM 所需要的 HelloWDM.INF 檔案:

;; The Win2K DDK documentation contains an excellent INF reference.;--------- Version Section ---------------------------------------------------[Version]Signature="$CHICAGO$"Provider=LC_DeviceDriverVer=8/21/2002,3.0.0.3; If device fits one of the standard classes, use the name and GUID here,; otherwise create your own device class and GUID as this example shows.Class=UnknownClassGUID={ff646f80-8def-11d2-9449-00105a075f6b};--------- SourceDiskNames and SourceDiskFiles Section -----------------------; These sections identify source disks and files for installation. They are; shown here as an example, but commented out.[SourceDisksNames]1 = "HelloWDM",Disk1,,[SourceDisksFiles]HelloWDM.sys = 1,objfre/i386,;--------- ClassInstall/ClassInstall32 Section -------------------------------; Not necessary if using a standard class; 9X Style[ClassInstall]Addreg=Class_AddReg; NT Style[ClassInstall32]Addreg=Class_AddReg[Class_AddReg]HKR,,,,%DeviceClassName%HKR,,Icon,,"-5";--------- DestinationDirs Section -------------------------------------------[DestinationDirs]YouMark_Files_Driver = 10,System32/Drivers;--------- Manufacturer and Models Sections ----------------------------------[Manufacturer]%MfgName%=Mfg0[Mfg0]; PCI hardware Ids use the form; PCI/VEN_aaaa&DEV_bbbb&SUBSYS_cccccccc&REV_dd;改成你自己的ID%DeviceDesc%=YouMark_DDI, PCI/VEN_9999&DEV_9999;---------- DDInstall Sections -----------------------------------------------; --------- Windows 9X -----------------; Experimentation has shown that DDInstall root names greater than 19 characters; cause problems in Windows 98[YouMark_DDI]CopyFiles=YouMark_Files_DriverAddReg=YouMark_9X_AddReg[YouMark_9X_AddReg]HKR,,DevLoader,,*ntkernHKR,,NTMPDriver,,HelloWDM.sysHKR, "Parameters", "BreakOnEntry", 0x00010001, 0; --------- Windows NT -----------------[YouMark_DDI.NT]CopyFiles=YouMark_Files_DriverAddReg=YouMark_NT_AddReg[YouMark_DDI.NT.Services]Addservice = HelloWDM, 0x00000002, YouMark_AddService[YouMark_AddService]DisplayName = %SvcDesc%ServiceType = 1 ; SERVICE_KERNEL_DRIVERStartType = 3 ; SERVICE_DEMAND_STARTErrorControl = 1 ; SERVICE_ERROR_NORMALServiceBinary = %10%/System32/Drivers/HelloWDM.sys[YouMark_NT_AddReg]HKLM, "System/CurrentControlSet/Services/HelloWDM/Parameters",/"BreakOnEntry", 0x00010001, 0; --------- Files (common) -------------[YouMark_Files_Driver]HelloWDM.sys;--------- Strings Section ---------------------------------------------------[Strings]ProviderName="Flying L Co.,Ltd."MfgName="LC Soft"DeviceDesc="Hello World WDM!"DeviceClassName="LC_Device"SvcDesc="???"
注意它可以同時在Win98或者Win2000中使用(系統會通過這個INF檔案裡面的欄位名稱,自動選擇適合當前系統的安裝方法的)。由於INF檔案的各個欄位含義比較複雜,限於篇幅,我在這裡就不詳細講解了,請讀者自行參閱有關的文章或者書籍。

準備好這個 HelloWDM.INF 檔案後,讓我們開啟控制台,雙擊“添加/刪除硬體”,選擇“添加/排除裝置故障”->“添加新裝置”->“否,我想從列表選擇硬體”->“其它裝置”->“從磁碟安裝”,選擇 HelloWDM.INF 所在的路徑,然後安裝。

當安裝完成後,系統就會添加上你寫好的驅動程式了。(可以在“裝置管理員”中查看到)。然後重啟電腦,這個驅動程式就投入使用啦。

 

 

 

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在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.