OutputDebugStringで出力したデバッグライトを自前で受け取る
デバッグ情報を自前で受け取りたい
WindowsプログラムをCで組む時に使うもので、OutputDebugString()というWin32APiがあるのはご存知だと思います。
VisualStudioなど統合環境で開発しているならば、デバッグ中に出力ウインドウに出力することができます。
フリーウェアでもDbgMOnなどがありますが、ここは自分に便利なデバッグモニタが欲しいところです。受信したデバッグ情報をシリアル通信で飛ばすなどリモート機能も簡単に作れますし。
というわけで簡単に作ってしまいましょう。
どんな仕組みになっているのか
OutputDebugString はどこへ出力しているのでしょうか。
ファイルに出力しているわけではありません。通信でもありません。出力先は共有メモリ(プロセス間の、です)です。
まず、”DBWIN_BUFFER”という名前付きマップドファイルを用意します。次に”DBWIN_BUFFER_READY”という名前付きイベントをオンにすることでOutputDebugStringが用意した共有メモリに出力します。OutputDebugStringは自分の出力が終わると、”DBWIN_DATA_READY”という名前付きイベントをオンにします。
面白いのは、Windows上でいろいろなプログラムが動いているとき、それぞれがOutputDebugStringで出力している情報をすべて取得できてしまうことです。そのためにその情報にはプロセスIDも含まれていて、ターゲットのプロセスだけに限定することも可能です。
早速、雛形になるようなものを作ってみましょう。
OutputDebugString出力を受信してファイル出力する
早速ソースコードを書いてみます。
マップドファイル、イベントともセキュリティ指定が必要なことを忘れないようにしてください。
//---------------------------------------------------------------------- // OutputDebugStringを受信してファイル出力する //---------------------------------------------------------------------- int WaitOutputDebugString() { HANDLE hEvAck ; HANDLE hEvReady ; HANDLE hMapFile ; LPVOID pMem ; BOOL bstat ; DWORD dstat ; int len ; SYSTEMTIME st ; FILE *fp ; char fname[260] ; char buff[4096] ; char name[260] ; char limit[260] ; DWORD lastPID ; LPDWORD pPID ; LPSTR pSTR ; SECURITY_ATTRIBUTES sa ; SECURITY_DESCRIPTOR sd ; //--------------- セキュリティ sa.nLength = sizeof(SECURITY_ATTRIBUTES) ; sa.bInheritHandle = TRUE ; sa.lpSecurityDescriptor = &sd ; bstat = InitializeSecurityDescriptor(&sd, SECURITY_DESCRIPTOR_REVISION) ; if (!bstat) return -1 ; bstat = SetSecurityDescriptorDacl(&sd, TRUE, (PACL)NULL, FALSE) ; if (!bstat) return -2 ; //--------------- ACKイベント(シグナル状態で、次のOutputDebugString()用意) hEvAck = CreateEvent(&sa, FALSE, FALSE, "DBWIN_BUFFER_READY") ; if (hEvAck == 0) return -3 ; if (GetLastError() == ERROR_ALREADY_EXISTS) return -4 ; //--------------- READYイベント(シグナル状態でOutputDebugString()出力が完了) hEvReady = CreateEvent(&sa, FALSE, FALSE, "DBWIN_DATA_READY") ; if (hEvReady == 0) return -5 ; if (GetLastError() == ERROR_ALREADY_EXISTS) return -6 ; //--------------- "DBWIN_BUFFER"という名称の共有メモリ(4096bytes) hMapFile = CreateFileMapping((HANDLE)0xFFFFFFFF, &sa, PAGE_READWRITE, 0, 4096, "DBWIN_BUFFER") ; if (hMapFile == 0) return -7 ; pMem = MapViewOfFile(hMapFile, FILE_MAP_READ, 0, 0, 4096) ; if (pMem == 0) return -8 ; //--------------- 先頭DWORDがプロセスID、以下が格納文字列 pPID = (LPDWORD)pMem ; pSTR = (LPSTR)(pPID + 1) ; lastPID = 0xFFFFFFFF ; SetEvent(hEvAck) ; fp = 0 ; while (TRUE) { dstat = WaitForSingleObject(hEvReady, INFINITE) ; if (dstat != WAIT_OBJECT_0) return -9 ; len = lstrlen(pSTR) ; //------------ ここで受け取った文字列、pSTR をコンソールなりファイルなりに出力すればよい // この例ではプロセスIDごとにファイル名を変えてファイル出力している sprintf(fname, "%08X.out", *pPID) ; fp = fopen(fname, "a+") ; if (fp) { GetLocalTime(&st) ; fprintf(fp, "[%04d/%02d/%02d %02d:%02d:%02d] %sn", st.wYear, st.wMonth, st.wDay, st.wHour, st.wMinute, st.wSecond, pSTR) ; fclose(fp) ; } SetEvent(hEvAck) ; } return 0 ; }