私的ちっぷすてきな月々

私的ちっぷす

管理人がふと振り返ったとき「成長したじゃんwwww」と思うための覚え書き。 恥ずかしくても書き直したりしないで、追記する。だから内容に期待してはいけない。 私的ちっぷすてきな日々の正当なる後継にあたる。

デスクトップヒープで遊んだメモ

Windows2000以降にはデスクトップヒープと呼ばれる、特殊なヒープが存在する。 これはUSERあるいはGDI系、つまるところGUIに関するヒープであり、 これが枯渇するとGUI系のAPIが正常に機能しなくなる。

デスクトップヒープはwin32k.sysが管理しているが、 割り当てはデスクトップ単位である。よってデスクトップヒープが枯渇するとき、 影響を受けるのは同一のデスクトップで動作するプロセスということになる。 ちなみに、デスクトップやセッションといった、基本的な概念はここでは説明しない。書くのめどい。 Desktop Heap Overviewを読んでください。

で、デスクトップヒープの状態を把握するにはDesktop Heap Monitorを使う。 展開後、以下のように実行してインストール完了。

>dheapinst.exe -y <シンボルへのパス>
>dheapmon.exe -l

以下のように実行すればアンインストールされるが、 再起動時には勝手にアンインストールされているので基本的には不要。

>dheapmon.exe -u
>dheapinst.exe -r

で、以下ソースをビルドした、シンプルなプロセスを起動ながら結果を観察する。


#include <windows.h>
#pragma comment(lib, "kernel32.lib")
#pragma comment(lib, "user32.lib")

LRESULT CALLBACK WindowProcedure(HWND hwnd, UINT mes, WPARAM wParam, LPARAM lParam) {
  switch(mes) {
  case WM_DESTROY:
    PostQuitMessage(0);
    return 0;
  }
  return DefWindowProc(hwnd, mes, wParam, lParam);
}

int WINAPI WinMain(HINSTANCE hThisInstance, HINSTANCE, LPSTR, int) {
  HWND       hwnd;
  MSG        mes;
  WNDCLASSEX wincl;
  static const char PROJECT_GUID[] = "{39E23A5C-E015-40c2-B341-73EE0C0F9E91}";

  wincl.cbSize        = sizeof(WNDCLASSEX);
  wincl.style         = CS_DBLCLKS;
  wincl.lpfnWndProc   = WindowProcedure;
  wincl.cbClsExtra    = wincl.cbWndExtra = 0;
  wincl.hInstance     = hThisInstance;
  wincl.hIcon         = wincl.hIconSm = LoadIcon(NULL, IDI_APPLICATION);
  wincl.hCursor       = LoadCursor(NULL, IDC_ARROW);
  wincl.hbrBackground = (HBRUSH)(COLOR_3DFACE+1);
  wincl.lpszMenuName  = NULL;
  wincl.lpszClassName = PROJECT_GUID;
  if (!RegisterClassEx(&wincl)) { return 1; }

  hwnd = CreateWindowEx(0,
                        PROJECT_GUID, PROJECT_GUID,
                        WS_OVERLAPPEDWINDOW | WS_VISIBLE,
                        CW_USEDEFAULT, CW_USEDEFAULT,
                        CW_USEDEFAULT, CW_USEDEFAULT,
                        HWND_DESKTOP, NULL, hThisInstance, NULL);
  if (hwnd == NULL) { return 1; }

  while (GetMessage(&mes, NULL, 0, 0) > 0) {
    DispatchMessage(&mes);
    TranslateMessage(&mes);
  }
  return (int)mes.wParam;
}

プロセスが起動するのは通常のデスクトップなので WinSta0\Default の値が変化する。 具体的には1プロセスあたり、 Allocated の値が約12KB増加した。 ただし、デスクトップヒープに関しては、 インターフェースや仕様が公開されていないため、12KBの根拠を知ることは不可能(と思っている)。

じゃあ、こいつを400プロセスほど起動してやろうか。バッチファイルで。

@echo off
cd /d c:\kktools\dheapmon8.1\x86
dheapinst.exe -y c:\WINDOWS\Symbols > test.log
dheapmon.exe -l >> test.log
echo 0Window >> test.log
dheapmon.exe -v -w WinSta0 -d Default >> test.log

for /L %%i in (1, 1, 400) do (
call :do_monitoring %%i 10
start test.exe
)

dheapmon.exe -u >> test.log
dheapinst.exe -r >> test.log
exit /b

:do_monitoring
set /a var=%1 %% %2
if %var%==0 (
echo %1Window >> test.log
dheapmon.exe -v -w WinSta0 -d Default >> test.log
)
exit /b

さすがバッチファイルだ。めんどくさい。

実行すると、約250プロセス程度から起動しなくなった。 ログを確認すると、99%以上デスクトップヒープを利用している。 この状態だと、殆どのウィンドウやメニューが表示できないので、 起動したプロセスを落とす方法を事前に用意しておく必要がある (タスクマネージャをあらかじめ起動しておいたり、使えるならtaskkillとかで)。

個人的に面白いと思ったのは、起動してCreateWindowなどで失敗するのではなく、 USER32.dllのDllMainで失敗するため、プログラム自体が起動しない点。 おそらく、USER32.dllの静的リンクをやめれば起動自体はするものと思われる。

システムプロセスで遊んだメモ

Windowsにはいくつかのシステムプロセスがある。 2000以降にあるシステムプロセスのうち、 smss.exe / csrss.exe / winlogon.exe / lsass.exe について。

ちなみに目的も成果もないのであしからず。メモ。うそもあると思う。 2000 Pro SP4 / 2003 R2 EE 評価版 SP2 / 2008 EE RC0で試している。 サーバOSとクライアントOSで違いがあるかも。

smss.exe

smss.exe は csrss.exe / winlogon.exe ハンドルを保有し、 待機することで生存確認を行っている。 いずれかのプロセスが死亡すると、KeBugCheck(KeBugCheckEx) を使い青画面に移行させる。

smss.exe 自身が死んだ場合は、直ちに何が起こるということはない。 smss.exe はリモートセッションの作成やデバッグイベントの待ち受けを行っているので、 このようなタイミングで問題が表面化することになる。

winlogon.exe

csrss.exe / winlogon.exe はセッションごとに生成される。

セッション 0 に属する winlogon.exe が死亡した場合は、 smss.exe によってシステムが停止させられる。 セッション 0 に属さない winlogon.exe が死亡した場合は、 そのセッションが切断されるのみでシステムの停止には至らない。 このセッションの切断を行っているのは smss.exe ではない。

面白いのは、監視者である smss.exe を先に殺した、あるいはサスペンドさせている場合、 セッション 0 の winlogon.exe が死んでも青画面にならないという点。 この場合、ログオフもされず、とりあえず普通にWindowsを利用できる。

Vistaからはセッション 0 に winlogon.exe は生成されなくなっているため、 winlogon.exe が死亡すると、即ログオフする格好になる。 また、2008では誰もログインしていないセッション(コンソール)の winlogon.exe を殺した場合、自動的に再生成される。 2003 では再生成されないので、コンソールを利用するには再起動が必要になる。

winlogon.exe をサスペンド(ないしはsmss.exeにばれない形で殺害)すると、 Windows+L / Ctrl+Alt+Delete / Ctrl+Shift+Esc などが効かなくなる。もちろん、ログアウトなどもできないし、runas的機能も実行されなくなる。 レジュームすると、サスペンドしている間に受けた指示を実行し始める。 サスペンドしてもプロセスは生きているので、smss.exe のチェックに引っかかることはない。

csrss.exe

csrss.exe / winlogon.exe はセッションごとに生成される。

winlogon.exe の場合、smss.exe が機能停止していればこれを殺害することができたが、 csrss.exe の場合は殺害すると必ず青画面にいく。 GUIで高いCPU負荷をかけると csrss.exe のCPU使用率が高まることからわかるように、 csrss.exe はユーザがWindowsを利用する動作と密着しているためだ。

ゆえに、これをサスペンドさせるとWindowsはフリーズしたようになる。 しかしフリーズしていても、カーネルの動作からはある程度切り離されて設計されているらしく、 システムは生きている。そのため、別ユーザがリモートログオンして、 サスペンドされた csrss.exe をレジュームさせると何事もなく再開する。

lsass.exe

lsass.exe は認証に関する部分を担当している。これが終了すると、 システムは60秒カウントダウンを表示し、カウント後シャットダウンされる。 このカウントを担当しているのはセッション 0 の winlogon.exe であり、 これが機能していないとカウントダウンも機能しない。

Vistaからはセッション 0 に winlogon.exe が生成されない。 警告のために wlrmdr.exe というプロセスが生成されるが、これや winlogon.exe を停止しても再起動がかかる。 再起動を阻止するには wininit.exe をサスペンドすればよい。 レジュームすると再起動する。ただし wininit.exe は死亡すると必ず青画面に行く。

VC++ 2008 Express Edition 覚書 〜 その2

2005 Academic Edition と共存できるようだったので Vista-SP1 にインストールしてみた。 バージョンは 9.0.21022.8 RTM(ただし英語版)。 今回は使用感ではなくて、プロジェクトの既定値を変更する方法をメモ。

プロジェクトの既定のプロパティを変更する

プロジェクトを新規作成するたびに、毎回プロジェクトの設定を変更する箇所があるので、 既定値を変更する。とりあえず個人的に修正が必要な箇所が以下。

項目 Win32 Debug Win32 Release
General > Character Set Use Unicode Character Set Use Unicode Character Set
Not Set Not Set
C++ > General
> Warning Level
Level 3 (/W3) Level 3 (/W3)
Level 4 (/W4) Level 4 (/W4)
C++ > Code Generation
> Runtime Library
Multi-threaded Debug DLL (/MDd) Multi-threaded DLL (/MD)
Multi-threaded Debug (/MTd) Multi-threaded (/MT)
C++ > Preconplied Headers
> Create/Use Preconplied Header
Use Precompiled Header (/Yu) Use Precompiled Header (/Yu)
Create Precompiled Header (/Yc) Create Precompiled Header (/Yc)
Linker > Debugging
> Generate Debug Info
Yes (/DEBUG) Yes (/DEBUG)
Yes (/DEBUG) No

基本的に、プロジェクトのプロパティを変更すると *.vcproj ファイルが更新されるので、 変更された項目名で検索をかけ、変化を調べてスクリプトの代入値を変更するだけ。 ちなみに、作業は管理者権限で行ったほうが楽。具体的には以下。

Character Set

...\Microsoft Visual Studio 9.0\VC\VCWizards\AppWiz\Generic\Application\scripts\1033\default.js の 127・197 行目ぐらいにある config.CharacterSet = charSetUNICODE; を 0 の代入に置き換える。

Warning Level

...\Microsoft Visual Studio 9.0\VC\VCWizards\1033\common.js の 672・699 行目ぐらいにある CLTool.WarningLevel = WarningLevel_3; を 4 の代入に置き換える。

Runtime Library

...\Microsoft Visual Studio 9.0\VC\VCWizards\AppWiz\Generic\Application\scripts\1033\default.js の 137 行目ぐらいにある CLTool.RuntimeLibrary = rtMultiThreadedDebugDLL; を 1 の代入に置き換える。 同じく 206 行目ぐらいにある CLTool.RuntimeLibrary = rtMultiThreadedDLL; を 0 の代入に置き換える。

Create/Use Preconplied Header

...\Microsoft Visual Studio 9.0\VC\VCWizards\1033\common.js の 781 行目ぐらいにある CLTool.UsePrecompiledHeader = pchUseUsingSpecific; を 1 の代入に置き換える。

Generate Debug Info(Release)

...\Microsoft Visual Studio 9.0\VC\VCWizards\AppWiz\Generic\Application\scripts\1033\default.js の 252 行目ぐらいにある LinkTool.GenerateDebugInformation = true; を false の代入に置き換える。

プロジェクトの既定のソースファイルを変更する

これも好みで変更する。

...\Microsoft Visual Studio 9.0\VC\VCWizards\AppWiz\Generic\Application\templates\1033 内にあるファイルを変更すればおk。

個人的には、タブ文字と空白文字が入り混じっていたり、 タブ幅を変更すると発狂しそうなぐらい見苦しくなるので、 今までこのテンプレートを残して利用した記憶がない。 あと、アイコンが 24KB*2 とか異常にでかいのも理解に苦しむ。

さっくり作る語るXP(32bit)でのドライバ開発環境

準備

必要なものを以下に示す。この記事は仮想マシン のテンプレートを開発環境に仕立て上げる ための手順であるので、殆ど自分用のメモになってしまうが、そこは理解されたし。 ちなみに、無料でダウンロードできるものに関しては、2007年11月23日現在で最新のものを利用している。

導入

セットアップ作業の順序と内容のメモ。特に記さない限り、フルパッケージインストールで、 インストールディレクトリはインストーラの初期値を使用している。

  1. WDKのインストール
    • [Windows Driver Kits] > [WDK 6001] という開発環境がインストールされる
    • [Windows Driver Kits Documentation] > [WDK 6001.070817] > [Help] というリファレンスがインストールされる
  2. SDKのインストール
    • [Microsoft Windows SDK] という開発環境がインストールされる
  3. MSDNのインストール
    • [Microsoft Developer Network] というリファレンスがインストールされる
  4. Visual Studio 2005 Academic Edition
    • C++ / C# のみ選択(個人的に使うのがこの言語、というだけの理由)。
    • これを完了した時点でWin32APIのプログラミングが可能になっている
  5. MSDNリファレンスの統合(利便性のため)
    1. [Microsoft Visual Studio 2005] > [Microsoft Visual Studio 2005 ドキュメント]起動
    2. [Visual Studio 2005 連結ヘルプ コネクション マネージャ]というリンクをクリック
    3. VSCC に含めることのできるコレクション をすべてチェック。以下、その項目
      • Windows Driver Development Kit
      • Microsoft Windows SDK
      • Windows SDK Collection
      • Microsoft .NET Framework 3.0 SDK
      • Microsoft .NET Framework 3.0 SDK for Visual Studio 2005
    4. [VSCCの更新] をクリック
    5. ドキュメントを再起動
  6. Microsof Update でサービスパック等をあてる
  7. [Visual Studio Registration] > [Windows SDK と Visual Studio 2005 の統合] を実行(利便性のため)
  8. Visual Studioのパス調整
    • プロジェクトディレクトリをDDK以下に移動(build コマンド使用時の利便性のため)
    • インクルードディレクトリの追加(以下は追加後。順番が違うとコンパイルエラーになることも多し!)
      • $(VCInstallDir)include
      • $(VCInstallDir)atlmfc\include
      • $(FrameworkSDKDir)include
      • C:\WinDDK\6001\inc\api
      • C:\WinDDK\6001\inc\crt
      • C:\WinDDK\6001\inc\ddk
      • C:\WinDDK\6001\inc\mfc42
      • C:\WinDDK\6001\inc\wdf\kmdf\1.7
      • C:\WinDDK\6001\inc\wdf\umdf\10
      • C:\Program Files\Microsoft SDKs\Windows\v6.0\Include
      • C:\Program Files\Microsoft SDKs\Windows\v6.0\Include\gl
    • ライブラリディレクトリの追加
      • C:\WinDDK\6001\lib\wxp\i386
      • wxp とかはターゲット環境に合わせて調整してください。詳細は下表。
        略記 wlh wnet wxp w2k
        意味 Vista & 2008 2003 XP 2000
  9. build コマンド実行時になぜかresource.rc等の "afxres.h" を解決できないので、 すべてのプロジェクトについて以下のとおり修正
    
    //#include "afxres.h"
    #include <winres.h>
    #ifndef IDC_STATIC
    #define IDC_STATIC (-1)
    #endif
    

あとは必要なツールを入れたりなんなり、ご自由に。 一応、テスト用のプロジェクトを置いておきます。 たのしくつかってね。なかよくつかってね。


(追記)Boost 1.34.1もセットアップ。基本的には外部リンクで説明してある手順でOK。 Boostのアーカイブを全角を含むパスに置くとbjamがエラーを返すこと、 ビルドに2時間とかかかること、展開後アーカイブが2GB、 ビルド後のライブラリ群が2GB使うことに注意。

webスクラップ 〜 Security
  1. ITセキュリティのアライ出し
  2. Windows 悪意のあるソフトウェアの削除ツール: これまでの成果と悪意のあるソフトウェアの傾向
  3. マイクロソフト セキュリティ インテリジェンス レポート (2007 年 1 月 - 6 月) - 日本語
  4. ホワイトハッカー道場

Windowsのセキュリティに関する情報各種。私の中に Windowsプログラミング → ドライバ → Windows内部 → Windowsセキュリティといった興味の流れがあるらしい。 実際ネットワーク系の知識が未だに殆どなくて、ホワイトハッカー道場 の後半部分がまるで理解できなかった。しかも理解しようという気になれないときている。

explorer /select, path

ある特定のファイルを選択した状態でウィンドウを開くには、 [ファイル名を指定して実行] やコマンドプロンプトで表題のように指定する。 プログラムでは次のように記述すればよい。


ShellExecute(NULL, NULL, _T("explorer.exe"), _T("/select,path"), NULL, SW_SHOWNORMAL);

この方法は、2000/XP/Vista(そしてたぶん9x系においても)問題なく動作する。 それ以外にも /e /n /root path というオプションがある(外部リンク参照)。

機能としては問題ないのだが、Vistaでは少し挙動が変わる。 2000/XP ではフォルダオプションで明示されていない限り、 このアクションではexplorerの2つ目のプロセスは自動終了する。 しかしVistaでは explorer プロセスが残り続けるのだ (ただし3プロセスにはならない)。

まぁ大きな問題ではないのだが、タスクマネージャ(taskmgr.exe)の同様の機能ではプロセスは残らない。 つまり、正規の方法ではないということだ。 taskmgr.exe を OLLYDBG にかけてみると SHOpenFolderAndSelectItems なるAPIを利用しているらしいことがわかる。

XPから追加された関数のようだ。以下のコードのように使用する。


bool OpenDirAndSelectItem(LPCTSTR pFullPath)
{
  if (FAILED(::CoInitialize(NULL))) { return false; }

  bool bRet = false;
  LPSHELLFOLDER pDesktopFolder = NULL;
  if (FAILED(::SHGetDesktopFolder(&pDesktopFolder))) { goto END; }

  OLECHAR olePath[MAX_PATH];
#ifdef UNICODE
  ::lstrcpyn(olePath, pFullPath, MAX_PATH);
#else
  ::MultiByteToWideChar(CP_ACP, MB_PRECOMPOSED, pFullPath, -1, olePath, MAX_PATH);
#endif /* UNICODE */

  ULONG chEaten      = 0;
  ULONG dwAttributes = 0;
  LPITEMIDLIST pidl  = NULL;
  if (FAILED(pDesktopFolder->ParseDisplayName(NULL, NULL, olePath,
                                              &chEaten, &pidl, &dwAttributes) ))
  {
    goto END;
  }

  LPITEMIDLIST pidl_d = NULL;
  if ( !::PathRemoveFileSpecW(olePath)
    || FAILED(pDesktopFolder->ParseDisplayName(NULL, NULL, olePath,
                                               &chEaten, &pidl_d, &dwAttributes) ))
  {
    goto END;
  }

  LPCITEMIDLIST cpidl   = pidl;
  LPCITEMIDLIST cpidl_d = pidl_d;
  bRet = (SUCCEEDED(::SHOpenFolderAndSelectItems(cpidl_d, 1, &cpidl, 0)));

END:
  if (pDesktopFolder) { pDesktopFolder->Release(); }
  ::CoUninitialize();
  return bRet;
}

void unittest()
{
  _ASSERTE(OpenDirAndSelectItem(_T("C:\\windows\\notepad.exe")));
  _ASSERTE(OpenDirAndSelectItem(_T("F:\\dev\\全角テスト環境\\貼美乳表")));    // file
  _ASSERTE(OpenDirAndSelectItem(_T("C:\\windows\\")));
  _ASSERTE(OpenDirAndSelectItem(_T("C:\\windows")));
  _ASSERTE(OpenDirAndSelectItem(_T("C:\\")));
  _ASSERTE(OpenDirAndSelectItem(_T("C:")));
  _ASSERTE(!OpenDirAndSelectItem(_T("Cjunkmetal")));    // nothing
  _ASSERTE(!OpenDirAndSelectItem(NULL));
}

と、コード書いて単体テストしてたら気づいたのだけれど、この構成だと C:\ とかを通すのに細工が必要になることに気づいた。 だもので、やる気なくして上の単体テストは通りませんがそのままです><


おまけ。QueryFullProcessImageName は Vista から追加されたAPI。 同じくtaskmgr.exeから出てきたものだが便利そうなのでメモ。

仮想メモリと3GBオプション

今日はXPのタスクマネージャを勉強してみよう。

プロセスタブ

[プロセス]タブでは複数の項目を表示できるが、中でも[メモリ使用量]と[仮想メモリ サイズ]は判りづらい。 前者は今まさに利用されているアクティブなメモリの量でワーキングセットとも呼ばれる。 後者は利用されてはいるがアクセスされていないと判断されているメモリの量で、 そのうちにpagefile.sysに追い出されてしまう。

ここでいうメモリとは、仮想メモリを指す。仮想メモリは タスクマネージャ[システム情報] ー [システム概要] で確認できるが、表現に使われる単語が一貫していない。 とりあえず、以下のように覚えておけばよい。

  1. 仮想メモリ≒物理メモリ+pagefile.sys
  2. 仮想メモリ=コミットチャージ(by タスクマネージャ・ステータスバー) =コミットチャージ制限値(by タスクマネージャ・[パフォーマンス]タブ) =ページファイルの空き容量(by システム情報)

ここで重要なのはユーザーモードプロセスからは物理メモリとpagefile.sysを区別する手段はなく、 メモリの確保は「仮想メモリ」というレイヤでのみ処理される。 malloc / new / HeapAlloc ... すべて「仮想メモリ」から確保される。

確保の上限はどうなっているのか。単純に仮想メモリ(コミットチャージ)がWindowsとしての限界だ。 物理メモリを2GB・pagefile.sysを3GBならば5GBまで malloc できる。

ただし、ひとつのユーザーモードプロセスからは2GBまでしか仮想メモリを認識できない。 2GB+2GB+1GBの3プロセス目でようやく malloc == NULL になる。 たとえば、以下のプログラムは実行すると約2GB確保して終了する。


#include <windows.h>
#include <cstdlib>
#include <cstdio>
int main()
{
  for (int i=0; malloc(1024 * 1024); ++i)
  {
    printf("\r%d MB", i);
  }
  puts("オワタ");
  ::Sleep(INFINITE);
  return 0;
}
2GB

そこで出てくるのが boot.ini の3GBオプションだ。有効にするには [operating systems] 以下の起動オプションレコードに /3GB と追記して再起動すればよい。 詳細な意味は以下のとおりだ。 ちなみにXP-Home(SP2)でも機能する。

/3GB
ユーザーモードのプログラムで 3 GB のメモリにアクセスできるようにします。 このフラグを指定しない場合は、Windows 2000, Windows NT がユーザーモードのプログラムに通常割り当てる 2 GB となります。この指定により、カーネル メモリの先頭が 3 GB の位置に移動します。 このスイッチは、Windows 2000 Advanced Server, Windows 2000 Datacenter Serve, または、Service Pack 3 以降を適用した Windows NT Server Enterprise Edition でのみ使用できます。

さらにアプリケーション側でも設定が必要だ。 リンカオプション /LARGEADDRESSAWARE を有効にしてビルドする。 これで /LARGEADDRESSAWARE を指定されたプロセスは3GBまで利用できるようになっている。

3GB

既存のファイルにこの /LARGEADDRESSAWARE フラグが立っているか否かを確認するには dumpbin.exe を使う。 FILE HEADER VALUES の characteristics に Application can handle large (>2GB) addresses という一文が含まれていれば有効である。

>dumpbin /headers VC8TestProject.exe
Microsoft (R) COFF/PE Dumper Version 8.00.50727.762
Copyright (C) Microsoft Corporation.  All rights reserved.


Dump of file VC8TestProject.exe

PE signature found

File Type: EXECUTABLE IMAGE

FILE HEADER VALUES
             14C machine (x86)
               5 number of sections
        46F46D0E time date stamp Sat Sep 22 10:17:02 2007
               0 file pointer to symbol table
               0 number of symbols
              E0 size of optional header
             122 characteristics
                   Executable
                   Application can handle large (>2GB) addresses
                   32 bit word machine

OPTIONAL HEADER VALUES
  (省略)

既存のファイルの有効無効を切り替えることもできる。 editbin.exe を使い、リンカオプションを渡してあげるだけだ。

>editbin /LARGEADDRESSAWARE non_large_handled.exe
Microsoft (R) COFF/PE Editor Version 8.00.50727.762
Copyright (C) Microsoft Corporation.  All rights reserved.

/GL コンパイラ オプションを指定して生成されたファイルに対しては、EDITBIN を使用できません。/GL 指定で生成されたバイナリ ファイルの変更は、再コンパイルとリンクで行う必要があります。 とあるが、そんなことも無いようで、成功した。

個人的には常時 /3GB の /LARGEADDRESSAWARE でいいような気もするが、 MSがそうしていないということには何かしらの理由があるのだろう。多分。

アプリケーション拡張

あるWin32のGUIプログラムがあるとしよう。このプログラムにすこしだけ手を加えて、 「実行ファイルのあるフォルダを開く」メニュー機能を追加したい。 しかし、これはソースが公開されていないか、 公開されているとしても手を加えてビルドするのは大変そうだ。

あなたがアセンブラに抵抗がなければ、デバッガで追いかけて、 適当な空きスペースにアセンブラコードを埋め込んだりするかもしれない。 しかしここはWin32APIのカテゴリだ。よろしい、ならばAPIだ。

まずは Resource Hacker などでメニューを追加してしまおう。 メニューはプログラムごとに管理の仕方が異なる可能性があるので、 動的に変化させるのは面倒そうだ。

書き換え

"&Process Explorer Folder"というキャプションのメニューを追加している。 IDには衝突してなさそうな数値 (今回は40700) を適当に振る。

実行

当然、表示はされるが 40700 のコマンドは処理されていないはずなので、実行しても何も起こらない。 今度は、その処理を追加する。起動中のこのプログラムに対して、単純に SetWindowsHookEx でメッセージ処理に割り込みすればよい。


LRESULT CALLBACK GetMsgProc(int code, WPARAM wParam, LPARAM lParam)
{
  const MSG* p = (MSG*)lParam;
  if (code < 0)                   goto END;
  if (!::IsWindow(p->hwnd))       goto END;
  if (p->message != WM_COMMAND)   goto END;
  if (LOWORD(p->wParam) != 40700) goto END;

  TCHAR binPath[MAX_PATH];
  bool bRet = (0 != ::GetModuleFileName(NULL, binPath, MAX_PATH));
  bRet = bRet && ::PathRemoveFileSpec(binPath);
  if (bRet) { ::ShellExecute(p->hwnd, NULL, binPath, NULL, NULL, SW_SHOWNORMAL); }

END:
  return ::CallNextHookEx(NULL, code, wParam, lParam);
}

プログラム的には素直。ただし SetWindowsHookEx でシステムワイドに展開する必要はないので、 DLLを CreateRemoteThread で注入する。

注入したDLLの DllMain で SetWindowsHookEx をかけたいところだが、 これを呼び出したスレッドは生存し続けれなければならない。 DllMain を呼んだスレッドはリモートから作成された一時的なものだし、 DllMain で待機関数を使うことはできない。 ならば DllMain で CreateThread して、そのなかでフック→サスペンドすればいい。


BOOL WINAPI DllMain(HMODULE hInstDLL, DWORD fdwReason, LPVOID lpvReserved)
{
  switch (fdwReason)
  {
  case DLL_PROCESS_ATTACH:
    ::DisableThreadLibraryCalls(hInstDLL);
    ::CloseHandle(::CreateThread(NULL, 0, &HookAndSuspendThread, hInstDLL, 0, NULL));
    break;
  case DLL_THREAD_ATTACH: break;
  case DLL_THREAD_DETACH: break;
  case DLL_PROCESS_DETACH:break;
  }
  return TRUE;
}

最後に SetWindowsHookEx に渡すべきスレッドIDが特定できないので、 CreateToolhelp32Snapshot(TH32CS_SNAPTHREAD, 0) でPIDが一致するすべてのスレッドに対して、 メッセージフックをかけていく。 どうせアンフックなどする必要がないのだから、ハンドルは捨ててしまおう。


DWORD WINAPI HookAndSuspendThread(LPVOID lpThreadParameter)
{
  HMODULE hDll = (HMODULE)lpThreadParameter;
//DWORD dwPID  = GetProcessIdOfHandle(::GetCurrentProcess());   // GetProcessIdOfHandle == GetProcessId
  DWORD dwPID  = ::GetCurrentProcessId();   // これでいいじゃん!
  HANDLE hSnap = ::CreateToolhelp32Snapshot(TH32CS_SNAPTHREAD, 0);
  if (hSnap == INVALID_HANDLE_VALUE) { return 0; }

  THREADENTRY32 te;
  te.dwSize = sizeof(te);
  for (BOOL  bRet = ::Thread32First(hSnap, &te);
       bRet; bRet = ::Thread32Next (hSnap, &te))
  {
    if (te.th32OwnerProcessID == dwPID)
    {
      ::SetWindowsHookEx(WH_GETMESSAGE, &GetMsgProc, hDll, te.th32ThreadID);
    }
  }
  ::CloseHandle(hSnap);
  ::PostMessage(HWND_BROADCAST, WM_NULL, 0, 0);
  ::SuspendThread(::GetCurrentThread());
  return 0;
}

DLLを強制的にアンロードすると、プログラムは例外を発生させ、強制終了する。 が、まあ通常そのような動作はさせないだろう。

最後に、今回扱ったソースとバイナリを置いておく。 px_extension.zip

webスクラップ 〜 Windows の管理: Windows Vista カーネルの内部
  1. 第 1 部 -- TechNet Magazine, February 2007
  2. 第 2 部 -- TechNet Magazine, March 2007
  3. 第 3 部 -- TechNet Magazine, April 2007

ユーザーとしてはVistaは嫌いだけど、開発もする人間としては興味は尽きない。 [イメージのランダム化を有効にする (/DYNAMICBASE)] の Address Space Load Randomization については3部で扱ってます。全部はまだ読めてないんですけどねー。


(追記)読んだ。( ゚д゚)ウッウー さすがにカーネルのメジャーバージョンが違うだけあって、 かなり変更が大掛かり。私のきな臭いアプリにも影響がありそうな部分があったり。 ブートの変更はもうちっとよく勉強しておいたほうが良いかな。

VC++ 2008 Express Edition 覚書
  • コンパイラのバージョンは15(>cl /?)。
  • 標準でWindows SDKの一部がインストールされる。なにもしなくてもWin32の開発ができる。
  • /MD ビルドしてできたバイナリは、VC8が入っている環境でもSxSで動かせない。
  • リソーススクリプトは扱えない(従来と同じ)。
  • wcoutで全角文字を表示しようとすると std::wcout.bad() == true になり以降出力できない問題はそのまま。
  • /OPT:NOWIN98 を指定すると 警告LNK4224を発生させる。
  • Just In Time デバッグはできない。
  • プロジェクトのプロパティにいくつか設定項目が追加されている。
    • [リンカ] > [マニフェスト ファイル] にいくつかの項目が追加されている。 これまで手書きで指定してきた asInvoker などをGUIで指定できるようになった。
    • [リンカ] > [詳細] > [ランダム化されたベース アドレス] を [イメージのランダム化を有効にする (/DYNAMICBASE)]を指定すると、 Vista以降での実行時にexeのベースアドレスが 0x40000 固定ではなくなる。
    • [リンカ] > [詳細] > [データ実行防止(DEP)] という項目がある。 [イメージは DEP と互換性がある (/NXCOMPAT)]がデフォルトだが、 私のCPUはDEPに対応していないのでよくわからない。
マニフェストとかのメモ

XP以前ではほとんど関係の無い話題。 Vistaで「必ず」管理者権限が必要な場合はマニフェストで昇格を要求させる。

dependency タグがXP風のコントロールを使うための指示で、 ms_asmv3:trustInfo タグが今回追加された昇格を要求するためのタグ。

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0">
<assemblyIdentity
  version="1.0.0.0"
  processorArchitecture="X86"
  name="ファイル名"
  type="win32"
/>
<description>ファイルの説明</description>
<dependency>
  <dependentAssembly>
    <assemblyIdentity
      type="win32"
      name="Microsoft.Windows.Common-Controls"
      version="6.0.0.0"
      processorArchitecture="X86"
      publicKeyToken="6595b64144ccf1df"
      language="*"
    />
  </dependentAssembly>
</dependency>
<ms_asmv3:trustInfo xmlns:ms_asmv3="urn:schemas-microsoft-com:asm.v3">
  <security>
    <requestedPrivileges>
<!--
      <requestedExecutionLevel level="asInvoker" /> 
      <requestedExecutionLevel level="highestAvailable" /> 
      <requestedExecutionLevel level="requireAdministrator" /> 
-->
      <requestedExecutionLevel level="requireAdministrator" />
    </requestedPrivileges>
  </security>
</ms_asmv3:trustInfo>
</assembly>
  • ファイル名.拡張子.manifest という名前のファイルで保存する。
  • 文字コードは UTF-8 で保存する。BOMはあっても無くてもよい。
  • 以下バイナリファイルへの埋め込み方。

直接 rcファイルに書き込む


1 RT_MANIFEST DISCARDABLE
{
"<?xml version="1.0" encoding="UTF-8" standalone="yes"?>\n"
〜〜省略〜〜
}

rcファイルに参照を書き込む


1 RT_MANIFEST DISCARDABLE "〜〜.manifest"

ビルド後のバイナリに埋め込む

[構成プロパティ] > [ビルド イベント] > [ビルド後のイベント] の [コマンド] に


"$(DevEnvDir)..\..\SDK\v2.0\bin\mt.exe" -manifest "$(ProjectDir)$(TargetName).exe.manifest" -outputresource:"$(TargetDir)$(TargetFileName)";#1

権限昇格が必要なアプリは、ビルドしてもVisualStudio上から起動することが出来ないので、 この3番目の方法で、Releaseビルド時のみ埋め込んでしまうのが一番使いやすいかなと。

その他雑多にメモしておく。

  • アプリケーションの名前、もしくはリソースに "setup""install" という文字列(大文字小文字を区別しない)が含まれていた場合、自動的に昇格が必要なアプリと判断される。
  • 権限昇格が必要なアプリに対するCreateProcessファミリは必ず失敗し、ERROR_ELEVATION_REQUIRED(740L) を返す。 ちなみに、この ERROR_ELEVATION_REQUIRED は最近のSDKでないと定義されていない。
  • 権限昇格が必要なアプリを起動したいときは、ShellExecute / ShellExecuteEx を使う。 第二引数、もしくはSHELLEXECUTEINFO構造体のメンバ lpVerb"runas" を指定すると、必ず昇格用ダイアログが表示される。 このダイアログをキャンセルした場合、APIは失敗し、ERROR_CANCELLED(1223L) を返す。
  • 権限昇格のダイアログを表示しているのは consent.exe というプロセス。 所詮はただのウィンドウなので、このプロセスを細工すれば昇格ダイアログを何とかできるんじゃないかと思うが、 M$がそんなことを許すつくりをしているとは思えないので未調査。
  • (追記)このウィンドウは只者ではないようだ。SPY++ (そういえばSDKのものは UISpy に替わっているね) で観たところ、子ウィンドウを一つも持たない。 クラス名は $$$Secure UAP Dummy Window Class For Interim Dialog となっていたが、検索しても情報ほとんど無い。まあ、だれもこのアプローチから成果を出していないからだろう。
  • (追記)requestedExecutionLevelには3種類の値を設定できる。意味は以下の通りらしく、 ファイル名監視やスタートアップの登録時に、本来必要でない管理者権限を要求される場合には asInvoker を指定するとよいようだ。
    1. asInvoker: 実行ユーザの普通の権限で実行
    2. highestAvailable: 実行ユーザの最高の権限で実行
    3. RequireAdministrator: 管理者権限で実行
LoadLibraryの速度

#define UNICODE
#define _UNICODE
#include <windows.h>
#include <tchar.h>
#include <crtdbg.h>
#include <iostream>
#include <iomanip>

// クロック数カウンタ
class ClockCounter
{
  LONGLONG begin;
  LONGLONG* const clock;

  LONGLONG GetClockCount() const
  {
    LARGE_INTEGER cycles;
    __asm
    {
      rdtsc
      mov cycles.LowPart,  eax
      mov cycles.HighPart, edx
    }
    return cycles.QuadPart;
  }

public:
  explicit
  ClockCounter(LONGLONG* clock)
    : clock(clock)
  {
    _ASSERTE(clock);
    this->begin = this->GetClockCount();
  }
  ~ClockCounter()
  {
    *this->clock = this->GetClockCount() - this->begin;
  }
};

// 時間カウンタ
class TimeCounter
{
  LARGE_INTEGER freq;
  LARGE_INTEGER begin;
  LARGE_INTEGER end;
  double* const time;

public:
  explicit
  TimeCounter(double* time)
    : time(time)
  {
    _ASSERTE(time);
    BOOL b = ::QueryPerformanceFrequency(&this->freq);
    _ASSERTE(b);UNREFERENCED_PARAMETER(b);
    ::QueryPerformanceCounter(&this->begin);
  }
  ~TimeCounter()
  {
    ::QueryPerformanceCounter(&this->end);
    *this->time = (double)(end.QuadPart - begin.QuadPart) * 1000 / freq.QuadPart;
  }
};

// テスト用関数
template <class C, typename T>
inline void f(LPCTSTR dllname, T* result)
{
  C obj(result);
  for (int i=0; i<1; ++i)
  {
    ::FreeLibrary(::LoadLibraryEx(dllname, NULL, 0));
  }
}


int main()
{
  // コメントを切り替えて計測

  //*/
  double time = 0;
  f<TimeCounter>(_T("VC8TestDll.dll"), &time);
  std::cout << std::setw(10) << std::right << time << "ms" << std::endl;
  f<TimeCounter>(_T("VC8TestDll_fast.dll"), &time);
  std::cout << std::setw(10) << std::right << time << "ms" << std::endl;

  /*/

  LONGLONG clock = 0;
  f<ClockCounter>(_T("VC8TestDll.dll"), &clock);
  std::cout << std::setw(10) << std::right << clock << "clocks" << std::endl;
  f<ClockCounter>(_T("VC8TestDll_fast.dll"), &clock);
  std::cout << std::setw(10) << std::right << clock << "clocks" << std::endl;
  //*/

  return 0;
}

// EOF

以上のコードを利用して、DLLの再配置発生と、LoadLibraryEx のフラグ DONT_RESOLVE_DLL_REFERENCES / LOAD_LIBRARY_AS_DATAFILE の速度的な変化について調べる。 VC8TestDll.dllVC8TestDll_fast.dllは同じもので、 DllMainCRTStartup で TRUE を返すだけであるが、前者はベースアドレスの調整により ntdll.dll と衝突し、再配置される。

DLL(単位:ミリ秒) VC8TestDll.dll VC8TestDll_fast.dll
フラグなし(LoadLibrary) 17.093000 1.826770
DONT_RESOLVE_DLL_REFERENCES 1.383420 0.671035
LOAD_LIBRARY_AS_DATAFILE 0.829714 0.627733
DLL(単位:クロック数) VC8TestDll.dll VC8TestDll_fast.dll
フラグなし(LoadLibrary) 52127140 8319108
DONT_RESOLVE_DLL_REFERENCES 3637396 1692084
LOAD_LIBRARY_AS_DATAFILE 2428912 1816736

ヤバイ。再配置ヤバイ。まじでヤバイよ、マジヤバイ。再配置ヤバイ。 まず遅い。もう遅いなんてもんじゃない。超遅い。遅いとかっても「"再"配置だから2倍くらい?」 とか、もう、そういうレベルじゃない。 何しろ10倍。スゲェ!

webスクラップ 〜 雑多
  1. ReBaseImage - ベースアドレス調整の指針
  2. 「Windows SDKのコンパイラは /analyze が付いてるよ。」という書き込みからの情報

DLLのベースアドレスは調整されてリリースされるべきなのは有名な話。 その指針がMSDNに書いてあることを知った。ごめんね、おかあさん、いままで てきとうにせっていしてたから、ごめんね。

VCのマイナーな定義済みマクロ

VCにはいくつか面白い定義済みマクロがある。 特に __COUNTER__ はソースコード上に配置されるごとにインクリメントされるので、 一意の数値に置換される。下のコードの実行結果を参照のこと。


#include <iostream>
#include <string>
#define UNIQUE_NUMBER()           __COUNTER__
#define OPERATOR(t)               (UNIQUE_NUMBER(), t)
#define OPERATOR_2(a, b)          OPERATOR(a)OPERATOR(b)
#define OPERATOR_3(a, b, c)       OPERATOR(a)OPERATOR_2(b, c)
#define OPERATOR_4(a, b, c, d)    OPERATOR(a)OPERATOR_3(b, c, d)
#define OPERATOR_5(a, b, c, d, e) OPERATOR(a)OPERATOR_4(b, c, d, e)

struct Function
{
  template <typename T>
  Function operator ()(const T& arg) const
  {
    std::cout << arg << "\n";
    std::cout << "\tsizeof       = " << sizeof(arg)   << "\n";
    std::cout << "\t__FUNCTION__ = " << __FUNCTION__  << "\n";
    std::cout << "\t__FUNCDNAME__= " << __FUNCDNAME__ << "\n";
    std::cout << "\t__FUNCSIG__  = " << __FUNCSIG__   << "\n";
    std::cout << "\n";
    return *this;
  }

  template <typename T>
  Function operator ()(int counter, const T& arg) const
  {
    std::cout << "__COUNTER__ = " << counter << "\n";
    return Function()(arg);
  }
};

int main(int argc, char* argv[])
{
  Function()
    (UNIQUE_NUMBER(), &argc)(UNIQUE_NUMBER(), argc)(UNIQUE_NUMBER(), &main)
    OPERATOR(argv)OPERATOR(argv[0])OPERATOR(argv[0][0])
    OPERATOR_5(std::string("String"), 0.0, "char*", L'w', static_cast<void*>(&argc));
  return 0;
}

__COUNTER__ = 0
0012FF3C
        sizeof       = 4
        __FUNCTION__ = Function::operator ()
        __FUNCDNAME__= ??$?RPAH@Function@@QBE?AU0@ABQAH@Z
        __FUNCSIG__  = struct Function __thiscall Function::operator ()<int*__w64 >(int *__w64 const &) const

__COUNTER__ = 1
1
        sizeof       = 4
        __FUNCTION__ = Function::operator ()
        __FUNCDNAME__= ??$?RH@Function@@QBE?AU0@ABH@Z
        __FUNCSIG__  = struct Function __thiscall Function::operator ()<int>(const int &) const

__COUNTER__ = 2
0043D5D6
        sizeof       = 4
        __FUNCTION__ = Function::operator ()
        __FUNCDNAME__= ??$?RP6AHHQAPAD@Z@Function@@QBE?AU0@ABQ6AHHQAPAD@Z@Z
        __FUNCSIG__  = struct Function __thiscall Function::operator ()<int(__cdecl *)(int,char *[])>(int (__cdecl *const &)(int,char *[])) const

__COUNTER__ = 3
00A11FB0
        sizeof       = 4
        __FUNCTION__ = Function::operator ()
        __FUNCDNAME__= ??$?RPAPAD@Function@@QBE?AU0@ABQAPAD@Z
        __FUNCSIG__  = struct Function __thiscall Function::operator ()<char**>(char **const &) const

__COUNTER__ = 4
c:\Users\normal\Desktop\__unittest__\__unittest__\debug\__unittest__.exe
        sizeof       = 4
        __FUNCTION__ = Function::operator ()
        __FUNCDNAME__= ??$?RPAD@Function@@QBE?AU0@ABQAD@Z
        __FUNCSIG__  = struct Function __thiscall Function::operator ()<char*>(char *const &) const

__COUNTER__ = 5
c
        sizeof       = 1
        __FUNCTION__ = Function::operator ()
        __FUNCDNAME__= ??$?RD@Function@@QBE?AU0@ABD@Z
        __FUNCSIG__  = struct Function __thiscall Function::operator ()<char>(const char &) const

__COUNTER__ = 6
String
        sizeof       = 32
        __FUNCTION__ = Function::operator ()
        __FUNCDNAME__= ??$?RV?$basic_string@DU?$char_traits@D@std@@V?$allocator@D@2@@std@@@Function@@QBE?AU0@ABV?$basic_string@DU?$char_traits@D@std@@V?$allocator@D@2@@std@@@Z
        __FUNCSIG__  = struct Function __thiscall Function::operator ()<class std::basic_string<char,struct std::char_traits<char>,class std::allocator<char> >>(const class std::basic_string<char,struct std::char_traits<char>,class std::allocator<char> > &) const

__COUNTER__ = 7
0
        sizeof       = 8
        __FUNCTION__ = Function::operator ()
        __FUNCDNAME__= ??$?RN@Function@@QBE?AU0@ABN@Z
        __FUNCSIG__  = struct Function __thiscall Function::operator ()<double>(const double &) const

__COUNTER__ = 8
char*
        sizeof       = 6
        __FUNCTION__ = Function::operator ()
        __FUNCDNAME__= ??$?R$$BY05$$CBD@Function@@QBE?AU0@AAY05$$CBD@Z
        __FUNCSIG__  = struct Function __thiscall Function::operator ()<const char[6]>(const char (&)[6]) const

__COUNTER__ = 9
119
        sizeof       = 2
        __FUNCTION__ = Function::operator ()
        __FUNCDNAME__= ??$?R_W@Function@@QBE?AU0@AB_W@Z
        __FUNCSIG__  = struct Function __thiscall Function::operator ()<wchar_t>(const wchar_t &) const

__COUNTER__ = 10
0012FF3C
        sizeof       = 4
        __FUNCTION__ = Function::operator ()
        __FUNCDNAME__= ??$?RPAX@Function@@QBE?AU0@ABQAX@Z
        __FUNCSIG__  = struct Function __thiscall Function::operator ()<void*>(void *const &) const

Press any key to continue . . .

予想通り、std::string 型に対する __FUNCSIG__ がキモイです。

プログラムのメモリ使用量を減らす - もう一度整理
プログラムのメモリ使用量を減らす をVista環境を交えて纏め。 今回は、拙作オーナードローメニューのサンプルを使い、 起動→メニューから [実行ファイルのフォルダを開く]コマンドを使用した直後の数値を使っている。
XP - テスト項目(単位:KB) メモリ使用量 最大メモリ使用量 ページフォルト 仮想メモリサイズ ページプール 非ページプール
標準 6496 6508 1872 2988 71 4
ImmDisableIME 5072 5108 1432 2248 31 2
ImmDisableIME
+ ShowWindow
344 2836 4725 2000 31 2
ImmDisableIME
+ SetProcessWorkingSetSize
380 3820 5461 2248 31 2
今回追加した SetProcesSetProcessWorkingSetSize も ShowWindow と同様にページアウトを促す効果がある。 効果はやはり劇的だ。
次にVista。w = ワーキングセット。
Vista - テスト項目(単位:KB) w(メモリ) ピークw(メモリ) メモリ(プライベートw) コミットサイズ ページプール 非ページプール
標準 10672 11336 2264 2704 100 5
ImmDisableIME 7600 8480 1796 2204 54 3
ImmDisableIME
+ ShowWindow
7688 8584 1820 2232 54 4
ImmDisableIME
+ SetProcessWorkingSetSize
1800 5036 796 2222 54 3

こんな僻地まで来てしまう人だからすでに知っているかとは思うが、 Vistaではメモリ管理が大きく変更されているので、単純比較はできないし、 そもそもそういう趣旨ではない。

ここで注目すべきは ShowWindow がページアウトを促さなくなっている点。 メモリ管理の変更の影響で、非表示処理の副作用がなくなり、ページアウトしなくなっている。 一方で、SetProcessWorkingSetSize はしっかり仕事をしている(ドキュメント化されているので当然だが)。

まあ何が言いたいかって言うと、Vista重いよママン!!!(つД`。)

標準
配布の状態
ImmDisableIME
ImmDisableIME((DWORD)-1); を使用
ShowWindow
ShowWindow(hWnd, SW_MINIMIZE); → ShowWindow(hWnd, SW_HIDE); と連続して使用。 各メッセージプロシージャのreturn前に実行している。
SetProcessWorkingSetSize
SetProcessWorkingSetSize(GetCurrentProcess(), (SIZE_T)-1, (SIZE_T)-1); を使用。 各メッセージプロシージャのreturn前に実行している。
久々にワロタ 〜 if (TRUE == expr)

// D language
        //...
        WNDCLASSEX wndcls;
        assert(wndcls.cbSize        == WNDCLASSEX.sizeof);
        assert(wndcls.cbClsExtra    == 0);
        assert(wndcls.cbWndExtra    == 0);
        assert(wndcls.hbrBackground == null);
        assert(wndcls.lpszMenuName  == null);
        wndcls.style         = CS_DBLCLKS;
        wndcls.lpfnWndProc   = &MainWndHandler.Procedure;   // user code
        wndcls.hInstance     = GetModuleHandle(null);
        wndcls.hIcon         =
        wndcls.hIconSm       = LoadIcon(cast(HINSTANCE)null, IDI_APPLICATION);
        wndcls.hCursor       = LoadCursor(null, IDC_ARROW);
        wndcls.lpszClassName = this.wndClsName.toz();       // user code
        return (TRUE == RegisterClassEx(&wndcls));
}
久々にワロタ。こういうミスを連発していたのが昔の俺なんだよな。 今の俺は昔のノウハウを忘れちゃってるから困る(冗談抜きで!!)
それはともかく、TRUEと比較しないのは鉄則である。assertなんか使ってる場合じゃない。 RegisterClassEx が発生させるエラーコード(ERROR_FILE_NOT_FOUND) 2 指定されたファイルが見つかりません。 は意味不明だし、これで3時間ほど考えた俺は何なんだ。 「成長してないじゃんwwww」
RLO(Right-to-Left Override)とgoogle-code-prettifyを使ってみる
実行結果

// C++
#include <windows.h>
int WINAPI WinMain(HINSTANCE, HINSTANCE, LPSTR, int)
{
  static const wchar_t RLO = static_cast<wchar_t>(MAKEWORD(0x2e, 0x20));
  wchar_t reverse[260] = { RLO, 0,};
  lstrcatW(reverse, L"ジャンクメタル");
  MessageBoxW(NULL, reverse, NULL, 0);
  return 0;
}
セキュリティ問題で有名なRLOを使った遊び。 でもって、このソースコード表示はgoogle-code-prettifyを使っています。 これまではSourceConverter使ってたんですけど、どっちがいいかなぁ。
実行結果 おまけ。挿入する wchar_t を適当に変更すればいろいろ遊べる。 目的の文字は charmap.exe を使って調べることができる。
(追記)メインで使っているFirefoxでは ol・li タグによる数値の部分もクリップボードにコピーすることがわかりました。 Opera・IEでは数値を含まず行数を表現できていたので評価していたのですが、残念です。 ブラウザを乗り換える気はないので、今後は google-code-prettify メインにしていきます。
D言語のアウトプット

D言語のコードの暫定アウトプット。ごみ置き場になるかも。

  1. 2007/06/04 - 基本的なWindowsアプリ
  2. 2007/06/04 - ダイアログベース
  3. 2007/06/30 - D言語で新しいプログラミングの世界が広がります (ZIP)
  4. 2007/06/30 - import文でバイナリを包含することが出来ます (ZIP) - HOOKのためだけのDLLはもう必要ありません(\hook\hook.exe)
プログラムのメモリ使用量を減らす

タスクマネージャ上のメモリ使用量を減らして「見せる」には、 ワーキングセットを減らせばよい。 まずはテストアプリの標準値。

標準値

このアプリケーションはタスクトレイに常駐し、 常に不可視のウィンドウをひとつ持っている。


ImmDisableIME((DWORD)-1);

を使う。

ImmDisableIME

詳しくは検索するといろいろ出てくるが、 IMEに関する複数のモジュールのロードが行われなくなる。 メモリ使用量の変化は少ないが、それ以外も確実に減少するので、 この方法はとてもお勧めできる。


ShowWindow(hWnd, SW_MINIMIZE);
ShowWindow(hWnd, SW_HIDE);

と連続して使う。

SW_MINIMIZE

劇的に減っていて逆に気持ち悪い。 操作をするたびに、また激しく増加していくので、 適当なタイミングで何度もコールすると効果的だ。 詳しくは検索するといろいろ出てくるが、Windowsはウインドウが隠されると、 俊敏な反応が必要なくなったアプリケーションと判断して、 メモリを退避させる動作をとることがこの結果の理屈らしい。


果たしてShowWindowの処理がWindowsにとって、 本当にやさしいものであるか否かはわからない。 ただ、一部の人種を欺くための効果的な手法ではあると思う。


(追記)プログラムのメモリ使用量を減らす - もう一度整理

vistaとドライバ
のぉぉっぉおおおおおおおおおぅ!! Vistaではカーネルモードで動作するソフトウェアの開発が極端に制限されるらしい (詳細は外部リンク参照)。 Vista購入を現実的に考えて、調べ始めた途端にこれだ。 ドライバについて興味津々の最近の自分にとっては、かなり暗澹たる気分にさせる話だが、 はてさてどうしたものだろうね……。
お前には失望した
自虐じゃないですよ。LoadImage関数のオプションLR_SHAREDのこと。 こいつの説明にはこうある

イメージを 2 回以上ロードする場合に、同じハンドルを使います。LR_SHARED を設定しない場合、 LoadImage を呼び出して同じリソースをもう一度ロードすると、最初とは異なるハンドルが返ります。 このフラグを使った場合、不要になったりソースはシステムによって破棄されます。 ……

まあ感覚的にいってスコープと参照0で破棄してくれることを期待する。 つまり以下のコードの出力値は一定であってほしいのだが、
  1. #include <windows.h>
  2. #include <iostream>
  3. int main()
  4. {
  5.   for (int i=0; i<100; ++i) {
  6.     HICON hIcon = (HICON)::LoadImage(::GetModuleHandle(TEXT("user32.dll")), 
  7.                                      MAKEINTRESOURCE(100), IMAGE_ICON, 00
  8.                                      LR_SHARED);    // あるいはここをLR_DEFAULTCOLOR(0)にする
  9.     std::cout << "GDI  : " << ::GetGuiResources(::GetCurrentProcess(), GR_GDIOBJECTS)   << "\t"
  10.               << "USER : " << ::GetGuiResources(::GetCurrentProcess(), GR_USEROBJECTS)  << "\n";
  11.   }
  12.   return 0;
  13. }
  14.  
GDI  : 7        USER : 2
GDI  : 10       USER : 3
GDI  : 13       USER : 4
GDI  : 16       USER : 5
 ....
GDI  : 295      USER : 98
GDI  : 298      USER : 99
GDI  : 301      USER : 100
GDI  : 304      USER : 101
続行するには何かキーを押してください . . .
うむ。こいつはCoolだ。この結果は LR_DEFAULTCOLOR(0x00) を指定したときと変わらない。 つまり私が正しいと思っていた以下のコードは非常に怪しい。
  1. #include <windows.h>
  2. #include <iostream>
  3. #include <commctrl.h>
  4. #pragma comment(lib, "comctl32.lib")
  5.  
  6. int main()
  7. {
  8.   HIMAGELIST hImglist = ::ImageList_Create(3232, ILC_COLOR32 | ILC_MASK, 1000);
  9.   for (int i=0; i<100; ++i) {
  10.     int idx = ::ImageList_AddIcon(hImglist, static_cast<HICON>(
  11.                 ::LoadImage(::GetModuleHandle(TEXT("user32.dll")), 
  12.                             MAKEINTRESOURCE(100), IMAGE_ICON, 3232, LR_SHARED) ) );
  13.     std::cout << i         << "\t"
  14.               << "GDI  : " << ::GetGuiResources(::GetCurrentProcess(), GR_GDIOBJECTS)   << "\t"
  15.               << "USER : " << ::GetGuiResources(::GetCurrentProcess(), GR_USEROBJECTS)  << "\n";
  16.   }
  17.   ::ImageList_Destroy(hImglist);
  18.   hImglist = NULL;
  19.   std::cout << "-------" << "\t"
  20.             << "GDI  : " << ::GetGuiResources(::GetCurrentProcess(), GR_GDIOBJECTS)   << "\t"
  21.             << "USER : " << ::GetGuiResources(::GetCurrentProcess(), GR_USEROBJECTS)  << "\n";
  22.   return 0;
  23. }
  24.  
98      GDI  : 311      USER : 100
99      GDI  : 314      USER : 101
------- GDI  : 304      USER : 101
続行するには何かキーを押してください . . .
LoadImageの結果は一旦変数に置き、それを代入後DestroyIconする。 しかし、このとき LR_SHARED を指定したままだと変化がない。指定を解除すれば

HICON hIcon = static_cast<HICON>(::LoadImage(::GetModuleHandle(TEXT("user32.dll")), 
                                             MAKEINTRESOURCE(100), IMAGE_ICON, 3232,
                                             LR_DEFAULTCOLOR));
int idx = ::ImageList_AddIcon(hImglist, hIcon);
::DestroyIcon(hIcon);
98      GDI  : 14       USER : 1
99      GDI  : 14       USER : 1
------- GDI  : 4        USER : 1
続行するには何かキーを押してください . . .
このとおりだ。そういえばどのサンプルを見てもLR_SHAREDは使っていないし、 自前でDestroyIconしている。これがリークなのかどうかはわからないが、 それにしても、LR_SHAREDには裏切られた気分だ (裏切りってのはいつも過信か、相手の質を見抜けなかった自分が原因なのね)。
NativeAPI Tips 04 [TEB構造体]
TEB構造体はとてもヽ(゚∀゚) ジャンボ! で味わい深い。まずは取得方法。
  1. #define _X86_
  2. extern "C" {
  3. #pragma warning(push, 0)
  4. # include <ntddk.h>
  5. #pragma warning(pop)
  6. }
  7. #include "structures.h"
  8. #include "functions.h"
  9. #pragma comment(lib, "ntdll.lib")
  10.  
  11.  
  12. int main()
  13. {
  14.   THREAD_BASIC_INFORMATION tbi;
  15.   ::NtQueryInformationThread(::GetCurrentThread(), ThreadBasicInformation,
  16.                              &tbi, sizeof(THREAD_BASIC_INFORMATION), NULL);
  17.   PTEB pTeb = static_cast<PTEB>(tbi.TebBaseAddress);
  18.   return 0;
  19. }
  20.  
あるいはこういうまんまなAPIもある。カレントプロセスに限定するなら、こちらのほうが簡単。

NTSYSAPI PTEB NTAPI NtCurrentTeb();
意外にもwinnt.hにも定義されていたりする。#if の嵐の中で私の環境では次のようになっていた。

__inline struct _TEB * NtCurrentTeb( void ) { return (struct _TEB *) (ULONG_PTR) __readfsdword (PcTeb); }
いずれの場合でもポイントは
  1. ntddk.hwindows.hは共存できないため、 情報量の多いntddk.hどちらか一方を使い、ほかの情報は自分で定義する (今回はntddk.hを使いwindows.h のものは structures.h / functions.h で定義している)
  2. 一部の構造体は、互換性の問題から定義が隠されているのでそれを自分で定義する(structures.h)
  3. ntdll.libのリンクが必要 (ユーザーモードでGetProcAddressするなら[Tips 02]のようにntdll.dllから拾う)
TEBでなにが出来るかはおいおい見ていくとして、 とりあえずDLLの参照カウントを見つけたのでそいつで胸キュンしてしまおう。
  1. #define _X86_
  2. extern "C" {
  3. #pragma warning(push, 0)
  4. # include <ntddk.h>
  5. #pragma warning(pop)
  6. }
  7. #include "structures.h"
  8. #include "functions.h"
  9. #pragma comment(lib, "ntdll.lib")
  10.  
  11.  
  12. static bool Static2Dynamic(const PVOID pDll)
  13. {
  14.   PLIST_ENTRY pList = &NtCurrentTeb()->Peb->LoaderData->InLoadOrderModuleList;
  15.  
  16.   for (PLDR_MODULE InLoadOrder = (PLDR_MODULE)pList->Flink;
  17.        InLoadOrder != (PLDR_MODULE)((PLDR_MODULE)pList->Blink)->InLoadOrderModuleList.Flink;
  18.        InLoadOrder = (PLDR_MODULE)InLoadOrder->InLoadOrderModuleList.Flink)
  19.   {
  20.     if (InLoadOrder->BaseAddress == pDll) {
  21.       // スタティックリンクされたモジュール(-1)をただの参照カウント 1 にする
  22.       if (InLoadOrder->LoadCount == -1) {
  23.         InLoadOrder->LoadCount = 1;
  24.       }
  25.       return true;
  26.     }
  27.   }
  28.   return false;
  29. }
  30.  
  31. int main()
  32. {
  33.   HANDLE hNtDll = NULL;
  34.   UNICODE_STRING us;
  35.   RtlInitUnicodeString(&us, L"ntdll.dll");
  36.   if ( NT_SUCCESS(LdrGetDllHandle(NULL, NULL, &us, &hNtDll))
  37.     && Static2Dynamic(hNtDll))
  38.   {
  39.     // 必須DLLをアンロードするため、ぬるぽ参照しておちます(WriteConsoleAには到達しない
  40.     LdrUnloadDll(hNtDll);
  41.     WriteConsoleA(NtCurrentTeb()->Peb->ProcessParameters->StdOutputHandle,
  42.                   __FUNCTION__, strlen(__FUNCTION__), NULL, NULL);
  43.   }
  44.   return 0;
  45. }
  46.  
このプログラムは静的リンクされているntdll.dllをアンロード可能にして、 アンロードしてしまう。-1 に変更すると逆に静的リンク扱いとなり絶対にアンロードできなくなる。
(追記)Vistaでもこのプログラムは動作する。個人的には意外だったが(メジャーバージョンアップしているから)、 やはりMSの互換性に対する配慮は尋常ではないらしい。
NativeAPI Tips 03 [PsSetLoadImageNotifyRoutine]
DLLのロードなどのイベントをキャッチする関数を登録できるAPI。 登録のみを担当し、解除には PsRemoveLoadImageNotifyRoutine を利用する。 利用するのだが、なぜかWin2Kでは解除側だけがコンパイルエラーになる。
>build
 …
error C3861: 'PsRemoveLoadImageNotifyRoutine': identifier not found, even with argument-dependent lookup
WinXP用としてなら問題ないので、そのバイナリを2Kに乗せてみると、 Install
やはりだめらしい。やむをえないので、WINVER > 0x0500などで登録だけして解除はしないようにすると、 一見してうまくいくように見える。 が、ドライバをアンインストールすると登録関数のアドレスは無効になるので
Error
あのな、M$。使えないのなら使えないように、両方禁止しておけと。 使えるけど、おちますよ^^ ってもうね、あほかと。
NativeAPI Tips 02 [NtSuspendProcess]
たのしい Undocumented functions その1。プロセス単位での停止。明らかに使えるのに、なぜ非公開なのか判らない。
  1. #include <windows.h>
  2. int main(int argc, char* argv[])
  3. {
  4.   typedef LONG NTSTATUS, *PNTSTATUS;
  5.   typedef NTSTATUS (NTAPI* FNtSuspendProcess)(IN HANDLE ProcessHandle);
  6.   typedef NTSTATUS (NTAPI* FNtResumeProcess)(IN HANDLE ProcessHandle);
  7.   if (argc == 1) { return 0; }
  8.  
  9.   FNtSuspendProcess fNtSuspendProcess = reinterpret_cast<FNtSuspendProcess>(
  10.     ::GetProcAddress(::GetModuleHandle(TEXT("ntdll.dll")), "NtSuspendProcess"));
  11.   FNtResumeProcess fNtResumeProcess = reinterpret_cast<FNtResumeProcess>(
  12.     ::GetProcAddress(::GetModuleHandle(TEXT("ntdll.dll")), "NtResumeProcess"));
  13.  
  14.   HANDLE hProcess = ::OpenProcess(PROCESS_ALL_ACCESS, FALSE, atoi(argv[1]));
  15.   NTSTATUS NtStatus = fNtSuspendProcess(hProcess);
  16.   system("pause");
  17.   NtStatus = fNtResumeProcess(hProcess);
  18.   return 0;
  19. }
  20.  
NativeAPI Tips 01 [RtlInitUnicodeString]
NativeAPIは文字列を受け取る際にUNICODE_STRING構造体を使うことが多い。 そのため初期化関数である RtlInitUnicodeString は lstrcpy ぐらい基礎になると思うのだが、いかんせんアクが強い。 まずAPIのシグネチャ。

VOID NTAPI RtlInitUnicodeString(PUNICODE_STRING DestinationString,
                                PCWSTR          SourceString);
RtlInitUnicodeString には const wchar_t* を渡す。 で、受け取る構造体。

typedef struct _UNICODE_STRING {
    USHORT Length;
    USHORT MaximumLength;
    PWSTR  Buffer;
} UNICODE_STRING, *PUNICODE_STRING;
Buffer が固定長配列でも const でもないので APIが内部でヒープ確保してBufferに配置するんだろうなー。などと考える。
甘い、とろけるほど甘い。こいつは 内部const_cast してポインタを保存してるだけ。 だから定数なんか壊しまくりんぐで「うはwwおkww」みたいな結果になる。
なぜか〜〜できない
なぜかコンパイルできない。変換可能ではなく、同じ型じゃないとだめなんだろう。 よくわからないがイラっとした。
  1. struct Abst {};
  2. struct A : public Abst {};
  3. struct B : public Abst {};
  4. int main()
  5. {
  6.   Abst* p = (1) ? new A : new B;
  7.   return 0;
  8. }
  9.  
DDK開発環境
ドライバ開発環境がアレなので改善するための覚書。 半分はうそでできているかもしれないけれども、自分用メモと、 同じようにアレな状況でコーディングしてる人の参考になればと思う。

VC++2005によるドライバコンパイル

コーディングして、コンパイル通って、後述のWinDbgでデバッグできるようになることが目的です。 私は、VCのコンパイラオプションも、標準buildコマンドも、正確なオプションの意味がわかっていないので、 これ以上の目的はもちません。したがってリリースは、やはり標準の Free Build をすることになります。

設定のベースとするのは [Win32 プロジェクト] > [DLL] (空のプロジェクト)で、 設定していくのは [Debug] ビルドのみです。これらの設定を行った、 ドライバプロジェクトの雛形をおいておきます。 buildのログを見れば、これよりもずっとbuildコマンドに近い環境を作ることが出来ます。

[オプション] > [プロジェクトおよびソリューション]
VC++ ディレクトリ [インクルード ディレクトリ] に
  • C:\WINDDK\3790.1830\inc\crt(追記)
  • C:\WINDDK\3790.1830\inc\mft42(追記)
  • C:\WINDDK\3790.1830\inc\wxp
  • C:\WINDDK\3790.1830\inc\ddk\wxp
  • C:\WINDDK\3790.1830\inc\ddk\wdm\wxp
[ライブラリ ファイル] に
  • C:\WINDDK\3790.1830\lib\wxp\i386
[構成プロパティ] > [C/C++] > [全般]
デバッグ情報の形式 [C7 互換 (/Z7)] もしくは [プログラム データベース (/Zi)]
WinDbgは[/ZI]に対応していない
[構成プロパティ] > [C/C++] > [プリプロセッサ]
プリプロセッサの定義 _X86_;DBG;$(NOINHERIT)
DBGはSDKでいう_DEBUG
[構成プロパティ] > [C/C++] > [コード生成]
簡易リビルドを行う いいえ
基本ランタイム チェック 既定値
バッファ セキュリティ チェック はい の場合はBufferOverflowK.lib が必要(追記)
[構成プロパティ] > [C/C++] > [詳細]
呼び出し規約 [__stdcall (/Gz)]
[構成プロパティ] > [リンカ] > [全般]
出力ファイル $(OutDir)\$(ProjectName).sys
インクリメンタル リンクを有効にする インクリメンタル リンクを行わない (/INCREMENTAL:NO)
[構成プロパティ] > [リンカ] > [入力]
追加の依存ファイル ntoskrnl.lib
すべての既定のライブラリの無視 [はい (/NODEFAULTLIB)]
必要に応じて追加するのはSDKと同じ。たとえば hal.lib はほぼ必須。 BufferOverflowK.lib が必要(追記)
[構成プロパティ] > [リンカ] > [詳細]
エントリ ポイント DriverEntry
ベース アドレス 0x10000
warning LNK4086: エントリポイント '_DriverEntry@8' は 12 バイトの引数を持つ __stdcall ではありません。イメージは動作しない可能性があります。 warning LNK4096: Windows 95、Windows 98 では /BASE 値 '0x10000' は無効です。イメージは動作しない可能性があります。 シラネ-ナ(゚Д゚)y─┛~~

(追記)もし標準 build が通って、VCでコンパイルできないときは buildで生成されるログを読む のがとても有効です。

ちなみに、ファイルをC++にする場合は、DriverEntryを extern "C" してあげないとネームマングリングでリンカが怒ります。 あと、最新版の開発キット( 6000.16386.WDK_ RTM.ISO) はコンパイルの仕方がわからないので(ハズカシ。vista用だから?) WDFv10.iso の次バージョン(なぜか今は配布されてない)を使ってます。


仮想マシンとWinDbgによるデバッグ

仮想マシンとホストマシンをWinDbgを使って通信し、 ドライバにアタッチするための手順を示します。パスは適宜読み替えてください。

1. 仮想マシン側の設定

通信用のシリアルポートと、boot.iniの設定をする必要があります。

QEMUの場合
デバッグメモ#4.4 / #4.5を参考にしてください。
VMwareの場合
[M.D.L] VMware 3 で Kernel Debugging!を参考にしてください。 以下に、私が多少混乱した箇所を補足しておきます。
  • 記事のVMwareのバージョンは古いものですが、5.5台でも問題ありませんでした
  • 2. Guest OS に Serial port を追加 は仮想マシンの設定画面から[追加(Add)]ボタンで追加します
  • 2. Guest OS に Serial port を追加『\\.\com_1』と書かれていますが、実際には 『\\.\pipe\com_1』を入力してください(初期値ですけどね)
  • /debugport=COM1 /baudrate=57600 とあります。debugportが先ほどのポート名と一致しませんが、この書き方で大丈夫です。 baudrateは通信速度(のようなもの)です。私の場合115200で問題ありませんでした。

2. 仮想マシンにテストのドライバを導入

といっても面倒くさいのがドライバの世界なので、ここは適当なサンプルとツールを利用します。

  1. Windows Device Driver Programming Part 1から、 driver1.zip・DIP.zipをDLします
  2. 仮想マシン内でDIP.exeを使って DHello.sysを仮想マシンにインストールします ※事前にDebugViewを起動しておくと出力が確認できます (画像 - インストール
  3. 一旦仮想マシンをシャットダウンします

3. WinDbgによるドライバへのアタッチ

ホストと仮想マシンの接続を手順を確認します。

  1. Debugging Tools for WindowsをDLしてホストマシンにインストールします
  2. 中に含まれているWinDbgを起動し、[File]メニュー > [Kernel Debug] を選択し、 [COM]タブで仮想マシンの設定と合うように設定します (画像 - 接続設定
  3. [OK]すると接続待機状態になります (画像 - 待機)。 WinDbg終了時に設定を保存するか否かを訊かれるので保存しておきます
  4. 待機状態で仮想マシンを起動します ※例ではWin2Kを仮想マシンにしているため以下のようになりました
    オペレーティング システムの選択
    
    
      Microsoft Windows 2000 Professional
      Microsoft Windows 2000 Professional [debugger enabled]
    
    上矢印キーと下矢印キーを遣って項目を選択し、Enter キーを押してください。
    
  5. [debugger enabled]を指定して起動すると、WinDbgが接続を確立します。 これで基本的な出力動作が確認できました (画像 - 接続成功
  6. 不要なのでDIP.exeDHello.sysをアンインストールしておきます
  7. 一旦仮想マシンとWinDbgをシャットダウンします

4. WinDbgによるソースレベルデバッグ

ソースレベルデバッグには、ブレークポイントと、ソース、 デバッグシンボルが必要なので、それを用意、設定します。 ブレークポイントは(その方法しか知らないので)ソースコード上に配置します。 Dhello.cppにブレーク命令を追加してください。


NTSTATUS DriverEntry(IN PDRIVER_OBJECT pDriverObject,
                     IN PUNICODE_STRING pRegistryPath)
{
    __asm int 3h
    DbgPrint("Hello World! from kernel mode.");
    return STATUS_SUCCESS;
}
  1. 上記のブレーク命令を追加します
  2. DHello.sysを再buildして生成しておきます。 当然ですがCheckedのほうがデバッグ情報は豊富です
  3. [3.] の手順でWinDbg+仮想マシンを起動します
  4. DIP.exeで新しいDHello.sysをインストールします。 ブレークが発生し、仮想マシンからWinDbgに制御が移ります
  5. WinDbgがソース等を検索できるように、それぞれの項目を設定します [Reload] をチェックしておくと、自動で再検索するのでチェックしておきます
  6. 設定が正しければ(多分)自動的にソースファイルが検索・表示されます。 追加したブレークポイントで停止していることが確認できると思います (画像 - ソースヾ(゚∀゚)ノハイ!ハイ!
    手動で確認したいときのコマンド
    • ロードしたデバッグシンボルを確認したいときは lm
    • デバッグシンボルを明示的にロードしたいときは ld Dhello
    • 明示的にソースを開くときは [File]メニュー > [Open Source File]
  7. あとは、適当にステップ実行やウインドウ表示してあそべます (画像 - ステップ実行(゚∀゚ )スキスキスキ) (画像 - ローカル変数  スーキスキス( ゚∀゚)

諸めも

WinDbgを話題の中心に据えた日本語の資料としては カーネルデバッガー「WinDbg」入門 があります。

WinDbgの「やさしいユーザーモードアプリケーションのデバッグ方法」は WinDbgのメモ があります。

慣れてくると、WinDbg起動後の手順がわずらわしくなります。 これはコマンドライン引数から設定できます。 デバッグメモ#4.3に設定例があります。

カーネルのデバッグシンボルを入手しましょう(何ゆえだ?)

  1. [3.] の手順でWinDbg+仮想マシンを起動します
  2. 普通に操作できる(デスクトップが表示された)状態になったら、 WinDbgの [Debug]メニュー > [Break] でWinDbgに制御を移します
  3. lmでロードしたデバッグシンボルを確認しておきます
  4. [File]メニュー > [Symbol File Path] もしくは
    1. .sympath srv*C:\WINDOWS\Symbols*http://msdl.microsoft.com/download/symbols (このパスはWinXPの既定値)
    2. .reload /f
    でSymbolディレクトリにデバッグシンボルをDLします(ちょっと時間がかかります)
  5. lmでロードしたデバッグシンボルを確認できます (画像 - カーネル(゚∀゚)キュンキュン!
さっくり作るC++&Win32API開発環境
ほぼ自分用のメモ。次に環境入れなおすときはVista導入になりそうだから参考にすらならないかも。
gcc
DevC++ JP をインストールするとgcc3.4.2がインストールされる。 binディレクトリにパスを通せば完了。
bcc
freecommandlinetools2.exe をインストールするとbcc5.5.1がインストールされる。 例によって \borland\bcc55\Bin内に link32.cfgbcc32.cfgが必要。 binディレクトリにパスを通せば完了。 Turbo C++ (bcc5.8)という選択肢もあるが、インストールまでが異常に面倒くさい。
vc8
Visual C++ 2005 Express Edition インストール で扱っているので省略。事実上必須のコンポーネントの最新版は Win32APIに。
ライブラリ
あって困らないもの
Academic Edition はここが(・∀・)イイ
  • Just-In-Time デバッグができて(・∀・)イイ
  • なんかデバッグ関係の表示(メモリなど)が多くて(・∀・)イイ
  • リソースエディタがついていて(・∀・)イイ
  • ステップインデバッグのときCRT関数でつまづかないのが(・∀・)イイ
  • spy++、guidgen、MSDNとの連携が最初からできていて(・∀・)イイ
1