Разработка отладчиков для программ с несколькими нитями

Библиотека отладки нитей (libpthdebug.a) содержит набор функций, предназначенных для отладки программ, в которых используется библиотека нитей.

Эта библиотека предназначена для отладки 32-разрядных и 64-разрядных приложений с несколькими нитями. Она может применяться только для процессов, поддерживающих отладку. Кроме того, с ее помощью можно получить информацию о нитях ее собственного приложения. На основе библиотеки можно создать отладчик с несколькими нитями, предназначенный для приложений с несколькими нитями. Библиотека libpthreads.a поддерживает работу с несколькими нитями и допускает использование отладчиков с несколькими нитями. Библиотека отладки нитей содержит 32-разрядный общий объект.

Отладчики, использующие утилиту ptrace, должны применять 32-разрядную версию библиотеки, так как утилита ptrace не поддерживается в 64-разрядном режиме. Отладчики, использующие утилиту /proc, могут применять и 32-, и 64-разрядную версию этой библиотеки.

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

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

Библиотека отладки нитей не сообщает сведения о взаимных блокировках и их атрибутах, переменных условия и их атрибутах, блокировках чтения/записи и их атрибутах, для которых параметр pshared равен PTHREAD_PROCESS_SHARED.

Инициализация

Отладчик должен инициализировать сеанс библиотеки отладки нитей для каждого процесса отладки. Это невозможно сделать до инициализации библиотеки нитей. С помощью функции pthdb_session_pthreaded отладчик может узнать, была ли инициализирована библиотека нитей. Функция pthdb_session_pthreaded проверяет, инициализирована ли библиотека нитей. Если да, то функция возвращает значение PTHDB_SUCCESS. Если нет, она возвращает значение PTHDB_NOT_PTHREADED. В обоих случаях она возвращает имя функции, с помощью которой можно установить точку прерывания для немедленного получения уведомления об инициализации библиотеки нитей. Таким образом, функция pthdb_session_pthreaded позволяет узнать об инициализации библиотеки нитей следующими способами:
  • Отладчик может вызывать эту функцию при каждой остановке в процессе отладки для того чтобы узнать, активизированы ли нити в отлаживаемой программе.
  • Отладчик может вызвать эту функцию один раз, а затем установить точку прерывания для получения уведомления об активизации нитей процесса, если эти нити еще не активизированы.

После инициализации библиотеки нитей отладчик должен вызвать функцию 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, передав в нее ИД нити, вызвавшей остановку отладчика. После фиксации библиотека нитей проверяет, что остановлена нить с тем же самым ИД, что и до фиксации.
Для блокирования нитей перед отладкой группы нитей или пошаговой отладкой одной нити нужно выполнить следующие действия:
  1. С помощью функций pthdb_pthread_hold и pthdb_pthread_unhold укажите, какие нити должны быть блокированы, а какие - разблокированы.
  2. Выберите способ проверки фиксации всех изменений состояния блокировки.
  3. С помощью функции pthdb_session_commit_tid определите список ИД нитей, выполнение которых необходимо продолжить для фиксации изменений.
  4. Возобновите работу нитей из этого списка, а также нити, вызвавшей остановку отладчика.

Функция 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);
}