11、调试利器GDB

什么是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 = 0
g_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)。


猜你喜欢

转载自blog.csdn.net/ws857707645/article/details/80885683
今日推荐