次の方法で共有


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

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

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

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

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

Performance-Optimized コードをデバッグする

デバッグ時に、シンボルが読み込まれたモジュールで !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

この出力では、"特性" 行の用語 パフォーマンス に注目してください。 これは、このパフォーマンスの最適化が 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のオフセットに対応します。

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