概述
程序开发离不开调试,可以断点调试,也可以打log调试,linux下断点调试c,c++程序用gdb。
断点调试虽然很爽,但是效率较低,浪费时间。好的程序有完备的log,任何有可能出错的地方,都有log记录,所以只要看log一眼就能知道哪里有问题。尤其是我们在做服务器开发的时候,线上是不可能让你打断点调试的。所以在程序里记上完备的log是良好的习惯,会为你节省大量的调试时间。
但是,断点调试是我等必备的职业技能之一,所以必须熟练掌握断点调试。
下面我们开始学习gdb调试。
先安装gdb,一路走来的同学应该已经会安装了,这里我就不啰嗦了。
[toc]
运行gdb
装完以后,输入gdb运行一下,就是下面的效果
# 这里输入gdb
bash$ gdb
GNU gdb (Ubuntu 8.2-0ubuntu1) 8.2
Copyright (C) 2018 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".
(gdb)
#这里等待输入命令
我们现在还没有程序可以调试,先退出gdb
# 接上一步,输入q,退出
(gdb) q
准备个程序,用于调试
共4个文件:
main.cpp
test_class.h
test_class.cpp
Makefile
test_class.h的内容
#ifndef __test_class_h__
#define __test_class_h__
class TestClass{
public:
TestClass();
virtual ~TestClass();
public:
int sum(int a, int b);
int fib(int n);
int fib2( int n );
};
#endif//__test_class_h__
test_class.cpp的内容
#include <stdio.h>
TestClass::TestClass(){
}
TestClass::~TestClass(){
}
int TestClass::sum(int a, int b){
int c = a+b;
return c;
}
int TestClass::fib(int n){
if( n <= 0 ){
return 0;
}
if( n == 1 ){
return 1;
}
int a1 = fib(n-1);
int a2 = fib(n-2);
int c = a1 + a2;
return c;
}
int TestClass::fib2( int n ){
if( n <= 0 ){
return 0;
}
if( n <= 2 ){
return 1;
}
int tmp[] = { 0, 1, 1 };
for( int i=2; i<n; ++i ){
tmp[0] = tmp[1];
tmp[1] = tmp[2];
tmp[2] = tmp[0] + tmp[1];
}
return tmp[2];
}
main.cpp的内容
#include <stdio.h>
#include <string.h>
#include "test_class.h"
int main(int argc, char** argv){
(void)argc;
(void)argv;
printf("hello dafein");
TestClass o;
int a = 1;
int b = 2;
int c = o.sum( a, b );
char buff[256]; memset(buff, 0x00, sizeof(buff));
snprintf(buff, sizeof(buff), "sum: %d+%d=%dn", a, b, c);
printf("%s", buff);
for( int i=1; i<10; ++i ){
int f = i;
int fv = o.fib( f );
int fv2 = o.fib2( f );
memset(buff, 0x00, sizeof(buff));
snprintf(buff, sizeof(buff), "fib: %d = %d
%dn", f, fv, fv2 );
printf("%s", buff);
}
return 0;
}
Makefile
LINK
= @echo linking $@ && g++
GCC
= @echo compiling $@ && g++
GC
= @echo compiling $@ && gcc
AR
= @echo generating static library $@ && ar crv
FLAGS
= -g -DDEBUG -W -Wall -fPIC
GCCFLAGS =
DEFINES =
HEADER
= -I./
LIBS
=
LINKFLAGS =
#LIBS
+= -lrt
#LIBS
+= -pthread
OBJECT := main.o
test_class.o
BIN_PATH = ./
TARGET = main
$(TARGET) : $(OBJECT)
$(LINK) $(FLAGS) $(LINKFLAGS) -o $@ $^ $(LIBS)
.cpp.o:
$(GCC) -c $(HEADER) $(FLAGS) $(GCCFLAGS) -fpermissive -o $@ $<
.c.o:
$(GC) -c $(HEADER) $(FLAGS) -fpermissive -o $@ $<
install: $(TARGET)
cp $(TARGET) $(BIN_PATH)
clean:
rm -rf $(TARGET) *.o *.so *.a
编译程序
bash$ make
compiling main.o
compiling test_class.o
linking main
查看一下生成的程序
bash$ ls
main
main.cpp
main.o
Makefile
test_class.cpp
test_class.h
test_class.o
用gdb运行程序就可以下断点了,
!!!注意
当运行gdb ./main的时候,只是用gdb加载了main程序,main本身还没开始运行。
bash$ gdb ./main
GNU gdb (Ubuntu 8.2-0ubuntu1) 8.2
Copyright (C) 2018 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 ./main...done.
(gdb)
显示当前运行到的代码,因为现在程序还没开始运行,所以显示在main函数
命令: list,可以简写为l
(gdb) l
1
#include <stdio.h>
2
#include <string.h>
3
#include "test_class.h"
4
5
int main(int argc, char** argv){
6
(void)argc;
7
(void)argv;
8
9
printf("hello dafein");
10
按回车会重复上一次的命令,也就是再执行一次l,效果是翻到下一页
(gdb)
#这里输入了回车,默认执行上一次的命令
11
TestClass o;
12
int a = 1;
13
int b = 2;
14
int c = o.sum( a, b );
15
16
char buff[256]; memset(buff, 0x00, sizeof(buff));
17
snprintf(buff, sizeof(buff), "sum: %d+%d=%dn", a, b, c);
18
printf("%s", buff);
19
20
for( int i=1; i<10; ++i ){
(gdb)
显示当前文件的第20行代码可以用l 20
(gdb) l 20
15
16
char buff[256]; memset(buff, 0x00, sizeof(buff));
17
snprintf(buff, sizeof(buff), "sum: %d+%d=%dn", a, b, c);
18
printf("%s", buff);
19
20
for( int i=1; i<10; ++i ){
21
int f = i;
22
int fv = o.fib( f );
23
24
int fv2 = o.fib2( f );
显示TestClass::fib的代码
(gdb) l TestClass::fib
8
9
int TestClass::sum(int a, int b){
10
int c = a+b;
11
return c;
12
}
13
int TestClass::fib(int n){
14
if( n <= 0 ){
15
return 0;
16
}
17
if( n == 1 ){
下断点
gdb里下断点很灵活,下面看几种下断点的方法:
命令: break,可以简写为b
- 在main函数下个断点
(gdb) b main
Breakpoint 1 at 0x11ae: file main.cpp, line 5.
- 在当前文件的第20行下个断点,因为当前文件是main.cpp所以断点下在main.cpp的第20号
(gdb) b 20
Breakpoint 2 at 0x127d: file main.cpp, line 20.
- 指定断点下在类的成员函数
(gdb) b TestClass::sum
Breakpoint 3 at 0x141e: file test_class.cpp, line 10.
- 指定断点下到test_class.cpp的第29行
(gdb) b test_class.cpp:29
Breakpoint 5 at 0x14b4: file test_class.cpp, line 29.
显示所有断点
命令: info break,可以简写为 i b
下面显示了当前共有4个断点,每个断点有相应的编号,Enb表示是否启用,我们可以禁用某个断点
(gdb) i b
Num
Type
Disp Enb Address
What
1
breakpoint
keep y
0x00000000000011ae in main(int, char**) at main.cpp:5
2
breakpoint
keep y
0x000000000000127d in main(int, char**) at main.cpp:20
3
breakpoint
keep y
0x000000000000141e in TestClass::sum(int, int) at test_class.cpp:10
4
breakpoint
keep y
0x00000000000014b4 in TestClass::fib2(int) at test_class.cpp:29
禁用断点
命令: disable,可以简写为dis
下面禁用4号断点,如果不指定编号,会禁用所有断点
(gdb) dis 4
再显示一下4号断点信息,可以看到Enb变为n了,表示该断点被禁用了
(gdb) i b 4
Num
Type
Disp Enb Address
What
4
breakpoint
keep n
0x00000000000014b4 in TestClass::fib2(int) at test_class.cpp:29
启用断点
命令: enable,可以简写为en
下面启用4号断点,如果不指定编号,会启用所有断点
(gdb) en 4
再显示一下4号断点,可以看到Enb变为y了
(gdb) i b 4
Num
Type
Disp Enb Address
What
4
breakpoint
keep y
0x00000000000014b4 in TestClass::fib2(int) at test_class.cpp:29
删除断点
命令: delete,可以简写为d
下面删除4号断点,如果不指定编号,会删除所有断点
(gdb) d 4
显示一下当前所有断点,可以看到4号断点没了
(gdb) i b
Num
Type
Disp Enb Address
What
1
breakpoint
keep y
0x00000000000011ae in main(int, char**) at main.cpp:5
2
breakpoint
keep y
0x000000000000127d in main(int, char**) at main.cpp:20
3
breakpoint
keep y
0x000000000000141e in TestClass::sum(int, int) at test_class.cpp:10
运行程序
命令: run 参数列表,可以简写为r
(gdb) r
Starting program: /home/dafei/test_gdb/main
# 因为我们在运行前已经打好断点,所以main程序刚跑起来就被断在main函数了
# 下面可以看到函数参数的值为argc=1, argv=0x7fffffffe1f8
Breakpoint 1, main (argc=1, argv=0x7fffffffe1f8) at main.cpp:5
5
int main(int argc, char** argv){
(gdb)
运行时带上参数abc
(gdb) r abc
The program being debugged has been started already.
Start it from the beginning? (y or n) y
Starting program: /home/dafei/test_gdb/main abc
# 下面可以看到argc为2,表示有一个参数,第一个为程序本身
Breakpoint 1, main (argc=2, argv=0x7fffffffe1d8) at main.cpp:5
5
int main(int argc, char** argv){
# 输出argv的值
(gdb) p argv[0]
$15 = 0x7fffffffe49c "/home/dafei/test_gdb/main"
(gdb) p argv[1]
$16 = 0x7fffffffe4d2 "abc"
显示源代码位置和编程语言
命令: info source 可以简写为i source
该命令还会打印出编译选项
(gdb) i source
Current source file is main.cpp
Compilation directory is /home/dafei/test_gdb
Located in /home/dafei/test_gdb/main.cpp
Contains 34 lines.
Source language is c++.
Producer is GNU C++14 8.2.0 -mtune=generic -march=x86-64 -g -fPIC -fpermissive -fstack-protector-strong.
Compiled with DWARF 2 debugging format.
Does not include preprocessor macro info.
打印变量
命令: print,可以简写为p
变量显示格式:
- x 按十六进制格式显示变量
- d 按十进制格式显示变量
- u 按十六进制格式显示无符号整型
- o 按八进制格式显示变量
- t 按二进制格式显示变量
- a 按十六进制格式显示变量
- c 按字符格式显示变量
- f 按浮点数格式显示变量
查看argc的值
(gdb) p argc
$2 = 1
查看变量的16进制值
(gdb) p/x argc
$6 = 0x1
查看argv的值
(gdb) p argv
$3 = (char **) 0x7fffffffe1f8
看*argv的值
(gdb) p *argv
$4 = 0x7fffffffe4a0 "/home/dafei/test_gdb/main"
查看argv[0]的值
(gdb) p argv[0]
$5 = 0x7fffffffe4a0 "/home/dafei/test_gdb/main"
(gdb)
下一步,跳过函数(与VS F10作用相同)
命令: next,可以简写为n
(gdb) n
# 程序计数器向前走了一步,下一步该执行第9行了
9
printf("hello dafein");
按回车重复上一步操作,也就是再执行一次n
(gdb)
# 第9行执行后,输出了一行字符串,并且程序计数器指向第11行
hello dafei
11
TestClass o;
再按次回车向前走一步
(gdb)
12
int a = 1;
打印一下TestClass o的值
(gdb) p o
$10 = {_vptr.TestClass = 0x555555557d58 <vtable for TestClass+16>}
进函数(与VS F11作用相同)
命令: step,可以简写为s
再n两次,这时程序计数器到了第14行
(gdb) n
13
int b = 2;
(gdb)
14
int c = o.sum( a, b );
第14行是调用o.sum函数,我们想进去看看,按s就进到函数里面了
(gdb) s
Breakpoint 3, TestClass::sum (this=0x7fffffffdfc8, a=1, b=2) at test_class.cpp:10
10
int c = a+b;
查看函数调用栈
命令: backtrace,可以简写为bt
(gdb) bt
#0
TestClass::sum (this=0x7fffffffdfc8, a=1, b=2) at test_class.cpp:10
#1
0x0000555555555209 in main (argc=2, argv=0x7fffffffe1d8) at main.cpp:14
切到上一层栈
命令: up
如果我们已经进到了一个函数,想再看看调用函数的地方,可以用up,切到上一层
(gdb) up
#1
0x0000555555555209 in main (argc=2, argv=0x7fffffffe1d8) at main.cpp:14
14
int c = o.sum( a, b );
切到下一层栈
命令: down
然后再切到下一层,用down
(gdb) down
#0
TestClass::sum (this=0x7fffffffdfc8, a=1, b=2) at test_class.cpp:10
10
int c = a+b;
运行当前函数到结束(与VS shift+F11效果相同)
命令: finish,可以简写为fin
在函数里面我们可以一直用n运行到函数结束,也可以直接用finish运行到该函数结束返回,finish结束后还会打出函数的返回值
(gdb) fin
Run till exit from #0
TestClass::sum (this=0x7fffffffdfc8, a=1, b=2) at test_class.cpp:11
0x0000555555555209 in main (argc=2, argv=0x7fffffffe1d8) at main.cpp:14
14
int c = o.sum( a, b );
Value returned is $19 = 3
修改变量值
命令: print 变量名=值,可以简写为p 变量名=值
# 先打印一下a的值看看是多少
(gdb) p a
$4 = 1
# 再修改a的值为2
(gdb) p a=2
$5 = 2
# 重新打印一下看看,值已经被改为2了
(gdb) p a
$6 = 2
(gdb)
打印所有局部变量
命令: info locals, 可以简写为i locals
(gdb) i locals
o = {_vptr.TestClass = 0x555555557d58 <vtable for TestClass+16>}
a = 2
b = 0
c = -137657753
#从这里开始是程序还没执行的语句,这些变量还未初始化
buff = "p 02 00 00 00 00 00 00 00360377377377377377377", '