Base notes of a young mistress
For debugging (at least for comfortable debugging) it is necessary to collect with debug information, in gcc the -g key is used for this:
g++ -pthread -g thr.cpp -o thr
You can either immediately start the process under gdb:
gdb ./thr
or bind to an already running process:
gdb -p <pid_of_already_running_process>
In the first case, after running the debugger, the process itself needs to be run with the run or 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 .......
The process can be suspended at any time by SIGINT or, in other words, Ctrl + C.
Determination of the situation
After stopping at an arbitrary point, it’s worth looking where we are, for this there are commands backtrace ( b ) and 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
As you can see, gdb is now in the context of the main thread's pthread_join() .
Change stack frame and print variable
To print a variable ( print ), you need to switch to the frame in which it is located for this, there is the frame command ( f ), at the same time you can see the listing ( 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) pi $1 = 3
where the two in frame 2 is the number of the frame of interest in the bt output.
Step by step debugging flow
To debug a specific thread step by step, you need to switch gdb to its context with the thread ( thr ) command:
(gdb) thread 2 [Switching to thread 2 (Thread 0x7ffff6ee0700 (LWP 28097))] #0 0x00007ffff72a4e4d in nanosleep () from /lib64/libpthread.so.0
Each thread has its own stack, so it will not be superfluous to look at the backtrace again and, if necessary, go to the desired frame.
(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
After that, you can debug the flow in the usual way with the help of 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) pi $2 = 5
A couple of notes worth mentioning are:
- For the sake of step-by-step debugging, the code was slightly reformatted, as seen in the listing above.
- Regarding multi-threaded applications: when control returns to process
n all threads are started at once, so you can see additional output almost after each command.
For later reading / viewing I recommend, at a minimum, the primer “ Debugging with GDB ”