zoukankan      html  css  js  c++  java
  • 多线程调试

    多线程调试必杀技 - GDB的non-stop模式

    作者:破砂锅

    开源的GDB被广泛使用在Linux、OSX、Unix和各种嵌入式系统(例如手机),这次它又带给我们一个惊喜。

    多线程调试之痛

    调 试器(如VS2008和老版GDB)往往只支持all-stop模式,调试多线程程序时,如果某个线程断在一个断点上,你的调试器会让整个程序 freeze,直到你continue这个线程,程序中的其他线程才会继续运行。这个限制使得被调试的程序不能够像真实环境中那样运行--当某个线程断在 一个断点上,让其他线程并行运行。

    GDBv7.0引入的non-stop模式使得这个问题迎刃而解。在这个模式下,

    • 当某个或多个线程断在一个断点上,其他线程仍会并行运行
    • 你可以选择某个被断的线程,并让它继续运行

    让我们想象一下,有了这个功能后

    • 当其他线程断在断点上时,程序里的定时器线程可以正常的运行了,从而避免不必要得超时 
    • 当其他线程断在断点上时,程序里的watchdog线程可以正常的运行了,从而避免嵌入式硬件以为系统崩溃而重启
    • 可以控制多个线程运行的顺序,从而重现deadlock场景了。由于GDB可以用python脚本驱动调试,理论上可以对程序在不同的线程运行顺序下进行自动化测试。

    因此,non-stop模式理所当然成为多线程调试“必杀技”。这2009年下半年之后发布的Linux版本里都带有GDBv7.0之后的版本。很好奇,不知道VS2010里是不是也支持类似的调试模式了。

    演示GDB的non-stop模式

    让破砂锅用一个C++小程序在Ubuntu Linux 09.10下demo这个必杀技。虽然我的demo使用命令行版gdb,如果你喜欢图形化的调试器,Eclipse2009年5月之后的版本可以轻松的调 用这个功能,详情参见Eclipse参见http://live.eclipse.org/node/723

    1. 编译以下程序nonstop

    1 // gdb non-stop mode demo
    2 // build instruction: g++ -g -o nonstop nonstop.cpp -lboost_thread
    3
    4 #include <iostream>
    5 #include <boost/thread/thread.hpp>
    6
    7 struct op
    8 {
    9         op(int id): m_id(id) {}
    10
    11 void operator()()
    12         {
    13                 std::cout << m_id << " begin" << std::endl;
    14                 std::cout << m_id << " end" << std::endl;
    15         }
    16
    17 int m_id;
    18 };
    19
    20 int main(int argc, char ** argv)
    21 {
    22         boost::thread t1(op(1)), t2(op(2)), t3(op(3));
    23         t1.join(); t2.join(); t3.join();
    24 return 0;
    25 }
    26

    2. 把一下3行添加到~/.gdbinit来打开non-stop模式

    set target-async 1
    set pagination off
    set non-stop on

    3. 启动gdb,设断点,运行.可以看到主线程1是running,3个子线程都断在断点上,而不是只有一个子线程断在断点上.

    ~/devroot/nonstop$ gdb ./nonstop
    GNU gdb (GDB) 7.0-ubuntu
    Reading symbols from /home/frankwu/devroot/nonstop/nonstop...done.
    (gdb) break 14
    Breakpoint 1 at 0x402058: file nonstop.cpp, line 14.
    (gdb) break 24
    Breakpoint 3 at 0x401805: file nonstop.cpp, line 24.
    (gdb) run
    Starting program: /home/frankwu/devroot/nonstop/nonstop
    [Thread debugging using libthread_db enabled]
    [New Thread 0x7ffff6c89910 (LWP 2762)]
    [New Thread 0x7ffff6488910 (LWP 2763)]
    1 begin
    Breakpoint 1, op::operator() (this=0x605118) at nonstop.cpp:14
    14                  std::cout << m_id << " end" << std::endl;
    2 begin
    Breakpoint 1, op::operator() (this=0x605388) at nonstop.cpp:14
    14                  std::cout << m_id << " end" << std::endl;
    [New Thread 0x7ffff5c87910 (LWP 2764)]
    3 begin
    Breakpoint 1, op::operator() (this=0x605618) at nonstop.cpp:14
    14                  std::cout << m_id << " end" << std::endl;
    (gdb) info threads
    4 Thread 0x7ffff5c87910 (LWP 2764)  op::operator() (this=0x605618) at nonstop.cpp:14
    3 Thread 0x7ffff6488910 (LWP 2763)  op::operator() (this=0x605388) at nonstop.cpp:14
    2 Thread 0x7ffff6c89910 (LWP 2762)  op::operator() (this=0x605118) at nonstop.cpp:14
    * 1 Thread 0x7ffff7fe3710 (LWP 2759)  (running)

    4. 让线程3继续运行,注意我顾意把主线程1也continue,这是我发现的workaround,否则gdb不能切回thread 1.

    (gdb) thread apply 3 1 continue
    Thread 3 (Thread 0x7ffff6488910 (LWP 2763)):
    Continuing.
    Thread 1 (Thread 0x7ffff7fe3710 (LWP 2759)):
    Continuing.
    Cannot execute this command while the selected thread is running.
    2 end
    [Thread 0x7ffff6488910 (LWP 2763) exited]
    warning: Unknown thread 3.
    Thread 1 (Thread 0x7ffff7fe3710 (LWP 2759)):
    Continuing.
    Cannot execute this command while the selected thread is running.
    (gdb) info threads
    4 Thread 0x7ffff5c87910 (LWP 2764)  op::operator() (this=0x605618) at nonstop.cpp:14
    2 Thread 0x7ffff6c89910 (LWP 2762)  op::operator() (this=0x605118) at nonstop.cpp:14
    * 1 Thread 0x7ffff7fe3710 (LWP 2759)  (running)

    5. 让另外两个线程继续运行而结束,主线程断在第24行,最后结束.

    (gdb) thread apply 4 2 1 continue
    Thread 4 (Thread 0x7ffff5c87910 (LWP 2764)):
    Continuing.
    Thread 2 (Thread 0x7ffff6c89910 (LWP 2762)):
    Continuing.
    Thread 1 (Thread 0x7ffff7fe3710 (LWP 2759)):
    Continuing.
    Cannot execute this command while the selected thread is running.
    3 end
    1 end
    [Thread 0x7ffff5c87910 (LWP 2764) exited]
    [Thread 0x7ffff6c89910 (LWP 2762) exited]
    Breakpoint 3, main (argc=1, argv=0x7fffffffe348) at nonstop.cpp:24
    24          return 0;
    (gdb) continue
    Thread 1 (Thread 0x7ffff7fe3710 (LWP 2759)):
    Continuing.
    Program exited normally.

    参考资料

    Debugging with GDB

    Reverse Debugging, Multi-Process and Non-Stop Debugging Come to the CDT

    线程有自己的寄存器,运行时堆栈或许还会有私有内存。
    gdb提供了以下供调试多线程的进程的功能:
    * 自动通告新线程。
    * "thread THREADNO ",一个用来在线程之间切换的命令。
    * "info threads ",一个用来查询现存线程的命令。
    * "thread apply [THREADNO] [ALL] ARGS ",一个用来向线程提供命令的命令。
    * 线程有关的断点设置。
    注意:这些特性不是在所有gdb版本都能使用,归根结底要看操作系统是否支持。
    如果你的gdb不支持这些命令,会显示出错信息:
        (gdb) info threads
        (gdb) thread 1
        Thread ID 1 not known. Use the "info threads " command to
        see the IDs of currently known threads.
    gdb的线程级调试功能允许你观察你程序运行中所有的线程,但无论什么时候
    gdb控制,总有一个“当前”线程。调试命令对“当前”进程起作用。
    一旦gdb发现了你程序中的一个新的线程,它会自动显示有关此线程的系统信
    息。比如:
        [New process 35 thread 27]
    不过格式和操作系统有关。
    为了调试的目的,gdb自己设置线程号。
    `info threads "
    显示进程中所有的线程的概要信息。gdb按顺序显示:
    1.线程号(gdb设置)
    2.目标系统的线程标识。
    3.此线程的当前堆栈。
    一前面打 "* "的线程表示是当前线程。
    例如:
    (gdb) info threads
    3 process 35 thread 27 0x34e5 in sigpause ()
    2 process 35 thread 23 0x34e5 in sigpause ()
    * 1 process 35 thread 13 main (argc=1, argv=0x7ffffff8)
    at threadtest.c:68
    `thread THREADNO "
    把线程号为THREADNO的线程设为当前线程。命令行参数THREADNO是gdb内定的
    线程号。你可以用 "info threads "命令来查看gdb内设置的线程号。gdb显示该线程
    的系统定义的标识号和线程对应的堆栈。比如:
    (gdb) thread 2
    [Switching to process 35 thread 23]
    0x34e5 in sigpause ()
    "Switching后的内容取决于你的操作系统对线程标识的定义。
    `thread apply [THREADNO] [ALL] ARGS "
    此命令让你对一个以上的线程发出相同的命令 "ARGS ",[THREADNO]的含义同上。
    如果你要向你进程中的所有的线程发出命令使用[ALL]选项。
    无论gdb何时中断了你的程序(因为一个断点或是一个信号),它自动选择信号或
    断点发生的线程为当前线程。gdb将用一个格式为 "[Switching to SYSTAG] "的消息
    来向你报告。

    对于多线程程序,你可以定义你的断点是否在所有的线程上,或是在某个特定的线程。GDB很容易帮你完成这一工作。
    break <linespec> thread <threadno>
    break <linespec> thread <threadno> if ...
    linespec指定了断点设置在的源程序的行号。threadno指定了线程的ID,注意,这个ID是GDB分配的,你可以通过“info threads”命令来查看正在运行程序中的线程信息。如果你不指定thread <threadno>则表示你的断点设在所有线程上面。你还可以为某线程指定断点条件。如:
    (gdb) break frik.c:13 thread 28 if bartab > lim
    当你的程序被GDB停住时,所有的运行线程都会被停住。这方便你你查看运行程序的总体情况。而在你恢复程序运行时,所有的线程也会被恢复运行。那怕是主进程在被单步调试时。

  • 相关阅读:
    最大子数组问题(分治策略实现)
    Solving the Detached Many-to-Many Problem with the Entity Framework
    Working With Entity Framework Detached Objects
    Attaching detached POCO to EF DbContext
    如何获取qq空间最近访问人列表
    Health Monitoring in ASP.NET 2.0
    problem with displaying the markers on Google maps
    WebMatrix Database.Open… Close() and Dispose()
    Accessing and Updating Data in ASP.NET: Retrieving XML Data with XmlDataSource Control
    Create web setup project that has crystal reports and sql script run manually on client system
  • 原文地址:https://www.cnblogs.com/stephen-init/p/3498944.html
Copyright © 2011-2022 走看看