列位安好。简单总结下GDB调试器的使用。
准备
默认情况下,gcc/g++编译的可执行文件是不包含调试信息的,GDB是一个源代码级的调试器,使用GDB调试程序需要程序的源代码、符号及其对应的行号等,其中符号和行号可以是单独的文件,亦可以在编译时嵌入到可执行文件中。使用gcc/g++时使用-g选项即可将必要的调试信息包含到可执行文件中,使用-g3选项还可以将源代码中的宏信息也包含进去。
另外,调试过程中需要随时查看源代码,但源代码并没有包含到可执行文件中。通常GDB在当前目录查找源文件,但某些情况下(比如调试系统命令)需要手动指明源代码的查找目录,directory ~向GDB指明到$HOME下查找源文件。
启动
GDB的启动很灵活,它的各种特性,你可以在Shell下通过选项和参数指定,也可以在GDB启动之后在GDB自己的命令行下使用GDB内置的命令来指定。最常用的是直接使用命令gdb PROGRAM启动,这样gdb自动加载符号表等调试信息。若要向被调试程序传递参数,可以采用gdb –args program ARG1 ARG2的形式,其中–args(或者-args)是必须的,它告诉GDB该选项之后已经没有GDB需要的选项了。另外,还可以直接使用gdb启动,然后使用file program加载调试信息。此时若要设置被调试程序的参数,可以使用set命令的args子命令,如set args ARG1 ARG2. 还有一种传递参数的方法,在下面介绍。
断点
调试程序,就是使用调试器(Debugger)通过检测和改变被调试程序(Debuggee)的状态、控制其执行的方式找出被调试程序中的错误和潜在的bug。调试程序,观察程序当前的行为的前提是让程序在“适当的时候”暂停运,那么什么是适当的时候呢?使用GDB时,让程序暂停运行需要使用断点。具体地,断点又可以分为普通断点(breakpoint以下简称断点),观察点(watchpoint),捕捉点(watchpoint)三类。
普通断点使用break命令(简写为b)设置。break命令的格式为break [bp-spec] [if CONDITION] [thread THREADNUM],bp-spec指明断点设置的位置,可以是行号、函数名或者指令地址,如果bp-spec省略,则断点被设置在程序所要执行的下一行代码(或者指令)上。if CONDITION指明当程序到达bp-spec位置时,只有CONDITION条件成立时程序才会暂停。thread THREADNUM用在多线程程序的调试中,断点只被设置在指定的线程号(GDB内部而不是系统使用的标号)为THREADNUM的线程上,如果THREADNUM为all则所有线程都会被设置断点。补充下bp-spec可以是函数名b FUNCTIONNAME(重载函数名需要使用”包含才能自动补全),可以是行号b LINENUMBER或者b FILENAME:LINENUMBER,还可以是指令地址b *ADDRESS。另外,break命令还有其它一些变种,比如tbreak设置临时断点,被使用一次就会自动删除,rbreak使用正则表达式来指明函数名。
观察点使用watch命令,命令格式与break相同,但它并不是指明断点的位置,而是指明一个表达式,每当该表达式的值改变时,程序便会被暂停。表达式可以是某个变量、由若干变量组成的表达式或者内存地址。
捕捉点是另一种断点,它使用某种事件的发生作为触发条件,命令各式为catch EVENT。这些事件主要包括异常的抛出和捕获(catch throw/catch)和某个系统调用(catch fork/open/exec, catch syscall CALLNUM)。
查看当前的断点设置情况可以使用breakpoints,也可以使用info breakpoints(或者简写为i b)命令,查看某个断点使用breakpoints bpnum,bpnum为断点号。
使用enable/disable bpnum使某个断点生效和失效,delete bpnum删除断点。bpnum还可以是一个范围,以此批量操作断点,比如d 2-6删除断点2到6。
使用ignore bpnum COUNT还可以使某个断点被或略COUNT次,即是说断点bpnum的前COUNT次到达都不会被触发,知道COUNT递减至0。另外,在COUNT递减至0之前,该断点上的条件是不会被考虑的。
CONDITION bpnum [if condition],修改bpnum上的触发条件,若if被省略,则bpnum断点上的条件将被删除。
运行
GDB启动和加载调试信息后,被调试程序并没有运行。使用run/r或者start命令,GDB建立子进程来运行被调试程序。run和start命令稍有不同,即run仅仅加载程序然后运行,而start会在程序的入口函数(main)设置一个临时断点,程序运行到那里就会暂停,临时断点也随即被清除。另外run和start命令后面都可以加上传递给被调试程序的参数,若不加参数则使用GDB启动时传递的参数或者使用set args命令设置的参数。若要清除参数而不退出GDB,使用不带参数的set args即可。
其它运行相关的命令还有
•continue/c,继续运行。
•next/n, 下一行,且不进入函数调用
•stop/s, 下一行,但进入函数调用
•ni或者si, 下一条指令,ni与si的区别同n与s的区别
•finish/fini, 继续运行至当前栈帧/函数刚刚退出
•until/u, 继续运行至某一行,在循环中时,u可以实现运行至循环刚刚退出,但这取决于循环的实现。
查看
调试的主要工作,我想就是检查程序的状态吧,内存的状态,程序的流程,指令的安排。GDB有多个命令来查看程序的状态,最常用的是list/l, print/p和x和disassemble。
•list linenum/function列出第linenum行或者function所在行附近的10行,list *address列出地址address附近的10行。
•list列出上一次list命令列出的代码后面的10行
•list -,列出上一次list命令列出的代码前的10行
•list列出的默认行数可由set listsize size来设置
•p /fmt VARIABLE根据fmt指定的格式打印变量VARIABLE的值,常用的fmt有d(decimal), u(unsigned), x(hex), o(octal), c(character), f(float), s(string)
•x /nfs ADDRESS是我最喜欢的命令,它显示ADDRESS地址开始的内容,形式由nfs指定。其中n为次数(个数),f是格式,除p命令可以使用的格式外,还可以使用i(instruction)打印指令,s为所打印内容的大小,可以是b(byte), h(half word), w(word, 4bytes), g(8bytes). nfs均可省略,若省略则使用最近一次使用x命令时的值,最初nfs是1dw。
•disassemble /[r][m] [ADDRESS]将ADDRESS所在函数进行反汇编,ADDRESS也可以是一个由行号组成的区间。不指定任何选项时disas只简单的列出反汇编指令,指定m(mixed)选项时会对应地列出指令和源代码,指定r(raw)时会打印出指令对应的十六进制编码。
退出
Ctrl-C会终止当前调试的程序(而不是调试器)。q(quit)退出GDB,若退出时被调试程序尚未结束,GDB会提示,请求确认。
其它
help
使用GDB的过程中,如果对某一个命令的用法不清楚,可以随时使用help/h寻求帮助。
info
info/i命令也是一个十分常用的GDB命令,可以查看许多信息。
•i program查看被调试程序的运行状态,如进程号、ip(指令指针)、是否运行、停止原因等。
•i b [bpnum]查看断点
•i f [frame-num]查看当前(或指定)栈帧,i f all还会列出当前栈帧的局部变量
•i line LINENUM查看代码行LINENUM,打印其指令地址,此命令后执行x/i可查看该地址处的指令,然后回车即可继续向后查看接下来的指令
•i reg查看寄存器状态,i all-reg查看包含浮点堆栈寄存器在内的所有寄存器情况
•i args查看当前栈帧的参数
•i s追踪栈帧信息,相当于backtrace/bt命令
•i threads查看当前进程的线程信息
回到过去
跟踪调试程序的过程中,偶尔会错过一些关键点,不得不重新启动程序。如果错过的这些关键点不容易再现,就更令人懊恼了。GDB提供一种机制可以让你将程序向后调整,重新来过。这种机制叫做checkpoint,你可以在程序运行的关键点处执行checkpoint命令,你将得到一个数字(check-num)来标识这个checkpoint。在以后的某个时刻使用restart check-num将程序回滚到设置该checkpoint的时刻,而此时此刻的“内存状态”恰如彼时彼刻,你可以重新调试这段程序(比如设置不同的变量值来测试不同的情况)。但覆水难收的道理你是懂得,回滚的这段程序之间产生的内存之外的效应是无法恢复的,比如写出到文件的数据收不回来了(但文件的指针偏移是可以恢复的,因为它的值保存在内存),通过网络发出的数据就更要不回来了。
使用i checkpoints可以查看checkpoint信息,比如check-num及其所处代码行。
据我揣测,GDB的这种“回到过去”的伎俩并不是逐步撤销之前运行的指令,而是在checkpoint命令执行的时候,把被调试程序fork/clone了。
多线程
调试多线程程序与普通程序没有太大的区别。
•i threads查看当前进程包含的线程的信息,包括线程号及其GDB内部标识号,当前处于前端的线程。
•thread thread-num切换到线程thread-num
•set scheduler-locking [on|off|step],这是一个比较重要的选项,它控制这当前调试线程与其它线程的执行关系。设置为on时,其它线程不会抢占当前线程。设置为off(默认)时,当前线程执行时随时可能被其它线程抢占。设置为step时,在执行step命令时不会被抢占,但使用next跳过函数调用时可以/可能会被抢占,即调度器会被执行。
技巧
回车。在GDB命令行下,简单的回车会执行上一个命令。而且GDB命令中可以使用回车重复执行的都是有记忆的,如果使用回车,该命令就会根据上一次的执行适当地调整参数重复执行。比如使用l func列出函数func附近的10行代码,接着回车就会打印接下来的10行。使用x/16xw ADDRESS以16进制形式查看ADDRESS处的16个字,接着回车就会以同样的格式打印接下来的16个字。如果你已经set
i=0了,那么通过pa[
i++]然后一直回车就可以依次打印数组a的元素了。x/i $eip打印下一条指令,接着回车就会打印下下一条指令,以此类推。但,有的指令是无法使用回车来重复执行的,比如run/start,总之,通常,你觉得不能/不适合重复执行的命令就无法重复执行。
命令简写,有的命令名称较长甚至很长,但GDB允许用户只使用足以区别其它命令的前几个字符来执行命令,当然你也可以使用TAB自动补全。另外一些极为常用的命令有专门的简写形式,通常只有一个字母,例如break/b, list/l, info/i, continue/c, next/n, step/s, nexti/ni, stepi/si, frame/f, print/p等等等等。
在GDB中可以自定义变量(仅供GDB内部使用),很多时候可以方便查看一些表达式的值。有的时候使用变量似乎是必须的,比如x (
esp)是不合法的,因为GDB不允许对寄存器变量进行解引用(dereference,butwhy?)。这时,设置变量set
p=(
esp),然后x
p就可以了。
这只是一个小小的不完全的总结,如果你从未使用过GDB,推荐使用RMS的《Debugging with GDB》学习,还有一本小书《GDB Pocket Reference》,不妨做你的调试菜谱,如果你喜欢GDB的话。
快捷键
- 加粗
Ctrl + B
- 斜体
Ctrl + I
- 引用
Ctrl + Q
- 插入链接
Ctrl + L
- 插入代码
Ctrl + K
- 插入图片
Ctrl + G
- 提升标题
Ctrl + H
- 有序列表
Ctrl + O
- 无序列表
Ctrl + U
- 横线
Ctrl + R
- 撤销
Ctrl + Z
- 重做
Ctrl + Y
Markdown及扩展
Markdown 是一种轻量级标记语言,它允许人们使用易读易写的纯文本格式编写文档,然后转换成格式丰富的HTML页面。 —— [ 维基百科 ]
使用简单的符号标识不同的标题,将某些文字标记为粗体或者斜体,创建一个链接等,详细语法参考帮助?。
本编辑器支持 Markdown Extra , 扩展了很多好用的功能。具体请参考Github.
表格
Markdown Extra 表格语法:
项目 | 价格 |
---|---|
Computer | $1600 |
Phone | $12 |
Pipe | $1 |
可以使用冒号来定义对齐方式:
项目 | 价格 | 数量 |
---|---|---|
Computer | 1600 元 | 5 |
Phone | 12 元 | 12 |
Pipe | 1 元 | 234 |
定义列表
-
Markdown Extra 定义列表语法:
项目1
项目2
- 定义 A
- 定义 B 项目3
- 定义 C
-
定义 D
定义D内容
代码块
代码块语法遵循标准markdown代码,例如:
@requires_authorization
def somefunc(param1='', param2=0):
'''A docstring'''
if param1 > param2: # interesting
print 'Greater'
return (param2 - param1 + 1) or None
class SomeClass:
pass
>>> message = '''interpreter
... prompt'''
脚注
生成一个脚注1.
目录
用 [TOC]
来生成目录:
数学公式
使用MathJax渲染LaTex 数学公式,详见math.stackexchange.com.
- 行内公式,数学公式为: Γ(n)=(n−1)!∀n∈N 。
- 块级公式:
更多LaTex语法请参考 这儿.
UML 图:
可以渲染序列图:
或者流程图:
离线写博客
即使用户在没有网络的情况下,也可以通过本编辑器离线写博客(直接在曾经使用过的浏览器中输入write.blog.csdn.net/mdeditor即可。Markdown编辑器使用浏览器离线存储将内容保存在本地。
用户写博客的过程中,内容实时保存在浏览器缓存中,在用户关闭浏览器或者其它异常情况下,内容不会丢失。用户再次打开浏览器时,会显示上次用户正在编辑的没有发表的内容。
博客发表后,本地缓存将被删除。
用户可以选择 把正在写的博客保存到服务器草稿箱,即使换浏览器或者清除缓存,内容也不会丢失。
注意:虽然浏览器存储大部分时候都比较可靠,但为了您的数据安全,在联网后,请务必及时发表或者保存到服务器草稿箱。
浏览器兼容
- 目前,本编辑器对Chrome浏览器支持最为完整。建议大家使用较新版本的Chrome。
- IE9以下不支持
- IE9,10,11存在以下问题
- 不支持离线功能
- IE9不支持文件导入导出
- IE10不支持拖拽文件导入
- 这里是 脚注 的 内容. ↩