管理人がふと振り返ったとき「成長したじゃん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 は csrss.exe / winlogon.exe ハンドルを保有し、 待機することで生存確認を行っている。 いずれかのプロセスが死亡すると、KeBugCheck(KeBugCheckEx) を使い青画面に移行させる。
smss.exe 自身が死んだ場合は、直ちに何が起こるということはない。 smss.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 / winlogon.exe はセッションごとに生成される。
winlogon.exe の場合、smss.exe が機能停止していればこれを殺害することができたが、 csrss.exe の場合は殺害すると必ず青画面にいく。 GUIで高いCPU負荷をかけると csrss.exe のCPU使用率が高まることからわかるように、 csrss.exe はユーザがWindowsを利用する動作と密着しているためだ。
ゆえに、これをサスペンドさせるとWindowsはフリーズしたようになる。 しかしフリーズしていても、カーネルの動作からはある程度切り離されて設計されているらしく、 システムは生きている。そのため、別ユーザがリモートログオンして、 サスペンドされた csrss.exe をレジュームさせると何事もなく再開する。
lsass.exe は認証に関する部分を担当している。これが終了すると、 システムは60秒カウントダウンを表示し、カウント後シャットダウンされる。 このカウントを担当しているのはセッション 0 の winlogon.exe であり、 これが機能していないとカウントダウンも機能しない。
Vistaからはセッション 0 に winlogon.exe が生成されない。 警告のために wlrmdr.exe というプロセスが生成されるが、これや winlogon.exe を停止しても再起動がかかる。 再起動を阻止するには wininit.exe をサスペンドすればよい。 レジュームすると再起動する。ただし wininit.exe は死亡すると必ず青画面に行く。
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 ファイルが更新されるので、 変更された項目名で検索をかけ、変化を調べてスクリプトの代入値を変更するだけ。 ちなみに、作業は管理者権限で行ったほうが楽。具体的には以下。
...\Microsoft Visual Studio 9.0\VC\VCWizards\AppWiz\Generic\Application\scripts\1033\default.js の 127・197 行目ぐらいにある config.CharacterSet = charSetUNICODE; を 0 の代入に置き換える。
...\Microsoft Visual Studio 9.0\VC\VCWizards\1033\common.js の 672・699 行目ぐらいにある CLTool.WarningLevel = WarningLevel_3; を 4 の代入に置き換える。
...\Microsoft Visual Studio 9.0\VC\VCWizards\AppWiz\Generic\Application\scripts\1033\default.js の 137 行目ぐらいにある CLTool.RuntimeLibrary = rtMultiThreadedDebugDLL; を 1 の代入に置き換える。 同じく 206 行目ぐらいにある CLTool.RuntimeLibrary = rtMultiThreadedDLL; を 0 の代入に置き換える。
...\Microsoft Visual Studio 9.0\VC\VCWizards\1033\common.js の 781 行目ぐらいにある CLTool.UsePrecompiledHeader = pchUseUsingSpecific; を 1 の代入に置き換える。
...\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 とか異常にでかいのも理解に苦しむ。
必要なものを以下に示す。この記事は仮想マシン のテンプレートを開発環境に仕立て上げる ための手順であるので、殆ど自分用のメモになってしまうが、そこは理解されたし。 ちなみに、無料でダウンロードできるものに関しては、2007年11月23日現在で最新のものを利用している。
セットアップ作業の順序と内容のメモ。特に記さない限り、フルパッケージインストールで、 インストールディレクトリはインストーラの初期値を使用している。
略記 | wlh | wnet | wxp | w2k |
---|---|---|---|---|
意味 | Vista & 2008 | 2003 | XP | 2000 |
//#include "afxres.h"
#include <winres.h>
#ifndef IDC_STATIC
#define IDC_STATIC (-1)
#endif
あとは必要なツールを入れたりなんなり、ご自由に。 一応、テスト用のプロジェクトを置いておきます。 たのしくつかってね。なかよくつかってね。
(追記)Boost 1.34.1もセットアップ。基本的には外部リンクで説明してある手順でOK。 Boostのアーカイブを全角を含むパスに置くとbjamがエラーを返すこと、 ビルドに2時間とかかかること、展開後アーカイブが2GB、 ビルド後のライブラリ群が2GB使うことに注意。
Windowsのセキュリティに関する情報各種。私の中に Windowsプログラミング → ドライバ → Windows内部 → Windowsセキュリティといった興味の流れがあるらしい。 実際ネットワーク系の知識が未だに殆どなくて、ホワイトハッカー道場 の後半部分がまるで理解できなかった。しかも理解しようという気になれないときている。
ある特定のファイルを選択した状態でウィンドウを開くには、 [ファイル名を指定して実行] やコマンドプロンプトで表題のように指定する。 プログラムでは次のように記述すればよい。
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から出てきたものだが便利そうなのでメモ。
今日はXPのタスクマネージャを勉強してみよう。
[プロセス]タブでは複数の項目を表示できるが、中でも[メモリ使用量]と[仮想メモリ サイズ]は判りづらい。 前者は今まさに利用されているアクティブなメモリの量でワーキングセットとも呼ばれる。 後者は利用されてはいるがアクセスされていないと判断されているメモリの量で、 そのうちにpagefile.sysに追い出されてしまう。
ここでいうメモリとは、仮想メモリを指す。仮想メモリは タスクマネージャや [システム情報] ー [システム概要] で確認できるが、表現に使われる単語が一貫していない。 とりあえず、以下のように覚えておけばよい。
ここで重要なのはユーザーモードプロセスからは物理メモリと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;
}
そこで出てくるのが 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まで利用できるようになっている。
既存のファイルにこの /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
ユーザーとしてはVistaは嫌いだけど、開発もする人間としては興味は尽きない。 [イメージのランダム化を有効にする (/DYNAMICBASE)] の Address Space Load Randomization については3部で扱ってます。全部はまだ読めてないんですけどねー。
(追記)読んだ。( ゚д゚)ウッウー さすがにカーネルのメジャーバージョンが違うだけあって、 かなり変更が大掛かり。私のきな臭いアプリにも影響がありそうな部分があったり。 ブートの変更はもうちっとよく勉強しておいたほうが良いかな。
XP以前ではほとんど関係の無い話題。 Vistaで「必ず」管理者権限が必要な場合はマニフェストで昇格を要求させる。
dependency タグがXP風のコントロールを使うための指示で、 ms_asmv3:trustInfo タグが今回追加された昇格を要求するためのタグ。
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><
1 RT_MANIFEST DISCARDABLE
{
"<?xml version="1.0" encoding="UTF-8" standalone="yes"?>\n"
〜〜省略〜〜
}
1 RT_MANIFEST DISCARDABLE "〜〜.manifest"
[構成プロパティ] > [ビルド イベント] > [ビルド後のイベント] の [コマンド] に
"$(DevEnvDir)..\..\SDK\v2.0\bin\mt.exe" -manifest "$(ProjectDir)$(TargetName).exe.manifest" -outputresource:"$(TargetDir)$(TargetFileName)";#1
権限昇格が必要なアプリは、ビルドしてもVisualStudio上から起動することが出来ないので、 この3番目の方法で、Releaseビルド時のみ埋め込んでしまうのが一番使いやすいかなと。
その他雑多にメモしておく。
#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.dll・VC8TestDll_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倍。スゲェ!
DLLのベースアドレスは調整されてリリースされるべきなのは有名な話。 その指針がMSDNに書いてあることを知った。ごめんね、おかあさん、いままで てきとうにせっていしてたから、ごめんね。
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__ がキモイです。
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 |
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重いよママン!!!(つД`。)
// 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));
}
久々にワロタ。こういうミスを連発していたのが昔の俺なんだよな。
今の俺は昔のノウハウを忘れちゃってるから困る(冗談抜きで!!)
指定されたファイルが見つかりません。は意味不明だし、これで3時間ほど考えた俺は何なんだ。 「成長してないじゃんwwww」
// 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使ってたんですけど、どっちがいいかなぁ。
D言語のコードの暫定アウトプット。ごみ置き場になるかも。
タスクマネージャ上のメモリ使用量を減らして「見せる」には、 ワーキングセットを減らせばよい。 まずはテストアプリの標準値。
このアプリケーションはタスクトレイに常駐し、 常に不可視のウィンドウをひとつ持っている。
を使う。
詳しくは検索するといろいろ出てくるが、 IMEに関する複数のモジュールのロードが行われなくなる。 メモリ使用量の変化は少ないが、それ以外も確実に減少するので、 この方法はとてもお勧めできる。
と連続して使う。
劇的に減っていて逆に気持ち悪い。 操作をするたびに、また激しく増加していくので、 適当なタイミングで何度もコールすると効果的だ。 詳しくは検索するといろいろ出てくるが、Windowsはウインドウが隠されると、 俊敏な反応が必要なくなったアプリケーションと判断して、 メモリを退避させる動作をとることがこの結果の理屈らしい。
果たしてShowWindowの処理がWindowsにとって、 本当にやさしいものであるか否かはわからない。 ただ、一部の人種を欺くための効果的な手法ではあると思う。
まあ感覚的にいってスコープと参照0で破棄してくれることを期待する。 つまり以下のコードの出力値は一定であってほしいのだが、イメージを 2 回以上ロードする場合に、同じハンドルを使います。LR_SHARED を設定しない場合、 LoadImage を呼び出して同じリソースをもう一度ロードすると、最初とは異なるハンドルが返ります。 このフラグを使った場合、不要になったりソースはシステムによって破棄されます。 ……
#include <windows.h>
#include <iostream>
int main()
{
for (int i=0; i<100; ++i) {
HICON hIcon = (HICON)::LoadImage(::GetModuleHandle(TEXT("user32.dll")),
MAKEINTRESOURCE(100), IMAGE_ICON, 0, 0,
LR_SHARED); // あるいはここをLR_DEFAULTCOLOR(0)にする
std::cout << "GDI : " << ::GetGuiResources(::GetCurrentProcess(), GR_GDIOBJECTS) << "\t"
<< "USER : " << ::GetGuiResources(::GetCurrentProcess(), GR_USEROBJECTS) << "\n";
}
return 0;
}
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) を指定したときと変わらない。 つまり私が正しいと思っていた以下のコードは非常に怪しい。
#include <windows.h>
#include <iostream>
#include <commctrl.h>
#pragma comment(lib, "comctl32.lib")
int main()
{
HIMAGELIST hImglist = ::ImageList_Create(32, 32, ILC_COLOR32 | ILC_MASK, 100, 0);
for (int i=0; i<100; ++i) {
int idx = ::ImageList_AddIcon(hImglist, static_cast<HICON>(
::LoadImage(::GetModuleHandle(TEXT("user32.dll")),
MAKEINTRESOURCE(100), IMAGE_ICON, 32, 32, LR_SHARED) ) );
std::cout << i << "\t"
<< "GDI : " << ::GetGuiResources(::GetCurrentProcess(), GR_GDIOBJECTS) << "\t"
<< "USER : " << ::GetGuiResources(::GetCurrentProcess(), GR_USEROBJECTS) << "\n";
}
::ImageList_Destroy(hImglist);
hImglist = NULL;
std::cout << "-------" << "\t"
<< "GDI : " << ::GetGuiResources(::GetCurrentProcess(), GR_GDIOBJECTS) << "\t"
<< "USER : " << ::GetGuiResources(::GetCurrentProcess(), GR_USEROBJECTS) << "\n";
return 0;
}
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, 32, 32,
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には裏切られた気分だ (裏切りってのはいつも過信か、相手の質を見抜けなかった自分が原因なのね)。
#define _X86_
extern "C" {
#pragma warning(push, 0)
# include <ntddk.h>
#pragma warning(pop)
}
#include "structures.h"
#include "functions.h"
#pragma comment(lib, "ntdll.lib")
int main()
{
THREAD_BASIC_INFORMATION tbi;
::NtQueryInformationThread(::GetCurrentThread(), ThreadBasicInformation,
&tbi, sizeof(THREAD_BASIC_INFORMATION), NULL);
PTEB pTeb = static_cast<PTEB>(tbi.TebBaseAddress);
return 0;
}
NTSYSAPI PTEB NTAPI NtCurrentTeb();
意外にもwinnt.hにも定義されていたりする。#if の嵐の中で私の環境では次のようになっていた。
__inline struct _TEB * NtCurrentTeb( void ) { return (struct _TEB *) (ULONG_PTR) __readfsdword (PcTeb); }
いずれの場合でもポイントは
#define _X86_
extern "C" {
#pragma warning(push, 0)
# include <ntddk.h>
#pragma warning(pop)
}
#include "structures.h"
#include "functions.h"
#pragma comment(lib, "ntdll.lib")
static bool Static2Dynamic(const PVOID pDll)
{
PLIST_ENTRY pList = &NtCurrentTeb()->Peb->LoaderData->InLoadOrderModuleList;
for (PLDR_MODULE InLoadOrder = (PLDR_MODULE)pList->Flink;
InLoadOrder != (PLDR_MODULE)((PLDR_MODULE)pList->Blink)->InLoadOrderModuleList.Flink;
InLoadOrder = (PLDR_MODULE)InLoadOrder->InLoadOrderModuleList.Flink)
{
if (InLoadOrder->BaseAddress == pDll) {
// スタティックリンクされたモジュール(-1)をただの参照カウント 1 にする
if (InLoadOrder->LoadCount == -1) {
InLoadOrder->LoadCount = 1;
}
return true;
}
}
return false;
}
int main()
{
HANDLE hNtDll = NULL;
UNICODE_STRING us;
RtlInitUnicodeString(&us, L"ntdll.dll");
if ( NT_SUCCESS(LdrGetDllHandle(NULL, NULL, &us, &hNtDll))
&& Static2Dynamic(hNtDll))
{
// 必須DLLをアンロードするため、ぬるぽ参照しておちます(WriteConsoleAには到達しない
LdrUnloadDll(hNtDll);
WriteConsoleA(NtCurrentTeb()->Peb->ProcessParameters->StdOutputHandle,
__FUNCTION__, strlen(__FUNCTION__), NULL, NULL);
}
return 0;
}
>build … error C3861: 'PsRemoveLoadImageNotifyRoutine': identifier not found, even with argument-dependent lookupWinXP用としてなら問題ないので、そのバイナリを2Kに乗せてみると、
#include <windows.h>
int main(int argc, char* argv[])
{
typedef LONG NTSTATUS, *PNTSTATUS;
typedef NTSTATUS (NTAPI* FNtSuspendProcess)(IN HANDLE ProcessHandle);
typedef NTSTATUS (NTAPI* FNtResumeProcess)(IN HANDLE ProcessHandle);
if (argc == 1) { return 0; }
FNtSuspendProcess fNtSuspendProcess = reinterpret_cast<FNtSuspendProcess>(
::GetProcAddress(::GetModuleHandle(TEXT("ntdll.dll")), "NtSuspendProcess"));
FNtResumeProcess fNtResumeProcess = reinterpret_cast<FNtResumeProcess>(
::GetProcAddress(::GetModuleHandle(TEXT("ntdll.dll")), "NtResumeProcess"));
HANDLE hProcess = ::OpenProcess(PROCESS_ALL_ACCESS, FALSE, atoi(argv[1]));
NTSTATUS NtStatus = fNtSuspendProcess(hProcess);
system("pause");
NtStatus = fNtResumeProcess(hProcess);
return 0;
}
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に配置するんだろうなー。などと考える。
struct Abst {};
struct A : public Abst {};
struct B : public Abst {};
int main()
{
Abst* p = (1) ? new A : new B;
return 0;
}
コーディングして、コンパイル通って、後述のWinDbgでデバッグできるようになることが目的です。 私は、VCのコンパイラオプションも、標準buildコマンドも、正確なオプションの意味がわかっていないので、 これ以上の目的はもちません。したがってリリースは、やはり標準の Free Build をすることになります。
設定のベースとするのは [Win32 プロジェクト] > [DLL] (空のプロジェクト)で、 設定していくのは [Debug] ビルドのみです。これらの設定を行った、 ドライバプロジェクトの雛形をおいておきます。 buildのログを見れば、これよりもずっとbuildコマンドに近い環境を作ることが出来ます。
[オプション] > [プロジェクトおよびソリューション] | |
---|---|
VC++ ディレクトリ | [インクルード ディレクトリ] に
|
[構成プロパティ] > [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を使って通信し、 ドライバにアタッチするための手順を示します。パスは適宜読み替えてください。
通信用のシリアルポートと、boot.iniの設定をする必要があります。
2. Guest OS に Serial port を追加は仮想マシンの設定画面から[追加(Add)]ボタンで追加します
2. Guest OS に Serial port を追加で
『\\.\com_1』と書かれていますが、実際には 『\\.\pipe\com_1』を入力してください(初期値ですけどね)
/debugport=COM1 /baudrate=57600とあります。debugportが先ほどのポート名と一致しませんが、この書き方で大丈夫です。 baudrateは通信速度(のようなもの)です。私の場合115200で問題ありませんでした。
といっても面倒くさいのがドライバの世界なので、ここは適当なサンプルとツールを利用します。
ホストと仮想マシンの接続を手順を確認します。
オペレーティング システムの選択
Microsoft Windows 2000 Professional
Microsoft Windows 2000 Professional [debugger enabled]
上矢印キーと下矢印キーを遣って項目を選択し、Enter キーを押してください。
ソースレベルデバッグには、ブレークポイントと、ソース、 デバッグシンボルが必要なので、それを用意、設定します。 ブレークポイントは(その方法しか知らないので)ソースコード上に配置します。 Dhello.cppにブレーク命令を追加してください。
NTSTATUS DriverEntry(IN PDRIVER_OBJECT pDriverObject,
IN PUNICODE_STRING pRegistryPath)
{
__asm int 3h
DbgPrint("Hello World! from kernel mode.");
return STATUS_SUCCESS;
}
WinDbgを話題の中心に据えた日本語の資料としては カーネルデバッガー「WinDbg」入門 があります。
WinDbgの「やさしいユーザーモードアプリケーションのデバッグ方法」は WinDbgのメモ があります。
慣れてくると、WinDbg起動後の手順がわずらわしくなります。 これはコマンドライン引数から設定できます。 デバッグメモ#4.3に設定例があります。
カーネルのデバッグシンボルを入手しましょう(何ゆえだ?)