Специфичные для потока данные из дампа ядра Linux

Как получить указатель на локальное хранилище потока или конкретные данные потока при анализе дампа ядра для Linux?

Я использую pthread_setspecific для хранения некоторых данных в локальном хранилище pthread.

моя многопоточная программа в Linux потерпела крах, и я хочу посмотреть, что хранится в локальном хранилище текущего запущенного потока.

Если я получаю указатель на локальное хранилище потока, я могу использовать ключ для получения сохраненных данных.

Есть ли в gdb команда для получения указателя на локальное хранилище потока?


person Vishwanath Sungal    schedule 31.05.2012    source источник


Ответы (2)


Если вы отлаживаете работающую программу, вы можете:

print pthread_getspecific(i)

Если у вас есть доступ к pthread_t потока, вы можете:

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

где i в нужном вам индексе, а pth — это pthread_t. См. nptl/pthread_getspecific.c в исходниках glibc.

Чтобы сделать это без вызова функции, вам нужно найти структуру pthread. На x86-64 он хранится в базе fs, которая устанавливается с помощью arch_prctl(ARCH_SET_FS_BASE, ...). Я не знаю, как получить доступ к этому из gdb, но вы можете получить его с помощью eu-readelf. Запустите eu-readelf --notes core_file и просмотрите записи для fs.base. Это число является значением pthread_t. (Чтобы выяснить, какой именно, вы можете сопоставить поле pid в той же записи с LWP, показанным в команде gdb info threads.)

Удачи!

person Andy Lutomirski    schedule 02.06.2012
comment
я не могу получить значение ptherad_t, есть ли другой способ получить это значение. мой первоначальный вопрос заключался в том, как получить структуру потока. - person Vishwanath Sungal; 05.06.2012

Насколько я знаю, в gdb нет команды для получения указателя на данные, хранящиеся через pthread_setspecific(). Однако есть несколько вариантов получения адреса памяти:

  • Изучите обратную трассировку каждого потока, проверяя каждый кадр, чтобы убедиться, что результат pthread_getspecific() все еще находится в стеке.
  • Измените существующий код, чтобы регистрировать как идентификатор потока, так и результат 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

Использование адресов, напечатанных из стандартного вывода:

(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), за которым следует целое число с идентификатором процесса (pid). Это полезно, чтобы указать, является ли исследуемая память структурой pthread.
  • cancelhandling и flags — это флаги. Конкретные значения не важны. Эти поля могут быть полезны, потому что их значения потенциально визуально отличимы от других полей, таких как поля, содержащие адреса памяти или счетчики.
  • specific_1stblock — это массив размером 32. Если структура pthread была инициализирована нулями, то должны повторяться 0s для 62~ слов, потому что в примере кода есть только одни данные для конкретного потока position_key размером в два слова.
  • specific — это массив, содержащий адреса памяти. Если структура pthread была инициализирована нулями, то должны повторяться 0s, но первым значением должен быть адрес памяти 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 для 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() была предоставлена ​​функция деструктора, то ее адрес памяти может находиться в массиве __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

person Tanner Sansbury    schedule 02.06.2012
comment
не могли бы вы объяснить, как определить регистр, который содержит структуру pthread. из информационного реестра - person Vishwanath Sungal; 05.06.2012
comment
стек поврежден, поэтому я не могу использовать bt, - person Vishwanath Sungal; 05.06.2012
comment
Я полагаю, что это зависит от реализации, поэтому либо обратитесь к реализации nptl, распечатайте и проанализируйте память по каждому адресу, хранящемуся в регистрах, либо попробуйте проанализировать поведение nptl в программе-макете, похожей на example.cpp. Кроме того, имейте в виду, что регистры могут быть недействительными из-за повреждения стека. - person Tanner Sansbury; 05.06.2012
comment
Обратите внимание, что -1219535984 в выводе gdb Thread -1219535984 уже является адресом pthread_t. - person Paolo Bonzini; 21.07.2015