Примечание
Для доступа к этой странице требуется авторизация. Вы можете попробовать войти или изменить каталоги.
Для доступа к этой странице требуется авторизация. Вы можете попробовать изменить каталоги.
Для Windows 8 отладчик и компилятор Windows были улучшены, чтобы можно было отлаживать оптимизированный код и отладить встроенные функции. Отладчик отображает параметры и локальные переменные независимо от того, хранятся ли они в регистрах или в стеке. Отладчик также отображает встроенные функции в списке вызовов. Для встроенных функций отладчик отображает локальные переменные, но не параметры.
При оптимизации кода он преобразуется для ускорения работы и использования меньшей памяти. Иногда функции удаляются в результате удаления мертвого кода, объединения кода или размещения функций inline. Локальные переменные и параметры также можно удалить. Многие оптимизации кода удаляют локальные переменные, которые не нужны или используются; другие оптимизации удаляют индукционные переменные в циклах. Устранение общих подвыражений объединяет локальные переменные.
Розничные сборки Windows оптимизированы. Таким образом, если вы используете розничную сборку Windows, особенно полезно иметь отладчик, предназначенный для работы с оптимизированным кодом. Для эффективной отладки оптимизированного кода требуются две основные функции: 1) точное отображение локальных переменных и 2) отображение встроенных функций в стеке вызовов.
точное отображение локальных переменных и параметров
Чтобы упростить точное отображение локальных переменных и параметров, компилятор записывает сведения о расположениях локальных переменных и параметров в файлах символов (PDB). Эти записи расположения отслеживают расположения хранилища переменных и определенные диапазоны кода, в которых эти расположения допустимы. Эти записи не только помогают отслеживать расположения (в регистрах или в слотах стека) переменных, но и перемещение переменных. Например, параметр может сначала находиться в регистре RCX, но перемещен в слот стека, чтобы освободить RCX, а затем перемещен в регистр R8, когда он активно используется в цикле, а затем перемещен в другой слот стека, когда код выходит из цикла. Отладчик Windows использует богатые записи расположения в PDB-файлах и использует указатель текущей инструкции для выбора соответствующих записей расположения для локальных переменных и параметров.
На этом снимке экрана окна "Локальные" в Visual Studio отображаются параметры и локальные переменные для функции в оптимизированном 64-разрядном приложении. Функция не является встроенной, поэтому мы видим как параметры, так и локальные переменные.
Команду dv -v можно использовать для просмотра расположений параметров и локальных переменных.
Обратите внимание, что окно "Локальные" отображает параметры правильно, даже если они хранятся в регистрах.
Помимо отслеживания переменных с помощью примитивных типов, записи расположения отслеживают элементы данных локальных структур и классов. В следующих данных отладчика отображаются локальные структуры.
0:000> dt My1
Local var Type _LocalStruct
+0x000 i1 : 0n0 (edi)
+0x004 i2 : 0n1 (rsp+0x94)
+0x008 i3 : 0n2 (rsp+0x90)
+0x00c i4 : 0n3 (rsp+0x208)
+0x010 i5 : 0n4 (r10d)
+0x014 i6 : 0n7 (rsp+0x200)
0:000> dt My2
Local var @ 0xefa60 Type _IntSum
+0x000 sum1 : 0n4760 (edx)
+0x004 sum2 : 0n30772 (ecx)
+0x008 sum3 : 0n2 (r12d)
+0x00c sum4 : 0n0
Ниже приведены некоторые наблюдения за предыдущими выходными данными отладчика.
- Локальная структура My1 иллюстрирует, что компилятор может распространять элементы данных локальной структуры в регистры и неконтинуальные слоты стека.
- Выходные данные команды dt My2 будут отличаться от выходных данных команды dt _IntSum 0xefa60. Нельзя предположить, что локальная структура займет непрерывный блок памяти стека. В случае My2только
sum4
остается в исходном блоке стека; остальные три члена данных перемещаются в регистры. - Некоторые элементы данных могут иметь несколько адресов. Например, My2.sum2 имеет два местоположения: одно в регистре ECX (выбранном отладчиком Windows), а другое — 0xefa60+0x4 (исходный слот стека). Это может произойти и для локальных переменных примитивного типа, а отладчик Windows использует предшествующие эвристики для определения, какое местоположение следует использовать. Например, местоположения регистров всегда имеют приоритет над расположениями стека.
отображение встроенных функций в стеке вызовов
Во время оптимизации кода некоторые функции помещаются в строку. То есть тело функции помещается непосредственно в код, подобно расширению макроса. Отсутствует вызов функции и не происходит возврат вызывающей стороне. Чтобы упростить отображение встроенных функций, компилятор сохраняет данные в PDB-файлах, которые помогают декодировать блоки кода для встроенных функций (то есть последовательности блоков кода в вызывающих функциях, принадлежащих вызываемым функциям, которые помещены встраиваемо) и локальные переменные (локальные переменные с областью видимости в этих блоках кода). Эти данные помогают отладчику включать встроенные функции в процессе размотки стека.
Предположим, что вы компилируете приложение и принуждаете функцию с именем func1
к встраиванию.
__forceinline int func1(int p1, int p2, int p3)
{
int num1 = 0;
int num2 = 0;
int num3 = 0;
...
}
Можно использовать команду bm, чтобы задать точку останова в func1
.
0:000> bm MyApp!func1
1: 000007f6`8d621088 @!"MyApp!func1" (MyApp!func1 inlined in MyApp!main+0x88)
0:000> g
Breakpoint 1 hit
MyApp!main+0x88:
000007f6`8d621088 488d0d21110000 lea rcx,[MyApp!`string' (000007f6`8d6221b0)]
После выполнения одного шага в func1
можно использовать команду k для просмотра func1
в стеке вызовов. Команду dv можно использовать для просмотра локальных переменных для func1
. Обратите внимание, что локальная переменная num3
отображается как недоступная. Локальная переменная может быть недоступна в оптимизированном коде по ряду причин. Возможно, переменная не существует в оптимизированном коде. Возможно, переменная еще не инициализирована или что переменная больше не используется.
0:000> p
MyApp!func1+0x7:
000007f6`8d62108f 8d3c33 lea edi,[rbx+rsi]
0:000> knL
# Child-SP RetAddr Call Site
00 (Inline Function) --------`-------- MyApp!func1+0x7
01 00000000`0050fc90 000007f6`8d6213f3 MyApp!main+0x8f
02 00000000`0050fcf0 000007ff`c6af0f7d MyApp!__tmainCRTStartup+0x10f
03 00000000`0050fd20 000007ff`c7063d6d KERNEL32!BaseThreadInitThunk+0xd
04 00000000`0050fd50 00000000`00000000 ntdll!RtlUserThreadStart+0x1d
0:000> dv -v
00000000`0050fcb0 num1 = 0n0
00000000`0050fcb4 num2 = 0n0
<unavailable> num3 = <value unavailable>
При просмотре кадра 1 в трассировке стека можно увидеть локальные переменные для функции main
. Обратите внимание, что две переменные хранятся в регистрах.
0:000> .frame 1
01 00000000`0050fc90 000007f6`8d6213f3 MyApp!main+0x8f
0:000> dv -v
00000000`0050fd08 c = 0n7
@ebx b = 0n13
@esi a = 0n6
Отладчик Windows объединяет данные из PDB-файлов, чтобы найти все места, где была размещена определенная функция. Команду x можно использовать для перечисления всех мест вызова встраиваемой функции.
0:000> x simple!MoreCalculate
00000000`ff6e1455 simple!MoreCalculate = (inline caller) simple!wmain+8d
00000000`ff6e1528 simple!MoreCalculate = (inline caller) simple!wmain+160
0:000> x simple!Calculate
00000000`ff6e141b simple!Calculate = (inline caller) simple!wmain+53
Поскольку отладчик Windows может перечислить все места вызова встроенной функции, он может устанавливать точки останова внутри встроенной функции, вычисляя смещения от мест вызова. Можно использовать команду bm (которая используется для задания точек останова, соответствующих шаблонам регулярных выражений), чтобы задать точки останова для встроенных функций.
Отладчик Windows группирует все точки останова, заданные для конкретной встроенной функции, в контейнер точек останова. Можно управлять контейнером точек останова в целом с помощью таких команд, как ,, bd, bc. См. следующие bd 3 и bc 3 примеры команд. Вы также можете управлять отдельными точками останова. См. следующий пример команды 2.
0:000> bm simple!MoreCalculate
2: 00000000`ff6e1455 @!"simple!MoreCalculate" (simple!MoreCalculate inlined in simple!wmain+0x8d)
4: 00000000`ff6e1528 @!"simple!MoreCalculate" (simple!MoreCalculate inlined in simple!wmain+0x160)
0:000> bl
0 e 00000000`ff6e13c8 [n:\win7\simple\simple.cpp @ 52] 0001 (0001) 0:**** simple!wmain
3 e <inline function> 0001 (0001) 0:**** {simple!MoreCalculate}
2 e 00000000`ff6e1455 [n:\win7\simple\simple.cpp @ 58] 0001 (0001) 0:**** simple!wmain+0x8d (inline function simple!MoreCalculate)
4 e 00000000`ff6e1528 [n:\win7\simple\simple.cpp @ 72] 0001 (0001) 0:**** simple!wmain+0x160 (inline function simple!MoreCalculate)
0:000> bd 3
0:000> be 2
0:000> bl
0 e 00000000`ff6e13c8 [n:\win7\simple\simple.cpp @ 52] 0001 (0001) 0:**** simple!wmain
3 d <inline function> 0001 (0001) 0:**** {simple!MoreCalculate}
2 e 00000000`ff6e1455 [n:\win7\simple\simple.cpp @ 58] 0001 (0001) 0:**** simple!wmain+0x8d (inline function simple!MoreCalculate)
4 d 00000000`ff6e1528 [n:\win7\simple\simple.cpp @ 72] 0001 (0001) 0:**** simple!wmain+0x160 (inline function simple!MoreCalculate)
0:000> bc 3
0:000> bl
0 e 00000000`ff6e13c8 [n:\win7\simple\simple.cpp @ 52] 0001 (0001) 0:**** simple!wmain
Поскольку нет явных инструкций по вызову или возврату встроенных функций, шаг по исходному коду особенно сложен для отладчика. Например, можно непреднамеренно выполнить шаг в встроенную функцию (если следующая инструкция является частью встроенной функции), или можно выполнить шаг и выйти из одной встроенной функции несколько раз (так как блоки кода встроенной функции были разделены и перемещены компилятором). Чтобы сохранить знакомое ощущение выполнения шагов, отладчик Windows поддерживает небольшой концептуальный стек вызовов для каждого адреса инструкции кода и создает внутреннюю машину состояний для выполнения операций пошагового входа, прохода и выхода. Это дает достаточно точное приближение к пошаговому опыту для не встроенных функций.
дополнительные сведения
Примечание Можно использовать команду .inline 0 для отключения встроенной отладки функции. Команда .inline 1 включает отладку встроенных функций. стандартные методы отладки