如何在分析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()
的结果是否仍然在堆栈上。 pthread_getspecific()
的结果。 下面是一个简单的程序在32位机器上的演示:
$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
(gdb) p ((position_t*)(0x09a350b0))->x $3 = 22 (gdb) p ((position_t*)(0x09a350b0))->y $4 = 26
如果您具有key
和pthread_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
结构。 cancelhandling
和flags
是标志。 具体的价值并不重要。 这些字段可能会有所帮助,因为它们的值可能与其他字段(如包含内存地址或计数器的字段)有明显区别。 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_key
的pthread_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
在技术上仍然可以在不知道key
和pthread_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