第十六章 疯狂Caché 命令行例程调试(三)
终端提示符显示程序堆栈信息
当中断命令挂起例程的执行或发生错误时,程序堆栈将保留一些堆栈信息。发生这种情况时,此信息的简要摘要将显示为终端提示符(NAMESPACE>)的一部分。例如,此信息可能采用以下形式:User 5D3>,其中:
| 代码 | 描述 |
|---|---|
5 |
指示有五个堆栈级别。堆栈级可能由DO、FOR、XECUTE、NEW、用户定义的函数调用、错误状态或中断状态引起。 |
d |
指示堆叠的最后一项是DO。 |
3 |
指示堆栈上有3个新状态、参数传递或用户定义函数。如果没有堆叠新命令、参数传递或用户定义函数,则此值为零。 |
下表列出了端子提示字母代码。
| 提示 | 定义 |
|---|---|
d |
DO |
e |
用户定义函数 |
f |
FOR循环 |
x |
XECUTE |
B |
BREAK 状态 |
E |
Error 状态 |
N |
NEW 状态 |
S |
登录状态 |
在下面的示例中,添加堆栈帧时,将显示命令行语句及其生成的程序员模式提示:
DHC-APP>new
DHC-APP 1S1>XECUTE "NEW WRITE 123 BREAK"
123
NEW WRITE 123 BREAK
^
<BREAK>
DHC-APP 3x1>NEW
DHC-APP 4B1>BREAK
BREAK
^
<BREAK>
DHC-APP 5N2>
可以使用Quit 1展开程序堆栈。以下是展开堆栈时的终端提示示例:
DHC-APP 5N2>q 1
DHC-APP 4B1>q 1
DHC-APP 3x1>q 1
DHC-APP 2N2>q 1
DHC-APP 1S1>q 1
DHC-APP>
USER 6f0>QUIT 1 /* for循环中出错. */
USER 5x0>QUIT 1 /* For循环位于XECUTE调用的代码中. */
USER 4f0>QUIT 1 /* XECUTE处于FOR循环中. */
USER 3f0>QUIT 1 /* 该for循环嵌套在另一个for循环中. */
USER 2d0>QUIT 1 /* do命令用于执行程序. */
USER 1S0>QUIT 1 /* 登录状态. */
USER>
For循环和While循环
以使用For或While执行相同的操作:循环,直到事件(通常是计数器增量)导致执行中断循环。但是,使用哪个循环构造会导致在代码模块上执行单步(中断“S+”或中断“L+”)调试。
for循环将新级别推送到堆栈上。WHILE循环不会更改堆栈级别。调试for循环时,从for循环内弹出堆栈(使用Break“C”GOTO``或QUIT 1)允许在for命令构造结束后立即使用命令继续单步调试。调试While循环时,发出Using Break“C”GoTo或Quit 1不会弹出堆栈,因此单步调试不会在While命令结束后继续。其余代码在不中断的情况下执行。
在中断或出错后恢复执行
在中断或错误后返回到终端提示时,Caché会跟踪导致中断或错误的命令的位置。稍后,只需在终端提示符下输入一个无参数的GOTO,即可在下一个命令恢复执行:
USER 4f0>GOTO
通过键入带参数的GOTO,可以在同一例程中带中断或错误的另一行的开头继续执行,如下所示:
USER 4f0>GOTO label3
还可以在不同例程的行首恢复执行:
USER 4f0>GOTO label3^rou
或者,可以使用无参数退出命令清除程序堆栈:
USER 4f0>QUIT
USER>
示例对话框
下面的示例中使用了以下例程。
MAIN ; 03 Jan 2019 11:40 AM
SET x=1,y=6,z=8
DO SUB1 WRITE !,"sum=",sum
QUIT
SUB1 ; 03 Jan 2019 11:42 AM
SET sum=x+y+z
QUIT
使用中断“L”,中断不会发生在例程SUB1中。
USER>BREAK "L"
USER>DO ^MAIN
SET x=1,y=6,z=8
^
<BREAK>MAIN+1^MAIN
USER 2d0>GOTO
DO ^SUB1 WRITE !,"sum=",sum
^
<BREAK>MAIN+2^MAIN
USER 2d0>GOTO
sum=15
QUIT
^
<BREAK>MAIN+3^MAIN
USER 2d0>GOTO
USER>
对于中断“L+”,中断也会发生在例程SUB1中。
USER>BREAK "L+"
USER>DO ^MAIN
SET x=1,y=6,z=8
^
<BREAK>MAIN+1^MAIN
USER 2d0>GOTO
DO ^SUB1 WRITE !,"sum=",sum
^
<BREAK>MAIN+2^MAIN
USER 2d0>GOTO
SET sum=x+y+z
^
<BREAK>SUB1+1^SUB1
USER 3d0>GOTO
QUIT
^
<BREAK>SUB1+2^SUB1
USER 3d0>GOTO
sum=15
QUIT
^
<BREAK>MAIN+3^MAIN
USER 2d0>GOTO
USER>
终端提示符下的新命令
无参数的new命令可以有效地将所有符号保存在符号表中,以便可以继续处理空的符号表。在出错或中断后出现终端提示时,可能会发现此命令特别有用。
要在不干扰符号表的情况下运行其他例程,请在终端提示符下发出不带参数的new命令。然后,系统会执行以下操作:
- 将当前帧堆栈在程序堆栈上。
- 返回输入新堆栈帧的终端提示。
例如:
USER 4d0>NEW
USER 5B1>DO ^%T
3:49 PM
USER 5B1>QUIT 1
USER 4d0>GOTO
5b1>提示表示系统已堆叠了通过分隔符输入的当前帧。1表示新命令具有堆叠的变量信息,可以通过发出Quit 1来删除这些信息。当希望恢复执行时,发出Quit 1以恢复旧符号表,并发出GOTO以恢复执行。
每当使用新命令、参数传递或用户定义函数时,系统都会将信息放在堆栈上,指示稍后在当前子例程或XECUTE级别的显式或隐式退出应删除某些变量并恢复其他变量的值。
可能会发现,了解是否执行了任何新命令、参数传递或用户定义函数(从而堆叠了一些变量)是很有用的,如果执行了,那么这些信息在堆栈中的位置有多远。
终端提示符下的QUIT命令
在终端提示符下,可以通过输入无参数退出命令从程序堆栈中删除所有项目:
USER 4f0>QUIT
USER>
要仅从程序堆栈中删除几个项目(例如,要离开当前正在执行的子例程并返回到上一个DO级别),请使用带整数参数的QUIT。Quit 1删除程序堆栈中的最后一项,Quit 3删除最后三项,依此类推,如下所示:
USER 9f0>QUIT 3
USER 6d0>
Caché错误消息
Caché在尖括号内显示错误消息,如<error>所示,后跟对发生错误时正在执行的行和例程的引用。插入符号(^)分隔行引用和例程。还显示了在发生错误时执行的命令的第一个字符下带有插入字符的中间代码行。例如:
SET x=y+3 DO ^ABC
^
<UNDEFINED>label+3^rou
此错误消息指示例程rou的行标签+3中的<unfinded>错误(指的是变量y)。此时,此消息也是特殊变量$ZERROR的值。
使用%STACK显示堆栈
可以使用%STACK实用程序执行以下操作:
- 显示进程执行堆栈的内容。
- 显示局部变量的值,包括使用新命令或通过参数传递“隐藏”的值。
- 显示进程状态变量的值,如
$IO和$JOB。
正在运行%STACK
通过输入以下命令可以执行%STACK:
USER>DO ^%STACK
如本例所示,%STACK实用程序显示没有变量的当前进程堆栈。
Level Type Line Source
1 SIGN ON
2 DO ~DO ^StackTest
3 NEW ALL/EXCL NEW (E)
4 DO TEST+1^StackTest SET A=1 ~DO TEST1 QUIT ;level=2
5 NEW NEW A
6 DO TEST1+1^StackTest ~DO TEST2 ;level = 3
7 ERROR TRAP SET $ZTRAP="TrapLabel^StackTest"
8 XECUTE TEST2+2^StackTest ~XECUTE "SET A=?TEST3()"
9 ?EXTFUNC ^StackTest ~SET A=?TEST3()
10 PARAMETER AA
11 DIRECT BREAK TEST3+1^StackTest ~BREAK
12 DO ^StackTest ~DO ^%STACK
在当前执行堆栈显示下,%STACK会提示执行堆栈显示操作。可以通过输入问号(?)获得帮助。在此提示下。在此提示符下按Return键可退出%STACK。
显示进程执行堆栈
根据在堆栈显示操作提示符下输入的内容,可以用四种形式显示当前流程执行堆栈:
- 不带变量,通过输入
*F - 使用特定的局部变量,输入
*V - 对于所有局部变量,通过输入
*P - 对于所有局部变量,前面是进程状态变量列表,方法是输入
*A
然后,%STACK在设备提示符上显示显示,使可以指定此信息的位置。按Return键向当前设备显示此信息。
显示不带变量的堆栈
首次进入%STACK实用程序或在Stack Display Action(堆栈显示操作)提示符下键入*F时,会出现不带变量的进程执行堆栈。
使用特定变量显示堆栈
在Stack Display Action提示下输入*V。这将提示输入要在堆栈中跟踪的局部变量的名称。指定单个变量或逗号分隔的变量列表.它返回所有局部变量的名称和值。在下面的示例中,正在跟踪变量e,并通过按Return将显示发送到终端,
Stack Display Action: *V
Now loading variable information ... 2 done.
Variable(s): e
Display on
Device: <RETURN>
显示包含所有已定义变量的堆栈
在Stack display Action提示符下输入*P以查看进程执行堆栈以及所有定义的局部变量的当前值。
显示包含所有变量(包括状态变量)的堆栈
在Stack Display Action(堆栈显示操作)提示符下输入*A以显示所有可能的报告。报告按以下顺序发布:
- 进程状态内部变量
- 具有所有局部变量的名称和值的进程执行堆栈
了解堆栈显示
堆栈上的每个项目都称为框架。下表介绍了为每个帧提供的信息。
%Stack实用程序信息
| 头 | 描述 |
|---|---|
Level |
标识堆栈中的级别。堆栈上最老的项目是数字1。没有关联标高编号的框架共享首次出现在其上方的标高。 |
Type |
标识堆栈上的帧类型,可以是:直接中断:遇到导致返回到直接模式的中断命令。直接调入:使用Caché调入接口从Caché外部的应用程序启动一个Caché进程。直接错误:遇到导致返回到直接模式的错误。DO:已执行DO命令。错误陷阱:如果例程设置$ZTRAP,则此帧标识错误将导致执行继续的位置。FOR:执行了FOR命令。NEW:执行了NEW命令。如果新命令有参数,则会显示它们。登录:Caché进程的执行已启动。XECUTE:执行了XECUTE命令。?EXTFUNC:执行了用户定义函数。 |
Line |
在格式Label+Offset^例程中标识与帧关联的ObjectScript源行(如果可用)。 |
Source |
显示该行的源代码(如果可用)。如果信号源太长,无法在提供的区域中显示,则可以使用水平滚动。如果设备是面向行的,则源自动换行,并在连续行前面加上“.”。 |
下表显示了级别、线条和源值是否可用于每种帧类型。标高下的"No" 表示标高编号不会递增,并且显示屏中不会显示任何标高编号。
可用的帧类型和值
| 帧类型 | Level |
Line |
Source |
|---|---|---|---|
DIRECT BREAK |
Yes | Yes | Yes |
DIRECT CALL IN |
Yes | No | No |
DIRECT ERROR |
Yes | Yes | Yes |
DO |
Yes | Yes | Yes |
ERROR TRAP |
No | No | 不会,但会显示新的$ZTRAP值。 |
FOR |
No | Yes | Yes |
NEW |
No | No | 显示新的形式(包含或排除)和受影响的变量。显示P值。 |
PARAMETER |
No | No | 显示形式参数列表。如果参数是通过引用传递的,则显示指向同一内存位置的其他变量。 |
SIGN ON |
Yes | No | No |
XECUTE |
Yes | Yes | Yes |
?EXTFUNC |
Yes | Yes | Yes |
如果从终端提示符调用这些参数,则行值为空。
显示特定堆栈级别的变量
要查看存在于给定堆栈帧级别的变量,请在“Stack display Action”提示符下输入?#,其中#是堆栈帧级别。下例显示了如果请求级别1的变量时的显示。
Stack Display Action: ?1
The following Variables are defined for Stack Level: 1
E
Stack Display Action:
使用变量显示堆栈级别
可以通过输入??显示在所有堆栈级别定义的变量。在“Stack Display Action”(堆栈显示操作)提示下。以下示例显示了选择此操作时的示例显示。
Stack Display Action: ??
Now loading variable information ... 19
Base Stack Level: 5
A
Base Stack Level: 3
A B C D
Base Stack Level: 1
E
Stack Display Action:
显示进程状态变量
要显示进程状态变量,如$IO,请在“Stack display Action”提示符下输入*S。将看到下表中列出的这些已定义变量(进程状态内部变量):
| 进程状态内部变量 | 文档 |
|---|---|
| $D = | $DEVICE 特殊变量 |
| $EC = ,M9, | $ECODE 特殊变量 |
| $ES = 4 | $ESTACK 特殊变量 |
| $ET = | $ETRAP 特殊变量 |
| $H = 64700,50668 | HOROLOG 特殊变量 |
$I = |TRM|:|5008 |
$IO 特殊变量 |
| $J = 5008 | $JOB 特殊变量 |
$KEY 特殊变量 |
|
| $P = | TRM |
| $Roles = %All | $ROLES 特殊变量 |
| $S = 268315992 | $STORAGE 特殊变量 |
| $T = 0 | $TEST 特殊变量 |
| $TL = 0 | $TLEVEL 特殊变量 |
| $USERNAME = glenn | $USERNAME 特殊变量 |
| $X = 0 | $X 特殊变量 |
| $Y = 17 | $Y 特殊变量 |
| $ZA = 0 | $ZA 特殊变量 |
$ZB 特殊变量 |
|
| $ZC = 0 | $ZCHILD 特殊变量 |
| $ZE = | $ZERROR 特殊变量 |
| $ZJ = 5 | $ZJOB 特殊变量 |
| $ZP = 0 | $ZPARENT 特殊变量 |
$ZR = ^||a |
$ZREFERENCE 特殊变量 |
| $ZS = 262144 | $ZSTORAGE 特殊变量 |
| $ZT = | $ZTRAP 特殊变量 |
| $ZTS = 64700,68668.58 | $ZTIMESTAMP 特殊变量 |
| $ZU(5) = USER | $NAMESPACE |
| $ZU(12) = c:\intersystems\cache\mgr| NormalizeDirectory() | |
| $ZU(18) = 0 | Undefined() |
| $ZU(20) = USER | UserRoutinePath() |
| $ZU(23,1) = 5 | |
| $ZU(34) = 0 | |
| $ZU(39) = USER | SysRoutinePath() |
| $ZU(55) = 0 | LanguageMode() |
| $ZU(56,1) = 1349 | |
| $ZU(61) = 16 | |
| $ZU(61,30,n) = 262160 | |
| JobType | |
| UserName | |
| ClientNodeName | |
| ClientExecutableName | |
| CSPSessionID | |
| ClientIPAddress | |
| State | |
| Routine | |
| NameSpace | |
$ZU(67,7,$J) = |TRM|:|5008 |
CurrentDevice |
| LinesExecuted | |
| GlobalReferences | |
| $ZU(68,1) = 0 | NullSubscripts() |
| $ZU(68,21) = 0 | SynchCommit() |
| $ZU(68,25) = 0 | |
| $ZU(68,27) = 1 | |
| $ZU(68,32) = 0 | ZDateNull() |
| $ZU(68,34) = 1 | AsynchError() |
| $ZU(68,36) = 0 | |
| $ZU(68,40) = 0 | SetZEOF() |
| $ZU(68,41) = 1 | |
| $ZU(68,43) = 0 | OldZU5() |
| $ZU(68,5) = 1 | BreakMode() |
| $ZU(68,6) = 0 | |
| $ZU(68,7) = 0 | RefInKind() |
| $ZU(131,0) = MYCOMPUTER | |
| $ZU(131,1) = MYCOMPUTER:CACHE | |
| $ZV = Cache for Windows (x86-64) 2017.2.1 (Build 801_1U) Mon Mar 12 2018 22:47:10 EST | $ZVERSION 特殊变量 |
打印堆栈和/或变量
当选择以下操作时,可以选择输出设备:
*P*A*V选择要显示的变量后。
其他调试工具
还可以使用其他工具来帮助调试过程。这些措施包括:
显示对具有$SYSTEM.OBJ.ShowReferences的对象的引用
要显示进程符号表中包含对给定对象的引用的所有变量,请使用%SYSTEM.OBJ类的ShowReferences(OREF)方法。OREF是给定对象的OREF(对象引用)。
错误捕获实用程序
错误捕获实用程序%ETN和%ERN通过存储变量和记录有关错误的其他相关信息来帮助进行错误分析。
%ETN应用程序错误陷阱
可能会发现,将错误陷阱设置为对应用程序错误执行实用程序%etn会很方便。该实用程序在出错时保存有关作业的有价值的信息,如执行堆栈和变量值。此信息保存在应用程序错误日志中,您可以使用%ern实用程序显示该日志,也可以在管理门户的查看应用程序错误日志页面(系统操作、系统日志、应用程序错误日志)上查看该日志。
使用以下代码将错误陷阱设置为此实用程序:
SET $ZTRAP="^%ETN"
备注:在程序中,不能将$ZTRAP设置为外部例程。由于此限制,不能在过程(包括属于程序的类方法)中使用^%ETN。但是,可以将$ZTRAP设置为调用%ETN的本地标签。
当发生错误并调用%ETN实用程序时,会看到类似于以下消息的消息:
Error has occurred: <SYNTAX> at 10:30 AM
因为%ETN以HALT命令结束(终止进程),所以可能只想在应用程序模式下使用例程时设置%ETN错误陷阱。当终端提示符处出现错误时,在终端上显示错误并进入调试器提示符以允许立即分析错误可能很有用。以下代码仅在Caché处于应用程序模式时设置错误陷阱:
SET $ZTRAP=$SELECT($ZJ#2:"",1:"^%ETN")
%ERN应用程序错误报告
%ern实用程序检查%ETN错误捕获实用程序记录的应用程序错误。
在下面的代码中,发出例程报告的ZLOAD,以说明通过使用“*load”加载所有变量,然后加载例程,可以重新创建发生错误时作业的状态,但记录有关DoS等信息的程序堆栈是空的。
USER>DO ^%ERN
For Date: 4/30/2018 3 Errors
Error: ?L
1) "<DIVIDE>zMyTest+2^Sample.MyStuff.1" at 10:27 am. $I=|TRM|:|10044 ($X=0 $Y=17)
$J=10044 $ZA=0 $ZB=$c(13) $ZS=262144 ($S=268242904)
WRITE 5/0
2) <SUBSCRIPT>REPORT+4^REPORT at 03:16 pm. $I=|TRM|:|10044 ($X=0 $Y=57)
$J=10044 $ZA=0 $ZB=$c(13) $ZS=2147483647 ($S=2199023047592)
SET ^REPORT(%DAT,TYPE)=I
3) <UNDEFINED>zMyTest+2^Sample.MyStuff.1 *undef" at 10:13 pm. $I=|TRM|:|12416 ($X=0 $Y=7)
$J=12416 $ZA=0 $ZB=$c(13) $ZS=262144 ($S=268279776)
WRITE undef
Error: 2
2) <SUBSCRIPT>REPORT+4^REPORT at 03:16 pm. $I=|TRM|:|10044 ($X=0 $Y=57)
$J=10044 $ZA=0 $ZB=$c(13) $ZS=2147483647 ($S=2199023047592)
SET ^REPORT(%DAT,TYPE)=I
Variable: %DAT
%DAT="Apr 30 2018"
Variable: TYPE
TYPE=""
Variable: *LOAD
USER>ZLOAD REPORT
USER>WRITE
%DAT="Apr 30 2018"
%DS=""
%TG="REPORT+1"
I=88
TYPE=""
XY="SET $X=250 WRITE *27,*91,DY+1,*59,DX+1,*72 SET $X=DX,$Y=DY"
USER>