2017年11月3日 星期五

How Windows Installs Devices

本章提供一個裝置安裝過程的高階概述。這個處理過程為當一個新的裝置連接到電腦時,由Windows以及裝置的安裝組件所執行,並由以下的步驟所組成:


Step1:  新的裝置被識別

在新裝置安裝驅動程式之前,裝置所連接的bus或是hub驅動會配置一個硬體識別碼(hardware ID)給裝置。Windows使用硬體識別碼來找尋最適合的裝置驅動包。

一個獨立硬體供應商(IHV)也可以定義一個或多個裝置相容識別碼(compatible IDs)。識別碼的格式類似硬體識別碼; 然而比起硬體識別碼更為通用,並且不需要特定的製造商或型號的訊息。如果作業系統透過硬體識別碼找不到匹配的驅動程式包,那Windows就會使用這些識別碼來選擇一個裝置驅動程式包。IHVs可以在INF檔案裡指定一個或多個compatible IDs。

Windows使用硬體識別碼以及相容識別碼來收尋裝置的驅動程式包。通過將設備的硬件ID和相容的ID與包的INF文件中指定的ID進行比較,為設備找到匹配的驅動程式包。

舉例當一個使用者將一個WLAN adapter插進電腦端的USB hub,會發生以下的事情:
  1. USB hub驅動程式會偵測到這個裝置。根據他從adapter查詢的信息,hub驅動程式會建立裝置的硬體識別碼。例如USB hub驅動對WLAN adapter建立USB\VID_1234&PID_5678&REV_0001  的硬體識別碼。其中VID_1234為供應商(vendor)識別碼,PID_5678為產品或是型號的識別碼,REV_001為裝置的修訂識別碼。
  2. USB hub驅動通知隨插即用管理員(PNP manager)已偵測到一個新的裝置。隨插即用管理員向hub驅動詢問此裝置所有的硬體識別碼。這個hub驅動可以對相同的裝置建立多個硬體識別碼。
  3. 隨插即用管理員通知Windows有一個新的裝置需要被安裝。作為通知的一部分,Windows得到一組硬體識別碼的列表。
  4. Windows開始收尋符合裝置識別碼的驅動包。如果無法透過硬體識別碼來找到匹配的驅動包,則會透過相容識別碼來找尋。


Step2:  裝置驅動被選取

當偵測以及辨識一個新的裝置以後,Windows以及他的裝置安裝組件會按照下面的步驟:

  • Windows為裝置收尋適合的驅動程式包。
    使用硬體識別碼來收尋適合的裝置驅動包。如果硬體識別碼或是相容識別碼符合INF黨內的ID,表示為適合的裝置驅動程式包。 根據作業系統版本的不同,Windows將在各個位置搜索匹配的驅動程式包。
    舉例在W7插入一個WLAN adapter,會發生以下的事情:
    在USB hub建立了WLAN adapter的硬體識別碼列表後,Windows 首先會收尋裝置儲存區(driver store)內符合的裝置驅動程式包。裝置安裝程序會在下面其中一個位置找尋匹配的驅動程式包。如果找到了一個驅動安裝包,則會將它放入驅動儲存區。
    如果是Windows 8以後則會發生以下的事情:
    在USB hub建立了WLAN adapter的硬體識別碼列表後,Windows 首先會收尋驅動儲存區(driver store)內符合的裝置驅動程式包。如果找到了一個驅動安裝包,則會立即安裝這個驅動程式。這個改動讓裝置可以更快的啟動。另外,在另一個程序,Windows會收尋Windows Update以及DevicePath,看有沒有比安裝的驅動更適合的驅動包。如果有發現,則會將它放入驅動儲存區內,然後在稍後安裝他。 


  • Windows從一個或多個驅動程式包中為設備選擇最合適的驅動程式。
    一旦Windows找到一個或多個匹配的裝置驅動包,Windows透過以下的步驟選擇最好的驅動包:
    1) 如果只有找到一個匹配的驅動包,則安裝此包。
    2) 如果找到多個匹配的驅動程式包,則Windows會對每一個驅動程式包,分配一個排名值,Windows會選擇最低排名值的驅動包來安裝。
    3) 如果多個驅動包擁有相同的最低排名值,Windows會使用下面的條件來選擇最佳的驅動程式:  (3.1) 驅動是否有被數位簽屬,始於Windows Vista,Windows總是會選擇簽屬的驅動來取代未簽屬的驅動程式。(3.2)在INF檔內的驅動日期以及版本。

Step3:  該設備的驅動程序已安裝

在Windows對新的裝置選取了最佳的驅動以後,他透過以下的方式保存裝置與驅動的訊息:

  • 電腦可以連接擁有相同型號以及版本的多個裝置。每個設備連接表示為一個設備實體。Windows將每個設備實例表示為唯一的設備節點(devnode)。 devnode包含有關設備的信息,例如設備是否已啟動以及哪些驅動程序已在設備上註冊通知。
  • Windows將每一個裝置驅動表示為一個驅動節點(driver node)。驅動節點包含所有裝置的所有軟體支援,例如任何的服務以及註冊項目。


一旦裝置與驅動實體化,Windows透過下面的步驟安裝驅動:
  1. 根據驅動程式包的INF文件中的指令,Windows執行以下的操作:
    將驅動程式的binary檔和其他關聯的文件複製到INF CopyFiles指令指定的硬碟上的位置。執行裝置實體有關的設定,例如註冊機碼的撰寫。
  2. Windows從驅動程式包的INF文件的Class和ClassGuid中確定設備設置類。為了優化裝置的安裝,對於同一類的裝置設定類將以相同方式設定和配置。
  3. 一旦驅動檔案被複製以後,Windows將控制權轉給PnP管理者。PnP管理者會讀取驅動並且啟動裝置。
  4. PnP管理者為了裝置加載相應的驅動以及可選的過濾式驅動。PnP管理者對尚未加載的驅動調用DriverEntry歷程。然後PnP管理者會對每一個驅動調用AddDevice歷程,始於低過濾式驅動(lower-filter),然後功能驅動(function driver),最後是高過濾式驅動(High-filter)。PnP對裝置配置資源,並且發送IRP_MN_START_DEVICE給裝置驅動程式。
一旦完成以後,表示裝置已經成功安裝並且準備好使用。






2017年11月1日 星期三

Roadmap for Device and Driver Installation

在Windows7以及之後的版本處理裝置驅動的安裝可以依據下面的步驟:
  • 步驟1: 學習Windows裝置與驅動的基本原理。
    你必須了解Windows系列中作業系統的裝置與驅動程式安裝的基本原理。這可以有助於你做出合適的設計決策,並且精簡你的開發程序。
  • 步驟2: 學習相關的驅動包(Driver Packages)以及他們的組成。
    驅動程式包含了所有你必須在Windows底下提供給裝置所安裝的組件。 要安裝裝置或是驅動程式,你必須擁有系統提供以及供應商提供的組件。系統提供的是所有裝置類別的通用安裝軟體。而供應商必須提供一個或多個裝置專用組件在驅動程式包裡面。
  • 步驟3: 學習INF檔案。
    INF檔案包含了用於提供系統安裝驅動包的資訊以及裝置設定,例如裝置的驅動以及任何裝置專用的應用軟體。
  • 步驟4: 為你的裝置驅動程式建立一個驅動程式包。
    你的驅動程式包必須提供一個INF檔案,裝置驅動檔案,以及可以選擇的附加軟體組件。你可以參考Toaster驅動包的例子來決定你的驅動安裝包所需要的組件。
  • 步驟5: 在開發與測試期間測試簽屬(Test-sign)你的驅動包。
    測試簽署是指使用一個測試憑證來簽屬一個預發行版本的驅動程式包,這可以用於在測試電腦上使用。特別的是這允許開發者使用自己簽屬的憑證來簽署驅動包,例如MakeCert工具來產生。這個功能允許開發者在啟動了驅動程式簽名驗證的Windows上安裝並且測試驅動程式包。
  • 步驟6: 發行簽署(Release-sign)你的驅動包並進行發布。
    在測試以及驗證完驅動包以後,你應該進行release-sign驅動包,release-sign識別了驅動包的發行者。雖然這個步驟是可以選擇的,但基於下面的原因還是應該對驅動包release-sign。
    1. 確保驅動包的真實性,完整性以及可靠度。Windows使用數位簽章來驗證發行者的身分並且驗證發布後的程式完整性。
    2. 提供最好的自動安裝體驗。
    3. 在Windows Vista以及之後的版本運行64位元的核心驅動程式。
    4. 播放下一代某些類型的優質內容。

    驅動程式包透過以下兩種方式發行簽名(release-sign)。1. 通過硬件認證工具包(HCK)獲得WHQL的發行簽名(release-sign) 2. 通過Software Publisher Certificate(SPC)來建立發行簽名。
  • 步驟7:  發布你的驅動程式包。最後的步驟就是發佈驅動包。如果你的驅動包符合HCK定義的品質標準,你可以透過微軟更新程式來發布。

2017年8月9日 星期三

Use "DebugView" to show log in c++ dll

void log(char* pszFormat, ...)
{

 static char logbuf[512]; 
 va_list args;

#if _DEBUG
 
 va_start(args, pszFormat);
 vsprintf_s(logbuf, pszFormat, args);
 OutputDebugStringA(logbuf);
 va_end(args);

#endif

}

2017年3月16日 星期四

Bug Check: STATUS_HEAP_CORRUPTION

前言:
同事最近遇到了服務程式莫名其妙停止的問題,通常服務程式(Service)會異常停止,不外乎程式異常或是記憶體操作異常所導致的程式崩潰,但這種timing問題其實是很難靠log去追蹤的(如果幾個月才發生一次,log的累積量其實不難想像的龐大)。好在系統還是有辦法記錄這類的程式崩潰,並且隨後再透過Windbg來分析問題的狀況。

工具:
首先,你會需要知道系統產生dump的位置在哪裡。通常我會直接開軟體(AppCrashView)來看,順便初步看一下問題發生的原因。

再來就是找出dump的所在地。


分析:
這是一個HEAP崩壞問題,發生核心要做HeapFree的時候。

可能是heap manager偵測到heap區塊被多次釋放所導致(heap_failure_block_not_busy)。下面是問題heap block的詳細情形,值得注意的是Entry的位址為block的起始位址也是heap block header的位址,總共是16bytes,然後User就是資料所在的位置,也是動態記憶體配置後,程式設計者實際存取操作的地方。Size就是整個區塊的大小。然後Unused特別解釋一下,當設計者配置一個byte的記憶體大小(char*a = malloc(1))時,系統實際上會配置16bytes倍數的區塊,所以如果你對a[15]操作的時候,是不會有問題的,但如果對a[16]操作可能就會發生heap overrun的問題,而這個a[1]-a[15]的區間就是所謂的Unused區,而heap header也包含在這裡,因為這是不可動的。而這裡的0x10bytes 指的就是header的大小(16bytes)。再來,透過下一個block的位址,我們也可以查詢記憶體的操作有沒有溢位(overflow)。

參考:
最後是參考的網址

2017年2月21日 星期二

Bug Check 0xE4: WORKER_INVALID

前言:
在驅動開發中常會使用到系統執行緒(System thread)來處理一些異步工作,例如一個虛擬串列阜驅動,對上接收應用程式(SCADA)的讀寫控制請求來讀取PLC下的感測器的數據。驅動可能會在短時間內收到大量的命令(non blocking write),然後將命令依序傳給底下的裝置。這時候驅動通常會把這些命令依序放入佇列中,然後交給另一個執行緒來處理這些動作。這個執行緒在核心驅動中有兩種寫法,一種是透過調用PsCreateSystemThread(用於長週期)來建立自己的工作執行緒,另一種則是透過建立WorkItem(用於短周期)來讓系統執行緒調用。

問題:
我在設計驅動架構中有一層就是實作一個TDI client來負責發送網路封包給遠端裝置,這會透過插入WorkItem的方式來處理這些命令。後面試跑了一下,常常會隨機發生BSOD。

分析:

透過下圖可以知道問題出在哪裡,一個配置的workitem已經在佇列中。表示說同一個workitem我插入了兩次。檢查程式碼,發現我配置的Routine,因為處理網路封包的時候與上層的命令是非同步的,所以在共用workitem的情況下,IoQueueWorkItem會被呼叫了兩次。藍屏之所以會隨機發生的原因就是因為有時候Routine處理的速度比插入workitem的時間還要慢。後來我在接收命令的dispatch中透過IoAllocateWorkItem來配置新的workitem,然後在Routine結束前透過IoFreeWorkItem來釋放,問題就可以解決。


補充:
1. 驅動會讓一個WorkItem關聯一個WorkItem回呼例程(routine) ,當系統工作線程處理一個work item時,他會調用關聯的WorkItem例程。

2. 系統工作線程會先移除在佇列中的workitem,然後在調用workitem關聯的回呼例程,所以在回呼例程中釋放workitem是安全的。

3.workitem關聯的routine運行在系統執行緒的上下文(context)。因為系統執行緒的pool是有限資源(推測是紀錄有關插入佇列中的workitem資源),所以執行rountine的時間要短,不然會造成系統死鎖。

參考:
System worker thread

2017年1月11日 星期三

在核心驅動中使用new/delete來建立class物件

筆記: 大部分WDM的sample code使用c語言來撰寫,如果想要在這個基礎上建立一個以C++ OO互動的環境。會希望能透過new/delete來建立這些物件。除了自己使用(ExAllocatePool/ExFreePool)來重載new/delete運算子以外,也可以直接透過include <kcom.h>來達到一樣的目的。詳細可以參考DDK中的avshows範例。