gdb调试工具常用命令 && kdb

编译程序时需要加上-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

1) %rax 作为函数返回值使用。
2) %rsp 栈指针寄存器,指向栈顶。
3) %rdi,%rsi,%rdx,%rcx,%r8,%r9 用作函数参数,依次对应第1参数,第2参数等。
4) %rbx,%rbp,%r12,%r13,%14,%15 用作数据存储,遵循被调用者使用规则,简单说就是随便用,调用子函数之前要备份它,以防他被修改。
5) %r10,%r11 用作数据存储,遵循调用者使用规则,简单说就是使用之前要先保存原值。
 
 
栈结构信息
X86_64的栈结构:
 
如上图所示:%rsp寄存器存储的地址指向当前函数的栈顶,%rbp寄存器是我们推栈的关键,它指向的位置保存了两个字,前一个函数栈的%rbp寄存器值和函数返回地址。
 
(三)推栈过程:
 
    下面开始介绍推栈过程,可以发现,推栈的过程就是跟踪查找%rbp寄存器的内容,而且我们无需像MIPS体系那样关注某个函数的堆栈的大小。
 
 1) 设备死机时会进KDB,通过KDB的rd命令可查看当前挂死环境所有寄存器的内容:
[12]kdb> rd
     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 &regs = 0xffff8800462374d8

2) SP寄存器指向当前调用栈的栈顶,通过md命令可查看调用栈的内容,而IP寄存器里存储的是正在执行的函数地址
[12]kdb> md 0xffff880046237570    
fa_traverse_dfa+0x1ec             
0xffff880046237570 ffff880046237580 0000000000000018   .u#F............
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是函数返回地址。
ffff880046237638   ........8v#F....
0xffff8800462375f0 ffffffffc22427a9                                   

fa_traverse+0x69        // 4)通过函数返回地址得到前一个调用函数名称fa_traverse。
0000000000000000   .'$.............
0xffff880046237600 ffff880046237688 0000000000000000   .v#F............
0xffff880046237610 0000000000000000 0000000000000000   ................
0xffff880046237620 0000000000000000 ffffffffc31ff918   ................
0xffff880046237630 ffffc9005dd67000                                 // 5)同时,我们得到了fa_traverse函数的BP寄存器的值,它指向的地址(ffff880046237638)同样存储了前一个函数的BP和返回地址。
ffff8800462376c8   .p.].....v#F....
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
                                   
原文地址:https://www.cnblogs.com/yipianchuyun/p/12307745.html