来自linux core dump的线程特定数据

时间:2012-05-31 21:02:27

标签: linux gdb coredump

在分析 linux 的核心转储时,如何获取指向线程本地存储或线程特定数据的指针?

我使用pthread_setspecific在pthread的本地stoare中存储一些数据。

我在linux上的多线程程序崩溃了,我想看看当前运行的线程的本地存储中存储了什么。

如果我获得指向线程本地存储的指针,我可以使用key来获取存储的数据。

gdb 中是否有命令获取 指向线程本地存储的指针

2 个答案:

答案 0 :(得分:5)

如果您正在调试实时节目,您可以:

print pthread_getspecific(i)

如果您有权访问该主题的pthread_t,您可以:

print ((struct pthread*)pth)->specific[i/32][i%32]

你想要的索引中的i和pth是pthread_t。请参阅glibc源代码中的nptl / pthread_getspecific.c。

要在不调用函数的情况下执行此操作,您需要找到struct pthread。在x86-64上,它存储在fs基础中,使用arch_prctl(ARCH_SET_FS_BASE,...)设置。我不知道如何从gdb访问它,但你可以用eu-readelf获取它。运行eu-readelf --notes core_file并查看fs.base的记录。该数字是pthread_t值。 (要确定它是哪一个,您可以将同一记录中的pid字段与gdb的info threads命令中显示的LWP匹配。)

祝你好运!

答案 1 :(得分:4)

据我所知,gdb中没有命令获取指向通过pthread_setspecific()存储的数据的指针。但是,有几个选项可以获取内存地址:

  • 检查每个线程的回溯,检查每个帧以查看pthread_getspecific()的结果是否仍在堆栈中。
  • 修改现有代码以记录线程ID和pthread_getspecific()的结果。
  • 在线程内部数据中找到特定于线程的数据列表,然后使用该键查找将包含该地址的记录。这种方法依赖于pthread库的实现;因此,它需要了解正在使用的pthread实现或需要一点耐心的逆向工程。

以下是在32位计算机上使用简单程序的演示:

  • g ++(GCC)4.1.2 20080704(Red Hat 4.1.2-48)
  • libpthread-2.5.so
  • GNU gdb Red Hat Linux(6.5-25.el5rh)

$cat example.cpp
#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

void* the_thread( void* ); 
void get_position();

struct position_t
{
  int x;
  int y;
};

namespace {
  pthread_key_t position_key;

  enum {
    NUMBER_OF_THREADS = 2
  };
} // unnamed

int main(int argc, char **argv)
{
  int result = pthread_key_create( &position_key, NULL );
  printf( "pthread_key_create -- key: %u, result: %i\n",
          position_key, result );

  pthread_t threads[NUMBER_OF_THREADS];
  for (unsigned int i = 0; i < NUMBER_OF_THREADS; ++i )
  {
     // Allocate a position per threads.
     position_t* position = new position_t();

     // Set position values.
     position->x = ( 1 + i ) * 11;
     position->y = ( 1 + i ) * 13;

     // Create the thread.
     result = pthread_create( &threads[i], NULL, the_thread, position );
  }

  // Give time for threads to enter their forever loop.
  sleep( 5 );

  // Abort.
  abort();
  return 0; 
}

void* the_thread( void* position )
{
   int result = pthread_setspecific( position_key, position );

   printf( "Thread: 0x%.8x, key: %u, value: 0x%.8x, result: %i\n",
           pthread_self(), position_key, position, result );

   get_position();
   return 0;
}

void get_position()
{
   position_t* position =
        reinterpret_cast< position_t* >( pthread_getspecific( position_key ) );

   printf( "Thread: 0x%.8x, key: %u, position: 0x%.8x, x: %i, y: %i\n",
           pthread_self(), position_key, position, position->x, position->y );

   // Wait forever.
   while( true ) {};
}

$ g++ -g -lpthread example.cpp && gdb -q ./a.out 
Using host libthread_db library "/lib/libthread_db.so.1".
(gdb) r
Starting program: /tmp/a.out 
[Thread debugging using libthread_db enabled]
[New Thread -1209043248 (LWP 17390)]
pthread_key_create -- key: 0, result: 0
[New Thread -1209046128 (LWP 17393)]
Thread: 0xb7ef6b90, key: 0, value: 0x09a35008, result: 0
Thread: 0xb7ef6b90, key: 0, position: 0x09a35008, x: 11, y: 13
[New Thread -1219535984 (LWP 17394)]
Thread: 0xb74f5b90, key: 0, value: 0x09a350b0, result: 0
Thread: 0xb74f5b90, key: 0, position: 0x09a350b0, x: 22, y: 26

Program received signal SIGABRT, Aborted.
[Switching to Thread -1209043248 (LWP 17390)]
0x00377402 in __kernel_vsyscall ()

使用仍在堆栈中的地址:

(gdb) info threads
  3 Thread -1219535984 (LWP 17394)  get_position () at example.cpp:71
  2 Thread -1209046128 (LWP 17393)  get_position () at example.cpp:71
* 1 Thread -1209043248 (LWP 17390)  0x00377402 in __kernel_vsyscall ()
(gdb) thread 3
[Switching to thread 3 (Thread -1219535984 (LWP 17394))]#0  get_position () 
 at example.cpp:71
71         while( true ) {};
(gdb) list get_position
57
58         get_position();
59         return 0;
60      }
61       
62      void get_position()
63      {
64         position_t* position =
65              reinterpret_cast< position_t* >( pthread_getspecific( 
 position_key ) );
66
(gdb) info locals
position = (position_t *) 0x9a350b0
(gdb) p position->x
$1 = 22
(gdb) p position->y
$2 = 26

使用从stdout打印的地址:

(gdb) p ((position_t*)(0x09a350b0))->x 
$3 = 22
(gdb) p ((position_t*)(0x09a350b0))->y
$4 = 26

在线程内部数据中找到特定于线程的数据列表:

如果您的值为keypthread_t,则此方法会更容易。

我将介绍有关我正在使用的pthread实现的详细信息:

  • pthread struct是pthread内部使用的线程描述符结构。
  • pthread_create()返回pthread_tunsigned int,其中包含相关pthread结构的地址。

首先,找到线程的pthread结构。

(gdb) info threads
* 3 Thread -1219535984 (LWP 17394)  get_position () at example.cpp:71
  2 Thread -1209046128 (LWP 17393)  get_position () at example.cpp:71
  1 Thread -1209043248 (LWP 17390)  0x00377402 in __kernel_vsyscall ()
(gdb) thread 1
[Switching to thread 1 (Thread -1209043248 (LWP 17390))]#0  0x00377402 in
 __kernel_vsyscall ()
(gdb) bt
#0  0x00377402 in __kernel_vsyscall ()
#1  0x0080ec10 in raise () from /lib/libc.so.6
#2  0x00810521 in abort () from /lib/libc.so.6
#3  0x0804880f in main () at example.cpp:47
(gdb) frame 3
#3  0x0804880f in main () at example.cpp:47
47        abort();
(gdb) info locals
result = 0
threads = {3085921168, 3075431312}
(gdb) p/x threads[1]
$5 = 0xb74f5b90

忽略许多字段,pthread结构定义如下:

struct pthread
{
  ...
  pid_t tid; // Thread ID (i.e. this thread descriptor).
  pid_t pid; // Process ID.
  ...
  struct pthread_key_data
  {
    uintptr_t seq;
    void *data;
  } specific_1stblock[PTHREAD_KEY_2NDLEVEL_SIZE];
  struct pthread_key_data *specific[PTHREAD_KEY_1STLEVEL_SIZE];
  ...
};
  • pthread_key_data.seq:包含应该相当低且与__pthread_keys[key].seq匹配的序列号。
  • pthread_key_data.data:包含提供给pthread_setspecific()
  • 的值
  • pthread.specific_1stblock是一个块,用于在尝试动态分配更多块之前存储特定于线程的数据。
  • pthread是针对特定于线程的数据的两级数组。索引0将包含pthread.specific_1stblock
  • 的内存地址
  • PTHREAD_KEY_2NDLEVEL_SIZE的大小为32。

该定义提供了一个关于在内存中寻找什么的相当好的想法:

  • 一个整数,其值为pthread内存地址(tid),后跟一个进程标识为pid的整数。这有助于指示正在检查的内存是否为pthread结构。
  • cancelhandlingflags是标志。具体值并不重要。这些字段可能会有所帮助,因为它们的值可能与其他字段明显不同,例如包含内存地址或计数器的字段。
  • specific_1stblock是一个大小为32的数组。如果pthread结构已经零初始化,那么应该重复0个62个单词,因为示例代码只有一个特定于线程的数据position_key,其大小为两个单词。
  • specific是一个包含内存地址的数组。如果pthread结构已经零初始化,那么应该重复0 s,但第一个值应该是specific_1stblock的内存地址。

打印一大块pthread的记忆:

(gdb) p/x *((int*)threads[1])@150
$6 = {0xb74f5b90, 0x9a350c8, 0xb74f5b90, 0x1, 0x377400, 0x7fb99100,
  0xcb40329e, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xb7ef6bd0,
  0x96b118, 0x43f2, 0x43ee, 0xb74f5be0, 0xffffffec, 0x0, 0x0, 0xb74f5470,
  0x0, 0x1, 0x9a350b0, 0x0 <repeats 62 times>, 0xb74f5bf8,
  0x0 <repeats 31 times>, 0x1000101, 0x0, 0x0, 0x0, 0xc2342345, 0xe0286,
  0x0, 0x0, 0x0, 0x0, 0x0, 0x80486ca, 0x9a350b0, 0x0 <repeats 13 times>, 
  0xb6af5000, 0xa01000}

通过分析内存中的模式,某些单词成为特定pthread字段的良好候选者:

0xb74f5b90, 0x9a350c8, 0xb74f5b90 (pthread.tid), 0x1, 0x377400 (pthread.pid) ...
0x1, 0x9a350b0, 0x0 <repeats 62 times> (pthread.specific_1stblock) ...
0xb74f5bf8, 0x0 <repeats 31 times>  (pthread.specific)

可以进行一些轻量级的健全性检查,例如检查pthread.specific[0]是否包含pthread.specific_1stblock的地址:

(gdb) p/x *((int*)0xb74f5bf8)@64
$7 = {0x1, 0x9a350b0, 0x0 <repeats 62 times>} ## matches specific_1stblock

现在已经识别出pthread.specific,通过计算&pthread的字偏移量来获取其内存地址。在这种情况下,它是90:

(gdb) set $specific=(int*)threads[1] + 90

通过position_key计算第一个和第二个索引:

  • 第一个数组的索引是key / PTHREAD_KEY_2NDLEVEL_SIZE
  • 第二个数组的索引是key % PTHREAD_KEY_2NDLEVEL_SIZE

    (gdb) set $index1=position_key/32
    (gdb) set $index2=position_key%32
    

找到pthread_key_data的{​​{1}}:

position_key

因此:

(gdb) set $level2=(int*)*($specific + $index1) (gdb) p/x *($level2 + (2*$index2))@2 $8 = {0x1, 0x9a350b0}
pthread_key_data.seq = 1

第一个单词是pthread_key_data.data = 0x9a350b0,应与seq匹配。由于处理原始内存,pthread_key_struct[position_key].seq将被转换为__pthread_keys,并且必须使用指针算术来计算int*的大小:

pthread_key_struct

因此:

(gdb) p *(&((int*)&__pthread_keys)[2*position_key])@2 $9 = {1, 0}
pthread_key_struct[position_key].seq = 1

pthread_key_struct[position_key].destr = NULL个数字匹配,所以一切看起来都不错。 seq包含将从pthread_key_data.data返回的值。

pthread_getspecific( position_key )

技术上仍然可以在不了解(gdb) set $position=(position_t*)0x9a350b0 (gdb) p $position->x $10 = 22 (gdb) p $position->y $11 = 26 key值的情况下找到特定于线程的数据:

  • 如果向pthread_t提供了析构函数,则其内存地址可能位于pthread_key_create()数组中。检查内存,计算偏移量并除以size __pthread_keys。这应该导致索引,这也恰好是关键:

    pthread_key_struct
  • 如果void* destr_fn( void* ); pthread_key_create( key, destr_fn ) __pthread_keys[key].destr == destr_fn 未知,则它可能存在于线程堆栈的寄存器中。这可能需要检查各种不同的内存地址,试图在内存中找到包含pthread_t结构的部分。

    pthread

    在这种情况下,(gdb) info thread 3 Thread -1219535984 (LWP 17394) get_position () at example.cpp:71 2 Thread -1209046128 (LWP 17393) get_position () at example.cpp:71 * 1 Thread -1209043248 (LWP 17390) 0x00377402 in __kernel_vsyscall () (gdb) thread 3 [Switching to thread 3 (Thread -1219535984 (LWP 17394))]#0 g get_position () at example.cpp:71 71 while( true ) {}; (gdb) bt #0 get_position () at example.cpp:71 #1 0x0804871d in the_thread (position=0x9a350b0) at example.cpp:58 #2 0x0095c43b in start_thread () from /lib/libpthread.so.0 #3 0x008b3fde in clone () from /lib/libc.so.6 (gdb) frame 2 #2 0x0095c43b in start_thread () from /lib/libpthread.so.0 (gdb) info register eax 0x3f 63 ecx 0xb74f52ac -1219538260 edx 0x0 0 ebx 0x96aff4 9875444 esp 0xb74f53c0 0xb74f53c0 ebp 0xb74f54a8 0xb74f54a8 esi 0x0 0 edi 0xb74f5b90 -1219535984 eip 0x95c43b 0x95c43b <start_thread+203> eflags 0x200286 [ PF SF IF ID ] cs 0x73 115 ss 0x7b 123 ds 0x7b 123 es 0x7b 123 fs 0x0 0 gs 0x33 51 寄存器包含edi结构的地址。

参考文献:descr.hpthread_key_create.cpthread_setspecific.cpthreadP.hinternaltypes.h