gdb отладка многопоточного приложения

409
17 января 2018, 17:17

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

Answer 1
Базовые заметки молодой хозяйке

Для отладки (по крайней мере для комфортной отладки) необходимо собирать с отладочной информацией, в 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.

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

После остановки в произвольной точке стоит посмотреть, где же мы находимся, для этого есть команды 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»

READ ALSO
c++ UDP Win Linux

c++ UDP Win Linux

Я хочу создать обёртку над сокетами в виде 2х простых классовНа подобии QTUdp

312
отсутствуют имена функция в выводе gdb

отсутствуют имена функция в выводе gdb

Использую данную функция для вывода stacktrace

280
Как открыть Qt project из терминала Ubuntu

Как открыть Qt project из терминала Ubuntu

Создал Qt игруКогда открываю из Qt creator все нормально работает

332
Перемещение QWidget C++

Перемещение QWidget C++

Создал QWidget с именем WPunktDialog в QMainWidgetИнтересует вопрос, как заставить QWidget двигатся при нажатии на него и перемещения мышки, т

270