Sdílet prostřednictvím


Ladění kódu optimalizovaného pro výkon

Microsoft má určité techniky, které používá k opětovnému uspořádání zkompilovaného a propojeného kódu, aby se spustil s vyšší efektivitou. Tyto techniky optimalizují komponentu pro hierarchie paměti a jsou založené na trénovacích scénářích.

Výsledná optimalizace snižuje stránkování (a chyby stránek) a zvyšuje prostorovou lokalitu mezi kódem a daty. Řeší klíčové kritické body výkonu, které by byly zavedeny špatným umístěním původního kódu. Součást, která prošla touto optimalizací, může mít svůj kód nebo datový blok v rámci funkce přesunuté do různých umístění binárního souboru.

V modulech optimalizovaných těmito technikami se umístění kódu a datových bloků často nacházejí na adresách paměti, které se liší od umístění, kde by se nacházely po normální kompilaci a propojení. Kromě toho můžou být funkce rozdělené do mnoha nesouvislých bloků, aby se nejčastěji používané cesty kódu mohly na stejných stránkách nacházet blízko sebe.

Proto funkce (nebo jakýkoli symbol) plus posun nemusí nutně mít stejný význam jako v neoptimalizovatelném kódu.

Ladění Performance-Optimized kódu

Při ladění zjistíte, jestli byl modul optimalizovaný pro výkon pomocí příkazu rozšíření !lmi na libovolném modulu, pro který byly načteny symboly:

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

V tomto výstupu si všimněte termínu perf na řádku "Charakteristiky". To znamená, že tato optimalizace výkonu byla použita pro ntdll.dll.

Debugger dokáže porozumět funkci nebo jinému symbolu bez posunu; to vám umožní nastavit breakpointy u funkcí nebo jiných symbolů bez problémů. Výstup operace dekompilace ale může být matoucí, protože tato dekompilace bude odrážet změny provedené optimalizátorem.

Vzhledem k tomu, že ladicí program se pokusí zůstat blízko původního kódu, mohou se objevit některé zábavné výsledky. Pravidlo při práci s kódy optimalizovanými pro výkon je jednoduché: u optimalizovaného kódu nelze provádět spolehlivou adresovou aritmetiku.

Tady je příklad:

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

V seznamu zarážek vidíte, že adresa IPTransmit je 0xF8640CA6.

Když v 0xF864B4CB zrušíte sestavení části kódu v rámci této funkce, výstup indikuje, že se jedná o 0xE48 bajty za začátkem funkce. Pokud však odečtete základ funkce z této adresy, skutečný posun se zdá být 0xA825.

Co se děje: Ladicí program skutečně ukazuje demontáž binárních instrukcí počínaje 0xF864B4CB. Místo výpočtu posunu jednoduchým odčítáním se ladicí program zobrazí – jak nejlépe dokáže – posun na položku funkce, protože existoval v původním kódu před provedením optimalizace. Tato hodnota je 0xE48.

Na druhou stranu, pokud se pokusíte podívat na IPTransmit+0xE48, uvidíte toto:

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

Co se zde děje, je, že ladicí program rozpozná symbol IPTransmit jako ekvivalent k adrese 0xF8640CA6 a analyzátor příkazů provede jednoduchý doplněk, který zjistí, že 0xF8640CA6 + 0xE48 = 0xF8641AEE. Tato adresa se pak použije jako argument příkazu u (Unassemble). Jakmile je toto umístění analyzováno, ladicí program zjistí, že to není IPTransmit plus posun 0xE48. Ve skutečnosti není součástí této funkce vůbec. Spíše odpovídá funkci ARPTransmit plus posun 0xD8.

Důvod, proč se to děje, je ten, že optimalizace výkonu není reverzibilní prostřednictvím aritmetiky adres. Ladicí program sice může vzít adresu a odvodit originální symbol a posun, ale nemá dostatek informací, aby mohl vzít symbol a posun a převést ho na správnou adresu. V těchto případech proto rozebírání není užitečné.