Библиотека сайта rus-linux.net
GDB
Глава 4 из книги "Архитектура приложений с открытым исходным кодом", том 2.
Оригинал: GDB
Автор: Stan Shebs
Перевод: А.Панин
4.6. Структуры данных
Перед подробным рассмотрением составных частей приложения GDB, давайте рассмотрим основные структуры данных, с которыми работает GDB. Так как при разработке GDB используется язык программирования C, они реализуются в виде структур (struct
) вместо объектов в стиле C++, но в большинстве случаев они рассматриваются как объекты, поэтому в данном случае мы также будем следовать практике разработчиков GDB и называть их объектами.
Точки останова
Точка останова является основным типом объекта, к которому пользователь может получить непосредственный доступ. Пользователь создает точку останова с помощью команды break
, аргументы которой задают расположение (location), в качестве которого может использоваться имя функции, номер строки для исходного кода или адрес для машинного кода. GDB ставит в соответствие объекту точки останова небольшое целочисленное значение, которое пользователь может использовать впоследствии для работы с точкой останова. В рамках GDB точка останова представлена структурой языка C (struct
) с множеством полей. Расположение точки останова преобразуется в машинный адрес, но при этом также сохраняется в оригинальном формате, ведь адрес может измениться, например, в том случае, когда программа повторно компилируется и загружается в рамках открытой сессии, поэтому требуется его повторный расчет.
Несколько типов объектов, а именно объекты точек наблюдения (watchpoints), точек перехвата (catchpoints) и точек трассировки (tracepoints), являющихся сходными по функциям с объектами точек останова, фактически используют структуру (struct
) объектов точек останова. Это позволяет быть уверенным в том, что функции создания, изменения и удаления объектов постоянно будут находиться в работоспособном состоянии.
Термин "расположение" также относится к адресам в памяти, в которых будет установлена точка останова. В случаях использования inline-функций и шаблонов языка C++, можно столкнуться с ситуацией, когда единственная установленная пользователем точка останова может соответствовать нескольким адресам; например, каждая копия inline-функции требует отдельного расположения точки останова, которая была установлена в строке исходного кода тела этой функции.
Символы и таблицы символов
Таблицы символов относятся к ключевым структурам данных GDB и могут иметь достаточно большие размеры, иногда занимая по несколько гигабайт оперативной памяти. В некоторой степени такое поведение неизбежно; приложение больших размеров на языке C++ может содержать миллионы символов, при этом используя системные заголовочные файлы, которые в свою очередь также содержат миллионы дополнительных символов. Каждая локальная переменная, каждый именованный тип, каждый элемент перечисления - все это отдельные символы.
GDB использует множество обходных путей для сокращения объема таблиц символов, примерами которых являются такие обходные пути, как создание неполных таблиц символов (более подробно о них будет сказано позже), битовых полей в структурах (struct
), и.т.д.
В дополнение к таблицам символов, которые по существу ставят символьные строки в соответствие адресам и данным о типах, GDB создает таблицы строк, с помощью которых поддерживается возможность перехода в двух направлениях; от номеров строк исходного кода к адресам в памяти и впоследствии от адресов памяти назад к строкам исходного кода. (Например, описанный ранее пошаговый алгоритм отладки не может работать без сопоставления адресов памяти и номеров строк исходного кода.)
Стековые фреймы
Процедурные языки программирования, для работы с которыми проектировался отладчик GDB, используют стандартную архитектуру времени исполнения, в рамках которой вызовы функций приводят к помещению программного счетчика в стек вместе с некоторой комбинацией из аргументов функций и локальных аргументов. Этот набор данных называется стековым фреймом (stack frame) или "фреймом" для краткости и в любой момент исполнения программы стек состоит из связанной последовательности фреймов. Подробности организации стекового фрейма значительно отличаются при переходе от использования одного чипа к другому, при этом они также зависят от используемых операционной системы, компилятора и параметров оптимизации.
Перенос GDB для использования при работе с новым чипом может потребовать разработки большого объема кода для анализа стека, так как программы (особенно при наличии ошибок, ведь именно в таких случаях пользователи наиболее часто запускают отладчик) могут быть остановлены в любом месте, причем фреймы могут быть неполными или частично перезаписанными самой программой. Что еще хуже, формирование стекового фрейма для каждого вызова функции замедляет приложение, поэтому проводящий хорошую оптимизацию компилятор при любом удобном случае упростит стековые фреймы или даже избавится от них, как это происходит при хвостовых вызовах.
Результат специфичного для чипа анализа стека, проводимого GDB, записывается путем формирования серий фреймовых объектов. Изначально GDB отслеживал фреймы, используя точное значение из регистра указателя фиксированных фреймов. Этот подход не позволял работать в случае применения вызовов inline-функций и других типов оптимизаций компилятора, поэтому в 2002 году разработчики GDB представили реализацию фреймовых объектов, которые записывают данные, полученные при исследовании каждого из фреймов, и связаны друг с другом, зеркалируя таким образом стековые фреймы приложения.
Выражения
Как и в случае с стековыми фреймами, GDB предполагает, что существует некая степень унификации всех поддерживаемых языков программирования и представляет все языки в виде древовидной структуры, созданной из узловых объектов. Набор типов узлов на самом деле является объединением всех типов выражений, свойственных всем различным языкам программирования; в отличие от компилятора, не существует причины, по которой пользователь не должен пытаться вычитать значение переменной языка Fortran из значения переменной языка C - при этом очевидно, что отличие значений этих переменных будет выражаться степенью числа два и в этот момент нам снова придется сказать "ага".
Значения
Результат вычисления сам по себе может быть более сложным, чем целочисленное значение или адрес в памяти, поэтому GDB также удерживает результаты вычисления в нумерованном списке истории, на который позднее могут даваться ссылки в выражениях. Для поддержания работоспособности этой системы, GDB использует структуру данных значений. Структуры значений (struct
) имеют множество полей, хранящих различные свойства; важные свойства представлены в форме указаний на то, является ли значение правым (r-value) или левым (l-value) (по отношению к левым значениям могут использоваться операции присваивания в языке C), а также указаний на то, может ли значение вычисляться при необходимости.
Продолжение статьи: Часть, ответственная за работу с символами