[windbg] Debugger extension to dump binary tree

先月、ある解析のためにデバッガー拡張 DLL を書いてみたところ、思いのほか簡単だったのでコードを共有します。バイナリ ツリーをダンプするだけのコマンドです。Windows カーネルで多用されているリストについては、dl や !list といった標準コマンドがありますが、ツリーに関してはなさそうだったので作りました。

[2015/2/15 追記]
本記事で紹介しているコードは、ターゲットが 64bit である場合に限定されていました。32bit にも対応するように書き直したコードを GitHub 上で公開しています。コマンドは !dumptree から !dt に変更しました。

https://github.com/msmania/bangon/

実は、Windows には木構造も随所に使われています。今回対象とするのは、Windows カーネルに実装されている RTL_SPLAY_LINKS 構造体です。

RTL_SPLAY_LINKS structure (Windows Drivers)
http://msdn.microsoft.com/en-us/library/windows/hardware/ff553351(v=vs.85).aspx

typedef struct _RTL_SPLAY_LINKS {
  struct _RTL_SPLAY_LINKS  *Parent;
  struct _RTL_SPLAY_LINKS  *LeftChild;
  struct _RTL_SPLAY_LINKS  *RightChild;
} RTL_SPLAY_LINKS, *PRTL_SPLAY_LINKS;

名前の通りスプレー木なので、木の回転が発生しています。データ構造としては、親と左右の子をメンバーに持つごく普通の木です。通常であれば、dt や dq を使ってがりがり値を見ていくところですが、ツリーが大きいと面倒なことこの上ありません。

スプレー木 – Wikipedia
http://ja.wikipedia.org/wiki/%E3%82%B9%E3%83%97%E3%83%AC%E3%83%BC%E6%9C%A8

デバッガー拡張 DLL の作り方については、ここが本家です。

Writing WdbgExts Extensions (Windows Debuggers)
http://msdn.microsoft.com/en-us/library/windows/hardware/ff561491(v=vs.85).aspx

デバッガーをインストールした場所の winext フォルダーに入っている DLL のエクスポート関数を見てみると分かりますが、デバッガーの拡張コマンドのそれぞれがエクスポート関数に対応しています。要は、DLL を作ればいいわけです。

image
uext.dll を dependency walker で開いたところ。!handle コマンドなどが見える。

Visual Studio で空の Win32 DLL プロジェクトを作って、以下 3 つのファイル (dllmain.cpp fext.cpp fext.def) を追加します。

dllmain.cpp は DllMain だけです。特に意味はありませんが、エクスポート関数とは別に定義しておくのが好きです。使いまわせるし。

//
// dllmain.cpp
//

#include <windows.h>

BOOL WINAPI DllMain(
  _In_  HINSTANCE hinstDLL,
  _In_  DWORD fdwReason,
  _In_  LPVOID lpvReserved
) {
    switch (fdwReason) {
    case DLL_PROCESS_ATTACH:
    case DLL_THREAD_ATTACH:
    case DLL_THREAD_DETACH:
    case DLL_PROCESS_DETACH:
        break;
    }
    return TRUE;
}

メインのファイルが fext.cpp です。エクスポート関数とその関連定義を全部書きます。ここでインクルードしている wdbgexts.h は、デバッガーをインストールした場所の sdk\inc フォルダー下に入っています。プロジェクト フォルダーにコピーしておくと楽です。

アルゴリズムは・・・Breadth-first search しているだけです。重複ぐらいは確認しています。

//
// fext.cpp
//

#include <Windows.h>
#include <set>
#include <queue>

#include "..\dbgsdk\wdbgexts.h"

#define LODWORD(ll) ((DWORD)(ll&0xffffffff))
#define HIDWORD(ll) ((DWORD)((ll>>32)&0xffffffff))

//
//
http://msdn.microsoft.com/en-us/library/windows/hardware/ff543968(v=vs.85).aspx
//
EXT_API_VERSION ApiVersion = {
    0,    // MajorVersion
    0,    // MinorVersion
    EXT_API_VERSION_NUMBER64,    // Revision
    0    // Reserved
};

//
//
http://msdn.microsoft.com/en-us/library/windows/hardware/ff561303(v=vs.85).aspx
// ExtensionApis is extern defined as WINDBG_EXTENSION_APIS in wdbgexts.h
//
WINDBG_EXTENSION_APIS ExtensionApis;

LPEXT_API_VERSION ExtensionApiVersion(void) {
    return &ApiVersion;
}

VOID WinDbgExtensionDllInit(
  PWINDBG_EXTENSION_APIS lpExtensionApis,
  USHORT MajorVersion,
  USHORT MinorVersion
) {
    ExtensionApis = *lpExtensionApis;
    return;
}

DECLARE_API (help) {
    dprintf("Hello!\n");
}

struct TREE_ITEM64 {
    ULONGLONG Parent;
    ULONGLONG LeftChild;
    ULONGLONG RightChild;
};

struct TREE_ITEM_INFO {
    DWORD Level;
    ULONGLONG Myself;
    TREE_ITEM64 Item;
};

std::queue<TREE_ITEM_INFO> TraverseQueue;
std::set<ULONGLONG> CorruptionCheck;

BOOL AddTreeItem(DWORD Level, ULONGLONG Address) {
    if ( CorruptionCheck.find(Address)!=CorruptionCheck.end() ) {
        return FALSE;
    }

    CorruptionCheck.insert(Address);

    DWORD BytesRead = 0;
    TREE_ITEM_INFO TreeItem;
    TreeItem.Level = Level;
    TreeItem.Myself = Address;
    ReadMemory(Address, &(TreeItem.Item), sizeof(TREE_ITEM64), &BytesRead);

    if ( BytesRead!=sizeof(TREE_ITEM64) ) {
        return FALSE;
    }

    TraverseQueue.push(TreeItem);
    return TRUE;
}

DECLARE_API (dumptree) {
    ULONGLONG RootAddress = GetExpression(args);

    if ( !RootAddress )
        return;
   
    while ( TraverseQueue.size() ) {
        TraverseQueue.pop();
    }
    CorruptionCheck.clear();

    AddTreeItem(0, RootAddress);

    DWORD CurrentLevel = 0;
    DWORD ItemCount = 0;
    while ( TraverseQueue.size() ) {
        const TREE_ITEM_INFO &Item = TraverseQueue.front();

        dprintf("L=%04x#%04x %08x`%08x : P=%08x`%08x L=%08x`%08x R=%08x`%08x\n",
            CurrentLevel, ItemCount,
            HIDWORD(Item.Myself), LODWORD(Item.Myself),
            HIDWORD(Item.Item.Parent), LODWORD(Item.Item.Parent),
            HIDWORD(Item.Item.LeftChild), LODWORD(Item.Item.LeftChild),
            HIDWORD(Item.Item.RightChild), LODWORD(Item.Item.RightChild));
       
        if ( Item.Level!=CurrentLevel ) {
            ItemCount = 0;
            CurrentLevel = Item.Level;
        }
       
        if ( Item.Item.LeftChild ) {
            if ( !AddTreeItem(Item.Level+1, Item.Item.LeftChild) ) {
                dprintf("Item %08x`%08x was duplicated!\n",
                    HIDWORD(Item.Item.LeftChild), LODWORD(Item.Item.LeftChild));
            }
        }

        if ( Item.Item.RightChild ) {
            if ( !AddTreeItem(Item.Level+1, Item.Item.RightChild) ) {
                dprintf("Item %08x`%08x was duplicated!\n",
                    HIDWORD(Item.Item.RightChild), LODWORD(Item.Item.RightChild));
            }
        }

        ++ItemCount;
        TraverseQueue.pop();
    }
}

最後に定義ファイルです。

;
; fext.def
;

LIBRARY "FEXT.dll"
EXPORTS
    WinDbgExtensionDllInit
    ExtensionApiVersion
    help
    dumptree

デバッガーは拡張 DLL を動的リンクするので、ヘッダーは不要です。

これらのファイルをコンパイル/リンクして、x64 ネイティブの DLL を作ります。デバッガーのプロセスが拡張 DLL を直接ロードする以上、デバッガーと DLL の CPU アーキテクチャーの種類は同じでなければなりません。もし、32bit デバッガー用の DLL を作る場合は、fext.cpp の修正も必要です。手元の環境で作成した fext.dll を開いたところです。dumptree という関数が拡張コマンドです。

image

さて、実際に使ってみます。Windows のどこに木構造なんてあるんだ、という話ですが、木のスプレー操作を行うための関数がカーネルに用意されているので、ここでブレークしたパラメーターから木構造を入手できます。RtlSplay という関数の引数がそのまま RTL_SPLAY_LINKS へのポインターになっています。

RtlSplay routine (Windows Drivers)
http://msdn.microsoft.com/en-us/library/windows/hardware/ff553226(v=vs.85).aspx

今回は、Windows 8 x64 の Hyper-V 仮想マシンに対してデバッガーを繋ぎました。ゲスト上で特に何の操作もしていないのですが、すぐにブレークしてくれました。NTFS に木構造があるようです。

kd> x nt!RtlSplay
fffff801`fe0cdd40 nt!RtlSplay (<no parameter info>)
kd> bp nt!RtlSplay
kd> g
Breakpoint 0 hit
nt!RtlSplay:
fffff801`fe0cdd40 483909          cmp     qword ptr [rcx],rcx
kd> .reload
Connected to Windows 8 9200 x64 target at (Sat Nov 30 12:58:10.979 2013 (UTC – 8:00)), ptr64 TRUE
Loading Kernel Symbols
………………………………………………………
……………………………………………………….
………..
Loading User Symbols
……………………………………………………….
.
Loading unloaded module list
……..
kd> k
Child-SP          RetAddr           Call Site
fffff880`04f02bb8 fffff880`0178d25f nt!RtlSplay
fffff880`04f02bc0 fffff880`01786487 Ntfs!NtfsFindPrefix+0x1df
fffff880`04f02c70 fffff880`017824a1 Ntfs!NtfsFindStartingNode+0x537
fffff880`04f02d30 fffff880`01786d0d Ntfs!NtfsCommonCreate+0x401
fffff880`04f02f50 fffff801`fe089767 Ntfs!NtfsCommonCreateCallout+0x1d
fffff880`04f02f80 fffff801`fe08972d nt!KxSwitchKernelStackCallout+0x27
fffff880`044e60e0 fffff801`fe0cff1e nt!KiSwitchKernelStackContinue
fffff880`044e6100 fffff801`fe0d0d85 nt!KeExpandKernelStackAndCalloutInternal+0x20e
fffff880`044e6200 fffff880`0177a7f4 nt!KeExpandKernelStackAndCalloutEx+0x25
fffff880`044e6240 fffff880`015714ee Ntfs!NtfsFsdCreate+0x1d4
fffff880`044e6420 fffff880`0159b35d fltmgr!FltpLegacyProcessingAfterPreCallbacksCompleted+0x25e
fffff880`044e64c0 fffff801`fe45f05b fltmgr!FltpCreate+0x34d
fffff880`044e6570 fffff801`fe45bc5d nt!IopParseDevice+0x77b
fffff880`044e6760 fffff801`fe4612b8 nt!ObpLookupObjectName+0x7a1
fffff880`044e6890 fffff801`fe472ebe nt!ObOpenObjectByName+0x258
fffff880`044e6960 fffff801`fe473609 nt!IopCreateFile+0x37c
fffff880`044e6a00 fffff801`fe08e053 nt!NtCreateFile+0x79
fffff880`044e6a90 000007ff`3ab530fa nt!KiSystemServiceCopyEnd+0x13
000000e7`eed4f128 000007ff`37e952dc ntdll!NtCreateFile+0xa
000000e7`eed4f130 000007ff`37e95411 KERNELBASE!CreateFileInternal+0x324
000000e7`eed4f2b0 000007ff`321b60f9 KERNELBASE!CreateFileW+0x6d
000000e7`eed4f310 000007ff`321b603a sysmain!PfXpGetFileSize+0x39
000000e7`eed4f360 000007ff`321b2ebe sysmain!PfXpSaveLayout+0x10a
000000e7`eed4f620 000007ff`321a6460 sysmain!PfXpUpdateOptimalLayout+0x1e4
000000e7`eed4f790 000007ff`3ab6cd41 sysmain!PfXpTaskCommonCallback+0x40
000000e7`eed4f7c0 000007ff`3ab58576 ntdll!TppExecuteWaitCallback+0x151
000000e7`eed4f830 000007ff`38f4167e ntdll!TppWorkerThread+0x388
000000e7`eed4fad0 000007ff`3ab6c3f1 KERNEL32!BaseThreadInitThunk+0x1a
000000e7`eed4fb00 00000000`00000000 ntdll!RtlUserThreadStart+0x1d
kd> !process -1 0
PROCESS fffffa8002b6d840
    SessionId: 0  Cid: 03fc    Peb: 7f7ab80e000  ParentCid: 0204
    DirBase: 186ba000  ObjectTable: fffff8a005ff0f40  HandleCount: <Data Not Accessible>
    Image: svchost.exe

kd>

ここで拡張 DLL を実行します。.load して呼び出すだけです。

kd> .load D:\MSWORK\fext\fext.dll
kd> !fext.help
Hello!
kd> !fext.dumptree @rcx
L=0000#0000 fffff8a0`00e794c8 : P=fffff8a0`00e794c8 L=fffff8a0`008ee988 R=fffff8a0`00883f28
L=0000#0001 fffff8a0`008ee988 : P=fffff8a0`00e794c8 L=fffff8a0`0603db38 R=fffff8a0`00901988
L=0001#0001 fffff8a0`00883f28 : P=fffff8a0`00e794c8 L=00000000`00000000 R=00000000`00000000
L=0001#0002 fffff8a0`0603db38 : P=fffff8a0`008ee988 L=fffff8a0`020d04c8 R=00000000`00000000
L=0002#0001 fffff8a0`00901988 : P=fffff8a0`008ee988 L=fffff8a0`00987810 R=00000000`00000000
L=0002#0002 fffff8a0`020d04c8 : P=fffff8a0`0603db38 L=00000000`00000000 R=fffff8a0`00986060
L=0003#0001 fffff8a0`00987810 : P=fffff8a0`00901988 L=00000000`00000000 R=00000000`00000000
L=0003#0002 fffff8a0`00986060 : P=fffff8a0`020d04c8 L=00000000`00000000 R=fffff8a0`06104a38
L=0004#0001 fffff8a0`06104a38 : P=fffff8a0`00986060 L=00000000`00000000 R=00000000`00000000
kd> dq @rcx l4
fffff8a0`00e794c8  fffff8a0`00e794c8 fffff8a0`008ee988
fffff8a0`00e794d8  fffff8a0`00883f28 00000000`0022000a
kd>

別のブレークでも試してみました。今度は win32k のようです。スプレーの途中なので、必ずしも木のルートがパラメーターに来るわけではありません。

kd> !process -1 0
PROCESS fffffa80018dd940
    SessionId: 1  Cid: 09a0    Peb: 7f7daf3f000  ParentCid: 0998
    DirBase: 214f8000  ObjectTable: fffff8a00740c9c0  HandleCount: <Data Not Accessible>
    Image: explorer.exe
kd> k
Child-SP          RetAddr           Call Site
fffff880`04fcd518 fffff801`fe0f79eb nt!RtlSplay
fffff880`04fcd520 fffff960`0028d943 nt!RtlLookupElementGenericTable+0x3b
fffff880`04fcd550 fffff960`0028d67d win32k!GreUpdateSprite+0x183
fffff880`04fcd780 fffff960`001e8035 win32k!GreUpdateSpriteDevLockEnd+0x808
fffff880`04fcdab0 fffff960`001e5b5e win32k!DEVLOCKBLTOBJ::~DEVLOCKBLTOBJ+0x165
fffff880`04fcdb00 fffff960`001eb4db win32k!NtGdiBitBltInternal+0x9ce
fffff880`04fcdd60 fffff801`fe08e053 win32k!NtGdiBitBlt+0x5b
fffff880`04fcddd0 000007ff`38d2322a nt!KiSystemServiceCopyEnd+0x13
00000000`0257f058 000007ff`38d231f1 GDI32!NtGdiBitBlt+0xa
00000000`0257f060 000007ff`369273c6 GDI32!BitBlt+0xd1
(Inline Function) ——–`——– UxTheme!CPaintBuffer::_PaintTargetRect+0x5b
(Inline Function) ——–`——– UxTheme!CPaintBuffer::_PaintImmediate+0x134
(Inline Function) ——–`——– UxTheme!CPaintBuffer::EndPaint+0x172
(Inline Function) ——–`——– UxTheme!CPaintBufferPool::Impl::End+0x1a6
(Inline Function) ——–`——– UxTheme!CPaintBufferPool::EndBufferedPaint+0x1a9
00000000`0257f120 000007f7`db5f2637 UxTheme!EndBufferedPaint+0x1f6
00000000`0257f1b0 000007f7`db5f2023 Explorer!CTaskListWnd::_HandlePaint+0x434
00000000`0257f2b0 000007f7`db5f1210 Explorer!CTaskListWnd::v_WndProc+0x6f
00000000`0257f3a0 000007ff`387f3e95 Explorer!CImpWndProc::s_WndProc+0x91
00000000`0257f3e0 000007ff`387f2a62 USER32!UserCallWinProcCheckWow+0x18d
00000000`0257f4a0 000007ff`387f294d USER32!DispatchClientMessage+0xf8
00000000`0257f500 000007ff`3ab54b47 USER32!_fnDWORD+0x2d
00000000`0257f560 000007ff`387f203a ntdll!KiUserCallbackDispatcherContinue
00000000`0257f5e8 000007ff`387f204c USER32!NtUserDispatchMessage+0xa
00000000`0257f5f0 000007f7`db5f1131 USER32!DispatchMessageWorker+0x2af
00000000`0257f670 000007f7`db61b41e Explorer!CTray::_MessageLoop+0x122
00000000`0257f700 000007ff`36641d4c Explorer!CTray::MainThreadProc+0x86
00000000`0257f730 000007ff`38f4167e SHCORE!COplockFileHandle::v_GetHandlerCLSID+0x12c
00000000`0257f820 000007ff`3ab6c3f1 KERNEL32!BaseThreadInitThunk+0x1a
00000000`0257f850 00000000`00000000 ntdll!RtlUserThreadStart+0x1d
kd> !fext.dumptree @rcx
L=0000#0000 fffff901`007780c0 : P=fffff901`007560b0 L=fffff901`0071f1f0 R=fffff901`03c63d30
L=0000#0001 fffff901`0071f1f0 : P=fffff901`007780c0 L=00000000`00000000 R=00000000`00000000
L=0001#0001 fffff901`03c63d30 : P=fffff901`007780c0 L=fffff901`00742230 R=fffff901`03c1f280
L=0001#0002 fffff901`00742230 : P=fffff901`03c63d30 L=fffff901`03c17280 R=fffff901`03c51590
L=0002#0001 fffff901`03c1f280 : P=fffff901`03c63d30 L=00000000`00000000 R=fffff901`03c440c0
L=0002#0002 fffff901`03c17280 : P=fffff901`00742230 L=00000000`00000000 R=fffff901`006e3280
L=0003#0001 fffff901`03c51590 : P=fffff901`00742230 L=00000000`00000000 R=00000000`00000000
L=0003#0002 fffff901`03c440c0 : P=fffff901`03c1f280 L=00000000`00000000 R=fffff901`03c252b0
L=0003#0003 fffff901`006e3280 : P=fffff901`03c17280 L=00000000`00000000 R=00000000`00000000
L=0004#0001 fffff901`03c252b0 : P=fffff901`03c440c0 L=fffff901`0014b240 R=00000000`00000000
L=0004#0002 fffff901`0014b240 : P=fffff901`03c252b0 L=00000000`00000000 R=fffff901`03c4b400
L=0005#0001 fffff901`03c4b400 : P=fffff901`0014b240 L=00000000`00000000 R=00000000`00000000

広告

[Win32] [C++] id3lib を使ってみる

2011年、明けましておめでとうございます。今年初記事は、しばらく続いていた SAP ではなく、Visual Studio ネタで。

多くの人が MP3 形式の音楽ファイルを利用していることでしょう。このファイル形式には、曲名やアーティスト情報、いつの間にやら画像も含めることができるようになっています。携帯音楽プレーヤーなどで再生すると、曲情報やらCD のジャケット写真やらが表示されていい感じです。iTunes, Winamp などのプレーヤーはもちろん、Windows などの OS でも標準で曲情報の編集機能がついて、便利になりました。

MP3 以外の音楽ファイルについては調べていませんが、このようなメタ情報をファイルに付加する形式には幾つかあり、MP3 の場合は ID3 という形式が有名だったりします。

http://ja.wikipedia.org/wiki/ID3%E3%82%BF%E3%82%B0

この情報をプログラムから使いたい場合、フォーマットを調べてがりがり編集するのも手ですが (ID3 は、思ったより複雑なフォーマットではないらしいが・・・)、世の中にはきっと便利なライブラリがあるに違いなく、そういうのを利用するのが手っ取り早いです。

ID3.org のサイトを真面目に読むと、以下のページで幾つかのライブラリが紹介されています。検索すると、他にも多数のライブラリが存在していそうですが、まあ本家の情報が一番信頼できると思いますので・・・

http://www.id3.org/Implementations

最終的には、Win32 コンソールのアプリケーションを作るのが目的なので、C++ のライブラリである ID3Lib を使います。それにしても Win32 から ID3 ライブラリを使うための情報が少ないこと少ないこと!

http://id3lib.sourceforge.net/

とりあえず、2011 年 1 月 2 日の最新バージョンは 3.8.3 らしい。というか 2003 年で更新がストップしている・・・。とりあえず id3lib-3.8.3.zip をダウンロード。ファイルを解凍すると、昔懐かしい dsw, dsp といった拡張子のファイルがある。Visual Studio 6.0 か!動くだろうか。

win32.readme.first.txt を参考にシナリオ B を試すが、スタティック リンクだと Unicode でプロジェクトをビルドするときに、シンボルが被ったり足りなくなったりでうまくいかないため、ダイナミック リンクに変更。シナリオ C は以下。

C)***Your project wants to link id3lib dynamic: (instructions below for vc)
1) Rename config.h.win32 to config.h
2) include prj/id3lib.dsp and zlib/prj/zlib.dsp to your workspace (*note this is a different id3lib than above)
3) make your project dependend on id3lib, and make id3lib dependend on zlib
4) Add /D ID3LIB_LINKOPTION=3 to your project options (settings, C/C++ tab) (*note this is a different option than above)
5) Add the following include dirs to your program:
   /I <path_to_id3lib>\\include /I <path_to_id3lib>\\include\\id3 
6) (add your code which uses id3lib)
7) Compile.
8 ) dump id3lib.dll in your programs project dir.
9) distribute your program including id3lib.dll
(MS recommend you distribute it in your programs dir and not in system(32) to avoid version conficts)

(win32.readme.first.txt より抜粋)

作業はこんな感じ↓

  1. id3lib-3.8.3\config.h.win32 を config.h にリネーム
  2. id3lib-3.8.3\prj\id3lib.dsw を手元にある Visual Studio 2010 で開いてプロジェクトを変換
    (id3lib, zlib という 2 つのプロジェクトが含まれている)
  3. 作ろうとしているソリューション mosea に、変換したプロジェクト prj\id3lib.vcxproj と zlib\prj\zlib.vcxproj を追加
    image
  4. ソリューション設定からプロジェクトの依存関係を設定 (mosea ← id3lib ← zlib)
    image image
  5. mosea プロジェクトの、コンパイラ設定で定数を定義 (/D ID3LIB_LINKOPTION=3)
    image image
  6. include の検索ディレクトリに id3lib の所定のディレクトリを追加
    image image
  7. インポートライブラリをリンク
    image image
  8. ソリューションをビルド!
    Debug も Release もエラーなし。
    image image
  9. 最後に、ビルド後のイベントとして DLL ファイルを mosea プロジェクト側にコピーするコマンドを追加しておきます。
    image image
    こうしておくと、DLL を所定の位置にコピーし忘れることがありません。
    image

これでライブラリを使える環境が整いました。"Hello, ID3Lib" 的なプログラムとして、アーティスト情報を表示するコードを書いてみました。作りかけ感たっぷりですが、これでも 1 時間ぐらいかかったような。

//
// main.cpp
//

#include <Windows.h>
#include <stdio.h>
#include <iostream>
#include <id3.h>

using namespace std;

bool ParseParameters(int argc, wchar_t *argv[]);
bool ID3_Get(LPCSTR);
bool ID3_Set(LPCSTR);

/*

Usage:
mosea.exe [options] [set|get]

Options:
-f <file>

Example:
mosea.exe -f c:\hogehoge.mp3 get

*/

bool ParseParameters(int argc, wchar_t *argv[]) {
    if ( argc<=1 || argc%2!=0 ) {
        goto help;
    }
   
    int argCmd= 0;
    if ( wcscmp(argv[argc-1], L"get")==0 ) {
        argCmd= 0;
    }
    else if ( wcscmp(argv[argc-1], L"set")==0 ) {
        argCmd= 1;
    }
    else {
        goto help;
    }
   
    char argPath[MAX_PATH];

    for ( int i=1 ; i<argc-1 ; i+=2 ) {
        if ( wcscmp(argv[i], L"-f")==0 ) {
            WideCharToMultiByte(CP_ACP, WC_NO_BEST_FIT_CHARS,
                argv[i+1], -1, argPath, MAX_PATH, NULL, NULL);
        }
    }
   
    if ( argCmd==0 ) {
        ID3_Get(argPath);
    }
    else {
        ID3_Set(argPath);
    }

    return true;

help:
    wcout << endl << L"Usage:" << endl;
    wcout << L"mosea.exe [options] [set|get]" << endl << endl;

    wcout << L"Options:" << endl;
    wcout << L"-f <file>" << endl << endl;

    wcout << L"Example:" << endl;
    wcout << L"mosea.exe -f c:\\hogehoge.mp3 get" << endl << endl;
   
    return false;
}

bool ID3_Get(LPCSTR file) {
    ID3Tag *pITag= ID3Tag_New();
    if ( pITag==NULL )
        return false;

    ID3Tag_Link(pITag, file);

    ID3Frame *pIFrame= ID3Tag_FindFrameWithID(pITag, ID3FID_LEADARTIST);
    if ( pIFrame==NULL )
        return false;

    ID3Field *pIField= ID3Frame_GetField(pIFrame, ID3FN_TEXT);
    if ( pIField==NULL )
        return false;

    char title[1024];
    ID3Field_GetASCII(pIField, title, 1024);

    printf_s("Artist(ASC): %s\r\n", title);   
   
    return true;
}

bool ID3_Set(LPCSTR file) {
    return true;
}

int wmain(int argc, wchar_t *argv[]) {
    if ( !ParseParameters(argc, argv) )
        return 0;

    return 0;
}

大きな問題点として、Unicode に対応していないので、アーティスト情報に Unicode 文字が入っていると正しく表示できません。ええ、試しに Beyoncé の曲を使うと見事に文字化けました。そのへんの対応は ID3Lib の仕様をちゃんと読まないといけないので、即興では無理でした。

>mosea -f 001a.mp3 get
Artist(ASC): Beyonce

>mosea -f 001w.mp3 get
Artist(ASC): フフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフ
フフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフ
フフフフフp琮 ← Beyoncé だと文字化ける

COM っぽい使い方をしていますが、id3\tag.h をインクルードすると C++ クラスも使えるので、後で書き換える予定です。実際 ID3Lib は COM サーバーとしても使えるようなことが書いてありましたが、Win32 からはそのまま使った方が楽なので、そのへんの検証はパス。