在 gdb 中的 pthread 线程本地存储上设置观察点

set watchpoint on pthread's thread-local storage in gdb

是否可以使用 GDB 在 pthread 的线程本地存储上设置观察点?我有一个运行的程序:

struct stored_type *res = pthread_getspecific(tls_key);

...在几千次之后调用它 returns 0 而不是有效指针。我真的很想弄清楚是什么将该值设置为 0。我尝试在 pthread_setspecificpthread_delete_key 上设置断点(我唯一能想到的会合理地导致密钥更改值) 并且这些断点没有被击中,所以我认为发生了某种超限。

我正在使用 Linux x86_64 和 glibc 2.23。

... the only things I could think of that would reasonably cause the key to change value

pthread_getspecific 到 return 最可能的原因 NULL:

  • 您实际上是在一个新线程中执行,其中 pthread_setspecific 尚未被调用,
  • 您正在调用 pthread_getspecific,而当前线程正在被销毁(即 pthread_exit 在堆栈中的某处),
  • 您正在信号处理程序中调用 pthread_getspecificpthread_* 函数中的 none 是异步信号安全的)。

假设上述 none 个原因在您的情况下是正确的,继续演出。

首先我们需要一个测试用例来演示。

#include <assert.h>
#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

pthread_key_t key;

void *thrfn(void *p) {
  int rc = pthread_setspecific(key, &p);
  assert(rc == 0);
  sleep(60);
  rc = pthread_setspecific(key, (void*)0x112233);
  assert(rc == 0);
  return p;
}

int main()
{
  pthread_t thr;
  int rc = pthread_key_create(&key, NULL);
  assert(rc == 0);

  rc = pthread_create(&thr, NULL, thrfn, NULL);
  assert(rc == 0);

  sleep(90);
  return 0;
}

gcc -g -pthread t.c

gdb -q ./a.out
(gdb) start

现在,拥有使用调试信息编译的 GLIBC 非常有帮助。大多数发行版都提供了一个 libc-dbg 或类似的包来提供它。查看pthread_setspecificsource,可以看到在线程描述符(self)里面有一个specific_1stblock数组,其中space为第一个[=25] =] == 32 个键槽是预先分配的(32 个不同的键通常绰绰有余)。

我们传递的值将存储在 self->specific_1stblock[key].data 中,这正是您要设置观察点的位置。

在我们的示例程序中,key == 0(因为这是第一个键)。把它们放在一起:

Starting program: /tmp/a.out
[Thread debugging using libthread_db enabled]
Using host libthread_db library "/lib64/libthread_db.so.1".

Temporary breakpoint 1, main () at t.c:21
21        int rc = pthread_key_create(&key, NULL);
(gdb) b pthread_setspecific
Breakpoint 2 at 0x7ffff7bc9460: file pthread_setspecific.c, line 28.
(gdb) c
Continuing.
[New Thread 0x7ffff77f6700 (LWP 58683)]
[Switching to Thread 0x7ffff77f6700 (LWP 58683)]

Breakpoint 2, __GI___pthread_setspecific (key=0, value=0x7ffff77f5ef8) at pthread_setspecific.c:28
28  pthread_setspecific.c: No such file or directory.
(gdb) n
35  in pthread_setspecific.c
(gdb) n
28  in pthread_setspecific.c
(gdb) p self
 = (struct pthread *) 0x7ffff77f6700
(gdb) watch -l self.specific_1stblock[key].data
Hardware watchpoint 3: -location self.specific_1stblock[key].data
(gdb) c
Continuing.
Hardware watchpoint 3: -location self.specific_1stblock[key].data

Old value = (void *) 0x0
New value = (void *) 0x7ffff77f5ef8
__GI___pthread_setspecific (key=<optimized out>, value=0x7ffff77f5ef8) at pthread_setspecific.c:89
89  in pthread_setspecific.c

请注意,新值正是我们传递给 pthread_setspecific 的值。

(gdb) c
Continuing.

Breakpoint 2, __GI___pthread_setspecific (key=0, value=0x112233) at pthread_setspecific.c:28
28  in pthread_setspecific.c
(gdb) c
Continuing.
Hardware watchpoint 3: -location self.specific_1stblock[key].data

Old value = (void *) 0x7ffff77f5ef8
New value = (void *) 0x112233
__GI___pthread_setspecific (key=<optimized out>, value=0x112233) at pthread_setspecific.c:89
89  in pthread_setspecific.c

这是我们的第二次 pthread_setspecific 通话

(gdb) c
Continuing.
Hardware watchpoint 3: -location self.specific_1stblock[key].data

Old value = (void *) 0x112233
New value = (void *) 0x0
__nptl_deallocate_tsd () at pthread_create.c:152
152 pthread_create.c: No such file or directory.

这是线程销毁,它会释放线程描述符本身。

(gdb) c
Continuing.
[Thread 0x7ffff77f6700 (LWP 58683) exited]
[Inferior 1 (process 58677) exited normally]