gdb 调试ko驱动 转载

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 <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 "i486-linux-gnu".
For bug reporting instructions, please see:
<http://www.gnu.org/software/gdb/bugs/>...
Reading symbols from /opt/qudong/qd13/gdbb...done.
(gdb)
1.list命令

在GDB中运行list命令(缩写l)可以列出代码

list ,显示程序linenum行周围的代码,如下:

(gdb) list 15
10   int i;
11 
12   int array1[10] =
13   {
14     48,56,77,33,33,11,226,544,78,90
15   };
16   int array2[10] =
17   {
18     85,99,66,0x199,393,11,1,2,3,4
19   };
(gdb)
list ,显示函数名为function的函数源程序,如下:

(gdb) list main
3 {
4   return a+b;
5 }

7 main()
8 {
9   int sum[10] = {0};
10   int i;
11 
12   int array1[10] =
(gdb)
下面演示使用GDB中的run(缩写r),break(缩写b设置断点),next(缩写n但不进行)命令控制程序的运行,并使用print(缩写P)命令打印程序中的变量sum过程,如下:

(gdb) break add //在add函数处设置断点
Breakpoint 1 at 0x8048377: file gdb.c, line 4.
(gdb) run
Starting program: /opt/qudong/qd13/gdbb

Breakpoint 1, add (a=48, b=85) 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
$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 <host_ip>:
<host_ip>:为主机的IP地址和端口,app是可执行的应用程序名
也可以如下,通过串口
gdbserver /dev/ttyS0 ./tdemo
2.主机
需要运行如下命令启动GDB
arm-linux-gdb
app与gdbserver中的app参数对应
之后,运行如下命令就可以款姐到目标板
target remote <target_ip>:
<target_ip>:为目标机的IP地址和端口
如果是串口
(gdb)target remote /dev/ttyS1
之后就可以使用GDB像调试本机上的程序一样调试目标机上的程序
posted @ 2019-09-20 08:35  eastgeneral  阅读(4523)  评论(0编辑  收藏  举报