Chinaunix首页 | 论坛 | 博客
  • 博客访问: 334006
  • 博文数量: 86
  • 博客积分: 0
  • 博客等级: 民兵
  • 技术积分: 185
  • 用 户 组: 普通用户
  • 注册时间: 2014-11-25 17:14
个人简介

代码才是最叼的

文章分类

全部博文(86)

文章存档

2019年(1)

2018年(1)

2017年(9)

2016年(19)

2015年(55)

2014年(1)

分类: LINUX

2015-07-27 11:24:42

GDB基本用法

GDB是GNU开源组织发布的一项强大的UNIX下的程序调试工具,GDB主要完成下面4个方面的功能

1.启动程序,可以按照工程师自定义的要求运行程序

2.让被调试的程序在工程师的指定端点处停住,断点可以是条件表达式

3.当程序被停止时,可以检查此程序中所发生的事,并追踪上文

4.动态地改变程序的执行环境

不管是调试Linux内核空间的驱动程序还是调试用户空间的应用程序,掌握GDB的用法都是必须的,而且,调式内核和调试应用程序的GDB命令式完成相同的

下面以一个应用程序来演示GDB调试器的用法:

#include
int add(int a,int b)
{
  return a+b;
}

main()
{
  int sum[10] = {0};
  int i;

  int array1[10] =
  {
    48,56,77,33,33,11,226,544,78,90
  };
  int array2[10] =
  {
    85,99,66,0x199,393,11,1,2,3,4
  };

  for(i = 0;i < 10;i++)
  {
    sum[i] = add(array1[i],array2[i]);
    printf("TQ2440 : %d",sum[i]);
  }
}

在linux中使用命令gcc -g gdb.c  -o gdbb来编译上面程序,将得到gdbb二进制进行文件,然后执行gdb gdbb命令进入调试状态如下:

root@www:/opt/qudong/qd13# gdb gdbb
GNU gdb (GDB) 7.1-ubuntu
Copyright (C) 2010 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later < and "show warranty" for details.
This GDB was configured as "i486-linux-gnu".
For bug reporting instructions, please see:
<
(gdb) next
21   for(i = 0;i < 10;i++)
(gdb) next
23     sum[i] = add(array1[i],array2[i]);
(gdb) print sum
$1 = {133, 0, 0, 0, 0, 0, 0, 0, 0, 0}
(gdb)
2.run命令

在GDB中,运行程序用run命令,在程序运行之前,我们可以设置如下方面的工作环境

(1)程序运行参数

set args可指定运行时参数,如set args 10 2 0 20 30;show args命令可以查看设置好的参数

(2)运行环境

path

可以设置程序的运行路径,show paths可以查看程序的运行路径(跟linux命令中的echo $PATH差不多)


(3)工作目录

cd

相当于shell的cd命令,pwd显示当前所在目录


(4)程序输出

info terminal用于显示程序用到的终端模式

3.break命令

在GDB中用break命令来设置断点,设置断点方法如下:

(1)break ,指定在函数function处设置断点(停止)

(2)break ,指定行号停止

(3)break+offset / break - offset ,在当前行号前面或后面的offset行停止

(4)break filename:linenum在源文件filename的linenum行处停止

(5)break filename:function,在源文件filename的function函数入口处停止

(6)break *address,在程序运行的内存地址处停止

(7)break,没有指定参数,表示在下一条指令处停止

 (8)break ... if,其中...可以是上述的break ,break+offset / break - offset,break 等,在条件成立时停止

4.单步命令

(1)step 单步跟踪,如果由函数调用,则进入该函数,step后面不加count 表示一条条地执行

(2)next 单步跟踪,如果由函数调用,它不会进入该函数,同样地,next后面不加count表示一条条地执行

(3)set step-mode,set step-mode on用于打开step-mode模式,这样,在进行单步调试时,程序不会因为没有debug信息而不停止,set step-mode off用于关闭step-mode模式

(4)finish,运行程序,直到当前函数完成返回

(5)until(缩写u),一直在循环体内执行单步,不会退出来

(6)stepi(缩写si)和nexti(缩写ni),用于单步跟踪一条机器码

5.continue命令

当程序被停止后,可以使用continue命令恢复程序的运行直到程序结束或到达下一个断点

6.print命令

在调试程序时,当程序被停止时,可以使用print命令(缩写p),来查看当前程序的运行数据,如下:

(gdb) print sum

$3 = {133, 0, 0, 0, 0, 0, 0, 0, 0, 0}

(gdb) next


Breakpoint 1, add (a=56, b=99) at gdb.c:4

4 return a+b;

(gdb) next

5 }

(gdb) next

main () at gdb.c:24

24 printf("TQ2440 : %d",sum[i]);

(gdb) next

21 for(i = 0;i < 10;i++)

(gdb) next

23 sum[i] = add(array1[i],array2[i]);

(gdb) print sum

$4 = {133, 155, 0, 0, 0, 0, 0, 0, 0, 0}

(gdb) 

7.watch命令
watch一般用来观察某个表达式的值是否有变化,如果有变化,马上停止程序执行(比如程序中的i等)
8.examine命令
该命令用来查看内存中地址的值
9.jump命令
一般来说,被调试的程序会按照程序代码的运行顺序依次执行,但是GDB也提供了乱序执行的功能,从而可以让程序随意跳跃
10.signal命令
使用singal命令,可以产生一个信号给调试程序,如中断信号,这非常方便程序的调试,signal的语法为signal
11.return命令
如果在函数中设置了调试断点,在断点后还有语句没有被执行,这是可以调用return命令来强制函数忽略没有执行的语句返回
return //立即返回
return
12.call命令
用于强制调用某函数
call
13.info命令
info命令可以在调试时查看寄存器,断点,观察点和信号等信息
info registers (查看除了否点寄存器以外的寄存器)
info all-registers(查看所有寄存器)
info break(查看断点)
info watchpoints(查看当前社会自的观察点)
info signal(查看那些信号被gdb检测)
14.disassemble命令
disassemble命令用与反汇编,它可以用来查看当前执行时源代码的机器码
disassemble func
(gdb) disassemble add
Dump of assembler code for function add:
   0x08048374 <+0>: push   ?p
   0x08048375 <+1>: mov    %esp,?p
   0x08048377 <+3>: mov    0xc(?p),?x
   0x0804837a <+6>: add    0x8(?p),?x
=> 0x0804837d <+9>: pop    ?p
   0x0804837e <+10>: ret    
End of assembler dump.
(gdb)
 
Linux内核调试
调试嵌入式Linux内核的方法如下:
1.目标机“插桩”,如打上KGDB补丁,这样主机上的GDB可与目标机的KGDB通过串口或网口通信
2.使用仿真器,仿真器可直接连接目标的JTAG/BDM,这样主机GDB就可以通过与仿真器的通信来控制目标机
3.在目标板上通过printk(),oops,strace等软件方法进行“观察”调试
内核打印信息
在Linux中,内核打印语句printk()会将内核信息输出到内核信息缓冲区,内核信息缓冲区是一个环形缓冲区(ring buffer),因此,如果塞入的消息过多,就会将之前的消息冲刷掉
printk()定义了8个消息级别,分别为0-7,越低级别(数值越大)的消息越不重要,第0级是紧急事件级,printk()级别定义如下:
#define KERN_EMERG "<0>" /system is unusable   
#define KERN_ALERT "<1>" /action must be taken immediately 
#define KERN_CRIT "<2>" /critical conditions   
#define KERN_ERR "<3>" /error conditions   
#define KERN_WARNING "<4>" /warning conditions   
#define KERN_NOTICE "<5>" /normal but significant condition 
#define KERN_INFO "<6>" /informational   
#define KERN_DEBUG "<7>"
通过/proc/sys/kernel/printk文件可以调节printk的输出低级
控制台日志级别:优先级高于该值的消息将被打印至控制台
默认的消息日志级别
最低控制台日志级别
默认的控制台日志级别
上述4个值的默认值设置为6,4,1,7
在设备驱动中我们经常需要输出调试或系统的信息,可以直接采用printk(<7>...)输出
 
使用/proc
在Linux系统中,/proc文件系统十分有用,它用于内核向用户导出信息,而且Linux中很多命令本身通过分析/proc下的文件来完成的,如ps,top等
在Linux系统中,可以用如下函数创建/proc节点:
static inline struct proc_dir_entry *create_proc_entry(const char *name,
 mode_t mode, struct proc_dir_entry *parent);
static inline struct proc_dir_entry *create_proc_read_entry(const char *name,
 mode_t mode, struct proc_dir_entry *base,
 read_proc_t *read_proc, void * data);
create_proc_entry()函数用于创建/proc节点,而create_proc_read_entry()调用create_proc_entry()创建只读的/proc节点,参数name为/proc节点的名称,mode_t 为创建了节点(文件)的权限,parent/base为父目录节点,如果为NULL。则指proc目录
下列函数用于创建/proc目录
struct proc_dir_entry *proc_mkdir(const char *name,struct proc_dir_entry *parent);
下面是上述两个创建目录和创建节点的一个例子
//创建/proc下的目录
example_dir = proc_mkdir("procfs_example",NULL);
if(ecample_dir == NULL){
rv = -ENOMEM;
goto out;
}
 
example_dir->owner = THIS_MODULE;
 
//创建一个例子/proc文件
example_file = create_proc_entry("example_file",0666,example_dir);
if(example_file == NULL){
rv = -ENOMEM;
goto out;
}
 
...
作为上述函数各返回值proc_dir_entry结构体中包含了/proc节点的度汗还是指针(read_proc_t *read_proc),写函数指针(write_proc_t *write_proc)以及父节点,子节点信息等.
Linux系统中可用如下函数删除/proc节点
void remove_proc_entry(const char *name,struct proc_dir_entry *parent);
 
监视工具
在Linux系统中,strace是一个相当有效的跟踪工具,它的主要特点是可以被用来监视系统调用,我们不仅可以用strace调试一个新开始的程序,也可以调试一个已经在运行的程序,它会得到各调用函数的返回值,如打开函数open(),成功时它会返回一个3(不一定是3),这时我们就可以通过这个返回值就可以知道函数成功与否(之后对fd为3的文件会就行的read(),write()等系统调用)。
 
内核调试器
kcore
GDB调试器可以把内核作为一个应用程序来调试,在这种方式中,需要给GDB指定未经压缩的内核镜像的文件名和"core"文件,对于一个正在运行的内核,"core文件"就是运行时的内存映像/proc/kcore,因此,使用GDB和kcore调试内核的典型命令如下:
gdb /usr/src/linux/vmlinux /proc/kcore
在gdb /vmlinux /proc/kcore这种调试方式中,GDB的绝大多数功能都不能使用,如修改内核变量的值,设置断点,单步执行等,可加载模块的symbol并没有包含在vmlinux中,必须使用一些辅助方法才能调试模块,Linux可加载模块是ELF格式的可执行映像,它们被分成几个段
.text:这个段包含模块可执行代码
.bss/.data:这两个段包含模块的变量,在编译时未初始化的变量在.bss段,而被初始化的段在.data段
当一个模块被加载后,在/sys/module目录下会新增一个对应模块的目录,下面是一个例子
[root@cgyl2010 ~]#ls
New0001.ko   devgecho     hello.c      myhello      signal.ko    wed
backlight    etc          hello.ko     myled.ko     sixqd.ko     yg.ko
bin          fb           home         myzd         sy           yubu
cgy          fb_test      leds         opt          sys          yueyi.ko
cgyled       fost.ko      lib          poll         thirdzd.ko   yy.ko
cs           gui.ko       linuxrc      proc         tmp          zd
dev          gy           mnt          root         usr
devg         hello        myanjian.ko  sbin         var
[root@cgyl2010 ~]#insmod yueyi.ko
[root@cgyl2010 ~]#cd /sys/module/yueyi/
[root@cgyl2010 /sys/module/yueyi]#
[root@cgyl2010 /sys/module/yueyi/sections]#ls -a
                         .devexit.text
..                         .devinit.text
.ARM.exidx                 .gnu.linkonce.this_module
.ARM.exidx.devexit.text    .note.gnu.build-id
.ARM.exidx.devinit.text    .rodata
.ARM.extab                 .rodata.str1.1
.ARM.extab.devexit.text    .strtab
.ARM.extab.devinit.text    .symtab
.bss                       .text
.data
[root@cgyl2010 /sys/module/yueyi/sections]#
通过cat其中的.text,.bss,.data可以得到我们想要的3个段的地址,如下:
[root@cgyl2010 /sys/module/yueyi/sections]#cat .text
0xbf006000
[root@cgyl2010 /sys/module/yueyi/sections]#cat .bss
0xbf007568
[root@cgyl2010 /sys/module/yueyi/sections]#cat .data
0xbf0073e4
[root@cgyl2010 /sys/module/yueyi/sections]#
之后就可以通过GDB的add-symbol-file来添加模块的符号信息,这样之后便可以查看模块中的变量了,如下;
(gdb)  add-symbol-file yueyi.ko 0xbf006000 -s .bss 0xbf007568 -s .data 0xbf0073e4
KDB
KDB项目由Silicon Graphics维护,为使内核内嵌KDB,需要从Silicon Graphics的FTP站点下载与内核版本有关的补丁
在两种情况下KDB会被调用,当KDB处于打开状态时,只要内核中紧急情况就会自动调用,其次,按下键盘上的"Pause"健时也可以手工调用KDB。使用KDB可进行内存和寄存器的修改,设置断点和跟踪堆栈等,克服部分上述GDB的不足。
KDB中进行内存显示和修改的常用命令是md和mm
kdb > md 0xc0000000 15 //显示从0xc0000000开始的15行内存
kdb > mm 0xc0000000 0x10 //将内存0xc0000000上的内容改为0x10
rd和rm命令分别用于显示寄存器的内容和修改寄存器的内容
kdb > bp sys_write //将对函数sys_write()设置断点
kdb > bl //列出断点列表中的断点
kdb > bc 1//清除断点号为1的断点
id命令:以地址/符号作为参数,它对从该地址开始的指令进行反汇编
go命令:让系统继续正常执行,
reboot命令用于冲虚系统
 
应用程序调试
在嵌入式系统中,为调试Linux应用程序,可在目标板上先运行GDBServer,再让主机上的GDB与目标板上的GDBServer通过网口或串口通信
1.目标板
需要运行如下命令启动GDBServer
gdbserver :
:为主机的IP地址和端口,app是可执行的应用程序名
也可以如下,通过串口
gdbserver /dev/ttyS0 ./tdemo
2.主机
需要运行如下命令启动GDB
arm-linux-gdb
app与gdbserver中的app参数对应
之后,运行如下命令就可以款姐到目标板
target remote :
:为目标机的IP地址和端口
如果是串口
(gdb)target remote /dev/ttyS1
之后就可以使用GDB像调试本机上的程序一样调试目标机上的程序
 
....
阅读(2286) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~