🔍
OutputDebugStringの文字列を取得する
本文のソースはRustでwindows-rs 0.58を使って書いています。
概要
OutputDebugString
はベクトル化例外を使って文字列を出力しています。
ベクトル化例外にはAddVectoredExceptionHandler
を使ってハンドラを登録でき、ハンドラに渡される引数から文字列を取得できます。
ちなみにDirect3D12のデバッグレイヤーの出力も取得できます。
手順
ここではOutputDebugString
の文字列を取得して標準エラー出力に流すことにします。
-
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"));
}
}
- ハンドラの引数
EXCEPTION_POINTERS
のExceptionRecord
にあるExceptionCode
にDBG_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
}
}
-
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_C
とDBG_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",
]
Discussion