GDB使用手册(五)、在GDB下运行程序

 在GDB下运行程序时,必须先在编译时生成调试信息。
 可以在选择的环境中带参数(如果有)启动GDB 。如果我们在做本地调试,我们可以重定向程序的输入和输出,调试一个已经运行的进程,或者杀死一个子进程。

编译:编译调试

 为了有效地调试程序,需要在编译时生成调试信息。此调试信息存储在目标文件中;它描述了每个变量或函数的数据类型以及可执行代码中源代码行号和地址之间的对应关系。
 要请求调试信息,请在运行编译器时指定“-g”选项。
 对要交付给客户的程序使用“-o”编译器选项进行优化编译。但是,一些编译器无法同时处理“-g”和“-o”选项。使用这些编译器,无法生成包含调试信息的优化可执行文件。
 GCC 是 GNU C/C++ 编译器,支持带或不带“-o”的“-g”,从而可以调试优化的代码。我们建议在编译程序时始终使用“-g”。
 旧版本的 GNU C 编译器允许使用变体选项“-gg”来获取调试信息。 GDB 不再支持这种格式;如果 GNU C 编译器有这个选项,不要使用它。
 GDB 知道预处理器宏并且可以向展示它们的扩展(参见宏)。如果单独指定 -g 标志,大多数编译器不会在调试信息中包含有关预处理器宏的信息。如果使用 DWARF 调试格式,则 GNU C 编译器 GCC 的 3.1 版和更高版本会提供宏信息,并指定选项 -g3。
 有关影响调试信息的 GCC 选项的更多信息,请参阅使用 GNU 编译器集合 (GCC) 中的调试程序或 GCC 的选项。
 如果使用编译器支持的最新版本的 DWARF 调试格式,将获得最佳调试体验。 DWARF 目前是 GDB 中最具表现力和最受支持的调试格式。

开始:启动程序

run
#or
r

 使用 run 命令在 GDB 下启动程序。必须首先使用 GDB 的参数指定程序名称(请参阅进入和退出 GDB),或者使用 file 或 exec-file 命令(请参阅指定文件的命令)。
 如果我们在支持进程的执行环境中运行程序,run 会创建一个低级进程并让该进程运行程序。在某些没有进程的环境中,run会跳转到程序的开头。其他目标,例如“remote”,始终在运行。如果收到这样的错误消息:

The "remote" target does not support "run".
Try "help target" or "continue".

 然后使用continue运行程序。可能需要先load(请参阅加载)。
 程序的执行受到它从上级接收到的某些信息的影响。 GDB 提供了指定此信息的方法,必须在启动程序之前执行这些操作。 (可以在启动程序后更改它,但此类更改只会在下次启动程序时影响程序。)这些信息可能分为四类:

参数。

 指定参数来将程序作为run命令的参数。如果目标上有一个 可用的shell,则该 shell 用于传递参数,以便可以使用常规约定(例如通配符扩展或变量替换)来描述参数。在 Unix 系统中,shell的环境变量为SHELL。如果我们没有定义 SHELL,GDB 使用默认的 shell (/bin/sh)。可以使用 set startup-with-shell 命令禁用任何 shell(详见下文)。

环境。

 程序通常从 GDB 继承其环境,但可以使用 GDB 命令 set environment 和 unset environment 来更改跟程序的相关的环境。

工作目录

 可以使用命令 set cwd 设置程序的工作目录。如果我们没有用这个命令设置任何工作目录,程序在本地调试时将继承 GDB 的工作目录,如果在远程调试时继承远程服务器的工作目录。

标准输入和输出。

 程序通常使用与 GDB 相同的设备进行标准输入和标准输出。可以在run命令行中重定向输入和输出,也可以使用 tty 命令为程序设置不同的设备。
 警告:当输入和输出重定向工作时,不能使用管道将正在调试的程序的输出传递给另一个程序;如果我们尝试这样做,GDB 很可能会调试错误的程序。
 当使用run命令时,程序立即开始执行。程序停止后,可以使用 print 或 call 命令调用程序中的函数。
 如果自 GDB 上次读取其符号以来,符号文件的修改时间发生了变化,则 GDB 丢弃其符号表,并再次读取它。当它这样做时,GDB 会尝试保留当前的断点。

  • start
     主程序的名称因语言而异。对于 C 或 C++,主过程名称始终是 main,但其他语言(例如 Ada)不需要为其主过程指定特定名称。调试器提供了一种方便的方式来启动程序的执行并在主过程的开始处停止,具体取决于所使用的语言。
     “start”命令相当于在主过程开始时设置一个临时断点,然后调用“run”命令。
     一些程序包含一个细化阶段,其中一些启动代码在调用主过程之前执行。这取决于用于编写程序的语言。例如,在 C++ 中,静态和全局对象的构造函数在调用 main 之前执行。因此,调试器可能会在到达主程序之前停止。但是,临时断点仍将停止执行。
     指定要提供给程序的参数作为“start”命令的参数。这些参数将逐字提供给底层的“run”命令。请注意,如果在后续调用“start”或“run”期间未提供任何参数,则将重复使用相同的参数。
     有时需要在细化过程中调试程序。在这些情况下,使用 start 命令停止执行程序会很慢,因为程序已经完成了细化阶段。在这些情况下,要么在运行程序之前在精化代码中插入断点,要么使用 starti 命令。
  • starti
     “starti”命令相当于在程序执行的第一条指令处设置一个临时断点,然后调用“run”命令。对于包含细化阶段的程序,starti 命令将在细化阶段开始时停止执行。
set exec-wrapper wrapper
show exec-wrapper
unset exec-wrapper

 当设置了“exec-wrapper”时,指定的wrapper用于启动程序进行调试。 GDB 使用 exec wrapper program 形式的 shell 命令启动程序。引号会添加到程序及其参数中,但不会添加到wrapper中,因此应该在shell 中添加适合的引号。wrapper一直运行,直到它执行程序,然后 GDB 获得控制权。
 可以使用任何最终调用 execve 及其参数的程序作为wrapper。几个标准的 Unix 实用程序可以做到这一点,例如env和nohup。任何以 exec “$@” 结尾的 Unix shell 脚本也可以工作。

 例如,可以使用 env 将环境变量传递给被调试程序,而无需在 shell 的环境中设置该变量:

(gdb) set exec-wrapper env 'LD_PRELOAD=libtest.so'
(gdb) run

 此命令在大多数目标上本地调试时可用,不包括 DJGPP、Cygwin、MS Windows 和 QNX Neutrino。

set startup-with-shell
set startup-with-shell on
set startup-with-shell off
show startup-with-shell

 在 Unix 系统上,默认情况下,如果目标上有可用的 shell,则 GDB 使用它来启动程序。 run 命令的参数被传递给 shell,shell 执行变量替换、扩展通配符和执行 I/O 重定向。在某些情况下,禁用 shell 的这种使用可能很有用,例如,在调试 shell 本身或诊断启动失败时,例如:

(gdb) run
Starting program: ./a.out
During startup program terminated with signal SIGSEGV, Segmentation fault.

 这表明用“exec-wrapper”指定的shell或wrapper崩溃了,而不是程序。大多数情况下,这是由 shell 的非交互模式初始化文件中的一些奇怪的东西引起的——例如 C-shell 的 .cshrc、Z shell 的 $.zshenv 或 BASH 的 BASH_ENV 环境变量中指定的文件。

set auto-connect-native-target
set auto-connect-native-target on
set auto-connect-native-target off
show auto-connect-native-target

 默认情况下,如果当前的下级还没有连接到任何目标(例如,使用target remote),run命令将程序作为本地计算机上 GDB 下的本机进程启动。如果确定不想在本地机器上调试程序,可以使用 set auto-connect-native-target off 命令告诉 GDB 不要自动连接到本机目标。
 如果打开,这是默认设置,并且如果当前下级尚未连接到目标,则运行命令会自动连接到本机目标(如果有可用的话)。
 如果关闭,并且当前下级尚未连接到目标,则运行命令将失败并显示错误:

(gdb) run
Don't know how to run.  Try "help target".

 如果当前的下级已经连接到一个目标,GDB 总是将它与 run 命令一起使用。
 在任何情况下,都可以使用 target native 命令显式连接到本机目标。例如,

(gdb) set auto-connect-native-target off
(gdb) run
Don't know how to run.  Try "help target".
(gdb) target native
(gdb) run
Starting program: ./a.out
[Inferior 1 (process 10421) exited normally]

 如果显式连接到本机目标,即使所有次要目标都退出,GDB 仍保持连接,为下一个run命令做好准备。使用 disconnect 命令断开连接。
 同样设置 auto-connect-native-target一样 设置的其他命令示例:attach、info proc、info os。

set disable-randomization
set disable-randomization on

 此选项(在 GDB 中默认启用)将关闭已启动程序的虚拟地址空间的本机随机化。此选项对于多个调试会话很有用,以使执行具有更好的可重现性,并且内存地址可跨调试会话重用。
 此功能仅在某些目标上实现,包括 GNU/Linux。在 GNU/Linux 上,可以使用

(gdb) set exec-wrapper setarch `uname -m` -R
set disable-randomization off

 保持启动的可执行文件的行为不变。一些错误只有在程序加载到特定地址时才会露出丑陋的脑袋。如果在 GDB 下运行程序时错误消失了,那可能是因为 GDB 默认禁用平台上的地址随机化,例如 GNU/Linux,这些平台对独立程序执行此操作。使用 set disable-randomization off 来尝试重现此类难以捉摸的错误。
 在可用的目标上,虚拟地址空间随机化可保护程序免受某些类型的安全攻击。在这些情况下,攻击者需要知道具体可执行代码的确切位置。随机化其位置使得不可能在其预期地址处注入滥用代码的跳转。
 预链接共享库提供了启动性能优势,但它使这些库中的地址可以通过在目标系统上进行非特权访问来预测特权进程。读取共享库二进制文件可以提供足够的信息来组装滥用它的恶意代码。即使是预链接的共享库也可以在新的随机地址加载,只需要在启动期间进行常规重定位过程。尚未预链接的共享库始终加载到随机选择的地址。
 与位置无关的可执行文件 (PIE) 包含与共享库类似的与位置无关的代码,因此此类可执行文件在启动时会加载到随机选择的地址。 PIE 可执行文件总是在随机地址加载甚至已经预链接的共享库。可以使用 gcc -fPIE -pie 构建这样的可执行文件。
 堆(malloc 存储)、堆栈和自定义 mmap 区域总是随机放置(只要启用了随机化)。

show disable-randomization

 显示显式禁用已启动程序的虚拟地址空间的本机随机化的当前设置。

参数:程序的参数

 程序的参数可以由 run 命令的参数指定。它们被传递到 shell,它扩展通配符并执行 I/O 重定向,然后再传递给程序。 SHELL 环境变量(如果存在)指定 GDB 使用的 shell。如果我们没有定义 SHELL,GDB 使用默认的 shell(Unix 上的 /bin/sh)。
 在非 Unix 系统上,程序通常由 GDB 直接调用,GDB 通过适当的系统调用模拟 I/O 重定向,通配符由程序的启动代码而不是 shell 扩展。
 不带参数运行使用与上一次运行相同的参数,或由 set args 命令设置的参数。

set args

 指定下次运行程序时要使用的参数。如果 set args 没有参数,run 会不带参数地执行程序。一旦我们用参数运行了程序,在下一次运行之前使用 set args 是在没有参数的情况下再次运行它的唯一方法。

show args

 显示参数以在程序启动时提供给程序。

环境:程序环境

 环境由一组环境变量及其值组成。环境变量通常会记录用户名、主目录、终端类型以及要运行的程序的搜索路径等内容。通常使用 shell 设置环境变量,它们会被运行的所有其他程序继承。调试时,尝试在修改后的环境中运行程序而不必重新启动 GDB 会很有用。

path directory

 将目录添加到将传递给程序的 PATH 环境变量(可执行文件的搜索路径)的前面。 GDB 使用的 PATH 的值不会改变。可以指定多个目录名称,由空格或系统相关的分隔符分隔(在 Unix 上为“:”,在 MS-DOS 和 MS-Windows 上为“;”)。如果目录已经在路径中,则将其移到最前面,因此搜索得更快。

 可以使用字符串“$cwd”来引用 GDB 搜索路径时的当前工作目录。如果改用“.”,它指的是执行路径命令的目录。在将目录添加到搜索路径之前,GDB 会替换目录参数(使用当前路径)中的“.”。

show paths

 显示可执行文件的搜索路径列表(PATH 环境变量)。

show environment [varname]

 打印环境变量 varname 的值,以便在程序启动时提供给它。如果不提供 varname,请打印要提供给程序的所有环境变量的名称和值。可以将环境缩写为 env。

set environment varname [=value]

 将环境变量 varname 设置为 value。程序(以及 GDB 用来启动它的 shell)的值会发生变化,而不是 GDB 本身。该值可以是任何字符串;环境变量的值只是字符串,任何解释都由程序本身提供。 value 参数是可选的;如果清除,则将变量设置为空值。
 例如,这个命令:

set env USER = foo

 告诉被调试程序,当随后运行时,它的用户名为“foo”。 (为了清晰可见,“=”前后都使用一个空格符;它们实际上不是必需的。)
 请注意,在 Unix 系统上,GDB 通过 shell 运行程序,该 shell 还继承了 set environment 设置的环境。如有必要,可以通过使用“env”程序作为wrapper而不是使用set environment来避免这种情况。请参阅 set exec-wrapper,以获取执行此操作的示例。
 用户设置的环境变量也会传输到 gdbserver 以在启动远程下级时使用。请参阅 QEnvironmentHexEncoded。

unset environment varname

 从环境中删除变量 varname 以传递给程序。这与‘set env varname =’不同; unset environment 从环境中删除变量,而不是为其分配一个空值。
 启动远程下级时,用户未设置的环境变量也会在 gdbserver 上未设置。请参阅 QEnvironmentUnset。
 警告:在 Unix 系统上,GDB 使用由 SHELL 环境变量指示的 shell 运行程序,如果它存在(或者 /bin/sh 如果不存在)。如果 SHELL 变量命名了一个在非交互方式启动时运行初始化文件的 shell — 例如 C-shell 的 .cshrc、Z shell 的 $.zshenv 或 BASH 的 BASH_ENV 环境变量中指定的文件 —在该文件中设置会影响程序。可能希望将环境变量设置移动到仅在登录时运行的文件,例如 .login 或 .profile。

工作目录:程序的工作目录

 每次使用 run 启动程序时,将使用 set cwd 命令指定的当前工作目录初始化下级。如果该命令没有指定目录,则本机调试时下级将继承GDB的当前工作目录作为其工作目录,或者远程调试时将继承远程服务器的当前工作目录。

set cwd [directory]

 将下级的工作目录设置为目录,该目录将被全局扩展以解析波浪号 (~)。如果未指定任何参数,该命令将清除设置并将其重置为空状态。这个设置对GDB的工作目录没有影响,只有下次启动suberial时才会生效。 ~ in directory是主目录的缩写,通常由 HOME 环境变量指向。在 MS-Windows 上,如果 HOME 没有定义,GDB 使用 HOMEDRIVE 和 HOMEPATH 的串联作为后备。

 还可以使用 cd 命令更改 GDB 的当前工作目录。请参阅 cd 命令。

show cwd

 显示下级的工作目录。如果 set cwd 没有指定目录,则默认的 suber 的工作目录与 GDB 的工作目录相同。

cd [directory]

 将 GDB 工作目录设置为 directory。如果没有给出,directory使用’~'。

 GDB 工作目录用作指定 GDB 操作文件的命令的默认目录。请参阅指定文件的命令。请参阅 set cwd 命令。

pwd

 打印 GDB 工作目录。
 通常不可能找到正在调试的进程的当前工作目录(因为程序可以在运行期间更改其目录)。如果在 GDB 支持 info proc 命令的系统上工作(请参阅进程信息),可以使用 info proc 命令找出被调试对象的当前工作目录。

输入/输出:程序的输入和输出

 默认情况下,在 GDB 下运行的程序会向 GDB 使用的同一终端进行输入和输出。 GDB 将终端切换到它自己的终端模式以与交互,但它会记录程序正在使用的终端模式,并在继续运行程序时切换回它们。

info terminal

 显示 GDB 记录的有关程序正在使用的终端模式的信息。

 可以使用带有 run 命令的 shell 重定向来重定向程序的输入and/or输出。例如,

run > outfile

 启动程序,将其输出转移到文件 outfile。
 另一种指定程序应在何处进行输入和输出的方法是使用 tty 命令。此命令接受文件名作为参数,并使此文件成为未来运行命令的默认文件。它还重置子进程的控制终端,以便将来运行命令。例如,

tty /dev/ttyb

 指示使用后续运行命令启动的进程默认在终端 /dev/ttyb 上进行输入和输出,并将其作为控制终端。
 run 中的显式重定向会覆盖 tty 命令对 input/output设备的影响,但不会覆盖它对控制终端的影响。
 当在运行命令中使用 tty 命令或重定向输入时,只有程序的输入会受到影响。 GDB 的输入仍然来自终端。 tty 是 set lower-tty 的别名。
 可以使用 show lower-tty 命令告诉 GDB 显示将用于程序未来运行的终端的名称。

set inferior-tty [ tty ]

 将被调试程序的 tty 设置为 tty。省略 tty 会恢复默认行为,即使用与 GDB 相同的终端。

show inferior-tty

 显示正在调试的程序的当前 tty。

附加:调试已经运行的进程

attach process-id

 这个命令附加到一个正在运行的进程——一个在 GDB 之外启动的进程。(info files 显示活动目标。)该命令将进程 ID 作为参数。找出 Unix 进程的进程 ID 的常用方法是使用 ps 实用程序,或使用“jobs -l”shell 命令。
 如果在执行命令后再次按 RET,则附加不会重复。
 要使用附加,程序必须在支持进程的环境中运行;例如,attach 不适用于缺少操作系统的裸板目标上的程序。还必须有权向进程发送信号。
 当使用附加时,调试器首先通过查看当前工作目录来查找在进程中运行的程序,然后(如果未找到该程序)使用源文件搜索路径(请参阅指定源目录)。还可以使用 file 命令加载程序。请参阅指定文件的命令。
 如果调试器可以确定在它附加到的进程中运行的可执行文件与 GDB 加载的当前 exec-file 不匹配,则选项 exec-file-mismatch 指定如何处理不匹配。 GDB 尝试通过比较文件的构建 ID(请参阅构建 ID)来比较文件(如果可用)。

set exec-file-mismatch ‘ask|warn|off’

 是否检测当前 GDB 加载的可执行文件与用于启动进程的可执行文件之间的不匹配。如果’ask’,默认,显示警告并询问用户是否加载进程可执行文件;如果是“警告”,则只显示警告;如果“关闭”,则不要尝试检测不匹配。如果用户确认加载进程可执行文件,那么它的符号也将被加载。

show exec-file-mismatch

显示 exec-file-mismatch 的当前值。
 在安排调试指定进程后,GDB 做的第一件事就是停止它。可以使用所有 GDB 命令检查和修改附加的进程,这些命令通常在使用 run 启动进程时可用。可以插入断点;可以单步执行并继续;可以修改存储。如果希望进程继续运行,可以在将 GDB 附加到进程后使用 continue 命令。

detach

 调试完附加进程后,可以使用 detach 命令将其从 GDB 控制中释放。分离进程继续执行。在 detach 命令之后,该进程和 GDB 再次变得完全独立,可以附加另一个进程或使用 run 启动一个进程。如果在执行命令后再次按 RET,则分离不会重复。
 如果在有附加进程时退出 GDB,则会分离该进程。如果使用 run 命令,则会终止该进程。默认情况下,如果尝试执行其中任何一项操作,GDB 会要求确认;可以使用 set confirm 命令控制是否需要确认(请参阅可选警告和消息)。

终止进程:杀死子进程

kill

 杀死程序在 GDB 下运行的子进程。
 如果希望调试核心转储而不是正在运行的进程,则此命令很有用。 当程序运行时,GDB 会忽略任何核心转储文件。
 在某些操作系统上,如果在 GDB 内部设置了断点,则无法在 GDB 外部执行程序。 在这种情况下,可以使用 kill 命令来允许在调试器之外运行程序。
 如果希望重新编译和重新链接程序,kill 命令也很有用,因为在许多系统上,当可执行文件在进程中运行时无法修改它。 在这种情况下,当下一次键入 run 时,GDB 会注意到文件已更改,并再次读取符号表(同时尝试保留当前的断点设置)。

劣质连接和程序:调试多个作废的连接和程序

 GDB 允许在单个会话中运行和调试多个程序。此外,某些系统上的 GDB 可能允许同时运行多个程序(否则必须先退出一个程序才能启动另一个程序)。在某些系统上,GDB 甚至可以让在不同的远程系统上同时调试多个程序。在最一般的情况下,可以在多个进程中的每个进程中拥有多个执行线程,从多个可执行文件启动,在不同的机器上运行。
 GDB 用一个称为下级的对象表示每个程序执行的状态。下级通常对应于一个进程,但更通用,也适用于没有进程的目标。下级可以在进程运行之前创建,并且可以在进程退出后保留。 Inferior 具有与进程 ID 不同的唯一标识符。通常每个下级也会有自己不同的地址空间,尽管一些嵌入式目标可能有几个下级在单个地址空间的不同部分运行。每个低级可能依次运行多个线程。
 要找出任何时候存在的劣势,请使用info inferiors:

info inferiors

 打印当前由 GDB 管理的所有低级的列表。默认情况下会打印所有的低级,但参数 id…——一个以空格分隔的低级数字列表——可用于将显示限制为只显示请求的低级。
 GDB 显示每个劣等(按此顺序):
GDB 分配的下级编号
目标系统的下级标识符
下级绑定的目标连接,包括GDB分配的唯一连接号,以及连接使用的协议。
下级正在运行的可执行文件的名称。
GDB 劣质编号前的星号“*”表示当前劣质。
例如,

(gdb) info inferiors
  Num  Description       Connection                      Executable
* 1    process 3401      1 (native)                      goodbye
  2    process 2307      2 (extended-remote host:10000)  hello

 要获取有关当前劣质的信息,请使用inferior:

inferior

 显示有关当前下级的信息。
例如,

(gdb) inferior
[Current inferior is 1 [process 3401] (helloworld)]

 要找出随时存在哪些打开的目标连接,请使用info connections:

info connections

 打印当前由 GDB 管理的所有打开的目标连接的列表。默认情况下会打印所有连接,但参数 id… - 以空格分隔的连接编号列表 - 可用于将显示限制为仅显示请求的连接。
GDB 显示每个连接(按此顺序):
GDB 分配的连接号。
连接使用的协议。
连接使用的协议的文本描述。
连接号前面的星号“*”表示当前下级的连接。
例如,

(gdb) info connections
  Num  What                        Description
* 1    extended-remote host:10000  Extended remote serial target in gdb-specific protocol
  2    native                      Native process
  3    core                        Local core dump file

 要在低级之间切换焦点,请使用 lower 命令:

inferior infno

 将无效数字 infno 设为当前劣势。参数 infno 是 GDB 分配的下级编号,如“info inferiors”显示的第一个字段所示。
 调试器便利变量“$_inferior”包含当前劣质的编号。可能会发现这在编写断点条件表达式、命令脚本等时很有用。有关便利变量的一般信息,请参阅便利变量。
 可以通过 add-inferior 和 clone-inferior 命令将多个可执行文件放入调试会话。在某些系统上,GDB 可以通过调用 fork 和 exec 来自动向调试会话添加低级。要从调试会话中删除劣势,请使用 remove-inferiors 命令。

add-inferior [ -copies n ] [ -exec executable ] [-no-connection ]

 添加 n 次要使用可执行文件作为可执行文件运行; n 默认为 1。如果未指定可执行文件,则下级开始为空,没有程序。仍然可以随时使用带有可执行文件名称作为参数的 file 命令来分配或更改分配给下级的程序。
 默认情况下,新的下级开始连接到与当前下级相同的目标连接。例如,如果当前的下级连接到具有目标远程的 gdbserver,那么新的下级将连接到同一个 gdbserver 实例。 “-no-connection”选项启动新的下级,但还没有连接。然后,可以例如使用 target 远程命令连接到其他一些 gdbserver 实例,使用 run 生成本地程序等。

clone-inferior [ -copies n ] [ infno ]

 添加 n 个准备执行与下级 infno 相同的程序的下级; n 默认为 1,infno 默认为当前下级数。此命令将 args、inferior-tty 和 cwd 属性的值从当前的低级复制到新的低级。它还使用 set environment 和 unset environment 命令传播用户对环境变量所做的更改。当想要运行正在调试的下级的另一个实例时,这是一个方便的命令。

(gdb) info inferiors
  Num  Description       Connection   Executable
* 1    process 29964     1 (native)   helloworld
(gdb) clone-inferior
Added inferior 2.
1 inferiors added.
(gdb) info inferiors
  Num  Description       Connection   Executable
* 1    process 29964     1 (native)   helloworld
  2    <null>            1 (native)   helloworld

 现在可以简单地将焦点切换到劣质 2 并运行它。

remove-inferiors infno…

 删除劣质或劣质信息…。无法删除使用此命令运行的下级。对于那些,首先使用 kill 或 detach 命令。
 要退出调试一个非当前下级的正在运行的下级,可以使用分离下级命令从它分离(允许它独立运行),或使用 kill 下级命令将其终止:

detach inferior infno…

 与由 GDB 劣质编号 infno 标识的劣质或劣质分离……。请注意,inferior 的条目仍然保留在 info Superiors 显示的低劣者列表中,但其 Description 将显示为“”。

kill inferiors infno…

 杀死由 GDB 劣质编号 infno 标识的劣质或劣质……。请注意,inferior 的条目仍然保留在 info Superiors 显示的低劣者列表中,但其 Description 将显示为“”。
 在成功完成 detach、detach Superiors、kill 或 kill lowers 等命令后,或者在正常进程退出后,suberial 仍然有效,并列有 info Superiors,准备重新启动。
 要在 GDB 的控制下启动或退出劣质程序时收到通知,请使用 set printinferior-events:

set print inferior-events
set print inferior-events on
set print inferior-events off

 当 GDB 注意到新的低级对象已启动或低级对象已退出或已分离时,set print lower-events 命令允许启用或禁用消息打印。默认情况下,将打印这些消息。

show print inferior-events

 显示当 GDB 检测到低级对象已启动、退出或已分离时是否打印消息。
 许多命令对多个程序的工作方式与对单个程序的工作方式相同:例如,print myglobal 将简单地显示 myglobal 在当前下级中的值。
 有时,在调试 GDB 本身时,在调试会话中获取有关下级、程序、地址空间的关系的更多信息可能会很有用。可以使用 maint info program-spaces 命令执行此操作。

maint info program-spaces

 打印当前由 GDB 管理的所有程序空间的列表。
 GDB 显示每个程序空间(按此顺序):
GDB分配的程序空间号
加载到程序空间的可执行文件的名称,例如 file 命令。
GDB 程序空间编号前的星号“*”表示当前程序空间。
 此外,在每个程序空间行下方,GDB 会打印不适合以表格形式显示的额外信息。例如,绑定到程序空间的劣等列表。

(gdb) maint info program-spaces
  Id   Executable
* 1    hello
  2    goodbye
        Bound inferiors: ID 1 (process 21561)

 在这里我们可以看到,没有次运行程序 hello,而进程 21561 正在运行程序 goodbye。在某些目标上,有可能多个劣等对象绑定到同一个程序空间。最常见的例子是调试 vfork 调用的父进程和子进程。例如,

(gdb) maint info program-spaces
  Id   Executable
* 1    vfork-test
        Bound inferiors: ID 2 (process 18050), ID 1 (process 18045)

 在这里,由于低级 1 执行了 vfork 调用,低级 2 和低级 1 都在同一程序空间中运行。

线程:多线程调试程序

 在某些操作系统中,例如 GNU/Linux 和 Solaris,单个程序可能有多个执行线程。线程的精确语义因操作系统而异,但总的来说,单个程序的线程类似于多个进程——除了它们共享一个地址空间(也就是说,它们都可以检查和修改相同的变量)。另一方面,每个线程都有自己的寄存器和执行堆栈,也许还有私有内存。
 GDB 为调试多线程程序提供了这些工具:
 自动通知新线程
‘thread thread-id’,一个在线程之间切换的命令
‘info threads’,查询现有线程的命令
‘线程应用[线程ID列表| all] args’,将命令应用于线程列表的命令
线程特定的断点
‘set print thread-events’,控制线程启动和退出时消息的打印。
“set libthread-db-search-path path”,如果默认选择与程序不兼容,用户可以指定使用哪个 libthread_db。
 GDB 线程调试工具允许在程序运行时观察所有线程——但每当 GDB 获得控制权时,特别是一个线程始终是调试的焦点。该线程称为当前线程。调试命令从当前线程的角度显示程序信息。
 每当 GDB 在程序中检测到新线程时,它会显示目标系统对线程的标识,并以“[New systag]”形式显示消息,其中 systag 是线程标识符,其形式因特定系统而异。例如,在 GNU/Linux 上,可能会看到

[New Thread 0x41e02940 (LWP 25582)]

 当 GDB 注意到一个新线程时。相比之下,在其他系统上,系统标签只是类似于“进程 368”,没有进一步的限定符。
 出于调试目的,GDB 将其自己的线程号(始终为单个整数)与下级的每个线程相关联。这个数字在下级的所有线程之间是唯一的,但在不同下级的线程之间不是唯一的。
 可以使用限定的 Superior-num.thread-num 语法(也称为限定的线程 ID)来引用下级中的给定线程,inferior-num 是下级编号,thread-num 是给定下级的线程编号。例如,线程 2.3 指的是下级 2 的线程号 3。如果省略了下级编号(例如线程 3),那么 GDB 会推断指的是当前下级的线程。
 在创建第二个低级之前,GDB 不会显示线程 ID 的低级编号部分,即使始终可以使用完整的低级 num.thread-num 形式来引用低级 1(初始低级)的线程。
 一些命令接受以空格分隔的线程 ID 列表作为参数。列表元素可以是:
显示在“信息线程”显示的第一个字段中的线程 ID,带或不带劣质限定符。例如,“2.1”或“1”。
一系列线程号,同样带有或不带有劣等限定符,如 inf.thr1-thr2 或 thr1-thr2。例如,“1.2-4”或“2-4”。
下级的所有线程,用星号通配符指定,带或不带下级限定符,如 inf.(例如,'1.‘)或 .前者指的是给定下级的所有线程,后者没有下级限定符的形式是指当前下级的所有线程。
 例如,如果当前的下级是 1,下级 7 有一个 ID 为 7.1 的线程,则线程列表 '1 2-3 4.5 6.7-9 7.
’ 包括下级 1 的线程 1 到 3,下级 4 的线程 5 , 劣 6 的螺纹 7 至 9 和劣 7 的所有螺纹。即扩展合格形式,同’1.1 1.2 1.3 4.5 6.7 6.8 6.9 7.1’。
 除了 per-inferior 编号之外,每个线程还分配了一个唯一的全局编号,也称为全局线程 ID,一个整数。与线程 ID 的线程号组件不同,没有两个线程具有相同的全局 ID,即使正在调试多个劣等线程。
 从 GDB 的角度来看,一个进程总是至少有一个线程。换句话说,即使程序不是多线程的,GDB 也会为程序的“主线程”分配一个线程号。
 调试器便利变量“ t h r e a d ” 和 “ _thread”和“ thread_gthread”分别包含当前线程的每个劣质线程号和全局线程号。可能会发现这在编写断点条件表达式、命令脚本等时很有用。有关便利变量的一般信息,请参阅便利变量。
 如果 GDB 检测到程序是多线程的,它会使用到达断点的线程的 ID 和名称来增加关于在断点处停止的通常消息。

Thread 2 "client" hit Breakpoint 1, send_message () at client.c:68

 同样,当程序接收到信号时:

Thread 1 "main" received signal SIGINT, Interrupt.
info threads [thread-id-list]

 显示有关一个或多个线程的信息。不带参数显示所有线程的信息。可以使用线程 ID 列表语法指定要显示的线程列表(请参阅线程 ID 列表)。
GDB 显示每个线程(按此顺序):
GDB 分配的 per-inferior 线程数
由 GDB 分配的全局线程号,如果指定了“-gid”选项
目标系统的线程标识符(systag)
线程的名称(如果已知)。线程既可以由用户命名(参见下面的线程名称),也可以在某些情况下由程序本身命名。
该线程的当前堆栈帧摘要
GDB 线程号左侧的星号“*”表示当前线程。
例如,

(gdb) info threads
  Id   Target Id             Frame
* 1    process 35 thread 13  main (argc=1, argv=0x7ffffff8)
  2    process 35 thread 23  0x34e5 in sigpause ()
  3    process 35 thread 27  0x34e5 in sigpause ()
    at threadtest.c:68

 如果正在调试多个低等线程,GDB 会使用限定的低等线程数格式显示线程 ID。否则,仅显示线程编号。
 如果我们指定了‘-gid’选项,GDB 会显示一列来指示每个线程的全局线程 ID:

(gdb) info threads
  Id   GId  Target Id             Frame
  1.1  1    process 35 thread 13  main (argc=1, argv=0x7ffffff8)
  1.2  3    process 35 thread 23  0x34e5 in sigpause ()
  1.3  4    process 35 thread 27  0x34e5 in sigpause ()
* 2.1  2    process 65 thread 1   main (argc=1, argv=0x7ffffff8)

 在 Solaris 上,可以使用特定于 Solaris 的命令显示有关用户线程的更多信息:

maint info sol-threads

 显示有关 Solaris 用户线程的信息。

thread thread-id

 使线程 ID thread-id 成为当前线程。命令参数 thread-id 是 GDB 线程 ID,如“信息线程”显示的第一个字段所示,带有或不带有劣等限定符(例如,“2.1”或“1”)。
 GDB 通过显示选择的线程的系统标识符及其当前堆栈帧摘要来响应:

(gdb) thread 2
[Switching to thread 2 (Thread 0xb7fdab70 (LWP 12747))]
#0  some_function (ignore=0x0) at example.c:8
8	    printf ("hello\n");

 与“[New …]”消息一样,“Switching to”之后的文本形式取决于系统识别线程的约定。

thread apply [thread-id-list | all [-ascending]] [flag]command

 线程应用命令允许将命名命令应用到一个或多个线程。使用线程 ID 列表语法指定想要影响的线程(请参阅线程 ID 列表),或指定 all 以应用于所有线程。要将命令按降序应用于所有线程,请键入 thread apply all command。要将命令按升序应用于所有线程,请键入 thread apply all -ascending command。
 标志参数控制产生什么输出以及如何处理将命令应用于线程时引发的错误。 flag 必须以 - 开头,后跟 qcs 中的一个字母。如果提供了多个标志,则必须分别给出它们,例如 -c -q。
 默认情况下,GDB 会在 command 产生的输出之前显示一些线程信息,并且在命令执行过程中出现的错误将 abort thread apply。以下标志可用于微调此行为:

  • -c
     代表“继续”的标志 -c 会导致显示命令中的任何错误,然后线程应用的执行将继续。
  • -s
     标志 -s 代表“静默”,它会导致命令产生的任何错误或空输出被静默忽略。即继续执行,但不打印线程信息和错误。
  • -q
     标志 -q (‘quiet’) 禁止打印线程信息。

 标志 -c 和 -s 不能一起使用。

taas [option]command

 线程的快捷方式 apply all -s [option]… 命令。在所有线程上应用命令,忽略错误和空输出。
 taas 命令接受与线程应用所有命令相同的选项。请参阅线程全部应用。

tfaas [option]command

 thread apply all -s – frame apply all -s [option]… 命令的快捷方式。对所有线程的所有帧应用命令,忽略错误和空输出。请注意,标志 -s 指定了两次:第一个 -s 确保 thread apply 仅显示 frame apply 为其产生一些输出的线程的线程信息。需要第二个 -s 以确保仅当命令成功生成某些输出时 frame apply 才显示帧的帧信息。
 例如,它可以用于在不知道该变量或参数所在的线程或框架的情况下打印局部变量或函数参数,使用:

(gdb) tfaas p some_local_var_i_do_not_remember_where_it_is

 tfaas 命令接受与 frame apply 命令相同的选项。见框架适用。

thread name [name]

 此命令为当前线程分配一个名称。如果未给出任何参数,则删除任何现有的用户指定名称。线程名称出现在“信息线程”显示中。
 在某些系统上,例如 GNU/Linux,GDB 能够确定操作系统给定的线程名称。在这些系统上,使用“线程名称”指定的名称将覆盖系统名称,删除用户指定的名称将导致 GDB 再次显示系统指定的名称。

thread find [regexp]

 搜索并显示名称或系统标签与提供的正则表达式匹配的线程 ID。
 除了作为“线程名称”命令的补充之外,此命令还允许通过其目标系统标签来识别线程。例如,在 GNU/Linux 上,目标 systag 是 LWP id。

(GDB) thread find 26688
Thread 4 has target id 'Thread 0x41e02940 (LWP 26688)'
(GDB) info thread 4
  Id   Target Id         Frame 
  4    Thread 0x41e02940 (LWP 26688) 0x00000031ca6cd372 in select ()
set print thread-events
set print thread-events on
set print thread-events off

 set print thread-events 命令允许在 GDB 注意到新线程已启动或线程已退出时启用或禁用消息打印。默认情况下,如果目标支持检测这些事件,则将打印这些消息。请注意,不能在所有目标上禁用这些消息。

show print thread-events

 显示当 GDB 检测到线程已经启动和退出时是否会打印消息。
 有关停止和启动具有多个线程的程序时 GDB 的行为方式的更多信息,请参阅停止和启动多线程程序。
 有关具有多个线程的程序中的观察点的信息,请参阅设置观察点。

set libthread-db-search-path [path]

 如果设置了这个变量,path 是一个以冒号分隔的目录列表,GDB 将使用它来搜索 libthread_db。如果省略路径,“libthread-db-search-path”将重置为其默认值(在 GNU/Linux 和 Solaris 系统上为 s d i r : sdir: sdir:pdir)。在内部,默认值来自 LIBTHREAD_DB_SEARCH_PATH 宏。
 在 GNU/Linux 和 Solaris 系统上,GDB 使用“帮助程序”libthread_db 库来获取有关劣等进程中线程的信息。 GDB 将使用“libthread-db-search-path”来查找 libthread_db。如果通过“set auto-load libthread-db”(参见 libthread_db.so.1 文件)启用了劣质特定线程调试库加载,GDB 也会首先进行咨询。
 “libthread-db-search-path”的特殊条目“ s d i r ” 指 的 是 通 常 搜 索 加 载 共 享 库 的 默 认 系 统 目 录 。 “ sdir”指的是通常搜索加载共享库的默认系统目录。 “ sdirsdir”条目是唯一不需要通过“set auto-load libthread-db”启用的条目(参见 libthread_db.so.1 文件)。
 “libthread-db-search-path”的特殊条目“$pdir”指的是在下级进程中加载​​ libpthread 的目录。
 对于 GDB 在上述目录中找到的任何 libthread_db 库,GDB 会尝试使用当前的下级进程对其进行初始化。如果这个初始化失败(这可能是由于 libthread_db 和 libpthread 之间的版本不匹配而发生的),GDB 将卸载 libthread_db,并继续下一个目录。如果没有一个 libthread_db 库初始化成功,GDB 将发出警告并且线程调试将被禁用。
 设置 libthread-db-search-path 目前仅在某些平台上实现。

show libthread-db-search-path

 显示当前 libthread_db 搜索路径。

set debug libthread-db
show debug libthread-db

 打开或关闭 libthread_db 相关事件的显示。使用 1 启用,使用 0 禁用。

set debug threads [on|off]
show debug threads

 当“on”时,GDB 将在创建和删除线程时打印附加消息。

分支:调试分支

 在大多数系统上,GDB 对使用 fork 函数创建附加进程的调试程序没有特别支持。当一个程序fork时,GDB会继续调试父进程,子进程会畅通无阻地运行。如果在子程序执行的任何代码中设置了断点,子程序将获得一个 SIGTRAP 信号,该信号(除非它捕获该信号)将导致它终止。
 但是,如果想调试子进程,则有一个不太痛苦的解决方法。在 fork 之后子进程执行的代码中调用 sleep 。仅当设置了某个环境变量或某个文件存在时才休眠可能很有用,这样当不想在子节点上运行 GDB 时就不会出现延迟。在孩子睡觉时,使用 ps 程序获取其进程 ID。然后告诉 GDB(如果也在调试父进程,则新调用 GDB)附加到子进程(请参阅附加)。从那时起,可以像附加的任何其他进程一样调试子进程。
 在某些系统上,GDB 支持使用 fork 或 vfork 函数创建附加进程的调试程序。在 GNU/Linux 平台上,内核版本 2.5.46 及更高版本支持此功能。
 在本机模式下以及在目标远程模式或目标扩展远程模式下连接到 gdbserver 时,支持 fork 调试命令。
 默认情况下,当一个程序fork时,GDB会继续调试父进程,子进程会畅通无阻地运行。
 如果要跟随子进程而不是父进程,请使用命令 set follow-fork-mode。

set follow-fork-mode mode

 将调试器响应设置为 fork 或 vfork 的程序调用。调用 fork 或 vfork 会创建一个新进程。模式参数可以是:
parent
 原来的进程是在fork之后调试的。子进程畅通无阻地运行。这是默认设置。
child
 新进程在分叉后调试。父进程畅通无阻地运行。

show follow-fork-mode

 显示当前调试器对 fork 或 vfork 调用的响应。
 在 Linux 上,如果要同时调试父进程和子进程,请使用命令 set detach-on-fork。

set detach-on-fork mode

 告诉 gdb 是在分叉后分离其中一个进程,还是保留调试器对它们的控制。

  • on
     子进程(或父进程,取决于 follow-fork-mode 的值)将被分离并允许独立运行。这是默认设置。
  • off
     这两个进程都将由 GDB 控制。一个进程(子进程或父进程,取决于 follow-fork-mode 的值)像往常一样被调试,而另一个被暂停。
show detach-on-fork

 显示 detach-on-fork 模式是否打开/关闭。
如果选择关闭“detach-on-fork”模式,那么 GDB 将保留对所有分叉进程(包括嵌套分叉)的控制。可以使用 info 低级命令列出在 GDB 控制下的分支进程,并使用 lower 命令从一个分支切换到另一个分支(请参阅调试多个下级连接和程序)。
 要退出调试其中一个分叉的进程,可以使用 detach assistants 命令从它分离(允许它独立运行),或者使用 kill lowers 命令将其终止。请参阅调试多个劣质连接和程序。
 如果要求调试子进程并且 vfork 后跟 exec,GDB 将执行新目标直到新目标中的第一个断点。如果在原始程序的 main 上设置了断点,则该断点也将设置在子进程的 main 上。
 在某些系统上,当 vfork 生成子进程时,无法调试子进程或父进程,直到 exec 调用完成。
 如果在执行 exec 调用后向 GDB 发出运行命令,新目标将重新启动。要重新启动父进程,请使用带有父可执行文件名称的 file 命令作为其参数。默认情况下,执行 exec 调用后,GDB 会丢弃前一个可执行映像的符号。可以使用 set follow-exec-mode 命令更改此行为。

set follow-exec-mode mode

 设置调试器对 exec 程序调用的响应。 exec 调用替换了进程的程序映像。
follow-exec-mode 可以是:

  • new
     GDB 创建一个新的下级并将该过程重新绑定到这个新的下级。在 exec 调用之前进程正在运行的程序可以通过重新启动原始劣质来重新启动。
    例如:
(gdb) info inferiors
(gdb) info inferior
  Id   Description   Executable
* 1    <null>        prog1
(gdb) run
process 12020 is executing new program: prog2
Program exited normally.
(gdb) info inferiors
  Id   Description   Executable
  1    <null>        prog1
* 2    <null>        prog2
  • same
     GDB 保持进程绑定到同一个下级。新的可执行映像替换了之前加载的可执行文件。在 exec 调用之后重新启动下级,例如使用 run 命令,重新启动进程在 exec 调用之后运行的可执行文件。这是默认模式。
    例如:
(gdb) info inferiors
  Id   Description   Executable
* 1    <null>        prog1
(gdb) run
process 12020 is executing new program: prog2
Program exited normally.
(gdb) info inferiors
  Id   Description   Executable
* 1    <null>        prog2

 本机模式和目标扩展远程模式支持 follow-exec-mode。
 每当进行 fork、vfork 或 exec 调用时,都可以使用 catch 命令使 GDB 停止。请参阅设置捕获点。

检查点/重启:设置书签以稍后返回

 在某些操作系统上 4,GDB 能够保存程序状态的快照,称为检查点,稍后再返回。
 返回到检查点有效地撤消了自保存检查点以来程序中发生的所有事情。这包括内存、寄存器甚至(在某些限制内)系统状态的变化。实际上,这就像时光倒流到保存检查点的那一刻。
 因此,如果正在单步执行一个程序,并且认为正在接近出现问题的地方,可以保存一个检查点。然后,如果不小心走得太远而错过了关键语句,则不必从头开始重新启动程序,可以返回检查点并从那里重新开始。
 如果需要大量时间或步骤才能达到认为发生错误的地步,这将特别有用。
 使用checkpoint/restart方法进行调试:

  • checkpoint
     保存被调试程序当前执行状态的快照。 checkpoint 命令不带参数,但每个检查点都分配有一个小的整数 id,类似于断点 id。
  • info checkpoints
     列出当前调试会话中保存的检查点。对于每个检查点,将列出以下信息:
Checkpoint ID
Process ID
Code Address
Source line, or label
restart checkpoint-id

 恢复保存为检查点编号 checkpoint-id 的程序状态。所有程序变量、寄存器、堆栈帧等都将返回到保存检查点时的值。本质上,gdb 将“倒计时”到保存检查点的时间点。
 请注意,断点、GDB 变量、命令历史记录等不受恢复检查点的影响。一般来说,检查点只恢复驻留在被调试程序中的东西,而不是调试器中的东西。

delete checkpoint checkpoint-id

 删除由 checkpoint-id 标识的先前保存的检查点。
 返回到先前保存的检查点将恢复被调试程序的用户状态,以及系统 (OS) 状态的重要子集,包括文件指针。它不会从文件中“取消写入”数据,但会将文件指针倒回到先前的位置,以便可以覆盖先前写入的数据。对于以读取模式打开的文件,指针也会被恢复,以便再次读取之前读取的数据。
 当然,已经发送到打印机(或其他外部设备)的字符不能被“抢回”,并且从例如接收到的字符。可以从内部程序缓冲区中删除串行设备,但不能将它们“推回”到串行管道中,准备再次接收。同样,已更改的文件的实际内容也无法恢复(此时)。
 但是,在这些限制条件下,实际上可以将程序“倒回”到之前保存的时间点,然后再次开始调试它——可以更改事件的进程,以便这次调试不同的执行路径。
 最后,当返回检查点时,有一点内部程序状态会有所不同——程序的进程 ID。每个检查点都有一个唯一的进程 ID(或 pid),并且每个检查点都与程序的原始 pid 不同。如果程序已保存其进程 ID 的本地副本,这可能会造成问题。

 使用检查点的一个不明显的好处

 在某些系统(例如 GNU/Linux)上,出于安全原因,地址空间随机化是在新进程上执行的。如果必须重新启动程序,这使得在绝对地址上设置断点或观察点变得困难或不可能,因为符号的绝对位置将从一次执行到下一次执行发生变化。
 但是,检查点是进程的相同副本。因此,如果在(例如)main 的开始处创建一个检查点,并简单地返回该检查点而不是重新启动进程,可以避免地址随机化的影响,并且符号将全部留在同一个位置。

  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

SunkingYang

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值