MSRレジスタを読み込むデバイスドライバ(64bit)を作成しIntel CPUのコア温度を表示する

icon 項目のみ表示/展開表示の切り替え

概要

MSRレジスタを呼び出すrdmsr命令は特権命令なのでカーネルモードでしか動作しません。
MSRレジスタを呼び出す32bitデバイスドライバ(rdmsrdrv64.sys)をインストールした状態で、別のプログラム(cputemp2.exe)からデバイスドライバを制御し各スレッドごとにCPUのジャンクション温度を取得します。
ジャンクション温度の最大値が取得できるCPU(Nehalem以上)は、各スレッドのジャンクション温度を表示します。
ジャンクション温度の最大値が取得できないCPU(Core 2 世代)は、最大ジャンクション温度までの現在の許容値を表示します。
ジャンクション温度が取得できないCPU(Pentium 4以下)は、プログラムを終了します。
一口にCPUの温度といってもジャンクション温度(半導体内部の温度)とケース温度(パッケージ中央の温度)の違いがあります。通常、明示されているのはケース温度の方です。
使用するには、まずデバイスドライバをインストールし、その後cputemp2.exeを実行します。
下図は、Core i3 M370のWindows Vista上で実行したケースです。
2コアでハイパースレッドが使用できるので4スレッド実行可能です。
tjmaxがジャンクション温度の許容温度、温度はジャンクション温度を表しています。
CPU0の場合、許容温度まで43度ということです。

64bitではインラインアセンブラが使えないのでアセンブラ部分をrdmsr.asmに格納しています。
64bitのデバイスドライバはWindowsを通常に起動した場合、証明機関の証明書で署名していない場合は、インストールできません。 詳細は、本文で触れていますが、証明機関(有料)の証明書を使用しない場合、ブート時にドライバー署名の強制を無効にするかテストモードで起動する必要があります。

テスト環境

コンパイラ

ドライバの開発 WDK7
ドライバを制御するソフトの開発 Visual C++ 2013 Express

実行環境

Windows 7 Enterprise 64bit
Windows 8.1 Enterprise 64bit

ドライバソースファイル等のフォルダー構成

d:¥rdmsrdrv64
│  
└─rdmsrdrv64
    │  rdmsrdrv64.cpp
    │  makefile
    │  sources
    │  
    └─objfre_win7_amd64
        └─amd64
                rdmsr.asm

ドライバ開発環境の構築とビルドの方法

デバイスドライバの開発にWDK7を使用します。以下のサイトよりWDK 7.1.0をダウンロードし、インストールします。
Windows Driver Kit (WDK)https://msdn.microsoft.com/ja-jp/library/windows/hardware/ff557573(v=vs.85).aspx
WDK7にはコンパイラも含まれています。(Visual C++ 2008の英語版相当)ただし、コマンドラインでしか使用できません。
文法チェックやインテリセンス機能を使用したいので、Visual Studioでソースコードの編集を行いWDK7でデバイスドライバを開発することとします。

Visual Studio C++でプロジェクトを作成

Visual Studioで新規プロジェクトを作成を開始し、テンプレートはメイクファイルプロジェクトを選択し後は適当に操作して完了させます。
ここではd:¥ドライブにrdmsrdrvプロジェクトを作成しました。
プロジェクトのプロパティを表示し、インクルードの検索パスにDDKのインクルードファイルが格納されているフォルダー名を指定します。(ディフォルトではC:¥WinDDK¥7600.16385.1¥inc¥ddk)
これによりVisual Studioのエディタ上で関数名の色が変わったり、インテリセンスが使えるようになり便利です。

makefileとsourcesファイルの作成

上記のファイルを作成します。保存先は、d:¥rdmsrdrv64¥rdmsrdrv64フォルダとします。本プログラムに必要なファイルは本ページの最後のダウンロードのリンク先に含まれています。
makefileとsourcesファイル名には拡張子はついていないので注意してください。

makefile

!INCLUDE $(NTMAKEENV)¥makefile.def

sources

TARGETNAMEで作成するデバイスドライバのファイル名を指定します。本例ではbeeptest.sysとなります。
SOURCESでソースファイル名を指定します。複数のソースファイルがある場合はスペースで区切ります。
TARGETNAME=rdmsrdrv64
TARGETTYPE=DRIVER
TARGETPATH=obj
SOURCES=rdmsrdrv64.cpp
AMD64_SOURCES=rdmsr.asm

WDK7によるデバイスドライバの作成

スタートメニューからWindows Driver Kits・WDK 7600.16385.1・Build Environments・Windows XP¥x86 Free Build Environmentを実行します。
メニューで選択するOSは開発しているパソコンのOSではなく、ドライバを実行するOSです。XPを選択すればXP,Vista,7で使用可能です。
コマンドプロンプトのウィンドウが開くので、CDコマンドを使用してソースファイルが存在するフォルダーに移動します。
buildコマンドでコンパイルできます。
cd d:¥rdmsrdrv64¥rdmsrdrv64
build
デバイスドライバは、d:¥rdmsrdrv64¥rdmsrdrv64¥objfre_win7_amd64¥amd64 に作成されます。

デバイスドライバのインストール

署名のない64bitドライバは、Windowsを普通に起動するとインストールできません。
署名を回避する方法については、署名がないドライバをインストールする方法を参照してください。
ドライバのインストールは、ドライバのインストール/アンインストールするプログラムを参照指定ください。

ドライバ ソースの概要

rdmsrdrv64.cpp

各関数名が勝手に修飾されないように、extern "C"で定義します。
各関数は、コンパイル時にOACRにより警告が出ないように独特の型でプロトタイプ宣言を行います。
#pragma alloc_textにより各関数をメモリ上の扱いを定義します。この定義を行った場合、各関数の入口にPAGED_CODE();を記述する必要があります。
INITを指定した場合、ページアウトしません。PAGEをした場合、ページアウト可能です。
MSRを読み込むためにrdmsr命令を実行する必要があるので、アセンブラの関数rdmsr_asmをプロトタイプ宣言しています。

DriverEntry関数

デバイスドライバが起動されたときの1度だけ実行される関数です。
デバイスドライバの名称の定義と、 デバイスドライバを使用するプログラムで実行されるCreateFile関数・CloseFile関数・DeviceIoControl関数に対する処理先等を定義します。
以後、使用する側から呼び出される場合は、定義した処理先が呼び出されます。

DeviceControlFunction関数

DriverEntry関数で登録されたデバイスドライバを使用する上のDeviceIoControl関数から呼び出されるプログラムで実行される関数である。
直接呼び出されるのではなく、カーネルがバッファ処理等を行ってから呼び出されます。
引数より入力・出力バッファのアドレス・サイズ等が含まれる構造体をIoGetCurrentIrpStackLocation関数により呼び出しますす。
アクセスIDを調べ該当する処理に振り分けます。本ドライバはIOCTL_RDMSR_CODEのみサポートしています。
入力バッファより、アドレス値を取得しrdmsr_asm関数を呼び出します。
存在しないMSRレジスタのアドレスの指定した場合、例外が発生しますのでプログラムが終了しないように、__try{ }__exceptで例外を捕捉します。
デバイスドライバを呼び出したプログラムからの例外の有無の確認は、出力バッファのサイズが0の場合、例外が発生したことを示します。
出力バッファにrdmsr_asm関数の戻り値を格納します。
IoCompleteRequestにより処理したコマンドを消去します。

UnloadDriverFunction関数

ドライバがアンロードされるときに呼び出されます。
IoDeleteDevice関数によりドライバを消去します。

rdmsrdrv64.asm

rdmsr_asm

第一引数はMSRのアドレス値でありecxレジスタに格納されています。
rdmsrを実行する前にrax,rdxを0クリアしておきます。
shl命令でrdxレジスタを32bit左シフトします。
add命令でraxとrdxで加算するとrdmsrの実行結果edx:eaxが戻り値raxにコピーされたことになります。

ドライバ(rdmsrdrv64.sys) ソースコード

rdmsrdrv64.cpp

//      WDMドライバサンプル(MSRの読み込み)
//      動作環境 Windows 7 8.1 64bit

#ifdef __cplusplus
        extern "C" {
#endif

#include <wdm.h>

#define IOCTL_RDMSR_CODE 0x800

//      エントリーポイント
DRIVER_INITIALIZE DriverEntry;

//      Create
__drv_dispatchType(IRP_MJ_CREATE) DRIVER_DISPATCH CreateFunction;
NTSTATUS CreateFunction(IN PDEVICE_OBJECT DeviceObject,IN PIRP Irp);

//      Close
__drv_dispatchType(IRP_MJ_CLOSE) DRIVER_DISPATCH CloseFunction;
NTSTATUS CloseFunction(IN PDEVICE_OBJECT DeviceObject, IN PIRP Irp);

//      DeviceControl
__drv_dispatchType(IRP_MJ_DEVICE_CONTROL) DRIVER_DISPATCH DeviceControlFunction;
NTSTATUS DeviceControlFunction(IN PDEVICE_OBJECT DeviceObject, IN PIRP Irp);

//      AddDevice
NTSTATUS AddDeviceFunction(IN PDEVICE_OBJECT DriverObject, IN PDEVICE_OBJECT DeviceObjcet);

//      UnloadDriver
DRIVER_UNLOAD UnloadDriverFunction;

DWORD64 rdmsr_asm(DWORD32 ads); //      rdmsr命令の実行結果の取得

#ifdef ALLOC_PRAGMA
#pragma alloc_text( INIT, DriverEntry )
#pragma alloc_text( PAGE, CreateFunction)
#pragma alloc_text( PAGE, CloseFunction)
#pragma alloc_text( PAGE, DeviceControlFunction)
#pragma alloc_text( PAGE, AddDeviceFunction)
#pragma alloc_text( PAGE, UnloadDriverFunction)


#endif

#ifdef __cplusplus
        }
#endif

//      ドライバーがロードされたときに呼び出される関数
NTSTATUS DriverEntry(IN PDRIVER_OBJECT pDriverObject,IN PUNICODE_STRING pRegistryPath){
        UNICODE_STRING usDeviceName,usDosDeviceName;
        PAGED_CODE();
        RtlInitUnicodeString( &usDeviceName, L"\\Device\\msrtest" );
        RtlInitUnicodeString(&usDosDeviceName, L"\\DosDevices\\msrtest");

        PDEVICE_OBJECT pDeviceObject = NULL;
        NTSTATUS NtStatus=IoCreateDevice( pDriverObject, 0, &usDeviceName, FILE_DEVICE_SECURE_OPEN, 0, FALSE, &pDeviceObject);
        if( NtStatus != STATUS_SUCCESS){
                return NtStatus;
        }
        pDriverObject->MajorFunction[IRP_MJ_CREATE]=CreateFunction;  //      CreateFile呼び出し時の処理を登録
        pDriverObject->MajorFunction[IRP_MJ_DEVICE_CONTROL] = DeviceControlFunction; //      DeviceControl呼び出し時の処理を登録
        pDriverObject->MajorFunction[IRP_MJ_CLOSE]=CloseFunction;    //      CloseFile呼び出し時の処理を登録
        
        pDriverObject->DriverUnload = UnloadDriverFunction;  //      ドライバがアンロードされる時の処理を登録

        NTSTATUS status = IoCreateSymbolicLink(&usDosDeviceName, &usDeviceName);

        return STATUS_SUCCESS;
}

//      Create
NTSTATUS CreateFunction(IN PDEVICE_OBJECT DeviceObject,IN PIRP Irp){
        PAGED_CODE();
        return STATUS_SUCCESS;
}

//      Close
NTSTATUS CloseFunction(IN PDEVICE_OBJECT DeviceObject,IN PIRP Irp){
        PAGED_CODE();
        return STATUS_SUCCESS;
}

//      DeviceControl   周波数,on/off
NTSTATUS DeviceControlFunction(IN PDEVICE_OBJECT DeviceObject, IN PIRP Irp){
        PAGED_CODE();
        int dwDataRead = 0, dwDataWritten = 0;
        NTSTATUS NtStatus = STATUS_NOT_SUPPORTED;
        PIO_STACK_LOCATION pIoStackIrp = IoGetCurrentIrpStackLocation(Irp);
        if (pIoStackIrp){
                // アクセスIDを調べる
                if (pIoStackIrp->Parameters.DeviceIoControl.IoControlCode == IOCTL_RDMSR_CODE){
                        // バッファポインタ取得
                        PCHAR pInputBuffer = (PCHAR)Irp->AssociatedIrp.SystemBuffer;
                        PCHAR pOutputBuffer = (PCHAR)Irp->AssociatedIrp.SystemBuffer;
                        // バッファサイズ取得
                        int InSize = pIoStackIrp->Parameters.DeviceIoControl.InputBufferLength;
                        int OutSize = pIoStackIrp->Parameters.DeviceIoControl.OutputBufferLength;
                        DWORD32 ads = *(DWORD32*)pInputBuffer;
                        __try{

#ifndef _WIN64  //      32bitモード
                                DWORD32* d32 = (DWORD32*)pOutputBuffer;
                                DWORD32 l, h;
                                __asm{
                                        mov     ecx, ads
                                                rdmsr
                                                mov     l, eax
                                                mov     h, edx
                                }
                                d32[0] = l;
                                d32[1] = h;
#else   //      64bitモード
                                DWORD64* d64 = (DWORD64*)pOutputBuffer;
                                *d64 = rdmsr_asm(ads);  //      rdmsr命令をアセンブラで呼び出す。
#endif
                                Irp->IoStatus.Information = 8;
                                NtStatus = STATUS_SUCCESS;
                        }
                        __except (EXCEPTION_EXECUTE_HANDLER){   //      rdmsr命令が存在しない場合、存在しないMSRアドレスの場合
                                Irp->IoStatus.Information = 0;
                                NtStatus = STATUS_NOT_SUPPORTED;
                        }
                }
        }
        Irp->IoStatus.Status = NtStatus;
        IoCompleteRequest(Irp, IO_NO_INCREMENT);
        return STATUS_SUCCESS;
}

//      AddDevice
NTSTATUS AddDeviceFunction(IN PDEVICE_OBJECT DriverObject, IN PDEVICE_OBJECT DeviceObjcet){
        PAGED_CODE();
        return STATUS_SUCCESS;
}

//      UnloadDriver
VOID UnloadDriverFunction(IN PDRIVER_OBJECT DriverObject){
        PAGED_CODE();
        IoDeleteDevice(DriverObject->DeviceObject);
}

rdmsrdrv.asm

;       64bit用 rdmsr呼び出し用の関数
;       第1引数    ecx:MSRアドレス
;       戻り値     rax: rdmsr命令の実行結果 edx:eax
        
        .code

        public  rdmsr_asm

rdmsr_asm       PROC
        xor             rax,rax
        xor             rdx,rdx
        rdmsr
        shl             rdx,32
        add             eax,edx
        ret
rdmsr_asm       ENDP


        END

制御ソフト ソースの概要

cputemp2.cpp

WinMain関数

最初に呼び出される関数です。
ダイアログボックスを表示します。

DlgProc関数

ダイアログボックスのプロシージャです。
WM_INITDIALOG
ダイアログボックスの初期化時に呼び出されます。
GetCpuMax関数により論理CPU数を取得します。
CreateFile APIによりデバイスドライバをオープンします。
デバイスドライバが見つからない場合は、エラーメッセージを出力しプログラムを終了します。
SetTimer APIにより1秒置きに、WM_TIMERメッセージを発生します。
WM_TIMER
1秒置きに呼び出されます。
GetCurrentThread APIにより自身のスレッドハンドルを取得します。
各論理CPUごとの処理
SetThreadAffinityMask APIにより実行する論理CPUを固定します。
RDMSR関数によりMSRのアドレスIA32_THERM_STATUS(0x019c)の値を取得します。取得値の22~16ビット目は、ジャンクション許容値-現在値を示します。
RDMSR関数によりMSRのアドレスIA32_TEMPERATURE_TARGET(0x01a2)の値を取得します。取得値の23~16ビット目は、ジャンクション許容値を示します。
RDMSR関数の戻り値がTRUEの場合は、指定したアドレスが存在することを示します。
IA32_THERM_STATUSとIA32_TEMPERATURE_TARGETの両方が取得できた場合は、ジャンクション許容値と現在値をダイアログに表示します。
IA32_THERM_STATUSのみ取得できた場合は、許容温度まで残り値を表示します。
IA32_THERM_STATUSとIA32_TEMPERATURE_TARGETの両方とも取得できなかった場合は、KillTimer APIによりタイマーを解放し、CloseHandle APIによりドライバを閉じて、EndDialog APIによりダイアログボックスを終了させます。
WM_COMMAND
IDCANCEL
KillTimer APIによりタイマーを解放し、CloseHandle APIによりドライバを閉じて、EndDialog APIによりダイアログボックスを終了させます。

RDMSR関数

第1引数がドライバのハンドル、第2引数がMSRのアドレス、第3引数が取得MSRの値を保存するポインタです。
DeviceIoControl経由でデバイスドライバのDeviceControlFunction関数を呼び出します。
DeviceIoControl APIの戻り値のバイト数をチェックし0バイトの場合はFALSEを返します。

GetCPUMax関数

GetSystemInfo APIにより論理CPU数を取得します。

制御ソフト(cputemp2) ソースコード

cputemp2.cpp

#include <windows.h>
#include <commctrl.h>
#include <stdio.h>
#include <tchar.h>

#include "resource.h"

#define TIMER_ID        200
#define IOCTL_RDMSR_CODE 0x800

#define IA32_TEMPERATURE_TARGET 0x01a2
#define IA32_THERM_STATUS               0x019c

#pragma comment(lib,"comctl32.lib")

LRESULT CALLBACK DlgProc(HWND hDlg, UINT msg, WPARAM wParam, LPARAM lParam);    //      ダイアログボックスプロシージャー
BOOL RDMSR(HANDLE h, DWORD32 adr,DWORD64* data);
int GetCPUMax(void);    //      論理CPU数を取得する

HINSTANCE hInst;


int WINAPI WinMain(HINSTANCE hCurInst, HINSTANCE hPrevInst, LPSTR lpsCmdLine, int nCmdShow){
        ::hInst = hCurInst;
        DialogBox(hCurInst, TEXT("DLG1"), 0, (DLGPROC)DlgProc);
        return (int)0;
}

//      ダイアログボックスプロシージャー

LRESULT CALLBACK DlgProc(HWND hDlg, UINT msg, WPARAM wParam, LPARAM lParam){
        static int CPUMAX;
        static UINT_PTR tid;
        static HANDLE hFile;
        TCHAR buf[1024];
        switch (msg) {
        case WM_INITDIALOG:{
                CPUMAX = GetCPUMax();

                hFile = CreateFile(_TEXT("\\\\.\\msrtest"),
                        GENERIC_READ | GENERIC_WRITE, 0, NULL, OPEN_EXISTING, 0, NULL);
                if (hFile == INVALID_HANDLE_VALUE){
                        MessageBox(hDlg, _TEXT("CreateFileが失敗しました"), _TEXT("エラー"), MB_OK);
                        EndDialog(hDlg, TRUE);
                }
                tid = SetTimer(hDlg, TIMER_ID, 1000, NULL);     
                return TRUE;
        }
        case WM_TIMER:{
                buf[0] = _T('\0');
                int n;
                int error=0;
                HANDLE h = GetCurrentThread();

                for (n = 0; n < CPUMAX; n++){
                        TCHAR s[32];
                        SetThreadAffinityMask(h, 1 << n);
                        int t,tjmax;
                        DWORD64 temp;
                        if (RDMSR(hFile, IA32_THERM_STATUS, &temp)==TRUE){
                                t = (temp >> 16) & 0x7f;
                                if (RDMSR(hFile, IA32_TEMPERATURE_TARGET, &temp)==TRUE){
                                        tjmax = (temp >> 16) & 0xff;
                                        _stprintf_s(s, sizeof(s) / sizeof(TCHAR), _TEXT("CPU%.2d:tjmax %d℃ 温度 %d℃\r\n"), n, tjmax,tjmax-t);
                                        _tcscat_s(buf, sizeof(buf) / sizeof(TCHAR), s);
                                }else{
                                        _stprintf_s(s, sizeof(s) / sizeof(TCHAR), _TEXT("CPU%.2d:許容温度まで残り%d℃\r\n"), n, t);
                                        _tcscat_s(buf, sizeof(buf) / sizeof(TCHAR), s);
                                }
                        }else{
                                error=-1;
                                break;
                        }
                }
                if(error){
                        KillTimer(hDlg, tid);
                        CloseHandle(hFile);
                        MessageBox(hDlg,_TEXT("このシステムはCPU温度を取得できません。"),_TEXT("エラー"),MB_OK);
                        EndDialog(hDlg, TRUE);
                }
                SetWindowText(GetDlgItem(hDlg, IDC_EDIT100), buf);
                break;
        }
        case WM_COMMAND:
                switch (LOWORD(wParam)) {
                case IDCANCEL:  //      ダイアログの終了
                        KillTimer(hDlg, tid);
                        CloseHandle(hFile);
                        EndDialog(hDlg, TRUE);
                        return TRUE;
                default:
                        return FALSE;
                }
        default:
                return FALSE;
        }
        return FALSE;
}

BOOL RDMSR(HANDLE h, DWORD32 adr,DWORD64* data){
        DWORD dwReturn;
        *data = adr;
        DeviceIoControl(h, IOCTL_RDMSR_CODE, data, sizeof(*data),
                data, sizeof(*data), &dwReturn, NULL);
        if (dwReturn == sizeof(DWORD64))
                return TRUE;
        else
                return FALSE;
}


//      論理CPU数を取得する

int GetCPUMax(void){
        SYSTEM_INFO sys;
        GetSystemInfo(&sys);
        return sys.dwNumberOfProcessors;
}

resource.h

#define IDC_EDIT100             100

resource.rc


#include <windows.h>
#include "resource.h"


DLG1 DIALOG DISCARDABLE 0, 0, 142, 142 
EXSTYLE WS_EX_DLGMODALFRAME
STYLE WS_POPUP | WS_THICKFRAME | WS_CAPTION | WS_SYSMENU | WS_MINIMIZEBOX | DS_SETFONT
CAPTION "CPU温度"
FONT 9, "MS 明朝"
{
 CONTROL "", IDC_EDIT100, "EDIT", WS_CHILD | WS_VISIBLE | ES_AUTOHSCROLL | ES_MULTILINE | ES_READONLY, 7, 7, 128, 128
}

実行ファイルとソースファイルのダウンロード

実行ファイルとソースファイルのダウンロード(rdmsrdrv64.zip)
rdmsrdrv64.zipの内容は以下のとおりです。
rdmsrdrv64.sysデバイスドライバは署名されておりません。
本文の署名されていないドライバをインストールする方法を参照して実行してください。
    rdmsrdrv64
    ├─rdmsrdrv64
    │  └─rdmsrdrv64
    │      │  rdmsrdrv64.cpp
    │      │  makefile
    │      │  sources
    │      │  
    │      └─objfre_win7_amd64
    │          └─amd64
    │                  rdmsrdrv64.sys
    │                  rdmsr.asm
    │                  
    └─cputemp2
            cputemp2.cpp
            cputemp2.exe
            cputemp2_64.exe