从Linux核心转储线程特定的数据

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

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

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

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

gdb有一个命令来获取线程本地存储指针

如果您正在调试直播节目,您可以:

print pthread_getspecific(i) 

如果你有权访问线程的pthread_t,你可以:

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

我在你想要的索引,pth是pthread_t。 请参阅glibc源文件中的nptl / pthread_getspecific.c。

要做到这一点而不调用一个函数,你需要找到struct pthread。 在x86-64上,它存储在使用arch_prctl(ARCH_SET_FS_BASE,…)设置的fs base中。 我不知道如何从gdb访问它,但你可以用eu-readelf来获得它。 运行eu-readelf --notes core_file并查看fs.base的记录。 这个数字是pthread_t的值。 (要找出它是哪一个,你可以用gdb的info threads命令中显示的LWP匹配同一记录中的pid字段。)

祝你好运!

据我所知,在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结构是pthread内部使用的线程描述符结构。
  • pthread_create()返回pthread_t ,它是一个unsigned 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 (ie 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 )值的整数,后跟一个带有进程id( pid )的整数。 这有助于指示正在检查的内存是否是一个pthread结构。
  • cancelhandlingflags是标志。 具体的价值并不重要。 这些字段可能会有所帮助,因为它们的值可能与其他字段(如包含内存地址或计数器的字段)有明显区别。
  • specific_1stblock是一个大小为32的数组。如果pthread结构已经被初始化为零,那么对于62个字应该重复0s,因为示例代码只有一个具有两个字大小的线程特定数据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 

找到position_keypthread_key_data

 (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 pthread_key_struct将被转换为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 

在技​​术上仍然可以在不知道keypthread_t值的情况下找到线程特定的数据:

  • 如果析构函数提供给pthread_key_create() ,那么它的内存地址可能会驻留在__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寄存器包含pthread结构的地址。

参考文献: descr.h , pthread_key_create.c , pthread_setspecific.c , pthreadP.h , internaltypes.h