Стеки вызовов и символы

Несколько утилит Sysinternals — включая Проводник Процесса, Монитор Процесса, и VMMap (Process Explorer, Process Monitor, and VMMap) — могут вывести на экран детали о путях выполнения кода, выполняемых в определенный момент времени, котрые называются стеками вызова (call stacks).

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

Что такое стек вызова?

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

Учебный пример, показанный в рисунке 2-4, демонстрирует эту последовательность. MyApp.exe выполняет модуль  DLL под названием HelperFunctions.dll.

Эта DLL включает функцию под названием EncryptThisText, который шифрует текст, который передан ей. После выполнения некоторых предварительных операций EncryptThisText вызывает API Windows CryptEncryptMessage, котрая находится в Crypt32.dll. В некоторый момент CryptEncryptMessage должен выделить некоторую память и вызывает функцию выделения памяти malloc, которавя назходится в модуле в Msvcrt.dll.

После того, как malloc сделал свою работу и выделил требуемую память, исполнительная система принимает решение перейти в точку, где CryptEncryptMessage закончил выполление. А когда CryptEncryptMessage завершил свою задачу, управление возвращается назад к точке в EncryptThisText сразу после его вызова в CryptEncryptMessage.

Стек вызова - конструкция, которая позволяет системе знать, как и куда возвратить управление в случае серии вызовов, а так же как передать параметры между функциями и сохранить локальные ­переменные.

В основе выполнения лежит алгоритм "последний пришел, первый вышел" (FIFO) способ, куда функции удаляют элементы в обратном порядке от того, как они добавляют их.

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

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

Соглашение для того, чтобы вывести на экран обратный адрес в стеке вызова является module!function+offset, где модуль! - имя исполнимого файла, содержащего функцию, и смещение - число байтов (в шестнадцатеричном) с начала функции (модуля). Если имя функции не доступно, адрес показывают просто как "module+offset". В то время как malloc выполняется в фиктивном примере, только данном, стек вызова мог бы быть похожим на это:

msvcrt!malloc+0x2a

crypt32!CryptEncryptMessage+0x9f

HelperFunctions!EncryptThisText+0x43

MyApp.exe+0x25d8

 

Как можно видеть, стек вызова не только показвает какая часть кода выполняется, но и как программа добралась туда.

Каковы Символы?

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

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

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

Примечание. Исполняемые файлы, загруженные в процессах пользовательского режима, являются обычно или файлами EXE, с которых новый процесс может быть запущен или файлы DLL, которые загружаются в существующий процесс. EXE и файлы DLL не ограничиваются использованию тех двух расширений файла, как бы то ни было. Файлы с COM или расширениями SCR - фактически файлы EXE, в то время как ACM, AX, CPL, DRV, и OCX - примеры других расширений файла DLL. И программы установки обычно извлекают и запускают файлы EXE с расширениями TMP.

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

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

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

Примечание. Утилиты Sysinternals в состоянии использовать только собственные (неуправляемые) файлы символа, сообщая о стеках вызова. Они не в состоянии сообщить об именах функций в пределах скомпилированных в JIT-compiled.NET.

 

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

 Более старые версии Microsoft Visual C + + создавали файлы символов только для отладки (Debug builds) до тех порпока разработчик явно изменил конфигурацию сборки.

 Более новые версии теперь создают файлы символа для сборки конечных версий (Release builds), а также записывает их в ту же самую папку с исполняемыми файлами. Microsoft Visual Basic 6 может создать файлы символа, но он не делает так по умолчанию.

Файлы символа могут содержать отличающиеся уровни детализации. Полные файлы символа (Full symbol files) (иногда называемый частными файлами символа, private symbol files) содержат детали, которые не находятся в общедоступных файлах символа (public symbol files), включая путь к и номер строки в пределах исходного файла, где символ определяется, имена параметров функции и типы, и имена переменной и типы. Компании-разработчики программного обеспечения, которые делают файлы символа внешне доступными обычно, выпускают только общедоступные файлы символа, сохраняя полные файлы символа для внутреннего пользования.

Средства отладки для Windows позволяют загрузить корректные файлы символа по требованию с сервера символа (symbol server). Сервер может хранить файлы символа для многих различных сборок данного исполняемого файла, и Средства отладки (Debugging Tools) загрузят тот, который соответствует изображению, которое Вы отлаживаете. (При этом используется метка времени и контрольная сумма, сохраненная в заголовке исполняемой программы как уникальный идентификатор.)

У Microsoft есть сервер символа, доступный по сети, которая позволяет получить общедоступные файлы символов Windows в свободном режиме.

Устанавливая Средства отладки для Windows и конфигурируя утилиты Sysinternals, чтобы использовать сервер символа Microsoft, можно легко видеть, какие функции Windows вызываются Вашими процессами.

 

Рисунок 2-5 показывает стек вызова для события, полученного с Монитором Процесса (Process Monitor).

Присутствие MSVBVM60.DLL на стеке (фрэймы 15 и 17-21) указывает, что это - Visual Basic 6 программа, потому что MSVBVM60.DLL есть Visual Basic 6 DLL на времени выполнения.

Большие смещения для фреймов MSVBVM60 предполагают, что символы не доступны для того модуля и что показанные имена не являются фактическими вызванными функциями.

Фрэйм 14 показывает вызов функции по имени Form1:: cmdCreate_Click в основной исполняемой программе (LuaBugs_VB6.exe).

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

 Эта функция затем вызывает CWshShell:: RegWrite в Wshom.ocx (фрэйм 13), указывая, что эта Visual Basic 6 программа использует Windows Script Host ActiveX, чтобы записать в ­реестр.

CWshShell:: RegWrite вызывает внутреннюю функцию в том же самом модуле (фрэйм 12), который призывает задокументированный API Windows RegCreateKeyExA Kernel32.dll (фрэйм 11).

Исполнительная система проходит через внутренние функции Kernel32 (фрэймы 8-10), и затем во встроенный API ZwCreateKey в Ntdll.dll (фрэйм 7). До сих пор все эти функции выполнились в пользовательском режиме, как обозначено U в столбце Frame, но во фрейме 6 программа переходит в  режим ядра, обозначенному K.

Двухбуквенные префиксы функций ядра (фреэмы 0-6) идентифицируют компоненты исполнительной ситемы, к которым они принадлежат.

Например, Cm обращается к Менеджеру конфигурации, который ответственен за реестр, и Ob обращается к Диспетчеру объектов. Именно во время обработки CmpCallCallBacks (фрэйм 0) была получена эта трассировка.

Отметим, что символьная информация, показанная во фреймах 0-13, была вся получена из общедоступных файлах символа  Windows, загруженных по требованию Монитором Процесса (Process Monitor), запущенног с сервера символа Microsoft (Microsoft’s symbol server).

Рис. 2-5. Process Monitor вызывает стек с информацией из символьных файлов.

00000000000000000


Понравилась статья? Добавь ее в закладку (CTRL+D) и не забудь поделиться с друзьями:  



double arrow
Сейчас читают про: