MyTetra Share
Делитесь знаниями!
Отладка многопоточного приложения в GDB
Время создания: 27.02.2020 13:20
Текстовые метки: linux, gdb, отладка, дебаг, debug, многопоточность, тред, thread
Раздел: Компьютер - Программирование - Отладчик GDB

Вопрос:


Возникла необходимость научиться отлаживать многопоточное приложение через gdb... предположим, есть такое приложение с несколькими потоками:


int main() {

static int i =0;

std::thread([](){ while(true) { ++i; std::this_thread::sleep_for( std::chrono::milliseconds(700)); std::cout << "hello\n";}}).detach();

std::thread([](){ while(true) { std::this_thread::sleep_for(std::chrono::milliseconds(600)); std::cout << "my\n";}}).detach();

std::thread([](){ while(true) { std::this_thread::sleep_for(std::chrono::milliseconds(900)); std::cout << "world\n";}}).detach();

std::thread([](){ while(true) { std::this_thread::sleep_for(std::chrono::milliseconds(700)); std::cout << "ro\n";}}).join();

}


Подключаемся через команду gdb -p number_of_pid:


Type "apropos word" to search for commands related to "word".

Attaching to process 9208

[New LWP 9209]

[New LWP 9210]

[New LWP 9211]

[New LWP 9212]

[Thread debugging using libthread_db enabled]

Using host libthread_db library "/lib64/libthread_db.so.1".

0x00007f1bae990acd in pthread_join () from /lib64/libpthread.so.0


И приложение останавливается... подскажите как увидеть в отладчике значение переменной i, если как только присоединяемся gdb, то все останавливается, а если делаем next, потом, предположим, br в какой-то строке, затем далее n, то все идет по-прежнему, значение переменной i(через print) вывести не удается...


Ответ:


Для отладки (по крайней мере для комфортной отладки) необходимо собирать с отладочной информацией, в gcc для этого используется ключ -g:


g++ -pthread -g thr.cpp -o thr


Можно либо сразу запустить процесс под gdb:


gdb ./thr


либо привязаться к уже запущенному процессу:


gdb -p <pid_of_already_running_process>


В первом случае, после запуска отладчика собственно процесс нужно запустить коммандой run или r:


(gdb) r

Starting program: /tmp/thr

[Thread debugging using libthread_db enabled]

Using host libthread_db library "/lib64/libthread_db.so.1".

[New Thread 0x7ffff6ee0700 (LWP 27964)]

[New Thread 0x7ffff66df700 (LWP 27965)]

[New Thread 0x7ffff5ede700 (LWP 27966)]

[New Thread 0x7ffff56dd700 (LWP 27967)]

my

hello

ro

world

.......


Процесс можно в любой момент приостановить SIGINT'ом или, другими словами, Ctrl+C. Если данная комбинация клавиш не работает, можно поступить следующим образом.


Вначале надо выяснить PID процесса программы, которая запущена под отладчиком. Это делается вв дополнительной консоли, например так:


$ ps aux | grep <programm_name>


Узнать номер сигнала SIGINT можно так:


$ kill -l

1) SIGHUP       2) SIGINT       3) SIGQUIT      4) SIGILL       5) SIGTRAP
6) SIGABRT      7) SIGBUS       8) SIGFPE       9) SIGKILL     10) SIGUSR1
11) SIGSEGV     12) SIGUSR2     13) SIGPIPE     14) SIGALRM     15) SIGTERM


В Linux номер сигнала SIGINT равен 2. Затем надо послать программе сигнал SIGINT:


$ kill -s 2 <PID>


В результате программа будет остановлена и управление перехватит отладчик.



Определение обстановки


После остановки в произвольной точке стоит посмотреть, где же мы находимся, для этого есть команды backtrace (b) и info threads (i th):


Thread 1 "thr" received signal SIGINT, Interrupt.

0x00007ffff729a93d in pthread_join () from /lib64/libpthread.so.0


(gdb) bt

#0 0x00007ffff729a93d in pthread_join () from /lib64/libpthread.so.0

#1 0x00007ffff7ab7537 in std::thread::join() () from /usr/lib/gcc/x86_64-pc-linux-gnu/6.4.0/libstdc++.so.6

#2 0x0000555555555310 in main () at thr.cpp:13


(gdb) i th

Id Target Id Frame

* 1 Thread 0x7ffff7f7d740 (LWP 28093) "thr" 0x00007ffff729a93d in pthread_join () from /lib64/libpthread.so.0

2 Thread 0x7ffff6ee0700 (LWP 28097) "thr" 0x00007ffff72a4e4d in nanosleep () from /lib64/libpthread.so.0

3 Thread 0x7ffff66df700 (LWP 28098) "thr" 0x00007ffff72a4e4d in nanosleep () from /lib64/libpthread.so.0

4 Thread 0x7ffff5ede700 (LWP 28099) "thr" 0x00007ffff72a4e4d in nanosleep () from /lib64/libpthread.so.0

5 Thread 0x7ffff56dd700 (LWP 28100) "thr" 0x00007ffff72a4e4d in nanosleep () from /lib64/libpthread.so.0


Как видно, gdb сейчас находится в контексте pthread_join() основного потока.



Смена кадра стека и печать переменной


Чтобы распечатать переменную (print) нужно переключиться на кадр, в котором она находится для этого есть команда frame (f), заодно можно посмотреть листинг(list):


(gdb) f 2

#2 0x0000555555555310 in main () at thr.cpp:13

13 std::thread([](){ while(true) { std::this_thread::sleep_for(std::chrono::milliseconds(700)); std::cout << "ro\n";}}).join();

(gdb) l

8 std::this_thread::sleep_for( std::chrono::milliseconds(700));

9 std::cout << "hello\n";}

10 }).detach();

11 std::thread([](){ while(true) { std::this_thread::sleep_for(std::chrono::milliseconds(600)); std::cout << "my\n";}}).detach();

12 std::thread([](){ while(true) { std::this_thread::sleep_for(std::chrono::milliseconds(900)); std::cout << "world\n";}}).detach();

13 std::thread([](){ while(true) { std::this_thread::sleep_for(std::chrono::milliseconds(700)); std::cout << "ro\n";}}).join();

14 }


(gdb) p i

$1 = 3


где двойка в frame 2 — это номер интересующего кадра в выводе bt.



Пошаговая отладка потока


Чтобы по-шагам отлаживать конкретный поток нужно переключить gdb в его контекст командой thread (thr):


(gdb) thread 2

[Switching to thread 2 (Thread 0x7ffff6ee0700 (LWP 28097))]

#0 0x00007ffff72a4e4d in nanosleep () from /lib64/libpthread.so.0


У каждого потока свой стек, поэтому не лишним будет снова посмотреть backtrace и, по необходимости, перейти в нужный кадр.


(gdb) bt

#0 0x00007ffff72a4e4d in nanosleep () from /lib64/libpthread.so.0

#1 0x0000555555556aa7 in std::this_thread::sleep_for<long, std::ratio<1l, 1000l> > (__rtime=...) at /usr/lib/gcc/x86_64-pc-linux-gnu/6.4.0/include/g++-v6/thread:323

#2 0x0000555555555140 in <lambda()>::operator()(void) const (__closure=0x55555576bc28) at thr.cpp:8

#3 0x0000555555556524 in std::_Bind_simple<main()::<lambda()>()>::_M_invoke<>(std::_Index_tuple<>) (this=0x55555576bc28) at /usr/lib/gcc/x86_64-pc-linux-gnu/6.4.0/include/g++-v6/functional:1391

#4 0x0000555555556394 in std::_Bind_simple<main()::<lambda()>()>::operator()(void) (this=0x55555576bc28) at /usr/lib/gcc/x86_64-pc-linux-gnu/6.4.0/include/g++-v6/functional:1380

#5 0x0000555555556292 in std::thread::_State_impl<std::_Bind_simple<main()::<lambda()>()> >::_M_run(void) (this=0x55555576bc20) at /usr/lib/gcc/x86_64-pc-linux-gnu/6.4.0/include/g++-v6/thread:197

#6 0x00007ffff7ab724e in ?? () from /usr/lib/gcc/x86_64-pc-linux-gnu/6.4.0/libstdc++.so.6

#7 0x00007ffff7299657 in start_thread () from /lib64/libpthread.so.0

#8 0x00007ffff6fd9c5f in clone () from /lib64/libc.so.6


После этого можно отлаживать поток привычным способом с помощью next/step (n/s):


(gdb) n

Single stepping until exit from function nanosleep,

which has no line number information.

my

world

ro

std::this_thread::sleep_for<long, std::ratio<1l, 1000l> > (__rtime=...) at /usr/lib/gcc/x86_64-pc-linux-gnu/6.4.0/include/g++-v6/thread:328

328 }

(gdb)

my

world

ro

<lambda()>::operator()(void) const (__closure=0x55555576bc28) at thr.cpp:9

9 std::cout << "hello\n";}

(gdb)

my

ro

world

hello

7 ++i;

(gdb)

my

world

ro

8 std::this_thread::sleep_for( std::chrono::milliseconds(700));

(gdb)

world

my

ro

my

ro

9 std::cout << "hello\n";}

(gdb)

my

world

ro

hello

7 ++i;

(gdb)

my

world

ro

8 std::this_thread::sleep_for( std::chrono::milliseconds(700));

(gdb) p i

$2 = 5


Стоит упомянуть пару замечаний:


Ради пошаговый отладки код был слегка переформатирован, как видно в листинге выше.

Касательно многопоточных приложений: при возврате управления процессу по n запускаются сразу все потоки, поэтому можно видеть дополнительный вывод почти после каждой комманды.


К последующему прочтению/просмотру рекомендую, как минимум, букварь «Отладка с помощью GDB»


 
MyTetra Share v.0.53
Яндекс индекс цитирования