次の方法で共有


パフォーマンスが最適化されたコードのデバッグ

Microsoft には、コードがより効率的に実行されるように、コンパイルおよびリンクされたコードを再配置するために使用する特定の手法があります。 これらの手法は、メモリ階層のコンポーネントを最適化し、トレーニング シナリオに基づいています。

結果として得られる最適化により、ページング (およびページ フォールト) が減少し、コードとデータの間の空間の局所性が増加します。 これは、元のコードの配置が不適切なことが原因で発生する主要なパフォーマンスのボトルネックに対処します。 この最適化が行われたコンポーネントでは、関数内のコードまたはデータ ブロックがバイナリの別の場所に移動される可能性があります。

これらの手法によって最適化されたモジュールでは、コードとデータ ブロックの場所が、通常のコンパイルとリンクの後に存在する場所とは異なるメモリ アドレスで見つかることがよくあります。 さらに、最もよく使用されるコード パスが同じページで互いに近くに配置できるように、関数は多数の連続しないブロックに分割されている可能性があります。

そのため、関数 (または任意のシンボル) とオフセットは、必ずしも最適化されていないコードと同じ意味を持つとは限りません。

パフォーマンスが最適化されたコードのデバッグ

デバッグ時に、シンボルが読み込まれたモジュールで !lmi 拡張コマンドを使用して、モジュールがパフォーマンス最適化されているかどうかを確認できます。

0:000> !lmi ntdll
Loaded Module Info: [ntdll]
         Module: ntdll
   Base Address: 77f80000
     Image Name: ntdll.dll
   Machine Type: 332 (I386)
     Time Stamp: 394193d2 Fri Jun 09 18:03:14 2000
       CheckSum: 861b1
Characteristics: 230e stripped perf
Debug Data Dirs: Type Size     VA  Pointer
                 MISC  110,     0,   76c00 [Data not mapped]
     Image Type: DBG      - Image read successfully from symbol server.
                 c:\symbols\dll\ntdll.dbg
    Symbol Type: DIA PDB  - Symbols loaded successfully from symbol server.
                 c:\symbols\dll\ntdll.pdb

この出力で、"Characteristics" 行の用語 perf に注目してください。 これは、このパフォーマンスの最適化が ntdll.dll に適用されたことを示しています。

デバッガーは、オフセットなしの関数またはその他のシンボルを認識できるため、問題なく関数やその他のラベルにブレークポイントを設定できます。 ただし、この逆アセンブリにはオプティマイザによって行われた変更が反映されるため、逆アセンブリ操作の出力は混乱を招く可能性があります。

デバッガーは元のコードから離れないようにするため、面白い結果が表示されることがあります。 パフォーマンス最適化コードで作業する際の経験則は、単に最適化されたコードに対して信頼性の高いアドレス演算を実行できないということです。

例を次に示します。

kd> bl
 0 e f8640ca6     0001 (0001) tcpip!IPTransmit
 1 e f8672660     0001 (0001) tcpip!IPFragment

kd> u f864b4cb
tcpip!IPTransmit+e48:
f864b4cb f3a4             rep     movsb
f864b4cd 8b75cc           mov     esi,[ebp-0x34]
f864b4d0 8b4d10           mov     ecx,[ebp+0x10]
f864b4d3 8b7da4           mov     edi,[ebp-0x5c]
f864b4d6 8bc6             mov     eax,esi
f864b4d8 6a10             push    0x10
f864b4da 034114           add     eax,[ecx+0x14]
f864b4dd 57               push    edi

ブレークポイントの一覧から、IPTransmit のアドレスが 0xF8640CA6 であることがわかります。

0xF864B4CB でこの関数内のコード セクションを逆アセンブルすると、出力はこれが関数の先頭から 0xE48 バイト過ぎていることを示しています。 ただし、このアドレスから関数のベースを減算すると、実際のオフセットは 0xA825 のように見えます。

何が起こっているのでしょう。デバッガーは、確かに 0xF864B4CB で始まるバイナリ命令の逆アセンブリを示しています。 しかし、単純な減算によってオフセットを計算する代わりに、デバッガーは、最適化が行われる前の元のコードに存在していた関数エントリへのオフセットを可能な限り表示します。 その値が 0xE48 です。

一方、IPTransmit+0xE48 を確認しようとすると、以下が表示されます。

kd> u tcpip!iptransmit+e48
tcpip!ARPTransmit+d8:
f8641aee 0856ff           or      [esi-0x1],dl
f8641af1 75fc             jnz     tcpip!ARPTransmit+0xd9 (f8641aef)
f8641af3 57               push    edi
f8641af4 e828eeffff       call    tcpip!ARPSendData (f8640921)
f8641af9 5f               pop     edi
f8641afa 5e               pop     esi
f8641afb 5b               pop     ebx
f8641afc c9               leave

ここで行われているのは、デバッガーがシンボル IPTransmit をアドレス 0xF8640CA6 と同等と認識し、コマンド パーサーが単純な追加を実行して、0xF8640CA6 + 0xE48 = 0xF8641AEE を見つけているということです。 その後、このアドレスは u (逆アセンブル) コマンドの引数として使用されます。 しかし、この場所が分析されると、デバッガーはこれが IPTransmit + オフセット 0xE48 ではないことを認識します。 実際、これはこの関数の一部ではありません。 むしろ、これは関数 ARPTransmit + オフセット 0xD8 に対応しています。

これが発生する原因は、アドレス演算を通じてパフォーマンスの最適化を元に戻すことはできないことです。 デバッガーはアドレスを取得し、その元のシンボルとオフセットを推測することはできますが、シンボルとオフセットを取得して正しいアドレスに変換するのに十分な情報を持っていません。 そのため、このような場合に逆アセンブリは役に立ちません。