Разработка отладчиков для программ с несколькими нитями
Библиотека отладки нитей (libpthdebug.a) содержит набор функций, предназначенных для отладки программ, в которых используется библиотека нитей.
Эта библиотека предназначена для отладки 32-разрядных и 64-разрядных приложений с несколькими нитями. Она может применяться только для процессов, поддерживающих отладку. Кроме того, с ее помощью можно получить информацию о нитях ее собственного приложения. На основе библиотеки можно создать отладчик с несколькими нитями, предназначенный для приложений с несколькими нитями. Библиотека libpthreads.a поддерживает работу с несколькими нитями и допускает использование отладчиков с несколькими нитями. Библиотека отладки нитей содержит 32-разрядный общий объект.
Отладчики, использующие утилиту ptrace, должны применять 32-разрядную версию библиотеки, так как утилита ptrace не поддерживается в 64-разрядном режиме. Отладчики, использующие утилиту /proc, могут применять и 32-, и 64-разрядную версию этой библиотеки.
Библиотека отладки нитей предоставляет отладчикам доступ к информации, хранящейся в библиотеке нитей. К такой информации относятся сведения о нитях, их атрибутах, взаимных блокировках, атрибутах блокировок, условных переменных, атрибутах условных переменных, блокировках чтения и записи, атрибутах этих блокировок, а также сведения о состоянии библиотеки нитей. Кроме того, в этой библиотеке содержатся средства для управления работой нитей.
Библиотека отладки нитей не сообщает сведения о взаимных блокировках и их атрибутах, переменных условия и их атрибутах, блокировках чтения/записи и их атрибутах, для которых параметр pshared равен PTHREAD_PROCESS_SHARED.
Инициализация
- Отладчик может вызывать эту функцию при каждой остановке в процессе отладки для того чтобы узнать, активизированы ли нити в отлаживаемой программе.
- Отладчик может вызвать эту функцию один раз, а затем установить точку прерывания для получения уведомления об активизации нитей процесса, если эти нити еще не активизированы.
После инициализации библиотеки нитей отладчик должен вызвать функцию pthdb_session_init для инициализации сеанса отладки. Библиотека отладки нитей поддерживает только один сеанс для каждого процесса отладки. Отладчик должен присвоить уникальный ИД пользователя и передать его в функцию pthdb_session_init, которая, в свою очередь, должна присвоить уникальный идентификатор сеансу. Этот идентификатор передается в качестве первого параметра всем остальным функциям библиотеки отладки нитей, за исключением pthdb_session_pthreaded. Когда библиотека отладки нитей запускает функцию обратного вызова, она передает отладчику выделенный им идентификатор пользователя. Функция pthdb_session_init проверяет список функций обратного вызова, предоставляемых отладчиком, и инициализирует структуры данных сеанса. Кроме того, эта функция устанавливает флаги сеанса.
Функции обратного вызова
- Получения адресов и данных
- Записи данных
- Передачи функций управления памятью отладчику
- При отладке самой библиотеки отладки нитей
Функция обновления
При каждой остановке отладчика после инициализации сеанса необходимо вызывать функцию pthdb_session_update. Эта функция устанавливает или обновляет списки нитей, атрибутов нитей, взаимных блокировок, атрибутов взаимных блокировок, переменных условия, атрибутов этих переменных, блокировок чтения/записи, атрибутов этих блокировок, ключей отдельных нитей и активных ключей. Память для списков выделяется с помощью функций обратного вызова.
Функции блокирования и освобождения
- Для пошаговой отладки нити необходимо, чтобы отладчик заблокировал остальные нити.
- Для отладки группы нитей отладчик должен заблокировать все нити, не входящие в группу.
- Функция pthdb_pthread_hold устанавливает для атрибута состояние блокировки нити значение блокирована.
- Функция
pthdb_pthread_unhold
устанавливает для атрибута состояние блокировки нити значение
разблокирована. Прим.: Функции pthdb_pthread_hold и pthdb_pthread_unhold должны применяться всегда, независимо от того, существует ли для нити соответствующая нить ядра.
- Функция pthdb_pthread_holdstate возвращает значение атрибута состояние блокировки нити.
- Функция pthdb_session_committed возвращает имя функции, которая будет вызвана после фиксации всех изменений состояния блокировки нити. В эту функцию можно добавить точку прерывания, чтобы сообщить отладчику о фиксации изменений.
- Функция pthdb_session_stop_tid передает в библиотеку отладки нитей ИД нити (TID), вызвавшей остановку отладчика. Далее эта информация передается в библиотеку нитей.
- Функция pthdb_session_commit_tid поочередно возвращает ИД нитей ядра, работу которых нужно возобновить для фиксации изменений. Эту функцию нужно вызывать в цикле до тех пор, пока не будет получено значение PTHDB_INVALID_TID. Если список нитей ядра пуст, для фиксации не нужно возобновлять работу каких-либо нитей.
- Перед запуском операции фиксации (после возобновления работы всех нитей, ИД которых были возвращены функцией pthdb_session_commit_tid) отладчик может вызвать функцию pthdb_session_committed, определить с ее помощью имя функции и установить точку прерывания. (Этот способ можно использовать только один раз за время работы процесса).
- Перед запуском операции фиксации отладчик может вызвать функцию pthdb_session_stop_tid, передав в нее ИД нити, вызвавшей остановку отладчика. После фиксации библиотека нитей проверяет, что остановлена нить с тем же самым ИД, что и до фиксации.
- С помощью функций pthdb_pthread_hold и pthdb_pthread_unhold укажите, какие нити должны быть блокированы, а какие - разблокированы.
- Выберите способ проверки фиксации всех изменений состояния блокировки.
- С помощью функции pthdb_session_commit_tid определите список ИД нитей, выполнение которых необходимо продолжить для фиксации изменений.
- Возобновите работу нитей из этого списка, а также нити, вызвавшей остановку отладчика.
Функция pthdb_session_continue_tid позволяет отладчику получить список нитей ядра, выполнение которых нужно продолжить перед переходом к пошаговой отладке нити или отладке группы нитей. Эту функцию нужно вызывать в цикле до тех пор, пока не будет получено значение PTHDB_INVALID_TID. Если список нитей ядра не пуст, отладчик должен возобновить работу указанных в нем нитей вместе с отлаживаемыми нитями. Остановка и возобновление работы отлаживаемой нити выполняется отладчиком. Отлаживаемая нить - это нить, вызвавшая остановку отладчика.
Функции работы с контекстом
Для получения контекстной информации служит функция pthdb_pthread_context, а для ее задания - функция pthdb_pthread_setcontext. Функция pthdb_pthread_context считывает контекст пользовательской нити из структуры данных нити ядра или пользовательской нити. Эта структура данных расположена в адресном пространстве процесса отладки. Если пользовательская нить не связана с нитью ядра, контекстная информация считывается из библиотеки нитей. Если пользовательская нить связана с нитью ядра, необходимая информация считывается из отладчика с помощью функций обратного вызова. При этом отладчик должен определить, работает ли нить ядра в режиме ядра или пользовательском режиме, и выдать информацию для соответствующего режима.
Если пользовательская нить связана с нитью ядра, находящейся в режиме ядра, то нельзя считать полную информацию о контексте для пользовательского режима, так как ядро хранит ее компоненты в разных местах. Часть этой информации можно получить с помощью функции getthrds, так как она всегда сохраняет стек пользовательского режима. Отладчик может получить доступ к этой информации, проверив значение thrdsinfo64.ti_scount. Если оно отлично от нуля, значит в структуре thrdsinfo64.ti_ustk находится стек пользовательского режима. С помощью стека пользовательского режима можно определить регистр адреса команды (IAR) и страницы функций обратного вызова, но нельзя узнать значения других регистров. Определение структуры thrdsinfo64 содержится в файле procinfo.h.
Функции списка
Библиотека отладки нитей управляет списками нитей, атрибутов нитей, взаимных блокировок, атрибутов взаимных блокировок, переменных условия, атрибутов переменных условия, блокировок чтения/записи, атрибутов блокировок чтения/записи, ключей отдельных нитей и активных ключей, представленных в виде ссылок соответствующих типов. Функции вида pthdb_объект возвращают ссылку на следующий элемент соответствующего списка, где объект может принимать одно из следующих значений: pthread, attr, mutex, mutexattr, cond, condattr, rwlock, rwlockattr или key. Если список пуст, либо был достигнут конец списка, возвращается значение PTHDB_INVALID_объект, где объект - это одно из следующих значений: PTHREAD , ATTR, MUTEX, MUTEXATTR, COND, CONDATTR, RWLOCK, RWLOCKATTR или KEY.
Функции работы с полями
Подробную информацию об объекте можно получить с помощью соответствующих функций работы с элементами объекта. Они имеют вид pthdb_объект_поле, где объект - это значение pthread, attr, mutex, mutexattr, cond, condattr, rwlock, rwlockattr или key, а поле - это имя поля подробной информации об объекте.
Настройка сеанса
Отладчик может изменить флаги сеанса с помощью функции pthdb_session_setflags. Эти флаги задают количество регистров, считываемых и записываемых при работе с контекстом, а также управляют печатью отладочной информации.
Текущие флаги сеанса можно получить с помощью функции pthdb_session_flags.
Завершение сеанса
При завершении сеанса отладки необходимо освободить память, выделенную под структуры данных сеанса, и удалить данные сеанса. Это можно сделать с помощью функции pthdb_session_destroy, которая освобождает память посредством функции обратного вызова. Эта функция освобождает всю память, которая была получена функциями pthdb_session_init и pthdb_session_update.
Пример применения функций блокирования и разблокирования
Приведенный ниже пример программы демонстрирует применение функций блокирования и разблокирования в работе отладчика:
/* директивы include */
#include <sys/pthdebug.h>
main()
{
tid_t stop_tid; /* нить, вызывающая остановку процесса */
pthdb_user_t user = <уникальное значение отладчика>;
pthdb_session_t session; /* <уникальное библиотечное значение> */
pthdb_callbacks_t callbacks = <функции обратного вызова>;
char *pthreaded_symbol=NULL;
char *committed_symbol;
int pthreaded = 0;
int pthdb_init = 0;
char *committed_symbol;
/* fork/exec или подключение к отлаживаемой программе */
/* отлаживаемая программа применяет функции ptrace()/ptracex() с опцией PT_TRACE_ME */
while (/* ожидание события */)
{
/* отладчик ждет активизации отлаживаемой программы */
if (pthreaded_symbol==NULL) {
rc = pthdb_session_pthreaded(user, &callbacks, pthreaded_symbol);
if (rc == PTHDB_NOT_PTHREADED)
{
/* установка точки прерывания для pthreaded_symbol */
}
else
pthreaded=1;
}
if (pthreaded == 1 && pthdb_init == 0) {
rc = pthdb_session_init(user, &session, PEM_32BIT, flags, &callbacks);
if (rc)
/* обработка ошибки и выход */
pthdb_init=1;
}
rc = pthdb_session_update(session)
if ( rc != PTHDB_SUCCESS)
/* обработка ошибки и выход */
while (/* считывание команд отладчика */)
{
switch (/* команда отладчика */)
{
...
case DB_HOLD:
/* независимо от того, связана нить с нитью ядра или нет */
rc = pthdb_pthread_hold(session, pthread);
if (rc)
/* обработка ошибки и выход */
case DB_UNHOLD:
/* независимо от того, связана нить с нитью ядра или нет */
rc = pthdb_pthread_unhold(session, pthread);
if (rc)
/* обработка ошибки и выход */
case DB_CONTINUE:
/* если нам не нужно блокировать нить до конца */
/* процесса */
if (pthreaded)
{
/* отладчик должен обработать список любой длины */
struct pthread commit_tids;
int commit_count = 0;
/* отладчик должен обработать список любой длины */
struct pthread continue_tids;
int continue_count = 0;
rc = pthdb_session_committed(session, committed_symbol);
if (rc != PTHDB_SUCCESS)
/* обработка ошибки */
/* установка точки прерывания для committed_symbol */
/* получение всех ИД нитей, необходимых для фиксации */
/* всех операций блокировки/освобождения */
do
{
rc = pthdb_session_commit_tid(session,
&commit_tids.th[commit_count++]);
if (rc != PTHDB_SUCCESS)
/* обработка ошибки и выход */
} while (commit_tids.th[commit_count - 1] != PTHDB_INVALID_TID);
/* остановка обработки нити, вызвавшей останов */
/* процесса, с помощью функции stop_park */
if (commit_count > 0) {
rc = ptrace(PTT_CONTINUE, stop_tid, stop_park, 0,
&commit_tids);
if (rc)
/* обработка ошибки и выход */
/* ожидание остановки процесса */
}
/* получение всех ИД нитей, необходимых для продолжения */
/* обработки нужных нитей */
do
{
rc = pthdb_session_continue_tid(session,
&continue_tids.th[continue_count++]);
if (rc != PTHDB_SUCCESS)
/* обработка ошибки и выход */
} while (continue_tids.th[continue_count - 1] != PTHDB_INVALID_TID);
/* добавление нужных нитей к списку continue_tids */
/* остановка нити, вызвавшей остановку */
/* процесса, если она не нужна */
rc = ptrace(PTT_CONTINUE, stop_tid, stop_park, 0,
&continue_tids);
if (rc)
/* обработка ошибки и выход */
}
case DB_EXIT:
rc = pthdb_session_destroy(session);
/* завершение очистки */
exit(0);
...
}
}
}
exit(0);
}