Debugger Command Cheat Sheet (windbg/gdb/lldb)

たまに gdb や lldb を使うときは、いつも基本的なコマンドをググるのに時間を費やしているので、自分用にコマンドをまとめてみました。こうしてみると、やはり Windows Debugger のコマンドはシンプルで洗練されていて好き。gdb と lldb のコマンドは長すぎるし、そもそもソースコードがあることが前提で、最適化済みのコードや、アセンブリを見てデバッグすることがほとんど想定されていないように思える。随時更新予定です。

(2015/8/3 追記)
.gdbinit や .lldbinit という設定ファイルを作ってカスタム コマンドを定義する方法があるらしい。GitHub に、いろいろな人の設定ファイルが公開されているので、使いやすいものを選んで利用することも可能。

(このブログ、テーブルのスタイルが指定できない・・? 後で何とかしないと。)

Debugger Commands (Last update: Aug. 2, 2015)
Windows Debugger GDB LLDB
bp b br set [-n <func>] [-a <addr>]
bp <addr> "<commands>" commands # br command add
bl info break br list
bc* delete break br del -f
bc# d # br del #
be enable br enable
bd disable br disable
~ info threads th list
.frame frame frame select
gc c c
g <addr>    
q q q
p ni ni
t si si
gu fin fin
r info registers reg r
r rax=0 set $rax = 0 reg w rax 0
k bt bt
dv info locals frame variable
dv <var> p p, po
dt ptype type lookup
u . l1 info line
info frame
frame info
uf disass /r d -b
ln l *<addr> l <addr>
x info line image lookup -n
dd <addr> l10 x/16wx <addr> x/16wx <addr>
dq <addr> l10 x/16gx <addr> x/16gx <addr>
dc <addr> l10 x/16c <addr> x/16c <addr>
db <addr> l10 x/16b <addr> x/16x <addr>
u <addr> l10 x/16i <addr> x/16i <addr>
ed <addr> <value> set *(int*)<addr>=<value> memory write -s 4 <addr> <value>
windbg.exe C-x C-a
C-x C-1
C-x C-2
gui

Other cheat sheets

広告

[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

[.NET] [Asm] Managed Code Debugging with SOS extension

久々の更新です。ようやく .NET デバッグが実戦でも通用するレベルになってきたので、初歩を紹介します。もちろん .NET デバッグといっても Visual Studio を使うのではなく、ntsd やら windbg といった Windows デバッガーを使います。Windows デバッガーの利点としては、稼働環境へファイルをコピーするだけでいい、リモート デバッグが可能、Visual Studio より細かいことができる、などが挙げられます。コンピューターの動作を理解するのにも役立ちますし、慣れてくると Visual Studio より速くデバッグできるようになります。それと、デバッガーの黒い画面を開いて仕事をしていると、周りから見ても 「仕事をしている感」 が醸し出されて便利です。(なんだそれは

ただし、まだ .NET Framework の動きはほとんど理解しきれていないので、細かい説明は端折ります。そのうち覚えます。

.NET のデバッグを行うためには、SOS と呼ばれるデバッガー エクステンション DLL が必要になります。ただし、これは .NET Framework に含まれているので、別途ダウンロードする必要はありません。

%windir%\Microsoft.NET\Framework\<.NET バージョン>\SOS.dll
%windir%\Microsoft.NET\Framework64\<.NET バージョン>\SOS.dll

SOS とは、"Son Of Strike" の略です。じゃあ Strike って何ぞ、という話ですが、これは以下のブログにそのエピソードが詳細に書かれています。もともと CLR 開発チームが "Lightning" という名前で作っていたものを、デバッガー エクステンションにしたときに "Strike" という名前に変えて、そこから一部のコードを取り除いたものだから "Son Of Strike" だそうです。

この記事には、そんな小話だけでなく SOS に関する非常に詳細な説明が書かれています。

SOS Debugging of the CLR, Part 1 – Jason Zander’s blog – Site Home – MSDN Blogs
http://blogs.msdn.com/b/jasonz/archive/2003/10/21/53581.aspx

MSDN だとこんなページもあります。

SOS.dll (SOS Debugging Extension)
http://msdn.microsoft.com/en-us/library/bb190764.aspx

このブログでは、細かいことは抜きにして早速デバッグしてみましょう。シンボルの設定は必ず行って下さい。順番が逆ですが、デバッグ環境の作り方をそのうち記事にするかもしれません。

Use the Microsoft Symbol Server to obtain debug symbol files
http://support.microsoft.com/kb/311503/en

今回の検証環境はこんな感じです。
現時点で最新の環境を使っていますが、Windows 7 でも XP でも同じことができるはずです。

  • OS: Windows Server 2012
  • CLR: 4.0.30319.18010 (.NET Framework 4.5)
  • IDE: Visual Studio 2012
  • Debugger: 6.2.9200.16384 (Windows Kit 8.0)

まずは、適当なプログラムを書きます。C# です、お決まりですね。もちろん F# でもいいです。

using System;
using System.IO;

namespace cssandbox {
    class Program {
        static void Main(string[] args) {
            var Prog = new Program();
            Prog.Print(Console.Out);
        }

        string mMessage1;
        string mMessage2;

        Program() {
            mMessage1 = "Hello!";
            Sub();
        }

        Program(string s) {
            mMessage1 = s;
            Sub();
        }

        void Sub() {
            mMessage2 = DateTime.Now.ToString();
        }

        void Print(TextWriter Writer) {
            Writer.WriteLine(mMessage1);
            Writer.WriteLine(mMessage2);
        }

    }
}

これを Debug 構成でビルドして、デバッガーから起動します。私は ntsd 派なのでこんな感じです。ntdll!LdrpDoDebuggerBreak で止まるはずで、これはネイティブ コードのデバッグと同じです。というか、まだこのタイミングでは CLR がロードされていないので、マネージド コードは存在しません。

image

Microsoft (R) Windows Debugger Version 6.2.9200.16384 X86
Copyright (c) Microsoft Corporation. All rights reserved.

CommandLine: cssandbox.exe
Symbol search path is: srv*d:\websymbols*
http://msdl.microsoft.com/download/symbols
Executable search path is:
ModLoad: 005e0000 005e8000   cssandbox.exe
ModLoad: 77820000 77977000   ntdll.dll
ModLoad: 6f2f0000 6f33a000   C:\Windows\SysWOW64\MSCOREE.DLL
ModLoad: 77630000 77760000   C:\Windows\SysWOW64\KERNEL32.dll
ModLoad: 76f20000 76fc6000   C:\Windows\SysWOW64\KERNELBASE.dll
(9a4.b10): Break instruction exception – code 80000003 (first chance)
eax=00000000 ebx=00000003 ecx=be050000 edx=00000000 esi=00000000 edi=00000000
eip=778c054d esp=0076f864 ebp=0076f890 iopl=0         nv up ei pl zr na pe nc
cs=0023  ss=002b  ds=002b  es=002b  fs=0053  gs=002b             efl=00000246
ntdll!LdrpDoDebuggerBreak+0x2b:
778c054d cc              int     3
0:000>

 

ここで重要なのが、CLR が動作するプラットフォームと、利用するデバッガーのプラットフォームを一致させておくことです。OS が 64 bit だったとしても、デバッグするアプリが 32bit で動作する場合は、32bit のデバッガーを使わないといけません。SOS.dll はデバッグ対象の CLR とバージョンとプラットフォームが一致していないと動かないのですが、当然 64bit のデバッガー プロセスから 32bit の SOS.dll をロードできないため、このような制限が生まれます。デバッガーを起動する前にデバッグ対象の動作プラットフォームを調べておきましょう。

SOS のロード

SOS のロードで一番単純な方法は、以下のように SOS.dll のパスを直接指定する方法です。

0:000> .load C:\Windows\Microsoft.NET\Framework\v4.0.30319\SOS.dll

32bit デバッガーから 64bit の SOS を読もうとすると、以下のように怒られます。

0:000> .load C:\Windows\Microsoft.NET\Framework64\v4.0.30319\SOS.dll
The call to LoadLibrary(C:\Windows\Microsoft.NET\Framework64\v4.0.30319\SOS.dll) failed, Win32 error 0n193
    "%1 is not a valid Win32 application."
Please check your debugger configuration and/or network access.

以上のようにプラットフォームの違いは判別されますが、.NET Framework バージョンの違いは判別されないので、以下のように .NET 2.0 の SOS はロードできてしまいます。これだと後々の SOS のコマンドが正しく動きません。

0:000> .load C:\Windows\Microsoft.NET\Framework\v2.0.50727\SOS.dll

いちいち .NET Framework のバージョンを調べるのが面倒くさい、という人のために .loadby というコマンドが存在します。こんな感じに使います。

0:000> sxe ld:clr
0:000> g
ModLoad: 6d500000 6db92000   C:\Windows\Microsoft.NET\Framework\v4.0.30319\clr.dll
eax=00000000 ebx=00800000 ecx=00000000 edx=00000000 esi=00000000 edi=7e60d000
eip=77860fe8 esp=0076f4e4 ebp=0076f53c iopl=0         nv up ei pl nz na pe nc
cs=0023  ss=002b  ds=002b  es=002b  fs=0053  gs=002b             efl=00000206
ntdll!NtMapViewOfSection+0xc:
77860fe8 c22800          ret     28h
0:000> .loadby sos clr

.loadby を使うと、指定したモジュールと同じところにある拡張 DLL をロードさせることができます。したがって、CLR.dll がロードされた後のタイミングで、clr.dll と同じところの SOS をロードすることで、適切なバージョンの SOS をロードすることができます。

デバッグするプログラムによっては clr で .loadby できず、代わりに mscoreei や mscorwks を使うことがあります。実現したいことは、CLR と同じ SOS をロードするだけなので、困ったら CLR のバージョンを調べて絶対パス指定で .load すれば OK です。

ブレーク ポイント

デバッグは、ブレークポイントを設定するところから始まります。そんなわけで、前述のサンプル プログラムの Main 関数で止めてみましょう。ネイティブ コードと違って、x コマンドは使えません。

単純な方法は、SOS の !name2ee コマンドを使う方法です。一気にやるとこんな感じです。

0:000> sxe ld:clrjit
0:000> g
(9a4.b10): Unknown exception – code 04242420 (first chance)
ModLoad: 6ee60000 6eece000   C:\Windows\Microsoft.NET\Framework\v4.0.30319\clrjit.dll
eax=00000000 ebx=00800000 ecx=00000000 edx=00000000 esi=00000000 edi=7e60d000
eip=77860fe8 esp=0076e5dc ebp=0076e634 iopl=0         nv up ei pl nz na po nc
cs=0023  ss=002b  ds=002b  es=002b  fs=0053  gs=002b             efl=00000202
ntdll!NtMapViewOfSection+0xc:
77860fe8 c22800          ret     28h
0:000> !name2ee cssandbox!cssandbox.Program.Main
Module:      008e2e94
Assembly:    cssandbox.exe
Token:       06000001
MethodDesc:  008e37ac
Name:        cssandbox.Program.Main(System.String[])
Not JITTED yet. Use !bpmd -md 008e37ac to break on run.
0:000> !bpmd -md 008e37ac
MethodDesc = 008e37ac
Adding pending breakpoints…
0:000> g
(9a4.b10): CLR notification exception – code e0444143 (first chance)
JITTED cssandbox!cssandbox.Program.Main(System.String[])
Setting breakpoint: bp 00AA0077 [cssandbox.Program.Main(System.String[])]
Breakpoint 0 hit
eax=00000000 ebx=0076f31c ecx=024a22cc edx=00000000 esi=00000000 edi=0076f290
eip=00aa0077 esp=0076f264 ebp=0076f278 iopl=0         nv up ei pl zr na pe nc
cs=0023  ss=002b  ds=002b  es=002b  fs=0053  gs=002b             efl=00000246
00aa0077 90              nop
0:000>

ブレークポイントを設定するのに使いたいコマンドは !bpmd です。bp コマンドは、メモリにロードされているコードのアドレスを直接指定しますが、上記の例では、!name2ee コマンドを使って取得した Method Descriptor という値を使ってブレーク ポイントを設定しています。

コマンドの出力にもありますが、.NET の大きな特徴として、実行時 (JIT) コンパイルが行われることが挙げられます。当然、まだコンパイルされていないメソッドに対して bp コマンドは使えません。そこで、Method Descriptor を間接的に使ってブレークポイントを設定するわけです。Method Descriptor や EEClass といった CLR の内部構造については、以下の記事などを参考にして下さい。まだよく知らないのですわ・・・スミマセン。

JIT and Run: .NET Framework の内部: CLR がランタイム オブジェクトを作成するしくみ — MSDN Magazine, 2005 年 5 月
 http://msdn.microsoft.com/ja-jp/magazine/ee216336.aspx

アプリケーションを起動して clr.dll がロード直後のタイミングでは、CLR の内部構造がほとんど何もできていないので、!name2ee すら実行することができず、以下のようなエラーが出ます。

0:000> !name2ee cssandbox!cssandbox.Program.Main
Failed to obtain AppDomain data.
Failed to request module list.

そこで今回は、clrjit.dll がロードされるタイミングを sxe で止めて、そのときに !name2ee を実行しました。

!bpmd した後の出力結果を見ると、JIT されたタイミングで bp コマンドが実行されているのが分かります(紫字部分)。JIT されてしまえば、ネイティブ コードと同じように扱うことができます。ただし、コンパイルされたコードはネイティブのものとは微妙に印象が異なるのが面白いところです。

ブレークポイントで止まっている状態で、以下のコマンドを実行してみます。

0:000> bl
0 e 00aa0077     0001 (0001)  0:****
0:000> !name2ee cssandbox!cssandbox.Program.Main
Module:      008e2e94
Assembly:    cssandbox.exe
Token:       06000001
MethodDesc:  008e37ac
Name:        cssandbox.Program.Main(System.String[])
JITTED Code Address: 00aa0050
0:000> r
eax=00000000 ebx=0076f31c ecx=024a22cc edx=00000000 esi=00000000 edi=0076f290
eip=00aa0077 esp=0076f264 ebp=0076f278 iopl=0         nv up ei pl zr na pe nc
cs=0023  ss=002b  ds=002b  es=002b  fs=0053  gs=002b             efl=00000246
00aa0077 90              nop
0:000> k
ChildEBP RetAddr
WARNING: Frame IP not in any known module. Following frames may be wrong.
0076f278 6d502652 0xaa0077
0076f284 6d51264f clr!CallDescrWorkerInternal+0x34
0076f2d8 6d512e95 clr!CallDescrWorkerWithHandler+0x6b
0076f350 6d5c74ec clr!MethodDescCallSite::CallTargetWorker+0x152
0076f47c 6d5c7610 clr!RunMain+0x1aa
0076f6f0 6d651dc4 clr!Assembly::ExecuteMainMethod+0x124
0076fbf4 6d651e67 clr!SystemDomain::ExecuteMainMethod+0x614
0076fc50 6d651f7a clr!ExecuteEXE+0x4c
0076fc90 6d65416a clr!_CorExeMainInternal+0xdc
0076fccc 6f27f5a3 clr!_CorExeMain+0x4d
0076fd04 6f2f7efd mscoreei!_CorExeMain+0x10a
0076fd1c 6f2f4de3 MSCOREE!ShellShim__CorExeMain+0x7d
0076fd24 77658543 MSCOREE!_CorExeMain_Exported+0x8
0076fd30 7787ac69 KERNEL32!BaseThreadInitThunk+0xe
0076fd74 7787ac3c ntdll!__RtlUserThreadStart+0x72
0076fd8c 00000000 ntdll!_RtlUserThreadStart+0x1b

まず、!bpmd によって自動的に bp コマンドが実行されたので、bl で確認できます。!name2ee コマンドを実行すると、JIT されたコードが 00aa0050 にロードされていることが分かります。bp された場所と若干ずれていますね。本当は 00aa0077 ではなく 00aa0050 で止まってほしいところです。止めたいところを !name2ee で調べて、JIT されていれば、bp コマンドを手動で打つこともできます。

また、r や k といったいつものコマンドも使うことができます。ただし、0xaa0077 のアドレスなどはシンボル名で解決されていません。スタックを見ると、clr.dll から cssandbox.exe が呼ばれていることが分かります。ステップ実行もできます。

変数を見る

目的の所で止めたら、変数の値を見たくなります。頑張れば dd コマンドを使えないこともないですが、.NET オブジェクトについては、SOS のエクステンションに頼ることになります。

cssandbox.exe に適当なパラメーターを指定して起動し、args の値を見る場合の例を示します。

Microsoft (R) Windows Debugger Version 6.2.9200.16384 X86
Copyright (c) Microsoft Corporation. All rights reserved.

CommandLine: cssandbox.exe ABCD 漢字
Symbol search path is: srv*d:\websymbols*
http://msdl.microsoft.com/download/symbols
Executable search path is:
ModLoad: 00af0000 00af8000   cssandbox.exe
ModLoad: 77820000 77977000   ntdll.dll
ModLoad: 6f2f0000 6f33a000   C:\Windows\SysWOW64\MSCOREE.DLL
ModLoad: 77630000 77760000   C:\Windows\SysWOW64\KERNEL32.dll
ModLoad: 76f20000 76fc6000   C:\Windows\SysWOW64\KERNELBASE.dll
(11a4.cf0): Break instruction exception – code 80000003 (first chance)
eax=00000000 ebx=00000003 ecx=18f00000 edx=00000000 esi=00000000 edi=00000000
eip=778c054d esp=00c7f76c ebp=00c7f798 iopl=0         nv up ei pl zr na pe nc
cs=0023  ss=002b  ds=002b  es=002b  fs=0053  gs=002b             efl=00000246
ntdll!LdrpDoDebuggerBreak+0x2b:
778c054d cc              int     3
0:000> sxe ld:clrjit
0:000> g
(11a4.cf0): Unknown exception – code 04242420 (first chance)
ModLoad: 6ee60000 6eece000   C:\Windows\Microsoft.NET\Framework\v4.0.30319\clrjit.dll
eax=00000000 ebx=00800000 ecx=00000000 edx=00000000 esi=00000000 edi=7ef7d000
eip=77860fe8 esp=00c7e4ec ebp=00c7e544 iopl=0         nv up ei pl nz na po nc
cs=0023  ss=002b  ds=002b  es=002b  fs=0053  gs=002b             efl=00000202
ntdll!NtMapViewOfSection+0xc:
77860fe8 c22800          ret     28h
0:000> .loadby sos clr
0:000> !bpmd cssandbox.exe cssandbox.Program.Main
Found 1 methods in module 00d72e94…
MethodDesc = 00d737ac
Adding pending breakpoints…
0:000> g
(11a4.cf0): CLR notification exception – code e0444143 (first chance)
JITTED cssandbox!cssandbox.Program.Main(System.String[])
Setting breakpoint: bp 01220077 [cssandbox.Program.Main(System.String[])]
Breakpoint 0 hit
eax=00000000 ebx=00c7f22c ecx=02d122cc edx=00000000 esi=00000000 edi=00c7f1a0
eip=01220077 esp=00c7f174 ebp=00c7f188 iopl=0         nv up ei pl zr na pe nc
cs=0023  ss=002b  ds=002b  es=002b  fs=0053  gs=002b             efl=00000246
01220077 90              nop
0:000> !dso
OS Thread Id: 0xcf0 (0)
ESP/REG  Object   Name
ecx      02d122cc System.Object[]    (System.String[])
00C7F184 02d122cc System.Object[]    (System.String[])
00C7F200 02d122cc System.Object[]    (System.String[])
00C7F35C 02d122cc System.Object[]    (System.String[])
00C7F394 02d122cc System.Object[]    (System.String[])
0:000> !da 02d122cc
Name:        System.String[]
MethodTable: 6c8eae88
EEClass:     6c5abb70
Size:        24(0x18) bytes
Array:       Rank 1, Number of elements 2, Type CLASS
Element Methodtable: 6c93afb0
[0] 02d122e4
[1] 02d122fc
0:000> !do 02d122e4
Name:        System.String
MethodTable: 6c93afb0
EEClass:     6c54486c
Size:        22(0x16) bytes
File:        C:\Windows\Microsoft.Net\assembly\GAC_32\mscorlib\v4.0_4.0.0.0__b77a5c561934e089\mscorl
ib.dll
String:      ABCD
Fields:
      MT    Field   Offset                 Type VT     Attr    Value Name
6c93c770  40000aa        4         System.Int32  1 instance        4 m_stringLength
6c93b9a8  40000ab        8          System.Char  1 instance       41 m_firstChar
6c93afb0  40000ac        c        System.String  0   shared   static Empty
    >> Domain:Value  010234f0:NotInit  <<
0:000> !do -nofields 02d122fc
Name:        System.String
MethodTable: 6c93afb0
EEClass:     6c54486c
Size:        18(0x12) bytes
File:        C:\Windows\Microsoft.Net\assembly\GAC_32\mscorlib\v4.0_4.0.0.0__b77a5c561934e089\mscorl
ib.dll
String:      漢字
0:000>

Main 関数で止めた後、!dso と !do コマンドを呼び出しています。!dso は、スタックに積まれているオブジェクトの一覧を表示するもので、!do はオブジェクトを表示するコマンドです。.オブジェクトが型情報を持っているので、dt と違って型を明示する必要がなくて便利です。dynamic 型でやるとどうなるんですかね。配列の場合は !do ではなく !da を使います。

!dso の結果を見ると args の値が ecx レジスターに入っています。実際、.NET のメソッドは fastcall で呼ばれるようです。

アセンブラを見る

!bpmd を使えばメソッドの先頭で止めることができますが、メソッドの途中で止める場合には、JIT されたコードのアセンブラを見ないといけません。ネイティブと同じように u や uf コマンドを使うことができます。例えば Main メソッドのアセンブラを uf で見るとこんな感じです。

0:000> !name2ee cssandbox cssandbox.Program.Main
Module:      00d72e94
Assembly:    cssandbox.exe
Token:       06000001
MethodDesc:  00d737ac
Name:        cssandbox.Program.Main(System.String[])
JITTED Code Address: 01220050
0:000> uf 01220050
01220050 55              push    ebp
01220051 8bec            mov     ebp,esp
01220053 83ec14          sub     esp,14h
01220056 33c0            xor     eax,eax
01220058 8945f4          mov     dword ptr [ebp-0Ch],eax
0122005b 8945f0          mov     dword ptr [ebp-10h],eax
0122005e 8945ec          mov     dword ptr [ebp-14h],eax
01220061 894dfc          mov     dword ptr [ebp-4],ecx
01220064 833d6031d70000  cmp     dword ptr ds:[0D73160h],0
0122006b 7405            je      01220072

0122006d e84270576c      call    clr!JIT_DbgIsJustMyCode (6d7970b4)

01220072 33d2            xor     edx,edx
01220074 8955f8          mov     dword ptr [ebp-8],edx
01220077 90              nop
01220078 b9f037d700      mov     ecx,0D737F0h
0122007d e87e20b4ff      call    00d62100
01220082 8945f4          mov     dword ptr [ebp-0Ch],eax
01220085 8b4df4          mov     ecx,dword ptr [ebp-0Ch]
01220088 ff151038d700    call    dword ptr ds:[0D73810h]
0122008e 8b45f4          mov     eax,dword ptr [ebp-0Ch]
01220091 8945f8          mov     dword ptr [ebp-8],eax
01220094 8b45f8          mov     eax,dword ptr [ebp-8]
01220097 8945f0          mov     dword ptr [ebp-10h],eax
0122009a e875d3686b      call    mscorlib_ni+0x36d414 (6c8ad414)
0122009f 8945ec          mov     dword ptr [ebp-14h],eax
012200a2 8b4df0          mov     ecx,dword ptr [ebp-10h]
012200a5 8b55ec          mov     edx,dword ptr [ebp-14h]
012200a8 3909            cmp     dword ptr [ecx],ecx
012200aa ff15e037d700    call    dword ptr ds:[0D737E0h]
012200b0 90              nop
012200b1 90              nop
012200b2 8be5            mov     esp,ebp
012200b4 5d              pop     ebp
012200b5 c3              ret
0:000>

いやー、硬派ですね。というのも、ほとんどの call 命令のオペランドが生アドレスだからでしょうか。

実は、アセンブラを見る時も SOS に頼ることができます。それが !U です。また、プライベート シンボルがある場合には、.lines を使うことでネイティブ コードと同じように行番号を表示させることができます。こんな感じです。

0:000> .lines
Line number information will be loaded
0:000> !U .
Normal JIT generated code
cssandbox.Program.Main(System.String[])
Begin 01220050, size 66

d:\VSDev\Projects\cssandbox\Program.cs @ 11:
01220050 55              push    ebp
01220051 8bec            mov     ebp,esp
01220053 83ec14          sub     esp,14h
01220056 33c0            xor     eax,eax
01220058 8945f4          mov     dword ptr [ebp-0Ch],eax
0122005b 8945f0          mov     dword ptr [ebp-10h],eax
0122005e 8945ec          mov     dword ptr [ebp-14h],eax
01220061 894dfc          mov     dword ptr [ebp-4],ecx
01220064 833d6031d70000  cmp     dword ptr ds:[0D73160h],0
0122006b 7405            je      01220072
0122006d e84270576c      call    clr!JIT_DbgIsJustMyCode (6d7970b4)
01220072 33d2            xor     edx,edx
01220074 8955f8          mov     dword ptr [ebp-8],edx
>>> 01220077 90              nop

d:\VSDev\Projects\cssandbox\Program.cs @ 12:
01220078 b9f037d700      mov     ecx,0D737F0h (MT: cssandbox.Program)
0122007d e87e20b4ff      call    00d62100 (JitHelp: CORINFO_HELP_NEWSFAST)
01220082 8945f4          mov     dword ptr [ebp-0Ch],eax
01220085 8b4df4          mov     ecx,dword ptr [ebp-0Ch]
01220088 ff151038d700    call    dword ptr ds:[0D73810h] (cssandbox.Program..ctor(), mdToken: 06000002)
0122008e 8b45f4          mov     eax,dword ptr [ebp-0Ch]
01220091 8945f8          mov     dword ptr [ebp-8],eax

d:\VSDev\Projects\cssandbox\Program.cs @ 13:
01220094 8b45f8          mov     eax,dword ptr [ebp-8]
01220097 8945f0          mov     dword ptr [ebp-10h],eax
0122009a e875d3686b      call    mscorlib_ni+0x36d414 (6c8ad414) (System.Console.get_Out(), mdToken: 06000945)
0122009f 8945ec          mov     dword ptr [ebp-14h],eax
012200a2 8b4df0          mov     ecx,dword ptr [ebp-10h]
012200a5 8b55ec          mov     edx,dword ptr [ebp-14h]
012200a8 3909            cmp     dword ptr [ecx],ecx
012200aa ff15e037d700    call    dword ptr ds:[0D737E0h] (cssandbox.Program.Print(System.IO.TextWriter), mdToken: 06000005)
012200b0 90              nop

d:\VSDev\Projects\cssandbox\Program.cs @ 14:
012200b1 90              nop
012200b2 8be5            mov     esp,ebp
012200b4 5d              pop     ebp
012200b5 c3              ret
0:000>

かなり読みやすくなりました。

コンストラクターについて

上のアセンブラで call 命令の部分を見ると、cssandbox.Program..ctor() というメソッドを呼び出す箇所があることに気づきます。コードを見ればすぐに分かりますが、これはコンストラクターです。コンストラクターは、内部的に .ctor というメソッドとして扱われるようです。ctor の先頭のドットも含めてメソッド名なので、完全修飾名にするとドットが連続する不思議な名前になります。

当然、!name2ee で Method Descriptor を見つけることもできます。Program.Program や Program.new のような名前では検索できないことを確認して下さい。今回は 2 つのコンストラクターをオーバーロードしていますので、両方とも検出され、パラメーターの種類も出してくれます。

0:000> !name2ee cssandbox cssandbox.Program.Program
Module:      00d72e94
Assembly:    cssandbox.exe
0:000> !name2ee cssandbox cssandbox.Program.new
Module:      00d72e94
Assembly:    cssandbox.exe
0:000> !name2ee cssandbox cssandbox.Program..ctor
Module:      00d72e94
Assembly:    cssandbox.exe
Token:       06000002
MethodDesc:  00d737b8
Name:        cssandbox.Program..ctor()
Not JITTED yet. Use !bpmd -md 00d737b8 to break on run.
———————–
Token:       06000003
MethodDesc:  00d737c0
Name:        cssandbox.Program..ctor(System.String)
Not JITTED yet. Use !bpmd -md 00d737c0 to break on run.
0:000>

[Win32] [x86 Assembler] Time Stamp Counter

久々にアセンブラ関連の記事を書きます。需要はあるのだろうか・・・

プログラムの中で、実行時間を計測したくなる場面は多々あります。そんなときはどんな API を使うでしょうか。パッと思いつくのは timeGetTime とか GetTickCount ですかね。GetSystemTime を使う人はあまりいないでしょう。QueryPerformanceCounter というのもあります。

timeGetTime 関数
http://msdn.microsoft.com/ja-jp/library/cc428795.aspx

GetTickCount 関数
http://msdn.microsoft.com/ja-jp/library/cc429827.aspx

GetSystemTime function
http://msdn.microsoft.com/en-us/library/ms724390(v=vs.85).aspx

QueryPerformanceCounter function
http://msdn.microsoft.com/en-us/library/ms644904(v=vs.85).aspx

どれを使ってもいいわけですが、とにかく厳密に測りたい場合や、一瞬で終わる処理を計測したい場合には、これらの API を使うと関数呼び出し時の時間のロスが無視できなくなります。もちろん、その分のロスを計算して結果から引くというのが普通ですが、アセンブラを使うという手も一応あります。

それが、RDTSC (=Read Time Stamp Counter) 命令です。詳細は IA-32 の仕様書に書いてありますが、RDTSC を実行すると、1 命令で Tick 値を取得することができます。しかも単位はクロック単位です。Pentium 以降の IA-32 アーキテクチャーでは、プロセッサーごとに TSC (=Time Stamp Counter) という 64bit カウンターが MSR (マシン固有レジスタ) に含まれており、RDTSC はこれを EDX:EAX レジスターにロードする命令です。

こんなプログラムを書いてみました。後でいろいろ遊ぶ伏線として、幾つかの関数を読んでいます。

なお、開発環境、実行環境は同じで、こんな感じです。

  • OS: Windows 7 SP1 (x64)
  • IDE: Visual Studio 2010 SP1
  • CPU: Core i3 530 2.93GHz (予算がないのだ)

//
// main.cpp
//

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

__int64 __fastcall rdtsc() {
    __asm {
        rdtsc
    }
}

__inline __int64 rdtsc_inline() {
    __asm {
        rdtsc
    }
}

void rdtsc_x86(long long int *ll) {
    __asm {
        rdtsc
        mov ecx, [ll]
        mov [ecx], eax
        mov [ecx+4], edx
    }
}

int wmain(int argc, wchar_t *argv[]) {
    if ( argc<2 )
        return ERROR_INVALID_PARAMETER;
   
    LARGE_INTEGER ll, ll1, ll2, ll3;

    wprintf(L"—–\n");

    DWORD wait= _wtoi(argv[1]);
    while (1) {
        QueryPerformanceCounter(&ll);
        ll1.QuadPart= rdtsc();
        ll2.QuadPart= rdtsc_inline();
        rdtsc_x86((long long*)&ll3);

        wprintf(L"QPC:  0x%08x`%08x\n", ll.HighPart, ll.LowPart);
        wprintf(L"ASM1: 0x%08x`%08x\n", ll1.HighPart, ll1.LowPart);
        wprintf(L"ASM2: 0x%08x`%08x\n", ll2.HighPart, ll2.LowPart);
        wprintf(L"ASM3: 0x%08x`%08x\n", ll3.HighPart, ll3.LowPart);
   
        wprintf(L"–\n");
        Sleep(wait);
    }

    return 0;
}

実行すると、こんな感じです。

E:\Visual Studio 2010\Projects\asmtest\Release> asmtest 1000
—–
QPC:  0x0000000a`cdfd9d44
ASM1: 0x00002b37`f6751321
ASM2: 0x00002b37`f675135e
ASM3: 0x00002b37`f6751394

QPC:  0x0000000a`ce28d20e
ASM1: 0x00002b38`a3483a0d
ASM2: 0x00002b38`a3483a4a
ASM3: 0x00002b38`a3483a80

QPC:  0x0000000a`ce54577b
ASM1: 0x00002b39`515df112
ASM2: 0x00002b39`515df142
ASM3: 0x00002b39`515df172

QPC:  0x0000000a`ce802c82
ASM1: 0x00002b3a`00b20afe
ASM2: 0x00002b3a`00b20b2e
ASM3: 0x00002b3a`00b20b5e

ASM1 の結果に絞って間隔を計算すると、ACD326EC, AE15B705, AF5419EC となり、平均値は AE14529F = 2,920,567,455 となります。プロセッサーのクロックが 2.93GHz なので、RDTSC は確かにクロック単位の値を取得しています。

サンプルでは 3 つの関数を書きました。

  • rdtsc ・・・ __fastcall 呼び出し規約
  • rdtsc_inline ・・・ __inline でインライン展開
  • rdtsc_x86 ・・・ パラメーター渡し

ソースコードを既定の Release ビルド設定でビルドすると、rdtsc_inline だけでなく rdtsc_x86 もインライン展開されます。rdtsc に __fastcall をつけたのはインライン展開を防ぐためで、__stdcall や __cdecl では展開されてしまいます。

コンパイルされた関数のアセンブラを見てみます。

0:000> uf asmtest!rdtsc
asmtest!rdtsc [e:\visual studio 2010\projects\asmtest\main.cpp @ 9]:
    9 011e1000 0f31            rdtsc
   13 011e1002 c3              ret

0:000> uf asmtest!wmain
asmtest!wmain [e:\visual studio 2010\projects\asmtest\main.cpp @ 30]:
   30 011e1010 55              push    ebp
   30 011e1011 8bec            mov     ebp,esp
   30 011e1013 83e4f8          and     esp,0FFFFFFF8h
   30 011e1016 83ec2c          sub     esp,2Ch
   31 011e1019 837d0802        cmp     dword ptr [ebp+8],2
   31 011e101d 53              push    ebx
   31 011e101e 56              push    esi
   31 011e101f 57              push    edi
   31 011e1020 7d0c            jge     asmtest!wmain+0x1e (011e102e)

asmtest!wmain+0x12 [e:\visual studio 2010\projects\asmtest\main.cpp @ 55]:
   55 011e1022 5f              pop     edi
   55 011e1023 5e              pop     esi
   55 011e1024 b857000000      mov     eax,57h
   55 011e1029 5b              pop     ebx
   55 011e102a 8be5            mov     esp,ebp
   55 011e102c 5d              pop     ebp
   55 011e102d c3              ret

asmtest!wmain+0x1e [e:\visual studio 2010\projects\asmtest\main.cpp @ 36]:
   36 011e102e 8b359c201e01    mov     esi,dword ptr [asmtest!_imp__wprintf (011e209c)]
   36 011e1034 68f4201e01      push    offset asmtest!`string’ (011e20f4)
   36 011e1039 ffd6            call    esi
   38 011e103b 8b450c          mov     eax,dword ptr [ebp+0Ch]
   38 011e103e 8b4804          mov     ecx,dword ptr [eax+4]
   38 011e1041 51              push    ecx
   38 011e1042 ff15a0201e01    call    dword ptr [asmtest!_imp___wtoi (011e20a0)]
   38 011e1048 83c408          add     esp,8
   38 011e104b 89442414        mov     dword ptr [esp+14h],eax
   38 011e104f 90              nop

asmtest!wmain+0x40 [e:\visual studio 2010\projects\asmtest\main.cpp @ 40]:
   40 011e1050 8d542418        lea     edx,[esp+18h]
   40 011e1054 52              push    edx
   40 011e1055 ff1500201e01    call    dword ptr [asmtest!_imp__QueryPerformanceCounter (011e2000)]
   41 011e105b e8a0ffffff      call    asmtest!rdtsc (011e1000)
   41 011e1060 8bd8            mov     ebx,eax
   41 011e1062 8954242c        mov     dword ptr [esp+2Ch],edx
   42 011e1066 0f31            rdtsc
   42 011e1068 8bf8            mov     edi,eax
   43 011e106a 8d442420        lea     eax,[esp+20h]
   43 011e106e 89542434        mov     dword ptr [esp+34h],edx
   43 011e1072 89442410        mov     dword ptr [esp+10h],eax

   43 011e1076 0f31            rdtsc
   43 011e1078 8b4c2410        mov     ecx,dword ptr [esp+10h]
   43 011e107c 8901            mov     dword ptr [ecx],eax
   43 011e107e 895104          mov     dword ptr [ecx+4],edx
   45 011e1081 8b4c2418        mov     ecx,dword ptr [esp+18h]
   45 011e1085 8b54241c        mov     edx,dword ptr [esp+1Ch]
   45 011e1089 51              push    ecx
   45 011e108a 52              push    edx
   45 011e108b 6804211e01      push    offset asmtest!`string’ (011e2104)
   45 011e1090 ffd6            call    esi
   46 011e1092 8b442438        mov     eax,dword ptr [esp+38h]
   46 011e1096 53              push    ebx
   46 011e1097 50              push    eax
   46 011e1098 682c211e01      push    offset asmtest!`string’ (011e212c)
   46 011e109d ffd6            call    esi
   47 011e109f 8b4c244c        mov     ecx,dword ptr [esp+4Ch]
   47 011e10a3 57              push    edi
   47 011e10a4 51              push    ecx
   47 011e10a5 6854211e01      push    offset asmtest!`string’ (011e2154)
   47 011e10aa ffd6            call    esi
   48 011e10ac 8b542444        mov     edx,dword ptr [esp+44h]
   48 011e10b0 8b442448        mov     eax,dword ptr [esp+48h]
   48 011e10b4 52              push    edx
   48 011e10b5 50              push    eax
   48 011e10b6 687c211e01      push    offset asmtest!`string’ (011e217c)
   48 011e10bb ffd6            call    esi
   50 011e10bd 68a4211e01      push    offset asmtest!`string’ (011e21a4)
   50 011e10c2 ffd6            call    esi
   51 011e10c4 8b4c2448        mov     ecx,dword ptr [esp+48h]
   51 011e10c8 83c434          add     esp,34h
   51 011e10cb 51              push    ecx
   51 011e10cc ff1504201e01    call    dword ptr [asmtest!_imp__Sleep (011e2004)]
   52 011e10d2 e979ffffff      jmp     asmtest!wmain+0x40 (011e1050)

関数 rdtsc は、RDTSC を実行するだけの関数になっています。インライン展開された関数は茶色と紫色で示しています。戻り値が EDX:EAX であることが考慮されて、うまく動くようになっています。edx レジスタが考慮されないのかと予想していましたが、インライン展開されても 64bit の戻り値に影響はないようです。

さて、次に QueryPerformanceCounter に注目してみます。上述の MSDN の説明を見ると、こんな注意書きがあります。

On a multiprocessor computer, it should not matter which processor is called. However, you can get different results on different processors due to bugs in the basic input/output system (BIOS) or the hardware abstraction layer (HAL). To specify processor affinity for a thread, use the SetThreadAffinityMask function.

「マルチ プロセッサーでも使えますよ。でも BIOS や HAL にバグがあるとプロセッサー毎に異なる結果が返ってくることがありますよ。」 ということです。BIOS や HAL のバグって言われても・・・既知のバグなら直しとけよ、としか言いようがありません。QueryPerformanceCounter を使うときは SetThreadAffinityMask を必ず使えってことなのでしょうか。微妙です。

少なくとも、QueryPerformanceCounter にはマルチプロセッサーを考慮した実装がなされていることが分かります。TSC カウンターはプロセッサー毎なので、先ほどの RDTSC 命令を呼び出す関数は、プロセッサー毎に異なる値を返します。よって、途中で割り込みが入って実行 CPU が変わることが予想される場合には使えません。

デバッグしていて気づきましたが、実は QueryPerformanceCounter は内部的に RDTSC を呼び出しています。これを確かめてみます。

0:000> x kernel32!QueryPerformanceCounter
76921732 kernel32!QueryPerformanceCounter = <no type information>
0:000> u 76921732
kernel32!QueryPerformanceCounter:
76921732 ff25d40d9276    jmp     dword ptr [kernel32!_imp__QueryPerformanceCounter (76920dd4)]
76921738 90              nop
76921739 90              nop
7692173a 90              nop
7692173b 90              nop
7692173c 90              nop
kernel32!IsDBCSLeadByte:
7692173d ff2568079276    jmp     dword ptr [kernel32!_imp__IsDBCSLeadByte (76920768)]
76921743 90              nop
0:000> dd 76920dd4 l1
76920dd4  775e8884
0:000> ln 775e8884
(775e8884)   ntdll!RtlQueryPerformanceCounter   |  (775e88e2)   ntdll!EtwEventEnabled
Exact matches:
    ntdll!RtlQueryPerformanceCounter = <no type information>

QueryPerformanceCounter は Kernel32.dll の関数ですが、これは単なるラッパーで、実体は ntdll.dll に実装されている RtlQueryPerformanceCounter であることが分かります。この関数のアセンブラを見てみます。

0:000> uf ntdll!RtlQueryPerformanceCounter
ntdll!RtlQueryPerformanceCounter:
775e8884 8bff            mov     edi,edi
775e8886 55              push    ebp
775e8887 8bec            mov     ebp,esp
775e8889 51              push    ecx
775e888a 51              push    ecx
775e888b f605ed02fe7f01  test    byte ptr [SharedUserData+0x2ed (7ffe02ed)],1
775e8892 0f840bf50400    je      ntdll!RtlQueryPerformanceCounter+0x55 (77637da3)

ntdll!RtlQueryPerformanceCounter+0x10:
775e8898 56              push    esi

ntdll!RtlQueryPerformanceCounter+0x11:
775e8899 8b0db803fe7f    mov     ecx,dword ptr [SharedUserData+0x3b8 (7ffe03b8)]
775e889f 8b35bc03fe7f    mov     esi,dword ptr [SharedUserData+0x3bc (7ffe03bc)]
775e88a5 a1b803fe7f      mov     eax,dword ptr [SharedUserData+0x3b8 (7ffe03b8)]
775e88aa 8b15bc03fe7f    mov     edx,dword ptr [SharedUserData+0x3bc (7ffe03bc)]
775e88b0 3bc8            cmp     ecx,eax
775e88b2 75e5            jne     ntdll!RtlQueryPerformanceCounter+0x11 (775e8899)

ntdll!RtlQueryPerformanceCounter+0x2c:
775e88b4 3bf2            cmp     esi,edx
775e88b6 75e1            jne     ntdll!RtlQueryPerformanceCounter+0x11 (775e8899)

ntdll!RtlQueryPerformanceCounter+0x30:
775e88b8 0f31            rdtsc
775e88ba 03c1            add     eax,ecx
775e88bc 0fb60ded02fe7f  movzx   ecx,byte ptr [SharedUserData+0x2ed (7ffe02ed)]
775e88c3 13d6            adc     edx,esi
775e88c5 c1e902          shr     ecx,2
775e88c8 e893ffffff      call    ntdll!_aullshr (775e8860)
775e88cd 8b4d08          mov     ecx,dword ptr [ebp+8]
775e88d0 8901            mov     dword ptr [ecx],eax
775e88d2 895104          mov     dword ptr [ecx+4],edx
775e88d5 5e              pop     esi

ntdll!RtlQueryPerformanceCounter+0x4e:
775e88d6 33c0            xor     eax,eax
775e88d8 40              inc     eax

ntdll!RtlQueryPerformanceCounter+0x51:
775e88d9 c9              leave
775e88da c20400          ret     4

ntdll!RtlQueryPerformanceCounter+0x55:
77637da3 8d45f8          lea     eax,[ebp-8]
77637da6 50              push    eax
77637da7 ff7508          push    dword ptr [ebp+8]
77637daa e8717ff9ff      call    ntdll!ZwQueryPerformanceCounter (775cfd20)
77637daf 85c0            test    eax,eax
77637db1 7d0d            jge     ntdll!RtlQueryPerformanceCounter+0x6f (77637dc0)

ntdll!RtlQueryPerformanceCounter+0x65:
77637db3 50              push    eax
77637db4 e89549fdff      call    ntdll!RtlSetLastWin32ErrorAndNtStatusFromNtStatus (7760c74e)

ntdll!RtlQueryPerformanceCounter+0x6b:
77637db9 33c0            xor     eax,eax
77637dbb e9190bfbff      jmp     ntdll!RtlQueryPerformanceCounter+0x51 (775e88d9)

ntdll!RtlQueryPerformanceCounter+0x6f:
77637dc0 837df800        cmp     dword ptr [ebp-8],0
77637dc4 0f850c0bfbff    jne     ntdll!RtlQueryPerformanceCounter+0x4e (775e88d6)

ntdll!RtlQueryPerformanceCounter+0x75:
77637dca 837dfc00        cmp     dword ptr [ebp-4],0
77637dce 0f85020bfbff    jne     ntdll!RtlQueryPerformanceCounter+0x4e (775e88d6)

ntdll!RtlQueryPerformanceCounter+0x7b:
77637dd4 6a78            push    78h
77637dd6 e814a5f9ff      call    ntdll!RtlSetLastWin32Error (775d22ef)
77637ddb ebdc            jmp     ntdll!RtlQueryPerformanceCounter+0x6b (77637db9)

RDTSC 命令がありました。さっきのサンプルプログラムでこの部分にブレークポイントを貼ると、確かに RDTSC が実行されていることが分かります。

0:002> bp 775e88b8
0:002> g
Breakpoint 0 hit
eax=00000000 ebx=14c34d78 ecx=00000000 edx=00000000 esi=00000000 edi=14c34da8
eip=775e88b8 esp=0045fe80 ebp=0045fe8c iopl=0         nv up ei pl zr na pe nc
cs=0023  ss=002b  ds=002b  es=002b  fs=0053  gs=002b             efl=00000246
ntdll!RtlQueryPerformanceCounter+0x30:
775e88b8 0f31            rdtsc
0:000> k
ChildEBP RetAddr
0045fe8c 00bb105b ntdll!RtlQueryPerformanceCounter+0x30
0045fecc 6a19263d asmtest!wmain+0x4b
0045ff18 7692339a MSVCR100!_initterm+0x13
0045ff24 775e9ed2 kernel32!BaseThreadInitThunk+0xe
0045ff64 775e9ea5 ntdll!__RtlUserThreadStart+0x70
0045ff7c 00000000 ntdll!_RtlUserThreadStart+0x1b

プログラムの実行結果を見ると分かりますが、QueryPerformanceCounter (以下 QPC と表記) と RDTSC の戻り値は全然違います。単位も異なっているようです。QPC の周波数は QueryPerformanceFrequency API で取得することができます。この環境で調べてみると、0x2BD8E6 = 2,852,116 となりました。クロックの 1/1000 ぐらいです。

QPC が RDTSC の値を元にしていることは確認できているので、何らかの計算をして周波数を調整していることになります。それが RtlQueryPerformanceCounter のアセンブラに隠されています。RDTSC の後のアセンブラをもう一度よく見てみます。

ntdll!RtlQueryPerformanceCounter+0x30:
775e88b8 0f31            rdtsc
775e88ba 03c1            add     eax,ecx
775e88bc 0fb60ded02fe7f  movzx   ecx,byte ptr [SharedUserData+0x2ed (7ffe02ed)]
775e88c3 13d6            adc     edx,esi
775e88c5 c1e902          shr     ecx,2
775e88c8 e893ffffff      call    ntdll!_aullshr (775e8860)
775e88cd 8b4d08          mov     ecx,dword ptr [ebp+8]
775e88d0 8901            mov     dword ptr [ecx],eax
775e88d2 895104          mov     dword ptr [ecx+4],edx
775e88d5 5e              pop     esi

ntdll!RtlQueryPerformanceCounter+0x4e:
775e88d6 33c0            xor     eax,eax
775e88d8 40              inc     eax

ntdll!RtlQueryPerformanceCounter+0x51:
775e88d9 c9              leave
775e88da c20400          ret     4

_anullshr という関数が怪しいですね。これを見てみます。

0:000> uf ntdll!_aullshr
ntdll!_aullshr:
775e8860 80f940          cmp     cl,40h
775e8863 7315            jae     ntdll!_aullshr+0x1a (775e887a)

ntdll!_aullshr+0x5:
775e8865 80f920          cmp     cl,20h
775e8868 7306            jae     ntdll!_aullshr+0x10 (775e8870)

ntdll!_aullshr+0xa:
775e886a 0fadd0          shrd    eax,edx,cl
775e886d d3ea            shr     edx,cl
775e886f c3              ret

ntdll!_aullshr+0x10:
775e8870 8bc2            mov     eax,edx
775e8872 33d2            xor     edx,edx
775e8874 80e11f          and     cl,1Fh
775e8877 d3e8            shr     eax,cl
775e8879 c3              ret

ntdll!_aullshr+0x1a:
775e887a 33c0            xor     eax,eax
775e887c 33d2            xor     edx,edx
775e887e c3              ret

eax と edx をcl だけ右シフトしています。これで QPC の結果が小さくなるわけです。
では ECX レジスタはどこから来ていたでしょうか。

775e88bc 0fb60ded02fe7f  movzx   ecx,byte ptr [SharedUserData+0x2ed (7ffe02ed)]
775e88c3 13d6            adc     edx,esi
775e88c5 c1e902          shr     ecx,2
775e88c8 e893ffffff      call    ntdll!_aullshr (775e8860)

SharedUserData から来ています。これは共有ユーザーモードページと呼ばれるデータで、デバッガー コマンドの !kuser で概要を表示できます。詳しくはデバッガーのヘルプを参照して下さい。

0:000> !kuser
_KUSER_SHARED_DATA at 7ffe0000
TickCount:    fa00000 * 000000000011d55a (0:05:04:21.406)
TimeZone Id: 0
ImageNumber Range: [8664 .. 8664]
Crypto Exponent: 0
SystemRoot: ‘C:\Windows’

ここでちょっとしたトリックが必要になります。
実はユーザーモード側から _KUSER_SHARED_DATA 構造体を見ても、全メンバーを見ることができません。そこで、カーネル モードから見る必要があります。同じ環境で livekd を使った出力がこれです。

Microsoft Windows [Version 6.1.7601]
Copyright (c) 2009 Microsoft Corporation.  All rights reserved.

E:\Visual Studio 2010\Projects\asmtest\Release>livekd

LiveKd v5.0 – Execute kd/windbg on a live system
Sysinternals – http://www.sysinternals.com
Copyright (C) 2000-2010 Mark Russinovich and Ken Johnson

Launching c:\debuggers\amd64\kd.exe:

Microsoft (R) Windows Debugger Version 6.12.0002.633 AMD64
Copyright (c) Microsoft Corporation. All rights reserved.

Loading Dump File [C:\Windows\livekd.dmp]
Kernel Complete Dump File: Full address space is available

Comment: ‘LiveKD live system view’
Symbol search path is: srv*c:\websymbols*
http://msdl.microsoft.com/download/symbols
Executable search path is:
Windows 7 Kernel Version 7601 (Service Pack 1) MP (4 procs) Free x64
Product: WinNt, suite: TerminalServer SingleUserTS
Built by: 7601.17640.amd64fre.win7sp1_gdr.110622-1506
Machine Name:
Kernel base = 0xfffff800`03a63000 PsLoadedModuleList = 0xfffff800`03ca8670
Debug session time: Sun Feb 13 11:34:57.897 17420 (UTC + 9:00)
System Uptime: 0 days 5:44:06.294
Loading Kernel Symbols
………………………………………………………
……………………………………………………….
……………………………………
Loading User Symbols
…………
Loading unloaded module list
……..Unable to enumerate user-mode unloaded modules, NTSTATUS 0xC0000147
0: kd> !kuser
*** ERROR: Module load completed but symbols could not be loaded for LiveKdD.SYS
_KUSER_SHARED_DATA at fffff78000000000
TickCount:    fa00000 * 0000000000142992 (0:05:44:06.281)
TimeZone Id: 0
ImageNumber Range: [8664 .. 8664]
Crypto Exponent: 0
SystemRoot: ‘C:\Windows’
0: kd> dt _KUSER_SHARED_DATA fffff78000000000
ntdll!_KUSER_SHARED_DATA
   +0x000 TickCountLowDeprecated : 0
(省略)
   +0x2ed TscQpcData       : 0x29 ‘)’
   +0x2ed TscQpcEnabled    : 0y1
   +0x2ed TscQpcSpareFlag  : 0y0
   +0x2ed TscQpcShift      : 0y001010 (0xa)
   +0x2ee TscQpcPad        : [2]  ""
(省略)

ntdll!_aullshr に渡されていたデータは SharedUserData+0x2ed でした。+2ed のところには、TscQpc*** という、まさに TSC と QPC に関連した値が保存されていることが分かります。

_KUSER_SHARED_DATA は公開されている構造体なので、Windows Driver Kit  に含まれる ntddk.h で定義を確認することができます。それがこれです。

//
// The following byte is consumed by the user-mode performance counter
// routines to improve latency when the source is the processor’s cycle
// counter.
//

union {
    UCHAR TscQpcData;
    struct {
        UCHAR TscQpcEnabled   : 1;
        UCHAR TscQpcSpareFlag : 1;
        UCHAR TscQpcShift     : 6;
    } DUMMYSTRUCTNAME;
} DUMMYUNIONNAME;

ビットフィールドになっていて、3 つのフィールドがあります。名前から推測できてしまいますが、一応アセンブラで確認していきましょう。ntdll!RtlQueryPerformanceCounter において、ntdll!_aullshr を呼び出す前に shr ecx,2 という命令で 2 ビット右シフトさせます。これは何かというと、TscQpcData を 2 ビット右シフト、つまり、ビットフィールドの TscQpcShift を取得していることになります。で、カーネル デバッガーの出力結果を見るとこの値は TscQpcShift : 0y001010 (0xa) 、つまり 10 です。これで謎が解けました。

RDTSC の結果と QPC の結果が 1000 倍ぐらい違っていましたが、これは TscQpcShift  の値だけ右シフトした値、つまり 1024 倍異なっていたことになります。これで QPC の仕組みは大体分かりました。

もう一度 ntdll!RtlQueryPerformanceCounter のアセンブラに戻ります。ntdll!RtlQueryPerformanceCounter+0x51 の後に ret 命令があって、ここで RDTSC を右シフトした値を返して終わるわけですが、関数自体は続いています。これはいつ呼ばれるでしょうか。それが関数のアタマにある、この部分です。

775e888b f605ed02fe7f01  test    byte ptr [SharedUserData+0x2ed (7ffe02ed)],1
775e8892 0f840bf50400    je      ntdll!RtlQueryPerformanceCounter+0x55 (77637da3)

また SharedUserData の値を使っています。+2ed なので TscQpcData の値ですが、今度は最下位ビットを test 命令で調べています。つまり TscQpcEnabled です。これが 0 の場合は、RDTSC が使われずに、ntdll!ZwQueryPerformanceCounter が呼ばれることになります。つまりカーネル モードの関数が呼ばれます。この関数が何を使っているのかについては、ここでは触れません。