Windows核心開發-4-核心程式設計基礎
這裡會構建一個簡單但是完整的驅動程式和一個客戶端,部署核心執行一些平時user下無法執行的操作。
將通過以下內容進行講解:
1 介紹
2 驅動初始化
3 Create和Close操作排程例項
4 DeviceIoControl操作排程例項
5 安裝和測試驅動程式
整個完整原始碼最後面
1 介紹
該驅動將解決Windows API設定執行緒優先順序的不靈活性。
在User模式下,執行緒的優先順序由其程序優先順序類和基於每個執行緒的偏移量組合來確定,偏移量具有有限的級別數。更改程序的優先順序類別可以採用SetPriorityClass函式來實現。
每個優先順序類對應著一個優先順序,這個對應的優先順序也是在程序中建立執行緒時預設的優先順序。可以使用SetThreadPriority函式來修改特定執行緒的優先順序。
基於程序優先順序類和執行緒的優先順序偏移量的可用執行緒優先順序圖 :
Priority Class | -Sat | -2 | -1 | 0(default) | +1 | +2 | +Sat | Comments |
---|---|---|---|---|---|---|---|---|
Idle(Low) | 1 | 2 | 3 | 4 | 5 | 6 | 15 | |
Below Normal | 1 | 4 | 5 | 6 | 7 | 8 | 15 | |
Normal | 1 | 6 | 7 | 8 | 9 | 10 | 15 | |
Above Normal | 1 | 8 | 9 | 10 | 11 | 12 | 15 | |
High | 1 | 11 | 12 | 13 | 14 | 15 | 15 | 只有6個級別可以選,不是7個。 |
Real-time | 16 | 22 | 23 | 24 | 25 | 26 | 31 | 16-31所有級別都可以選 |
SetThreadPriority函式可以接受指定偏移量的值,五個普通級別分別對應的偏移量是從-2到2:THREAD_PRIORITY_LOWEST (-2), THREAD_PRIORITY_BELOW_NORMAL (-1), THREAD_PRIORITY_NORMAL (0), THREAD_PRIORITY_ABOVE_NORMAL (+1), THREAD_PRIORITY_HIGHEST (+2)。另外兩個級別被稱為飽和級別,將優先順序設定為支援的兩個極端:THREAD_PRIORITY_IDLE (-Sat) 和 THREAD_PRIORITY_TIME_CRITICAL (+Sat)。
//修改優先順序的例子
SetPriorityClass(GetCurrentProcess(), ABOVE_NORMAL_PRIORITY_CLASS);
//將程序優先順序類修改為ABOVE NORAML
SetThreadPriority(GetCurrentThread(), THREAD_PRIORITY_ABOVE_NORMAL);
//將執行緒優先順序修改為Above_normal
上面的基於程序優先順序類和執行緒的優先順序偏移量的可用執行緒優先順序圖表示了我們要解決的問題,這裡只有一小部分的執行緒優先順序可以設定,我們這次準備寫的驅動就是為了來繞過這些限制,允許將執行緒的優先順序設定為任意數字並且不用考慮程序優先順序類。
小結:設計驅動的目的就是在User態下設計的執行緒優先順序不太行,東西比較少,而且麻煩,採用核心來處理後直接將執行緒的優先順序設定為任何數字而且不用考慮進行的優先順序類。
2 Driver Initialization初始化驅動
建立WDM專案,刪除inf檔案,再建立C++原始檔,然後新增WDK標頭檔案建立一個空的DriverEntry()函式。
#include<ntddk.h>
extern"C"
NTSTATUS DriverEntry(_In_ PDRIVER_OBJECT DriverObject, _In_ PUNICODE_STRING RegistryPath)
{
return STATUS_SUCCESS;
}
大部分一般的驅動需要實現以下內容:
1 設定Unload解除安裝函式。
2 設定驅動程式支援的排程例項。
3 建立裝置物件。
4 建立對裝置物件的符號連結。
實現完以上的內容後,一個驅動程式就可以進行互動了。
2.1 Unload
第一步:建立一個Unload例項函式,並且將DriverEntry中的驅動物件指標指向Unload:
#include<ntddk.h>
void PriorityBoosterUnload(_In_ PDRIVER_OBJECT DriverObject);
extern"C"
NTSTATUS DriverEntry(_In_ PDRIVER_OBJECT DriverObject, _In_ PUNICODE_STRING RegistryPath)
{
DriverObject->DriverUnload = PriorityBoosterUnload;
return STATUS_SUCCESS;
}
對於Unload函式,我們需要根據DriverEntry函式來具體實現裡面的邏輯結構,因為該函式的主要目的還是釋放資源。
2.2 Dispatch routines排程例項
其實也可以理解為互動。其實所有的驅動都應該支援IRP_MJ_CREATE和IRP_MJ_CLOSE操作,不然是無法開啟和關閉驅動物件的。
在DriverEntry中新增以下程式碼:
#include<ntddk.h>
void PriorityBoosterUnload(_In_ PDRIVER_OBJECT DriverObject);
NTSTATUS PriorityBoosterCreateClose(_In_ PDEVICE_OBJECT DeviceObject, _In_ PIRP Irp);//新增
extern"C"
NTSTATUS DriverEntry(_In_ PDRIVER_OBJECT DriverObject, _In_ PUNICODE_STRING RegistryPath)
{
DriverObject->DriverUnload = PriorityBoosterUnload;
DriverObject->MajorFunction[IRP_MJ_CREATE] = PriorityBoosterCreateClose;//新增
DriverObject->MajorFunction[IRP_MJ_CLOSE] = PriorityBoosterCreateClose;//新增
return STATUS_SUCCESS;
}
這裡我們注意到Create和CLOSE都指向的是同一個例項函式,這是因為它們兩個執行的程式碼邏輯差不多,如果有比較複雜的情況,可以將其分開寫。其實所有的驅動物件的majorfunction函式指標陣列都有一個相同的原型(因為他們都是函式指標陣列的一部分),所以這裡的新增的函式申明就是major function對於的函式原型:
NTSTATUS PriorityBoosterCreateClose(_In_ PDEVICE_OBJECT DeviceObject, _In_ PIRP Irp);
//當然函式的名字是可以改的。
該函式必須返回一個NTSTATUS型別變數,然後接受一個裝置物件的指標,和一個指向I/O Request Packet(IRP)的指標,對於所有型別的請求,IRP是儲存請求資訊的主要物件。
2.3 將資訊傳給驅動程式
光有Create和Close肯定不夠的,因為我們的需求裡面我們還需要告訴驅動給那一個執行緒設定成為什麼優先順序。用User Client的角度來看有三個基本API可以用WriteFile,ReadFile,DeviceIoControl,Read是一個讀,不能寫資料進去,所以從驅動程式來看就可以不用這個函數了,因為我們要把資訊傳給驅動。對於Write和DeviceIoControl的選擇這個就全看大家喜歡了。一般來說如果真的是一個寫的操作就用Write,但是對於其它任何東西DeviceIoControl肯定是首選,因為它是將資料傳入和傳出驅動程式的通用機制。
更改執行緒的優先順序並不是純粹的Write,所以這裡我們採用DeviceIoControl:
BOOL WINAPI DeviceIoControl(
_In_ HANDLE hDevice,
_In_ DWORD dwIoControlCode,
_In_reads_bytes_opt_(nInBufferSize) LPVOID lpInBuffer,
_In_ DWORD nInBufferSize,
_Out_writes_bytes_to_opt_(nOutBufferSize,*lpBytesReturned) LPVOID lpOutBuffer,
_In_ DWORD nOutBufferSize,
_Out_opt_ LPDWORD lpBytesReturned,
_Inout_opt_ LPOVERLAPPED lpOverlapped);
對DeviceIoControl來說有三個東西非常重要:
1:可控制的程式碼
2:一個輸入緩衝區
3:一個輸出緩衝區
DeviceIoControl:比較靈活,可以支援多種控制程式碼。
在驅動端,DeviceIoControl對應IRP_MJ_DEVICE_CONTROL的MajorFunction函式指標陣列的內容。所以新增已下程式碼到DriverEntry中:
DriverObject->MajorFunction[IRP_MJ_DEVICE_CONTROL] = PriorityBoosterDeviceControl;
2.4 客戶端和驅動的通訊協議
為了讓Client和kernel可以交換資料,我們得實現剛剛申明的PriorityBoosterDeviceControl函式,我們需要控制的程式碼邏輯以及輸出輸入的緩衝區,緩衝區應該包含我們需要的執行緒ID和要設定的優先順序,這些資訊由客戶端提供驅動對其採取行動。要兩個互動就意味著Client/kernel需要有一個單獨的檔案來傳輸資訊。
所以這裡我們新建一個PriorityBoosterCommon.h 標頭檔案來作為資訊傳輸的介質,該檔案也會被Client使用。
該檔案我們需要兩個資料一個是需要的結構體,另一個是更改執行緒優先順序的控制程式碼。
先看看結構體:
struct ThreadData {
ULONG ThreadId;
int Priority;
};
需要執行緒的唯一ID和目標優先順序,TID(Thread ID)是一個32位無符號整數,用ULONG不用DWORD是因為ntddk裡面沒有DWORK只有ULONG,而ULONG比較通用。
優先順序應該是1-31之間的數字,所以採用一個簡單的int就好了。
接下來還需要一個控制程式碼,該控制程式碼必須採用CTL_CODE巨集來定義,該巨集接受構成最終控制程式碼的四個引數,CTL_CODE巨集的定義:
#define CTL_CODE( DeviceType, Function, Method, Access ) ( \
((DeviceType) << 16) | ((Access) << 14) | ((Function) << 2) | (Method))
對這段巨集定義的講解:
引數 | 作用 |
---|---|
DeviceType | 標識一種裝置,可以是WDK中定義的FILE_DEVICE_XXX裡面常量之一,但是這個主要用於硬體,和我們這個軟體的驅動來說這個值不重要,但是微軟指定第三方的驅動程式的這個值應該以0x8000開頭 |
Function | 一個升序數字用來表示特定操作,一般情況下,這個數字在同一驅動的不同控制程式碼下必須不同,同樣,任何數字都可以,但是官方文件規定 第三方驅動程式該值應該以0x800開頭 |
Method | 最重要的部分,表示客戶端提供的輸入和輸出緩衝區如何傳遞給驅動程式,對於我們的驅動程式這裡採用最簡單的值METHOD_NEITHER |
Access | 指示此操作是針對驅動程式 (FILE_WRITE_ACCESS)、來自驅動程式 (FILE_READ_ACCESS) 還是雙向 (FILE_ANY_ACCESS)。 |
這裡我們採用下面這種巨集定義:
#define PRIORITY_BOOSTER_DEVICE 0x8000
#define IOCTL_PRIORITY_BOOSTER_SET_PRIORITY CTL_CODE(PRIORITY_BOOSTER_DEVICE, \
0x800, METHOD_NEITHER, FILE_ANY_ACCESS)
2.3 建立裝置物件
在DriverEntry中還需要裝置物件,以便我們可以開啟控制代碼來到達驅動程式。
典型的軟體驅動程式只需要一個裝置物件,並帶有指向它的符號連結(可以理解為檔案的快捷方式)來方便User Client獲取它的控制代碼。
建立一個裝置物件需要使用IoCreateDevice API:
NTSTATUS IoCreateDevice(
_In_ PDRIVER_OBJECT DriverObject,
_In_ ULONG DeviceExtensionSize,
_In_opt_ PUNICODE_STRING DeviceName,
_In_ DEVICE_TYPE DeviceType,
_In_ ULONG DeviceCharacteristics,
_In_ BOOLEAN Exclusive,
_Outptr_ PDEVICE_OBJECT *DeviceObject);
IoCreateDevice API引數解析
引數 | 說明 |
---|---|
DriverObject | 裝置物件所屬的驅動程式物件,一般只用來傳遞給DriverEntry函式的驅動程式物件 |
DeviceExtensionSize | 除了sizeof(DEVICE-OBJECT)還有額外位元組,用於將某些資料結構與裝置相關聯。 對於僅建立單個裝置物件的軟體驅動程式而言,它不太有用,因為裝置所需的狀態可以簡單地由全域性變數管理。 |
DeviceName | 內部裝置名稱,通常在裝置物件管理器目錄下建立 |
DeviceType | 與某種型別的基於硬體的驅動程式相關。對於軟體驅動採用FILE_DEVICE_UNKNOWN值 |
DeviceCharacteristics | 一組標誌和某些特定驅動程式相關(軟體驅動程式很少用它),如果軟體驅動程式支援真正的名稱空間則該值指定0或者FILE_DEVICE_SECURE_OPEN |
Exclusive | 是否允許多個檔案物件開啟同一裝置。FALSE同意,TRUE不同意 |
DeviceObject | 返回的裝置物件指標,如果成功函式會從Non paged Pool非分頁記憶體池分配結構並將結果指標儲存在引用引數中 |
在建立裝置物件前,先要建立一個UNICODE_STRING字串來儲存該內部裝置名字:
UNICODE_STRING devName = RTL_CONSTANT_STRING(L"\\Device\\PriorityBooster");
// 或者RtlInitUnicodeString(&devName, L"\\Device\\ThreadBoost");
裝置物件的名字可以是任意的,但是必須在Device目錄下。有兩種使用常量字串來初始化UNICODE_STRING的辦法,第一種是使用RtlInitUnicodeString 這個很好用,但是RtlInitUnicodeString必須計算字串中的字元數才能很好的初始化。
還有一種更快的辦法是採用RTL_CONSTANT_STRING巨集,它在編譯時靜態計算字串的長度,這意味著它只能與常量字串一起工作。
然後在DriverEntry中寫入我們的程式碼:
PDEVICE_OBJECT DeviceObject;
NTSTATUS status = IoCreateDevice(
DriverObject // our driver object,
0 // no need for extra bytes,
&devName // the device name,
FILE_DEVICE_UNKNOWN // device type,
0 // characteristics flags,
FALSE // not exclusive,
&DeviceObject // the resulting pointer
);
if (!NT_SUCCESS(status)) {
KdPrint(("Failed to create device object (0x%08X)\n", status));
return status;
}
2.4 建立符號連結
現在我們有一個指向我們的裝置物件的指標,下一步需要提供符號連結來使User態的呼叫者可以訪問該裝置物件,以下幾行程式碼建立一個符號連結並將其連線到我們的裝置物件:
UNICODE_STRING symLink = RTL_CONSTANT_STRING(L"\\??\\PriorityBooster");
status = IoCreateSymbolicLink(&symLink, &devName);
if (!NT_SUCCESS(status)) {
KdPrint(("Failed to create symbolic link (0x%08X)\n", status));
IoDeleteDevice(DeviceObject);
return status;
}
同樣名字隨你取,但是目錄必須是 \??目錄下
IoCreateSymbolicLink 通過接受符號連結和連結的目標來完成工作。但是務必注意,如果建立失敗,需要呼叫IoDeleteDevice來銷燬建立的內容。一般情況下,如果DriverEntry返回的是失敗狀態,則不會呼叫Unload函式,如果我們有很多初始化要做,那麼記得如果失敗記得銷燬掉。
一旦我們前面的都成功了,那麼一定不要忘記在Unload函式裡面撤銷在DriverEntry中所做的任何事情。
2.5 unload撤銷
我們前面建立了裝置物件,已經符號連結,是先有的裝置物件後有的符號連結。所以我們銷燬的時候得反著來,先銷燬符號連結,再銷燬裝置物件。這裡有點像C++的析構函數了。
void PriorityBoosterUnload(_In_ PDRIVER_OBJECT DriverObject) {
UNICODE_STRING symLink = RTL_CONSTANT_STRING(L"\\??\\PriorityBooster");
// delete symbolic link
IoDeleteSymbolicLink(&symLink);
// delete device object
IoDeleteDevice(DriverObject->DeviceObject);
}
2.6 初始化驅動小結:
對於驅動一般有幾個板塊,1 開始 2 中間互動 3 結束
中間互動呢一般是User和Kernel通過I/O裝置物件來進行互動,常常是採取開闢裝置物件,然後User採用符號連結來使用該物件裝置物件可以理解為一個用來互動的東西。然後結束需要刪除掉中間用了的東西。
3 Client Code 編寫客戶端程式碼
再在該解決方案下新增一個空專案,然後新建一個.cpp檔案來編寫客戶端程式碼:
新增以下標頭檔案:
#include"../PriorityBooster/PriorityBoosterCommon.h"
#include<Windows.h>
#include<stdio.h>
#include<iostream>
//PriorityBoosterCommon.h是我們用來給Client和Driver進行互動的檔案。
修改main函式來接受命令列引數,我們需要接受執行緒id,和優先順序的value值。
int main(int argc, const char* argv[])
{
if (argc < 3)
{
std::cout << "Usage: Booster <threadid> <priority>" << endl;
return 0;
}
}
然後需要開啟裝置的控制代碼來獲取裝置傳輸的資料,CreateFile 的第一個引數“filename”應該是字首為“\\.\”的符號連結:
HANDLE hDevice = CreateFile(L"\\\\.\\PriorityBooster", GENERIC_WRITE,
FILE_SHARE_WRITE, nullptr, OPEN_EXISTING, 0, nullptr);
if (hDevice == INVALID_HANDLE_VALUE)
return Error("Failed to open device");
//再定義了一個error函式來列印錯誤的文字。
int Error(const char* message) {
printf("%s (error=%d)\n", message, GetLastError());
return 1;
}
CreateFile會通過IRP_MJ_CREATE排程例項來寫入驅動,如果驅動沒有載入就會報錯說沒有符號連結。就會收到錯誤2(File not found)。
現在通過符號連結拿到了裝置的控制代碼,現在開始可以呼叫DeviceIoControl和裝置物件進行互動了。在互動前先定義結構體和給給結構體賦值。
ThreadData TempData;
TempData.ThreadId = atoi(argv[1]);//命令列的第一個引數,atoi字串int
TempData.Priority = atoi(argv[2]);//命令列的第一個引數
呼叫DeviceIoControl傳遞資料,然後關閉Device控制代碼:
DWORD returned;
BOOL success = DeviceIoControl(
hDevice,//裝置控制代碼
IOCTL_PRIORITY_BOOSTER_SET_PRIORITY,//控制程式碼
&TempData, sizeof(TempData),//輸入buffer和length
nullptr, 0, //輸出buffer和length
&returned, nullptr
);
CloseHandle(hDevice);
DeviceControl通過IRP_MJ_DEVICE_CONTROL majorfunction的例項函式來和driver互動。
這樣客戶端的程式碼就搞定了。
4 開啟和關閉的排程函式例項
現在我們需要新增的就是驅動程式碼裡面的排程函式,因為之前我們只是申明瞭而已,並沒有實現這個函式。
4.1 Create/Close排程函式
Create和Close的排程函式是最好實現的,只需要返回成功就好。
_Use_decl_annotations_//函式的註釋和引數的註釋一樣,可有可無
NTSTATUS PriorityBoosterCreateClose(PDEVICE_OBJECT DeviceObject, PIRP Irp) {
UNREFERENCED_PARAMETER(DeviceObject);
Irp->IoStatus.Status = STATUS_SUCCESS;
Irp->IoStatus.Information = 0;
IoCompleteRequest(Irp, IO_NO_INCREMENT);
return STATUS_SUCCESS;
}
每個排程例項函式都接受裝置物件和一個I/O物件IRP,裝置物件不用處理,因為我們這裡只有一個裝置物件只能是我們在DriverEntry中建立的那個,IRP非常重要,在下下章講。
IRP是一個表示請求(互動)的半文件結構,通常來自執行中的管理器:I/O管理器、即插即用管理器或電源管理器。對於一個簡單的軟體驅動程式很可能就是一個I/O管理器,IRP如何建立不用考慮,driver的目的都是用來處理IRP,請求(互動)的細節需要完成它才行。
驅動的各種請求都是包含在IRP中,通過檢視IRP的成員可以找到請求的型別和詳細資料。
需要注意的是IRP不會單獨執行,它伴隨著一個或多個IO_STACK_LOCATION型別的結構。我們這個簡單驅動就只有一個IO_STACK_LOCATION。
簡單來說就是我們需要的一些資訊在基礎的IRP結構裡,還有一些在我們裝置堆疊的IO_STACK_LOCATION中。
在建立和關閉的情況下,我們不需要檢視任何成員。 我們只需要在其 IoStatus 成員(型別為 IO_STATUS_BLOCK)中設定 IRP 的狀態,該成員有兩個成員:
Status | 表明此請求將完成的狀態 |
Information | 一個多型成員,在不同的請求中意味著不同的東西。 在建立和關閉的情況下,零值就可以了。 |
為了真正完成IRP,還在最後呼叫了IoCompleteRequest函式,這個函式主要是將IRP傳播回給它的呼叫者通知客戶端操作已經完成。第二個引數是驅動程式可以提供給其客戶端的臨時優先順序提升至,大多數情況下0值是比較好的IO_NO_INCREMENT被定義為0,因為這樣請求就是同步的了,就大家優先順序都一樣。該函式還要做的最後一個操作是返回與放入 IRP 的操作相同的狀態。
4.2 DeviceIoControl排程函式
這是最重要的地方了。首先需要檢查的是控制程式碼。 典型的驅動程式可能支援很多控制碼,所以如果控制碼不被識別,我們立即返回請求失敗 :
_Use_decl_annotations_
NTSTATUS PriorityBoosterDeviceControl(PDEVICE_OBJECT, PIRP Irp) {
// 獲取IO_STACK_LOCATION
auto stack = IoGetCurrentIrpStackLocation(Irp); // IO_STACK_LOCATION*
auto status = STATUS_SUCCESS;
switch (stack->Parameters.DeviceIoControl.IoControlCode) {
//獲取控制程式碼
case IOCTL_PRIORITY_BOOSTER_SET_PRIORITY:
// do the work
break;
default:
status = STATUS_INVALID_DEVICE_REQUEST;
break;
}
任何獲取IRP資訊的關鍵是檢視和當前裝置管理的IO_STACK_LOCATION中的內容,呼叫IoGetCurrentIrpStackLocation會返回一個指向正確IO_STACK_LOCATION的指標。
IO_STACK_LOCATION中的主要成分是一個名為Parameters 的union成員,它包含一組結構體和每種IRP一一對應。
在IRP_MJ_DEVICE_CONTROL 情況下,我們要檢視它的DeviceControl成員,在該結構體中我們可以找到傳遞給client的資訊,如:控制程式碼、緩衝區和緩衝區長度等等。
不管前面怎麼判斷,最後必須有一段程式碼來確定執行,來返回status:
Irp->IoStatus.Status = status;
Irp->IoStatus.Information = 0;
IoCompleteRequest(Irp, IO_NO_INCREMENT);
return status;
這段程式碼通常是放到最後,如果前面的都判斷正確時才真正實現DeviceControl。
開頭和結尾我們寫好了,最後是最好玩和最重要的部分了,就是修改執行緒優先順序。
首先我們要檢測我們收到的緩衝區是否足夠大可以包含一個ThreadData,為什麼要檢測?因為Kernel和User下的棧是不一樣的,不屬於一個東西,所以必須檢查,特別是對於kernel的東西檢查是非常重要的。
指向User提供的輸入緩衝區指標在Type3InputBuff中,輸入緩衝區長度在InputBufferLength中:
if (stack->Parameters.DeviceIoControl.InputBufferLength < sizeof(ThreadData)) {
status = STATUS_BUFFER_TOO_SMALL;
break;
}
然後假設緩衝區夠大,那麼我們就獲取得到緩衝區指標:
auto data = (ThreadData*)stack->Parameters.DeviceIoControl.Type3InputBuffer;
如果指標為空需要終止:
if (data == nullptr) {
status = STATUS_INVALID_PARAMETER;
break;
}
然後檢測執行緒的優先順序number是否在1-31之間:
if (data->Priority < 1 || data->Priority > 31) {
status = STATUS_INVALID_PARAMETER;
break;
}
接下來呼叫設定執行緒優先順序API :
KPRIORITY KeSetPriorityThread(
_Inout_ PKTHREAD Thread,
_In_ KPRIORITY Priority);
PKTHREAD是一個8位整數,執行緒本身是由一個指向KTHREAD物件的指標來標識的,KTHREAD是核心管理執行緒的方式之一,KTHREAD沒有文件來記錄只能由API來通過執行緒ID獲取Kernel中指向真是執行緒物件的指標。該API叫做PsLookupThreadByThreadId,使用它需要新增標頭檔案<ntifs.h>。
現在可以把執行緒ID變成一個指標了:
PETHREAD Thread;
status = PsLookupThreadByThreadId(ULongToHandle(data->ThreadId), &Thread);
if (!NT_SUCCESS(status))
break;
找到之後就可以呼叫優先順序函式來改動優先順序了:
KeSetPriorityThread((PKTHREAD)Thread, data->Priority);
但是在使用完之後還需要釋放執行緒控制代碼防止資源濫用:
ObDereferenceObject(Thread);
最終函式程式碼:
_Use_decl_annotations_
NTSTATUS PriorityBoosterDeviceControl(PDEVICE_OBJECT, PIRP Irp) {
// get our IO_STACK_LOCATION
auto stack = IoGetCurrentIrpStackLocation(Irp);
auto status = STATUS_SUCCESS;
switch (stack->Parameters.DeviceIoControl.IoControlCode) {
case IOCTL_PRIORITY_BOOSTER_SET_PRIORITY:
{
// do the work
if (stack->Parameters.DeviceIoControl.InputBufferLength < sizeof(ThreadData)) {
status = STATUS_BUFFER_TOO_SMALL;
break;
}
auto data = (ThreadData*)stack->Parameters.DeviceIoControl.Type3InputBuffer;
if (data == nullptr) {
status = STATUS_INVALID_PARAMETER;
break;
}
if (data->Priority < 1 || data->Priority > 31) {
status = STATUS_INVALID_PARAMETER;
break;
}
PETHREAD Thread;
status = PsLookupThreadByThreadId(ULongToHandle(data->ThreadId), &Thread);
if (!NT_SUCCESS(status))
break;
KeSetPriorityThread((PKTHREAD)Thread, data->Priority);
ObDereferenceObject(Thread);
KdPrint(("Thread Priority change for %d to %d succeeded!\n",
data->ThreadId, data->Priority));
break;
}
default:
status = STATUS_INVALID_DEVICE_REQUEST;
break;
}
Irp->IoStatus.Status = status;
Irp->IoStatus.Information = 0;
IoCompleteRequest(Irp, IO_NO_INCREMENT);
return status;
}
最終的完全程式碼:
Windows核心驅動--實現修改執行緒優先順序demo - Sna1lGo - 部落格園 (cnblogs.com)
5 安裝和測試
終於到了這裡了,千萬不要把東西直接安裝到本機上,說不定就藍屏了。最好是用虛擬機器來操作。
在虛擬機器中用sc.exe來載入,不清楚的可以看一看前面的部落格:
Windows核心開發-2-開始核心開發-2-核心開發入門 - Sna1lGo - 部落格園 (cnblogs.com)
新增並載入該驅動,採用WinObj來檢視載入的資料:
符號連結沒問題
然後使用一下:
這裡我們通過process Explorer看到cmd程序由一個執行緒的級別是8,我們給它改一下
booster 768 25
搞定
總結
這裡從頭到尾寫了一個簡單但是完整的驅動,還寫了一個客戶端互動。挺不錯了。加油!