一,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
”命令可以知道0x7fffffffe568
是func2
的函數堆棧幀地址,使用“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
”命令(wa
是watch
命令的縮寫)以後,只有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
”命令(aw
是awatch
命令的縮寫)以後,每次讀取或改變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
”命令(p
是print
命令的縮寫)。其中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
是一個byte
,h
是兩個byte
(halfword),w
是四個byte
(word),g
是八個byte
(giant word)。
以上面程序爲例:
(1) 以16進制格式打印數組前a
16個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個a
byte的值:
(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-fork
是on
)命令,這樣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
”(i
是info
命令縮寫)查看進程狀態,可以看到父子進程都在被gdb調試的狀態,前面顯示“*”是正在調試的進程。當父進程退出後,用“inferior infno
”切換到子進程去調試。
此外,如果想讓父子進程都同時運行,可以使用“set schedule-multiple on
”(默認schedule-multiple
是off
)命令,仍以上述代碼爲例:
(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_func
裏a++
語句打上斷點,當斷點第一次命中時,打印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
”命令除了支持off
和on
模式外(默認是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:浮點格式)