Отладка кода Performance-Optimized

Корпорация Майкрософт использует определенные методы для повторного упорядочения скомпилированного и связанного кода, чтобы он выполнялся с большей эффективностью. Эти методы оптимизируют компонент для иерархий памяти и основаны на сценариях обучения.

Результирующая оптимизация сокращает разбиение по страницам (и ошибки страниц) и увеличивает пространственное расположение между кодом и данными. Он устраняет ключевое узкое место производительности, которое может быть вызвано плохим расположением исходного кода. Компонент, прошедший эту оптимизацию, может иметь свой код или блок данных в функции, перемещенный в разные расположения двоичного файла.

В модулях, оптимизированных с помощью этих методов, расположения блоков кода и данных часто находятся на адресах памяти, отличных от расположений, в которых они будут находиться после обычной компиляции и связывания. Кроме того, функции могли быть разделены на множество несмежных блоков, чтобы наиболее часто используемые пути к коду могли располагаться близко друг к другу на одних и том же страницах.

Таким образом, функция (или любой символ) и смещение не обязательно будут иметь то же значение, что и в неоптимизованном коде.

Отладка кода 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

В этих выходных данных обратите внимание на термин perf в строке "Характеристики". Это означает, что эта оптимизация производительности была применена к ntdll.dll.

Отладчик может понять функцию или другой символ без смещения; это позволяет устанавливать точки останова для функций или других меток без каких-либо проблем. Однако выходные данные операции dissassembly могут быть запутанными, так как эта дизассемблация будет отражать изменения, внесенные оптимизатором.

Так как отладчик будет пытаться держаться близко к исходному коду, вы можете увидеть некоторые забавные результаты. Эмпирическое правило при работе с кодами, оптимизированными для производительности, заключается в том, что вы не можете выполнять арифметическую арифметику надежных адресов в оптимизированном коде.

Вот пример:

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 (Unassemble). Но после анализа этого расположения отладчик обнаруживает, что это не IPTransmit плюс смещение 0xE48. Действительно, она вообще не является частью этой функции. Скорее, он соответствует функции ARPTransmit плюс смещение 0xD8.

Причина этого заключается в том, что оптимизация производительности не обратима с помощью адресной арифметики. Хотя отладчик может принимать адрес и выводить его исходный символ и смещение, он не имеет достаточно информации, чтобы взять символ и смещение и перевести их в правильный адрес. Следовательно, дизассемблировать в таких случаях не рекомендуется.