Udostępnij przez


Debugowanie kodu Performance-Optimized

Firma Microsoft ma pewne techniki, których używa do ponownego rozmieszczania skompilowanego i połączonego kodu, dzięki czemu działa z większą wydajnością. Te techniki optymalizują składnik pod kątem hierarchii pamięci i są oparte na scenariuszach szkoleniowych.

Wynikowa optymalizacja zmniejsza stronicowanie (i błędy stron) i zwiększa lokalność przestrzenną między kodem a danymi. Dotyczy to kluczowego wąskiego gardła wydajności spowodowanego złym umiejscowieniem oryginalnego kodu. Składnik, który przeszedł tę optymalizację, może mieć swój kod lub blok danych w ramach funkcji przeniesiony do różnych lokalizacji pliku binarnego.

W modułach zoptymalizowanych przez te techniki lokalizacje bloków kodu i danych często znajdują się w adresach pamięci innych niż lokalizacje, w których będą znajdować się po normalnej kompilacji i łączeniu. Ponadto funkcje mogły zostać podzielone na wiele nieciągłych bloków, aby najczęściej używane ścieżki kodu znajdowały się blisko siebie na tych samych stronach.

W związku z tym funkcja (lub dowolny symbol) plus przesunięcie nie musi mieć takiego samego znaczenia, jak w kodzie nieoptymalizowanym.

Debugowanie kodu Performance-Optimized

Podczas debugowania można sprawdzić, czy moduł został zoptymalizowany pod kątem wydajności przy użyciu polecenia rozszerzenia !lmi w dowolnym module, dla którego załadowano symbole:

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

W tych danych wyjściowych zwróć uwagę na termin perf w wierszu "Charakterystyki". Oznacza to, że ta optymalizacja wydajności została zastosowana do ntdll.dll.

Debuger jest w stanie zrozumieć funkcję lub inny symbol bez przesunięcia; Pozwala to ustawić punkty przerwania na funkcjach lub innych etykietach bez żadnego problemu. Jednak dane wyjściowe operacji dezasemblacji mogą być mylące, ponieważ ta dezasemblacja będzie odzwierciedlać zmiany wprowadzone przez optymalizator.

Ponieważ debuger spróbuje pozostać blisko oryginalnego kodu, mogą pojawić się zabawne wyniki. Ogólna zasada podczas pracy z kodami zoptymalizowanymi pod kątem wydajności jest taka, że nie można wykonać pewnej arytmetyki adresów w zoptymalizowanym kodzie.

Oto przykład:

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

Na liście punktów przerwania widać, że adres IPTransmit jest 0xF8640CA6.

Po zdeasemblowaniu sekcji kodu w ramach tej funkcji w 0xF864B4CB, dane wyjściowe wskazują, że jest to 0xE48 bajtów od początku funkcji. Jeśli jednak odejmiesz podstawę funkcji od tego adresu, rzeczywiste przesunięcie wydaje się być 0xA825.

Dzieje się tak: debuger rzeczywiście pokazuje dezasemblowanie instrukcji binarnych rozpoczynających się od 0xF864B4CB. Jednak zamiast obliczać przesunięcie przez proste odejmowanie, debuger wyświetla — w miarę możliwości — przesunięcie do punktu wejścia funkcji, jak istniało w oryginalnym kodzie przed wprowadzeniem optymalizacji. Ta wartość jest 0xE48.

Z drugiej strony, jeśli spróbujesz przyjrzeć się adresowi IPTransmit+0xE48, zobaczysz to:

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

Dzieje się tak, że debuger rozpoznaje symbol IPTransmit jako odpowiednik adresu 0xF8640CA6, a analizator poleceń wykonuje proste dodawanie, aby ustalić, że 0xF8640CA6 + 0xE48 = 0xF8641AEE. Ten adres jest następnie używany jako argument polecenia u (Unassemble). Jednak po przeanalizowaniu tej lokalizacji debuger wykrywa, że to nie jest IPTransmit z przesunięciem 0xE48. Rzeczywiście, nie jest to w ogóle część tej funkcji. Raczej odpowiada funkcji ARPTransmit z przesunięciem o 0xD8.

Przyczyną jest to, że optymalizacja wydajności nie jest odwracalna za pośrednictwem arytmetyki adresów. Podczas gdy debuger może przyjąć adres i deduować jego oryginalny symbol i przesunięcie, nie ma wystarczającej ilości informacji, aby pobrać symbol i przesunięcie i przetłumaczyć go na poprawny adres. W związku z tym dezasembleracja nie jest przydatna w tych przypadkach.