gdb基本使用及多线程调试

14 篇文章 0 订阅

在linux环境下进行c++开发调试的时候,不可避免需要用到gdb,它可以控制程序的启动暂停、添加断点、打印堆栈,能够帮助我们尽快的发现问题、定位错误,是一把利器。本文打算总结一下gdb的简单使用。

添加编译参数

要使用gdb调试,首先在编译程序时需要添加编译参数-g,这样可执行文件中包含供gdb调试器进行调试所需要的信息,缺点就是文件稍微大一些。例如:

g++ main.cpp -o test -g

其次,当程序发生Segment fault时,操作系统把程序当前的内存状况存储在一个core文件中,调试需要用到core文件,可以通过下面命令来打开生成core文件。

ulimit -c unlimited

基本原理

gdb为什么可以控制进程的执行喃?主要是通过ptrace系统调用,原型如下

#include <sys/ptrace.h>
long ptrace(enum __ptrace_request request, pid_t pid, void *addr, void *data);
DESCRIPTION
       The  ptrace()  system  call  provides  a  means by which one process (the "tracer") may observe and control the execution of another
       process (the "tracee"), and examine and change the tracee's memory and registers.  It is  primarily  used  to  implement  breakpoint
       debugging and system call tracing.

通过man ptrace我们可以看到,ptrace系统调用提供了一种方法,通过这种方法tracer可以观察和控制另一个进程tracee的执行,并检查和更改tracee的内存和寄存器。主要用于实现断点调试和系统调用跟踪。

参数request是一个枚举量,它的值决定要执行的操作:
PTRACE_TRACEME:
  此进程将由其父进程跟踪,这个参数只由tracee使用,下面的其他的request参数是由父进程使用的。

PTRACE_ATTACH:
  attach到一个指定的进程,使其成为调用进程的跟踪对象。

PTRACE_CONT:
  重新启动已停止的跟踪进程。

对直接gdb启动调试程序的时候来讲,首先gdb会fork一个子进程,这时子进程会调用ptrace系统调用,传入request为PTRACE_TRACEME,然后调用exec执行调试程序。
对使用gdb使用attach命令对已启动的程序进行调试时,传入的request为PTRACE_ATTACH,gdb就自动成为调试进程的父进程。
当gdb和被调试的进程建立好这种关系后,发给目标程序的任何信号(除SIGKILL之外)gdb都会先收到,会根据信号的属性决定在继续目标程序运行时是否将信号实际交付给目标程序。

基本使用

1.将之前线程池sample稍作修改,编译并启动:

void task1() {
    int init = 1;
    while (1) {
        std::cout << "task1 start" << std::endl;
        init = init * 2;
        std::this_thread::sleep_for(std::chrono::seconds(3));
        std::cout << "task1 finish" << std::endl;
    }
}

void task2() {
    int init = 2;
    while (1) {
        std::cout << "task2 start" << std::endl;
        init = init * 2;
        std::this_thread::sleep_for(std::chrono::seconds(5));
        std::cout << "task2 finish" << std::endl;
    }
}

void task3() {
    int init = 3;
    while (1) {
        std::cout << "task3 start" << std::endl;
        init = init * 2;
        std::this_thread::sleep_for(std::chrono::seconds(8));
        std::cout << "task3 finish" << std::endl;
    }
}

int main() {
    ThreadPool thread_pool(3);
    thread_pool.AddTask(task1);
    thread_pool.AddTask(task2);
    thread_pool.AddTask(task3);

    std::this_thread::sleep_for(std::chrono::seconds(1));
    return 0;
}
$g++ main.cpp -o test_gdb --std=c++11 -lpthread -g
$./test_gdb

2.查看运行的程序的pid

$ps aux | grep test_gdb
xx       21632  0.0  0.0 236680  1748 pts/1    Sl+  16:56   0:00 ./test_gdb
xx       21677  0.0  0.0  15968  1016 pts/20   S+   16:57   0:00 grep --color=auto test_gdb

3.查看运行程序的线程

$ps -aL | grep test_gdb
21632 21632 pts/1    00:00:00 test_gdb
21632 21633 pts/1    00:00:00 test_gdb
21632 21634 pts/1    00:00:00 test_gdb
21632 21635 pts/1    00:00:00 test_gdb

4.查看主线程和子线程的关系

$pstree -p 21632
test_gdb(21632)─┬─{test_gdb}(21633)
                ├─{test_gdb}(21634)
                └─{test_gdb}(21635)

5.使用gdb attach到线层进行调试,这里我们attach到主线程

$gdb attach -p 21632
GNU gdb (Ubuntu 7.11.1-0ubuntu1~16.5) 7.11.1
Copyright (C) 2016 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.  Type "show copying"
and "show warranty" for details.
This GDB was configured as "x86_64-linux-gnu".
Type "show configuration" for configuration details.
For bug reporting instructions, please see:
<http://www.gnu.org/software/gdb/bugs/>.
Find the GDB manual and other documentation resources online at:
<http://www.gnu.org/software/gdb/documentation/>.
For help, type "help".
Type "apropos word" to search for commands related to "word"...
attach: No such file or directory.
Attaching to process 21632
[New LWP 21633]
[New LWP 21634]
[New LWP 21635]
[Thread debugging using libthread_db enabled]
Using host libthread_db library "/lib/x86_64-linux-gnu/libthread_db.so.1".
0x00007fcfeb96998d in pthread_join (threadid=140530974414592, thread_return=0x0) at pthread_join.c:90
90      pthread_join.c: No such file or directory.

从上面也可以看到创建了三个子线程
[New LWP 21633]
[New LWP 21634]
[New LWP 21635]

6.查看线程信息 info thread

(gdb) info thread
  Id   Target Id         Frame 
* 1    Thread 0x7fcfebd5e740 (LWP 21632) "test_gdb" 0x00007fcfeb96998d in pthread_join (threadid=140530974414592, thread_return=0x0)
    at pthread_join.c:90
  2    Thread 0x7fcfeacf5700 (LWP 21633) "test_gdb" 0x00007fcfeb971c1d in nanosleep () at ../sysdeps/unix/syscall-template.S:84
  3    Thread 0x7fcfea4f4700 (LWP 21634) "test_gdb" 0x00007fcfeb971c1d in nanosleep () at ../sysdeps/unix/syscall-template.S:84
  4    Thread 0x7fcfe9cf3700 (LWP 21635) "test_gdb" 0x00007fcfeb971c1d in nanosleep () at ../sysdeps/unix/syscall-template.S:84

可以看到一共有4个线程,前面有个星号的表示当前线程。

7.切换线程 thread id

(gdb) thread 2
[Switching to thread 2 (Thread 0x7fcfeacf5700 (LWP 21633))]
#0  0x00007fcfeb971c1d in nanosleep () at ../sysdeps/unix/syscall-template.S:84
84      ../sysdeps/unix/syscall-template.S: No such file or directory.
(gdb) info thread
  Id   Target Id         Frame 
  1    Thread 0x7fcfebd5e740 (LWP 21632) "test_gdb" 0x00007fcfeb96998d in pthread_join (threadid=140530974414592, thread_return=0x0)
    at pthread_join.c:90
* 2    Thread 0x7fcfeacf5700 (LWP 21633) "test_gdb" 0x00007fcfeb971c1d in nanosleep () at ../sysdeps/unix/syscall-template.S:84
  3    Thread 0x7fcfea4f4700 (LWP 21634) "test_gdb" 0x00007fcfeb971c1d in nanosleep () at ../sysdeps/unix/syscall-template.S:84
  4    Thread 0x7fcfe9cf3700 (LWP 21635) "test_gdb" 0x00007fcfeb971c1d in nanosleep () at ../sysdeps/unix/syscall-template.S:84

8.查看线程栈结构 bt

(gdb) bt
#0  0x00007fcfeb971c1d in nanosleep () at ../sysdeps/unix/syscall-template.S:84
#1  0x0000000000402e99 in std::this_thread::sleep_for<long, std::ratio<1l, 1l> > (__rtime=...) at /usr/include/c++/4.8/thread:282
#2  0x000000000040183b in task2 () at main.cpp:24
#3  0x00000000004037e6 in std::_Function_handler<void (), void (*)()>::_M_invoke(std::_Any_data const&) (__functor=...)
    at /usr/include/c++/4.8/functional:2071
#4  0x0000000000402b36 in std::function<void ()>::operator()() const (this=0x7fcfeacf4e10) at /usr/include/c++/4.8/functional:2471
#5  0x00000000004021c4 in MyThreadPool::ThreadPool::Run (this=0x7ffc067b0fe0) at ThreadPool.hpp:43
#6  0x00000000004073a1 in std::_Mem_fn<void (MyThreadPool::ThreadPool::*)()>::operator()<, void>(MyThreadPool::ThreadPool*) const (
    this=0x10e0eb8, __object=0x7ffc067b0fe0) at /usr/include/c++/4.8/functional:601
#7  0x00000000004072a7 in std::_Bind_simple<std::_Mem_fn<void (MyThreadPool::ThreadPool::*)()> (MyThreadPool::ThreadPool*)>::_M_invoke<0ul>(std::_Index_tuple<0ul>) (this=0x10e0eb0) at /usr/include/c++/4.8/functional:1732
#8  0x0000000000407165 in std::_Bind_simple<std::_Mem_fn<void (MyThreadPool::ThreadPool::*)()> (MyThreadPool::ThreadPool*)>::operator()() (
    this=0x10e0eb0) at /usr/include/c++/4.8/functional:1720
#9  0x0000000000406fd2 in std::thread::_Impl<std::_Bind_simple<std::_Mem_fn<void (MyThreadPool::ThreadPool::*)()> (MyThreadPool::ThreadPool*)> >::_M_run() (this=0x10e0e98) at /usr/include/c++/4.8/thread:115
#10 0x00007fcfeb697c80 in ?? () from /usr/lib/x86_64-linux-gnu/libstdc++.so.6
#11 0x00007fcfeb9686ba in start_thread (arg=0x7fcfeacf5700) at pthread_create.c:333
#12 0x00007fcfeb10641d in clone () at ../sysdeps/unix/sysv/linux/x86_64/clone.S:109

可以看到thread 2对应的是task2函数。
9.添加断点 break ( b ) filename:line-number / function-name
显示断点信息 info b
清除Num号断点 delete Num
清除N行上所有断点 clear N

(gdb) b task2
Breakpoint 2 at 0x4017ef: file main.cpp, line 20.
(gdb) info b
Num     Type           Disp Enb Address            What
2       breakpoint     keep y   0x00000000004017ef in task2() 
                                                   at main.cpp:20
(gdb) clear task2
Deleted breakpoint 2

添加断点之后继续运行,可以重新启动程序,运行到断点处停止

(gdb) b task2
Breakpoint 3 at 0x4017ef: file main.cpp, line 20.
(gdb) r
The program being debugged has been started already.
Start it from the beginning? (y or n) y
Starting program: /home/xj/c++11/threadpool/test_gdb 
[Thread debugging using libthread_db enabled]
Using host libthread_db library "/lib/x86_64-linux-gnu/libthread_db.so.1".
[New Thread 0x7ffff6f4e700 (LWP 21909)]
[New Thread 0x7ffff674d700 (LWP 21910)]
[New Thread 0x7ffff5f4c700 (LWP 21911)]
task1 start
task3 start
[Switching to Thread 0x7ffff674d700 (LWP 21910)]

Thread 3 "test_gdb" hit Breakpoint 3, task2 () at main.cpp:20
20          int init = 2;

可以看到重新启动程序之后,程序在task2函数的第一行停止了。
10.只运行当前线程 set scheduler-locking on
执行下一步 next ( n )
继续执行 continue ( c )
打印参数 print ( p )

(gdb) set scheduler-locking on
(gdb) n
22              std::cout << "task2 start" << std::endl;
(gdb) n
task2 start
23              init = init * 2;
(gdb) print init
$1 = 2
(gdb) n
24              std::this_thread::sleep_for(std::chrono::seconds(5));
(gdb) print init
$2 = 4

只运行当前线程即task2,继续执行几步,打印init查看值。

所有线程都运行 set scheduler-locking off

11.显示代码 list,默认当前行往下10行

(gdb) list 
11          while (1) {
12              std::cout << "task1 start" << std::endl;
13              init = init * 2;
14              std::this_thread::sleep_for(std::chrono::seconds(3));
15              std::cout << "task1 finish" << std::endl;
16          }
17      }
18
19      void task2() {
20          int init = 2;

12.强制返回当前函数 return

(gdb) return
Make task2() return now? (y or n) y
#0  std::_Function_handler<void (), void (*)()>::_M_invoke(std::_Any_data const&) (__functor=...) at /usr/include/c++/4.8/functional:2073
2073          }

13.检测表达式变化 watch

(gdb) watch init==4
Hardware watchpoint 7: init==4
(gdb) info b
Num     Type           Disp Enb Address            What
3       breakpoint     keep y   0x00000000004017ef in task2() at main.cpp:20
        breakpoint already hit 1 time
7       hw watchpoint  keep y                      init==4
(gdb) c
Continuing.
task1 finish
task1 start
[Thread 0x7ffff6f4e700 (LWP 23988) exited]
task2 start

Thread 3 "test_gdb" hit Hardware watchpoint 7: init==4

Old value = false
New value = true
task2 () at main.cpp:24
24              std::this_thread::sleep_for(std::chrono::seconds(5));
(gdb) print init
$6 = 4

14.此外还有一些常用的命令:
(1)设置参数 set args
(2)修改变量的值 print

(gdb) print init=200
$7 = 200

(3)执行上一次命令 回车
(4)单步执行 step
与next区别在于,next遇到函数会跳过,step则会进入函数。
(5)查看哪一层调用栈 frame ( f )

(gdb) bt
#0  0x00007ff97b998c1d in nanosleep () at ../sysdeps/unix/syscall-template.S:84
#1  0x0000000000402e99 in std::this_thread::sleep_for<long, std::ratio<1l, 1l> > (__rtime=...) at /usr/include/c++/4.8/thread:282
#2  0x000000000040183b in task2 () at main.cpp:24
#3  0x00000000004037e6 in std::_Function_handler<void (), void (*)()>::_M_invoke(std::_Any_data const&) (__functor=...)
    at /usr/include/c++/4.8/functional:2071
#4  0x0000000000402b36 in std::function<void ()>::operator()() const (this=0x7ff97ad1be10) at /usr/include/c++/4.8/functional:2471
#5  0x00000000004021c4 in MyThreadPool::ThreadPool::Run (this=0x7ffc7d328240) at ThreadPool.hpp:43
#6  0x00000000004073a1 in std::_Mem_fn<void (MyThreadPool::ThreadPool::*)()>::operator()<, void>(MyThreadPool::ThreadPool*) const (
    this=0xe71eb8, __object=0x7ffc7d328240) at /usr/include/c++/4.8/functional:601
#7  0x00000000004072a7 in std::_Bind_simple<std::_Mem_fn<void (MyThreadPool::ThreadPool::*)()> (MyThreadPool::ThreadPool*)>::_M_invoke<0ul>(std::_Index_tuple<0ul>) (this=0xe71eb0) at /usr/include/c++/4.8/functional:1732
#8  0x0000000000407165 in std::_Bind_simple<std::_Mem_fn<void (MyThreadPool::ThreadPool::*)()> (MyThreadPool::ThreadPool*)>::operator()() (
    this=0xe71eb0) at /usr/include/c++/4.8/functional:1720
#9  0x0000000000406fd2 in std::thread::_Impl<std::_Bind_simple<std::_Mem_fn<void (MyThreadPool::ThreadPool::*)()> (MyThreadPool::ThreadPool*)> >::_M_run() (this=0xe71e98) at /usr/include/c++/4.8/thread:115
#10 0x00007ff97b6bec80 in ?? () from /usr/lib/x86_64-linux-gnu/libstdc++.so.6
#11 0x00007ff97b98f6ba in start_thread (arg=0x7ff97ad1c700) at pthread_create.c:333
#12 0x00007ff97b12d41d in clone () at ../sysdeps/unix/sysv/linux/x86_64/clone.S:109
(gdb) f 2
#2  0x000000000040183b in task2 () at main.cpp:24
24              std::this_thread::sleep_for(std::chrono::seconds(5));

更多详细的使用命令,可以通过help查看。

  • 0
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值