Debugging C++ Multithreaded Programs with GDB

We are currently in a multi-core era, and multithreaded programs are very common. This article will explore how to debug C++ multithreaded programs.

Example Code

This article designs a demo multithreaded program where the main thread starts two business threads that execute the functions business_1 and business_2. Each of these functions defines a counter object that increments by 1 every 100 milliseconds.

#include <thread>#include <chrono>
class counter{public:    void add_one()    {        ++count_;    }private:    uint32_t count_{ 0 };};
void business_1(){    counter cnt{};    while (true) {        cnt.add_one();        std::this_thread::sleep_for(std::chrono::milliseconds(100));    }}
void business_2(){    counter cnt{};    while (true) {        cnt.add_one();        std::this_thread::sleep_for(std::chrono::milliseconds(100));    }}
int main(){    std::thread t1(&business_1);    std::thread t2(&business_2);    t1.join();    t2.join();    return 0;}

Basic Debugging

Step 1: Run the Program with GDB

Command: gdb -tui test

Step 2: Set a Breakpoint on the add_one Member Function of the Counter Class

Command: b counter::add_oneDebugging C++ Multithreaded Programs with GDB

Step 3: Run the Program

Command: r As shown in the figure below, after running, thread 2 first triggered the breakpoint set in the previous step.Debugging C++ Multithreaded Programs with GDB

Step 4: Check Thread Status

Command: info threadDebugging C++ Multithreaded Programs with GDBAt this point, there are 3 threads running:(1) Thread 1 is the main thread, which waits for threads 2 and 3 to exit after calling join on them;(2) Thread 2 is a child thread, but it is currently unclear whether it is business_1 or business_2;(3) Thread 3 is a child thread, but it is currently unclear whether it is business_1 or business_2;Thread 2 is marked with an *, indicating it is the current thread.

Step 5: View Thread Stack Information

Command: thread apply all btDebugging C++ Multithreaded Programs with GDBFrom the above figure, it can be seen that Thread 2 is running the business_1 function, and Thread 3 is running the business_2 function.Note: You can also view the stack information of a specific thread with the following commands:(1) To view the current thread stack, use bt(2) To view a specific thread stack, for example, for Thread 2, run thread apply 2 bt

Step 6: Switch Threads

Command: thread thread_number Assuming you want to switch to thread 3, execute thread 3Debugging C++ Multithreaded Programs with GDB

Step 7: Step Execution

Command: nDebugging C++ Multithreaded Programs with GDBDuring the step execution process, an issue arises, as shown in the figure above. Since both child threads have breakpoints set on counter::add_one, when stepping through the current thread, the other thread’s breakpoint is always triggered, leading to frequent switching between threads 2 and 3, which is not our expectation.

Problem Description

In Step 7 of the basic debugging section, we encountered the problem of frequent thread switching during step debugging. This is due to both child threads having breakpoints set on counter::add_one. When thread 2 is stepped through, thread 3 is also running, which causes thread 3 to trigger the breakpoint and lead to a switch.Is there a way to keep the current thread from switching during debugging? The answer is yes; the solution is to set the appropriate scheduler-locking parameter.

Scheduler-Locking

The function of scheduler-locking is to control whether other threads can execute while debugging the current thread. The values that can be set are:(1) off, do not lock threads; during debugging, all threads run together;(2) on, only the current thread is allowed to execute; during debugging, other threads will not execute;(3) step, during step debugging, other threads will not execute, but when executing continue, until, or finish commands, other threads will also execute.Note: The default value of scheduler-locking varies by version of gdb. You can check the default value with show scheduler-locking. In this article’s example, the default value is off.

Solution

After understanding the function and parameters of scheduler-locking, we know that to prevent the current thread from switching during debugging, we just need to set scheduler-locking to on. The commands are as follows:(1) set scheduler-locking on — Set(2) show scheduler-locking — Check if the setting was successfulAfter setting, input c multiple times for debugging, as shown in the figure below. This time, the thread remains on thread 2 and is no longer switched, solving the problem.Debugging C++ Multithreaded Programs with GDB

Conclusion

When designing large-scale applications, code reusability is often emphasized. Different threads may call the same functions, and it is common to set breakpoints on these generic functions during debugging. Mastering the debugging techniques in this article will help you navigate the multithreading debugging process more effectively.

📬 Feel free to follow the WeChat public account “Hankin-Liu’s Technical Research Room” for continuous sharing of content related to trusted innovation, software performance testing, optimization, programming skills, and software debugging techniques, providing valuable and well-grounded technical insights.

Leave a Comment