第十六章 疯狂Caché 命令行例程调试(二)

跟踪执行

可以使用以下形式的ZBREAK控制是否启用ZBREAK命令的“T”操作:

ZBREAK/TRACE:STATE[:DEVICE]

其中,状态可以是:

  • ON 启用跟踪。
  • OFF 禁用跟踪。
  • ALL 通过执行等效的:ZBREAK/TRACE:ON[:Device]Break“L+”ZBREAK$:“T”来启用应用程序跟踪

当DEVICE与ALLON STATE关键字一起使用时,跟踪消息将重定向到指定设备,而不是主体设备。如果设备尚未打开,则Caché会尝试使用写入和附加选项将其作为顺序文件打开。

当使用OFF STATE关键字指定DEVICE时,如果文件当前处于打开状态,则Caché将关闭该文件。

注意:ZBREAK/TRACE:OFF不删除或禁用由ZBREAK/TRACE:ALL设置的单步断点定义,也不清除由ZBREAK/TRACE:ALL设置的“L+”单步执行。还必须发出命令ZBREAK--$Break“C”来删除单步执行;或者,可以使用单个命令Break“off”来关闭进程的所有调试。

/// d ##class(PHA.TEST.ObjectScript).TestBreak()
ClassMethod TestBreak()
{
	b "off"
	s x = 2
	b ;1
	s x = x -3	
	
	f i = 1:1:10 d
	.s x = x +i
	
	q x
}

跟踪消息在与“T”操作相关联的断点处生成。除了一个例外,所有断点的跟踪消息格式如下:

Trace:LINE_REFERENCE处的ZBREAK

其中LINE_REFERENCE是断点的行引用。

通过命令执行单步执行时,单步断点的跟踪消息格式略有不同:

Trace:LINE_REFERENCE SOURCE_OFFSET处的ZBREAK

其中LINE_REFERENCE是断点的行参考,SOURCE_OFFSET是源行中发生中断的位置的从0开始的偏移量。

操作系统说明:

  • Windows - Windows平台支持连接到COM端口(如COM1:)的终端设备向另一设备发送跟踪消息。不能使用控制台或终端窗口。可以为跟踪设备指定顺序文件
  • UNIX® 要将跟踪消息发送到UNIX®平台上的另一台设备,请执行以下操作:
  1. 登录/dev/tty01。:
  2. 通过输入tty命令验证设备名称:
$ tty
/dev/tty01
  1. 发出以下命令以避免争用设备:
$ exec sleep 50000
  1. 返回到工作窗口。

  2. 启动并输入Caché。

  3. 发出跟踪命令:

ZBREAK /T:ON:"/dev/tty01"
  1. 运行程序。
    如果使用“T”操作设置了断点或观察点,会看到跟踪消息出现在连接到/dev/tty01的窗口中。

跟踪消息格式

如果设置代码断点,将显示以下消息:

Trace: ZBREAK at label2^rou2

如果设置变量观察点,则会出现以下消息之一:

Trace: ZBREAK SET var=val at label2^rou2 
Trace: ZBREAK SET var=Array Val at label2^rou2
Trace: ZBREAK KILL var at label2^rou2
  • var是正在关注的变量。
  • val是为该变量设置的新值。

如果发出新命令,则不会收到跟踪消息。但是,下次在新级别对变量发出setkill命令时,将触发对该变量的跟踪。如果通过引用例程传递变量,则即使名称实际上已更改,仍会跟踪该变量。

中断按键和中断

通常,按中断键序列(通常为CTRL-C)会生成可捕获(<INTERRUPT>)错误。要将中断处理设置为导致中断而不是<INTERRUPT>错误,请使用以下ZBREAK命令:ZBREAK/INTERRUPT:BREAK

这会在按下中断键时导致中断,即使已在设备的应用程序级别禁用中断。

如果在从终端读取数据时按中断键,则可能需要按Return键才能显示中断模式提示。要重置中断处理以生成错误而不是导致中断,请发出以下命令:ZBREAK/INTERRUPT:NORMAL

显示有关当前调试环境的信息

要显示有关当前调试环境的信息,包括当前定义的所有中断点或观察点,请发出不带参数的ZBREAK命令。

无参数ZBREAK命令描述调试环境的以下方面:

  • CTRL-C是否导致中断
  • 是否显示使用ZBREAK命令中的“T”操作指定的跟踪输出
  • 所有已定义断点的位置,带有描述其启用/禁用状态、操作、条件和可执行代码的标志
  • 存在观察点的所有变量,带有描述其启用/禁用状态、操作、条件和可执行代码的标志

此命令的输出将显示在定义为调试设备的设备上,该设备是的主要设备,除非已经使用使用调试设备一节中介绍的ZBREAK /DEBUG命令以不同的方式定义了调试设备。

下表介绍了为每个断点和观察点提供的标志:

显示部分 含义
断点/观察点的识别 断点的例程中的行。监视点的本地变量。
F: 提供有关ZBREAK命令中定义的操作类型的信息的标志。
S: 延迟执行ZBREAK-命令中定义的断点/观察点的迭代次数。
C: ZBREAK命令中设置的条件参数。
E: ZBREAK命令中设置的EXECUTE_CODE参数。

下表说明了如何在断点/观察点显示中解释F:值。F:值是第一列中适用的值的列表。

含义
E 已启用断点或观察点
D 已禁用断点或观察点
B 执行中断操作
L 执行“L”
L+ 执行“L+”
S 执行“S”
S+ 执行“S+”
T 输出跟踪消息

默认显示

首次进入Caché并使用ZB时,输出如下所示:

DHC-APP>zb
BREAK:
 
 No breakpoints
 No watchpoints
 
DHC-APP>

这意味着

  • 跟踪执行已关闭
  • 如果按CTRL-C,则不会中断
  • 未定义任何中断/观察点

断点和监视点存在时显示

此示例显示正在定义的两个断点和一个观察点

DHC-APP>ZBREAK +3^test:::"WRITE ""IN test"""
 
DHC-APP>ZBREAK -+3^test#5
 
DHC-APP>ZBREAK +5^test:"L"
 
DHC-APP>ZBREAK -+5^test
 
DHC-APP>ZBREAK *a:"T":"a=5"
 
DHC-APP>ZBREAK /TRACE:ON
 
DHC-APP>ZBREAK
BREAK: Trace ON
 
+3^test F:EB S:5 C: E:"WRITE ""IN test"""
+5^test F:DL S:0 C: E:
a F:ET S:0 C:"a=5" E:
 
DHC-APP>

前两个ZBREAK命令定义延迟的断点;后两个ZBREAK命令定义禁用的断点;第五个ZBREAK命令定义观察点。第六个ZBREAK命令启用跟踪执行。最后一个不带参数的ZBREAK命令显示有关当前调试设置的信息。

在该示例中,ZBREAK显示显示

  • 跟踪已打开
  • 如果按下CTRL-C,则不会中断。

然后,输出描述两个断点和一个观察点:

  • 第一个断点的F标志等于“EB”,S标志等于5,这意味着第五次遇到该行时将出现断点。E标志显示可执行代码,这些代码将在显示中断的Caché 终端提示之前运行。
  • 第二个断点的F标志等于“DL”,这意味着它被禁用,但如果启用,它将中断,然后单步执行断点位置之后的每一行代码。
  • 观察点的F标志为“ET”,表示观察点已启用。由于跟踪执行处于打开状态,跟踪消息将出现在跟踪设备上。由于未定义跟踪设备,因此跟踪设备将是主体设备。
  • C标志表示仅当条件为真时才显示跟踪。

使用调试设备

调试设备是指符合以下条件的设备:

  • ZBREAK命令显示有关调试环境的信息。
  • 如果发生中断,则会出现Caché终端提示。

注意:在Windows平台上,只有连接到COM端口的终端设备(如COM1)才支持到另一台设备的跟踪消息:

当进入Caché时,调试设备将自动设置主设备。在任何时候,调试I/O都可以通过以下命令发送到备用设备:ZBREAK/DEBUG:“DEVICE”

注:还可以执行特定于操作系统的操作。

在UNIX®系统上,要使tty01设备发生中断,请发出以下命令:

ZBREAK /D:"/dev/tty01/"

当由于CTRL-C或正在触发的断点或观察点而发生中断时,它会出现在连接到设备的窗口中。该窗口将成为活动窗口。

如果设备尚未打开,则执行自动打开。如果设备已经打开,则遵循任何现有的打开参数。

重要提示:如果指定的设备不是交互式设备(如终端),可能无法从休息中返回。但是,系统不强制执行此限制。

调试器示例Caché

首先,假设正在调试名为test的简单程序,如下所示。目标是将1放入变量a,将2放入变量b,将3放入变量c

test; Assign the values 1, 2, and 3 to the variables a, b, and c
 SET a=1
 SET b=2
 SET c=3 KILL a WRITE "in test, at end"
 QUIT

但是,当运行test时,只有变量bc保存正确的值:

USER>DO ^test
in test, at end
USER>WRITE
b=2
c=3
USER>

程序中的问题很明显:变量a在第4行被终止。但是,假设需要使用调试器来确定这一点.

可以使用ZBREAK命令设置例程测试中每行代码的单步执行(“L”操作)。通过结合单步执行和写入a的值,可以确定问题出在第4行:

USER>NEW 
USER 1S1>ZBREAK 
BREAK
No breakpoints
No watchpoints
USER 1S1>ZBREAK ^test:"L"
USER 1S1>DO ^test 
SET a=1
^
<BREAK>test+1^test
USER 3d3>WRITE a 
<UNDEFINED>^test
USER 3d3>GOTO
SET b=2
^
<BREAK>test+2^test
USER 3d3>WRITE a 
1
USER 3d3>GOTO
SET c=3 KILL a WRITE "in test, at end"
^
<BREAK>test+3^test
USER 3d3>WRITE a
1
USER 3d3>GOTO
in test, at end
QUIT
^
<BREAK>test+4^test
USER 3d3>WRITE a 
WRITE a
^
<UNDEFINED>^test
USER 3d3>GOTO
USER 1S1>

现在可以检查该行并注意kill a命令。在更复杂的代码中,现在可能希望通过该行单步执行命令(“S”操作)。

如果问题发生在DOFORXECUTE命令或用户定义函数中,可以使用“L+”或“S+”操作单步执行较低级别代码中的行或命令。

了解Caché 调试器错误

Caché调试器使用适当的Caché错误消息标记条件或执行参数中的错误。

如果错误在EXECUTE_CODE参数中,则当执行代码显示在错误消息之前时,条件将包含在执行代码周围。条件特殊变量($test)始终在执行代码结束时设置回1,以便调试器处理代码的其余部分正常工作。当控制返回到例程时,例程内的$TEST值将恢复。

假设为示例程序测试发出以下ZBREAK命令:

USER>ZBREAK test+1^test:"B":"a=5":"WRITE b"

在程序测试中,变量b未在TEST+1行定义,因此存在错误。错误显示如下:

IF a=5 XECUTE "WRITE b" IF 1
^
<UNDEFINED>test+1^test

如果尚未定义条件,则将在执行代码之前和之后定义一个假TRUE条件;例如:

USER>IF 1 WRITE b IF 1
使用中断进行调试

Caché包括三种形式的Break命令:

  • 没有在例程代码中插入参数的Break会在该位置建立断点。在代码执行期间遇到此断点时,将暂停执行并返回到终端提示符。
  • 带字母字符串参数中断可在启用逐行或逐命令单步执行代码的位置建立或删除断点。
  • 带整数参数的BREAK命令启用或禁用CTRL-C用户中断。

使用无参 Break暂停例程执行

要挂起正在运行的例程并将进程返回到终端提示符,请在例程中希望执行暂时停止的位置输入一个无参数中断。

当Caché遇到中断时,它会执行以下步骤:

  1. 挂起正在运行的例程
  2. 将进程返回到终端提示符。从终端提示符下,可以发出ObjectScript命令、修改数据以及执行进一步的例程或子例程,即使是出现错误或其他中断的例程或子例程也是如此。

要在例程挂起时恢复执行,请发出无参数的·GOTO·命令。

可能会发现,在无参数的Break命令上指定后置条件非常有用,这样只需设置后置条件变量就可以重新运行相同的代码,而不必更改例程。例如,例程中可能有以下行:

CHECK BREAK:$DATA(debug)

然后,可以设置变量DEBUG以挂起例程并将作业返回到终端提示符,或清除变量DEBUG以继续运行例程。

使用带参数的Break暂停例程执行

不必在要挂起例程的每个位置放置无参数中断命令。Caché提供了几个参数选项,允许逐步执行代码。可以通过单个步骤(换行“S”)或命令行(换行“L”)单步执行代码。

/// d ##class(PHA.TEST.ObjectScript).TestBreak()
ClassMethod TestBreak()
{
	b "L"
	s x = 2

	s x = x -3	
	
	f i = 1:1:10 d
	.s x = x +i
	
	q x
}

中断“S”和中断“L”之间的一个区别是许多命令行由多个步骤组成。这并不总是显而易见的。例如,下面都是一行(和一个ObjectScript命令),但每个命令都被解析为两个步骤:set x=1,y=2,kill x,y,write“hello”,!,if x=1,y=2

断开“S”和断开“L”都忽略标签行、注释和Try语句(尽管都在try块的右花括号处断开)。中断“S”在CATCH语句处中断(如果输入了CATCH块);中断“L”不会。

当打断使进程返回到端子提示时,打断状态不是STACKED。因此,可以更改中断状态,当发出无参数goto以恢复执行例程时,新状态仍然有效。

每当输入DOXECUTEFOR或用户定义函数时,Caché都会堆栈中断状态。如果选择BREAK“C”关闭断开,系统会在DOXECUTEFOR或用户定义函数结束时恢复断开状态。否则,Caché将忽略堆叠状态。

因此,如果在较低的子例程级别启用中断,则在例程返回到较高的子例程级别后,中断会继续.相反,如果在较高级别有效的低子例程级别禁用中断,则当返回到该较高级别时,中断将恢复。可以使用Break“C-”在所有级别禁用中断。

可以使用Break“L+”Break“S+”DOXECUTEFOR或用户定义函数内启用中断。

可以使用BREAK“L-”禁用当前级别的中断,但启用上一级别的换行。可以使用Break“S-”在当前级别禁用中断,但在上一级别启用单步中断。

正在关闭调试

要删除已为进程建立的所有调试,请使用Break “off”命令。此命令删除所有断点和观察点,并关闭所有程序堆栈级别的单步执行。它还会删除与调试和跟踪设备的关联,但不会关闭它们。

调用Break “Off”相当于发出以下一组命令:

 ZBREAK /CLEAR 
 ZBREAK /TRACE:OFF 
 ZBREAK /DEBUG:"" 
 ZBREAK /ERRORTRAP:ON 
 BREAK "C-"