gdb
十月 10, 2022 #gdbgdb
参考自: https://www.cnblogs.com/euphie/p/9781482.html http://blog.chinaunix.net/uid-30038461-id-5136170.html https://blog.csdn.net/faxiang1230/article/details/108848470 https://www.cnblogs.com/arnoldlu/p/9633254.html
1、GDB简介
Linux 包含了一个叫 gdb 的 GNU 调试程序。gdb 是一个用来调试 C 和 C++ 程序的强力调试器,它使你能在程序运行时观察程序的内部结构和内存的使用情况。以下是 gdb 所提供的一些功能:
- 它使你能监视你程序中变量的值;
- 它使你能设置断点以使程序在指定的代码行上停止执行;
- 它使你能一行行的执行你的代码。
在命令行上键入 gdb 并按回车键就可以运行 gdb 了,如果一切正常的话,gdb 将被启动并且你将在屏幕上看到类似的内容:

当你启动 gdb 后,你能在命令行上指定很多的选项。你也可以以下面的方式来运行 gdb:
gdb
当你用这种方式运行 gdb,你能直接指定想要调试的程序,这将告诉 gdb 装入名为 fname 的可执行文件。你也可以用 gdb 去检查一个因程序异常终止而产生的 core 文件,或者与一个正在运行的程序相连。你可以参考 gdb 指南页或在命令行上键入 gdb -h 得到一个有关这些选项的说明的简单列表。
2、GDB配置与.gdbinit的编写
GDB同样支持配置文件,在启动时自动执行配置文件中的命令,有以下几个地方可以控制/etc/gdbinit, ~/.gdbinit, ./.gdbinit。可以通过添加gdb -nx参数来禁止执行配置文件的命令。
通过配置文件对于重复性的调试比较友好,减少很多的重复设置的工作量。
新版的gdb从安全角度进行了一些限制,默认./.gdbinit不会加载,如果需要禁止这个安全功能,可以修改/.gdbinit,添加set auto-load safe-path /。如果只是想加载某个目录下的.gdbinit,可以在/.gdbinit下添加add-auto-load-safe-path /home/linux/project/Demo/linux/gdb/.gdbinit。
当 GDB(即 GNU Project Debugger)启动时,它在当前用户的主目录中寻找一个名为 .gdbinit 的文件;如果该文件存在,则 GDB 就执行该文件中的所有命令。通常,该文件用于简单的配置命令,如设置所需的缺省汇编程序格式(Intel® 或 Motorola)或用于显示输入和输出数据的缺省基数(十进制或十六进制)。它还可以读取宏编码语言,从而允许实现更强大的自定义。该语言遵循如下基本格式:
define
...
end
document
...
end
该命令称为用户命令。可以将所有其他标准 GDB 命令与流控制指令结合使用并向其传递参数,从而创建一种语言,以允许为正在调试的特定应用程序而自定义调试器的行为。
从简单开始:清屏
从简单开始并在此基础上逐步发展始终是个好主意。启动 xterm,调出您最喜欢的编辑器,让我们开始创建一个有用的 .gdbinit 文件吧!调试器产生的输出可能非常零乱,根据个人偏好,在使用任何可能产生混乱的工具时,许多人都希望能够清屏。GDB 没有用于清屏的内置命令,但它可以调用 shell 函数;下面的代码跳到调试器之外以使用cls 命令来清除 xterm 控制台:
define cls
shell clear
end
document cls
Clears the screen with a simple command.
end
此定义的上半部分(在 define … end 动词所界定的范围内)构成了在调用该命令时所执行的代码。
此定义的下半部分(在 document … end 所界定的范围内)由 GDB 命令解释器使用,用于在您键入help cls 时显示与cls 命令关联的文本。
在将该代码键入 .gdbinit 文件以后,调出 GDB 并输入cls 命令。此时屏幕被清除,您所看到的就只有 GDB 提示符。您的 GDB 自定义之旅已经开始了!
3、编译调试代码(Compiling Code for Debugging)
gdb很多功能依赖debug信息,例如根据函数名设置断点,backtrace查看栈桢,info args等功能,所以为了使 gdb 正常工作,你必须使你的程序在编译时包含调试信息。调试信息包含你程序里的每个变量的类型和在可执行文件里的地址映射以及源代码的行号, gdb 利用这些信息使源代码和机器码相关联。
编译源代码时防止优化:-O0 -g,其中 -O0 可以防止编译器优化,特别是对于局部变量或者只读变量, -g 要求生成的文件带有调试信息。
(注:melis SDK要在menuconfig中将编译优化等级选为debug)
4、GDB基本命令
gdb 支持很多的命令使你能实现不同的功能。这些命令从简单的文件装入到允许你检查所调用的堆栈内容的复杂命令,后面列出了你在用 gdb 调试时会用到的一些命令。想了解 gdb 的详细使用请参考 gdb 的指南页。
- 进入GDB: #gdb# test
test是要调试的程序,由gcc test.c -g -o test生成。进入后提示符变为(gdb) 。
- 查看源码: (gdb) l
源码会进行行号提示。
如果需要查看在其他文件中定义的函数,在l后加上函数名即可定位到这个函数的定义及查看附近的其他源码。或者:使用断点或单步运行,到某个函数处使用s进入这个函数。
- 设置断点: (gdb) b 6
这样会在运行到源码第6行时停止,可以查看变量的值、堆栈情况等;这个行号是gdb的行号。
- 查看断点处情况: (gdb) info b
可以键入"info b"来查看断点处情况,可以设置多个断点;
- 运行代码: (gdb) r
- 显示变量值: (gdb) p n
在程序暂停时,键入"p 变量名"(print)即可;
GDB在显示变量值时都会在对应值之前加上 N N",而无需写冗长的变量名;
- 观察变量: (gdb) watch n
在某一循环处,往往希望能够观察一个变量的变化情况,这时就可以键入命令"watch"来观察变量的变化情况,GDB在"n"设置了观察点;
- 单步运行: (gdb) n
- 程序继续运行: (gdb) c
使程序继续往下运行,直到再次遇到断点或程序结束;
- 退出GDB: (gdb) q
4.1 断点调试
| 命令格式 | 例子 | 作用 |
|---|---|---|
| break + 设置断点的行号 | break n | 在n行处设置断点 |
| tbreak + 行号或函数名 | tbreak n/func | 设置临时断点,到达后被自动删除 |
| break + filename + 行号 | break main.c:10 | 用于在指定文件对应行设置断点 |
| break + filename + 函数名 | break main.c:main | 用于在指定文件对应函数入口处设置断点 |
| break + <0x…> | break 0x3400a | 用于在内存某一位置处暂停 |
| break + 行号 + if + 条件 | break 10 if i==3 | 用于设置条件断点,在循环中使用非常方便 |
| info breakpoints/watchpoints [n] | info break 或 info b | n表示断点编号,查看断点/观察点的情况 |
| clear + 要清除的断点行号 | clear 10 | 用于清除对应行的断点,要给出断点的行号,清除时GDB会给出提示 |
| delete + 要清除的断点编号 | delete 3 或 del 3 | 用于清除断点和自动显示的表达式的命令,要给出断点的编号,清除时GDB不会给出任何提示 |
| disable/enable + 断点编号 | disable 3 | 让所设断点暂时失效/使能,如果要让多个编号处的断点失效/使能,可将编号之间用空格隔开 |
| awatch/watch + 变量 | awatch/watch i | 设置一个观察点,当变量被读出或写入时程序被暂停 |
| rwatch + 变量 | rwatch i | 设置一个观察点,当变量被读出时,程序被暂停 |
| catch | 设置捕捉点来补捉程序运行时的一些事件。如:载入共享库(动态链接库)或是C++的异常 | |
| tcatch | 只设置一次捕捉点,当程序停住以后,应点被自动删除 | |
| 断点触发时自动执行命令: |
commands 断点 断点 //选择断点列表,可以是多个
... //执行命令,当为空时表示删除断点触发时执行的命令。
ends
示例:在打印函数位置处入口地址处设置断点,打印字符串的内容

4.2 数据命令
| 命令格式 | 例子 | 作用 |
|---|---|---|
| display +表达式 | display a | 用于显示表达式的值,每当程序运行到断点处都会显示表达式的值 |
| info display | 用于显示当前所有要显示值的表达式的情况 | |
| delete + display 编号 | delete 3 | 用于删除一个要显示值的表达式,被删除的表达式将不被显示 |
| disable/enable + display 编号 | disable/enable 3 | 使一个要显示值的表达式暂时失效/使能 |
| undisplay + display 编号 | undisplay 3 | 用于结束某个表达式值的显示 |
| whatis + 变量 | whatis i | 显示某个表达式的数据类型 |
| print( p ) + 变量/表达式 | p n | 用于打印变量或表达式的值 |
| set + 变量 = 变量值 | set i = 3 | 改变程序中某个变量的值 |
| examine + 地址 | examine 0xxxx | 该命令用来查看内存中地址的值 |
| disassemble + 函数 | disassemble func | 该命令用于反汇编,它可以用来查看当前执行时源代码的机器码 |
- 在使用print命令时,可以对变量按指定格式进行输出,其命令格式为print /变量名 + 格式,其中常用的变量格式:x:十六进制;d:十进制;u:无符号数;o:八进制;c:字符格式;f:浮点数。
- info reg可以显示寄存器内容。

在寄存器名之前加$可以显示寄存器内容,
p $寄存器:显示寄存器内容
p/x $寄存器:十六进制显示寄存器内容。

用x命令可以显示内容内容,“x/格式 地址”。
x $pc:显示程序指针内容
x/i $pc:显示程序指针汇编。
x/10i $pc:显示程序指针之后10条指令。
x/128wx 0xfc207000:从0xfc20700开始以16进制打印128个word。

4.3 调试运行环境相关命令
| 命令格式 | 例子 | 作用 |
|---|---|---|
| set args | set args arg1 arg2 | 设置运行参数 |
| show args | show args | 参看运行参数 |
| set width + 数目 | set width 70 | 设置GDB的行宽 |
| cd + 工作目录 | cd …/ | 切换工作目录 |
| run | r/run | 程序开始执行 |
| step(s) | s | 进入式(会进入到所调用的子函数中)单步执行,进入函数的前提是,此函数被编译有debug信息 |
| stepi(si) | si | 用于进入式单步跟踪一条机器码 |
| next(n) | n | 非进入式(不会进入到所调用的子函数中)单步执行 |
| nexti(si) | ni | 用于非进入式单步跟踪一条机器码 |
| set step-mode | set step-mode on/off | 用于打开/关闭step-mode模式,打开情况下,在进行单步调试时,程序不会因为没有debug信息而不停止 |
| finish | finish | 一直运行到函数返回并打印函数返回时的堆栈地址和返回值及参数值等信息 |
| until + 行数 | u 3 | 运行到函数某一行 |
| continue( c ) | c | 执行到下一个断点或程序结束 |
| return <返回值> | return 5 | 改变程序流程,直接结束当前函数,并将指定值返回 |
| call + 函数 | call func | 在当前位置执行所要运行的函数 |
| 4.4 堆栈相关命令 |
| 命令格式 | 例子 | 作用 |
|---|---|---|
| backtrace/bt | bt | 用来打印栈帧指针 |
| frame | frame 1 | 用于打印指定栈帧 |
| info reg | info reg | 查看寄存器使用情况 |
| info stack | info stack | 查看堆栈使用情况 |
| up/down | up/down | 跳到上一层/下一层函数 |
| backtrace/bt 也可以在该命令后加上要打印的栈帧指针的个数,查看程序执行到此时,是经过哪些函数呼叫的程序,程序“调用堆栈”是当前函数之前的所有已调用函数的列表(包括当前函数)。每个函数及其变量都被分配了一个“帧”,最近调用的函数在 0 号帧中(“底部”帧) |
4.5 跳转执行
- jump: 指定下一条语句的运行点。可以是文件的行号,可以是file:line格式,可以是+num这种偏移量格式。表示着下一条运行语句从哪里开始。相当于改变了PC寄存器内容,堆栈内容并没有改变,跨函数跳转容易发生错误。
4.6 信号命令
- signal: signal SIGXXX,产生XXX信号,如SIGINT。一种速查Linux查询信号的方法:# kill -l
4.7 Catchpoint
catch可以根据某些类型事件来停止程序执行。
可以通过catch syscall close,捕捉产生系统调用close的时候停止程序执行。
其他的catch事件还包括,throw、syscall、assert、exception等等。
4.8 符号加载
有些场景需要我们手动加载符号表,远程调试时gdb server和gdb client不在同一个进程中或者相同的机器上,程序在运行时加载到随机地址。加载符号表有两种方式:file 和 add-symbol-file。
- file /path/to/program :读取程序和符号表到内存中,它本身可以选择被调试的程序,附带的将符号表加载到gdb中,但是它并不能指定地址。
- add-symbol-file /path/to/symbol-file start-addr :将符号表加载到某个位置。
用法: add-symbol-file FILE [-readnow | -readnever] [-o OFF] [ADDR] [-s SECT-NAME SECT-ADDR]… 选项: - FILE:编译好的带调试信息的debug文件。 - ADDR:文件文本的起始地址。 - 每个’-s’参数提供了一个段名和地址,如果数据段和bss段不是与文本连续的,则应该指定。SECT-NAME是在SECT-ADDR中加载的section名。 - OFF:一个可选的偏移量,它被添加到没有指定其他地址的所有节的默认加载地址中。 - '-readnow’选项将导致GDB立即读取整个符号文件。这使得指挥速度变慢,但可能使未来的行动更快。 含义: 从文件中加载符号表,假设文件已经动态加载。
4.9 运行shell命令
- 如(gdb)shell ls来运行ls
4.10 更多程序运行选项和调试
- 程序运行参数
set args 可指定运行时参数。(如:set args 10 20 30 40 50) show args 命令可以查看设置好的运行参数。
- 运行环境
path 可设定程序的运行路径。 show paths 查看程序的运行路径。 set environment varname [=value] 设置环境变量。如:set env USER=hchen show environment [varname] 查看环境变量。
- 工作目录
cd 相当于shell的cd命令。 pwd 显示当前的所在目录。
- 程序的输入输出
info terminal 显示你程序用到的终端的模式。 使用重定向控制程序输出。如:run > outfile tty命令可以指写输入输出的终端设备。如:tty /dev/ttyb
- 调试已运行的程序
两种方法: (1) 在UNIX下用ps查看正在运行的程序的PID(进程ID),然后用gdb PID格式挂接正在运行的程序。 (2) 先用gdb 关联上源代码,并进行gdb,在gdb中用attach命令来挂接进程的PID。并用detach来取消挂接的进程。
- 暂停 / 恢复程
序运行当进程被gdb停住时,你可以使用info program 来查看程序的是否在运行,进程号,被暂停的原因。 在gdb中,我们可以有以下几种暂停方式:断点(BreakPoint)、观察点(WatchPoint)、捕捉点(CatchPoint)、信号(Signals)、线程停止(Thread Stops) ,如果要恢复程序运行,可以使用c或是continue命令。
- 线程(Thread Stops)
如果程序是多线程,可以定义断点是否在所有的线程上,或是在某个特定的线程。 break thread break thread if ... linespec指定了断点设置在的源程序的行号。threadno指定了线程的ID,注意,这个ID是GDB分配的,可以通过“info threads”命令来查看正在运行程序中的线程信息。如果不指定thread 则表示断点设在所有线程上面。还可以为某线程指定断点条件。如: (gdb) break frik.c:13 thread 28 if bartab > lim 当你的程序被GDB停住时,所有的运行线程都会被停住。这方便查看运行程序的总体情况。而在你恢复程序运行时,所有的线程也会被恢复运行。
4.11 调试core文件
(1)Core Dump: Core的意思是内存,Dump的意思是扔出来,堆出来。开发和使用Unix程序时,有时程序莫名其妙的down了,却没有任何的提示(有时候会提示core dumped),这时候可以查看一下有没有形如core.进程号的文件生成,这个文件便是操作系统把程序down掉时的内存内容扔出来生成的, 它可以做为调试程序的参考
(2)生成Core文件
一般默认情况下,core file的大小被设置为了0,这样系统就不dump出core file了。修改后才能生成core文件。
ulimit -c unlimited ulimit unlimited
这些需要有root权限,在ubuntu下每次重新打开中断都需要重新输入上面的第一条命令,来设置core大小为无限。
core文件生成路径:输入可执行文件运行命令的同一路径下。若系统生成的core文件不带其他任何扩展名称,则全部命名为core。新的core文件生成将覆盖原来的core文件。
1)/proc/sys/kernel/core_uses_pid 可以控制core文件的文件名中是否添加pid作为扩展。文件内容为1,表示添加pid作为扩展名,生成的core文件格式为core.xxxx;为0则表示生成的core文件统一命名为core。
可通过以下命令修改此文件:
echo "1" > /proc/sys/kernel/core_uses_pid
2)proc/sys/kernel/core_pattern可以控制core文件保存位置和文件名格式。
可通过以下命令修改此文件:
echo "/corefile/core-%e-%p-%t" > core_pattern,可以将core文件统一生成到/corefile目录下,产生的文件名为core-命令名-pid-时间戳
以下是参数列表:
%p - insert pid into filename 添加pid %u - insert current uid into filename 添加当前uid %g - insert current gid into filename 添加当前gid %s - insert signal that caused the coredump into the filename 添加导致产生core的信号 %t - insert UNIX time that the coredump occurred into filename 添加core文件生成时的unix时间 %h - insert hostname where the coredump happened into filename 添加主机名 %e - insert coredumping executable name into filename 添加命令名
(3)用gdb查看core文件
发生core dump之后,用gdb进行查看core文件的内容,以定位文件中引发core dump的行。
gdb [exec file] [core file]
如:gdb ./test core
或 gdb ./a.out
core-file core.xxxx
gdb后,用bt命令backtrace或where查看程序运行到哪里,来定位core dump的文件->行。
待调试的可执行文件,在编译的时候需要加-g,core文件才能正常显示出错信息。
1)gdb -core=core.xxxx
file ./a.out
bt
2)gdb -c core.xxxx
file ./a.out
bt
(4)用gdb实时观察某进程crash信息
启动进程
gdb -p PID
c
运行进程至crash
gdb会显示crash信息
bt
(5)gdb dump 进程内存
- 获取目标进程的虚拟地址映射
$ cat /proc/[pid]/maps
root@x86_64: ## cat /proc/2436/maps |grep vdso
7fffd91f3000-7fffd91f5000 r-xp 00000000 00:00 0 [vdso]
- dump内存地址
#### commands: dump memory file start-addr end-addr
(gdb) dump memory /home/linux/dump 0x7fffd91f3000 0x7fffd91f5000
- 查看二进制文件
使用vi打开二进制文件分析
vi dump
:%!xxd
使用hexdump查看二进制文件
hexdump -C dump
4.12 TUI调试
TUI(TextUser Interface)为GDB调试的文本用户界面,可以方便地显示源代码、汇编和寄存器文本窗口。命令:layout 或 focus。
- layout:用于分割窗口,可以一边查看代码,一边测试。主要有以下几种用法:
- layout src:显示源代码窗口
- layout asm:显示汇编窗口
- layout regs:显示源代码/汇编和寄存器窗口
- layout split:显示源代码和汇编窗口
- layout next:显示下一个layout
- layout prev:显示上一个layout
- Ctrl + L:刷新窗口
- Ctrl + x,再按1:单窗口模式,显示一个窗口
- Ctrl + x,再按2:双窗口模式,显示两个窗口
- Ctrl + x,再按a:回到传统模式,即退出layout,回到执行layout之前的调试窗口。
源代码窗口和汇编窗口会高亮显示程序运行位置并以’>'符号标记。有两个特殊标记用于标识断点,第一个标记用于标识断点类型:
B:程序至少有一次运行到了该断点 b:程序没有运行到过该断点 H:程序至少有一次运行到了该硬件断点 h:程序没有运行到过该硬件断点
第二个标记用于标识断点使能与否:
+:断点使能Breakpointis enabled. -:断点被禁用Breakpointis disabled.
当调试程序时,源代码窗口、汇编窗口和寄存器窗口的内容会自动更新。

5、GDB基本使用方法
GDB调试的三种方式:
- 目标板直接使用GDB进行调试。(本地调试)
- 目标板使用gdbserver,主机使用xxx-linux-gdb作为客户端。(远程调试)
- 目标板使用ulimit -c unlimited,生成core文件;然后主机使用xxx-linux-gdb ./test ./core。
5.1 远程调试
有些场景下需要进行远程调试,目标机上不具备gdb运行的条件和环境,例如嵌入式设备上内存,存储有限,需要远程调试,最典型的是 android 系统和应用调试,整个android的源码加上编译出来的中间文件有上百G,一般的手机上满足不了调试的需求。
服务端
服务端通常是通过 gdbserver 直接控制目标程序的运行并监听来自 gdb client 的请求,一般是通过网络端口来监听,还可以通过串口设备的直连进行通信。
gdbserver64 <host_ip>:6000 ./a.out //从程序的启动开始调试,并监听本机6000端口
gdbserver64 <host_ip>:6000 --attach $(pid) //附着到已经运行的程序上,并监听本机6000端口
(其实该步操作相当于平头哥软件连接我们的本机端口)
也可以如下,通过串口
gdbserver /dev/ttyS0 ./a.out
客户端
客户端通过需要首先连接到 gdbserver,之后还可能需要做一些其他的设置之后才能正常调试。
(gdb) target remote :6000
(gdb) file 带符号表的文件 //加载符号表,如 file ekernel/melis30.elf
(gdb) set solib-search-path $KERNEL_OUTPUT //动态库的根路径
(gdb) set substitute-path /home/linux/kernel /root/mykernel //设置源文件的路径,在 /home/linux/kernel 中编译,但是现在位于/root/mykernel
//加载该目录下的所有文件
(gdb) info sharedlibrary //用 info sharedlibrary来查看目标库文件的符号表是否加载成功
(如果是在远程服务器上运行gdb,需要在端口号前加本机的ip地址, 如target remote 192.168.204.19:6000)
至此,客户端和服务端连接到相同的端口号,建立起了网络通信。
对于android上的远程调试还需要一些特殊设置,adnroid设备和 host 一般是通过 adb 连接的,所以在 gdb client 连接 gdb server时还需要做一些特殊设置:
- 通过 adb ip 方式连接 android 设备:adb connect $(ip)
- 在 android 上开启 gdb server : gdbserver64 :6000 --attach $(pid)
- host机器上通过adb进行端口转发设置:adb forward tcp:6000 tcp:6000 ,监听 host 上6000端口的请求并转发给 android 上 6000 端口
- gdb client 连接 gdb server: (gdb) target remote :6000
GDB/GDBSERVER远程调试模型的原理如下:

在GDB/GDBSERVER调试模型中,GDBSERVER是一个轻量级的GDB调试器,在调试过程中担任着调试代理的角色。在调试过程中,主机和目标机之间使用串口或者网络作为通信的通道。在主机上GDB通过这条通道使用一种基于ASCII的简单通讯协议RSP与在目标机上运行的GDBSERVER进行通讯。GDB发送指令,如内存、寄存器读写,GDBSERVER则首先与运行被调试程序映像的进程进行绑定,然后等待GDB发来的数据,对包含命令的数据包进行解析之后便进行相关处理,然后将结果返回给主机上的GDB。
RSP协议将GDB/GDBSERVER间通讯的内容更看做是数据包,数据包的内容都使用ASCII字符。每一个数据包都遵循这样的格式:$ <调试信息>#<校验码>。

如上图所示,包的内容会以16进制的形式来编码(enhex),#后面的两位数字是校验码,具体的计算方式是数据包中所有字符求和再用256求余数。而数据包的内容,也就是RSP协议的载体,将会是gdb接收的命令。接受方在收到数据包之后,对数据包进行校验,若正确回应“+”,反之回应“-”。
当主机使用gdb对运行应用的进程进行调试时,进程中内置的gdbserver与应用进行绑定,开启socket进行监听client连接,默认是tcp:3333端口,可以使用--gdb tcp:6000选择任意端口。当client发送请求时,gdbserver收到包进行校验之后gdb_handle_packet()进行分发处理。如gdb调试端发送x/ <n/f/u>


5.2 本地调试

推荐阅读
gdb在线文档:https://sourceware.org/gdb/current/onlinedocs/gdb/
5.3 应用程序调试
在嵌入式系统中,为调试Linux应用程序,可在目标板上先运行GDBServer,再让主机上的GDB与目标板上的GDBServer通过网口或串口通信。
(1)目标板
需要运行如下命令启动GDBServer:
gdbserver <host_ip>:端口号 app
<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像调试本机上的程序一样调试目标机上的程序 …
5.4 Linux内核调试
调试嵌入式Linux内核的方法如下:
- 目标机“插桩”,如打上KGDB补丁,这样主机上的GDB可与目标机的KGDB通过串口或网口通信;
- 使用仿真器,仿真器可直接连接目标的JTAG/BDM,这样主机GDB就可以通过与仿真器的通信来控制目标机;
- 在目标板上通过printk(),oops,strace等软件方法进行“观察”调试。
内核打印信息:
在Linux中,内核打印语句printk()会将内核信息输出到内核信息缓冲区,内核信息缓冲区是一个环形缓冲区(ring buffer),因此,如果塞入的消息过多,就会将之前的消息冲刷掉。
printk()定义了8个消息级别,分别为0-7,越低级别(数值越大)的消息越不重要,第0级是紧急事件级,printk()级别定义如下:
KERN_EMERG "<0>" /system is unusable
KERN_ALERT "<1>" /action must be taken immediately
KERN_CRIT "<2>" /critical conditions
KERN_ERR "<3>" /error conditions
KERN_WARNING "<4>" /warning conditions
KERN_NOTICE "<5>" /normal but significant condition
KERN_INFO "<6>" /informational
KERN_DEBUG "<7>"
通过/proc/sys/kernel/printk文件可以调节printk的输出等级。
控制台日志级别:优先级高于该值的消息将被打印至控制台 默认的消息日志级别 最低控制台日志级别 默认的控制台日志级别 上述4个值的默认值设置为6,4,1,7
在设备驱动中我们经常需要输出调试或系统的信息,可以直接采用printk(<7>...)输出
5.5 内核调试器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