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.
Feedback
https://aka.ms/ContentUserFeedback.
Bald verfügbar: Im Laufe des Jahres 2024 werden wir GitHub-Issues stufenweise als Feedbackmechanismus für Inhalte abbauen und durch ein neues Feedbacksystem ersetzen. Weitere Informationen finden Sie unterFeedback senden und anzeigen für