开发多线程的程序调试器

pthread 调试库 (libpthdebug.a) 提供的函数集合允许开发者为使用 pthread 库的应用程序提供调试能力。

pthread 调试库可用于调试 32 位和 64 位 pthreaded 应用程序。 此库仅用于调试目标调试进程。 它也可用于检查它自己的应用程序的 pthread 信息。 多线程调试器可使用此库调试多线程应用程序。 libpthreads.a 库中支持多线程调试器,这是线程安全。 pthread 调试库包含 32 位共享对象和 64 位共享对象。

使用 ptrace 工具的调试器必须链接到库的 32 位版本,因为 64 位方式下不支持 ptrace 工具。 使用 /proc 工具的调试器可链接到此库的 32 位版本或者 64 位版本。

pthread 调试库为调试器提供对 pthread 库信息的访问。 这包括有关 pthread、pthread 属性、互斥对象、互斥属性、条件变量、条件变量属性、读/写锁、读/写锁属性的信息,以及有关 pthread 库状态的信息。 此库还提供控制 pthread 执行的帮助。

注: 对于 64 位和 32 位应用程序,此库返回的所有数据 (地址和寄存器) 都采用 64 位格式。 调试器负责为 32 位应用程序将这些值转换为 32 位格式。 调试 32 位应用程序时,地址和寄存器的高半段被忽略。

pshared 值为 PTHREAD_PROCESS_SHARED 时,pthread 调试库不报告互斥对象、互斥属性、条件变量、条件变量属性、读/写锁、读/写锁属性。

初始化

调试器必须初始化每一个调试进程的 pthread 调试库会话。 直到调试进程中的 pthread 库已经被初始化才能进行该操作。 提供 pthdb_session_pthreaded 函数以在调试进程中初始化 pthread 后通知调试器。 每次调用 pthdb_session_pthread 函数时,它都会检查是否已初始化 pthread 库。 如果已初始化,那么将返回PTHDB_SUCCESS。否则,返回PTHDB_NOT_PTHREADED. 在这两种情况下,它都返回一个函数名,该函数名可用于设置一个断点,以便立即通知 pthread 库已初始化。 因此,pthdb_session_pthreaded 函数提供以下方法确定 pthread 库何时已经被初始化。
  • 调试器在每次调试进程停止时调用函数来查看被调试的程序是否为 pthreaded。
  • 调试器调用函数一次,如果正在被调试的程序不是 pthreaded,那么设置断点以在调试进程为 pthreaded 时通知调试器。

调试进程为 pthread 后,调试器必须调用 pthdb_session_init 函数来初始化调试进程的会话。 对于单个调试进程,pthread 调试库支持一个会话。 调试器必须指定唯一的用户标识并将其传递给 pthdb_session_init,然后该函数会将必须被作为第一个参数传递的唯一的会话标识指定给所有其他 pthread 调试库函数(除了 pthdb_session_pthreaded 以外)。 当 pthread 调试库调用回调函数时,它会将调试器指定的唯一用户标识传递回调试器。 pthdb_session_init 函数检查调试器提供的回调函数列表,并初始化会话的数据结构。 此函数还设置会话标记。

回调函数

pthread 调试库使用回调函数执行以下操作:
  • 获取地址和数据
  • 写数据
  • 对调试器进行存储管理
  • 帮助调试 pthread 调试库

更新函数

每次调试器停止时,在初始化会话后,都需要调用 pthdb_session_update 函数。 此函数设置或复位 pthread、pthread 属性、互斥对象、互斥属性、条件变量、条件变量属性、读/写锁、读/写锁属性、特定于 pthread 的键和活动键的列表。 它使用回调函数管理列表的内存。

保持和不保持函数

出于下面的原因,调试器必须支持保持和不保持线程:
  • 要允许用户单步执行单个线程,必须能够保持一个或者多个其他线程。
  • 对于通过可用线程的子集继续的用户,必须可能保持不在集合中的线程。
下面是执行保持和不保持任务的函数的列表:
  • pthdb_pthread_hold 函数将 pthread 的 hold state 设置为hold.
  • pthdb_pthread_unhold 函数将 pthread 的 hold state 设置为unhold.
    注: 无论 pthread 是否具有内核线程,都必须始终使用 pthdb_pthread_holdpthdb_pthread_unhold 函数。
  • pthdb_pthread_holdstate 函数返回 pthread 的 hold state
  • pthdb_session_committed 函数报告在落实所有 hold 和 unhold 更改后调用的函数的函数名。 可在此函数中设置断点已在落实保持和不保持更改后通知调试器。
  • pthdb_session_stop_tid 函数通知 pthread 调试库,它通知 pthread 库 停止调试器的线程的线程标识 (TID)。
  • pthdb_session_commit_tid 函数返回必须继续落实暂挂和取消暂挂更改的内核线程列表 (一次一个内核线程)。 必须重复调用此函数,直到PTHDB_INVALID_TID报告。 如果内核线程列表为空,就不必继续任何线程来执行落实操作。
调试器可以下列方式确定何时所有的保持或者不保持更改均已落实:
  • 在落实操作(继续 pthdb_session_commit_tid 函数返回的所有 TID)启动之前,调试器能够调用 pthdb_session_committed 函数获取函数名并设置断点。 (此方法在进程的生命周期内只能使用一次。)
  • 在落实操作启动之前,调试器用停止调试器的线程的 TID 调用 pthdb_session_stop_tid 函数。 当落实操作完成时,pthread 库确保相同的 TID 在落实操作之前被停止。
要保持或者不保持 pthreads,请在继续一组 pthread 或者单步调试单个 pthread 之前使用下面的过程:
  1. 使用 pthdb_pthread_holdpthdb_pthread_unhold 函数设置保持哪个 pthread 以及不保持哪个 pthread。
  2. 选择用于确定何时所有的保持或者不保持更改均已落实的方法。
  3. 使用 pthdb_session_commit_tid 函数确定必须被继续以落实保持或者不保持更改的 TID 的列表。
  4. 继续前面步骤中停止调试器的 TID 和线程。

pthdb_session_continue_tid 函数允许调试器获取必须继续的内核线程列表,然后再继续单步执行单个 pthread 或继续一组 pthread。 必须重复调用此函数,直到PTHDB_INVALID_TID报告。 如果内核线程列表不为空,那么调试器必须继续这些内核线程和其他其明显感兴趣的线程。 调试器负责放置停止线程和继续停止线程。 停止线程是使得调试器进入的线程。

上下文函数

pthdb_pthread_context 函数获取上下文信息, pthdb_pthread_setcontext 函数设置上下文。 pthdb_pthread_context 函数从调试进程地址空间中的内核或者 pthread 数据结构获得 pthread 的上下文信息。 如果 pthread 与内核线程不相关,那么获得 pthread 库保存的上下文信息。 如果 pthread 与内核线程相关,那么通过使用回调的调试器获得信息。 调试器负责确定,内核线程处于内核方式还是用户方式,然后为该方式提供正确的信息。

当带有内核线程的 pthread 处于内核方式时,您不能获得完整的用户方式上下文,因为内核将其保存在不止一处。 getthrds 函数可用于获得部分此信息,因为它始终保存用户方式堆栈。 调试器可以通过检查 thrdsinfo64.ti_scount 结构发现该信息。 如果其为非零,那么用户方式堆栈在 thrdsinfo64.ti_ustk 结构中。 可以从用户方式堆栈确定指令地址寄存器 (IAR) 和回调帧,但是不能确定其他寄存器值。 thrdsinfo64 结构定义在 procinfo.h 文件中。

列表函数

pthread 调试库维护 pthread、pthread 属性、互斥对象、互斥属性、条件变量、条件变量属性、读/写锁、读/写锁属性、特定于 pthread 的键和活动键的列表,每个列表通过特定类型的句柄表示。 pthdb_对象 函数返回相应列表中的下一个句柄,其中 对象 是下列其中一项: pthreadattrmutexmutexattrcondcondattrrwlockrwlockattrkey。 如果列表为空或者到达列表尾部,那么报告 PTHDB_INVALID_object,其中 object 是下列其中一个:PTHREAD , ATTRMUTEXMUTEXATTRCONDCONDATTRRWLOCKRWLOCKATTR 或者 KEY

字段函数

可以使用合适的对象成员函数 (pthdb_object _field) 获取有关对象的详细信息,其中 object 为下述之一:pthreadattrmutexmutexattrcondcondattrrwlockrwlockattr 或者 key,其中,field 是对象的详细信息的字段名。

定制会话

pthdb_session_setflags 函数允许调试器更改用于定制会话的标志。 这些标记控制在上下文操作期间被读或者写的寄存器数,并控制调试信息的显示。

pthdb_session_flags 函数获取会话的当前标志。

终止会话

在调试会话结束时,必须取消对会话数据结构的分配,还必须删除会话数据。 这是通过调用 pthdb_session_destroy 函数完成的,该函数使用回调函数来释放内存。 将取消 pthdb_session_initpthdb_session_update 函数分配的所有内存。

保持/不保持函数的示例

下面的伪码示例说明调试器如何使用保持/不保持代码:

/* includes */

#include <sys/pthdebug.h>

main()
{
    tid_t stop_tid; /* thread which stopped the process */
    pthdb_user_t user = <unique debugger value>;
    pthdb_session_t session; /* <unique library value> */
    pthdb_callbacks_t callbacks = <callback functions>;
    char *pthreaded_symbol=NULL;
    char *committed_symbol;
    int pthreaded = 0;
    int pthdb_init = 0;
    char *committed_symbol;

    /* fork/exec or attach to the program that is being debugged */

    /* the program that is being debugged uses ptrace()/ptracex() with PT_TRACE_ME */

    while (/* waiting on an event */) 
    {
      /* debugger waits on the program that is being debugged */

      if (pthreaded_symbol==NULL) {
        rc = pthdb_session_pthreaded(user, &callbacks, pthreaded_symbol);
        if (rc == PTHDB_NOT_PTHREADED)
        {
            /* set breakpoint at pthreaded_symbol */
        }
        else
          pthreaded=1;	
      }
      if (pthreaded == 1 && pthdb_init == 0) {
          rc = pthdb_session_init(user, &session, PEM_32BIT, flags, &callbacks);
          if (rc)
              /* handle error and exit */
          pthdb_init=1;
      }
  
      rc = pthdb_session_update(session)
      if ( rc != PTHDB_SUCCESS)
	/* handle error and exit */

      while (/* accepting debugger commands */)
      {
          switch (/* debugger command */)
          {
              ...
              case DB_HOLD:
                  /* regardless of pthread with or without kernel thread */
                  rc = pthdb_pthread_hold(session, pthread);
                  if (rc)
                      /* handle error and exit */
              case DB_UNHOLD:
                  /* regardless of pthread with or without kernel thread */
                  rc = pthdb_pthread_unhold(session, pthread);
                  if (rc)
                      /* handle error and exit */
              case DB_CONTINUE:
                  /* unless we have never held threads for the life */
                  /* of the process */
                  if (pthreaded)
                  {
                      /* debugger must handle list of any size */
                      struct pthread commit_tids;
                      int commit_count = 0;
                      /* debugger must handle list of any size */
                      struct pthread continue_tids;
                      int continue_count = 0;
		      
		      rc = pthdb_session_committed(session, committed_symbol);
		      if (rc != PTHDB_SUCCESS)
			  /* handle error */
	              /* set break point  at committed_symbol */	
		      
                      /* gather any tids necessary to commit hold/unhold */
                      /* operations */
                      do
                      {
                          rc = pthdb_session_commit_tid(session, 
                                                &commit_tids.th[commit_count++]);
                          if (rc != PTHDB_SUCCESS)
                              /* handle error and exit */
                      } while (commit_tids.th[commit_count - 1] != PTHDB_INVALID_TID);
  
                      /* set up thread which stopped the process to be */
                      /* parked using the stop_park function*/
  
		      if (commit_count > 0) {
                        rc = ptrace(PTT_CONTINUE, stop_tid, stop_park, 0, 
                                                              &commit_tids);
                        if (rc)
                            /* handle error and exit */
  
                        /* wait on process to stop */
		      }
  
                      /* gather any tids necessary to continue */
                      /* interesting threads */
                      do
                      {
                          rc = pthdb_session_continue_tid(session, 
                                          &continue_tids.th[continue_count++]);
                           if (rc != PTHDB_SUCCESS)
                              /* handle error and exit */
                      } while (continue_tids.th[continue_count - 1] != PTHDB_INVALID_TID);
  
                      /* add interesting threads to continue_tids */
  
                      /* set up thread which stopped the process to be parked */
                      /* unless it is an interesting thread */
  
                      rc = ptrace(PTT_CONTINUE, stop_tid, stop_park, 0, 
                                                                &continue_tids);
                      if (rc)
                          /* handle error and exit */
                  }
              case DB_EXIT:
		rc = pthdb_session_destroy(session);
		/* other clean up code */
		exit(0);
              ...
          }
      }
  
    }
    exit(0);
}