gdb 常用命令
文章目录
- gdb 常用命令
- gdb 调试一般步骤
- 常用命令
- info
- step、next、continue、finish、until
- examine
- break
- info 、enable、disable和delete命令
- backtrace和frame
- backtrace
- frame
- list
- whatis和ptype
- thread
- next、step
- return、finish
- until
- jump
- disassemble
- set args 和 show args
- tbreak
- watch
- 多线程调试
- 调试多线程程序的工具
- 停止和开始多线程模式
- All-Stop Mode
- Non-Stop Mode
- Reference
- >>>>> 欢迎关注公众号【三戒纪元】 <<<<<
gdb 调试一般步骤
gdb <program>
—— 开始调试程序;set args <args1 args2 ……argsn>
—— 设置程序需要的参数;break <condition>
—— 设置断点;run
—— 开始调试程序;next
—— 执行下一步;quit
—— 退出调试;
常用命令
info
info <local parameter>
: 查看局部变量的值
step、next、continue、finish、until
- step (s) : 进入子函数
- next (n) : 执行下一步
- continue ©:继续执行
- finish (f) : 结束子函数
- until (u) :结束循环
examine
-
examine (x) addr
x/nfu addr : 查看内存地址
-
x /10wx addr:查看10个4字节,按16进制输出
-
x /10i addr :查看改地址的10条指令
输出格式:
- x : 16进制显示变量
- u :16进制无符号
- d:10进制显示变量
- o:8进制
- t:2进制
- a:16进制
- c:字符
- f:浮点
- s:字符串
break
- break FunctionName : 在函数的入口处添加一个断点
- break LineNo : 在当前文件行号为LineNo处添加断点
- break FileName:LineNo : 在FileName文件行号为LineNo处添加一个断点
- break FileName:FunctionName : 在FileName文件的FunctionName函数的入口处添加断点
- break -/+offset : 在当前程序暂停位置的前/后 offset 行处下断点
- break … if cond : 下条件断点
info 、enable、disable和delete命令
-
info break,也可简写为
i b
,作用是显示当前所有断点信息; -
disable [breakpoints] [list…] / disable 断点编号
禁用指定的断点或所有断点(如果未列出)。 禁用的断点没有任何作用,但不会被忘记。 所有选项(例如忽略计数、条件和命令)都会被记住,以防稍后再次启用断点。 可以将disable缩写为dis。 -
enable [breakpoints] [list…] / enable 断点编号
启用指定的断点(或所有定义的断点)。 它们再次有效地阻止您的程序。 -
enable [breakpoints] once list…
暂时启用指定的断点。 gdb 在停止程序后立即禁用任何这些断点。 -
enable [breakpoints] count count list…
暂时启用指定的断点。 gdb 记录每个指定断点的计数,并在命中断点时减少断点的计数。
当任意计数达到 0 时,gdb 会禁用该断点。 如果断点有忽略计数,则在计数受到影响之前,该计数将递减至 0。 -
enable [breakpoints] delete list…
使指定的断点工作一次,然后就死掉。 一旦你的程序停止在那里,gdb 就会删除任何这些断点。 tbreak 命令设置的断点在此状态下开始。 -
delete 断点编号,删除某个断点。
backtrace和frame
backtrace
回溯(backtrace)是程序如何到达当前位置的摘要。 它每帧显示一行,对于许多帧,从当前正在执行的帧(零帧)开始,然后是其
调用者(第一帧),并在堆栈上。
要打印整个堆栈的回溯,请使用 backtrace 命令或其别名 bt。
此命令将为堆栈中的帧每帧打印一行。 默认情况下,所有堆栈框架被打印。
可以随时通过输入系统中断来停止回溯字符,通常是 Ctrl-c。
backtrace [option]... [qualifier]... [count]
bt [option]... [qualifier]... [count]
- backtrack / bt: 打印整个堆栈的回溯
- thread apply all bt : 所有线程堆栈信息
可选计数可以是以下之一:
-
n
仅打印最里面的 n 帧,其中 n 是正数。
-
-n
只打印最外面的n帧,其中n是正数。
选项(option):
- -full :打印局部变量的值。 这个可以组合使用可选计数来限制显示的帧数。
- -no-filters :不要在此回溯上运行 Python 帧过滤器。关闭所有帧过滤器。 这仅在 gdb 有时才相关已配置 Python 支持。
-hide 帧过滤器可能决定“删除”某些帧。 通常,此类省略的框架仍会打印,但会缩进相对于导致它们被忽略的过滤帧。 这-hide 选项会导致根本不打印省略的帧。
backtrace 命令还支持许多选项,这些选项允许覆盖由 set backtrace 和 set print 设置的相关全局打印设置
子命令:
-
-past-main [on|off] :设置回溯是否应继续经过 main
-
-past-entry [on|off] :设置回溯是否应继续经过入口点程序。
-
-entry-values no|only|preferred|if-needed|both|compact|default :在函数入口处设置函数参数的打印。
-frame-arguments all|scalars|none :设置非标量框架参数的打印
-raw-frame-arguments [on|off] :设置是否以原始形式打印帧参数
-frame-info auto|source-line|location|source-andlocation|location-and-address|short-location :设置打印帧信息
保留可选限定符是为了向后兼容。 它可以是一个以下的:
- full :相当于-full 选项
- no-filters :相当于-no-filters 选项
- hide :相当于-hide 选项
frame
大多数用于检查程序中堆栈和其他数据的命令都适用于当前选择的堆栈帧。 以下是选择堆栈帧的命令;
所有这些都通过打印刚刚选择的堆栈框架的简短描述来完成。
frame [ frame-selection-spec ]
f [ frame-selection-spec ]
帧命令允许选择不同的堆栈帧。 frameselection-spec
可以是以下任何一个
- evel num 选择帧级别编号
回想一下,第 0 帧是最里面的(当前正在执行的)帧,第 1 帧是调用最里面的帧的帧,依此类推。 最高级别的框架通常是主框架。
由于这是导航帧堆栈的最常见方法,字符串级别可以省略。 例如,以下两个命令是等效的:
(gdb) frame 3
(gdb) frame level 3
- address stack-address
选择具有堆栈地址 stack-address 的帧。 帧的堆栈地址可以在信息帧的输出中看到,例如
(gdb) info frame
Stack level 1, frame at 0x7fffffffda30:
rip = 0x40066d in b (amd64-entry-value.cc:59); saved rip 0x4004c5
tail call frame, caller of frame at 0x7fffffffda30
source language c++.
Arglist at unknown address.
Locals at unknown address, Previous frame’s sp is 0x7fffffffda30
该帧的堆栈地址是 0x7fffffffda30,如以下行所示:
Stack level 1, frame at 0x7fffffffda30:
-
function function-name
选择函数 function-name 的堆栈帧。 如果函数 function-name 有多个堆栈帧,则选择最里面的堆栈帧。查看堆栈地址 [ pc-addr ] -
view stack-address [ pc-addr ]
查看不属于 gdb 回溯的帧。 所查看的帧具有堆栈地址 stack-addr,以及可选的程序计数器地址 pc-addr。
这主要在堆栈帧的链接被错误损坏、导致 gdb 无法为所有帧正确分配编号的情况下有用。 此外,当您的程序具有多个堆栈并在它们之间切换时,这会很有用。
当使用帧视图查看当前回溯之外的帧时,您始终可以使用先前的堆栈帧选择指令之一返回到原始堆栈,例如帧级别 0。 -
up n
在堆栈中向上移动 n 个帧; n 默认为 1。对于正数 n,这将前进到最外层的帧、更高的帧编号、存在时间更长的帧。
-
down n
在堆栈中向下移动 n 个帧; n 默认为 1。对于正数 n,这将向最里面的帧前进,到较低的帧编号,到最近创建的帧。 可以像这样做一样缩写。
所有这些命令都以打印描述该帧的两行输出结束。 第一行显示帧号、函数名称、参数以及该帧中执行的源文件和行号。 第二行显示该源行的文本。例如:
(gdb) up
#1 0x22f0 in main (argc=1, argv=0xf7fffbf4, env=0xf7fffbfc)
at env.c:10
10 read_input_file (argv[i]);
在这样的打印输出之后,不带参数的 list 命令打印以帧中执行点为中心的十行。 还可以通过键入 edit 使用最喜欢的编辑程序在执行时编辑程序。
-
select-frame [ frame-selection-spec ]
select-frame 命令是frame 的一个变体,它在选择新的frame 后不显示新的frame。 该命令主要用于 gdb 其中的输出可能是不必要的并且会分散注意力 -
up-silently n
down-silently n
这两个命令分别是 up 和 down 的变体; 它们的不同之处在于它们默默地完成工作,不会导致新帧的显示。 它们主要用于 gdb 命令脚本,其中的输出可能是不必要的并且会分散注意力 -
info frame
info f此命令打印所选堆栈帧的详细描述,包括:
- 帧的地址
- 下一个帧的地址(由该帧调用)
- 下一个帧的地址(该帧的调用者)
- 编写与该框架对应的源代码的语言
- 帧参数的地址
- 帧局部变量的地址
- 其中保存的程序计数器(调用者框架中的执行地址)
- 帧中保存了哪些寄存器
当出现问题导致堆栈格式不符合通常的约定时,详细描述非常有用。
list
要打印源文件中的行,请使用 list 命令(缩写为 l)。
默认情况下,打印十行。
有多种方法可以指定要打印文件的哪一部分;
命令格式及作用:
- list : 输出上一次list命令显示的代码后面的代码 : 如果是第一次执行list命令 : 则会显示当前正在执行代码位置附近的码;
- list - : 带一个减号 : 显示上一次list命令显示的代码前面的代码
- list LineNo : 显示当前代码文件第 LineNo 行附近的代码
- list FileName:LineNo : 显示 FileName 文件第 LineNo 行附近的代码
- list FunctionName : 显示当前文件的 FunctionName 函数附近的代码
- list FileName:FunctionName : 显示 FileName 文件的 FunctionName 函数附件的代码
- list from,to : 其中from和to是具体的代码位置 : 显示这之间的代码
list命令默认只会输出 10 行源代码 : 也可以使用如下命令修改
- show listsize : 查看 list 命令显示的代码行数
- set listsize count : 设置 list 命令显示的代码行数为 count
- set listsize unlimited :使 list 命令显示源代码行数(除非 list 参数明确指定其他数字)。 将 count 设置为无限制或 0 表示没有限制。
-
print expr : 计算表达式 expr 并显示结果值。 该表达式可以包括对正在调试的程序中的函数的调用。
-
print <param_name> : 用于在调试过程中查看变量的值
-
print <param_name>=value : 用于在调试过程中修改变量的值
-
print a+b+c : 可以进行一定的表达式计算 : 这里是计算a、b、c三个变量之和
-
print func() : 输出func函数执行的结果 : 常见的用途是打印系统函数执行失败原因:print strerror(errno)
-
*print this : 在c++对象中 : 可以输出当前对象的各成员变量的值
whatis和ptype
命令格式及功能:
- whatis val: 用于查看变量类型;
- ptype val: 作用和 whatis 类似,但功能更强大,可以查看复合数据类型,会打印出该类型的成员变量。
thread
命令格式及作用:
- info thread:查看当前进程的所有线程运行情况;
- thread 线程编号:切换到具体编号的线程上去;
next、step
next 和 step 都是单步执行,但也有差别:
- next 是 单步步过(step over):即遇到函数直接跳过,不进入函数内部。
- step 是 单步步入(step into):即遇到函数会进入函数内部。
return、finish
return 和 finish 都是退出函数,但也有差别:
- return: 命令是立即退出当前函数,剩下的代码不会执行了,return 还可以指定函数的返回值。
- finish :命令是会继续执行完该函数剩余代码再正常退出。
until
Until 执行直到程序到达大于当前帧或当前帧内指定位置(与break 命令相同的参数)的源代码行。
执行到指定位置停下来,命令参数和 break 命令一样
jump
- jump LineNo:跳转到代码的 LineNo 行的位置;
- jump +10:跳转到距离当前代码下10行的位置;
- jump *0x12345678:跳转到 0x12345678 地址的代码处,地址前要加星号;
有2点注意的:
- 中间跳过的代码是不会执行的;
- 跳到的位置后如果没有断点,那么GDB会自动继续往后执行;
disassemble
该命令用于查看某段代码的汇编指令。
set args 和 show args
很多程序启动需要我们传递参数,set args 就是用来设置程序启动参数的,show args 命令用来查询通过 set args 设置的参数
- set args args1:设置单个启动参数 args1;
- set args “-p” “password”:如果单个参数之间有空格,可以使用引号将参数包裹起来;
- set args args1 args2 args3:设置多个启动参数,参数之间用空格隔开;
- set args:不带参数,则清除之前设置的参数;
tbreak
设置仅针对一次停止启用的断点。 参数与break命令相同,断点设置方式相同,但程序第一次停止后断点会自动删除。
watch
只要表达式的值发生变化,就可以使用观察点来停止执行,而无需预测可能发生这种情况的特定位置。 (这有时称为数据断点)
表达式可以像单个变量的值一样简单,也可以像通过运算符组合的多个变量一样复杂。
示例包括:
• 对单个变量值的引用。
• 地址转换为适当的数据类型。 例如,“*(int )0x12345678
”将监视指定地址处的 4 字节区域(假设 int 占用 4 字节)。
• 任意复杂的表达式,例如“ab + c/d
”。 该表达式可以使用程序本机语言中有效的任何运算符。
watch 命令用来监视一个变量或者一段内存,当这个变量或者内存的值发生变化时,GDB就会中断下来。被监视的某个变量或内存地址会产生一个 watch point(观察点)。
- watch 整型变量;
- watch 指针变量,监视的是指针变量本身
- watch *指针变量,监视的是指针所指的内容
- watch 数组变量或内存区间
通过 info watch 命令可以查看当前所有监视的变量,通过 delete watch编号 可以删除对某个变量的监视。yo
多线程调试
在某些操作系统中,例如 GNU/Linux 和 Solaris,单个程序可能有多个执行线程。
线程的精确语义因操作系统而异,但一般来说,单个程序的线程类似于多个进程,只是它们共享一个地址空间(即它们都可以检查和修改相同的变量)。
另一方面,每个线程都有自己的寄存器和执行堆栈,也许还有私有内存。
多线程程序的编写更容易产生异常或 Bug,例如线程之间因竞争同一资源发生了死锁、多个线程同时对同一资源进行读和写等等。
使用GDB调试多线程,需要监控多个线程的执行过程。
用GDB调试多线程程序时,编译需要添加 -lpthread
参数。
gdb 线程调试工具允许在程序运行时观察所有线程,但每当 gdb 控制时,一个特定的线程始终是调试的焦点。 该线程称为当前线程。 调试命令从当前线程的角度显示程序信息。
每当 gdb 在程序中检测到新线程时,它都会显示该线程的目标系统标识,并显示一条格式为“[New systag]”的消息,其中 systag 是一个线程标识符,其形式根据特定系统而有所不同。 例如,在 gnu/Linux 上,当 gdb 注意到新线程时,您可能会看到
[New Thread 0x41e02940 (LWP 25582)]
。
相比之下,在其他系统上,系统标记只是类似于“process 368”,没有进一步的限定符。
出于调试目的,gdb 将其自己的线程号(始终是单个整数)与下级( inferior )线程的每个线程相关联。 该数字在下级的所有线程之间是唯一的,但在不同下级的线程之间不是唯一的。
可以使用限定的inferior-num.thread-num
语法(也称为限定的线程ID)引用下级中的给定线程,其中inferior-num是下级编号,thread-num是给定下级的线程编号。 例如,线程 2.3 指的是下级线程 2 的线程号 3。如果省略了下级线程号(例如线程 3),则 gdb 会推断您指的是当前下级线程。
在创建第二个下级之前,gdb 不会显示线程 ID 的下级编号部分,即使始终可以使用完整的下级编号.线程编号形式来引用下级 1(初始下级)的线程。
某些命令接受空格分隔的线程 ID 列表作为参数。 列表元素可以是:
- “info threads”显示的第一个字段中显示的线程 ID,带或不带劣质限定符。 例如,“2.1”或“1”。
- 线程号范围,同样带或不带 inferior 限定符,如 inf.thr1- thr2 或 thr1-thr2 中。 例如,“1.2-4”或“2-4”。
下级的所有线程,用星号通配符指定,带或不带下级限定符,如 inf.*(
例如“1.*
”)或 *
。 前者是指给定的下级的所有线程,而后者不带下级限定符的形式是指当前下级的所有线程。*
例如,当前inferior为1,inferior 7有1个ID为7.1的线程,则线程列表’1 2-3 4.5 6.7-9 7.*'包含inferior 1的线程1到3,inferior 4的线程5 、inferior 6 的线程 7 到 9 以及inferior 7 的所有线程。即,在扩展限定形式中,与“1.1 1.2 1.3 4.5 6.7 6.8 6.9 7.1”相同。
除了每个下级编号之外,每个线程还分配有一个唯一的全局编号,也称为全局线程 ID,是一个整数。 与线程 ID 的线程号组成部分不同,即使您正在调试多个下级线程,也没有两个线程具有相同的全局 ID。
从 gdb 的角度来看,一个进程总是至少有一个线程。 换句话说,即使程序不是多线程的,gdb 也会为程序的“主线程”分配一个线程号。
调试多线程程序的工具
-
新线程的自动通知
-
thread thread-id:在线程之间切换的命令,切换为 编号为thread-id的线程
-
info thread: 查询现有所有线程的命令,打印所有线程的信息
-
thread apply [thread-id-list | all] args:将命令应用于线程列表的命令
-
break location thread 线程编号:在 location 位置设置普通断点,该断点只作用在特定编号的线程上;
-
线程特定的断点
-
set print thread-events:控制线程启动和退出时消息的打印
-
set libthread-db-search-path path: 如果默认选择与程序不兼容,用户可以指定要使用的 libthread_db
停止和开始多线程模式
gdb 支持调试具有多个线程的程序。
有两种模式可以在调试器中控制程序的执行。
在默认模式(称为全停止模式 All-Stop Mode)下,当程序中的任何线程停止时(例如,在断点处或单步执行时),程序中的所有其他线程也会被 gdb 停止。
在某些目标上,gdb 还支持不间断模式(Non-Stop Mode),在该模式下,当调试器中检查停止的线程时,其他线程可以继续自由运行。
All-Stop Mode
使用GDB调试多线程程序时,默认的调试模式是:一个线程暂停运行,其他线程也随即暂停;一个线程启动运行,其他线程也随即启动。但在一些场景中,我们希望只让特定线程运行,其他线程都维持在暂停状态,即要防止线程切换,要达到这种效果,需要借助 set scheduler-locking 命令。
-
set scheduler-locking mode : 设置调度程序锁定模式。 它适用于正常执行、记录模式和重放模式。
-
set scheduler-locking off : 没有锁定,任何线程都可以随时运行
-
set scheduler-locking on : 当下级线程恢复时,只有当前线程可以运行
-
set scheduler-locking step :
当step时,其行为类似于当前线程打开,其他线程关闭。 当单步执行时,当前线程以外的线程永远没有机会运行,而当使用“continue”、“until”或“finish”等命令时,它们可以完全自由地运行。 该模式针对单步进行了优化; 它可以防止其他线程在单步执行时抢占当前线程,这样调试的焦点就不会意外改变。 但是,除非另一个线程在其时间片内遇到断点,否则 gdb 不会将当前线程更改为远离正在调试的线程。
-
set scheduler-locking step replay: 其行为类似于重播模式下的打开状态,以及记录模式或正常执行期间的关闭状态。 这是默认模式。
-
-
show scheduler-locking : 查看线程锁定状态;
默认情况下,当发出诸如 continue、next 或 step 等执行命令之一时,gdb 仅允许当前下级线程运行。
-
例如,如果 gdb 附加到两个下级线程,每个下级线程有两个线程,则 continue 命令仅恢复当前下级线程的两个线程。
-
例如,当调试 fork 出的程序并且您希望在调试子程序时保持父程序停止(例如,这样它就不会运行退出)时,这很有用。
在其他情况下,你可能对检查 gdb 附加到的任何进程的当前状态不感兴趣,并且你可能希望恢复所有进程,直到遇到某个断点。 在后一种情况下,可以使用 set Schedule-multiple
命令指示 gdb 允许所有下级的所有线程运行。
-
set schedule-multiple
设置发出执行命令时允许恢复多个进程的线程的模式。 打开时,允许所有进程的所有线程运行。 关闭时,仅恢复当前进程的线程。 默认关闭。 当设置为打开时,或者当您单步执行并设置为单步执行时,调度程序锁定模式优先。
-
show schedule-multiple
显示当前多进程线程恢复执行的模式。
Non-Stop Mode
对于某些多线程目标,gdb 支持一种可选的操作模式,可以在调试器中检查已停止的程序线程,而其他线程继续自由执行。
这可以最大限度地减少调试实时系统时的入侵,例如某些线程具有实时限制或必须继续响应外部事件的程序。 这称为不间断模式(Non-Stop Mode)。
在不间断模式下,当一个线程停止报告调试事件时,只有该线程停止; 与全停止模式行为相反,gdb 也不会停止其他线程。
此外,诸如 continue 和 step 之类的执行命令默认仅适用于不间断模式下的当前线程,而不是像全停止模式下那样适用于所有线程。
这允许你可以在全停止模式下不可能的方式显式控制线程 - 例如,单步执行一个线程,同时允许其他线程自由运行,单步执行一个线程,同时保持所有其他线程停止,或者独立且同时单步执行多个线程。
要进入不间断模式,请在运行或附加到程序之前使用以下命令序列:
# If using the CLI, pagination breaks non-stop.
set pagination off
# Finally, turn it on!
set non-stop on
可以使用这些命令来操作不间断模式设置:
- set non-stop on :启用不间断模式选择。
- set non-stop off:禁用不间断模式选择。
- show non-stop:显示当前的不间断启用设置。
Reference
- GDB使用详解