🔍

OutputDebugStringの文字列を取得する

に公開

本文のソースはRustでwindows-rs 0.58を使って書いています。

概要

OutputDebugStringはベクトル化例外を使って文字列を出力しています。

ベクトル化例外にはAddVectoredExceptionHandlerを使ってハンドラを登録でき、ハンドラに渡される引数から文字列を取得できます。

ちなみにDirect3D12のデバッグレイヤーの出力も取得できます。

手順

ここではOutputDebugStringの文字列を取得して標準エラー出力に流すことにします。

  1. AddVectoredExceptionHandlerでハンドラを登録します。
unsafe extern "system" fn handler_proc(pointers: *mut EXCEPTION_POINTERS) -> i32 {
    // ここに処理を書いていく
}

fn main() {
    unsafe {
        // ハンドラの登録
        AddVectoredExceptionHandler(0, Some(handler_proc));
        // 出力してみる
        OutputDebugStringA(windows::core::s!("output A"));
        OutputDebugStringW(windows::core::w!("output W"));
    }
}
  1. ハンドラの引数EXCEPTION_POINTERSExceptionRecordにあるExceptionCodeDBG_PRINTEXCEPTION_CまたはDBG_PRINTEXCEPTION_WIDE_Cが入ってるかどうかを調べます。
unsafe extern "system" fn handler_proc(pointers: *mut EXCEPTION_POINTERS) -> i32 {
    // ポインタなので一応チェックしている
    // あとパニックにしたくない
    let Some(pointers) = pointers.as_ref() else {
        return EXCEPTION_CONTINUE_SEARCH;
    };
    let Some(record) = pointers.ExceptionRecord.as_ref() else {
        return EXCEPTION_CONTINUE_SEARCH;
    };
    // 2.の処理はここから
    match record.ExceptionCode {
        DBG_PRINTEXCEPTION_C => {
            // OutputDebugStringAの場合はここにくる
            EXCEPTION_CONTINUE_EXECUTION
        }
        DBG_PRINTEXCEPTION_WIDE_C => {
            // OutputDebugStringWの場合はここにくる
            EXCEPTION_CONTINUE_EXECUTION
        }
        _ => EXCEPTION_CONTINUE_SEARCH
    }
}
  1. ExceptionRecordにあるExceptionInformation[0]に文字列の長さ、ExceptionInformation[1]に文字列へのポインタが入っているのでこれらから文字列を取得します。この時ExceptionInformation[0]に入っている文字列の長さは0終端を含みDBG_PRINTEXCEPTION_Cではバイト長、DBG_PRINTEXCEPTION_WIDE_Cでは文字数となっています。
    そして取得した文字列を標準エラー出力に流せばOKです。
match record.ExceptionCode {
    DBG_PRINTEXCEPTION_C => {
        let len = record.ExceptionInformation[0];
        // マルチバイト文字列なのでu8
        let data = std::slice::from_raw_parts(
            record.ExceptionInformation[1] as *const u8,
            len - 1 // 0終端の分をlen - 1で切っている
        );
        // 標準エラー出力に流す
        // マルチバイト文字列なので本来ちゃんと変換しないといけないが
        // 面倒なので`std::str::from_utf8`でお茶を濁す
        eprintln!(
            "DBG_PRINTEXCEPTION_C: {}: {:?}",
            record.ExceptionInformation[0],
            std::str::from_utf8(data)
        );
        EXCEPTION_CONTINUE_EXECUTION
    }
    DBG_PRINTEXCEPTION_WIDE_C => {
        let len = record.ExceptionInformation[0];
        // ワイド文字列なのでu16
        let data = std::slice::from_raw_parts(
            record.ExceptionInformation[1] as *const u16,
            len - 1 // 0終端の分をlen - 1で切っている
        );
        // 標準エラー出力に流す
        eprintln!(
            "DBG_PRINTEXCEPTION_WIDE_C: {}: {:?}",
            record.ExceptionInformation[0],
            String::from_utf16(data)
        );
        EXCEPTION_CONTINUE_EXECUTION
    }
}

注意点

デバッガがある場合

VSCodeのcppvsdbgのようにデバッグ実行をするとデバッガが入る場合は、AddVectoredExceptionHandlerの最初の引数に0以外を入れてもデバッガのハンドラが優先されて登録したハンドラは呼び出されないようです。

OutputDebugStringWの場合

OutputDebugStringWを呼ぶとDBG_PRINTEXCEPTION_WIDE_Cが投げられますが、
どのハンドラもEXCEPTION_CONTINUE_EXECUTIONを返さなかった場合にフォールバックとしてDBG_PRINTEXCEPTION_Cが投げられます。

例えばDEB_PRINTEXCEPTION_CDBG_PRINTEXCEPTION_WIDE_Cの両方で文字列を出力するとして、
ハンドラでDBG_PRINTEXCEPTION_WIDE_Cで文字列を出力してEXCEPTION_CONTINUE_SEARCHを返した後に後続のハンドラがEXCEPTION_CONTINUE_EXECUTIONを返さないと
DBG_PRINTEXCEPTION_Cが来て二重に出力されます。

ソース

use windows::Win32::{Foundation::*, System::Diagnostics::Debug::*};

unsafe extern "system" fn handler_proc(pointers: *mut EXCEPTION_POINTERS) -> i32 {
    let Some(pointers) = pointers.as_ref() else {
        return EXCEPTION_CONTINUE_SEARCH;
    };
    let Some(record) = pointers.ExceptionRecord.as_ref() else {
        return EXCEPTION_CONTINUE_SEARCH;
    };
    match record.ExceptionCode {
        DBG_PRINTEXCEPTION_C => {
            let len = record.ExceptionInformation[0];
            let data = std::slice::from_raw_parts(
                record.ExceptionInformation[1] as *const u8,
                len - 1
            );
            eprintln!(
                "DBG_PRINTEXCEPTION_C: {}: {:?}",
                record.ExceptionInformation[0],
                std::str::from_utf8(data)
            );
            EXCEPTION_CONTINUE_EXECUTION
        }
        DBG_PRINTEXCEPTION_WIDE_C => {
            let len = record.ExceptionInformation[0];
            let data = std::slice::from_raw_parts(
                record.ExceptionInformation[1] as *const u16,
                len - 1
            );
            eprintln!(
                "DBG_PRINTEXCEPTION_WIDE_C: {}: {:?}",
                record.ExceptionInformation[0],
                String::from_utf16(data)
            );
            EXCEPTION_CONTINUE_EXECUTION
        }
        _ => EXCEPTION_CONTINUE_SEARCH
    }
}

fn main() {
    unsafe {
        AddVectoredExceptionHandler(0, Some(handler_proc));
        OutputDebugStringA(windows::core::s!("output A"));
        OutputDebugStringW(windows::core::w!("output W"));
    }
}
Cargo.toml
[dependencies.windows]
version = "0.58"
features = [
    "Win32_Foundation",
    "Win32_System_Kernel",
    "Win32_System_Diagnostics_Debug",
]
GitHubで編集を提案

Discussion