编译程序时需要加上-g,之后才能用gdb进行调试:gcc -g main.c -o main
gdb中命令:
回车键:重复上一命令
(gdb)help:查看命令帮助,具体命令查询在gdb中输入help + 命令,简写h
(gdb)run:重新开始运行文件(run-text:加载文本文件,run-bin:加载二进制文件),简写r
(gdb)start:单步执行,运行程序,停在第一执行语句
(gdb)list:查看原代码(list-n,从第n行开始查看代码。list+ 函数名:查看具体函数),简写l
(gdb)set:设置变量的值
(gdb)next:单步调试(逐过程,函数直接执行),简写n
(gdb)step:单步调试(逐语句:跳入自定义函数内部执行),简写s
(gdb)backtrace:查看函数的调用的栈帧和层级关系,简写bt
(gdb)frame:切换函数的栈帧,简写f
(gdb)info:查看函数内部局部变量的数值,简写i
(gdb)finish:结束当前函数,返回到函数调用点
(gdb)continue:继续运行,简写c
(gdb)print:打印值及地址,简写p
(gdb)quit:退出gdb,简写q
(gdb)break+num:在第num行设置断点,简写b
(gdb)info breakpoints:查看当前设置的所有断点
(gdb)delete breakpoints num:删除第num个断点,简写d
(gdb)display:追踪查看具体变量值
(gdb)undisplay:取消追踪观察变量
(gdb)watch:被设置观察点的变量发生修改时,打印显示
(gdb)i watch:显示观察点
(gdb)enable breakpoints:启用断点
(gdb)disable breakpoints:禁用断点
(gdb)x:查看内存x/20xw 显示20个单元,16进制,4字节每单元
(gdb)run argv[1] argv[2]:调试时命令行传参
(gdb)set follow-fork-mode child#Makefile项目管理:选择跟踪父子进程(fork())
core文件:先用$ ulimit -c 1024 开启core,当程序出错会自动生成core文件。调试时 gdb a.out core
ctrl+c:退出输入
首先介绍下GDB常用命令
流程
start <args>或run <args>:bin程序开始执行
c(continue):从断点开始继续执行,直到下一断点或结束
n(next):执行下一行(跳过函数)
s(step):执行下一行(进入函数)
finish:运行到当前函数结束退出
运行状态下ctrl-c:暂停
q(quit),或暂停状态下ctrl-c:退出
断点
b <func-name>:在函数上打断点
b * <address>:在代码段地址上打断点
i b:查看断点
d b <N>:禁用指定编号的断点
e b <N>:启用指定编号的断点
b ... if <expr>:条件断点:若<expr>为真(或非NULL),在断点处暂停
临时进入shell模式
shell(进入)、exit(退出)
帮助:help或?
查看信息
bt(backtrace):查看调用栈
i r:查看寄存器信息
x/Ns:以字符串形式查看指定变量/地址
x/Nd:以十进制数字形式查看指定变量/地址
x/Nx:以十六进制形式查看指定变量/地址
x/Ni:查看指定函数/地址的汇编指令
p <expr>:查看<expr>的值
x/b &str:查看全局变量的字符串,str是一个全局字符串
其它
暂停状态下回车:执行上一条命令
支持上翻、下翻
设置打印字符的个数为不限制数量,设置为unlimited或者zero。
(gdb) set print elements 0
(gdb) show print elements
Limit on string chars or arry elemtns to print is unlimited
CPU寄存器信息
mips:
zero:永远为0
v0/v1:临时寄存器;函数返回时存返回值
a0 ~ a3:临时寄存器;调用函数时存参数
t0 ~ t9:临时寄存器。t9常用于临时存要调用的子函数入口地址
s0 ~ s8:非易失性寄存器
gp:全局指针
sp:栈顶指针
ra:当前函数返回地址
pc:当前指令
x86:
r15 = 0x0000000000000007 r14 = 0x0000000000000000
r13 = 0x0000000000000000 r12 = 0xffffc9005dd6b2b0
bp = 0xffff8800462375e8 bx = 0xffff8800463780a0 (这里的bp寄存器和上面提到的%rbp寄存器是等价的)
r11 = 0x0000000000000000 r10 = 0xffffc9005dd735b0
r9 = 0x0000000000000002 r8 = 0xffffc9005dd67130
ax = 0x0000000004230423 cx = 0xffffc90061f9b6d3
dx = 0x0000000000000095 si = 0x0000000000000000
di = 0xffffc9005dd67008 orig_ax = 0xffffffffffffffff
ip = 0xffffffffc224156c cs = 0x0000000000000010
flags = 0x0000000000010246 sp = 0xffff880046237570
ss = 0x0000000000000018 ®s = 0xffff8800462374d8
2) SP寄存器指向当前调用栈的栈顶,通过md命令可查看调用栈的内容,而IP寄存器里存储的是正在执行的函数地址
0xffff880046237580 ffff880046237696 ffff880046378355 .v#F....U.7F....
0xffff880046237590 0000000000000000 ffffc9005dd7772e .........w.]....
0xffff8800462375a0 ffff8800462376e4 ffff880046378356 .v#F....V.7F....
0xffff8800462375b0 ffff880046378010 0000000042dd1653 ..7F....S..B....
0xffff8800462375c0 0000000000000001 0000000000000000 ................
0xffff8800462375d0 0000000000000000 0000000000000346 ........F.......
0xffff8800462375e0 0000000000000000 // 3)BP寄存器指向的地址(0xffff8800462375e8),存储了前一个函数栈的BP和返回地址。ffff880046237638 是前一个函数栈的BP值, ffffffffc22427a9是函数返回地址。
0xffff8800462375f0 ffffffffc22427a9
fa_traverse+0x69 // 4)通过函数返回地址得到前一个调用函数名称fa_traverse。
0xffff880046237600 ffff880046237688 0000000000000000 .v#F............
0xffff880046237610 0000000000000000 0000000000000000 ................
0xffff880046237620 0000000000000000 ffffffffc31ff918 ................
0xffff880046237630 ffffc9005dd67000 // 5)同时,我们得到了fa_traverse函数的BP寄存器的值,它指向的地址(ffff880046237638)同样存储了前一个函数的BP和返回地址。
0xffff880046237640 ffffffffc22428ed // 6)以此类推, 接着向下推栈即可。
regex_traverse+0x7d
0000000000000000 .($.............
0xffff880046237650 ffff880046237680 ffff880046237690 .v#F.....v#F....
0xffff880046237660 ffff8800462376e4 0000000000000000 .v#F............
[12]kdb>
0xffff880046237670 ffff880046237688 0000000000000000 .v#F............
0xffff880046237680 0000000000000000 0000000000000001 ................
0xffff880046237690 0000000000000000 0000000000000000 ................
0xffff8800462376a0 ffffffffc31ff660 ffff880029099c00 `..........)....
0xffff8800462376b0 0000000000000000 0000000000000010 ................
0xffff8800462376c0 0000000000000000
ffff8800462376f8 .........v#F....
0xffff8800462376d0 ffffffffc223f533
pcre_engine_regex_check+0x43
ffffffffc210f000 3.#.............
0xffff8800462376e0 00003a98c31ff660 ffff880029099c00 `....:.....)....
0xffff8800462376f0 ffffffffc31ff660
ffff880046237778 `.......xw#F....
0xffff880046237700 ffffffffc237dea1
dcdm_pcre_new_single_rule_proc+0xf1
0000020000000200 ..7.............
0xffff880046237710 0000000000000000 ffff880029099c30 ........0..)....
0xffff880046237720 ffffe90142dd16f0 ffffe90142dd16d8 ...B.......B....
0xffff880046237730 ffffffffc31ff918 0000000000000000 ................
0xffff880046237740 ffff880029099c1c ffff8800462377a8 ...).....w#F....
0xffff880046237750 0000000000000000 ffff880046370000 ..........7F....
0xffff880046237760 ffffffffc31ff660 0000000000000000 `...............
0xffff880046237770 ffff880029099c00
ffff8800462377d8 ...).....w#F....
0xffff880046237780 ffffffffc237e34b
dcdm_pcre_new_flow_proc+0x1eb
ffff880065a04880 K.7......H.e....
0xffff880046237790 0000000165a1e0f6 ffffe90142dd16c0 ...e.......B....
0xffff8800462377a0 ffff880065a04880 ffff8800462377d8 .H.e.....w#F....
0xffff8800462377b0 ffff880065a04880 ffff880046370000 .H.e......7F....
0xffff8800462377c0 ffffe90142dd16a8 000000000002d2a0 ...B............
0xffff8800462377d0 ffffe90142dd1518 ffff8800462377f8 ...B.....w#F....
0xffff8800462377e0 ffffffffc237e3cb ffff880065a04880 ..7......H.e....
0xffff8800462377f0 ffff880046370000 ffff880046237898 ..7F.....x#F....
0xffff880046237800 ffffffffc237d311 0000000000000000 ..7.............
0xffff880046237810 0000000000000000 ffff880046237868 ........hx#F....
0xffff880046237820 ffffffffc21eb9ff 471d0201ba190101 ...............G
0xffff880046237830 000000061606db27 ffff880046237858 '.......Xx#F....
0xffff880046237840 ffff880065a04880 ffffffffc5ed6860 .H.e....`h......
0xffff880046237850 42000004c2380ca3 ffff880046237898 ..8....B.x#F....
0xffff880046237860 00000001c23785f4 ffff8800462378b8 ..7......x#F....
调试进程
使用方法
gdb -p <pid>
适用场景
某进程运行过程中会出故障,需要跟踪执行过程
例:页面下发配置时发生500错误,需分析web_main代码流程
某进程已经出故障但未崩溃,需要查看当前正在哪个流程中
例:串口卡住,但远程诊断能用,需要析vtysh卡在何处
操作流程:
进入gdb模式;
打断点;
c;
进行页面/命令行等操作,使进程运行至断点处;
分析寄存器、内存信息。
反复进行3~5步操作,直到分析明白。
q;
调试bin
使用方法
gdb <bin-file-path>
适用场景
某程序执行时不会转变成后台进程,运行过程中会出故障,需要跟踪执行过程
例:配置恢复有启动项失败,需要从startup程序跟踪其执行流程
某程序执行时会转变成后台进程,但进入自身循环之前(相当于进程启动阶段)会出故障,需要分析该过程执行流程
例:eventd进程进入死循环前出故障,需要分析该执行过程
set follow-fork-mode (parent|child)命令用法
用于指定跟随子进程还是父进程(默认为parent)
当前进程切换成后台进程时,执行set follow-fork-mode child
调用生成子进程的函数(如system、popen、exec系列函数等)前,执行set follow-fork-mode parent
操作流程:
与后台进程类似,但在1~3之间执行set follow-fork-mode child,确定已切换到后台后set follow-fork-mode parent切回来。
调试启动项:
先找到要调用的启动项所在文件startup_N_*、所在行M
对/usr/bin/startup挂gdb
先打断点、执行set follow-fork-mode child
运行:r -p N M
KDB
kdb提供丰富的命令实现运行控制、内存操纵、寄存器操纵、断点设置、堆栈跟踪等许多功能,总共有33条命令,下面分别进行介绍。
运行控制类
包括go、ss和ssb三个命令,提供对程序执行的控制。具体用法如下:
go:继续程序执行
格式:go
该命令使内核继续执行,直到遇到一个断点才停止。如果没有设置断点,该命令将离开kdb调试器,系统回到正常运行状态。Caps和Scroll指示灯恢复到原来的状态。
ss:单步执行程序
格式:ss
该命令仅仅执行下一条指令,执行完后停止。这在进行跟踪时是必不可少的。
ssb:执行到分支或者函数调用时停止
格式:ssb
该命令与ss的区别是,ss只执行一条语句,而ssb执行一组语句,它使指令继续执行,在遇到一个分支语句,或者遇到一个函数调用语句时停止。
断点类
kdb提供强大的断点功能,包括设置断点、清除断点、激活断点、使断点失效,kdb也可以设置硬件断点。断点指令包括bp、bl、bpa、bph、bpha、bc、be和bd。
bp:设置或者显示断点
格式:bp [<vaddr>]
该命令设置一个新的断点,其中vaddr是要设置的断点的地址。如果不带参数,运行bp将显示当前设置的所有断点。
bl:设置或者显示断点
格式:bl [<vaddr>]
该命令的操作与bp命令相同。
bpa:设置或者显示全局断点
格式:bpa [<vaddr>]
该命令设置一个全局断点,或者显示所有全局断点,用法同上。
bph:设置硬件断点或者显示所有断点
格式:bph [vaddr [datar|dataw|io [length]]]
如果不带参数,则显示所有断点。如果带参数,那么设置断点。其中vaddr为要设置硬件断点的地址,datar表示对该内存区进行读操作,dataw表示写操作,io表示对该内存区进行io输入输出操作。length指明读写io操作的数据长度。
bpha:设置硬件断点或者显示所有断点
格式和用法同bph。
bc:清除断点
格式:bc <bpnum>
清除标号为bpnum的断点。如果断点号为“*”,将清除所有断点。
bd:使断点无效
格式:bd <bpnum>
使标号为bpnum的断点无效,如果标号为“*”,表示使所有断点无效。
be:激活断点
格式:be <bpnum>
激活标号为bpnum的断点。如果标号为“*”,将激活所有无效的断点。
内存操作类
内存操作类命令包括对内存进行显示和修改的md、mdr、mds、mm四条命令。
md:显示内存内容
格式1:md [vaddr [line-count [output-radix]] ]
显示地址为vaddr的内存的内容。line-count为要显示的内存的行数,output-radix指定以8进制、10进制或者16进制显示。如果省略line-count和output-radix,那么将以设置的环境变量MDCOUNT和RADIX方式显示。如果不带任何参数,md命令将接着上次md命令的后续地址显示内存内容
格式2:mdWcn
在缺省情况下,md以当前环境变量BYTESPERWORD的值读取数据,在读取硬件寄存器的时候,需要指定数据的宽度。这是可以使用mdWcn来进行读取,W是读取的宽度,单位是字节,cn为要读取的数目。
mdr:显示原始内存的内容
格式:mdr <vaddr> <count>
从指定地址vaddr开始显示count长度的内存,它打印一连串的内存数据。这个命令是留给外部的调试器使用的,一般很少使用。
mds:以符号的方式显示内存的内容
格式:mds [vaddr [line-count [output-radix]]]
从指定地址vaddr开始显示内存的内容,与md的区别是每行仅显示一个字,并且它试图将该地址与符号表进行匹配,如果找到,那么它将显示相应的符号名以及偏移值。如果不带参数,它将从上次mds的末尾开始显示。
mm:修改内存内容
格式1:mm <vaddr> <new content>
将指定地址vaddr开始的数据修改为新的数据。修改的数据的长度为一个机器字。
格式2:mmW <vaddr> <new content>
意义同上,区别在于它改变W字节的内容。
堆栈跟踪类
该类指令实现对堆栈的跟踪,包括bt、btp和bta三条命令。
bt:显示调用堆栈
格式:bt [<stack-frame addr>]
如果不指定参数,它根据当前寄存器的内容显示堆栈,提供当前活动线程的完整的堆栈跟踪。如果指定stack-frame addr参数,它将从该地址开始跟踪。
btp:显示进程的堆栈
格式:btp <pid>
显示由pid指定的进程的堆栈。
bta:显示所有进程的堆栈
格式:bta
寄存器类
寄存器类命令包括对寄存器内容进行显示和修改的rd和rm指令,以及异常帧显示指令ef。
rd:显示寄存器内容
格式:rd [c|d|u]
如果不带任何参数,rd显示所有进入kdb调试器时该点所设置的所有通用寄存器的值。如果带c参数,它将显示控制寄存器cr0、cr1、cr2、cr4 寄存器的内容。如果带d参数,它显示调试寄存器的内容。如果带u参数,它显示当进入kdb调试器时当前任务的所有寄存器。
rm:修改寄存器的内容
格式:rm <register-name> <register-content>
该命令修改register-name指定的寄存器的内容为register-content。其中register-name 为%eax、%ebx、%ecx、%edx、%esi、%edi、%esp、%eip或%ebp。如果参数为%%,由rd u指定的寄存器将被修改。当前rm命令不允许修改控制寄存器,也不允许显示和修改Pentium和Pentium Pro系列的特定寄存器。
ef:显示异常帧
格式: ef <vaddr>
显示vaddr地址处的异常帧。
环境变量类
这类指令对kdb调试器环境变量进行显示和设置。包括set和env命令。
set:设置环境变量
格式:set <env-var=value>
将环境变量env-var的值设置为value。最多有33个环境变量,每个环境变量最大512字节。kdb的主要环境变量有:
PROMPT:kdb调试器提示符,缺省为kdb。
MOREPROMPT:在一屏显示不下的情况下,系统的提示符,缺省为more。
RADIX:显示数据时所使用的数制,缺省为16进制。
LINES:kdb调试器显示行数。缺省为24行。
COLUMNS:kdb调试器显示的列数。缺省为80列。
MDCOUNT:执行md指令时显示的内存行数,缺省为8行。
BTARGS:执行bt跟踪时,指定任一函数在打印时所使用参数最大个数。
SSCOUNT:该环境变量规定在执行ssb命令时,如果显示超过此数,执行将停止。缺省为20。
IDMODE:反汇编时所使用的指令格式。缺省为x86。
BYTESPERWORD:指定字的长度,缺省为4个字节。
IDCOUNT:反汇编时,一次反汇编的指令长度,缺省为16条指令。
env:显示环境变量
格式:env
显示所有环境变量的值。
杂项
id:指令反汇编
格式:id <vaddr>
从vaddr开始的地址反汇编指令。
cpu:切换到另一个CPU
格式:cpu <cpunum>
这条命令仅仅在SMP结构下有用,它切换到由cpunum指定的CPU。
ps:显示所有活动的进程
格式:ps
显示当前的活动的进程。包括pid、父进程pid、CPU号、当前状态,以及对应的线程。
reboot:重新启动机器
格式:reboot
在某些情况下,内核无法返回到正常工作状态,这时可以利用reboot重新启动机器。注意在重启机器前,它不进行任何状态保存的工作。
sections:列出内核中所有已知的段的信息
格式:sections
列出模块和内核的所有已知的段的信息。首先是模块信息,最后是内核信息。包括模块名和一个或者多个段的信息。段信息包括段名、段起始地址、段结束地址和段标识。本命令仅仅是为外部调试器而设立的。
sr:激活SysRq代码,也就是调用MAGIC_SYSRQ函数
格式:sr <sysrq key>
将sysrq key字符作为参数传递给SysRq函数进行处理,就像你已经键入了SysRq键和该字符一样。如果要使用这个命令,需要在配置内核时,选择Magic SysRq Key。然后在新内核启动后,使用如下命令激活SysRq功能。
#echo “1” > /proc/sys/kernel/sysrq
这是一个功能强大的命令,它使得在kdb中可以使用操作系统提供的SysRq处理函数。
lsmod:列出内核中加载的所有模块
格式:lsmod
显示所有模块的信息。包括模块名、模块大小、模块结构地址、引用计数,以及被哪个模块所引用。
rmmod:卸载一个模块
格式:rmmod <modname>
将由modname指定的模块从内核中卸载。
ll:对链表中的每个元素重复执行命令
格式:ll <addr> <link-offset> <cmd>
它对以地址addr开头的链表的头link-offset个元素,重复执行cmd命令。
help和?:显示帮助信息。
格式:help 或者?
显示kdb的命令以及简单的用法。
常用的一些内核模块操作
查看加载模块用lsmod或者cat /proc/modules(这个还可以查看模块加载地址)
卸载模块用rmmod,有时加上-f强行卸载
加载模块用modprobe或者insmod,区别转载如下:
"
insmod 与 modprobe 都是载入 kernel module,不过一般差别于 modprobe 能够处理 module 载入的相依问题。
比方你要载入 a module,但是 a module 要求系统先载入 b module 时,直接用 insmod 挂入通常都会出现错误讯息,不过 modprobe 倒是能够知道先载入 b module 后才载入 a module,如此相依性就会满足。
不过 modprobe 并不是大神,不会厉害到知道 module 之间的相依性为何,该程式是读取 /lib/modules/2.6.xx/modules.dep 档案得知相依性的。而该档案是透过 depmod 程式所建立。
modprobe:
modprobe可载入指定的个别模块,或是载入一组相依的模块。modprobe会根据depmod所产生的相依关系,决定要载入哪些模块。若在载入过程中发生错误,在modprobe会卸载整组的模块。
-a或--all 载入全部的模块。
-c或--show-conf 显示所有模块的设置信息。
-d或--debug 使用排错模式。
-l或--list 显示可用的模块。
-r, --remove //若在命令指定模块,则删除指定模块,否则,指定"自动清除"模式
-t或--type 指定模块类型。
-v或--verbose 执行时显示详细的信息。
-V或--version 显示版本信息。
-help 显示帮助。
-C, --config configfile //指定配置文件.默认使用/etc/modules.conf文件为配置文件
modprobe -r ip_vs # 删除ip_vs模块
modprobe -l | grep ip_vs # 查看ip_vs模块是否编译进内核
lsmod -l | grep ip_vs # 查看ip_vs模块是否载入进内核
kdb gdb 显示断点 bl info break 清除断点 bc xxx del xxx 打断点 bp break(b) 使能断点 be xxx enable xxx 去使能断点 bd xxx disable xxx 继续执行 go continue(c)--reverse-continue 单步执行 next(n)--reverse-next 显示寄存器 rd info register 修改内存 mm sym/addr v set symbol/addr=value 显示内存 md print(p) 显示函数栈 bt bt step--reverse-step finish--reverse-finish