什么是GDB?
GNU项目中的调试器(gnu debuger)
能够跟踪程序的执行,也能够恢复程序奔溃前的状态。
为什么需要GDB?
软件不是一次性开发完成的(是软件就有bug,是程序就有问题)
调试是软件开发过程中不可或缺的技术(调试工具很重要)
GDB的常规应用:
自定义程序的启动方式(指定影响程序运行的参数)
设置条件断点(在条件满足时暂停程序的执行)
回溯检查导致程序异常就结束的原因(Core Dump)
动态改变程序执行流(定位问题的辅助方式)。
前边的工具集与GDB区别:
工具集:静态分析工具:目标是程序文件
GDB:动态分析工具,目标是进程,程序文件加载执行得到进程。
GDB的启动方式:
直接启动:
gdb
gdb test.out (关注test.out产生的进程)
gdb test.out core (core程序异常奔溃的库文件)
动态链接:gdb test.out pid (动态关注test.out的一个进程pid 进程号)
GDB应用实例一:
gdb //启动
(gdb) file test.out //载入目标程序
(gdb) set args arg1 arg2 //设置命令行参数main函数括号里边的,改变应用程序的启动参数
(gdb) run //执行程序
gdb (gdb) file test.out <=等价=> gdb test.out
应用示例二:
gdb //启动
(gdb) attch pid(进程号) //链接到目标进程,链接成功后,目标进程将停止执行
(gdb) continue //恢复执行
gdb (gdb) attach pid <=等价=>gdb test.out pid
命令:ulimit -c unlimited 在奔溃时产生core文件,
gdb test.out core
#0 0x08048575 in func () at func.c:7 错误
与add2line一致的,add2line并不是分析,而是翻译处错误所在。quit退出gdb
而gdb是动态分析工具,跟踪进程执行,非法立即输出。
ctrl c 停止执行,continue继续执行
ps aux 查看进程
sudo gdb 启动:以sudo启动gdb,动态链接到进程,否则没权限
attach 2192 //动态链接到gdb与进程
使用GDB进程断点调试:
断点类型:
软件中断:由非法指令异常实现(软件中断):对运行在内存中的程序有效
硬件中断:由硬件特性实现(数量有限):在flash中运行的程序软件中断无效,依赖硬件中断
数据断点:由硬件特性实现(数量有限):监视内存,内存读写时停止
软件断点的相关操作:
通过函数名设置断点:
break func_name [if var = value ] //[ 条件 ]
tbreak func_name [if var =value ]
break与tbreak区别:次数区别,break总是有效,tbreak是一次断点
通过文件名行号设置断点:
break file_name:line_num [if var = value ]
tbreak func_name:line_num [if var =value ]
软件断点的相关操作:
操作 命令
断点查看 info breakpoints
断点删除 delete 1 2 n ; delete breakpoints (删除所有断点)
断点状态改变 enable 1 2 n;enable breakpoints; disable 1 2 n; disable breakpoints
调试时的的常用操作:
操作 命令
变量查看 print name
变量设置 set var name=value
执行下一行代码 next
连续执行n行代码 next n
执行进入函数 step
强制当前函数返回 return [value]
运行至当前函数返回 finish
执行至目标行 until line
跳转执行 jump line
硬件断点及其应用:
当代吗位于只读存储器(Flash)时,只能通过硬件断点调试。
硬件断点需要硬件支持,数量有限。
GDB中通过hbreak命令支持硬件断点。h--hard
hbreak与break使用方式完全一致。
命令:start :启动后立即暂停
第一次调试:
gcc -g test.c func.c -o test.out
(gdb) set args d.e.segsd
(gdb) start
break test.c:37 //设置断点
info breakpoints //查看断点
continue //开始执行
next //执行下一条
set var i=100 //设置i的值为100
$2=100 //第二次调用
tbreak test.c:43 //test.c的43行设置断点
jump 45 //跳到45行
第二次调试:
tbreak func //通过函数名打断点
return //强制返回
第三次调试:
show can-use-hw-watchpoints //显示有几个硬件断点
hbreak func //硬件方式设断点
print g_pointer //打印 g_pointer
set var g_pointer=(int*)malloc(sizeof(int)) //设置g_pointer指向堆空间
continue
小结:GDB是GNU项目中的调试器,能够跟踪或改变程序的执行。
GDB能够根据Core Dump回溯检查导致程序结束的原因。
GDB同时支持软件断点,硬件断点和数据断点。
GDB是嵌入式开发中必须掌握的重要工具。
12、GDB下
数据断点:数量有限
GDB中支持数据断点的设置。
watch命令用于监视变量是否被改变(本质为硬件断点)
watch命令的用法:watch var_name
GDB中的内存查看:
GDB中可以检查任意内存区域中的数据。
命令语法:x /Nuf expression
N-需要打印的单元数
u-每个单元的大小
f-数据打印的格式
示例:x /4bx 0x804a024 (4bx: 4表示单元数目,b表示byte,x:通过x方式呈现,十六进制)
x命令中参数u对应的单位:
格式 打印方式
b 单字节
h 双字节
w 四字节
g 八字节
格式 打印方式
x 十六进制
d 有符号十进制
u 无符号十进制
o 八进制
t 二进制
a 地址
c 字符
f 浮点数
示例:判断系统大小端
(gdb) set var=1
(gdb) print /a &var
结果:$1=0x804a024 <var>
(gdb) x /4bx 0x804a024 //查看内存值16进制
0x804a024 <var>: 0x01 0x00 0x00 0x00 结果 保存在低字节
(gdb) x /1bx 0x804a024
0x804a024 <var>: 0x01 打印一个字节
低地址存放低位数据:小端系统
示例:
#include <stdio.h>
#include <pthread.h>
#include <unistd.h>
int g_var = 0;
void* thread_func(void* args)
{
sleep(5);
g_var = 1;
}
int main()
{
int i = 0;
pthread_t tid = 0;
pthread_create(&tid, NULL, thread_func, NULL);
for(i=0; i<10; i++)
{
printf("g_var = %d\n", g_var);
sleep(1);
}
}
gcc -g -lpthread(表示用到多线程库) test.c -o test.out
./test.out
gdb test.out
start 启动 //停在main函数开头
watch g_var //硬件数据断点设置了
continue
结果:
[New Thread 0xb7fedb70 (LWP 2190)]
g_var = 0g_var = 0
g_var = 0
g_var = 0
g_var = 0
[Switching to Thread 0xb7fedb70 (LWP 2190)]
Hardware watchpoint 2: g_var // 数据断点生效
Old value = 0
New value = 1
thread_func (args=0x0) at watch.c:12 //改变的行
12 }
print g_var //打印g_var
print /a &g_var //打印地址 ===》$2 = 0x804a024 <g_var>
x /4bx 0x804a024 //x命令,打印16进制4个字节==》0x804a024 <g_var>:0x01 0x00 0x00 0x00
x /1bx 0x804a024==》0x804a024 <g_var>: 0x01
低地址放低位(字节)数据:小端
continue
(2)函数调用栈的查看(backtrace和frame)
backtrace:查看函数调用的顺序(函数调用栈的信息)。
frame N:切换到栈编号为N的上下文中。
info frame:查看当前函数调用的栈帧信息。
深入info命令:
命令 功能说明
info registers 查看当前寄存器的值
info args 查看当前函数参数的值
info locals 查看当前局部变量的值
info frame 查看当前栈帧的详细信息
info vareable 查看程序中的变量符号
info functions 查看程序中的函数符号
#include <stdio.h>
int sum(int n)
{
int ret = 0;
if( n > 0 )
{
ret = n + sum(n-1);
}
return ret;
}
int main()
{
int s = 0;
s = sum(10);
printf("sum = %d\n", s);
return 0;
}
gcc -g frame.c -o test.out //编译
./test.out
gdb
file test.out
start
break sum if n==0 //条件断点,n=0停止
info breakpoints
continue
backtrace //打印 ,函数调用栈顺序
#0 sum (n=0) at frame.c:6
#1 0x080483e5 in sum (n=1) at frame.c:10
#2 0x080483e5 in sum (n=2) at frame.c:10
#3 0x080483e5 in sum (n=3) at frame.c:10
#4 0x080483e5 in sum (n=4) at frame.c:10
#5 0x080483e5 in sum (n=5) at frame.c:10
#6 0x080483e5 in sum (n=6) at frame.c:10
#7 0x080483e5 in sum (n=7) at frame.c:10
#8 0x080483e5 in sum (n=8) at frame.c:10
#9 0x080483e5 in sum (n=9) at frame.c:10
#10 0x080483e5 in sum (n=10) at frame.c:10
#11 0x0804840d in main () at frame.c:21
从main开始执行,在main里边调用了sum函数,第一次调用以参数10调用,第二次调用用参数9调用。。。。一直调用直到参数为0,
next //执行一条语句
(gdb) next
13 return ret;
(gdb) info args //查看当前函数参数值
n = 0
这时,切换函数调用栈上下文,
frame 7 //切换到编号为7的函数栈帧函数调用上下文中,打印:
#7 0x080483e5 in sum (n=7) at frame.c:10
10 ret = n + sum(n-1);(语句停留在这里,因为所调用的函数没返回,所以停留这里)
(gdb) info args //打印
n = 7
(gdb) info locals //查看局部变量的值
ret = 0
(gdb)
(gdb) frame 0 //因为函数还停留在条件断点这里,所以要返回到0这里分析
#0 sum (n=0) at frame.c:13
13 return ret;
(gdb) info registers //打印当前上下文中重要寄存器的值
eax 0x0 0
ecx 0xcb2829e5 -886560283
edx 0x1 1
ebx 0x287ff4 2654196
esp 0xbffff080(当前sp寄存器的值) 0xbffff080
ebp 0xbffff0a8 0xbffff0a8
esi 0x0 0
edi 0x0 0
eip 0x80483eb 0x80483eb <sum+39>
eflags 0x246 [ PF ZF IF ]
cs 0x73 115
ss 0x7b 123
ds 0x7b 123
es 0x7b 123
fs 0x0 0
gs 0x33 51
(gdb) info frame //打印当前栈帧的详细信息,也就是当前函数调用对应的活动对象的详细信息
Stack level 0, frame at 0xbffff0b0:
eip = 0x80483eb in sum (frame.c:13); saved eip 0x80483e5
called by frame at 0xbffff0e0
source language c.
Arglist at 0xbffff0a8, args: n=0
Locals at 0xbffff0a8, Previous frame's sp is 0xbffff0b0(上一个sp的值)
Saved registers:
ebp at 0xbffff0a8(调用这个函数之前,ebp地址值保持在这里), eip at 0xbffff0ac
(gdb) x /1wx 0xbffff0a8 //以16进制连续打印4字节,起始地址
0xbffff0a8: 0xbffff0d8 //结果:意味着函数调用之前ebp寄存器的值是0xbffff0d8
(gdb) next
14 }
(gdb) next
13 return ret; //到了sum1的地方
(gdb) info args
n = 1
(gdb) info registers
eax 0x1 1
ecx 0xcb2829e5 -886560283
edx 0x1 1
ebx 0x287ff4 2654196
esp 0xbffff0b0(与上边一样) 0xbffff0b0
ebp 0xbffff0d8(与上边一样) 0xbffff0d8
esi 0x0 0
edi 0x0 0
eip 0x80483eb 0x80483eb <sum+39>
eflags 0x202 [ IF ]
cs 0x73 115
ss 0x7b 123
ds 0x7b 123
es 0x7b 123
fs 0x0 0
gs 0x33 51
函数调用的时候没有触发断点,什么在返回之后才触发断点? 设置n==5为断点的时候,函数会停在调用函数5那里,然后continue函数会运行到了调用完sum0之后的返回值时参数为5的时候,然后一步一步运行就使得从5到10的调用。有个问题是为什么开始next的时候就是一个断点到一个断点的跳跃,而当函数调用完返回的时候next就是一个数一个数的跳跃了。。
一些调试中的小技巧:
操作 命令
断点处自动打印 display /f expression ; undisplay
查看程序中的符号 whatis; ptype
GDB中的代码查看 list; set listsize N
GDB中的Shell操作 shell
技巧示例:断点处自动打印
(gdb) shell gcc -g tricks.c -o test.out //通过shell编译程序
(gdb) file test.out
(gdb) start
(gdb) break test.c:18 //设置断点 18行
list tricks.c:18 ==》查看这行所在函数的函数体(部分)
(gdb) set listsize 20==》设置显示20行
(gdb) show listsize =》查看成功没
(gdb) continue
(gdb) display /d i //十进制打印i
(gdb) display /d i*i //十进制打印i*i
(gdb) display /a &i //打印地址
run 运行 我们的设置还在
undisplay 取消自动打印
自动打印。
技巧示例:符号查看 程序暂停时
(gdb) whatis func
=》type=int()
(gdb) ptype func
=》type=int()
(gdb) whatis g_var
=》type=int
(gdb) ptype g_var
=》type=int
(gdb) whatis struct ST
=>type=struct ST
(gdb) ptype struct ST
=》type=struct ST{ int i; int j; }
info variables 查看全局变量符号 回车查看更多
info functions 查看函数
(gdb) shell cat tricks.c //查看这个文件 ,打印
#include <stdio.h>
int g_var = 1;
struct ST
{
int i;
int j;
};
int func()
{
struct ST st[5] = {0};
int i = 0;
for(i=0; i<5; i++)
{
st[i].i = i;
st[i].j = i * i;
}
for(i=0; i<5; i++)
{
printf("st[%d].i = %d\n", i, st[i].i);
printf("st[%d].j = %d\n", i, st[i].j);
}
}
int main()
{
static c_var = 2;
func();
return 0;
}
shell gedit tricks.c ==》到文件查看
小结:
GDB支持数据断点的设置(一种类型的硬件断点)
watch用于监视变量是否被改变,x用于查看内存中的数据。
GDB支持函数调用栈的查看(backtrace,info frames)
GDB支持运行时对程序中的符号进行查看(whatis,ptype)。