Freigeben über


Debuggen von Performance-Optimized Code

Microsoft verfügt über bestimmte Techniken, die verwendet werden, um kompilierten und verknüpften Code so neu anzuordnen, dass er effizienter ausgeführt wird. Diese Techniken optimieren die Komponente für Speicherhierarchien und basieren auf Trainingsszenarien.

Die resultierende Optimierung reduziert das Paging (und Seitenfehler) und erhöht die räumliche Lokalität zwischen Code und Daten. Es behebt einen wichtigen Leistungsengpass, der durch eine schlechte Positionierung des ursprünglichen Codes entstehen würde. Bei einer Komponente, die diese Optimierung durchlaufen hat, kann ihr Code oder Datenblock innerhalb einer Funktion an andere Speicherorte der Binärdatei verschoben werden.

In Modulen, die durch diese Techniken optimiert wurden, befinden sich die Speicherorte von Code- und Datenblöcken häufig an anderen Speicheradressen als an den Speicherorten, an denen sie sich nach der normalen Kompilierung und Verknüpfung befinden würden. Darüber hinaus können Funktionen in viele nicht zusammenhängende Blöcke unterteilt worden sein, damit die am häufigsten verwendeten Codepfade nah beieinander auf denselben Seiten angeordnet werden können.

Daher haben eine Funktion (oder ein beliebiges Symbol) und ein Offset nicht unbedingt die gleiche Bedeutung wie in nicht optimiertem Code.

Debuggen von Performance-Optimized Code

Beim Debuggen können Sie sehen, ob ein Modul leistungsoptimiert wurde, indem Sie den Erweiterungsbefehl !lmi für jedes Modul verwenden, für das Symbole geladen wurden:

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

Beachten Sie in dieser Ausgabe den Begriff perf in der Zeile "Merkmale". Dies gibt an, dass diese Leistungsoptimierung auf ntdll.dll angewendet wurde.

Der Debugger kann eine Funktion oder ein anderes Symbol ohne Offset verstehen. Dies ermöglicht es Ihnen, Haltepunkte für Funktionen oder andere Bezeichnungen ohne Probleme festzulegen. Die Ausgabe eines Dissassembly-Vorgangs kann jedoch verwirrend sein, da diese Demontage die vom Optimierer vorgenommenen Änderungen widerspiegelt.

Da der Debugger versucht, in der Nähe des ursprünglichen Codes zu bleiben, werden möglicherweise einige amüsierende Ergebnisse angezeigt. Die Faustregel bei der Arbeit mit leistungsoptimierten Codes besteht einfach darin, dass Sie keine zuverlässige Adressarithmetik für optimierten Code ausführen können.

Beispiel:

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

Sie können in der Haltepunktliste sehen, dass die Adresse von IPTransmit 0xF8640CA6 ist.

Wenn Sie einen Codeabschnitt innerhalb dieser Funktion bei 0xF864B4CB aufheben, gibt die Ausgabe an, dass dies 0xE48 Bytes über den Anfang der Funktion hinaus ist. Wenn Sie jedoch die Basis der Funktion von dieser Adresse subtrahieren, scheint der tatsächliche Offset 0xA825 zu sein.

Was geschieht, ist Folgendes: Der Debugger zeigt tatsächlich eine Disassemblierung der binären Anweisungen ab 0xF864B4CB. Anstatt jedoch den Offset durch eine einfache Subtraktion zu berechnen, zeigt der Debugger so gut wie möglich den Offset zum Funktionseintrag an, wie er im ursprünglichen Code vorhanden war, bevor die Optimierungen durchgeführt wurden. Dieser Wert ist 0xE48.

Wenn Sie hingegen versuchen, IPTransmit+0xE48 zu betrachten, sehen Sie Folgendes:

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

Hier geschieht, dass der Debugger das Symbol IPTransmit als gleichwertig mit der adresse 0xF8640CA6 erkennt, und der Befehlsparser führt eine einfache Ergänzung durch, um zu ermitteln, dass 0xF8640CA6 + 0xE48 = 0xF8641AEE. Diese Adresse wird dann als Argument für den Befehl u (Unassemble) verwendet. Sobald dieser Speicherort analysiert wurde, erkennt der Debugger jedoch, dass es sich nicht um IPTransmit und einen Offset von 0xE48 handelt. In der Tat ist es überhaupt nicht Teil dieser Funktion. Sie entspricht vielmehr der Funktion ARPTransmit plus einem Offset von 0xD8.

Der Grund dafür ist, dass die Leistungsoptimierung nicht durch Adressarithmetik rückgängig gemacht wird. Während der Debugger eine Adresse annehmen und das ursprüngliche Symbol und den Offset ableiten kann, verfügt er nicht über genügend Informationen, um ein Symbol und einen Offset zu nehmen und in die richtige Adresse zu übersetzen. Daher ist die Disassemblierung in diesen Fällen nicht sinnvoll.