다음을 통해 공유


자습서: Hello World Windows 드라이버 작성(Kernel-Mode 드라이버 프레임워크)

이 문서에서는 KMDF(Kernel-Mode Driver Framework)를 사용하여 작은 유니버설 Windows 드라이버 작성한 다음 별도의 컴퓨터에 드라이버를 배포하고 설치하는 방법을 설명합니다.

필수 구성 요소

  • 단계에 따라 WDK(Windows 드라이버 키트) 설치합니다. WDK를 설치할 때 Windows 디버깅 도구가 포함됩니다.

  • Visual Studio 2022 설치합니다. Visual Studio 2022를 설치할 때 C++ 워크로드에서 데스크톱 개발을 선택한 후, 개별 구성 요소에서 다음을 추가합니다:

    • MSVC v143 - VS 2022 C++ ARM64/ARM64EC Spectre 보안 기능이 적용된 라이브러리(최신)
    • MSVC v143 - VS 2022 C++ x64/x86 Spectre 완화 라이브러리(최신)
    • 최신 v143 빌드 도구를 위한 Spectre 완화 기능이 있는 C++ ATL(ARM64/ARM64EC)
    • 최신 v143 빌드 도구용 C++ ATL (Spectre 취약점 완화 포함, x86 & x64)
    • 최신 v143 빌드 도구(ARM64/ARM64EC)용 Spectre 완화 기능이 있는 C++ MFC
    • 최신 v143 빌드 도구용 C++ MFC(Spectre 취약점 완화 대책 포함, x86 & x64)
    • Windows 드라이버 키트

드라이버 만들기 및 빌드

  1. Microsoft Visual Studio를 엽니다. 파일 메뉴에서 새 > 프로젝트를 선택합니다.

  2. 새 프로젝트 만들기 대화 상자의 왼쪽 드롭다운에서 C++ 선택하고, 가운데 드롭다운에서 Windows 선택하고, 오른쪽 드롭다운에서 드라이버 선택합니다.

  3. 프로젝트 형식 목록에서 커널 모드 드라이버, 비어 있음(KMDF) 선택합니다. 다음선택합니다.

    커널 모드 드라이버 옵션이 선택된 Visual Studio 새 프로젝트 대화 상자의 스크린샷

    Visual Studio에서 드라이버 프로젝트 템플릿을 찾을 수 없는 경우 WDK Visual Studio 확장이 제대로 설치되지 않았습니다. 이 문제를 해결하려면 Visual Studio 설치 관리자을 시작하고, 수정을 선택한 다음, 개별 구성 요소 탭에서 Windows 드라이버 키트 를 추가하고, 수정을 선택합니다.

  4. 새 프로젝트 구성 대화 상자의 프로젝트 이름 필드에 "KmdfHelloWorld"를 입력합니다.

    메모

    새 KMDF 또는 UMDF 드라이버를 만들 때 32자 이하의 드라이버 이름을 선택해야 합니다. 이 길이 제한은 wdfglobals.h에 정의되어 있습니다.

  5. 위치 필드에 새 프로젝트를 만들 디렉터리를 입력합니다.

  6. 솔루션 및 프로젝트를 동일한 디렉터리에 배치하고 만들기를선택합니다.

    만들기 단추가 강조 표시된 새 프로젝트 구성 대화 상자의 Visual Studio 스크린샷

    Visual Studio는 하나의 프로젝트와 솔루션을 만듭니다. 솔루션 탐색기 창에서 볼 수 있습니다. 솔루션 탐색기 창이 표시되지 않으면 보기 메뉴에서 솔루션 탐색기 선택합니다. 솔루션에는 KmdfHelloWorld라는 드라이버 프로젝트가 있습니다.

    솔루션 및 KmdfHelloWorld라는 빈 드라이버 프로젝트를 표시하는 Visual Studio 솔루션 탐색기 창의 스크린샷

  7. 솔루션 탐색기 창에서 솔루션 'KmdfHelloWorld'(프로젝트 1개 중 1개)를 마우스 오른쪽 선택하고 Configuration Manager선택합니다. 드라이버 프로젝트에 대한 구성 및 플랫폼을 선택합니다. 예를 들어 디버그 선택하고 x64 .

  8. 솔루션 탐색기 창에서 KmdfHelloWorld 프로젝트를 마우스 오른쪽 선택하고추가한 다음 새 항목 선택합니다.

  9. 새 항목 추가 대화 상자에서 "Driver.c"를 입력합니다.

    메모

    파일 이름 확장명은 .c이며, .cpp이 아닙니다.

    를 선택한 후을 추가합니다. Driver.c 파일은 다음과 같이 원본 파일아래에 추가됩니다.

    드라이버 프로젝트에 추가된 driver.c 파일을 표시하는 Visual Studio 솔루션 탐색기 창의 스크린샷

첫 번째 드라이버 코드 작성

이제 빈 Hello World 프로젝트를 만들고 Driver.c 소스 파일을 추가했으므로 두 가지 기본 이벤트 콜백 함수를 구현하여 드라이버가 실행하는 데 필요한 가장 기본적인 코드를 작성합니다.

  1. Driver.c에서 먼저 다음 헤더를 포함합니다.

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

    Ntddk.h추가할 수 없는 경우 구성(> C/C++ -> General)을 열고 추가 포함 디렉터리>C:\Program Files (x86)\Windows Kits\10\Include\<build#>\km추가하여 <build#> WDK 설치의 적절한 디렉터리로 대체합니다.

    Ntddk.h 모든 드라이버에 대한 핵심 Windows 커널 정의를 포함하고 Wdf.h WDF(Windows 드라이버 프레임워크)를 기반으로 하는 드라이버에 대한 정의를 포함합니다.

  2. 다음으로, 두 콜백에 대한 선언을 제공합니다.

    DRIVER_INITIALIZE DriverEntry;
    EVT_WDF_DRIVER_DEVICE_ADD KmdfHelloWorldEvtDeviceAdd;
    
  3. 다음 코드를 사용하여 DriverEntry작성합니다.

    NTSTATUS 
    DriverEntry(
        _In_ PDRIVER_OBJECT     DriverObject, 
        _In_ PUNICODE_STRING    RegistryPath
    )
    {
        // NTSTATUS variable to record success or failure
        NTSTATUS status = STATUS_SUCCESS;
    
        // Allocate the driver configuration object
        WDF_DRIVER_CONFIG config;
    
        // Print "Hello World" for DriverEntry
        KdPrintEx(( DPFLTR_IHVDRIVER_ID, DPFLTR_INFO_LEVEL, "KmdfHelloWorld: DriverEntry\n" ));
    
        // Initialize the driver configuration object to register the
        // entry point for the EvtDeviceAdd callback, KmdfHelloWorldEvtDeviceAdd
        WDF_DRIVER_CONFIG_INIT(&config, 
                               KmdfHelloWorldEvtDeviceAdd
                               );
    
        // Finally, create the driver object
        status = WdfDriverCreate(DriverObject, 
                                 RegistryPath, 
                                 WDF_NO_OBJECT_ATTRIBUTES, 
                                 &config, 
                                 WDF_NO_HANDLE
                                 );
        return status;
    }
    

    DriverEntry는 모든 드라이버의 진입점이며, 이는 많은 사용자 모드 애플리케이션에 대한 Main()와 같습니다. DriverEntry 작업은 드라이버 차원의 구조 및 리소스를 초기화하는 것입니다. 이 예제에서는 DriverEntry대해 "Hello World"를 인쇄하고, EvtDeviceAdd 콜백의 진입점을 등록하도록 드라이버 개체를 구성한 다음, 드라이버 개체를 만들고 반환했습니다.

    드라이버 개체는 디바이스 개체, I/O 큐, 타이머, 스핀 잠금 등을 포함하는 드라이버에서 만들 수 있는 다른 모든 프레임워크 개체의 부모 개체 역할을 합니다. 프레임워크 개체에 대한 자세한 내용은 Framework 개체 소개참조하세요.

    DriverEntry경우 코드 분석 및 디버깅에 도움이 되도록 이름을 "DriverEntry"로 유지하는 것이 좋습니다.

  4. 다음으로, 다음 코드를 사용하여 KmdfHelloWorldEvtDeviceAdd작성합니다.

    NTSTATUS 
    KmdfHelloWorldEvtDeviceAdd(
        _In_    WDFDRIVER       Driver, 
        _Inout_ PWDFDEVICE_INIT DeviceInit
    )
    {
        // We're not using the driver object,
        // so we need to mark it as unreferenced
        UNREFERENCED_PARAMETER(Driver);
    
        NTSTATUS status;
    
        // Allocate the device object
        WDFDEVICE hDevice;    
    
        // Print "Hello World"
        KdPrintEx(( DPFLTR_IHVDRIVER_ID, DPFLTR_INFO_LEVEL, "KmdfHelloWorld: KmdfHelloWorldEvtDeviceAdd\n" ));
    
        // Create the device object
        status = WdfDeviceCreate(&DeviceInit, 
                                 WDF_NO_OBJECT_ATTRIBUTES,
                                 &hDevice
                                 );
        return status;
    }
    

    EvtDeviceAdd 디바이스가 도착했음을 감지하면 시스템에서 호출됩니다. 해당 작업은 해당 디바이스에 대한 구조 및 리소스를 초기화하는 것입니다. 이 예제에서는 EvtDeviceAdd대한 "Hello World" 메시지를 출력하고 디바이스 개체를 만들고 반환했습니다. 작성하는 다른 드라이버에서는 하드웨어용 I/O 큐를 만들거나, 디바이스 관련 정보에 대한 디바이스 컨텍스트 스토리지 공간을 설정하거나, 디바이스를 준비하는 데 필요한 다른 작업을 수행할 수 있습니다.

    디바이스 추가 콜백의 경우 드라이버 이름을 접두사로 지정하는 방법을 확인합니다(KmdfHelloWorldEvtDeviceAdd). 일반적으로 이러한 방식으로 드라이버 함수의 이름을 지정하여 다른 드라이버의 함수와 구별하는 것이 좋습니다. DriverEntry만 정확히 그렇게 이름을 지정해야 합니다.

  5. 이제 전체 Driver.c는 다음과 같습니다.

    #include <ntddk.h>
    #include <wdf.h>
    DRIVER_INITIALIZE DriverEntry;
    EVT_WDF_DRIVER_DEVICE_ADD KmdfHelloWorldEvtDeviceAdd;
    
    NTSTATUS 
    DriverEntry(
        _In_ PDRIVER_OBJECT     DriverObject, 
        _In_ PUNICODE_STRING    RegistryPath
    )
    {
        // NTSTATUS variable to record success or failure
        NTSTATUS status = STATUS_SUCCESS;
    
        // Allocate the driver configuration object
        WDF_DRIVER_CONFIG config;
    
        // Print "Hello World" for DriverEntry
        KdPrintEx(( DPFLTR_IHVDRIVER_ID, DPFLTR_INFO_LEVEL, "KmdfHelloWorld: DriverEntry\n" ));
    
        // Initialize the driver configuration object to register the
        // entry point for the EvtDeviceAdd callback, KmdfHelloWorldEvtDeviceAdd
        WDF_DRIVER_CONFIG_INIT(&config, 
                               KmdfHelloWorldEvtDeviceAdd
                               );
    
        // Finally, create the driver object
        status = WdfDriverCreate(DriverObject, 
                                 RegistryPath, 
                                 WDF_NO_OBJECT_ATTRIBUTES, 
                                 &config, 
                                 WDF_NO_HANDLE
                                 );
        return status;
    }
    
    NTSTATUS 
    KmdfHelloWorldEvtDeviceAdd(
        _In_    WDFDRIVER       Driver, 
        _Inout_ PWDFDEVICE_INIT DeviceInit
    )
    {
        // We're not using the driver object,
        // so we need to mark it as unreferenced
        UNREFERENCED_PARAMETER(Driver);
    
        NTSTATUS status;
    
        // Allocate the device object
        WDFDEVICE hDevice;    
    
        // Print "Hello World"
        KdPrintEx(( DPFLTR_IHVDRIVER_ID, DPFLTR_INFO_LEVEL, "KmdfHelloWorld: KmdfHelloWorldEvtDeviceAdd\n" ));
    
        // Create the device object
        status = WdfDeviceCreate(&DeviceInit, 
                                 WDF_NO_OBJECT_ATTRIBUTES,
                                 &hDevice
                                 );
        return status;
    }
    
  6. Driver.c를 저장합니다.

이 예제에서는 드라이버의 기본 개념을 보여 줍니다. 드라이버 컬렉션은 초기화되면 시스템이 필요할 때 호출할 때까지 대기하는 "콜백 컬렉션"입니다. 시스템 호출은 새 디바이스 도착 이벤트, 사용자 모드 애플리케이션의 I/O 요청, 시스템 전원 종료 이벤트, 다른 드라이버의 요청 또는 사용자가 예기치 않게 디바이스를 분리할 때의 기습 제거 이벤트일 수 있습니다. 다행히 "Hello World"라고 말하려면 드라이버 및 장치 생성에 대해서만 걱정할 필요가 있었습니다.

다음으로, 드라이버를 빌드합니다.

드라이버 빌드

  1. 솔루션 탐색기 창에서 솔루션 'KmdfHelloWorld'(프로젝트 1개 중 1개)를 마우스 오른쪽 선택하고 Configuration Manager선택합니다. 드라이버 프로젝트에 대한 구성 및 플랫폼을 선택합니다. 이 연습에서는 디버그, x64을 선택하세요.

  2. 솔루션 탐색기 창에서 KmdfHelloWorld 마우스 오른쪽 단추를 선택하고 속성선택합니다. Wpp 추적 > 모든 옵션Wpp 추적 실행없음으로 설정합니다. 적용 선택한 다음 확인 .

  3. 드라이버를 빌드하려면 빌드 메뉴에서 빌드 솔루션 선택합니다. Visual Studio는 출력 창에서 빌드 진행률을 표시합니다. 출력 창이 표시되지 않으면 보기 메뉴에서 출력 선택합니다. 솔루션이 성공적으로 빌드되었는지 확인하면 Visual Studio를 닫을 수 있습니다.

  4. 빌드된 드라이버를 보려면 파일 탐색기에서 KmdfHelloWorld 폴더로 이동한 다음 x64\Debug\KmdfHelloWorld . 폴더에는 다음이 포함됩니다.

    • KmdfHelloWorld.sys -- 커널 모드 드라이버 파일
    • KmdfHelloWorld.inf - 드라이버를 설치할 때 Windows에서 사용하는 정보 파일입니다.
    • KmdfHelloWorld.cat - 설치 관리자가 드라이버의 테스트 서명을 확인하는 데 사용하는 카탈로그 파일입니다.

드라이버를 빌드할 때 DriverVer set to a date in the future 표시되는 경우 Inf2Cat이 /uselocaltime설정되도록 드라이버 프로젝트 설정을 변경합니다. 이렇게 하려면 구성 속성->Inf2Cat->일반>현지 시간사용합니다. 이제 Stampinf Inf2Cat 모두 현지 시간을 사용합니다.

드라이버 배포

일반적으로 드라이버를 테스트하고 디버그할 때 디버거와 드라이버는 별도의 컴퓨터에서 실행됩니다. 디버거를 실행하는 컴퓨터를 호스트 컴퓨터호출하고 드라이버를 실행하는 컴퓨터를 대상 컴퓨터. 대상 컴퓨터는 테스트 컴퓨터라고도불립니다.

지금까지 Visual Studio를 사용하여 호스트 컴퓨터에서 드라이버를 빌드했습니다. 이제 대상 컴퓨터를 구성해야 합니다.

  1. 드라이버 배포 및 테스트용 컴퓨터 준비(WDK 10) 지침에 나와 있는 대로 따릅니다.

    네트워크 케이블을 사용하여 대상 컴퓨터를 자동으로 프로비전하는 단계를 수행하는 경우 포트 및 키를 기록해 둡다. 디버깅 단계의 뒷부분에서 사용합니다. 이 예제에서는 50000을 포트로 사용하고, 1.2.3.4을 키로 사용합니다.

    실제 드라이버 디버깅 시나리오에서는 KDNET 생성 키를 사용하는 것이 좋습니다. KDNET을 사용하여 임의 키를 생성하는 방법에 대한 자세한 내용은 디버그 드라이버 - 단계별 랩(Sysvad 커널 모드) 항목을 참조하세요.

  2. 호스트 컴퓨터에서 Visual Studio에서 솔루션을 엽니다. KmdfHelloWorld 폴더에서 솔루션 파일 KmdfHelloWorld.sln 두 번 클릭할 수 있습니다.

  3. 솔루션 탐색기 창에서 KmdfHelloWorld 프로젝트를 마우스 오른쪽 단추로 클릭하고 속성 선택합니다.

  4. 드라이버 설치 > 배포이동합니다.

  5. 대상 디바이스 이름테스트 및 디버깅을 위해 구성한 컴퓨터의 이름을 선택합니다. 이 연습에서는 MyTestComputer라는 컴퓨터를 사용합니다.

  6. 최신 버전의 드라이버를 테스트하고 있는지 확인하려면 배포 전에이전 드라이버 버전 제거를 확인하세요.

  7. 하드웨어 ID 드라이버 업데이트선택하고 드라이버의 하드웨어 ID를 입력합니다. 이 연습의 경우 하드웨어 ID는 Root\KmdfHelloWorld입니다. 확인선택하십시오.

    메모

    이 연습에서는 하드웨어 ID가 실제 하드웨어 부분을 식별하지 않습니다. 디바이스 트리 루트 노드의 자식으로 지정된 가상 디바이스를 식별합니다. 실제 하드웨어의 경우 하드웨어 ID 드라이버 업데이트 선택하지 마세요. 대신 설치 및 확인선택합니다. 드라이버의 정보(INF) 파일에 하드웨어 ID가 표시됩니다. 솔루션 탐색기 창에서 KmdfHelloWorld > 드라이버 파일이동하고 KmdfHelloWorld.inf를 두 번 클릭합니다. 하드웨어 ID는 [Standard.NT$ARCH$] 아래에 있습니다.

    [Standard.NT$ARCH$]
    %KmdfHelloWorld.DeviceDesc%=KmdfHelloWorld_Device, Root\KmdfHelloWorld
    
  8. 빌드 메뉴에서 솔루션 배포을 선택합니다. Visual Studio는 드라이버를 설치하고 실행하는 데 필요한 파일을 자동으로 대상 컴퓨터에 복사합니다. 배포에는 1~2분 정도 걸릴 수 있습니다.

    드라이버를 배포하면 드라이버 파일이 테스트 컴퓨터의 %Systemdrive%\drivertest\drivers 폴더에 복사됩니다. 배포 중에 문제가 발생하는 경우 파일이 테스트 컴퓨터에 복사되었는지 확인할 수 있습니다. .inf, .cat, 테스트 인증서 및 .sys 파일 및 기타 필요한 파일이 %systemdrive%\drivertest\drivers 폴더에 있는지 확인합니다.

    드라이버 배포에 대한 자세한 내용은 테스트 컴퓨터드라이버 배포를 참조하세요.

드라이버 설치

Hello World 드라이버를 대상 컴퓨터에 배포하면 이제 드라이버를 설치합니다. 이전에 자동 옵션을 사용하여 Visual Studio에서 대상 컴퓨터를 프로비전한 경우 Visual Studio는 프로비전 프로세스의 일부로 테스트 서명된 드라이버를 실행하도록 대상 컴퓨터를 설정했습니다. 이제 DevCon 도구를 사용하여 드라이버를 설치하기만 하면 됩니다.

  1. 호스트 컴퓨터에서 WDK 설치의 도구 폴더로 이동하여 DevCon 도구를 찾습니다. 예를 들어 다음 폴더를 확인합니다.

    C:\Program Files (x86)\Windows Kits\10\Tools\x64\devcon.exe

    DevCon 도구를 원격 컴퓨터에 복사합니다.

  2. 대상 컴퓨터에서 드라이버 파일이 포함된 폴더로 이동한 다음 DevCon 도구를 실행하여 드라이버를 설치합니다.

    1. 다음은 드라이버를 설치하는 데 사용할 devcon 도구의 일반 구문입니다.

      devcon install <INF 파일><하드웨어 ID>

      이 드라이버를 설치하는 데 필요한 INF 파일은 KmdfHelloWorld.inf입니다. INF 파일에는 KmdfHelloWorld.sys드라이버 이진 파일을 설치하기 위한 하드웨어 ID가 포함되어 있습니다. 하드웨어 ID가 INF 파일에 있는 것을 기억하세요. 해당 ID는 Root\KmdfHelloWorld입니다.

    2. 관리자 권한으로 명령 프롬프트 창을 엽니다. 빌드된 드라이버 .sys 파일이 포함된 폴더로 이동하고 다음 명령을 입력합니다.

      devcon install kmdfhelloworld.inf root\kmdfhelloworld

      devcon 인식되지 않는 것에 대한 오류 메시지가 표시되면 devcon 도구에 경로를 추가해 보세요. 예를 들어 C:\Tools 대상 컴퓨터의 폴더에 복사한 경우 다음 명령을 사용해 보세요.

      c:\tools\devcon install kmdfhelloworld.inf root\kmdfhelloworld

      테스트 드라이버가 서명되지 않은 드라이버임을 나타내는 대화 상자가 나타납니다. 이 드라이버를 설치할 것을 계속하려면 선택하세요.

      드라이버 설치 프로세스 중에 표시되는 보안 경고의 스크린샷

드라이버 디버그

이제 대상 컴퓨터에 KmdfHelloWorld 드라이버를 설치했으므로 호스트 컴퓨터에서 원격으로 디버거를 연결합니다.

  1. 호스트 컴퓨터에서 관리자 권한으로 명령 프롬프트 창을 엽니다. WinDbg.exe 디렉터리로 변경합니다. Windows 키트 설치의 일부로 설치된 WDK(Windows 드라이버 키트)의 x64version WinDbg.exe 사용합니다. WinDbg.exe기본 경로는 다음과 같습니다.

    C:\Program Files (x86)\Windows Kits\10\Debuggers\x64

  2. WinDbg를 시작하여 다음 명령을 사용하여 대상 컴퓨터의 커널 디버그 세션에 연결합니다. 포트 및 키의 값은 대상 컴퓨터를 프로비전하는 데 사용한 값과 동일해야 합니다. 포트에 대해 50000을 사용하고, 키에 대해 1.2.3.4을 사용하여 배포 단계에서 사용한 값입니다. k 플래그는 커널 디버그 세션임을 나타냅니다.

    WinDbg -k net:port=50000,key=1.2.3.4

  3. 디버그 메뉴에서 중단선택하세요. 호스트 컴퓨터의 디버거가 대상 컴퓨터에 연결됩니다. 디버거 명령 창에서 커널 디버깅 명령 프롬프트를 볼 수 있습니다. kd>.

  4. 이 시점에서 kd> 프롬프트에서 명령을 입력하여 디버거를 실험할 수 있습니다. 예를 들어 다음 명령을 사용해 볼 수 있습니다.

  5. 대상 컴퓨터를 다시 실행하려면 디버그 메뉴에서 이동 선택하거나 "g"를 누른 다음 "enter"를 누릅니다.

  6. 디버깅 세션을 중지하려면 디버거 분리디버그 메뉴에서 선택합니다.

    중요하다

    "go" 명령을 사용하여 디버거를 종료하기 전에 대상 컴퓨터가 다시 실행되도록 하거나, 대상 컴퓨터가 디버거와 통신 중이므로 마우스 및 키보드 입력에 응답하지 않는 상태로 유지됩니다.

드라이버 디버깅 프로세스에 대한 자세한 단계별 연습은 유니버설 드라이버 디버그 - 단계별 랩(Echo Kernel-Mode)참조하세요.

원격 디버깅에 대한 자세한 내용은 WinDbg 사용하여원격 디버깅을 참조하세요.

Windows용 디버깅 도구

유니버설 드라이버 디버그 - 단계별 랩(Echo Kernel-Mode)

첫 번째 드라이버를 작성하기