在分析Linux的核心转储时,如何获取指向线程本地存储或线程特定数据的指针?
我用来在的本地存储pthread_setspecific
中存储一些数据。pthread
我在 Linux 上的多线程程序崩溃了,我想看看当前运行线程的本地存储中存储了什么。
如果我得到指向线程本地存储的指针,我可以使用 key 来获取存储的数据。
gdb中是否有获取指向线程本地存储的指针的命令?
如果您正在调试实时程序,您可以:
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
将同一记录中的字段与 gdbinfo threads
命令中显示的 LWP 进行匹配。)
祝你好运!
据我所知,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
struct 是 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 (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
),后跟一个具有进程 ID ( pid
) 的整数。这有助于指示正在检查的内存是否是pthread
结构。cancelhandling
并且flags
是标志。具体值并不重要。这些字段可能很有帮助,因为它们的值可能与其他字段明显区分开来,例如包含内存地址或计数器的字段。specific_1stblock
是一个大小为 32 的数组。如果pthread
结构已被零初始化,则应该重复0
s 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
for 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
which should match pthread_key_struct[position_key].seq
。由于处理原始内存,__pthread_keys
将被强制转换为int*
并且必须进行指针算术以考虑 sizeof 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
数组中。检查内存,计算偏移量并除以 sizeof 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