online doc of Compiling for Debugging
gcc/g++是在编译时加入-g(注意 -g 参数要在 -o 之前,否则可能没有调试信息)
-g分4个等级:
info 简写为 i
info locals :查看局部变量,可以简写: i lo.
info args :查看函数入参 :i ar
info break : 查看断点: i b
info registers :查看寄存器的情况
info threads :当前已知线程
allows overriding the output format used by the command
/o - octal
/x - hexadecimal
/d - decimal
/u - unsigned decimal
/t - binary
/f - floating point
/a - address
/c - char
/s - string
p arr[0]@len
set max-value-size unlimited
ptype/pty 后跟结构体名,或结构体变量
set print pretty
backtrace :显示栈信息(调用链)。简写为bt。
frame x 切换到第x帧。其中x会在bt命令中显示,从0开始。0表示栈顶。简写为f。
up/down x 往栈顶/栈底移动x帧。当不输入x时,默认为1。
(gdb) layout src:显示源码窗口: la sr
(gdb) layout asm:显示汇编窗口
(gdb) layout regs:显示寄存器窗口
(gdb) layout split:显示源码和汇编窗口
(gdb) layout next:显示下一个layout窗口
(gdb) layout prev:显示上一个layout窗口
Ctrl + L:刷新窗口
Ctrl + x,再按1:单窗口模式
Ctrl + x,再按2:双窗口模式
Ctrl + x,再按a:退出layout,回到执行layout之前的调试窗口。
1, set substitute-path from_path to_path
,替换源码文件路径。当编译机与运行程序的机器代码路径不同时,需要使用该指令替换代码路径,否则你无法在gdb中看到源码。
2, 为静态库设置源码路径: dir path
简写为: comm
先查看现有的断点,然后 comm 后跟上断点序号,写入这个断点要做的事,end结束。
step
单步调试,步入当前函数。可简写为snext
单步调试,步过当前函数。可简写为nuntil
执行到当前循环完成。可简写为ufinish
执行到当前函数返回continue
继续运行程序到下一个断点,或结束。可简写为creturn
: 强制函数返回。可以指定返回值jump
使当前执行的程序跳转到某一行,或者跳转到某个地址。由于只会使程序跳转而不会改变栈值,因此若跳出函数到另外的地方 会导致return出错。另外,熟悉汇编的人都知道,程序运行时,有一个寄存器用于保存当前代码所在的内存地址。所以,jump命令也就是改变了这个寄存器中的值。于是,你可以使用“set $pc”来更改跳转执行的地址。如: set $pc = 0x485call
调用函数。 command n
>printf "x is %d\n",x
>c
>end
set var x=10
改变当前变量x的值。也可以这样用:set {int}0x83040 = 10把内存地址0x83040的值强制转换为int并赋值为10是指当执行到程序某一步时,程序交出控制权进入调试器。值得注意的是,break会有一些变体:tbreak,hbreak,thbreak与rbreak。tbreak与break功能相同,只是所设置的断点在触发一次后自动删除。hbreak是一个硬件断点。thbreak则既是一个临时的硬件断点。注意硬件断点需要硬件支持,某些硬件可能不支持这种类型的断点。rbreak稍微特殊一些,它会在匹配正则表达式的全部位置加上断点,后面会有详细讲解。除去rbreak,其他break家族的使用方法如下:
查看断点: info break
, 简写i b
break 可以缩写为 b
break xxx.cpp:y
。在文件 xxx.cpp 的第 y 行加入断点。break x.cpp:func
。在x.cpp的func函数入口处加入断点。x.cpp可以不提供直接使用break func。注意由于重载(overload)的存在,因此gdb可能会询问你希望在哪个函数加上断点。break 0xN
。在地址N处加入断点。N必须为一个有效的代码段(code segment)地址。1, save break file.bp
保存断点到 file.bp
2, source file.bp
加载断点(so file.bp)
监视点是监视内存中某个地址,当该地址的数据被改变(或者被读取)时,程序交出控制权进入调试器。注意监视点分为软件模式和硬件模式:GDB 使用软件监视点的方式是在单步执行你的程序的同时测试变量的值,所以执行程序的速度会变慢。同时,软件监视点仅在当前线程有效。幸运的是,32 位的 Intel x86 处理器提供了 4 个特殊的调试寄存器用来方便调试程序,GDB 可以使用这些寄存器建立硬件监视点。GDB 总是会优先使用硬件监视点,因为这样不会减慢程序的执行速度。然而,可用的(enable的)硬件监视点的个数是有限的。如果你设置了过多的硬件监视点,当程序从中断的状态变为执行的状态(例如continue,until或者finish)时,GDB 可能无法把它们全部激活。另外,活动的硬件监视点的数量只有在试图继续执行程序时才能知道,也就是说,即使你设置了过多的硬件监视点,gdb在你运行程序之前也不会警告你。
设置监视点的命令有3个,watch(写监视),rwatch(读监视)以及awatch(读写监视)。他们的使用方法一样,皆为以下几种:
(r/a)watch var
。var是一个变量名。当x的值改变/被读取时,程序交出控制权进入调试器。
(r/a)watch *0xdeadbeef
。0xdeadbeef为一个有效地址。当该地址的内容变化/被读取时,程序交出控制权进入调试器。
(r/a)watch *(int *)0xdeadbeef
。0xdeadbeef为一个有效地址。当该地址的中的int指针指向的内容变化/被读取时,程序交出控制权进入调试器。
(r/a)watch -l *(int *)0xdeadbeef
。0xdeadbeef为一个有效地址。当该地址的中的int指针指向的内容变化/被读取,或者该地址的内容变化/被读取时,程序交出控制权进入调试器。
注意3)和4)的区别在于,当加入-l选项后,会同时监视表达式本身以及表达式指向的内容。
注意:watch 地址的时候,要加解引用符号 *
If you watch for a change in a numerically entered address you need to dereference it, as the address itself is just a constant number which will never change. GDB refuses to create a watchpoint that watches a never-changing value
(gdb) watch 0x600850
Cannot watch constant value 0x600850.
(gdb) watch *(int *) 0x600850
Watchpoint 1: *(int *) 6293584
跟踪点与上面三个断点不同之处在于,它只是跟踪记录信息而不会中断程序的运行。当你的程序是realtime程序,或者与其他的程序有交互时,你可能会希望使用跟踪点达到监视程序而又不破坏程序自身行为的目的。与断点相同的是,跟踪点会保存下在跟踪点时的一些内存信息供使用者查阅,例如数组或者对象。
另外,tracepoints可以通过save命令保存,以方便使用者下次再次进入程序调试时不需要重设这些跟踪点。
gdb可以保存某一个时间点的程序状态或者说是程序映像,并且稍后又可以返回到这个状态。这个称之为checkpoint。
每个检查点是进程的一个拷贝。这样当一个bug很难重现,而又担心调试过头了又要从头开始重现时,可以在估计要重现这个bug之前,做一个checkpoint,这样即使debug过头了,也可以从这个checkpoint开始,而不用重启整个程序并且期待它重现这个bug(也许需要很久!!)。
但是每个checkpoint有一个唯一的进程id,这个pid与原始程序的pid不同,因此如果程序需要使用pid的信息时,需要慎重考虑。
core dump又叫核心转储, 当程序运行过程中发生异常, 程序异常退出时, 由操作系统把程序当前的内存状况存储在一个core文件中, 叫core dump. (linux中如果内存越界会收到SIGSEGV信号,然后就会core dump)
1.内存访问越界
a) 由于使用错误的下标,导致数组访问越界
b) 搜索字符串时,依靠字符串结束符来判断字符串是否结束,但是字符串没有正常的使用结束符
c) 使用strcpy, strcat, sprintf, strcmp, strcasecmp等字符串操作函数,将目标字符串读/写爆。应该使用strncpy, strlcpy, strncat, strlcat, snprintf, strncmp, strncasecmp等函数防止读写越界。
2 多线程程序使用了线程不安全的函数。
3 多线程读写的数据未加锁保护。对于会被多个线程同时访问的全局数据,应该注意加锁保护,否则很容易造成core dump
4 非法指针
a) 使用空指针
b) 随意使用指针转换。一个指向一段内存的指针,除非确定这段内存原先就分配为某种结构或类型,或者这种结构或类型的数组,否则不要将它转换为这种结构或类型 的指针,而应该将这段内存拷贝到一个这种结构或类型中,再访问这个结构或类型。这是因为如果这段内存的开始地址不是按照这种结构或类型对齐的,那么访问它 时就很容易因为bus error而core dump.
5 堆栈溢出.不要使用大的局部变量(因为局部变量都分配在栈上),这样容易造成堆栈溢出,破坏系统的栈和堆结构,导致出现莫名其妙的错误。
首先通过ulimit命 令查看一下系统是否配置支持了dump core的功能。通过ulimit -c或ulimit -a,可以查看core file大小的配置情况,如果为0,则表示系统关闭了dump core。可以通过ulimit -c unlimited来打开。若发生了段错误,但没有core dump,是由于系统禁止core文件的生成。
解决方法:
$ulimit -c unlimited (只对当前shell进程有效)
或在~/.bashrc 的最后加入: ulimit -c unlimited (一劳永逸)
gdb [exec file] [core file]
如: gdb ./test test.core
可以产生core的c语言语句: *(char *)0 = 0;
修改文件命令:
把core文件与执行程序相同路径
echo "core-%e-%p" > /proc/sys/kernel/core_pattern
或者:
sysctl -w kernel.core_pattern=/corefile/core-%e-%p-%t kernel.core_pattern = /corefile/core-%e-%p-%t
可以将core文件统一生成到/corefile目录下,产生的文件名为core-命令名-pid-时间戳
以下是参数列表:
%e - insert coredumping executable name into filename 添加导致产生core的命令名
%p - insert pid into filename 添加pid(进程id)
%u - insert current uid into filename 添加当前uid(用户id)
%g - insert current gid into filename 添加当前gid(用户组id)
%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 添加主机名
在内核中还有一个与coredump相关的设置,就是/proc/sys/kernel/core_uses_pid。如果这个文件的内容被配置成1,
那么即使core_pattern中没有设置%p,最后生成的core dump文件名仍会加上进程ID。
emacs :https://www.cnblogs.com/gaowengang/p/5799292.html
https://www.cnblogs.com/xsln/p/gdb_instructions1.html
汇编:https://www.cnblogs.com/zhangyachen/p/9227037.html