פתרון: התקן תוכנה עם בקשות מושהות¶
נתחיל מבחירת המקום שבו מחזיקים את הבקשה. אפשר לשמור WDFREQUEST במערך פרטי, לסמן אותו cancelable ולנהל בעצמנו מרוץ בין ביטול לבין טיימר. בפרויקט הזה נבחר בתור ידני של KMDF. הבחירה משאירה את הבקשה בבעלות ה־framework במשך ההמתנה, ומרכזת אצלנו רק את הרגע הקצר שבין שליפה לסיום.
ההחלטה אינה מבטלת את הצורך להבין cancellation. היא משנה את החוזה שעליו צריך להגן. בזמן שהבקשה בתור, מנגנון ביטול של התור יכול להעביר אותה ל־EvtIoCanceledOnQueue. אחרי שליפה מוצלחת על ידי הטיימר, אנחנו בעליה ואנחנו חייבים לסיים אותה. הפעולה עצמה קצרה ואינה נשלחת לחומרה, ולכן אין שלב ארוך של בקשה פעילה שצריך לסמן cancelable.
הקבצים וסביבת הבנייה¶
הקוד המלא מופיע בהמשך הדף ואפשר להוריד את כותרת הפרוטוקול, את מימוש הדרייבר, את קובץ ההתקנה ואת לקוח הבדיקה. שמרו את ארבעת הקבצים באותה תיקיית מקור.
צרו ב־Visual Studio 2022 פרויקט Kernel Mode Driver, Empty מסוג KMDF בשם AmitEchoLab, עם WDK 26100.6584 ו־SDK תואם. הגדירו את יעד ה־KMDF ל־1.33 ואת הפלט ל־x64. הסירו מקובצי הבנייה קובצי דוגמה שה־template יצר, אם הם מגדירים DriverEntry או callbacks נוספים. הוסיפו את AmitEchoLab.cpp ואת AmitProtocol.h, והשתמשו ב־INF שסופק במקום קובץ דוגמה בעל שם אחר.
השאירו את הגדרות הקישור של פרויקט הדרייבר, לרבות ספריות KMDF וכניסת ה־framework, כפי שה־template מגדיר. אל תחליפו את הפרויקט בתוכנית console רגילה ואל תגדירו ידנית entry point שמדלג על אתחול ה־framework. הקוד כתוב בתת־קבוצה של C++ ללא STL, חריגות, RTTI או תלות ב־CRT של מצב משתמש.
בנו מחלון הפיתוח המתאים לאחר שהגדרתם בפרויקט את חתימת המעבדה:
msbuild AmitEchoLab.vcxproj /p:Configuration=Debug /p:Platform=x64
cl /std:c++20 /EHsc /W4 AmitClient.cpp /link Setupapi.lib
הפעילו את בדיקות ה־INF והחתימה של ה־WDK ותעדו את התוצאות. קובץ ה־catalog נוצר בתהליך האריזה והחתימה; הוא אינו קובץ טקסט שאפשר להחליף בקובץ ריק. החבילה צריכה להכיל את ה־SYS, ה־INF וה־CAT התואמים לאותו build.
הקיבוע ל־KMDF 1.33 מחייב מערכת יעד תואמת. העיטור ב־INF מגביל את דגם ה־x64 לבנייה 22000 ומעלה, אך אינו מהווה הוכחה שהתנהגות עתידית נבדקה. הקוד והחבילה לא נבנו ב־WDK ולא הוטענו לקרנל במהלך כתיבת חומר זה.
ההתקנה במכונת המעבדה¶
הכינו מראש VM עם snapshot, חיבור debugger ואישור להריץ חבילת test-signed לפי נוהל המעבדה. פקודת התקנה של חבילה ב־driver store אינה בהכרח יוצרת התקן root חדש. בפרויקט הזה יוצרים את ה־devnode באמצעות כלי DevCon של סביבת הפיתוח, מתוך מכונת היעד:
devcon install AmitEchoLab.inf Root\AmitEchoLab
devcon status Root\AmitEchoLab
.\AmitClient.exe --normal
.\AmitClient.exe --cancel
.\AmitClient.exe --close
.\AmitClient.exe --badlength
הפעילו את הלקוח מחלון מוגבה בתוך ה־VM. תיאור האבטחה ב־INF מאפשר גישה ל־SYSTEM ולמנהלים בלבד. בדיקת הפתיחה מחלון שאינו מוגבה צריכה להיכשל בהתאם ל־token ולמדיניות שהותקנו בפועל. הבדיקה חשובה במיוחד לאחר שינוי INF, משום שהתקן ישן עלול עדיין להשתמש בהגדרות מחבילה קודמת.
הסירו את ההתקן המדויק בסיום הניסוי:
הסרת ההתקן והסרת החבילה מה־driver store הן פעולות שונות. כאשר צריך להסיר גם חבילה, זהו תחילה את שם ה־oem INF של החבילה שלנו באמצעות כלי המערכת, ואל תסירו חבילות אחרות לפי השערה.
כיצד נשמרים הגבולות¶
ה־header המשותף מגדיר מבנים בני שמונה בתים ללא מצביעים. הדרייבר בודק גודל מדויק, גרסה וגבול מספרי לפני ההעברה לתור. הוא מעתיק את המספר ל־request context, כך שהקוד של הטיימר אינו תלוי בשאלה אם הקלט והפלט של METHOD_BUFFERED משתמשים באותו buffer בסיסי.
הבדיקה של ארבע בקשות מתבצעת תחת מנגנון הסנכרון של ההתקן. כל callbacks של התורים ושל הטיימר משתתפים באותו תחום סנכרון, ולכן שני callbacks אינם יכולים לראות בו זמנית שלוש בקשות ולהוסיף כל אחד בקשה חמישית. זהו פישוט לימודי בעל עלות: גם לקוחות בלתי תלויים אינם מעובדים במקביל בתוך callbacks אלה.
הגבול אינו מגביל לבדו את הזיכרון של כל שכבת הקלט והפלט. בקשה עשויה להיווצר ולהמתין בתור הראשי לפני שהבדיקה שלנו תרוץ. מערכת שמקבלת לקוחות לא מהימנים דורשת גם חשיבה על מדיניות פתיחה, קצב קלט, מגבלת בקשות בטיסה ומשאבים שה־framework מקצה.
אחרי ForwardToIoQueue מוצלח הקוד אינו קורא שוב את ה־request. הטיימר מקבל בעלות רק אם RetrieveNextRequest הצליח. לאחר CompleteWithInformation גם הקריאה לתור לצורך תזמון העבודה הבאה אינה משתמשת בבקשה שהושלמה.
רמות הפסיקה והנעילות¶
הגדרת ExecutionLevelDispatch יחד עם SynchronizationScopeDevice גורמת לסנכרון callbacks הרלוונטיים של קלט ופלט באמצעות נעילת ה־framework. הטיימר הוא טיימר DPC חד־פעמי עם AutomaticSerialization, והקוד שלו אינו pageable ואינו ממתין. גם פונקציות העזר שנקראות ממנו חייבות לעמוד באותם תנאים.
| המסלול | ההקשר בפרויקט | הפעולה המותרת והגבול |
|---|---|---|
| קבלת IOCTL | תחום הסנכרון של ההתקן ברמת DISPATCH | בדיקת buffer, העברה לתור וסיום קצר |
| טיימר | תחום הסנכרון של ההתקן ברמת DISPATCH | שליפה וסיום ללא המתנה |
| ביטול בתור | תחום הסנכרון של ההתקן | השלמת בקשה בלי גישה למצב pageable |
| סגירת file object | כניסה ברמת PASSIVE, נעילה מפורשת מעלה ל־DISPATCH | ניקוי bounded של בקשות הלקוח בלבד |
| כניסה ויציאה מ־D0 | הקשר PnP ברמת PASSIVE | נעילה קצרה לעדכון מצב והמתנה מחוץ לה |
| הסרה מפתיעה | הקשר PnP ברמת PASSIVE | חסימת עבודה חדשה ללא גישה לחומרה |
אובייקטי הקובץ מקבלים במפורש ExecutionLevelPassive ו־SynchronizationScopeNone, כנדרש בחוזה של SetFileObjectConfig כאשר ההתקן האב מוגדר אחרת. הקריאה ל־WdfDeviceCreate עלולה להיכשל אם נניח בטעות שמותר להם לרשת את הגדרות ההתקן שלנו. פונקציית EvtFileCleanup נכנסת ללא נעילת הקלט והפלט, נועלת את ההתקן במפורש, מוציאה את בקשות הלקוח ומשחררת את הנעילה. הלולאה חסומה על ידי עומק התור, וכל פעולה בתוכה חייבת להיות מותרת ברמת DISPATCH גם שה־callback התחיל ברמת PASSIVE.
ה־callbacks של PnP אינם מקבלים באופן אוטומטי את אותה נעילה שבה משתמשים callbacks של התור. לכן D0Exit נועל במפורש, מסמן שההתקן אינו online ומשחרר את הנעילה. רק לאחר השחרור הוא מחכה לסיום הטיימר ומרוקן את התור.
נניח שהיינו קוראים WdfTimerStop עם המתנה בזמן שהנעילה מוחזקת. טיימר שכבר הוזמן היה יכול להמתין לאותה נעילה כדי להיכנס ל־callback, בעוד D0Exit ממתין ליציאתו. סדר הפעולות שבפתרון מונע את מעגל ההמתנה הזה. התור הידני אינו מנוהל כוח אוטומטית, ולכן הריקון המפורש שלו הוא חלק מהפרוטוקול שלנו.
הטיימר והתור הם ילדים של אובייקט ההתקן. היחס הזה מסייע לשחרור הסופי, אבל אינו מחליף עצירה מסודרת של עבודה בעת יציאה מ־D0. בעת חזרה ל־D0 מתחילים שוב את התור ומפרסמים Online לפני שהמערכת ממשיכה למסור קלט רגיל.
כיצד הלקוח מוכיח שהבקשה נגמרה¶
הלקוח מוצא את ההתקן דרך interface GUID באמצעות SetupAPI. הוא אינו מניח ששמו הסמלי של התקן root קבוע, ואינו ממציא נתיב מתוך מזהה החומרה. לאחר פתיחה אסינכרונית הוא משייך את ה־handle ל־IOCP ושומר את ה־OVERLAPPED ואת שני המבנים על המחסנית עד completion.
המצב --close סוגר את ה־handle לפני ההשלמה. איסוף התוצאה נעשה דרך ה־IOCP שנשאר פתוח, ולכן אינו דורש קריאה ל־GetOverlappedResult באמצעות handle שכבר נסגר. הצלחה עדיין מותרת אם הבקשה ניצחה את הביטול. שגיאת ERROR_OPERATION_ABORTED היא תוצאה תקינה אם הביטול ניצח.
הלקוח כולל timeout לצורך זיהוי תקלה, אך אינו משתמש בו כהוכחה שהקרנל הפסיק לגעת בזיכרון. לאחר timeout הוא מנסה לבטל וממשיך להמתין לתוצאה סופית. אם פורט ההשלמות מחזיר מצב לא צפוי שאינו מזהה את הבקשה, הוא שומר את התהליך חי לבדיקה במקום לשחרר את המחסנית בזמן שעלולה להיות בקשה פעילה. התנהגות זו מתאימה ללקוח אבחון; מוצר צריך לתכנן מדיניות התאוששות שלמה בלי להפר את חוזה חיי הזיכרון.
מה בודקים ומה עדיין צריך להרחיב¶
הפקודה --badlength היא בדיקת דחייה בסיסית. היא מדפיסה את השגיאה, אך exit code תקין שלה אומר שנצפתה דחייה כלשהי, ולא שנבדק קוד שגיאה מסוים. השוו את התוצאה לחוזה הדרייבר ובנו בדיקות ייעודיות לכל קלט פסול, כולל אורך הפלט, מספר הגרסה וקוד הפעולה.
לבדיקת עומס, הרחיבו את הלקוח למערך של חמש בקשות עם OVERLAPPED ו־bufferים נפרדים ויציבים. שלחו אותן לפני השלמת הטיימר, ורשמו אילו בקשות התקבלו ואיזו נדחתה. שימו לב שהשהיה של מאה מילישניות אינה חסם על תזמון הלקוח; אם המערכת נתקעת בין שליחות, הטיימר עלול לפנות מקום לפני הבקשה החמישית. לצורך הוכחת התנאי המדויק אפשר להשתמש ב־breakpoint במכונת המעבדה ולבדוק את עומק התור לפני המשך הריצה.
לבידוד לקוחות, פתחו שני handles נפרדים והגישו בקשה מכל אחד. סגרו רק את הראשון ואספו את שתי התוצאות. השלמת הבקשה של הלקוח השני בהצלחה מוכיחה את התרחיש המסוים, וניתוח השימוש ב־RetrieveRequestByFileObject מסביר מדוע cleanup אינו מרוקן את כל התור.
הפעילו Driver Verifier רק במכונת היעד ובדקו גם restart של ההתקן בזמן שהלקוח פעיל. שמרו dump אם מתקבלת קריסה, והתאימו לו PDB מאותו build. הרחבה לחומרה אמיתית תחייב תכנון נוסף של משאבים, interrupts, DMA ועצירת פעולות חומרה; הטיימר של התקן התוכנה אינו מדמה את כל החוזים האלה.
מקורות לבדיקת החוזים¶
בדקו במיוחד את חוזה ההגדרות של אובייקטי קובץ, משום שהורשת הגדרות ההתקן אינה תקפה כאן.
קראו את ההסבר הרשמי על סנכרון אוטומטי, את החוזה של שליפת בקשה לפי file object, ואת החוזה של ריקון תור סינכרוני. המקורות מגדירים את תנאי הקריאה; ההסבר כאן מתאר את הבחירות המסוימות של הפרויקט.
הצליבו את בקרת הגישה להתקני KMDF ואת עיטורי גרסת היעד ב־INF לפני שינוי חבילת ההתקנה. בדקו גם את היסטוריית גרסאות KMDF כאשר מחליפים את מערכת היעד או את גרסת ה־framework.
הקוד המלא של הפרוטוקול¶
#pragma once
// Include Windows.h + winioctl.h, or ntddk.h, before this header.
static const GUID GUID_DEVINTERFACE_AMIT_ECHO =
{0x72d0c7a1,0xe721,0x4c10,{0x91,0x7c,0x34,0x61,0x9d,0x4b,0x20,0x51}};
#define IOCTL_AMIT_NEXT CTL_CODE(0x8000,0x800,METHOD_BUFFERED,FILE_READ_DATA|FILE_WRITE_DATA)
struct AMIT_INPUT { ULONG Version; ULONG Value; };
struct AMIT_OUTPUT { ULONG Version; ULONG Value; };
static_assert(sizeof(AMIT_INPUT)==8 && sizeof(AMIT_OUTPUT)==8);
הקוד המלא של הדרייבר¶
#include <ntddk.h>
#include <wdf.h>
#include "AmitProtocol.h"
struct DEVICE_CONTEXT {
WDFQUEUE PendingQueue;
WDFTIMER Timer;
BOOLEAN Online;
};
WDF_DECLARE_CONTEXT_TYPE_WITH_NAME(DEVICE_CONTEXT, AmitGetDeviceContext)
struct REQUEST_CONTEXT { ULONG Value; };
WDF_DECLARE_CONTEXT_TYPE_WITH_NAME(REQUEST_CONTEXT, AmitGetRequestContext)
extern "C" DRIVER_INITIALIZE DriverEntry;
EVT_WDF_DRIVER_DEVICE_ADD AmitEvtDeviceAdd;
EVT_WDF_IO_QUEUE_IO_DEVICE_CONTROL AmitEvtIoDeviceControl;
EVT_WDF_IO_QUEUE_IO_CANCELED_ON_QUEUE AmitEvtCanceled;
EVT_WDF_TIMER AmitEvtTimer;
EVT_WDF_FILE_CLEANUP AmitEvtFileCleanup;
EVT_WDF_DEVICE_D0_ENTRY AmitEvtD0Entry;
EVT_WDF_DEVICE_D0_EXIT AmitEvtD0Exit;
EVT_WDF_DEVICE_SURPRISE_REMOVAL AmitEvtSurpriseRemoval;
extern "C" NTSTATUS DriverEntry(PDRIVER_OBJECT driver, PUNICODE_STRING path) {
WDF_DRIVER_CONFIG config;
WDF_DRIVER_CONFIG_INIT(&config, AmitEvtDeviceAdd);
return WdfDriverCreate(driver,path,WDF_NO_OBJECT_ATTRIBUTES,
&config,WDF_NO_HANDLE);
}
NTSTATUS AmitEvtDeviceAdd(WDFDRIVER driver, PWDFDEVICE_INIT init) {
UNREFERENCED_PARAMETER(driver);
PAGED_CODE();
WdfDeviceInitSetDeviceType(init,0x8000);
WDF_PNPPOWER_EVENT_CALLBACKS power;
WDF_PNPPOWER_EVENT_CALLBACKS_INIT(&power);
power.EvtDeviceD0Entry=AmitEvtD0Entry;
power.EvtDeviceD0Exit=AmitEvtD0Exit;
power.EvtDeviceSurpriseRemoval=AmitEvtSurpriseRemoval;
WdfDeviceInitSetPnpPowerEventCallbacks(init,&power);
WDF_FILEOBJECT_CONFIG files;
WDF_FILEOBJECT_CONFIG_INIT(&files,WDF_NO_EVENT_CALLBACK,
WDF_NO_EVENT_CALLBACK,AmitEvtFileCleanup);
WDF_OBJECT_ATTRIBUTES fileAttributes;
WDF_OBJECT_ATTRIBUTES_INIT(&fileAttributes);
fileAttributes.SynchronizationScope=WdfSynchronizationScopeNone;
fileAttributes.ExecutionLevel=WdfExecutionLevelPassive;
WdfDeviceInitSetFileObjectConfig(init,&files,&fileAttributes);
WDF_OBJECT_ATTRIBUTES requestAttributes;
WDF_OBJECT_ATTRIBUTES_INIT_CONTEXT_TYPE(&requestAttributes,REQUEST_CONTEXT);
WdfDeviceInitSetRequestAttributes(init,&requestAttributes);
WDF_OBJECT_ATTRIBUTES attributes;
WDF_OBJECT_ATTRIBUTES_INIT_CONTEXT_TYPE(&attributes,DEVICE_CONTEXT);
attributes.ExecutionLevel=WdfExecutionLevelDispatch;
attributes.SynchronizationScope=WdfSynchronizationScopeDevice;
WDFDEVICE device;
NTSTATUS status=WdfDeviceCreate(&init,&attributes,&device);
if(!NT_SUCCESS(status)) return status;
auto* ctx=AmitGetDeviceContext(device);
WDF_IO_QUEUE_CONFIG queue;
WDF_IO_QUEUE_CONFIG_INIT(&queue,WdfIoQueueDispatchManual);
queue.PowerManaged=WdfFalse;
queue.EvtIoCanceledOnQueue=AmitEvtCanceled;
status=WdfIoQueueCreate(device,&queue,WDF_NO_OBJECT_ATTRIBUTES,
&ctx->PendingQueue);
if(!NT_SUCCESS(status)) return status;
WDF_TIMER_CONFIG timer;
WDF_TIMER_CONFIG_INIT(&timer,AmitEvtTimer);
timer.AutomaticSerialization=TRUE;
WDF_OBJECT_ATTRIBUTES timerAttributes;
WDF_OBJECT_ATTRIBUTES_INIT(&timerAttributes);
timerAttributes.ParentObject=device;
status=WdfTimerCreate(&timer,&timerAttributes,&ctx->Timer);
if(!NT_SUCCESS(status)) return status;
WDF_IO_QUEUE_CONFIG_INIT_DEFAULT_QUEUE(&queue,WdfIoQueueDispatchParallel);
queue.PowerManaged=WdfTrue;
queue.EvtIoDeviceControl=AmitEvtIoDeviceControl;
status=WdfIoQueueCreate(device,&queue,WDF_NO_OBJECT_ATTRIBUTES,WDF_NO_HANDLE);
if(!NT_SUCCESS(status)) return status;
return WdfDeviceCreateDeviceInterface(device,
&GUID_DEVINTERFACE_AMIT_ECHO,nullptr);
}
void AmitEvtIoDeviceControl(WDFQUEUE queue,WDFREQUEST request,
size_t outLength,size_t inLength,ULONG code) {
WDFDEVICE device=WdfIoQueueGetDevice(queue);
auto* ctx=AmitGetDeviceContext(device);
if(!ctx->Online) {
WdfRequestComplete(request,STATUS_DEVICE_NOT_READY); return;
}
if(code!=IOCTL_AMIT_NEXT) {
WdfRequestComplete(request,STATUS_INVALID_DEVICE_REQUEST); return;
}
if(inLength!=sizeof(AMIT_INPUT) || outLength!=sizeof(AMIT_OUTPUT)) {
WdfRequestComplete(request,STATUS_INVALID_BUFFER_SIZE); return;
}
AMIT_INPUT* input=nullptr;
NTSTATUS status=WdfRequestRetrieveInputBuffer(request,sizeof(*input),
reinterpret_cast<PVOID*>(&input),nullptr);
if(!NT_SUCCESS(status)) { WdfRequestComplete(request,status); return; }
const AMIT_INPUT captured=*input;
if(captured.Version!=1 || captured.Value==MAXULONG) {
WdfRequestComplete(request,STATUS_INVALID_PARAMETER); return;
}
ULONG queued=0,owned=0;
WdfIoQueueGetState(ctx->PendingQueue,&queued,&owned);
if(queued>=4) {
WdfRequestComplete(request,STATUS_DEVICE_BUSY); return;
}
AmitGetRequestContext(request)->Value=captured.Value;
status=WdfRequestForwardToIoQueue(request,ctx->PendingQueue);
if(!NT_SUCCESS(status)) { WdfRequestComplete(request,status); return; }
// No access to request after successful forwarding.
if(queued==0) WdfTimerStart(ctx->Timer,WDF_REL_TIMEOUT_IN_MS(100));
}
void AmitEvtTimer(WDFTIMER timer) {
WDFDEVICE device=reinterpret_cast<WDFDEVICE>(WdfTimerGetParentObject(timer));
auto* ctx=AmitGetDeviceContext(device);
if(!ctx->Online) return;
WDFREQUEST request;
NTSTATUS status=WdfIoQueueRetrieveNextRequest(ctx->PendingQueue,&request);
if(NT_SUCCESS(status)) {
AMIT_OUTPUT* output=nullptr;
status=WdfRequestRetrieveOutputBuffer(request,sizeof(*output),
reinterpret_cast<PVOID*>(&output),nullptr);
if(NT_SUCCESS(status)) {
const ULONG value=AmitGetRequestContext(request)->Value;
output->Version=1;
output->Value=value+1;
WdfRequestCompleteWithInformation(request,STATUS_SUCCESS,
sizeof(*output));
} else {
WdfRequestComplete(request,status);
}
} else if(status!=STATUS_NO_MORE_ENTRIES) {
KdPrintEx((DPFLTR_IHVDRIVER_ID,DPFLTR_ERROR_LEVEL,
"AmitEchoLab: retrieve failed %08lx\n",status));
}
ULONG queued=0,owned=0;
WdfIoQueueGetState(ctx->PendingQueue,&queued,&owned);
if(queued!=0) WdfTimerStart(timer,WDF_REL_TIMEOUT_IN_MS(100));
}
void AmitEvtCanceled(WDFQUEUE queue,WDFREQUEST request) {
UNREFERENCED_PARAMETER(queue);
WdfRequestComplete(request,STATUS_CANCELLED);
}
void AmitEvtFileCleanup(WDFFILEOBJECT file) {
PAGED_CODE();
WDFDEVICE device=WdfFileObjectGetDevice(file);
auto* ctx=AmitGetDeviceContext(device);
WDFREQUEST request;
WdfObjectAcquireLock(device);
while(NT_SUCCESS(WdfIoQueueRetrieveRequestByFileObject(
ctx->PendingQueue,file,&request))) {
WdfRequestComplete(request,STATUS_CANCELLED);
}
WdfObjectReleaseLock(device);
}
NTSTATUS AmitEvtD0Entry(WDFDEVICE device,WDF_POWER_DEVICE_STATE previous) {
UNREFERENCED_PARAMETER(previous);
PAGED_CODE();
auto* ctx=AmitGetDeviceContext(device);
WdfIoQueueStart(ctx->PendingQueue);
WdfObjectAcquireLock(device);
ctx->Online=TRUE;
WdfObjectReleaseLock(device);
return STATUS_SUCCESS;
}
NTSTATUS AmitEvtD0Exit(WDFDEVICE device,WDF_POWER_DEVICE_STATE target) {
UNREFERENCED_PARAMETER(target);
PAGED_CODE();
auto* ctx=AmitGetDeviceContext(device);
WdfObjectAcquireLock(device);
ctx->Online=FALSE;
WdfObjectReleaseLock(device);
WdfTimerStop(ctx->Timer,TRUE);
WdfIoQueuePurgeSynchronously(ctx->PendingQueue);
return STATUS_SUCCESS;
}
void AmitEvtSurpriseRemoval(WDFDEVICE device) {
PAGED_CODE();
auto* ctx=AmitGetDeviceContext(device);
WdfObjectAcquireLock(device);
ctx->Online=FALSE;
WdfObjectReleaseLock(device);
}
חבילת ההתקנה¶
[Version]
Signature="$WINDOWS NT$"
Class=System
ClassGuid={4d36e97d-e325-11ce-bfc1-08002be10318}
Provider=%ProviderName%
CatalogFile=AmitEchoLab.cat
DriverVer=10/29/2026,1.0.0.0
PnpLockdown=1
[DestinationDirs]
DriverCopy=13
[SourceDisksNames]
1=%DiskName%,,,
[SourceDisksFiles]
AmitEchoLab.sys=1
[Manufacturer]
%ProviderName%=Models,NTamd64.10.0...22000
[Models.NTamd64.10.0...22000]
%DeviceName%=Install,Root\AmitEchoLab
[Install.NT]
CopyFiles=DriverCopy
[DriverCopy]
AmitEchoLab.sys
[Install.NT.Services]
AddService=AmitEchoLab,0x00000002,Service_Inst
[Service_Inst]
DisplayName=%ServiceName%
ServiceType=1
StartType=3
ErrorControl=1
ServiceBinary=%13%\AmitEchoLab.sys
[Install.NT.HW]
AddReg=DeviceSecurity
[DeviceSecurity]
HKR,,Security,0x00000000,"D:P(A;;GA;;;SY)(A;;GA;;;BA)"
[Install.NT.Wdf]
KmdfService=AmitEchoLab,WdfSection
[WdfSection]
KmdfLibraryVersion=1.33
[Strings]
ProviderName="AmitTech Lab"
DiskName="AmitTech Lab Driver"
DeviceName="AmitTech Deferred Software Device"
ServiceName="AmitTech Deferred Request Lab"
הקוד המלא של הלקוח¶
#define UNICODE
#define _UNICODE
#include <Windows.h>
#include <winioctl.h>
#include <setupapi.h>
#include <iostream>
#include <vector>
#include <string>
#include <system_error>
#include "AmitProtocol.h"
#pragma comment(lib,"Setupapi.lib")
struct Handle {
HANDLE value=INVALID_HANDLE_VALUE;
explicit Handle(HANDLE h=INVALID_HANDLE_VALUE):value(h){}
~Handle(){ if(value && value!=INVALID_HANDLE_VALUE) CloseHandle(value); }
Handle(const Handle&)=delete;
Handle& operator=(const Handle&)=delete;
void close(){ if(value && value!=INVALID_HANDLE_VALUE) CloseHandle(value);
value=INVALID_HANDLE_VALUE; }
};
[[noreturn]] void fail(const char* api) {
throw std::system_error(static_cast<int>(GetLastError()),
std::system_category(),api);
}
std::wstring devicePath() {
HDEVINFO raw=SetupDiGetClassDevsW(&GUID_DEVINTERFACE_AMIT_ECHO,nullptr,
nullptr,DIGCF_PRESENT|DIGCF_DEVICEINTERFACE);
if(raw==INVALID_HANDLE_VALUE) fail("SetupDiGetClassDevs");
struct Set { HDEVINFO h; ~Set(){SetupDiDestroyDeviceInfoList(h);} } set{raw};
SP_DEVICE_INTERFACE_DATA interfaceData{};
interfaceData.cbSize=sizeof(interfaceData);
if(!SetupDiEnumDeviceInterfaces(raw,nullptr,
&GUID_DEVINTERFACE_AMIT_ECHO,0,&interfaceData))
fail("SetupDiEnumDeviceInterfaces");
DWORD size=0;
SetupDiGetDeviceInterfaceDetailW(raw,&interfaceData,nullptr,0,&size,nullptr);
if(GetLastError()!=ERROR_INSUFFICIENT_BUFFER || size==0)
fail("SetupDiGetDeviceInterfaceDetail(size)");
// Default operator new supplies alignment sufficient for this SDK struct.
std::vector<unsigned char> storage(size);
auto* detail=reinterpret_cast<SP_DEVICE_INTERFACE_DETAIL_DATA_W*>(storage.data());
detail->cbSize=sizeof(*detail);
if(!SetupDiGetDeviceInterfaceDetailW(raw,&interfaceData,detail,size,
nullptr,nullptr))
fail("SetupDiGetDeviceInterfaceDetail");
return detail->DevicePath;
}
int wmain(int argc,wchar_t** argv) {
try {
const std::wstring mode=argc>1 ? argv[1] : L"--normal";
if(mode!=L"--normal" && mode!=L"--cancel" &&
mode!=L"--close" && mode!=L"--badlength") {
std::wcerr<<L"Use --normal, --cancel, --close or --badlength\n";
return 2;
}
Handle port(CreateIoCompletionPort(INVALID_HANDLE_VALUE,nullptr,0,1));
if(!port.value) fail("CreateIoCompletionPort");
const auto path=devicePath();
Handle file(CreateFileW(path.c_str(),GENERIC_READ|GENERIC_WRITE,
FILE_SHARE_READ|FILE_SHARE_WRITE,nullptr,OPEN_EXISTING,
FILE_FLAG_OVERLAPPED,nullptr));
if(file.value==INVALID_HANDLE_VALUE) fail("CreateFile");
constexpr ULONG_PTR key=17;
if(!CreateIoCompletionPort(file.value,port.value,key,0))
fail("Associate IOCP");
AMIT_INPUT input{1,41};
AMIT_OUTPUT output{};
OVERLAPPED ov{};
DWORD ignored=0;
BOOL submitted=DeviceIoControl(file.value,IOCTL_AMIT_NEXT,
&input,mode==L"--badlength"?4:sizeof(input),
&output,sizeof(output),&ignored,&ov);
if(!submitted) {
DWORD error=GetLastError();
if(error!=ERROR_IO_PENDING) {
std::cout<<"Immediate error: "<<error<<"\n";
return mode==L"--badlength" ? 0 : 1;
}
}
if(mode==L"--cancel") {
if(!CancelIoEx(file.value,&ov)) {
DWORD error=GetLastError();
std::cout<<"CancelIoEx: "<<error<<"\n";
// Still drain: failure to cancel does not prove completion.
}
}
if(mode==L"--close") file.close();
DWORD bytes=0;
ULONG_PTR receivedKey=0;
OVERLAPPED* received=nullptr;
BOOL ok=GetQueuedCompletionStatus(port.value,&bytes,
&receivedKey,&received,5000);
DWORD error=ok?ERROR_SUCCESS:GetLastError();
if(!received && !ok && error==WAIT_TIMEOUT) {
std::cerr<<"Timeout; cancelling and draining before releasing buffers\n";
if(file.value!=INVALID_HANDLE_VALUE) CancelIoEx(file.value,&ov);
ok=GetQueuedCompletionStatus(port.value,&bytes,
&receivedKey,&received,INFINITE);
error=ok?ERROR_SUCCESS:GetLastError();
}
if(received!=&ov || receivedKey!=key) {
// Unexpected port failure cannot establish the request's lifetime.
// Keep storage alive for inspection instead of unwinding it.
std::cerr<<"Unexpected completion state; inspect the VM\n";
for(;;) Sleep(INFINITE);
}
if(!ok) {
std::cout<<"Terminal error: "<<error<<"\n";
if(mode==L"--badlength") return 0;
return (mode==L"--cancel" || mode==L"--close") &&
error==ERROR_OPERATION_ABORTED ? 0 : 1;
}
std::cout<<"bytes="<<bytes<<" version="<<output.Version
<<" value="<<output.Value<<"\n";
return bytes==sizeof(output) && output.Version==1 &&
output.Value==42 ? 0 : 1;
} catch(const std::exception& e) {
std::cerr<<e.what()<<"\n";
return 1;
}
}