- 概要
- 必須条件
- ReactOSを試しに使ってみる
- ReactOSをビルドして使ってみる(MinGWの場合)
- ReactOSをビルドして使ってみる(Visual Studioの場合)
- Win2003評価版のデバッグ方法(WinDbgを使用)
- Win2003のデバッグの練習(WinDbgを使用)
- ReactOSのデバッグ方法 (MinGW + KDB の場合)
- ReactOSのデバッグの練習(KDBの場合)
- ReactOSのデバッグ方法 (Visual Studio + WinDbgの場合)
- 日本語WinXPのデバッグ方法(WinDbgを使用)
- デバッグシンボルで逆コンパする
- オブジェクト指向の逆コンパ
- 仮想テーブルについて
- 実際に開発に参加する
- ローカルのReactOSを最新に保つ
- その他
- 連絡先
概要
オープンソースで無料の「Windows互換を目指すOS」ReactOSについて、開発者になるために必要な知識を解説します。
強力なデバッガであるWinDbgを使うと、WindowsやReactOSをデバッグしてどのように動作しているのかを調査できます。
必須条件
- WindowsXP以降のPCを持っている
- C言語の素養がある
- 時間と精神力がある
- メインメモリ4GB以上
- 8GB以上の空きがあるSSD
ReactOSを試しに使ってみる
すでに用意されたISOイメージを使ってReactOSを気軽に試すことができます。
- ReactOSのホームページをよく読む(英語)。
- 仮想マシン(VirtualBox推奨)をインストールする (Other Windows (32-bit)) 。現在利用できるフリーの仮想マシンには、 QEMU と VirtualBox と VMWare Workstation Player があります。
- ReactOSのISOイメージをダウンロードする。最新の正式リリースは0.4.14です(2023年3月現在)。正式リリースよりもNightly Buildが新しいですが、正式リリースに含まれている日本語フォントがありません。
- ISOイメージを使って仮想マシンにReactOSをインストールする。
- 仮想マシンでReactOSを試しに使ってみる。
ISOイメージは、CD-Rなどに焼いて実機で使うことができます(オススメはしませんが)。日本語フォントファイルがないISOファイルでは、アプリマネージャで「ReactOS JPN Package」をインストールすると日本語化できます。
ReactOSをビルドして使ってみる(MinGWの場合)
ReactOSの開発を始めたい、ReactOSを改変して試したい場合は、自分でReactOSをビルドする必要があります。
- ReactOS Development Wikiをよく読む(英語)。
- ReactOS Build Environment (RosBE)をhttps://reactos.org/wiki/ReactOS_Build_Environmentからインストールする(※
C:\RosBE
へのインストール推奨)。RosBEにはMinGWの開発環境が含まれています。 - お好みのGitクライアントをインストールする(Git for WindowsかMSYS2のどちらかインストールでgitコマンドを使えるようになる)。BashとGitについて少し勉強する。
- GitHubにSign upして、自分のGitHubアカウントを作成する。
- https://github.com/reactos/reactos をForkする(GitHub上でForkボタンを押す)。
- 適当な開発用のフォルダでGitクライアントで、「
git clone http://github.com/(自分のGitHub ID)/reactos
」を実行する。「reactos
」フォルダができる。 - 日本語が必要ならば、「
reactos/modules
」フォルダに「optional
」フォルダを作成して、その中にフォント「DroidSansFallback.ttf
」ファイルを置く(https://svn.reactos.org/optional/ からダウンロードできます)。ブラウザが必要であれば、「modules/optional
」フォルダに「wine_gecko-2.40-x86.msi
」ファイルを置く。 - 「
reactos
」フォルダの中へ「cd
」コマンドで移動し、RosBEで「configure -DENABLE_ROSTESTS=1 -DENABLE_ROSAPPS=1 -DCMAKE_GENERATOR:STRING=Ninja
」を実行する(※1)。「output-MinGW-i386
」フォルダが作成される。 - 「
cd output-MinGW-i386
」コマンドでフォルダの中に移動する。 - 「
ninja bootcd
」でビルド。コーヒーでも飲みながら待つ。「output-MinGW-i386
」フォルダの中にISOファイル「bootcd.iso
」が作成される(※2)。 - ビルドしたISOイメージを使って仮想マシン (Other Windows (32-bit)) にReactOSをインストールする。
- ビルドしたReactOSを使ってみる。
※1:「オレはRosTestsなんて使わねーよ」という人は「-DENABLE_ROSTESTS=1
」を指定しないことでビルド時間・インストール時間が節約できます。
※2:「ninja livecd
」を実行するとlivecd.iso
ができます。LiveCDはインストールしないので素早く試すことができて、BootCDに比べて時間の節約になるかもしれませんが、いくつか足りない機能があります。
ReactOSをビルドして使ってみる(Visual Studioの場合)
WinDbgでReactOSをデバッグしたい場合は、Visual Studioでビルドして、シンボルファイル(*.pdb
)を生成する必要があります。事前にVisual StudioのインストーラでVisual C++をインストールする必要があります。以下では、Visual Studio 2019を想定しています。
- ReactOS Development Wikiをよく読む(英語)。
- ReactOS Build Environment (RosBE)をhttps://reactos.org/wiki/ReactOS_Build_Environmentからインストールする(※
C:\RosBE
へのインストール推奨)。 - お好みのGitクライアントをインストールする(Git for WindowsかMSYS2のどちらかインストールでgitコマンドを使えるようになる)。BashとGitについて少し勉強する。
- GitHubにSign upして、自分のGitHubアカウントを作成する。
- https://github.com/reactos/reactos をForkする(GitHub上でForkボタンを押す)。
- 適当な開発用のフォルダでGitクライアントで、「
git clone http://github.com/(自分のGitHub ID)/reactos
」を実行する。「reactos
」フォルダができる。 - 日本語が必要ならば、「
reactos/modules
」フォルダに「optional
」フォルダを作成して、その中にフォント「DroidSansFallback.ttf
」ファイルを置く(https://svn.reactos.org/optional/ からダウンロードできます)。ブラウザが必要であれば、「modules/optional
」フォルダに「wine_gecko-2.40-x86.msi
」ファイルを置く。 - VS Command Prompt (x86があればx86のコマンドプロンプトを使う) で「
reactos
」フォルダの中へcd
コマンドで移動し、以下のコマンドを順番に実行する(※1)。- 「
set PATH=C:\RosBE\bin;%PATH%
」 - 「
set M4=C:\RosBE\Bin\m4.exe
」 - 「
set BISON_PKGDATADIR=C:\RosBE\share\bison
」 - 「
configure.cmd -DENABLE_ROSTESTS=1 -DENABLE_ROSAPPS=1 -DCMAKE_GENERATOR:STRING=Ninja
」
- 「
- 「
output-VS-i386
」フォルダが作成される。 - 「
output-VS-i386
」フォルダの中にcd
コマンドで移動し、「ninja bootcd
」を実行してビルドする。お茶でも飲みながら待つ。 - しばらく待つと「
output-VS-i386
」フォルダの中にISOファイル「bootcd.iso
」が作成される。 - 「
output-VS-i386
」フォルダの中にmsvc_pdb
フォルダがある。これがWinDbgで使えるシンボルファイル(*.pdb
)が含まれているフォルダです。 - ビルドしたISOイメージを使って仮想マシンにReactOS (Other Windows (32-bit)) をインストールする。
- ビルドしたReactOSを使ってみる。
※1:「オレはRosTestsなんて使わねーよ」という人は「-DENABLE_ROSTESTS=1
」を指定しないことでビルド時間・インストール時間が節約できます。
※2:RosBEのCMakeは少しハックされたCMakeです。オリジナルのCMakeとはちょっと違います。
Win2003評価版のデバッグ方法(WinDbgを使用)
ReactOSはWindows Server 2003を参考にして作られています。Win2003の動作を深く調査するためには、Win2003をデバッグする必要があります。
- WinDbg Preview をストア(Microsoft Store)からインストールする。
- Win2003 Server評価版 をVirtualBoxにインストールする(評価版は期限切れのため、システム日時の変更が必要。日付を2008-01-01などに設定する)。
- 画面が停止して長時間、動かない場合は、手動でリセットする必要があるかもしれません(電源管理が古い。「仮想マシン」→「リセット」)。
- Win2003の電源を切る前に管理者権限のコマンドプロンプトで「
bootcfg /debug ON /PORT COM1 /ID 1
」を実行する。また、VirtualBox Guest Additionsも忘れずインストールする。
- Win2003の電源が切れたら、VirtualBoxの設定でシリアルポートCOM1にパイプ(
\\.\pipe\com1
)を設定する(下の画像参照)。「シリアルポート」の「シリアルポートを有効化」にチェックを入れ、「ポートモード」を「ホストにパイプ」に変更して、「パス/アドレス」に「\\.\pipe\com1
」を指定する。チェックボックス「存在するパイプ/ソケットに接続」のチェックをはずす。※「\
」は、半角の円記号です。
- WinDbgを起動し、「Settings」の「Debugging settings」で「Default symbol path」に「
srv*
」を設定。
- 「ファイル」メニューから「Attach to Kernel」を選択し、「Pipe」「Reconnect」にチェックを入れ、「Port」に「
\\.\pipe\com1
」を設定し、「OK」ボタンを押す。
- その後、Win2003を再起動すればデバッグが開始される。
仮想マシンの状態を保存するために、VirtualBoxを操作して仮想マシンのスナップショットを作成する。
WinDbgのコマンドについては以下を参照。
注意:システム日時が古い状態でソースファイルを編集しないで下さい。ターゲットファイルよりタイムスタンプの古いファイルは、更新なしと見なされます。
Win2003のデバッグの練習(WinDbgを使用)
では、実際にWin2003とWinDbgを使ってデバッグを試してみよう。今回は、explorer.exe
のSHELL32!SHFileOperationW
をデバッグしてみます。SHFileOperationW
関数は、文字通りファイルやフォルダをコピー・移動・削除などするシェル関数です。
- 期限切れ対策のためにシステム日時を変更する。
- 「ファイル」メニューから「Attach to Kernel」を選択し、「OK」をクリックする。
- デバッガが再接続待ちになる。デバッグを開始するためにWin2003を再起動する。
- 「Break」ボタンを押してブレークする。これでデバッガコマンドが入力できる状態になる。
- デバッガで「
!process 0 0
」コマンドを実行。プロセスのリストが表示される。そのリストの中からexplorer.exe
を探す。
...
PROCESS 81dec8b0 SessionId: 0 Cid: 06f4 Peb: 7ffdf000 ParentCid: 06d0
DirBase: 14d28000 ObjectTable: e1937640 HandleCount: 350.
Image: explorer.exe
...
- 「
explorer.exe
」という項目に「PROCESS 81dec8b0
」と書いてあるから、これを使ってexplorer.exe
のプロセスを アタッチする。「.process 81dec8b0
」コマンドを実行。さらに「.reload /user
」コマンドを実行してユーザモードのデバッグを可能にする。 - 「
LM
」コマンドを実行して、モジュールのリストを表示する。
kd> LM
...
7c800000 7c8c2000 ntdll (pdb symbols) C:\symbols\ntdll.pdb\4613B105DA224E789F26051F952BE5A82\ntdll.pdb
7c8d0000 7d0cf000 SHELL32 (deferred)
7d1e0000 7d27c000 ADVAPI32 (deferred)
...
- モジュールに
SHELL32
があるのが分かる。さらにシンボルを検索するために「x SHELL32!SHFile*
」を実行する。
kd> x SHELL32!SHFile*
7c9a1cde SHELL32!SHFileOperationW (_SHFileOperationW@4)
7c9a1fc6 SHELL32!SHFileOperationA (_SHFileOperationA@4)
7c959535 SHELL32!SHFileSysBindToStorage (_SHFileSysBindToStorage@24)
- ここでシンボル「
SHELL32!SHFileOperationW
」にデバッグブレークを設定するために「bp SHELL32!SHFileOperationW
」を実行する。 - 「
g
」コマンドでWin2003のデバッグ実行を再開する。 - Win2003を操作して、ファイルをコピーして貼り付けしようとすると、ブレークが発生する。
kd> g
Breakpoint 0 hit
SHELL32!SHFileOperationW:
001b:7c9a1cde 8bff mov edi,edi
- 「kp」コマンドを実行すると呼び出し履歴が得られる。
kd> kp
# ChildEBP RetAddr
00 01aef86c 7ca05f85 SHELL32!SHFileOperationW
01 01aefce0 7ca062a3 SHELL32!CFSDropTarget::_MoveCopy+0x1ff
02 01aeff38 7ca06349 SHELL32!CFSDropTarget::_DoDrop+0x270
03 01aeff54 77da3f12 SHELL32!CFSDropTarget::_DoDropThreadProc+0x46
04 01aeffb8 77e6482f SHLWAPI!WrapperThreadProc+0x94
05 01aeffec 00000000 kernel32!BaseThreadStart+0x34
これはSHFileOperationW
関数がCFSDropTarget::_MoveCopy
関数から呼び出されているのが分かる。さらにステップ実行すれば、SHELL32!SHFileOperationW
のデバッグが可能になる。
ReactOSのデバッグ方法 (MinGW + KDB の場合)
WinDbgは強力なデバッガですが、ReactOSにはKDB(またはKdbg)という別のカーネルデバッガが組み込まれています。KDBはWinDbgと比べると貧相ですが、WinDbgよりも高速にデバッグができます。
MinGW + KDBでReactOSをデバッグする方法は以下の通りです。
- VirtualBoxにMinGWでビルドしたReactOSをインストールした後、電源OFFの状態でVirtualBoxの「設定」でシリアルポートを有効化し、適当な場所のRawファイルに出力するようにする。(※注記: RawファイルじゃなくてPuTTYとパイプを使って接続しても構いません。TeraTerm? 何それ?)
- ReactOSを起動し、起動メニューで「
ReactOS (Debug)
」を選ぶ。
- するとデバッグモードで起動し、シリアルポートにデバッグ情報が出力される。
ReactOSでは、ソースコードの中でDPRINT1
文(もしくはERR
文)をC言語のprintf
のように使えばデバッグ出力されるようになっています(デバッグしたいプロジェクト内に「WINE_DEFAULT_DEBUG_CHANNEL(...);
」のような記載があるときはデバッグ出力に「ERR
」を、ないときは「DPRINT1
」を使って下さい。また、ソース内に「#define NDEBUG
」がある場合は、それを消して下さい)。
キーボードのTab+K
を押せば、デバッガに強制的に移行する。 デバッガに入ると、VirtualBoxの画面の動きが止まるが、 気にせず、デバッグ出力を監視しながら、キーボードでデバッガに入力できる。「cont
」コマンドでデバッガから復帰できる。
KDBのコマンドについてはこちらを参照して下さい:
ReactOSのデバッグの練習(KDBの場合)
今回は、「explorer.exe
」プロセスの中の「kernel32.dll
」にある CreateDirectoryW
(kernel32!CreateDirectoryW
)をデバッグする。CreateDirectoryW
はフォルダを作成する関数だ。
CreateDirectoryW
のアドレスが欲しいので"dll/win32/kernel32/client/file/dir.c"
のCreateDirectoryW
の関数の中に次の行を追加してリビルドする。
DPRINT1("CreateDirectoryW address: %p\n", CreateDirectoryW);
- ReactOSにGuest Additionsをインストールした後、デバッグモードで起動する。
- デスクトップにフォルダを作成すると「
CreateDirectoryW address: ...
」のような行がデバッグ出力されるはずだ。
(dll/win32/kernel32/client/file/dir.c:102) CreateDirectoryW address: 7C5DA9D0
CreateDirectoryW
のアドレスは0x7c5da9d0
だとわかる。これを使って後でブレークポイントを設定する。
Tab+K
でデバッガに入る。
[7h
Entered debugger on embedded INT3 at 0x0008:0x80958ae8.
- 「
proc list
」でプロセスの一覧を得る。
kdb:> proc list
PID State Filename
0x00000004 In Memory System
0x00000068 In Memory smss.exe
0x00000098 In Memory csrss.exe
0x000000ac In Memory winlogon.exe
0x000000c4 In Memory services.exe
...
0x000001d8 In Memory explorer.exe
...
- 一覧の中から「explorer.exe」を探すと
0x000001d8 In Memory explorer.exe
という行が見つかるので、「explorer.exe
」プロセスにアタッチするために「proc attach 0x1d8
」を実行する。
kdb:> proc attach 0x1d8
Attached to process 0x000001d8, thread 0x000001db.
CreateDirectoryW
のアドレスは0x7c5da9d0
だった。 これを使ってブレークポイントを設定する。
kdb:> bpx 0x7c5da9d0
Breakpoint 0 inserted.
Breakpoint 0 enabled.
kdb:> set condition INT3 first always
- 「
cont
」コマンドで実行を再開し、再びフォルダを作ってみる。ブレークポイントで停止する。
kdb:> cont
Entered debugger on breakpoint #0: EXEC 0x001b:0x7c5da9d0
kdb:>
- 「
bt
」コマンドで呼び出し履歴を取得してみる。
kdb:> bt
Eip:
<kernel32.dll:2a9d0>
Frames:
<shell32.dll:55876>
<shell32.dll:55db3>
<shell32.dll:570a7>
<shell32.dll:48654>
<shell32.dll:4985e>
<shell32.dll:49f70>
<shell32.dll:29af8>
<shell32.dll:70d9b>
<shell32.dll:71e40>
<shell32.dll:71edc>
<shell32.dll:6a456>
<shell32.dll:6a9cf>
...
ここで「kernel32.dll:2a9d0
」やら「shell32.dll:55876
」など、わけのわからないものが 書かれているが、これらは、RosBEの「raddr2line
」というコマンドラインツールを使えば、 ソースコードの行番号として解読できる。
※ うまくいかない場合はビルドされたshell32.dllへの相対パスを指定すること。
解読した結果は次の通り。
<kernel32.dll:2a9d0>: dll/win32/kernel32/client/file/dir.c:92 (CreateDirectoryW)
<shell32.dll:55876>: dll/win32/shell32/shlfileop.cpp:1310 (copy_dir_to_dir)
<shell32.dll:55db3>: dll/win32/shell32/shlfileop.cpp:1502 (copy_files)
<shell32.dll:570a7>: dll/win32/shell32/shlfileop.cpp:1966 (SHFileOperationW)
<shell32.dll:48654>: dll/win32/shell32/droptargets/CFSDropTarget.cpp:90 (CFSDropTarget::_CopyItems)
<shell32.dll:4985e>: dll/win32/shell32/droptargets/CFSDropTarget.cpp:647 (CFSDropTarget::_DoDrop)
<shell32.dll:49f70>: dll/win32/shell32/droptargets/CFSDropTarget.cpp:442 (CFSDropTarget::Drop)
<shell32.dll:29af8>: dll/win32/shell32/CShellLink.cpp:3167 (CShellLink::Drop)
<shell32.dll:70d9b>: dll/win32/shell32/CSendToMenu.cpp:91 (CSendToMenu::DoDrop)
<shell32.dll:71e40>: dll/win32/shell32/CSendToMenu.cpp:439 (CSendToMenu::DoSendToItem)
<shell32.dll:71edc>: dll/win32/shell32/CSendToMenu.cpp:499 (CSendToMenu::InvokeCommand)
<shell32.dll:6a456>: dll/win32/shell32/CDefaultContextMenu.cpp:1020 (CDefaultContextMenu::InvokeShellExt)
<shell32.dll:6a9cf>: dll/win32/shell32/CDefaultContextMenu.cpp:1195 (CDefaultContextMenu::InvokeCommand)
<shell32.dll:5e0ee>: dll/win32/shell32/CDefView.cpp:1391 (CDefView::InvokeContextMenuCommand)
<shell32.dll:5e61b>: dll/win32/shell32/CDefView.cpp:1539 (CDefView::OnContextMenu)
<shell32.dll:d1e84>: dll/win32/shell32/CDefView.cpp:321 (CDefView::ProcessWindowMessage)
<shell32.dll:d1a3e>: sdk/lib/atl/atlwin.h:1565 (CDefView::WindowProc)
ReactOSのデバッグ方法 (Visual Studio + WinDbgの場合)
- VirtualBoxで新しい仮想マシン (Other Windows (32-bit)) を作成し、そこにVSでビルドした
bootcd.iso
を使ってReactOSをインストールする。 - 仮想マシンの状態を保存するために、VirtualBoxを操作して仮想マシンのスナップショットを作成する。
- 仮想マシンをいったん終了し、仮想マシンのシリアルポートの設定をする。シリアルポートCOM1にパイプ(
\\.\pipe\com1
)を設定する。「シリアルポート」の「シリアルポートを有効化」にチェックを入れ、「ポートモード」を「ホストにパイプ」に変更して、「パス/アドレス」に「\\.\pipe\com1
」を指定する。チェックボックス「存在するパイプ/ソケットに接続」のチェックをはずす。 - WinDbgを起動し、「Settings」の「Debugging settings」で「Default symbol path」にシンボルの場所(
msvc_pdb
フォルダの場所)を設定。「ファイル」メニューから「Attach to Kernel」を選択し、「Pipe」「Reconnect」にチェックを入れ、「Port」に「\\.\pipe\com1
」を設定し、「OK」ボタンを押す。 - 仮想マシンを起動する。うまくいけばReactOSをWinDbgでデバッグできる。
日本語WinXPのデバッグ方法(WinDbgを使用)
上記のWin2003は、英語版でしたので、日本語に関する開発をするときには役に立たないことがあります。Microsoftから正式に配布されたXPモードに含まれている日本語WinXPを使えば、日本語の開発が可能です。
- 日本語XPモードの配布ファイル「
WindowsXPMode_ja-jp.exe
」をどこかから拾ってくる(Wayback Machineに配布元URLを入力して下さい)。2022年現在、配布元(https://www.microsoft.com/ja-jp/download/details.aspx?id=8002
)からは直接はダウンロードできないようです。 - 「
WindowsXPMode_ja-jp.exe
」を7-Zipで展開する。ファイル「xpminstl32.msi
」ができる。 - さらに「
msiexec.exe /a xpminstl32.msi targetdir="%~dp0xpmode"
」という内容のバッチファイル(拡張子.bat)を適当な名前で作成、xpminstl32.msi
と同じ場所に置き、ダブルクリックして実行する。 - しばらく待つとファイル
"Windows XP Mode base.vhd"
が展開される。このファイルのプロパティの「読み込み専用」を解除。 - 期限切れのため、システム時計を過去(2008-01-01)に戻して仮想マシンのWinXPにログオンする。
- WinXPの電源を切る前に管理者権限のコマンドプロンプトで「
bootcfg /debug ON /PORT COM1 /ID 1
」を実行する。また、VirtualBox Guest Additionsも忘れずインストールする。 ※ただし、「許可されていないプログラムから……保護する」はチェックを外すこと。 - 仮想マシンの状態を保存するために、VirtualBoxを操作して仮想マシンのスナップショットを作成する。
- 仮想マシンをいったん終了し、仮想マシンのシリアルポートの設定をする。シリアルポートCOM1にパイプ(
\\.\pipe\com1
)を設定する。「シリアルポート」の「シリアルポートを有効化」にチェックを入れ、「ポートモード」を「ホストにパイプ」に変更して、「パス/アドレス」に「\\.\pipe\com1
」を指定する。チェックボックス「存在するパイプ/ソケットに接続」のチェックをはずす。 - WinDbgを起動し、「Settings」の「Debugging settings」で「Default symbol path」に「
srv*
」を設定。「ファイル」メニューから「Attach to Kernel」を選択し、「Pipe」「Reconnect」にチェックを入れ、「Port」に「\\.\pipe\com1
」を設定し、「OK」ボタンを押す。 - 仮想マシンを起動する。うまくいけばWinXPをWinDbgでデバッグできる。
※ COM1に空きがないとか、COM1とは別のポートを使いたい人は、COM2などを代わりに使ったりできるようです。
※ Default symbol pathは、複数のパスが指定できます。「srv*
」はインターネットから読み込まれるパスのようです。 シンボルファイルはフォルダとタイムスタンプなどで厳密に管理されているので、バイナリと合わない間違ったシンボルファイルが読み込まれる恐れはないようです。
デバッグシンボルで逆コンパする
WinDbgでWindowsをデバッグするとき、ターゲットのプロセス(例えばexplorer.exe
)で、どうにかしてターゲットのモジュール(例えばSHLWAPI.DLL
)をうまく読み込ませ、WinDbgのxコマンドで x shlwapi!*
などと、シンボルを読み込ませると、どこかのサーバーからシンボルファイル(*.pdb
)がダウンロードされます。このPDBファイルは有益な関数名や型情報などが含まれており、逆コンパのGhidraやIDA Freeなどで活用できます。SHLWAPI.DLL
を逆コンパで読み込み、SHLWAPI.DLL
に対するPDBファイルのある場所を指定すればいいのです。
逆コンパはグレーですので、そっくりそのまま利用するのは避けるべきでしょう。実際に活用する場合は仕組みの理解とコードの独自性と合理性が求められます。可能ならば名前や順序などを変え、トラップストリートにも注意する必要があります。ただの猿真似ではなく、仕組みを理解して合理的に設計したという事実が望まれます。仕組み上、書いたコードがどうしてもオリジナルと同じ処理になってしまうことはありますが、それは別の話です。最終工程ではナンバリングの名前や無意味な名前は修正した方がいいでしょう。
逆コンパは完璧な技術ではありません。逆コンパも時々、間違えたり、失敗します。逆汗や複数の逆コンパを使い分けるといいでしょう。
オブジェクト指向の逆コンパ
Windowsのシェルと呼ばれる高度なシステムでは、オブジェクト指向のCOM (Component Object Model)が活用されており、COMの逆コンパでは、C++の構造体・クラスに悩まされることになります。
COMの逆コンパにおける基本戦略は次のようになるかと思います。
- ウェブ情報やGrepを活用して、関数情報と型情報を貪欲に収集する。
- 可能ならば、関数の引数の型、戻り値の型、ローカル変数の型を定める。
HRESULT
らしき戻り値は、E_
やS_
で始まる定数に変える。標準メソッドのほとんどの戻り値がHRESULT
型です。- 派生クラスを解析する前に基底クラスを解析する。
- 可能ならば、継承しているインターフェイス(複数可)と仮想テーブル(Vtbl)の情報を把握する。コンストラクタ(ctor)には、仮想テーブルを構築する方法が書いてあるはず。未知のインターフェイスなら仮想テーブルの型情報から試行錯誤して作成する必要がある。メソッドの型情報を作る場合は、呼び出し規約にも注意。
- ターゲットのクラスに必要なインターフェイス・クラスを継承する。順序とオフセットに注意。オフセットなどのCOM情報は
com_apitest
を参照。 - クラスの動的アロケーションがあれば、クラスのバイトサイズがわかる。サイズがわからないときは試行錯誤する。
- 無効なフィールド参照がなくなるまで、もしくは、クラスのバイトサイズまで、未開の地の構造体(クラス)に仮のメンバー変数を入植する。例えば
DWORD dwUnknown0x0, dwUnknown0x4
, etc. などのようにする。配列を使っても構わない。無効なフィールド参照は逆コンパの障害である。後で型と名前を変更できるので、仮の型と名前で構わない。 - 型が間違っていると思われる箇所は、
Retype
(型の変更)してみて試行錯誤する。型を間違っても元に戻せます。 - インターフェイスポインタのオフセットが基底クラスからずれている場合は「シフト構造体」を作成する。例えば
struct CMyStruct : IMyInterface1, IMyInterface2 {...};
に対して0x4
バイト(sizeof(IMyInterface1*)
)ずれたstruct CMyStructShift0x4 : IMyInterface2 {...};
を作成して、インターフェイスIMyInterface2
の標準メソッドのthis
引数の型をCMyStructShift0x4
へのポインタにする。この場合、派生クラスから基底クラスのメンバーを参照した場合、負の参照が生じる(this[-1].m_foo_bar
など)。負の参照を機械的に解決する方法は未解決問題である。今のところ、構造体のレイアウトを把握して、負の参照がどこを指すのか判断するしかない。 - 関数や変数の相互参照(cross-reference; XREF)を活用する。クラスや関数の外側から関数の型がわかる場合がある。
- ハンガリアン記法などの命名規則を意識し、常にわかりやすい命名を心がける。
- 特に注記したいことがある場合は、コメントを追加する。
仮想テーブルについて
例えば、IUnknown
というインターフェイスはC++で堅苦しく書くと次のようになります。
struct IUnknown
{
virtual HRESULT STDMETHODCALLTYPE QueryInterface(REFIID riid, void **ppvObject) = 0;
virtual ULONG STDMETHODCALLTYPE AddRef() = 0;
virtual ULONG STDMETHODCALLTYPE Release() = 0;
};
MS風に書くとこうなります:
struct IUnknown
{
STDMETHOD(QueryInterface)(REFIID riid, void **ppvObject) PURE;
STDMETHOD_(ULONG, AddRef)() PURE;
STDMETHOD_(ULONG, Release)() PURE;
};
これをC言語で厳格に書くと、こうなります:
struct IUnknown;
typedef struct IUnknownVtbl
{
HRESULT (__stdcall *QueryInterface)(struct IUnknown *pThis, REFIID riid, void **ppvObject);
ULONG (__stdcall *AddRef)(struct IUnknown *pThis);
ULONG (__stdcall *Release)(struct IUnknown *pThis);
} IUnknownVtbl;
typedef struct IUnknown
{
IUnknownVtbl *lpVtbl;
} IUnknown;
ここでIUnknownVtbl
がインターフェイスIUnknown
の仮想テーブルです。C言語からは pThis->lpVtbl->AddRef(pThis)
のように呼び出さないといけないことに注意して下さい。これは不便なので、IUnknown_AddRef(pThis)
のようなマクロが付いてくることがあります。
COMでは次のようにC言語とC++の両方で使えるようにインターフェイスを記述できます。
#undef INTERFACE
#define INTERFACE IUnknown
DECLARE_INTERFACE(IUnknown)
{
STDMETHOD(QueryInterface)(THIS_ REFIID, PVOID *) PURE;
STDMETHOD_(ULONG, AddRef)(THIS) PURE;
STDMETHOD_(ULONG, Release)(THIS) PURE;
};
#undef INTERFACE
IDA Freeで独自のインターフェイスを定義する場合は[Local Types]
で仮想テーブルを表す構造体を作成し、さらにそれへのポインタlpVtbl
を含む構造体を作成する必要があります(未確認? 他にいいやり方があるかも)。
実際に開発に参加する
- ReactOS JIRAで不具合の報告を見ることができます。不具合や修正パッチを報告するには、ReactOSアカウントが必要です。アカウントがない場合はこちらから登録下さい。
- GitHubのreactos/reactosにプルリクエストすることでReactOSの修正を提案することができます。ただし、Git/GitHubのアカウント設定で本名とメールアドレスを公開する必要があります。
- もっと開発したいのに権限がないという人は、JIRAに地道に修正パッチを送ってチームの信頼を勝ち取りましよう。
- チャットルームでは気軽に質問をすることができます。ReactOSアカウントが必要です。
ローカルのReactOSを最新に保つ
ときどき、ローカルのReactOSを最新に更新したいことがあるかもしれません。そのときは次のようにコマンドを実行します。
# upstreamという名前で公式ReactOSのリモートリポジトリのURLを登録する。
git remote set-url upstream https://github.com/reactos/reactos.git
# ローカルの変更を元に戻す。
git checkout .
# masterブランチに移動。
git checkout master
# upstreamの変更点を取り込む。
git fetch upstream
# upstreamの変更点を適用する。
git rebase upstream/master
その他
- 複数ファイルから特定のテキストを探す場合は、grepというツールを使うと便利です。
- こちらのページから 高速grepできます。
ASSERT(0)
やDebugBreak()
などを使えば特定の場所でデバッガを停止できます。- TwitterでReactOSのことをつぶやく時は、ハッシュタグ #ReactOS を付けないと妨害工作で見えなくなってしまいます。必ず付けよう、ハッシュタグ。
連絡先
この記事に書いてあることがうまくいかなかったときは、必ずご報告下さい。電子メールで katayama.hirofumi.mz@gmail.com まで。
コメント