<dl id='2dk7a'></dl>
    <i id='2dk7a'></i>

      <i id='2dk7a'><div id='2dk7a'><ins id='2dk7a'></ins></div></i>
    1. <ins id='2dk7a'></ins>

    2. <tr id='2dk7a'><strong id='2dk7a'></strong><small id='2dk7a'></small><button id='2dk7a'></button><li id='2dk7a'><noscript id='2dk7a'><big id='2dk7a'></big><dt id='2dk7a'></dt></noscript></li></tr><ol id='2dk7a'><table id='2dk7a'><blockquote id='2dk7a'><tbody id='2dk7a'></tbody></blockquote></table></ol><u id='2dk7a'></u><kbd id='2dk7a'><kbd id='2dk7a'></kbd></kbd>
      <span id='2dk7a'></span>

        1. <fieldset id='2dk7a'></fieldset>
          <acronym id='2dk7a'><em id='2dk7a'></em><td id='2dk7a'><div id='2dk7a'></div></td></acronym><address id='2dk7a'><big id='2dk7a'><big id='2dk7a'></big><legend id='2dk7a'></legend></big></address>

          <code id='2dk7a'><strong id='2dk7a'></strong></code>

          探索 Gdb7.0 的新特性反向调试 (reverse debug)

          • 时间:
          • 浏览:4
          • 来源:124软件资讯网
            弁言

              GDB7.0 是 2009 年 10 月份正式公布的  。和多数法式员一样  ,那则新闻并未曾引起我的注重  ,由于 gdb 为数不多的几个新版本都让人以为很是平庸  。没有让人振奋的新特征  。

              一晃几个月已往了  ,随意浏览 gdb 主页的时间 ,我突然发现一个叫做反向调试 (reverse debug) 的特征 ,默默地列在不引人注目的地方  。”反向调试”  ?我们调试总是下一步  ,下一步  ,反向调试就是上一步  ,上一步了  ?

              经由简朴的试用  ,我发现这是一个很是有用的特征 ,值得我们去学习和掌握 。使用该功效 ,您可以让被调试历程反向执行  。您可能会问  ,这有什么用处呢  ?

              嗯  ,我以为软件调试往往是一个推测的历程  ,一样平常的通俗人似乎不太可能第一次就将断点设置在最准确的位置 ,以是我们经常会发现主要的代码执行路径已经错过 。好比运行到断点之前  ,法式的某些状态就已经不准确了  。以往  ,我们只好退出当前调试会话 ,重新再来  。

              每次错误的推测都必须让一切重新再来  ,若是您的运气不佳  ,很快就会以为很是抓狂吧  。

              如果有反向调试功效  ,在这种情形下 ,我们无须重新启动调试法式  ,只要简朴地让被调试历程反向执行到我们嫌疑的地方 ,若是这里没有问题  ,我们还可以继续正向执行调试 ,云云这般 。犹如我们在学习英语时使用复读机一样  ,往返将听不懂的部门重放  ,剖析  。这无疑将极大地提高事情效率  。

              反向调试的使用简介

              反向调试的基本原理为 record/replay  。将被调试历程的执行历程录制下来  ,然后便可以像 DVD 一样的恣意反向或正向回放  。因此 ,为了使用反向调试  ,您首先需要使用 record 下令举行录制 。

              今后的调试历程和您以前所熟悉的历程一样  ,不外您现在多了几个反向控制的下令:

            表 1. 反向调试基本下令

            Command name description
            Reverse-continue ('rc')Continueprogram being debugged but run it in reverse
            Reverse-finishExecute backward until just before the selected stack frame is called
            Reverse-next ('rn')Step program backward, proceeding through subroutine calls.
            Reverse-nexti ('rni')Step backward one instruction, but proceed through called subroutines.
            Reverse-step ('rs')Step program backward until it reaches the beginning of a previousline
            Reverse-stepiStep backward exactly one instruction
            set exec-directionSet direction of execution.

              让我们假设您已经使用”break 10”下令在法式的第 10 行设置断点 。然后输入”run”让历程最先执行  。不久 ,法式会暂停在第 10 行代码处  ,控制权返回 gdb  ,并等候您的下令 。此时若是您使用 next 下令  ,那么法式会顺序执行第 11 行代码  ,若是您使用 reverse-next  ,那么法式将回退执行第 9 行代码  。

              使用反向调试下令之后  ,任何时间  ,您还可以自由地使用其他的 gdb 下令 。好比 print 来打印变量的值等等  。很是利便  。

              反向调试的实现原理

              除了使用这项特征所带来的利益之外  ,或许更令人着迷的是该特征的实现原理吧  。我们都未曾见过时光倒流  ,能够转头执行指令的处置惩罚器也貌似从未泛起过  ,那么 gdb 是怎样实现反向执行的呢  ?

              为了说明这个问题 ,首先我们需要回首一些 gdb 的基本观点  。

              GDB 的基本观点

              Gdb 的一些主要术语以及 gdb 的整体结构

              进入一个生疏的国家前先学几句他们的常用语会比力好  。GDB 这个小天下中也经常使用一些特有的名词 ,我们最好首先熟悉他们  。

              Exec  ,指一个可执行文件  ,可以是 ELF 花样 ,也可以是古老的 a.out  。

              Inferior ,指一个正在运行的 exec  ,一样平常就是指被调试历程  。

              接下来最好我们能够对 gdb 有一个整体的  ,高度归纳综合的相识  。

              Gdb 的设计目的是为种种平台上的人们提供一个通用的调试器  ,因此它必须有可扩展性  ,以便人们可以将它移植到差别的硬件和软件情况下 。

              为了实现这个目的  ,GDB 的系统结构接纳分层和封装的设计头脑  ,将那些依赖于特定软硬件情况的部门举行抽象和封装 。最主要的两个封装观点即是 gdbarch 和 target  。他们和 gdb core 之间的关系大致可以用下图来形貌:

            图 1. GDB 的系统结构
            探索 Gdb7.0 的新特性反向调试 (reverse debug)

              当需要在差别的 OS 上运行 GDB 时 ,只需提供响应的 target 便可;同样  ,当需要支持一种新的新的处置惩罚器时  ,也只需提供新的 gdbarch  ,而 gdb core 则无需任何修改 。

              关于 gdbarch

              Gdbarch 是一个封装了所有关于处置惩罚器硬件细节的数据结构  。在这个数据结构中  ,不仅包罗一些形貌处置惩罚器特征的变量 ,也包罗一些函数(喜欢 OO 的人会自然地遐想到类  。)这些函数实现了对详细硬件的一些主要操作  ,好比剖析 stack frame  ,等等  。

              完整的 gdbarch 数据结构很是重大  ,无法逐一列出  ,下表分类总结了 gdbarch 中的主要信息:

            表 2. gdbarch 数据结构

            分类 说明
            形貌硬件系统结构和 ABI 细节的信息 好比 :
            endianism : 大端系统照旧小端系统
            好比 return_value:形貌该处置惩罚器 ABI 中划定的处置惩罚函数返回值的要领
            breakpoint_from_pc: 用于断点替换的机械指令 , 好比 i386 中为 int3
            struct gdbarch_tdep additional target specific data, beyond that which is
            covered by the standard struct gdbarch.
            形貌尺度数据结构的信息 高级语言的 int, char 等尺度数据结构的详细界说
            会见和显示寄存器的函数 read_pc: 返回指令指针寄存器
            num_regs: 返回寄存器的个数
            会见和剖析 stack frame 的函数 差别系统结构的 stack frame 都不尽相同  。这些函数提供了怎样剖析和建立 stack frame 的详细实现函数  。

              可以看到 gdbarch 封装了所有 gdb 运行时所需要的硬件信息 ,以及怎样会见和处置惩罚这些信息的详细函数  。类似于面向工具设计中的类的设计 ,将关于处置惩罚器硬件细节的数据和要领都封装到 gdbarch 数据结构中  。

              关于 target

              同 gdbarch 一样  ,target 也是一种封装  。但 target 所封装的观点更庞大一些  。它不仅封装某一种操作系统  ,也封装了差别的”调试方式”  。

              首先  ,差别的操作系统对应差别的 target  。同样在 i386 处置惩罚器下事情  ,Linux 和 vxworks 对于 debug 的支持是差别的 ,好比怎样建立历程  ,怎样控制历程等  。这些差别对于 gdb core 是透明的 ,由 target 来屏障  。

              此外  ,target 还封装了差别的”调试方式”  。这个词比力抽象  ,最好是举例说明  。

              好比  ,同样是在 i386 Linux 下面 ,您即可以使用 native 方式调试 exec  ,也可以调试一个 core dump 文件 ,还可以 attach 一个正在运行的历程并调试它  。打开一个可执行文件和一个 core dump 文件的要领是差别的 ,同样  ,将一个可执行文件 load 进内存执行和 attach 到一个正在执行的历程也是差别的  。

              对于这些差别  ,gdb 也接纳 target 举行封装 。对于 gdb core 来说 ,当它需要让一个调试目的最先运行时  ,便挪用 target 响应的回调函数  ,而不必体贴这个回调函数怎样实现 。启动历程的详细的细节由差别的 target 来详细实现  。当 target 为 exec  ,即一个磁盘上的可执行文件时 ,可以使用 fork+exec 的方式;当 target 是一个远程调试目的时  ,可以通过 TCP/IP 发送一个下令给 gdb server 举行远程调试;当 target 是一个已经在运行的历程时  ,需要使用 ptrace 的 attach 下令挂载上去  。诸如这些细节 ,gdb 一切由 target 这个观点来封装  。

              这即是 target 这个观点的主要意义  ,不外  ,另有一些事实让 target 越发庞大 。

              有时间  ,人们希望在统一个 gdb 会话中调试多个 target  。最常见的例子是调试 core dump 文件时  ,往往需要同时打开发生 core dume 的可执行文件 ,以便读取符号  。

              好比法式 a.out 发生了 core dump 文件 core.2629  ,当用 gdb 打开 core dump 文件后  ,使用 bt 下令检察挪用顺序时  ,人们不能看到函数名  。

            图 2. 没有符号信息的挪用客栈显示
            探索 Gdb7.0 的新特性反向调试 (reverse debug)

              此时人们往往还需要用 file 下令打开 a.out 法式  ,即一个 exec  。

            图 3. 有了符号信息的挪用客栈显示
            探索 Gdb7.0 的新特性反向调试 (reverse debug)

              除了 core dump 剖析 ,另有其它一些情形要求 gdb 同时治理多个 target  。为了应对这些需求 ,gdb 对 target 接纳了分层、优先级治理的客栈模式 。客栈中的每一层由一个形如 xyz_stratum 的离奇名字来标示  ,如下图所示:

            图 4. GDB 的 target stratum
            探索 Gdb7.0 的新特性反向调试 (reverse debug)

              这个客栈的优先级从上到下递增 ,gdb 总是接纳最高优先级 target 所提供的函数举行操作  。

              以图 2  ,3 中的下令为例  ,打开 core dump 文件时 ,core_stratum 层的 target 被 push 进入 target stack;当用户使用下令 file a.out 时  ,一个 file_stratum 层的 target 被 push 进入 target stack 。他们遵照自身的优先级归于差别的层  ,绝不会弄错  。

              当 target stack 中只有 core_stratum 的 target 时 ,如果用户希望执行 run 下令是不行能的  ,由于 core dump 文件无法运行  ,而当载入了 exec target 后  ,这个 file_stratum 层的 target 提供了和 run 下令响应的回调函数  ,从而使得 gdb 可以使用 run 下令启动一个 inferior  。您在稍后的章节中可以看出  ,这种分层结构对反向调试的实现很是有资助  。

              下表列出了 target 数据结构的主要内容:

            表 3. Target 数据结构

            分类 说明
            关于 target 的说明信息 好比 :
            to_name: target 的名字
            to_stratum: 该 target 在 target stack 中的层数
            控制调试目的的函数 好比 :
            to_open: 打开 target, 对于 exec 或 core dump 文件执行文件打开操作 ; 对于 remote target, 打开 socket 建设 TCP 毗连等操作 .
            会见调试目的寄存器和内存的函数 好比 :
            to_store_registers
            处置惩罚断点的函数 好比 :
            Insert_break_point
            控制调试历程的函数 好比 :
            to_resume. Function to tell the target to start running again (or for the first time).
            等等 。

              GDB 运行时的基本流程

              对一个目的历程举行调试  ,需要操作系统提供响应的调试功效  。好比将一个正在运行的历程暂停并读写其地址空间等 。在传统 Unix 中 ,一样平常由 ptrace 系统挪用提供这些功效 。本文不计划详细先容 ptrace  ,读者可以通过参考资料 [5] 获得更详细的先容  。

              但 ptrace 的编程模式极大地影响了 gdb 的设计  ,下面我们研究 gdb 怎样使用 Ptrace  。

              首先  ,gdb 挪用 ptrace 将一个目的历程暂停  ,然后  ,gdb 可以通过 ptrace 读写目的历程的地址空间  ,还可以通过 ptrace 让目的历程进入单步执行状态  。Ptrace 函数返回之后  ,gdb 便最先等候目的历程发来的信号  ,以便进一步的事情  。

              以单步执行为例  ,gdb 挪用 ptrace 将目的历程设置为单步执行模式之后  ,便最先等候 inferior 的新闻  。由于处于单步模式  ,inferior 每执行一条指令 ,Linux 便将该历程挂起  ,并发送一个信号给 ptrace 的挪用者  ,即 gdb  。Gdb 接受到这条信号 ( 通过 wait 系统挪用 ) 后  ,便知道目的历程已经完成了一次单步  ,然后举行响应处置惩罚  ,好比判断这里是否有断点 ,或进入交互界面等候用户的下令等等  。

              这很是类似窗口系统中的新闻循环模式 。Ptrace 的这一事情模式影响了整个 gdb 的设计  ,无论详细的 target 是否支持 ptrace ,gdb 都接纳这种新闻循环模式  。

              明白了以上的基础知识  ,您就可以最先探索反向调试的详细实现细节了  。

              反向调试原理和代码导读

              原理概述

              如前所述 ,反向调试的基本原理是录制回放 。它将 inferior 运行历程中的每条指令的执行细节录制下来  ,存放到 log 中 。当需要回退时  ,从 log 中读取前一条指令执行的细节 ,凭据这些细节 ,执行 undo 操作  ,从而将 inferior 恢复到其时的状态 ,云云便实现了“上一步”  。

              undo 就是将某条指令所做的事情作废  。好比指令 A 将寄存器 reg1 的值加了 1  ,那么 undo 就是将其减一  。

              原理很简朴  ,然而要将此想法付诸实现 ,人们必须解决几个详细的问题:

              怎样录制  ,又怎样回放呢  ?

              首先  ,gdb7.0 引入了一个新的 target ,叫做 record target  。这个 target 提供了录制和回放的基本代码框架 。

              其次  ,当我们说到一条指令的执行细节时  ,事实是指那些详细内容呢  ?或者说我们事实应该录制些什么呢  ?这些记载怎样组织  ?这即是 record list 的主要内容 。下面我们逐一来相识这些知识  。

              Record target

              反向调试引入了一个新的 target  ,叫做”record” ,它事情在 target stack 的第二层  。Gdb target 的分层结构带来了这样一种利益:高层的 target 可以复用底层 target 的功效 ,并在其上添加分外的功效  。我想我们可以这么说:低层 target 完成基本的低级功效  ,高层 target 完成更高级的功效 。

              Record target 就是一个带录制功效的高层 target  ,它依赖低层 target 完成诸如启动历程  ,插入断点  ,控制单步等基本功效 。在此之上  ,它将 inferior 执行历程中的每条指令的细节记载下来 。此外  ,它还处置惩罚几个反向调试特有的下令 ,reverse next 等  。

              当用户希望举行反向执行时  ,record target 并不需要低层 target 的资助 。由于 inferior 的执行历程都已经被记载在一个 log 中  ,反向执行时  ,record target 从 log 中读取记载 ,并 undo 这些记载的影响  ,好比恢复先前寄存器的值  ,恢复被指令修改的内存值等  ,从而到达了反向执行的效果  。

              下面我们详细剖析几个主要的 record target 所提供的函数  ,从而对上述基本头脑有更深入的明白  。

              首先看 record_open 操作 。如前所述  ,Record target 可以看尴尬刁难低层 target 的一个 wrapper  。Record_open 时  ,首先将当前 target 的主要回调函数 ( 好比后续将说明的 to_resume, to_wait 等 ) 复制到一系列的 beneath function pointers 中 。然后将”record target” push 到 target stack 的顶层 。

              第二个主要的操作是 record_resume 。Record_resume 在 gdb 决议让目的历程最先运行之前被挪用 ,因此这里是绝佳的录制点  。该函数的实现比力简朴:

            清单 1. record_resume 函数

             record_resume (struct target_ops *ops, ptid_t ptid, int step, 
                    enum target_signal signal) 
             { 
             record_resume_step = step; 
             if (!RECORD_IS_REPLAY) 
              { 
               if (do_record_message (get_current_regcache (), signal)) 
                { 
                 record_resume_error = 0; 
                } 
               else 
                { 
                 record_resume_error = 1; 
                 return; 
                } 
               record_beneath_to_resume (record_beneath_to_resume_ops, ptid, 1, 
                            signal); 
              } 
             } 

              Record_resume 首先挪用 do_record_message 举行录制  ,然后挪用低层 target 的 to_resume 函数(已经生存在 beneath function pointer 中)完成基本的 resume 事情  。

              这里需要注重一点  ,在挪用 record_beneath_to_resume时  ,第三个参数 step为 1 ,即单步执行  。这是由于 record target需要录制目的历程的每条指令  ,因此如果用户下令为 continue或 next  ,而不是 step时 ,目的历程将继续执行下去直到遇到断点为止 ,在此时代的指令 gdb无法获知 ,便也无从记载  。因此 record target强制目的历程进入单步执行状态  。以便录制每一条指令  。

              第三个主要的操作是 record_wait  。从函数的名字便可以猜得该函数是 gdb 等候目的历程信号的函数  。

              当 record target 执行了 record_resume 之后  ,inferior 恢复执行  。而 gdb 自己则最先等候 inferior 的信号  。前面已经看到  ,record_resume 强行让 inferior 进入单步状态  ,因此 inferior 在执行完一条指令后  ,便会被强制挂起  ,并向 gdb 发送一个 SIGCHLD 信号  。此时 record_wait() 便最先执行  。

              该函数首先判断是否需要举行录制  ,若是需要 ,则进一步判断当前的 inferior 是否是单步执行状态 ,若是是 ,则不需要举行录制  ,由于马上 inferior 就会停下来  ,而 gdb 再次让 inferior 恢复执行时将挪用 record_resume ,那里会执行录制事情  。

              但若是当前的 inferior 不在单步状态  ,且下一条指令不是断点  ,那么若是让 inferior 继续执行则意味着 record target 将错事后续的指令执行而无法举行录制 。因此 ,在这种情形下  ,record_wait 将进入一个循环  ,在每次循环迭代中执行录制  ,并让 inferior 进入单步执行状态  ,直到遇到断点或者 Inferior 执行 exit 为止  。伪代码如下:

            清单 2. 执行录制的伪代码

             while(1) 
             { 
             waitForNextSignal(); 
             recordThisInst(); 
             resumeInferiorAndSingleStep(); 
             if(this is a breakpoint || this is end of inferior) 
              break; 
             } 

              这样 ,通过 record_wait 的处置惩罚  ,inferior 的每条执行指令都将被录制下来  。

              Record_wait 的另外一半代码是处置惩罚 replay 的  。如果当前用户希望反向执行  ,那么 record_wait 就从日志中读取 inferior 上一条执行指令的相关记载 ,恢复寄存器  ,内存等上下文  ,从而实现“上一步”的操作 。

              Record list

              每次执行一条指令 ,record target 便将关于该指令的信息录制下来  。这些信息可以完整地形貌一条指令执行的效果  。在现在的 record target 中  ,记载的信息只包罗两类:寄存器和内存  。

              一条机械指令能够改变的就是寄存器或内存  。因此每次执行一条指令  ,record target 对该指令举行剖析  ,若是它修改了内存  ,那么便记载下被修改的内存的地址和值;若是它修改了寄存器  ,便记载下寄存器的序号和详细的值 。

              这些修改记载由 struct record_entry 表现和存储  。

            清单 3 单个记载的数据结构

             struct record_entry 
             { 
             struct record_entry *prev; 
             struct record_entry *next; 
             enum record_type type; 
             union 
             { 
               
              struct record_reg_entry reg; 
               
              struct record_mem_entry mem; 
               
              struct record_end_entry end; 
             } u; 
             }; 

              多个 record_entry 通过 prev 和 next 毗连成 record_list 链表 。一条机械指令可能既修改内存也修改寄存器  ,因此一条指令的执行效果由 record_list 中的多个 entry 组成 。有三种 entry  ,表现寄存器的 entry  ,表现 memory 的 entry 和标志指令竣事的 entry  。顾名思义  ,register entry 用来记载寄存器的修改情形;memory entry 用来记载内存的修改;end entry 表现指令竣事 。

              如下图所示:

            图 5. 反向调试的 log 结构
            探索 Gdb7.0 的新特性反向调试 (reverse debug)

              第一条指令 inst1 由三个 entry 组成  ,一个 memory entry, 一个 reg entry 和一个 end entry  。讲明 inst1 修改了内存和寄存器;同理 ,inst2  ,3 等也使用了同样的数据结构  。

              函数 do_record_message

              函数 do_record_message 详细完成指令执行的录制细节  。抛开 gdb 代码的层层挪用细节  ,该函数的详细事情是挪用 gdbarch 所提供的 process_record 回调函数  。

              对于 i386  ,详细的 process_record 函数为

            清单 4. 函数 process_record 界说

             int i386_process_record (struct gdbarch *gdbarch, struct regcache *regcache, 
             CORE_ADDR addr) 

              这是一个 1000 多行的巨型函数  ,我建议各人不必精读其中的每一行代码  。  。 。

              大要说来 ,该函数首先反汇编正在执行的机械指令  ,凭据反汇编的效果剖析该指令是否修改了寄存器或者内存  。若是有所修改  ,就划分分配新的 reg entry 或者 mem entry 并插入到 record_list  ,当对该指令的所有执行效果都分配了响应的 record_entry 之后  ,挪用 record_arch_list_add_end 插入一个 end entry  ,然后返回  。这样  ,do_record_message() 执行完后  ,关于当前指令的所有细节都被生存到 record_list 中了 。

              record target 执行录制的时序图

              Record target 的录制历程用时序图来表现比力容易明白 ,由于将相关操作串起来的是事务而不是函数挪用关系  。如果您计划跟踪函数的挪用关系  ,那么很快就会迷失到晕头转向 。参考资料 [7] 是我看到的最好的关于 gdb 的文档 ,我以为其中最棒的部门就是 gdb 下令执行时的时序图  ,这是一种很是好的表现要领  。下面我计划用时序图来完整地形貌前面罗罗嗦嗦几千字却依然形貌不清的工具 。

              最简朴的 record 下令序列为:

             > b main 
             > run 
             > record 
             > continue 

              我们用图 6 来表现上述下令序列所对应的、gdb 内部执行历程的时序图  。

            图 6. 录制的时序图
            探索 Gdb7.0 的新特性反向调试 (reverse debug)

              当用户输入 run 下令后  ,gdb 会挪用 target_insert_breakpoint 将现在 active 的断点设置到 inferior 中 ,对于 i386 的 arch 目的  ,会将 inferior 的断点处的指令替换为 int 3 指令 。然后  ,gdb 挪用 target_resume ,恢复 Inferior 的运行  。此时  ,target stack 顶端的 target 会执行正常的 target_resume 操作  ,好比 ptrace(CONTINUE)  。

              当 inferior 执行到断点地址时  ,此时的指令被替换为 int3  ,因此会发生一个 trap  。该信号被 gdb 捕捉进入 target_wait  。此时 gdb 重新获得控制权  ,进入 current_interp_command_loop 等候用户输入下令  。

              接下来用户输入 record 下令 ,这将导致 gdb 挪用 record_open 函数  ,将 record target 推入 target stack 的栈顶  ,并回到 gdb 的下令行等候用户输入新的下令 。

              下一条下令是 continue  ,gdb core 将挪用当前 target 的 target_resume 函数来恢复 inferior 的运行  。此时的 target_resume 函数已经酿成 record_resume  。通过前面的代码剖析  ,我们可以知道  ,此时 record_resume 将挪用 do_record_message 完成第一条指令的录制 ,target_resume 函数执行完将返回 process() 函数  。Gdb core 此时将挪用 target_wait() 函数来等候 inferior 的下一个新闻  。同样  ,此时的 target_wait 为 record_wait() ,因此 gdb 进入 record_wait()  。

              Record_wait 将完成剩余的录制历程  。由于当前的用户下令为 continue  ,因此 record_wait 将进入代码清单 2 所示的循环  ,循环执行下列操作:

              录制当前指令

              挪用下一层的 resume 函数并将 step 参数设置为一  ,强制 inferior 进入单步执行  。

              等候 inferior 的新闻

              由于单步执行  ,inferior 在执行完一条指令后又将 gdb 的 wait 操作叫醒  ,继续上述循环  。云云循环往复  ,直到遇到断点或者执行到 exit 为止  ,从而完成录制历程  。

              Recored target 回放功效的实现比力简朴  ,本文长度有限  ,读者可以自行剖析  。

              GDB 反向调试的局限

              Gdb 的反向调试是从 2006 年左右最先研发的  ,虽然现在已经正式公布  。但照旧不太稳固  ,且有一些局限  。简述如下:

              有 side effect 的语句虽然能够回退执行  ,但其所造成的 side effect 则无法打消  。好比打印到屏幕上的字符并不会由于打印语句的回退而自动消逝  。

              因此  ,反向调试不适用于 IO 操作 。

              此外 , 支持反向调试的处置惩罚器系统结构还很有限 , 需要更多的研发职员到场进来 .

              竣事语

              许多人都在问 ,反向调试事实有多大的现实用处  ?我在本文的开头便简朴先容了一种使用它的场景  ,但我想这并不能令心存嫌疑的人满足  。现实上 ,以我的小我私家履历来看  ,50% 的法式员从来不使用调试器 。对于许多现实事情 ,纵然不使用调试器  ,通过不停的打印和代码剖析最终也能够解决问题  。但如果能准确地使用调试器  ,或许能够越发有用地解决问题  ,从而将人生的名贵时间使用在其他更有意义的地方  。正如 Norman Matloff 所说 ,调试是一种艺术  。没有艺术  ,人类依然可以生活 ,然而远在 1 万多年前  ,拉科斯的史前人也要在岩洞的墙壁上涂抹出一头牛或者一匹马 ,那有什么现实用处呢  ?