לדלג לתוכן

מימוש באמצעות KMDF

נניח שכל driver היה צריך לנהל בעצמו כל מעבר PnP, כל תור וכל מסלול ביטול בסיסי. כמות הקוד שאינו ייחודי להתקן הייתה גדולה, וכל טעות הייתה פועלת בקרנל. המסגרת KMDF מספקת אובייקטים וחוזים שמרכזים חלק מן העבודה הזאת, אבל עדיין דורשת שנבין מי מחזיק בקשה ואיפה מותר לבצע כל פעולה.

נבנה driver תוכנה קטן שמקבל מספר ומחזיר את המספר הבא. הוא אינו נוגע בחומרה ואינו מממש את כל פרויקט הפרק. מטרתו לחבר DriverEntry, יצירת device, תור ו־IOCTL למקור מלא שאפשר להכניס לפרויקט KMDF.

הקשר הבנייה

הבסיס הלימודי הוא Visual Studio 2022 עם WDK 26100.6584 ו־SDK תואם, יעד Windows 11 ב־x64 ו־KMDF 1.33. זו בחירת גרסה לקורס, לא טענה שזו הגרסה החדשה ביותר. יוצרים פרויקט Kernel Mode Driver מסוג KMDF, בוחרים Debug ו־x64 ומחליפים את קובצי callback לדוגמה במקור הבא. אין להשאיר DriverEntry נוסף מן התבנית.

המקור הוא C++ מצומצם: אין STL, חריגות או RTTI, ואין הנחה ש־CRT של תוכנת משתמש זמין. בונים דרך פרויקט WDK עם כלי WindowsKernelModeDriver10.0, ולא באמצעות פקודת cl של console. התקנה וחבילת INF נעשות רק ב־VM כפי שמפורט בפרויקט.

#include <ntddk.h>
#include <wdf.h>
#include <initguid.h>

DEFINE_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; };

extern "C" DRIVER_INITIALIZE DriverEntry;
EVT_WDF_DRIVER_DEVICE_ADD AmitEvtDeviceAdd;
EVT_WDF_IO_QUEUE_IO_DEVICE_CONTROL AmitEvtIoDeviceControl;

extern "C"
NTSTATUS DriverEntry(PDRIVER_OBJECT driver, PUNICODE_STRING registryPath) {
    WDF_DRIVER_CONFIG config;
    WDF_DRIVER_CONFIG_INIT(&config, AmitEvtDeviceAdd);
    return WdfDriverCreate(driver, registryPath,
        WDF_NO_OBJECT_ATTRIBUTES, &config, WDF_NO_HANDLE);
}

NTSTATUS AmitEvtDeviceAdd(WDFDRIVER driver, PWDFDEVICE_INIT deviceInit) {
    UNREFERENCED_PARAMETER(driver);
    WdfDeviceInitSetDeviceType(deviceInit, 0x8000);
    WDF_OBJECT_ATTRIBUTES attributes;
    WDF_OBJECT_ATTRIBUTES_INIT(&attributes);
    attributes.ExecutionLevel = WdfExecutionLevelPassive;
    WDFDEVICE device;
    NTSTATUS status = WdfDeviceCreate(&deviceInit, &attributes, &device);
    if (!NT_SUCCESS(status)) return status;
    status = WdfDeviceCreateDeviceInterface(
        device, &GUID_DEVINTERFACE_AMIT_ECHO, nullptr);
    if (!NT_SUCCESS(status)) return status;

    WDF_IO_QUEUE_CONFIG queue;
    WDF_IO_QUEUE_CONFIG_INIT_DEFAULT_QUEUE(&queue, WdfIoQueueDispatchSequential);
    queue.EvtIoDeviceControl = AmitEvtIoDeviceControl;
    return WdfIoQueueCreate(device, &queue, WDF_NO_OBJECT_ATTRIBUTES,
                            WDF_NO_HANDLE);
}

VOID AmitEvtIoDeviceControl(WDFQUEUE queue, WDFREQUEST request,
    size_t outputLength, size_t inputLength, ULONG code) {
    UNREFERENCED_PARAMETER(queue);
    if (code != IOCTL_AMIT_NEXT) {
        WdfRequestComplete(request, STATUS_INVALID_DEVICE_REQUEST);
        return;
    }
    if (inputLength != sizeof(AMIT_INPUT) ||
        outputLength != sizeof(AMIT_OUTPUT)) {
        WdfRequestComplete(request, STATUS_INVALID_BUFFER_SIZE);
        return;
    }
    AMIT_INPUT* input = nullptr;
    NTSTATUS status = WdfRequestRetrieveInputBuffer(
        request, sizeof(AMIT_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;
    }
    AMIT_OUTPUT* output = nullptr;
    status = WdfRequestRetrieveOutputBuffer(
        request, sizeof(AMIT_OUTPUT), reinterpret_cast<PVOID*>(&output), nullptr);
    if (!NT_SUCCESS(status)) {
        WdfRequestComplete(request, status);
        return;
    }
    output->Version = 1;
    output->Value = captured.Value + 1;
    WdfRequestCompleteWithInformation(request, STATUS_SUCCESS, sizeof(*output));
}

ה־input וה־output של METHOD_BUFFERED יכולים להתייחס לאותו system buffer. לכן מעתיקים את הקלט לערך מקומי לפני כתיבה לפלט. בדיקת האורך נעשית לפני retrieval, וה־API עדיין נבדק משום שהחוזה של הבקשה הוא מקור האמת הסופי. המידע שמוחזר ב־completion הוא מספר הבתים שנכתבו בפועל, לא גודל buffer שהמשתמש ביקש.

עץ אובייקטים אינו רישיון לגישה מאוחרת

אובייקט device הוא הורה של התור כברירת מחדל. ה־framework מנקה ילדים לפי חוזי החיים שלו כאשר ההורה נמחק. אפשר להצמיד context לאובייקט ולהשתמש ב־WDF_DECLARE_CONTEXT_TYPE_WITH_NAME כדי לקבל גישה מטיפוס מוגדר.

ההורות אינה אומרת ש־pointer גולמי ל־context נשאר תקף בכל callback עתידי. אם work item חיצוני מחזיק אותו, צריך להבטיח שהעבודה הסתיימה לפני מחיקת ההורה או להחזיק reference לפי החוזה. גם ניקוי אוטומטי אינו פוטר משחרור משאב שאינו אובייקט WDF.

שלושה סוגי תורים

תור sequential מוסר בקשה אחת בכל פעם לפי חוזה ההתקדמות שלו. תור parallel מאפשר כמה בקשות להימסר, ולכן דורש תיאום על מצב משותף. תור manual שומר בקשות עד שה־driver מבקש אותן במפורש. בפרויקט נשתמש בתור קבלה ובתור manual כדי להפריד בין בדיקת בקשה לבין ביצוע מושהה.

מסירה sequential אינה הבטחה שכל callbacks של ההתקן מסונכרנים זה עם זה. טיימר, callback של target או מסלול PnP יכולים לדרוש סנכרון נפרד. מנגד, synchronization scope של framework אינו בהכרח הגבלה על מספר הבקשות שכבר נמסרו ולא הושלמו. סוג התור וסוג הסנכרון פותרים בעיות שונות.

רמת ביצוע וסביבת callback

בדוגמה ביקשנו PASSIVE_LEVEL עבור callbacks הרלוונטיים. הדבר מאפשר אפשרויות מסוימות שלא קיימות ב־DISPATCH_LEVEL, אך אינו מבטיח context של תהליך המשתמש המקורי. לכן אין להשתמש בכתובת משתמש גולמית רק משום שה־IRQL נמוך.

בפרויקט המושהה נבחר callbacks קצרים שאינם חוסמים, ונציין במפורש אילו מהם פועלים ב־DISPATCH_LEVEL. כל פונקציה שנקראת מהם חייבת להתאים לאותו חוזה, כולל קוד עזר שנראה תמים.

קריאה להעמקה

התיעוד של תורי KMDF, סנכרון אוטומטי והיסטוריית גרסאות KMDF מגדיר את הבחירות. הקוד דורש בניית WDK ובדיקת VM; בדיקת C++ רגילה עם Windows SDK אינה מאמתת driver.