gdb調試總結

一,gdb調試基礎

(1)用gdb編譯程序時,首先在編譯程序時加-g

gcc test.c -o gdb_test -g

(2)運行程序

  • gdb + 編譯後的文件名
  • 在命令行中先輸入gdb,按回車,再輸入file + 編譯後的文件

(3)run命令就可以將程序運行起來了,簡寫 r

(4)斷點

  • 可以對某一行進行打斷點 例:對程序的第三行進行打斷點 b 3
  • 多個文件,可以對某個文件的某一行打斷點, 例:對test.c的第三行打斷點 b test.c:3 
  • 可以對某個函數進行打斷點 例:對程序中函數打斷點 b func
  • 也可以對多個文件中的某一個文件的函數打斷點,例: b test.c:func
  • 還可以以條件表達式設置斷點, 例: break n if 條件

(5)暫停某個斷點:

  • 如果不需要程序在該斷點暫停時,有兩種方法,一種是使該斷點失效,一種是直接刪除該斷點
  • enable和disable break啓用和暫停某個斷點
  • delete break 刪除所有的斷點
  • delete break n 刪除某個斷點 n爲斷點號
  • clear 行號    刪除設在某一行的斷點

(6)查看斷點信息

  • info b  查看所有斷點信息

  • info b n  查看第n個斷點的信息

(7)打印變量值print,簡化p

(8)程序繼續執行continue,簡化c

(9)顯示程序list,簡化l

  • list 5,10   顯示第5行到第10行的代碼;
  • list func   顯示func函數週圍的代碼,顯示範圍和list參數有關;
  • list test.c:5,10  顯示源文件test.c第5行到第10行的代碼,一般用於調試含多個源文件的程序。

(10)清屏shell clear

(11)next和step

next,繼續執行下一條語句;還有一條命令step,與之類似,不同的是,當下一條語句遇到函數調用的時候,next不會跟蹤進入函數,而是繼續執行下面的語句,而step命令則會跟蹤進入函數內部。

函數返回可以使用return和finish命令

(12)退出gdb調試 quit

二,函數

(1)直接執行函數,使用“call”或“print”命令

#include <stdio.h>

int global = 1;

int func(void) 
{
	return (++global);
}

int main(void)
{
	printf("%d\n", global);
	return 0;
}

使用gdb調試程序時,可以使用“call”或“print”命令直接調用函數執行。以上面程序爲例:

(gdb) start
Temporary breakpoint 1 at 0x4004e3: file a.c, line 12.
Starting program: /data2/home/nanxiao/a

Temporary breakpoint 1, main () at a.c:12
12              printf("%d\n", global);
(gdb) call func()
$1 = 2
(gdb) print func()
$2 = 3
(gdb) n
3
13              return 0;

可以看到執行兩次func函數後,global的值變成3

(2)選擇函數堆棧幀“frame n”命令

#include <stdio.h>

int func1(int a)
{
        return 2 * a;
}

int func2(int a)
{
        int c = 0;
        c = 2 * func1(a);
        return c;
}

int func3(int a)
{
        int c = 0;
        c = 2 * func2(a);
        return c;
}

int main(void)
{
        printf("%d\n", func3(10));
        return 0;
}

用gdb調試程序時,當程序暫停後,可以用“frame n”命令選擇函數堆棧幀,其中n是層數。以上面程序爲例:

(gdb) b test.c:5
Breakpoint 1 at 0x40053d: file test.c, line 5.
(gdb) r
Starting program: /home/nanxiao/test

Breakpoint 1, func1 (a=10) at test.c:5
5               return 2 * a;
(gdb) bt
#0  func1 (a=10) at test.c:5
#1  0x0000000000400560 in func2 (a=10) at test.c:11
#2  0x0000000000400586 in func3 (a=10) at test.c:18
#3  0x000000000040059e in main () at test.c:24
(gdb) frame 2
#2  0x0000000000400586 in func3 (a=10) at test.c:18
18              c = 2 * func2(a);

可以看到程序斷住後,最內層的函數幀爲第0幀。執行frame 2命令後,當前的堆棧幀變成了fun3的函數幀。

也可以用“frame addr”命令選擇函數堆棧幀,其中addr是堆棧地址。仍以上面程序爲例:

(gdb) frame 2
#2  0x0000000000400586 in func3 (a=10) at test.c:18
18              c = 2 * func2(a);
(gdb) i frame
Stack level 2, frame at 0x7fffffffe590:
 rip = 0x400586 in func3 (test.c:18); saved rip = 0x40059e
 called by frame at 0x7fffffffe5a0, caller of frame at 0x7fffffffe568
 source language c.
 Arglist at 0x7fffffffe580, args: a=10
 Locals at 0x7fffffffe580, Previous frame's sp is 0x7fffffffe590
 Saved registers:
  rbp at 0x7fffffffe580, rip at 0x7fffffffe588
(gdb) frame 0x7fffffffe568
#1  0x0000000000400560 in func2 (a=10) at test.c:11
11              c = 2 * func1(a);

使用“i frame”命令可以知道0x7fffffffe568func2的函數堆棧幀地址,使用“frame 0x7fffffffe568”可以切換到func2的函數堆棧幀。

(3)用gdb調試程序時,當程序暫停後,可以用“up n”或“down n”命令向上或向下選擇函數堆棧幀,其中n是層數。

(4)退出正在調試的函數“finish”命令或者“return”命令退出

三,斷點

(1)在程序入口處打斷點

獲取程序入口

方法一:

$ strip a.out
$ readelf -h a.out 
ELF Header:
  Magic:   7f 45 4c 46 02 01 01 00 00 00 00 00 00 00 00 00 
  Class:                             ELF64
  Data:                              2's complement, little endian
  Version:                           1 (current)
  OS/ABI:                            UNIX - System V
  ABI Version:                       0
  Type:                              EXEC (Executable file)
  Machine:                           Advanced Micro Devices X86-64
  Version:                           0x1
  Entry point address:               0x400440
  Start of program headers:          64 (bytes into file)
  Start of section headers:          4496 (bytes into file)
  Flags:                             0x0
  Size of this header:               64 (bytes)
  Size of program headers:           56 (bytes)
  Number of program headers:         9
  Size of section headers:           64 (bytes)
  Number of section headers:         29
  Section header string table index: 28

方法二:

$ gdb a.out 
>>> info files
Symbols from "/home/me/a.out".
Local exec file:
	`/home/me/a.out', file type elf64-x86-64.
	Entry point: 0x400440
	0x0000000000400238 - 0x0000000000400254 is .interp
	0x0000000000400254 - 0x0000000000400274 is .note.ABI-tag
	0x0000000000400274 - 0x0000000000400298 is .note.gnu.build-id
	0x0000000000400298 - 0x00000000004002b4 is .gnu.hash
	0x00000000004002b8 - 0x0000000000400318 is .dynsym
	0x0000000000400318 - 0x0000000000400355 is .dynstr
	0x0000000000400356 - 0x000000000040035e is .gnu.version
	0x0000000000400360 - 0x0000000000400380 is .gnu.version_r
	0x0000000000400380 - 0x0000000000400398 is .rela.dyn
	0x0000000000400398 - 0x00000000004003e0 is .rela.plt
	0x00000000004003e0 - 0x00000000004003fa is .init
	0x0000000000400400 - 0x0000000000400440 is .plt
	0x0000000000400440 - 0x00000000004005c2 is .text
	0x00000000004005c4 - 0x00000000004005cd is .fini
	0x00000000004005d0 - 0x00000000004005e0 is .rodata
	0x00000000004005e0 - 0x0000000000400614 is .eh_frame_hdr
	0x0000000000400618 - 0x000000000040070c is .eh_frame
	0x0000000000600e10 - 0x0000000000600e18 is .init_array
	0x0000000000600e18 - 0x0000000000600e20 is .fini_array
	0x0000000000600e20 - 0x0000000000600e28 is .jcr
	0x0000000000600e28 - 0x0000000000600ff8 is .dynamic
	0x0000000000600ff8 - 0x0000000000601000 is .got
	0x0000000000601000 - 0x0000000000601030 is .got.plt
	0x0000000000601030 - 0x0000000000601040 is .data
	0x0000000000601040 - 0x0000000000601048 is .bss

當調試沒有調試信息的程序時,直接運行start命令是沒有效果的:

(gdb) start
Function "main" not defined.

如果不知道main在何處,那麼可以在程序入口處打斷點。先通過readelf或者進入gdb,執行info files獲得入口地址,然後:

(gdb) b *0x400440
(gdb) r

(2)保存已經設置的斷點

在gdb中,可以使用如下命令將設置的斷點保存下來:

(gdb) save breakpoints file-name-to-save

下此調試時,可以使用如下命令批量設置保存的斷點:

(gdb) source file-name-to-save
(gdb) info breakpoints 
Num     Type           Disp Enb Address            What
1       breakpoint     keep y   0x00000000005a7af0 in gdb_main at /home/xmj/project/binutils-trunk/gdb/main.c:1061
2       breakpoint     keep y   0x00000000005a6bd0 in captured_main at /home/xmj/project/binutils-trunk/gdb/main.c:310
3       breakpoint     keep y   0x00000000005a68b0 in captured_command_loop at /home/xmj/project/binutils-trunk/gdb/main.c:

(3)設置臨時斷點tbreak命令

#include <stdio.h>
#include <pthread.h>

typedef struct
{
        int a;
        int b;
        int c;
        int d;
        pthread_mutex_t mutex;
}ex_st;

int main(void) {
        ex_st st = {1, 2, 3, 4, PTHREAD_MUTEX_INITIALIZER};
        printf("%d,%d,%d,%d\n", st.a, st.b, st.c, st.d);
        return 0;
}

在使用gdb時,如果想讓斷點只生效一次,可以使用“tbreak”命令(縮寫爲:tb)。以上面程序爲例:

(gdb) tb a.c:15
Temporary breakpoint 1 at 0x400500: file a.c, line 15.
(gdb) i b
Num     Type           Disp Enb Address            What
1       breakpoint     del  y   0x0000000000400500 in main at a.c:15
(gdb) r
Starting program: /data2/home/nanxiao/a

Temporary breakpoint 1, main () at a.c:15
15              printf("%d,%d,%d,%d\n", st.a, st.b, st.c, st.d);
(gdb) i b
No breakpoints or watchpoints.

首先在文件的第15行設置臨時斷點,當程序斷住後,用“i b”("info breakpoints"縮寫)命令查看斷點,發現斷點沒有了。也就是斷點命中一次後,就被刪掉了。

四,觀察點

(1)設置觀察點watch命令

#include <stdio.h>
#include <pthread.h>
#include <unistd.h>
int a = 0;

void *thread1_func(void *p_arg)
{
        while (1)
        {
                a++;
                sleep(10);
        }
}

int main(int argc, char* argv[])
{
        pthread_t t1;

        pthread_create(&t1, NULL, thread1_func, "Thread 1");
		
        sleep(1000);
        return 0;
}

gdb可以使用“watch”命令設置觀察點,也就是當一個變量值發生變化時,程序會停下來。以上面程序爲例:

(gdb) start
Temporary breakpoint 1 at 0x4005a8: file a.c, line 19.
Starting program: /data2/home/nanxiao/a
[Thread debugging using libthread_db enabled]
Using host libthread_db library "/lib64/libthread_db.so.1".

Temporary breakpoint 1, main () at a.c:19
19              pthread_create(&t1, NULL, thread1_func, "Thread 1");
(gdb) watch a
Hardware watchpoint 2: a
(gdb) r
Starting program: /data2/home/nanxiao/a
[Thread debugging using libthread_db enabled]
Using host libthread_db library "/lib64/libthread_db.so.1".
[New Thread 0x7ffff782c700 (LWP 8813)]
[Switching to Thread 0x7ffff782c700 (LWP 8813)]
Hardware watchpoint 2: a

Old value = 0
New value = 1
thread1_func (p_arg=0x4006d8) at a.c:11
11                      sleep(10);
(gdb) c
Continuing.
Hardware watchpoint 2: a

Old value = 1
New value = 2
thread1_func (p_arg=0x4006d8) at a.c:11
11                      sleep(10);

可以看到,使用“watch a”命令以後,當a的值變化:由0變成1,由1變成2,程序都會停下來。

查看斷點

列出當前所設置了的所有觀察點:
info watchpoints

watch 所設置的斷點也可以用控制斷點的命令來控制。如 disable、enable、delete等

(2)設置觀察點只針對特定線程生效“watch expr thread threadnum”命令

#include <stdio.h>
#include <pthread.h>

int a = 0;

void *thread1_func(void *p_arg)
{
        while (1)
        {
                a++;
                sleep(10);
        }
}

void *thread2_func(void *p_arg)
{
        while (1)
        {
                a++;
                sleep(10);
        }
}

int main(void)
{
        pthread_t t1, t2;

        pthread_create(&t1, NULL, thread1_func, "Thread 1");
	pthread_create(&t2, NULL, thread2_func, "Thread 2");

        sleep(1000);
        return;
}

gdb可以使用“watch expr thread threadnum”命令設置觀察點只針對特定線程生效,也就是隻有編號爲threadnum的線程改變了變量的值,程序纔會停下來,其它編號線程改變變量的值不會讓程序停住。以上面程序爲例:

(gdb) start
Temporary breakpoint 1 at 0x4005d4: file a.c, line 28.
Starting program: /data2/home/nanxiao/a
[Thread debugging using libthread_db enabled]
Using host libthread_db library "/lib64/libthread_db.so.1".

Temporary breakpoint 1, main () at a.c:28
28              pthread_create(&t1, NULL, thread1_func, "Thread 1");
(gdb) n
[New Thread 0x7ffff782c700 (LWP 25443)]
29              pthread_create(&t2, NULL, thread2_func, "Thread 2");
(gdb)
[New Thread 0x7ffff6e2b700 (LWP 25444)]
31              sleep(1000);
(gdb) i threads
  Id   Target Id         Frame
  3    Thread 0x7ffff6e2b700 (LWP 25444) 0x00007ffff7915911 in clone () from /lib64/libc.so.6
  2    Thread 0x7ffff782c700 (LWP 25443) 0x00007ffff78d9bcd in nanosleep () from /lib64/libc.so.6
* 1    Thread 0x7ffff7fe9700 (LWP 25413) main () at a.c:31
(gdb) wa a thread 2
Hardware watchpoint 2: a
(gdb) c
Continuing.
[Switching to Thread 0x7ffff782c700 (LWP 25443)]
Hardware watchpoint 2: a

Old value = 1
New value = 3
thread1_func (p_arg=0x400718) at a.c:11
11                      sleep(10);
(gdb) c
Continuing.
Hardware watchpoint 2: a

Old value = 3
New value = 5
thread1_func (p_arg=0x400718) at a.c:11
11                      sleep(10);
(gdb) c
Continuing.
Hardware watchpoint 2: a

Old value = 5
New value = 7
thread1_func (p_arg=0x400718) at a.c:11
11                      sleep(10);

可以看到,使用“wa a thread 2”命令(wawatch命令的縮寫)以後,只有thread1_func改變a的值纔會讓程序停下來。
需要注意的是這種針對特定線程設置觀察點方式只對硬件觀察點才生效。

(3)設置讀寫觀察點“awatch”命令

#include <stdio.h>
#include <pthread.h>

int a = 0;

void *thread1_func(void *p_arg)
{
        while (1)
        {
                a++;
                sleep(10);
        }
}

void *thread2_func(void *p_arg)
{
        while (1)
        {
                printf("%d\n", a);;
                sleep(10);
        }
}

int main(void)
{
        pthread_t t1, t2;

        pthread_create(&t1, NULL, thread1_func, "Thread 1");
        pthread_create(&t2, NULL, thread2_func, "Thread 2");

        sleep(1000);
        return;
}

gdb可以使用“awatch”命令設置讀寫觀察點,也就是當發生讀取變量或改變變量值的行爲時,程序就會暫停住。以上面程序爲例:

(gdb) aw a
Hardware access (read/write) watchpoint 1: a
(gdb) r
Starting program: /data2/home/nanxiao/a
[Thread debugging using libthread_db enabled]
Using host libthread_db library "/lib64/libthread_db.so.1".
[New Thread 0x7ffff782c700 (LWP 16938)]
[Switching to Thread 0x7ffff782c700 (LWP 16938)]
Hardware access (read/write) watchpoint 1: a

Value = 0
0x00000000004005c6 in thread1_func (p_arg=0x40076c) at a.c:10
10                      a++;
(gdb) c
Continuing.
Hardware access (read/write) watchpoint 1: a

Old value = 0
New value = 1
thread1_func (p_arg=0x40076c) at a.c:11
11                      sleep(10);
(gdb) c
Continuing.
[New Thread 0x7ffff6e2b700 (LWP 16939)]
[Switching to Thread 0x7ffff6e2b700 (LWP 16939)]
Hardware access (read/write) watchpoint 1: a

Value = 1
0x00000000004005f2 in thread2_func (p_arg=0x400775) at a.c:19
19                      printf("%d\n", a);;
(gdb) c
Continuing.
1
[Switching to Thread 0x7ffff782c700 (LWP 16938)]
Hardware access (read/write) watchpoint 1: a

Value = 1
0x00000000004005c6 in thread1_func (p_arg=0x40076c) at a.c:10
10                      a++;

可以看到,使用“aw a”命令(awawatch命令的縮寫)以後,每次讀取或改變a的值都會讓程序停下來。
需要注意的是awatch命令只對硬件觀察點才生效。

五,打印

(1)打印STL容器中的內容

#include <iostream>
#include <vector>

using namespace std;

int main ()
{
  vector<int> vec(10); // 10 zero-initialized elements

  for (int i = 0; i < vec.size(); i++)
    vec[i] = i;

  cout << "vec contains:";
  for (int i = 0; i < vec.size(); i++)
    cout << ' ' << vec[i];
  cout << '\n';

  return 0;
}

gdb 7.0之後,可以使用gcc提供的python腳本,來改善顯示結果:

(gdb) p vec
$1 = std::vector of length 10, capacity 10 = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9}

(2)打印大數組中的內容

int main()
{
  int array[201];
  int i;

  for (i = 0; i < 201; i++)
    array[i] = i;

  return 0;
}

在gdb中,如果要打印大數組的內容,缺省最多會顯示200個元素:

(gdb) p array
$1 = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 
  48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 
  95, 96, 97, 98, 99, 100, 101, 102, 103, 104, 105, 106, 107, 108, 109, 110, 111, 112, 113, 114, 115, 116, 117, 118, 119, 120, 121, 122, 123, 124, 125, 126, 127, 128, 129, 130, 131, 132, 
  133, 134, 135, 136, 137, 138, 139, 140, 141, 142, 143, 144, 145, 146, 147, 148, 149, 150, 151, 152, 153, 154, 155, 156, 157, 158, 159, 160, 161, 162, 163, 164, 165, 166, 167, 168, 169, 
  170, 171, 172, 173, 174, 175, 176, 177, 178, 179, 180, 181, 182, 183, 184, 185, 186, 187, 188, 189, 190, 191, 192, 193, 194, 195, 196, 197, 198, 199...}

可以使用如下命令,設置這個最大限制數:

(gdb) set print elements number-of-elements

也可以使用如下命令,設置爲沒有限制:

(gdb) set print elements 0

(gdb) set print elements unlimited
(gdb) p array
$2 = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 
  48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 
  95, 96, 97, 98, 99, 100, 101, 102, 103, 104, 105, 106, 107, 108, 109, 110, 111, 112, 113, 114, 115, 116, 117, 118, 119, 120, 121, 122, 123, 124, 125, 126, 127, 128, 129, 130, 131, 132, 
  133, 134, 135, 136, 137, 138, 139, 140, 141, 142, 143, 144, 145, 146, 147, 148, 149, 150, 151, 152, 153, 154, 155, 156, 157, 158, 159, 160, 161, 162, 163, 164, 165, 166, 167, 168, 169, 
  170, 171, 172, 173, 174, 175, 176, 177, 178, 179, 180, 181, 182, 183, 184, 185, 186, 187, 188, 189, 190, 191, 19

在gdb中,如果要打印數組中任意連續元素的值,可以使用“p array[index]@num”命令(pprint命令的縮寫)。其中index是數組索引(從0開始計數),num是連續多少個元素。

(3)打印函數局部變量的值“bt full”命令

#include <stdio.h>

void fun_a(void)
{
	int a = 0;
	printf("%d\n", a);
}

void fun_b(void)
{
	int b = 1;
	fun_a();
	printf("%d\n", b);
}

void fun_c(void)
{
	int c = 2;
	fun_b();
	printf("%d\n", c);
}

void fun_d(void)
{
	int d = 3;
	fun_c();
	printf("%d\n", d);
}

int main(void)
{
	int var = -1;
	fun_d();
	return 0;
}

如果要打印函數局部變量的值,可以使用“bt full”命令(bt是backtrace的縮寫)。首先我們在函數fun_a裏打上斷點,當程序斷住時,顯示調用棧信息:

(gdb) bt
#0  fun_a () at a.c:6
#1  0x000109b0 in fun_b () at a.c:12
#2  0x000109e4 in fun_c () at a.c:19
#3  0x00010a18 in fun_d () at a.c:26
#4  0x00010a4c in main () at a.c:33

接下來,用“bt full”命令顯示各個函數的局部變量值:

(gdb) bt full
#0  fun_a () at a.c:6
        a = 0
#1  0x000109b0 in fun_b () at a.c:12
        b = 1
#2  0x000109e4 in fun_c () at a.c:19
        c = 2
#3  0x00010a18 in fun_d () at a.c:26
        d = 3
#4  0x00010a4c in main () at a.c:33
        var = -1

也可以使用如下“bt full n”,意思是從內向外顯示n個棧楨,及其局部變量,例如:

(gdb) bt full 2
#0  fun_a () at a.c:6
        a = 0
#1  0x000109b0 in fun_b () at a.c:12
        b = 1
(More stack frames follow...)

而“bt full -n”,意思是從外向內顯示n個棧楨,及其局部變量,例如:

(gdb) bt full -2
#3  0x00010a18 in fun_d () at a.c:26
        d = 3
#4  0x00010a4c in main () at a.c:33
        var = -1

如果只是想打印當前函數局部變量的值,可以使用如下命令:

(gdb) info locals
a = 0

(4)打印變量的類型和所在文件whatis和ptype

#include <stdio.h>

struct child {
  char name[10];
  enum { boy, girl } gender;
};

struct child he = { "Tom", boy };

int main (void)
{
  static struct child she = { "Jerry", girl };
  printf ("Hello %s %s.\n", he.gender == boy ? "boy" : "girl", he.name);
  printf ("Hello %s %s.\n", she.gender == boy ? "boy" : "girl", she.name);
  return 0;
}

在gdb中,可以使用如下命令查看變量的類型:

(gdb) whatis he
type = struct child

如果想查看詳細的類型信息:

(gdb) ptype he
type = struct child {
    char name[10];
    enum {boy, girl} gender;
}

(5)打印內存的值“x”命令

#include <stdio.h>

int main(void)
{
        int i = 0;
        char a[100];

        for (i = 0; i < sizeof(a); i++)
        {
                a[i] = i;
        }

        return 0;
}

gdb中使用“x”命令來打印內存的值,格式爲“x /nfu addr”。含義爲以f格式打印從addr開始的n個長度單元爲u的內存值。參數具體含義如下:

a)n:輸出單元的個數。
b)f:是輸出格式。比如x是以16進制形式輸出,o是以8進制形式輸出,等等。
c)u:標明一個單元的長度。b是一個byteh是兩個byte(halfword),w是四個byte(word),g是八個byte(giant word)。

以上面程序爲例:
(1) 以16進制格式打印數組前a16個byte的值:

(gdb) x/16xb a
0x7fffffffe4a0: 0x00    0x01    0x02    0x03    0x04    0x05    0x06    0x07
0x7fffffffe4a8: 0x08    0x09    0x0a    0x0b    0x0c    0x0d    0x0e    0x0f

(2) 以無符號10進制格式打印數組a前16個byte的值:

(gdb) x/16ub a
0x7fffffffe4a0: 0       1       2       3       4       5       6       7
0x7fffffffe4a8: 8       9       10      11      12      13      14      15

(3) 以2進制格式打印數組前16個abyte的值:

(gdb) x/16tb a
0x7fffffffe4a0: 00000000        00000001        00000010        00000011        00000100        00000101        00000110        00000111
0x7fffffffe4a8: 00001000        00001001        00001010        00001011        00001100        00001101        00001110        00001111

(4) 以16進制格式打印數組a前16個word(4個byte)的值:

(gdb) x/16xw a
0x7fffffffe4a0: 0x03020100      0x07060504      0x0b0a0908      0x0f0e0d0c
0x7fffffffe4b0: 0x13121110      0x17161514      0x1b1a1918      0x1f1e1d1c
0x7fffffffe4c0: 0x23222120      0x27262524      0x2b2a2928      0x2f2e2d2c
0x7fffffffe4d0: 0x33323130      0x37363534      0x3b3a3938      0x3f3e3d3c
用gdb查看內存格式: 
    x /nfu ptr

說明
x 是 examine 的縮寫
n表示要顯示的內存單元的個數

f表示顯示方式, 可取如下值
x 按十六進制格式顯示變量。
d 按十進制格式顯示變量。
u 按十進制格式顯示無符號整型。
o 按八進制格式顯示變量。
t 按二進制格式顯示變量。
a 按十六進制格式顯示變量。
i 指令地址格式
c 按字符格式顯示變量。
f 按浮點數格式顯示變量。

u表示一個地址單元的長度
b表示單字節,
h表示雙字節,
w表示四字節,
g表示八字節

Format letters are o(octal), x(hex), d(decimal), u(unsigned decimal),
t(binary), f(float), a(address), i(instruction), c(char) and s(string).
Size letters are b(byte), h(halfword), w(word), g(giant, 8 bytes)

ptr 表示從那個地址開始

所以查看十進制地址的方法即爲:
(gdb) x /9db 0x00001fa4
從內存地址0x00001fa4讀取內容,b表示以單字節爲一個單位,9表示九個單位,d表示按十進制顯示。

(6)每行打印一個結構體成員“set print pretty on”命令

#include <stdio.h>
#include <pthread.h>

typedef struct
{
        int a;
        int b;
        int c;
        int d;
        pthread_mutex_t mutex;
}ex_st;

int main(void) {
        ex_st st = {1, 2, 3, 4, PTHREAD_MUTEX_INITIALIZER};
        printf("%d,%d,%d,%d\n", st.a, st.b, st.c, st.d);
        return 0;
}

默認情況下,gdb以一種“緊湊”的方式打印結構體。以上面代碼爲例:

(gdb) n
15              printf("%d,%d,%d,%d\n", st.a, st.b, st.c, st.d);
(gdb) p st
$1 = {a = 1, b = 2, c = 3, d = 4, mutex = {__data = {__lock = 0, __count = 0, __owner = 0, __nusers = 0, __kind = 0,
      __spins = 0, __list = {__prev = 0x0, __next = 0x0}}, __size = '\000' <repeats 39 times>, __align = 0}}

可以看到結構體的顯示很混亂,尤其是結構體裏還嵌套着其它結構體時。

可以執行“set print pretty on”命令,這樣每行只會顯示結構體的一名成員,而且還會根據成員的定義層次進行縮進:

(gdb) set print pretty on
(gdb) p st
$2 = {
  a = 1,
  b = 2,
  c = 3,
  d = 4,
  mutex = {
    __data = {
      __lock = 0,
      __count = 0,
      __owner = 0,
      __nusers = 0,
      __kind = 0,
      __spins = 0,
      __list = {
        __prev = 0x0,
        __next = 0x0
      }
    },
    __size = '\000' <repeats 39 times>,
    __align = 0
  }
}

六.多進程和多線程

(1)調試子進程“set follow-fork-mode child”

#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>

int main(void) {
	pid_t pid;

	pid = fork();
	if (pid < 0)
	{
		exit(1);
	}
	else if (pid > 0)
	{
		exit(0);
	}
	printf("hello world\n");
	return 0;
}

在調試多進程程序時,gdb默認會追蹤父進程。例如:

(gdb) start
Temporary breakpoint 1 at 0x40055c: file a.c, line 8.
Starting program: /data2/home/nanxiao/a

Temporary breakpoint 1, main () at a.c:8
8               pid = fork();
(gdb) n
9               if (pid < 0)
(gdb) hello world

13              else if (pid > 0)
(gdb)
15                      exit(0);
(gdb)
[Inferior 1 (process 12786) exited normally]

可以看到程序執行到第15行:父進程退出。

如果要調試子進程,要使用如下命令:“set follow-fork-mode child”,例如:

(gdb) set follow-fork-mode child
(gdb) start
Temporary breakpoint 1 at 0x40055c: file a.c, line 8.
Starting program: /data2/home/nanxiao/a

Temporary breakpoint 1, main () at a.c:8
8               pid = fork();
(gdb) n
[New process 12241]
[Switching to process 12241]
9               if (pid < 0)
(gdb)
13              else if (pid > 0)
(gdb)
17              printf("hello world\n");
(gdb)
hello world
18              return 0;

可以看到程序執行到第17行:子進程打印“hello world”。

(2)同時調試父進程和子進程“set detach-on-fork off

#include <stdio.h>
#include <stdlib.h>

int main(void) {
    pid_t pid;

    pid = fork();
    if (pid < 0)
    {
        exit(1);
    }
    else if (pid > 0)
    {
        printf("Parent\n");
        exit(0);
    }
    printf("Child\n");
    return 0;
}

在調試多進程程序時,gdb默認只會追蹤父進程的運行,而子進程會獨立運行,gdb不會控制。以上面程序爲例:

(gdb) start
Temporary breakpoint 1 at 0x40055c: file a.c, line 7.
Starting program: /data2/home/nanxiao/a

Temporary breakpoint 1, main () at a.c:7
7           pid = fork();
(gdb) n
8           if (pid < 0)
(gdb) Child

12          else if (pid > 0)
(gdb)
14              printf("Parent\n");
(gdb)
Parent
15              exit(0);

可以看到當單步執行到第8行時,程序打印出“Child” ,證明子進程已經開始獨立運行。

如果要同時調試父進程和子進程,可以使用“set detach-on-fork off”(默認detach-on-forkon)命令,這樣gdb就能同時調試父子進程,並且在調試一個進程時,另外一個進程處於掛起狀態。仍以上面程序爲例:

(gdb) set detach-on-fork off
(gdb) start
Temporary breakpoint 1 at 0x40055c: file a.c, line 7.
Starting program: /data2/home/nanxiao/a

Temporary breakpoint 1, main () at a.c:7
7           pid = fork();
(gdb) n
[New process 1050]
8           if (pid < 0)
(gdb)
12          else if (pid > 0)
(gdb) i inferior
  Num  Description       Executable
  2    process 1050      /data2/home/nanxiao/a
* 1    process 1046      /data2/home/nanxiao/a
(gdb) n
14              printf("Parent\n");
(gdb) n
Parent
15              exit(0);
(gdb)
[Inferior 1 (process 1046) exited normally]
(gdb)
The program is not being run.
(gdb) i inferiors
  Num  Description       Executable
  2    process 1050      /data2/home/nanxiao/a
* 1    <null>            /data2/home/nanxiao/a
(gdb) inferior 2
[Switching to inferior 2 [process 1050] (/data2/home/nanxiao/a)]
[Switching to thread 2 (process 1050)]
#0  0x00007ffff7af6cad in fork () from /lib64/libc.so.6
(gdb) bt
#0  0x00007ffff7af6cad in fork () from /lib64/libc.so.6
#1  0x0000000000400561 in main () at a.c:7
(gdb) n
Single stepping until exit from function fork,
which has no line number information.
main () at a.c:8
8           if (pid < 0)
(gdb)
12          else if (pid > 0)
(gdb)
17          printf("Child\n");
(gdb)
Child
18          return 0;
(gdb)

在使用“set detach-on-fork off”命令後,用“i inferiors”(iinfo命令縮寫)查看進程狀態,可以看到父子進程都在被gdb調試的狀態,前面顯示“*”是正在調試的進程。當父進程退出後,用“inferior infno”切換到子進程去調試。

此外,如果想讓父子進程都同時運行,可以使用“set schedule-multiple on”(默認schedule-multipleoff)命令,仍以上述代碼爲例:

(gdb) set detach-on-fork off
(gdb) set schedule-multiple on
(gdb) start
Temporary breakpoint 1 at 0x40059c: file a.c, line 7.
Starting program: /data2/home/nanxiao/a

Temporary breakpoint 1, main () at a.c:7
7           pid = fork();
(gdb) n
[New process 26597]
Child

可以看到打印出了“Child”,證明子進程也在運行了。

(3)只允許一個線程運行“set scheduler-locking on”命令

#include <stdio.h>
#include <pthread.h>
int a = 0;
int b = 0;
void *thread1_func(void *p_arg)
{
        while (1)
        {
                a++;
                sleep(1);
        }
}

void *thread2_func(void *p_arg)
{
        while (1)
        {
                b++;
                sleep(1);
        }
}

int main(void)
{
        pthread_t t1, t2;

        pthread_create(&t1, NULL, thread1_func, "Thread 1");
        pthread_create(&t2, NULL, thread2_func, "Thread 2");

        sleep(1000);
        return;
}

用gdb調試多線程程序時,一旦程序斷住,所有的線程都處於暫停狀態。此時當你調試其中一個線程時(比如執行“step”,“next”命令),所有的線程都會同時執行。以上面程序爲例:

(gdb) b a.c:9
Breakpoint 1 at 0x400580: file a.c, line 9.
(gdb) r
Starting program: /data2/home/nanxiao/a
[Thread debugging using libthread_db enabled]
Using host libthread_db library "/lib64/libthread_db.so.1".
[New Thread 0x7ffff782c700 (LWP 17368)]
[Switching to Thread 0x7ffff782c700 (LWP 17368)]

Breakpoint 1, thread1_func (p_arg=0x400718) at a.c:9
9                       a++;
(gdb) p b
$1 = 0
(gdb) s
10                      sleep(1);
(gdb) s
[New Thread 0x7ffff6e2b700 (LWP 17369)]
11              }
(gdb)

Breakpoint 1, thread1_func (p_arg=0x400718) at a.c:9
9                       a++;
(gdb)
10                      sleep(1);
(gdb) p b
$2 = 3

thread1_func更新全局變量a的值,thread2_func更新全局變量b的值。我在thread1_funca++語句打上斷點,當斷點第一次命中時,打印b的值是0,在單步調試thread1_func幾次後,b的值變成3,證明在單步調試thread1_func時,thread2_func也在執行。
如果想在調試一個線程時,讓其它線程暫停執行,可以使用“set scheduler-locking on”命令:

(gdb) b a.c:9
Breakpoint 1 at 0x400580: file a.c, line 9.
(gdb) r
Starting program: /data2/home/nanxiao/a
[Thread debugging using libthread_db enabled]
Using host libthread_db library "/lib64/libthread_db.so.1".
[New Thread 0x7ffff782c700 (LWP 19783)]
[Switching to Thread 0x7ffff782c700 (LWP 19783)]

Breakpoint 1, thread1_func (p_arg=0x400718) at a.c:9
9                       a++;
(gdb) set scheduler-locking on
(gdb) p b
$1 = 0
(gdb) s
10                      sleep(1);
(gdb)
11              }
(gdb)

Breakpoint 1, thread1_func (p_arg=0x400718) at a.c:9
9                       a++;
(gdb)
10                      sleep(1);
(gdb)
11              }
(gdb) p b
$2 = 0

可以看到在單步調試thread1_func幾次後,b的值仍然爲0,證明在在單步調試thread1_func時,thread2_func沒有執行。

此外,“set scheduler-locking”命令除了支持offon模式外(默認是off),還有一個step模式。含義是:當用"step"命令調試線程時,其它線程不會執行,但是用其它命令(比如"next")調試線程時,其它線程也許會執行。

(4)一個gdb會話中同時調試多個程序

a.c:
#include <stdio.h>
int func(int a, int b)
{
        int c = a * b;
        printf("c is %d\n", c);
}

int main(void)
{
        func(1, 2);
        return 0;
}


b.c:
#include <stdio.h>

int func1(int a)
{
        return 2 * a;
}

int func2(int a)
{
        int c = 0;
        c = 2 * func1(a);
        return c;
}

int func3(int a)
{
        int c = 0;
        c = 2 * func2(a);
        return c;
}

int main(void)
{
        printf("%d\n", func3(10));
        return 0;
}

gdb支持在一個會話中同時調試多個程序。以上面程序爲例,首先調試a程序:

root@bash:~$ gdb a
GNU gdb (Ubuntu 7.7-0ubuntu3) 7.7
Copyright (C) 2014 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"...
Reading symbols from a...done.
(gdb) start
Temporary breakpoint 1 at 0x400568: file a.c, line 10.
Starting program: /home/nanxiao/a

接着使用“add-inferior [ -copies n ] [ -exec executable ]”命令加載可執行文件b。其中n默認爲1:

(gdb) add-inferior -copies 2 -exec b
Added inferior 2
Reading symbols from b...done.
Added inferior 3
Reading symbols from b...done.
(gdb) i inferiors
  Num  Description       Executable
  3    <null>            /home/nanxiao/b
  2    <null>            /home/nanxiao/b
* 1    process 1586      /home/nanxiao/a
(gdb) inferior 2
[Switching to inferior 2 [<null>] (/home/nanxiao/b)]
(gdb) start
Temporary breakpoint 2 at 0x400568: main. (3 locations)
Starting program: /home/nanxiao/b

Temporary breakpoint 2, main () at b.c:24
24              printf("%d\n", func3(10));
(gdb) i inferiors
  Num  Description       Executable
  3    <null>            /home/nanxiao/b
* 2    process 1590      /home/nanxiao/b
  1    process 1586      /home/nanxiao/a

可以看到可以調試b程序了。

另外也可用“clone-inferior [ -copies n ] [ infno ]”克隆現有的inferior,其中n默認爲1,infno默認爲當前的inferior

(gdb) i inferiors
  Num  Description       Executable
  3    <null>            /home/nanxiao/b
* 2    process 1590      /home/nanxiao/b
  1    process 1586      /home/nanxiao/a
(gdb) clone-inferior -copies 1
Added inferior 4.
(gdb) i inferiors
  Num  Description       Executable
  4    <null>            /home/nanxiao/b
  3    <null>            /home/nanxiao/b
* 2    process 1590      /home/nanxiao/b
  1    process 1586      /home/nanxiao/a

可以看到又多了一個b程序。

(5)打印程序進程空間信息“maint info program-spaces

a.c:
#include <stdio.h>
int func(int a, int b)
{
        int c = a * b;
        printf("c is %d\n", c);
}

int main(void)
{
        func(1, 2);
        return 0;
}


b.c:
#include <stdio.h>

int func1(int a)
{
        return 2 * a;
}

int func2(int a)
{
        int c = 0;
        c = 2 * func1(a);
        return c;
}

int func3(int a)
{
        int c = 0;
        c = 2 * func2(a);
        return c;
}

int main(void)
{
        printf("%d\n", func3(10));
        return 0;
}

使用gdb調試多個進程時,可以使用“maint info program-spaces”打印當前所有被調試的進程信息。以上面程序爲例:

[root@localhost nan]# gdb a
GNU gdb (GDB) 7.8.1
......
Reading symbols from a...done.
(gdb) start
Temporary breakpoint 1 at 0x4004f9: file a.c, line 10.
Starting program: /home/nan/a 

Temporary breakpoint 1, main () at a.c:10
10              func(1, 2);
(gdb) add-inferior -exec b
Added inferior 2
Reading symbols from b...done.
(gdb) i inferiors b
Args must be numbers or '$' variables.
(gdb) i inferiors
  Num  Description       Executable        
  2    <null>            /home/nan/b       
* 1    process 15753     /home/nan/a       
(gdb) inferior 2
[Switching to inferior 2 [<null>] (/home/nan/b)]
(gdb) start
Temporary breakpoint 2 at 0x4004f9: main. (2 locations)
Starting program: /home/nan/b 

Temporary breakpoint 2, main () at b.c:24
24              printf("%d\n", func3(10));
(gdb) i inferiors
  Num  Description       Executable        
* 2    process 15902     /home/nan/b       
  1    process 15753     /home/nan/a       
(gdb) clone-inferior -copies 2
Added inferior 3.
Added inferior 4.
(gdb) i inferiors
  Num  Description       Executable        
  4    <null>            /home/nan/b       
  3    <null>            /home/nan/b       
* 2    process 15902     /home/nan/b       
  1    process 15753     /home/nan/a       
(gdb) maint info program-spaces
  Id   Executable        
  4    /home/nan/b       
        Bound inferiors: ID 4 (process 0)
  3    /home/nan/b       
        Bound inferiors: ID 3 (process 0)
* 2    /home/nan/b       
        Bound inferiors: ID 2 (process 15902)
  1    /home/nan/a       
        Bound inferiors: ID 1 (process 15753)

可以看到執行“maint info program-spaces”命令後,打印出當前有4個program-spaces(編號從1到4)。另外還有每個program-spaces對應的程序,inferior編號及進程號。

(6)gdb跟蹤父子進程

  • 使用gdb調試的時候,gdb只能跟蹤一個進程。可以在fork函數調用之前,通過指令設置gdb調試工具跟蹤父進程或者是跟蹤子進程。默認跟蹤父進程。
  • set follow-fork-mode child 命令設置gdb在fork之後跟蹤子進程。
  • set follow-fork-mode parent 設置跟蹤父進程。
  • 注意:一定要在fork函數調用之前設置纔有效。

七, 編輯源碼

啓動調試後,不想退出程序而編輯源碼,如何做呢?

gdb 模式下用的默認編輯器是 /bin/ex ,如果沒有或者想換成其他編輯器,如VIM,可以這樣:

export EDITOR=/usr/bin/vim

gdb 模式下編輯源碼:

(gdb)edit 3  # 編輯第三行
(gdb)edit func # 編輯func函數
(gdb)edit test.c:5 #編輯test.c第五行

完了之後,重新編譯程序( 注意一定要帶上 shell 命令,表明是shell命令 ):

(gdb)shell gcc -g -o main main.c test.c

或者這樣:

啓動是帶上 tui(Text User Interface),可以在多個窗口調試:

gdb main -tui

八,帶參數調試

1. 啓動的時候帶上參數

gdb --args xxx 參數

2. 啓動之後 run 帶上參數

# gdb xxx
(gdb)run 參數

3. 啓動之後 set args 設置參數

# gdb xxx
(gdb) set args 參數

九,coredump調試

coredump 調試依賴於 core 文件,core 文件是程序非法執行後 core dump 後產生的文件。這是 Linux 系統的一種保護機制,當出現某些連開發和測試費了九牛二虎之力都沒能發現的問題時,Linux 系統還提供了最後一道屏障,通過 core 文件就可以讓這些問題原形畢露。

1 開啓 core dump

要想讓程序崩潰時產生 core 文件,需要開啓,輸入 ulimit -c,如果輸出爲 0,表示默認關閉 core dump。

有兩種方式可以開啓,一種就是通過 ulimit 命令,一種是在程序中寫代碼開啓,這裏只講第一種,第二種參考文末的引用1。

ulimit -c unlimied  # 表示不限制core文件大小
ulimit -c 10        # 設置最大大小,單位爲塊,一塊默認爲512字節

上面是臨時開啓,永久開啓要修改 /etc/security/limits.conf 文件,增加一行:

# /etc/security/limits.conf
# <domain>        <type>  <item>  <value>
    *               soft    core    unlimited

這樣就可以生成 core 文件,文件名就是 core,並且默認在當前程序所在目錄下生成,如果要指定目錄,則可以 echo "/tmp/corefile-%e-%p-%t" > /proc/sys/kernel/core_pattern 設置 core 文件保存在目錄 "/tmp/corefile" 下,文件名格式爲 “core-命令名-pid-時間戳”

還可以通過 echo 1 > /proc/sys/kernel/core_uses_pid 使得生成的 core 文件變成 core.pid,pid 是該進程的 pid。

2 調試 core dump

使用

gdb <program> core文件名

或者 gdb 啓動後,使用

  • -core <file>

  • -c <file>

來調試 core 文件

下面是一個例子:

#include <stdio.h>
int func(int *p)
{
    int y = *p;
    return y;
}
int main()
{
    int *p = NULL;
    return func(p);
}

編譯:gdb -g -o core_dump core_dump.c,用 gdb 查看 core 文件

root@root:~$ gcc core_demo.c -o core_demo -g
root@root:~$ ./core_demo
Segmentation fault (core dumped)

root@root:~$ gdb core_demo core_demo.core.24816
...
Core was generated by './core_demo'.
Program terminated with signal 11, Segmentation fault.
#0  0x080483cd in func (p=0x0) at core_demo.c:5
5       int y = *p;
(gdb)  where
#0  0x080483cd in func (p=0x0) at core_demo.c:5
#1  0x080483ef in main () at core_demo.c:12
(gdb) info frame
Stack level 0, frame at 0xffd590a4:
 eip = 0x80483cd in func (core_demo.c:5); saved eip 0x80483ef
 called by frame at 0xffd590c0
 source language c.
 Arglist at 0xffd5909c, args: p=0x0
 Locals at 0xffd5909c, Previous frame's sp is 0xffd590a4
 Saved registers:
  ebp at 0xffd5909c, eip at 0xffd590a0
(gdb)

可以看到,我們可以還原 core_demo 執行時的場景,並使用 where 查看當前程序調用函數棧幀,還可以使用 gdb 中的命令查看寄存器,變量等信息。

七、其他

(1)進入函數調試 s,退出函數 finish,結束當前循環 until

(2)調用和執行一個函數: call function(args)和print function(args)

(3)調試已經運行的進程gdb -p processID

(4)pstree命令,用於查看進程樹之間的關係,即哪個進程是父進程,哪個是子進程,可以清楚的看出來是誰創建了誰

(5)p array@idx 可以查看數組 array 中 idx 處的值

(6)設置display,比如 display a 這樣以後每次調試都會輸出a變量的值

(7)p x=8 在調試過程中修改變量x的值,下面生效

(8)jump 實現跳轉,可以是文件的行號,也可以是file:line,也可以是+num這種格式 jump address是代碼行的內存地址

(9)until +行號 運行至某行停住,不僅僅跳出循環

(10)skip 在 step 時跳過一些不想關注的函數或者某個文件的代碼,如 skip function add 表示跳過函數 add,skip file step.c 跳過文件 step.c,info skip 查看跳過的信息。

(11)ptype 輸出結構體類型

(12)p/x c 按十六進制打印內容(x:十六進制,d:十進制,o:八進制,t:二進制,c:字符格式,f:浮點格式)

 

 

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