Oracle® Developer Studio 12.5: dbxtool 教程

退出打印视图

更新时间: 2016 年 6 月
 
 

使用高级断点技巧

    本节演示了使用断点的一些高级技巧:

  • 使用断点计数

  • 使用受限制的断点

  • 拾取有用的断点计数

  • 监视点

  • 使用断点条件

  • 使用弹出进行微重播

  • 使用修复并继续

本节和示例程序受到了在 dbx 中发现的一个实际错误的启发,该错误的发现顺序与本节中所述的顺序相同。


注 -  要获取本节中显示的正确输出,示例程序必须仍“包含错误”。如果修复了错误,请从程序示例重新下载 OracleDeveloperStudio12.5-Samples 目录。

源代码包含一个名为 in 的样例输入文件,该文件会在示例程序中触发一个错误。in 包含以下代码:

display nonexistent_var	# should yield an error
display var
stop in X	# will cause one "stopped" message and display
stop in Y	# will cause second "stopped" message and display
run
cont
cont
run
cont
cont

使用该输入文件运行程序时,输出如下所示:

$ a.out < in
> display nonexistent_var 
error: Don't know about 'nonexistent_var'
> display var 
will display 'var'
> stop in X 
> stop in Y 
> run 
running ...
stopped in X
var = {
      a = '100'
      b = '101
      c = '<error>'
      d = '102
      e = '103'
      f = '104'
      }
> cont 
stopped in Y
var = {
      a = '105'
      b = '106'
      c = '<error>'
      d = '107'
      e = '108'
      f = '109'
      }
> cont 
exited
> run 
running ...
stopped in X
var = {
      a = '110'
      b = '111'

      c = '<error>'
      d = '112'
      e = '113'
      f = '114'
      }
> cont 
stopped in Y
var = {
      a = '115'
      b = '116'

      c = '<error>'
      d = '117'
      e = '118'
      f = '119'
      }
> cont 
exited
> quit
Goodby

此输出可能看起来很长,但此示例的重点是展示长时间运行的复杂程序所使用的技术,在这些程序中,单步执行代码或仅仅进行跟踪是不切实际的。

请注意,显示字段 c 的值时,您将获取 <error> 的值。如果该字段包含错误地址,可能会发生这样的情形。

问题

请注意,当您第二次运行程序时,您收到了在第一次运行时没有收到的其他错误消息:

error: cannot get value of 'var.c'

error() 函数使用变量 err_silent 使错误消息在某些情况下不再出现。例如,对于 display 命令,不会显示错误消息,而会将问题显示为 c = '<error>'

步骤 1:反复性

第一步是设置一个调试目标并配置该目标,以便可通过单击 "Restart"(重新启动)image: 轻松地重复错误。

    按如下方式开始调试程序:

  1. 如果您尚未编译示例程序,请按照 程序示例 中的说明进行操作。

  2. 选择 "Debug"(调试)> "Debug Executable"(调试可执行文件)。

  3. 在 "Debug Executable"(调试可执行文件)对话框中,浏览可执行文件或键入可执行文件的路径。

  4. 在 "Arguments"(参数)字段中,键入:

    < in

    可执行文件路径的目录部分显示在 "Working Directory"(工作目录)字段中。

  5. 单击 "Debug"(调试)。

    image:“Debug Executable“(调试可执行文件)对话框

在真实情形中,您可能还希望填充 "Environment"(环境)字段。

调试程序时,dbxtool 会创建一个调试目标。您可以通过选择 "Debug"(调试)> "Debug Recent"(调试近来的)并选择所需的可执行文件来使用同一调试配置。

您可以从 dbx 命令行设置其中的多个属性。这些属性将存储在调试目标配置中。

以下技巧有助于轻松保持可重复性。添加断点时,可以通过单击 "Restart"(重新启动)快速转至某个感兴趣的位置,而不必在出现各种中间断点时单击 "Continue"(继续)。

步骤 2:第一个断点

error() 函数输出一条错误消息时,在该函数内放置第一个断点。此断点将成为第 33 行上的一个行断点。

在较大的程序中,您可以通过键入以下内容(例如,在 "Debugger Console"(调试器控制台)窗口中)来轻松更改编辑器窗口中的当前函数:

(dbx) func error

淡紫色条表示 func 命令所找到的匹配项。

  1. 通过在编辑器窗口左边界中数字 33 的上面单击,创建行断点。

    image:第 31 行上有淡紫色条、第 33 行上有断点的编辑器窗口
  2. 单击 "Restart"(重新启动)image: 以运行该程序,命中断点时,堆栈跟踪会显示由于 in 文件中的模拟命令而生成的错误消息:

    > display var	# should yield an error

    调用 error() 是预期行为。

    image:包含错误消息帧的 “Call Stack“(调用堆栈)窗口
  3. 单击 "Continue"(继续)image: 继续执行该进程并再次命中该断点。

    此时将显示一条意外的错误消息。

    image:包含错误消息帧的 “Call Stack“(调用堆栈)窗口

步骤 3:断点计数

最好是在每次运行时都能反复到达此位置,而不必在第一次命中断点之后由于以下命令而单击 "Continue"(继续):

> display var # should yield an error

您可以编辑程序或输入脚本,然后消除第一个麻烦的 display 命令。但是,您正在处理的特定输入顺序可能是重现此错误的关键,因此您不需要更改输入。

    由于您关注的是第二次到达此断点,因此,可将其计数设置为 2。

  1. 在 "Breakpoints"(断点)窗口中,右键单击该断点,然后选择 "Customize"(定制)。

  2. 在 "Customize Breakpoint"(定制断点)对话框中,在 "Count Limit"(计数限制)字段中键入 2。

  3. 单击 "OK"(确定)。

    image:“Customize Breakpoint“(定制断点)对话框

现在,您就可以反复到达您关注的位置了。

在本例中,是否选择计数 2 无关紧要。但是,有时会对某个感兴趣的位置进行多次调用。请参阅步骤 7:确定计数值以轻松选择合适的计数值。但现在,您可以先了解一下如何通过另一种方式仅在您关注的调用中在 error() 中停止。

步骤 4:受限制的断点

  1. 对于 error() 内部的断点,打开 "Customize Breakpoint"(定制断点)对话框,然后通过从 "Count Limit"(计数限制)的下拉式列表中选择 "Always stop"(总是停止)禁用断点计数。

  2. 重新运行程序。

    注意两次在 error() 中停止的堆栈跟踪。第一次,在 error() 中的停止与以下屏幕相似:

image:“Call Stack“(调用堆栈)窗口

第二次,在 error() 中的停止与以下屏幕相似:

image:“Call Stack“(调用堆栈)窗口

要安排为在从 runProgram(第 [7] 帧)调用时在此断点处停止,请再次打开 "Customize Breakpoint"(定制断点)对话框并将 "While In"(满足条件)字段设置为 runProgram。

image:“Customize Breakpoint“(定制断点)窗口

步骤 5:查找原因

由于 err_silent 不 > 0,因此发出了不需要的错误消息。通过气球表达式求值看一下 err_silent 的值。

  1. 将光标放在第 31 行中 err_silent 的上方,然后等待其值显示出来。

    image:气球表达式求值显示 err_silent = 0 的编辑器窗口

    跟随堆栈看一下设置 err_silent 的位置。

  2. 单击 "Make Caller Current"(使调用方成为当前调用方)image: 两次以到达 evaluateField(),该函数已调用 evaluateFieldPrepare(),模拟一个可能正在处理 err_silent 的复杂函数。

    image:在 evaluateField 中有淡紫色条的编辑器窗口
  3. 再次单击 "Make Caller Current"(使调用方成为当前调用方)以到达 printField(),此处 err_silent 正在递增。printField() 也已调用 printFieldPrepare(),printFieldPrepare 也模拟一个可能正在处理 err_silent 的复杂函数。

    image:在 printField 中有淡紫色条的编辑器窗口

    请注意 err_silent++err_silent-- 如何将某些代码包围起来。

    err_silent 可能在 printFieldPrepare()evaluateFieldPrepare() 中出错,或者当控制到达 printField() 时 err_silent 已经出错。

步骤 6:更多断点计数

    要查明 err_silent 是在对 printField() 的调用之前还是之后出错,请在 printField() 中放置一个断点。

  1. 选择 printField(),右键单击,然后选择 "New Breakpoint"(新建断点)。

    新的断点类型已预先选择,并且 "Function"(函数)字段已使用 printfield 预先填充。

  2. 单击 "OK"(确定)。

    image:“New Breakpoint“(新建断点)对话框
  3. 单击 "Restart"(重新启动)image:

    第一次命中断点的时间是第一次运行期间第一次停止在第一个字段 var.a 上时。err_silent 为 0 是可以接受的。

    image:包含气球表达式求值的编辑器窗口
  4. 单击 "Continue"(继续)。

    err_silent 仍可以接受。

  5. 再次单击 "Continue"(继续)。

    err_silent 仍可以接受。

到达对 printField() 的特定调用(该调用导致了不需要的错误消息)可能需要一段时间。您需要在 printField 断点上使用断点计数。可是应该将计数设置成什么呢?在该简单示例中,您可以尝试对运行和停止以及显示的字段进行计数,但实际上该过程可能会更加困难。有一种方法可以半自动地确定该计数。

步骤 7:确定计数值

  1. 打开 "Customize Breakpoint"(定制断点)对话框,找到 printField() 上的断点,然后将 "Count Limit"(计数限制)字段设置为无限大。

    image:“Customize Breakpoint“(定制断点)对话框

    此设置意味着您将永远不会在此断点处停止。但是,仍将进行计数。

  2. 将 "Breakpoints"(断点)窗口设置为显示更多属性(如计数)。

    1. 单击 "Breakpoints"(断点)窗口右上角的 "Change Visible Columns"(更改可视列)按钮 image:

    2. 选择 "Count Limit"(计数限制)、"Count"(计数)和 "While In"(满足条件)。

    3. 单击 "OK"(确定)。

      image:“Change Visible Columns“(更改可视列)对话框
  3. 再次运行程序。您将命中 error() 内部的断点,也就是受 runProgram() 限制的断点。

  4. 现在来看一下 printField() 上断点的计数。

    image:“Breakpoints“(断点)窗口

    计数为 15。

  5. 再次在 "Customize Breakpoint"(定制断点)窗口中单击 "Count Limit"(计数限制)列中的下拉式列表,选择 "Use current Count value"(使用当前计数值)将当前计数传送给计数限制,然后单击 "OK"(确定)。

现在如果运行程序,您将在最后一次调用 printField() 时在该函数中停止,然后显示意外的错误消息。

步骤 8:确定具体原因

再次使用气球表达式求值检查 err_silent。现在的值为 -1。最有可能的原因是,在您到达 printField() 之前,一个 err_silent-- 执行得太多,或者一个 err_silent++ 执行得太少。

您可以通过仔细检查代码,在与该示例类似的小程序中找到这个不匹配的 err_silent 对。但是,大型程序可能包含大量的以下配对:

err_silent++;
err_silent--;

更为快捷地找到不匹配的 err_silent 对的方法是使用监视点。

错误的原因可能根本不是不匹配的 err_silent++;err_silent--; 对,而是一个覆盖了 err_silent 内容的异常指针。在捕获此类问题时,监视点会比较有效。

步骤 9:使用监视点

    err_silent 上创建监视点:

  1. 选择 err_silent 变量,右键单击,然后选择 "New Breakpoint"(新建断点)。

  2. 将 "Breakpoint Type"(断点类型)设置为 "Access"(访问)。

    请注意 "Settings"(设置)部分如何变化以及 "Address"(地址)字段是如何成为 & err_silent 的。

  3. 在 "When"(时间)字段中选择 "After"(之后)。

  4. 在 "Operation"(操作)字段中选择 "Write"(写入)。

  5. 单击 "OK"(确定)。

    image:“New Breakpoint“(新建断点)窗口
  6. 运行程序。

    您在 init() 处停止。err_silent 递增到了 1,之后就停止了执行。

  7. 单击 "Continue"(继续)。

    您再次在 init() 中停止。

  8. 再次单击 "Continue"(继续)。

    您再次在 init() 中停止。

  9. 再次单击 "Continue"(继续)。

    您再次在 init() 中停止。

  10. 再次单击 "Continue"(继续)。

    现在您将在 stopIn() 中停止。此时看起来也是一切正常,没有出现 -1。

可以设置断点条件,而不是反复地单击 "Continue"(继续),直到将 err_silent 设置为 -1。

步骤 10:断点条件

    为您的监视点添加一个条件:

  1. 在 "Breakpoints"(断点)窗口中,右键单击 "After"(之后)写入断点,然后选择 "Customize"(定制)。

  2. 验证是否在 "When"(时间)字段中选择了 "After"(之后)。

    通过选择 "After"(之后),您可以查看更改后的 err_silent 的值。

  3. 将 "Condition"(条件)字段设置为 err_silent == -1

  4. 单击 "OK"(确定)。

    image:“Customize Breakpoint“(定制断点)对话框
  5. 再次运行程序。

    您在 checkThings() 处停止,这是第一次将 err_silent 设置为 -1。在您查找匹配的 err_silent++ 时,您会看清错误:err_silent 仅在该函数的 else 部分中递增。

    image:显示在 checkThings 处停止的程序的编辑器窗口

    这是您所要寻找的错误吗?

步骤 11:通过弹出堆栈来验证诊断

有一种方法可以核实您是否确实检查完函数的 else 块,那就是在 checkThings() 上设置一个断点并运行程序。但 checkThings() 可能会被多次调用。您可以使用断点计数或受限制的断点来实现对 checkThings() 的正确调用,但重播最近所执行内容的更快方法是弹出堆栈。

  1. 选择 "Debug"(调试)> "Stack"(堆栈)> "Pop Topmost Call"(弹出最顶层调用)。

    请注意 "Pop Topmost Call"(弹出最顶层调用)不会撤消任何内容。尤其是,err_silent 的值已出错,因为您正从数据调试切换到控制流调试。

    进程状态恢复到包含对 checkThings() 的调用的行的开始处。

  2. 单击 "Step Into"(步入)image: 并在再次调用 checkThings() 时进行观察。

    在单步执行 checkThings() 时,您可以验证该进程是否执行了 if 块(此处 err_silent 没有递增,并且接着会递减至 -1)。

image:显示程序在 checkThings( ) 的 if 部分中停止的编辑器窗口

尽管您似乎已找到编程错误,但您可能需要反复对其进行检查。

步骤 12:使用修复进一步验证诊断

请修复代码并验证错误确实已经消除。

  1. 通过将 err_silent++ 置于 if 语句的上方来修复代码。

    image:显示 err_silent++ 已移动的编辑器窗口
  2. 选择 "Debug"(调试)> "Apply Code Changes"(应用代码更改),或者按 "Apply Code Changes"(应用代码更改)按钮 image:

  3. 禁用 printField 断点和监视点,但保留 error() 中断点的启用状态。

    image:“Breakpoints“(断点)窗口
  4. 再次运行程序。

请注意,程序已完成但没有命中 error() 中的断点,其输出符合预期。

image:“Output“(输出)窗口

讨论

该示例说明了与使用断点和步进结尾处所讨论的模式相同的模式,即,用户在出错之前的某个点停止行为异常的程序,然后单步执行代码,将代码的本意与代码实际的行为相比较。主要差异在于,查找出错之前的点的过程要复杂一些。