gdb基本使用及多線程調試

在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查看。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章