1
2.FAQ
hello gdb!
首先准備好我們的調試文件test.cpp 如下
int main(0
{
int i=0;
i=10;
std::cout<<"hello,gdb"<<i<<std::endl;
return 0;
}
編譯之。。
或者用
為簡單起見 以后通用g++ ,這是GNU默認的c++編譯器。
參數解釋:
-g 產生相應的調試信息,
-o 編譯產生后的文件名 在這里也就test准備開始調試 執行gdb載入要調試的程序(注意,這里並沒開始執行test,只是載入)
這個時候。你會看到命令行如下輸出
GNU gdb 6.3.50-20050815 (Apple version gdb-1820) (Sat Jun 16 02:40:11 UTC 2012)
Copyright 2004 Free Software Foundation, Inc.
GDB is free software, covered by the GNU General Public License, and you are
welcome to change it and/or distribute copies of it under certain conditions.
Type "show copying" to see the conditions.
There is absolutely no warranty for GDB. Type "show warranty" for details.
This GDB was configured as "x86_64-apple-darwin"...Reading symbols for shared libraries ... done
(gdb)
此時,讓我們開始在調試環境下執行test,輸入
你將看到輸出
Starting program: /Users/Desktop/Debugging/test
Reading symbols for shared libraries ++......................... done
hello,gdb10
Program exited normally.
好了,以上的步驟,就相當於你在xcode里用debug模式運行程序。
讓程序在運行中停下來
在gdb中,有3種方式讓程序停下來。
1.breakpoint 這種形式是我們最常用的,反映在xcode里就是在行前雙擊。產生一個藍色的標簽。
2.watchpoint 這種用的也多,如果你要監測一段內存是否發生變化。或者說一個變量是否發生變化,可以用它。
3.catchpoint 這種形式是當特點形式的事件發生時,會停止程序運行。比如說,系統拋異常了。這個在xcode里也比較常用 ,捕獲異常時非常方便,能讓開發人員很快的定位到代碼中拋異常的位置。
這3種point 在GNU中統稱breakpoint--斷點GNU 參考 。比較囧 ,當然。重點說1.breakpoint 的應用。
有以下用法
break 3 //在當前調試的文件中的第3行停止程序
break main //在當前調試的文件中的symbol table中找main這樣的名字。如果找到。則停止程序。
break test.cpp:3 // 在test.cpp文件中的第3行停止程序
break test.cpp:main //在test.cpp文件的symbol table中找main這樣的名字。如果找到。則停止程序。
你可以在命令行里執行以上所有
Breakpoint 1 at 0x100000d34: file test.cpp, line 3.
(gdb) b main
Note: breakpoint 1 also set at pc 0x100000d34.
Breakpoint 2 at 0x100000d34: file test.cpp, line 4.
(gdb) b test.cpp:3
Note: breakpoints 1 and 2 also set at pc 0x100000d34.
Breakpoint 3 at 0x100000d34: file test.cpp, line 4.
(gdb) b test.cpp:main
Note: breakpoints 1, 2 and 3 also set at pc 0x100000d34.
Breakpoint 4 at 0x100000d34: file test.cpp, line 4.
查看一下斷點的情況 執行
這命令有點長,你可以用縮寫 i b
關於縮寫,gdb遵循一個這樣的原則。只要縮寫不會產生歧義。任何縮寫都可以
比如說 info break
我打以下命令都可以
i bre
i break
i bre
執行i b 后,顯示如下
Num Type Disp Enb Address What
1 breakpoint keep y 0x0000000100000d34 in main at test.cpp:3
2 breakpoint keep y 0x0000000100000d34 in main at test.cpp:4
3 breakpoint keep y 0x0000000100000d34 in main at test.cpp:4
4 breakpoint keep y 0x0000000100000d34 in main at test.
以上各行含義解釋如下
Num
這是一個斷點的標示id,以后有需要索引這個斷點的地方全靠它。
Type
如我們前面所說。這是3個斷點中的一種
Disp
縮寫(Dispensable),代表的意思就說能不能被自動刪掉。有一處斷點叫臨時斷點。就是中斷一次后。斷點就被刪除了,下次再經過時,不會中斷 tbreak 3 (在第3行設置臨時斷點) 這里顯示的是keep ,代表永久斷點。
Enb
縮寫(Enable) 在xcode中。你可以暫時性的不激活斷點,但是斷點還存在,y代表激活。n代表不激活。
Address
這個代表的是斷點在內存中設置的具體位置。一般匯編人員用。。普通青年用不上。
What
這個是對斷點的一些信息描述。
如上所示。我們在第3行打一個斷點。以某種方式在第4行打了3個斷點。重復的斷點只會停一次。
現在,讓我再次以調試模式運行test 。輸入
顯示
/Users/cubase01/Desktop/Debugging/Chapter_01/insert_sort/pg_019/test
Breakpoint 1, main () at test.cpp:4
4 int i=0;
程序停下來了。。此時程序並沒有執行第4行,是將要執行這一行,切記。
我想。你有以下這幾個疑問,FAQ時間到。
FAQ:
1.不是說在第3行有斷點嗎?怎么先到了第4行?
在xcode里調試。開發人員會有一種錯覺。就是認為源文件與調試的行是一一對應的。實際上不是。g++ 在編譯時通過 -g命令生成一個叫symbol table的東東,翻成中文叫符號表。作用就是將二進制代碼與源文件對應起來。 將斷點斷在main上。gdb認為這對調試沒有任何作用。所以會自動優化到int i=0 。這種優化在-g命令時還不是很明顯。當你開啟g++ -O9時,g++會強力優化代碼 。比如說以下代碼
i=1;
i=2;
i=3;
用g++ -O9編譯后。會被優化成int i=3;如果你用優化后的代碼去調試。。要得到准確的行號是很難的。這可以算是一個比較麻煩的地方。因為優化很可能優化出bug。導致無法正常調試。
2.怎么查看i的值?
在命令行里輸入
輸出
$1 = 0
(gdb)
3 怎么單步調試?
在命令行里輸入
輸出
5 i=10;
4.我這樣一步一步點好累啊!!能一次走兩步么 ?
一次走3步。
5.我想重新調試程序,怎么辦啊! 再次運行
6 我想直接執行到下一個斷點,怎么辦?
如果后面沒有斷點了.那就直接結束了.
2
2.斷點基本管理。
3.函數基本調試.
1.多文件調試斷點標記方法
首先引入兩個文件。
main.c
void swap(int *a, int
*b);
int main( void )
{
int i = 3;
int j
= 5;
printf("i: %d, j: %d\n", i,
j);
swap(&i,
&j);
printf("i: %d, j: %d\n", i,
j);
return 0;
}
swapper.c
{
int c =
*a;
*a = *b;
*b = c;
}
編譯之
gcc -o swap main.o swapper.o
載入swap 程序
1.那我想。怎么讓我調試到swap函數呢?
有幾種方法:
1.直接用在gdb中強行指定
//或者以下
2.利用step命令,相當於xcode 里的step into 命令。首先在main 函數執行到swap時打上斷點也就是
當執行到將要調用swap函數時,執行next 命令(縮寫n)
完整的命令行輸出如下:
bogon:swap cubase01$ gdb swap
GNU gdb 6.3.50-20050815 (Apple version
gdb-1820) (Sat Jun 16 02:40:11 UTC 2012)
Copyright 2004 Free Software
Foundation, Inc.
GDB is free software, covered by the GNU General Public
License, and you are
welcome to change it and/or distribute copies of it
under certain conditions.
Type "show copying" to see the conditions.
There
is absolutely no warranty for GDB. Type "show warranty" for
details.
This GDB was configured as "x86_64-apple-darwin"...Reading symbols
for shared libraries .. done
(gdb) b main.c:10
Breakpoint 1 at 0x100000e57: file main.c, line
10.
(gdb) r
Starting program:
/Users/cubase01/Desktop/Debugging/Chapter_02/swap/swap
Reading symbols for
shared libraries +........................ done
i: 3, j: 5
Breakpoint 1, main () at
main.c:10
10 swap(&i,
&j);
(gdb) s
swap (a=0x7fff5fbffac4, b=0x7fff5fbffac0) at
swapper.c:3
3 int c = *a;
(gdb)
2.斷點基本管理
說到管理。我想做過數據庫的都會想到 增刪改查四大名捕。。。沒錯!那我們從4位入手。。
增
b main.c:4
b main.c:main
刪
delete 1 2 5 //可以同時刪除多個斷點
delete //如果沒有參數 則刪除所有斷點
clear //消除gdb當前行的斷點.
clear main //注意沒有delete main這樣的命令 clear main的意思是,清除函數main內所有的斷點。
clear main.c:main //同上
clear 2
clear main.c:2
有人會問為什么一定要指定文件名?
答:因為你不一定是在這個文件里調試.
又問:哪我怎么知道我現在是在哪個文件里調試?
答:用list命令,同時你也可以用list命令切換你當前的命令作用對象.
禁用/啟用
disable 1 5
enable once 1 5 // 只啟用斷點一起.中斷后將置為disable狀態
改
改的話主要用到地方是將普通斷點改為條件斷點,這在第3章會有具體介紹.
查
3.函數基本調試
函數重要的就是進入函數與跳出函數,對應的gdb命令就是
finish
看一下以下具體應用
(gdb) list main
1 #include <stdio.h>
2 void swap(int *a, int *b);
3
4 int main( void )
5 {
6 int i = 3;
7 int j = 5;
8
9 printf("i: %d, j: %d\n", i, j);
10 swap(&i, &j);
(gdb) b 10
Breakpoint 11 at 0x100000e57: file main.c, line 10.
(gdb) run
Starting program: /Users/cubase01/Desktop/Debugging/Chapter_02/swap/swap
i: 3, j: 5
Breakpoint 11, main () at main.c:10
10 swap(&i, &j);
(gdb) step
swap (a=0x7fff5fbffac4, b=0x7fff5fbffac0) at swapper.c:3
3 int c = *a;
(gdb) finish
Run till exit from #0 swap (a=0x7fff5fbffac4, b=0x7fff5fbffac0) at swapper.c:3
main () at main.c:11
11 printf("i: %d, j: %d\n", i, j);
3
前面兩往篇把基本的調試命令介紹了一篇.下面.來點稍微高級的.這章將要介紹的是
1. 斷點的邏輯判斷
首先准備好要調試的文件.簡單簡單...
看着這段代碼,有幾個調試需求如下:
需求1.請在i=5的時候停下來.
需求2.請在i=5后,停下來之后.直接跳到return 0;
用前面說的基本方法,滿足這兩個需求是沒問題的.但是比較麻煩.
需求1. 見以下命令行輸出
需求2.
則是在需求1的基礎上在return 0 上打斷點.
並刪除打在printf("hello,world!")上的斷點.
並執行continue
以上是用基本的命令完成的.可以看出比較麻煩.
下面介紹簡單的方法.
需求1,2同時解決
條件斷點中的包括所有c++中的邏輯判斷符(>=,<=,etc).運算符(+ , - ,* / , etc).包括位運算(| ,~ , etc).
更重要的是: 你還可以在條件斷點中用function,只要這個函數鏈接到了這個函數所在的調試庫.如果不是調試庫.而發行庫.那這個函數的返回值只會int類型.對於double float 或者其他類型.會解釋不正確. 比如說
想強轉? (gdb) p (double)(0.0) .沒用.在以后介紹gdb變量的時候.會有一個折衷的辦法.再說.
但如果只是設置了
想臨時加 if i>3怎么辦?
這樣,先查找到第line 3這個斷點的Num.假設為9
輸入
取消條件斷點? 簡單
4
接下來的開始命令的具體事例講解
引入以下文件:
以下命令中的括號代表的意義為[]可選 {}必須
1.commands
command命令里可以綁定很多函數.只要能夠鏈接的到.庫函數.你自己定義的函數都可以.甚至還可以使用函數的返回值.只要函數的返回值是int類型.
2.define
命令行輸出如下
5
1.watchpoint,誰動了我的內存
watchpoint 顧名思義,監視.在很多ide里都有這個功能.用起來跟breakpoint稍微有點不一樣.我們知道breakpoint是可以在調試之前事先打好斷點.但watchpoint不同.watchpoint必須要在當前scope里的設置.舉例來說
拿以下fibonacci.c舉例
命令行,監視全局變量
bogon:fibonacci cubase01$ gdb fibonacci //加載gdb fibonacci
GNU gdb 6.3.50-20050815 (Apple
version gdb-1820) (Sat Jun 16 02:40:11 UTC 2012)
Copyright 2004 Free Software
Foundation, Inc.
GDB is free software, covered by the GNU General Public
License, and you are
welcome to change it and/or distribute copies of it
under certain conditions.
Type "show copying" to see the conditions.
There
is absolutely no warranty for GDB. Type "show warranty" for
details.
This GDB was configured as "x86_64-apple-darwin"...Reading symbols
for shared libraries .. done
(gdb) wa global //設置watchpoint 注意這是一個global變量.所以它的scope也是global的
Hardware watchpoint 1: global
(gdb) r
Starting
program: /Users/cubase01/Desktop/Debugging/Chapter_02/fibonacci/fibonacci
Reading symbols for shared libraries +........................
done
Fibonacci(3) is 3.
Hardware watchpoint 1: global
Old value = 0
New value = 2 //發現值改變.停下來.
main () at
fibonacci.c:8
8 return 0;
(gdb)
以上有幾點要注意的地方 .
- watchpoint只能在當前scope里設置.
- watchpoint在脫離scope后.所有失去scope的變量會被自動刪除.
- watchpoint監視的是內存值的變化.如果內存值被賦予了一個一樣的值.那也不會起作用.
watchpoint也可以像條件斷點一樣加上條件.它們的區別是watchpoint會在作用域之后自動刪除.沒前作用域之前不會存在.
條件監視斷點里可以包含的表達式有:
- 類似$arg0這些已經在gdb中定義的變量.
- 任何在當前 scpoe里的變量
- 定義了的字符,數字常量
- 預處理了的宏
- 語言里的條件判斷
修改如下:
bogon:fibonacci cubase01$ gdb
GNU gdb 6.3.50-20050815 (Apple version
gdb-1820) (Sat Jun 16 02:40:11 UTC 2012)
Copyright 2004 Free Software
Foundation, Inc.
GDB is free software, covered by the GNU General Public
License, and you are
welcome to change it and/or distribute copies of it
under certain conditions.
Type "show copying" to see the conditions.
There
is absolutely no warranty for GDB. Type "show warranty" for
details.
This GDB was configured as "x86_64-apple-darwin".
(gdb) file
fibonacci
Reading symbols for shared libraries .. done
Reading symbols
from /Users/cubase01/Desktop/Debugging/Chapter_02/fibonacci/fibonacci...Reading
symbols from
/Users/cubase01/Desktop/Debugging/Chapter_02/fibonacci/fibonacci.dSYM/Contents/Resources/DWARF/fibonacci...done.
done.
(gdb)
b main
Breakpoint 1 at 0x100000e34: file fibonacci.c, line 6.
(gdb)
run
Starting program:
/Users/cubase01/Desktop/Debugging/Chapter_02/fibonacci/fibonacci
Reading
symbols for shared libraries +........................ done
Breakpoint 1, main () at
fibonacci.c:6
6 int a=1;
(gdb)
wa a
Hardware watchpoint 2: a
(gdb) r
The program being debugged
has been started already.
Start it from the beginning? (y or n) n
Program
not restarted.
(gdb) c
Continuing.
Hardware watchpoint 2: a
Old value = 0
New value = 1
main () at
fibonacci.c:7
7
printf("Fibonacci(3) is %d.\n", fibonacci(3));
(gdb)
c
Continuing.
Fibonacci(3) is 3.
Hardware watchpoint 2: a
Old value = 1
New value = 2
main () at
fibonacci.c:10
10 return
0;
(gdb)
Tips:
Num Type Disp Enb Address What
1 hw watchpoint keep y global breakpoint already hit 1 time
有沒有發現上面的watchpoint type 是hw,這是因為watchpoint可以由硬件實現.速度很快.如果硬件不能實現gdb則會利用VM技術實現.也比較快.如果前兩種都不行.GDB會自己通過軟件實現..很慢.
6
前面已經把95%的調試技巧全講完了.接下來一起學習一下方便調試的東西.這些命令或者技巧是能讓你更方便的調試.
接下來將介紹以下高級命令
display //在每次斷點停下來時,打印當前scope中的變量
call //調用當前scope中某個函數
怎么查看當前stack中的變量?
怎么打印其他格式的值?
怎么管理display例表?
怎么臨時改變變量的值?
怎么設置啟動時的命令行參數?
為什么每次print時,都有$1,$2之類的東東在前面?
怎么重復上一命令?
怎么在gdb中設置一個變量?
Tips
怎么打印數組?
准備以下文件
main()
{
x = (int *) malloc( 25*sizeof(int) );
x[3] = 12;
}
Breakpoint 1 at 0x100000f24: file solutions_in_gdb.c, line 6.
(gdb)
r
Starting program:
/Users/cubase01/Desktop/Debugging/Chapter_03/solutions_in_gdb/solutions_in_gdb
Reading symbols for shared libraries +........................ done
Breakpoint 1, main () at
solutions_in_gdb.c:6
6 x[3] =
12;
(gdb) p *x@25
$1 = {0 <repeats 25 times>}
(gdb)
n
7 }
(gdb) p *x@25 //這個是關鍵的東西 p指針@元素個數
$2 = {0, 0, 0, 12, 0 <repeats
21 times>}
(gdb)
怎么查看當前stack中的變量?
怎么打印其他格式的值?
p/c i //打印字符
p/s i //打印字符串
p/f i //打印float
怎么管理display例表?
Auto-display expressions now in effect:
Num Enb Expression
1: y i
Num
這個跟斷點Num的意義一樣.是display某值的一個標識.
Enb
縮寫(enable),是否激活
要禁用的話.啟用用enable.刪除display命令有點不合群.undisplay 1
enable display 1
undisplay 1
Expression
display 的變量值
怎么臨時改變變量的值?
怎么設置啟動時的命令行參數?
當然.你設置完后.要調用run啟動這些命令.跟直接執行以下是一樣的
打印命令行參數
為什么每次print時,都有$1,$2之類的東東在前面?
這個東西叫 value history. 惡心翻譯一下就是值的歷史吧. 比如說你打印
*a =1;
(gdb)p a
$1 =(int*) 0x32fa231 //打印出值的地址
(gdb)p
$1
//打印出$1更新后的值
$2 =(int*) 0x32fa231
(gdb)p
*$2
//打印出$2指向的值
$3 = 1
(gdb) p
$
//打印出上一個value history的值
$4 = 1
(gdb) p
&$
//打印出上一個value history的地址
$5 =(int*) 0x32fa231
怎么重復上一命令??
直接按回車.
怎么在gdb中設置一個變量?
set $b=2
$b++
Tips
大家覺得下面兩個函數有什么區別? 麻煩用call命令在gdb中分別調用它們(跟gdb沒關系.只是順便舉例).
發現區別沒有.printf不一定要會即時調用.我建議大家看一下這篇文章.一個fork的面試題 .
7
這一篇主要說一下類的一些相關調試.
類相關gdb調試
准備以下類
#include <cstdlib>
#include <iostream>
using namespace std;
class node {
public:
static class node *root; // root of the entire tree
int val; // stored value
class node *left; // ptr to smaller child
class node *right; // ptr to larger child
node(int x); // constructor, setting val = x
static void insert(int x); // insert x into the tree
static void printtree(class node *nptr); // print subtree rooted at *nptr
};
class node *node::root = 0;
node::node(int x)
{
val = x;
left = right = 0;
}
void node::insert(int x)
{
if (node::root == 0) {
node::root = new node(x);
return;
}
class node *tmp=root;
while (1)
{
if (x < tmp->val)
{
if (tmp->left != 0) {
tmp = tmp->left;
} else {
tmp->left = new node(x);
break;
}
} else {
if (tmp->right != 0) {
tmp = tmp->right;
} else {
tmp->right = new node(x);
break;
}
}
}
}
void node::printtree(class node *np)
{
if (np == 0) return;
node::printtree(np->left);
cout << np->val << endl;
node::printtree(np->right);
}
int main(int argc, char *argv[])
{
for (int i = 1; i < argc; i++)
node::insert(atoi(argv[i]));
node::printtree(node::root);
}
怎么打印類中的靜態變量?
打印方法
怎么查看類的結構?
(gdb) b main
Breakpoint 1 at 0x100000c9f: file bintree.cc, line 72.
(gdb) r
Starting program: /Users/cubase01/Desktop/Debugging/Chapter_03/bintree++/bintree
Reading symbols for shared libraries ++......................... done
Breakpoint 1, main (argc=1, argv=0x7fff5fbffac0) at bintree.cc:72
72 for (int i = 1; i < argc; i++)
(gdb) ptype node
type = class node {
public:
int val;
node *left;
node *right;
static node *root;
node(int);
node(int);
node(int);
static void insert(int);
static void printtree(node*);
}
(gdb)
8
調試中最煩的就是線程. 線程太多隊伍不好帶...
在mac下.線程用的最多的還是pthread. 先准備好要調試的線程源碼:
以下這個文件是從<<The art of debugging with gdb >>中借來用的.
sieve.c
// deleting all multiples of 2, all multiples of 3, all multiples of 5,
// etc.; not efficient, e.g. each thread should do deleting for a whole
// block of values of base before going to nextbase for more
// usage: sieve nthreads n
// where nthreads is the number of worker threads
#include <stdio.h>
#include <math.h>
#include <pthread.h>
#define MAX_N 100000000
#define MAX_THREADS 100
// shared variables
int nthreads, // number of threads (not counting main())
n, // upper bound of range in which to find primes
prime[MAX_N+1], // in the end, prime[i] = 1 if i prime, else 0
nextbase; // next sieve multiplier to be used
int work[MAX_THREADS]; // to measure how much work each thread does,
// in terms of number of sieve multipliers checked
// lock index for the shared variable nextbase
pthread_mutex_t nextbaselock = PTHREAD_MUTEX_INITIALIZER;
// ID structs for the threads
pthread_t id[MAX_THREADS];
// "crosses out" all multiples of k, from k*k on
void crossout(int k)
{ int i;
for (i = k; i*k <= n; i++) {
prime[i*k] = 0;
}
}
// worker thread routine
void *worker(int tn) // tn is the thread number (0,1,...)
{ int lim,base;
// no need to check multipliers bigger than sqrt(n)
lim = sqrt(n);
do {
// get next sieve multiplier, avoiding duplication across threads
pthread_mutex_lock(&nextbaselock);
base = nextbase += 2;
pthread_mutex_unlock(&nextbaselock);
if (base <= lim) {
work[tn]++; // log work done by this thread
// don't bother with crossing out if base is known to be
// composite
if (prime[base])
crossout(base);
}
else return;
} while (1);
}
main(int argc, char **argv)
{ int nprimes, // number of primes found
totwork, // number of base values checked
i;
void *p;
n = atoi(argv[1]);
nthreads = atoi(argv[2]);
for (i = 2; i <= n; i++)
prime[i] = 1;
crossout(2);
nextbase = 1;
// get threads started
for (i = 0; i < nthreads; i++) {
pthread_create(&id[i],NULL,(void *) worker,(void *) i);
}
// wait for all done
totwork = 0;
for (i = 0; i < nthreads; i++) {
pthread_join(id[i],&p);
printf("%d values of base done\n",work[i]);
totwork += work[i];
}
printf("%d total values of base done\n",totwork);
// report results
nprimes = 0;
for (i = 2; i <= n; i++)
if (prime[i]) nprimes++;
printf("the number of primes found was %d\n",nprimes);
}
怎么列舉當前的線程?
info threads
在斷點停下后.怎么切換到其他線程去
thread n
怎么指定在某個斷點停下,當在某個線程時.
break 30 thread 1
9
為什么你的程序會崩潰?
在寫c/c++程序時.最常見的崩潰就是
invalid access of memory 讀取了沒有權限的內存導致崩潰.出現這種情況時. 硬件會執行一個jump命令到操作系統.由操作系統處理.
調試時,內存是在Virtual Memory(VM)里模擬.這也是為了方便內存調試.
反應在xcode里就是segementation fault/seg fault
在visual studio里是general protection fault 反正不管怎么取名.它的意思就是說程序讀了它不該讀的內存.
正是因為在調試時內存是在虛擬內存里分配的.所以有一些segmentation fault不是很明顯.原因是:
在VM里的內存分配有一個最小分配值.取名叫page. 在奔騰系統處理器里.這個值默認為4096 bytes.所以當你有一個程序占用了10000 bytes時.它總共會占用3個page.而不是2.5個.page已經是最小單位.不能再分割.
這就會出現一個問題.如果程序在多出來的page里出了錯.很可能在調試時並不會報錯.