watch -n
指令的使用:
# 每隔1s在终端打印一次当前系统内存使用情况
watch -n 1 "cat /proc/meminfo"
# 每隔1s查看当前系统中所有正在运行的进程
# ps:查看系统进程; -e:显示所有进程;-f:全格式
# ps -aux指令也用于查看进程。两者的输出风格不同,内容几乎无差别,一般推荐使用-elf
watch -n 1 "ps -elf"
nm
指令的使用:
# 查看可执行文件或者动态链接库的符号表(函数、变量等)
nm ***.exe
# 加上grep可以精确定位
nm ***.exe | grep 待查名称
gdb
指令:
# 打开一个可视化的gdb调试终端,开始调试程序文件
gdb -tui **.exe
# 此外,也可以对正在运行中的进程,切入gdb调试
gdb -p 进程PID
# or
gdb attach 进程PID
(gdb)l 数字n # (小写L)显示出代码从第n行开始的内容,默认显示10行;之后再次输入l,会再往后输出10行
(gdb)set args model/yolo4_tf.xmodel 0 -t 8 # set args:设置程序启动参数
(gdb)run # run指令:进入主程序,立即开始执行,直到遇到断点或者程序结束
(gdb)start # start指令:进入主程序,停在main()主函数入口处;等待下一步指示(手动打断点等)
(gdb)break n (简写b n) # 在第n行处设置断点(可以带上代码路径和代码名称: b /usr/codeprj/OAGUPDATE.cpp:578)
(gdb)break func # 在函数func()的入口处设置断点,如:break cb_button
(gdb)info b # 显示当前程序的断点设置情况
(gdb)delete 断点号n # 删除第n个断点
(gdb)clear 行号n # 清除第n行的断点,每行可能有多个不同的断点
(gdb)disable 断点号n # 暂停第n个断点
(gdb)enable 断点号n # 开启第n个断点
(gdb)delete breakpoints # 清除所有断点
(gdb)quit # 使用 quit 命令退出当前gdb调试进程;之后如果再次启动调试,会消除上一次调试操作中建立的所有断点
(gdb)continue # 继续执行代码,直到遇到下一个断点或者代码执行结束!
# GDB 调试器共提供了3种可实现单步调试程序的方法,即使用 next、step 和 until 命令
(gdb)next # 当遇到包含调用函数的语句时,无论函数内部包含多少行代码,next指令都会一步执行完
(gdb)step # 当step命令所执行的代码行中包含函数时,会进入该函数内部,在函数第一行代码处停止执行
(gdb)finish/return # 结束当前执行的函数:finish命令会执行函数到正常退出;而return命令是立即结束执行当前函数并返回,即剩下未执行的也不管了
(gdb)print num # 输出或者修改指定变量或者表达式的值
(gdb)print file::variable # file用于指定具体的文件名,variable表示要查看的目标变量或表达式
(gdb)print function::variable # funciton 用于指定具体所在函数的函数名
(gdb)p/x variable # 按十六进制格式显示变量
(gdb)p/d variable # 按十进制显示
(gdb)p/u variable # 按十六进制显示无符号整型
(gdb)p/o variable # 按八进制显示变量
(gdb)p/t variable # 按二进制显示变量
(gdb)p/c variable # 按字符格式显示变量
(gdb)p/f variable # 按浮点数显示变量
# 打印数组内容的同时显示出数组下标
set print array-indexes on
# 数组默认打印200个元素,但也可以设置具体个数
set print elements 具体数字num # num为0时,即不限制元素个数
# 选择打印数组范围
print *(arr+起始地址)@num
# 打印arr+128后的6个元素
print *(arr+128)@6
print arr # 打印arr数组
backtrace
查看栈桢信息(函数调用的顺序):
当我们阅读代码和查找BUG时,往往有一个烦恼。就是我们不知道函数的调用顺序。而这些函数调用顺序对应我们理解程序结构,程序运行过程是很有帮助的。关于函数的信息都存放在栈中。
# 栈帧就是一个函数执行的环境:函数参数、函数的局部变量、函数执行完后返回到哪里等等 bt:backtrace
(gdb)bt # 查看当前所处函数栈帧的数据
(gdb)bt full # 打印当前所处函数栈帧的所有参数信息
# 定位栈异常的指令:
(gdb)frame N # 切换到栈编号为N的栈帧 N:bt对应的栈编号
(gdb)info frame # 打印当前函数(N)所处的栈桢信息
(gdb)info locals # 打印当前函数内的局部变量
(gdb)info args # 查看当前函数参数
示例:
//frame.c
#include
int sum(int n)
{
int ret = 0;
if( n > 0 )
{
ret = n + sum(n-1);
}
return ret;
}
int main()
{
int s = 0;
s = sum(10);
printf("sum = %d\n", s);
return 0;
}
示例代码的调试过程:
1. 设置断点:设置到递归结束标志的位置:
(gdb) start
The program being debugged has been started already.
Start it from the beginning? (y or n) y
Temporary breakpoint 4 at 0x80483f9: file frame.c, line 19.
Starting program: /home/delphi/workspace/test.out
Temporary breakpoint 4, main () at frame.c:19
19 int s = 0;
(gdb) break sum if n==0 # 设置sum函数中, n==0 时的数据断点。
Breakpoint 5 at 0x80483ca: file frame.c, line 6.
(gdb) info break # 查看断点信息
Num Type Disp Enb Address What
5 breakpoint keep y 0x080483ca in sum at frame.c:6
stop only if n==0
2. 查看函数调用过程:
(gdb) continue
Continuing.
Breakpoint 5, sum (n=0) at frame.c:6
6 int ret = 0;
# 通过栈桢号判断函数的调用顺序:上一行的函数被下一行的函数调用
(gdb) backtrace # 查看函数调用的顺序
#0 sum (n=0) at frame.c:6
#1 0x080483e5 in sum (n=1) at frame.c:10
#2 0x080483e5 in sum (n=2) at frame.c:10
#3 0x080483e5 in sum (n=3) at frame.c:10
#4 0x080483e5 in sum (n=4) at frame.c:10
#5 0x080483e5 in sum (n=5) at frame.c:10
#6 0x080483e5 in sum (n=6) at frame.c:10
#7 0x080483e5 in sum (n=7) at frame.c:10
#8 0x080483e5 in sum (n=8) at frame.c:10
#9 0x080483e5 in sum (n=9) at frame.c:10
#10 0x080483e5 in sum (n=10) at frame.c:10
#11 0x0804840d in main () at frame.c:21
3. 分析函数调用过程:
(gdb) next # 单步执行,不进入函数
8 if( n > 0 )
(gdb) next
13 return ret;
(gdb) info args # 查看当前函数参数的值
n = 0
(gdb) frame 7 # 切换栈编号为7的上下文中
#7 0x080483e5 in sum (n=7) at frame.c:10
10 ret = n + sum(n-1);
(gdb) info args # 查看栈编号为7时函数参数的值
n = 7
(gdb) info locals # 查看当前局部变量ret的值
ret = 0 # 计算结果
(gdb) frame 0
#0 sum (n=0) at frame.c:13
13 return ret;
(gdb) info registers # 查看当前寄存器的值
eax 0x0 0
ecx 0x241be83d 605808701
edx 0x1 1
ebx 0x287ff4 2654196
esp 0xbffff070 0xbffff070
ebp 0xbffff098 0xbffff098
esi 0x0 0
edi 0x0 0
eip 0x80483eb 0x80483eb <sum+39>
eflags 0x200246 [ PF ZF IF ID ]
cs 0x73 115
ss 0x7b 123
ds 0x7b 123
es 0x7b 123
fs 0x0 0
gs 0x33 51
(gdb) info frame # 查看当前栈帧的详细信息
Stack level 0, frame at 0xbffff0a0:
eip = 0x80483eb in sum (frame.c:13); saved eip 0x80483e5
called by frame at 0xbffff0d0
source language c.
Arglist at 0xbffff098, args: n=0
Locals at 0xbffff098, Previous frame's sp is 0xbffff0a0 # 上一个栈指针地址
Saved registers: # 将下面值保存到寄存器中
ebp at 0xbffff098, eip at 0xbffff09c
(gdb) x /1wx 0xbffff098 # 查看ebp地址中的值
0xbffff098: 0xbffff0c8
(gdb) next
14 }
(gdb) next
13 return ret;
(gdb) info args
n = 1
(gdb) info registers # 查看栈帧编号为1的寄存器值
eax 0x1 1
ecx 0x241be83d 605808701
edx 0x1 1
ebx 0x287ff4 2654196
esp 0xbffff0a0 0xbffff0a0
ebp 0xbffff0c8 0xbffff0c8
esi 0x0 0
edi 0x0 0
eip 0x80483eb 0x80483eb <sum+39>
eflags 0x200202 [ IF ID ]
cs 0x73 115
ss 0x7b 123
ds 0x7b 123
es 0x7b 123
fs 0x0 0
gs 0x33 51
(gdb) info locals
ret = 1 # 计算结果
调试执行异常崩溃的程序:
当然,如果直接使用gdb **.exe
的方式调试程序,就不用这种定位崩溃方式了!
在Linux操作系统中,当程序执行发生异常崩溃时,系统可以将发生崩溃时的内存数据、调用堆栈情况等信息自动记录下载,并存储到一个文件中,该文件通常称为core文件,Linux系统所具备的这种功能又称为核心转储(core dump)。幸运的是,GDB对core
文件的分析和调试提供有非常强大的功能支持,当程序发生异常崩溃时,通过GDB调试产生的core
文件,往往可以更快速的解决问题。
如何设置core dump
文件在系统的生成存放目录:Linux-Coredump分析基础 ubuntu如何生成core文件 core文件去哪了
写个代码验证一下:
#include
int main() {
char *a = NULL;
*a = 2; // 这行一看就是有BUG
return 0;
}
编译:
# -g:一定不能少,否则后面用gdb调试时候不能直观地显示出BUG所在位置
$ g++ -g -o test core.cpp
$ ./test
Segmentation fault (core dumped) <-- 发生段错误,并生成了 core 文件
可以根据生成时间查找core dump
文件:
# 这里假设core文件被存放在/home/homework/coresave路径下
ls /home/homework/coresave -hl | grep test
-rw-rw-rw- 1 root root 400K Mar 13 15:08 core.test.27725.1615619332
-rw-rw-rw- 1 root root 400K Mar 13 15:26 core.test.7791.1615620408
-rw-rw-rw- 1 root root 540K Mar 11 10:29 core.test.1868.1615429740
-rw-rw-rw- 1 root root 400K Mar 13 15:07 core.test.26880.1615619264
-rw-rw-rw- 1 root root 404K Mar 3 19:42 core.test.28802.1614771771
用gdb
进行调试:
# test: 要调试的可执行文件的名称
$ gdb test /home/homework/coresave/core.test1.7791.1615620408
Reading symbols from /home/zhudi/project/linux/blog/gdb/test...done.
warning: core file may not match specified executable file.
[New LWP 7791]
Core was generated by `./test'.
Program terminated with signal 11, Segmentation fault.
#0 0x00000000004005bd in main () at core.cpp:5
5 *a = 2;
由此可见,程序崩溃了在第五行,定位到了出现问题的代码位置。
注意,因为 Python 是一种解释型语言,可以通过 pdb
shell 执行命令。 ipdb
是一种增强型的 pdb
,它使用IPython
作为 REPL并开启了 tab 补全、语法高亮、更好的回溯和更好的内省,同时还保留了pdb
模块相同的接口。
调试python代码,可以使用ipdb调试工具
python -m ipdb bubble.py
进入调试过程后,基本操作指令和gdb大同小异:
# 显示当前行附近的11行或继续执行之前的源码显示
$ l(ist)
# 打断点
$ b 6 # 在第六行
# 一步一步执行,遇到函数就进入
$ step
# 继续执行直到当前函数的下一条语句或者 return 语句
$ next
# 继续运行,直到执行结束/出现报错/遇到断点
$ c
# 代码因某种原因停下后,打印数据;可以配合step使用
$ p 变量名
$ p locals() # 打印当前时刻所有变量
# 继续执行直到当前函数返回
$ return
# 停止调试
$ q
# 重启调试
$ restart
对于更底层的编程语言,您可能需要了解一下 gdb
( 以及它的改进版 pwndbg
) 和 lldb
。
它们都对类 C 语言的调试进行了优化,它允许您探索任意进程及其机器状态:寄存器、堆栈、程序计数器等。