Written August 2025
Following is a walkthrough of a debugging session, hopefully providing a feel for how it works while also pointing out several tips and other details that might otherwise go unnoticed in the help references.
We’ll start the debugging session with a target job TSKAAC, specifying the ersatz location ADDS: for the LSX directory:
.ADB /TARGETJOB=TSKAAC/LSXDIR=ADDS:
The target job in this case is running the program EXLIB:ADDSVUE[908,81], currently sitting at an input prompt. Assuming the target job is not blocking signals and/or TCP connections and is at least 7.0.1778, it should acknowledge the debugging request within a few seconds, after which we’ll see the following:
ADB version 1.0(106) Remote debugger
Message sent to TSKAAC on job 3 (JACKX1:3E)
Waiting for ACK .... [ACK]
Waiting for target to connect [^C to abort] ...
Connection Accepted!
Cmd/Rep: INIT/ACK Status: TIW / Running
Loc: ADDSVUE.RUN:009c33 Level: 0
Msg: Init Debugging
Debugging
ADB >
The indented block, similar to what we can request at any time with the INFO command, provides basic information about the state of the target job, including the name of the program it is running (ADDSVUE.RUN).
At this point, the debugging session consists of commands entered at the ADB prompt. You can display the available commands using the HELP command as shown here:
ADB > HELP
Breakpoint commands:
B{REAK} {program.ext}{:location} - set breakpoint
I{NFO} BREAK - list breakpoints
D(ELETE) # - delete breakpoint #
Run commands:
R{UN} cmdlin - force debuggee to run cmdlin (at dot prompt onl
F{ORCE} text - force text (+CR) into target input buffer
C{ONT} - continue (run) to next breakpoint
S{TEP} - execute one ASB instruction (step into calls)
N(EXT} - execute one ASB instruction (step over calls)
A(BORT) - send ^C to debuggee
Information/Display commands:
L{IST} {program.ext}{:location} - list source code
P{RINT} variable - display value of variable
I{NFO} - display status of debuggee
B{ACKTRACE} - show call stack (at breakpoint)
BT - same as BACKTRACE
Misc commands:
WIDTH ### - change display width
QUIT - quit debugging (debuggee continues running)
Enter by itself will typically default to prior command; this is useful for repetitive commands like STEP, NEXT and LIST +.
Typically, you would start with the LIST command to look at the source code in the vicinity of where the program counter is:
ADB > LIST
Loading LSX info ... 522 labels
Loading SYM info ... 2239 variables
Resolving local variable scope ... 807
ADDSVUE.RUN ...
009b9e endif
009b9e
009b9e totwidth = Fn'ADDS'Setup'Display'Record(ds, fnformatter$="Fn'<type>'
UnPack$")
009bbe
009bbe if not dlgview then ! [119] silence output in dl
gview mode
009bc5 ? "Total width of all display fields: ";totwidth
009bf7 endif
009bf7
009bf7 if Interactive then ! [113]
009bfd a = 0
009c04 input "Enter 1 to display structure info ... ",a
009c34 if a then
009c3a call Fn'ADDS'View'Structures(ds)
009c41 endif
009c41 endif
009c41
009c41 xcall SIZE, fspec'data$, filebytes
009c4d if filebytes < 1 then
ADB >
Note the messages at the top about loading the LSX / SYM info. This will happen whenever a command requires symbol information and the target program has changed. Multiple LSX / SYM files can be loaded during a single debugging session, as needed when debugging both a program and SBX modules it calls, or a series of programs chained to. Also note that the current location will be highlighted (009c04 above) if included in the list.
At this point we could hit ENTER to page forward, or LIST a specific location, such as the function referenced at location 009c3a above, e.g. LIST Fn’ADDS’View’Structures().
Aside from specifying explicit locations to LIST, you can also use the minus key ( – ) to page backwards and the plus key ( + ) to page forwards. And as with most ADB commands, just hitting ENTER will repeat the prior command, so you can continue in either direction by just hitting ENTER.
Note that labels, functions and variables are all case sensitive and that functions/procedures should be specified with a pair of empty parens (), althouigh they will be assumed for labels starting with Fn.
To minimize the amount of line wrapping, consider using the WIDTH command to increase the number of characters displayed per line.
We could also just set a breakpoint at the start of that function, like this:
ADB > BREAK Fn’ADDS’View’Structures()
Breakpoint set - use INFO BREAK to list breakpoints
Now, since the target program is at an INPUT statement, we can either switch to that session (if we have access), or we can force a response of “1” (to view the structure) using the FORCE command:
ADB > FORCE 1
That should cause the target program to call the Fn’ADDS’View’Structures() function and run into our breakpoint, at which point we’ll see:
Breakpoint @ ADDSVUE.RUN:0039ab
0039ab map2 flddef'hdr$,s, 80, " F# Struct or Field Name Type Size Subs :”
Cmd/Rep: INFO/ACK Status: RUN / At Break
Loc: ADDSVUE.RUN:0039ab Level: 2
ADB >
When a breakpoint is hit, the target will report its location, allowing ADB to display the source line. For a function, that will typically be the first MAP or other statement within the function. To get a better sense of where we are, we can use the LIST command again:
ADB > LIST 39AB
ADDSVUE.RUN ...
00398c endif
00398c endif
00398c EndFunction
003991
003991
003991 Function Fn'ADDS'View'Structures(ds as DYNSTRUCT:inputonly) as i4
0039ab
0039ab map1 locals
0039ab map2 rspec$,s,100
0039ab map2 rch,b,2
0039ab map2 flddef'hdr$,s, 80, " F# Struct or Field Name Type S
Note that while not theoretically necessary, in some cases a LIST command with no location specified may be ambiguous, in which case adding the location counter eliminates all confusion.
Also note that it is easy to get confused or out of sync given the semi-independence of the target program from ADB (with a mixture of synchronous and asynchronous communication between the two processes). A good go-to is to use the INFO command to re-sync and clarify the state of the target.
At this point you may want to single-step through the function:
ADB > STEP
0039f2 map2 flddef'mask$,s,80, "###. \----------Name------------\ \\...”
ADB >
003b34 Fields = fn'adds'get'layout(ds)
ADB >
004089 dsname$ = Fn'Dynst'Name'From'Instance$(ds)
Hitting ENTER at the ADB prompt repeats the prior command, and each STEP command executes one instruction and then displays the next source line. The above sequence (three STEP commands, the first one explicit, the other two implicit via ENTER) shows us stepping into the fn’adds’get’layout() function where we are about to call another function, Fn’Dynst’Nsme’From’Instance$(). Since we are already nested inside one or more functions, we might want to use the BACKTRACE (or BT) command to get a sense of our call stack:
ADB > BT
[Call stack:]
[1] 009c3a call Fn'ADDS'View'Structures(ds)
[2] 003b34 Fields = fn'adds'get'layout(ds)
[3] 004089 dsname$ = Fn'Dynst'Name'From'Instance$(ds)
[End of stack trace]
ADB >
The above stack trace shows that from the top level we called the function Fn’ADDS’View’Structures(), which then called fn’adds’get’layout(), which is about to call Fn’Dynst’Nsme’From’Instance$(). If we aren’t interested in stepping into that function, we can use the NEXT command in place of STEP to proceed to the next instruction in the function we are currently in:
ADB > NEXT
004094 debug.print (99,"adds") "layout",dsname$,DynName$
ADB >
0040ee if dsname$ # DynName$ then
ADB >
The NEXT command executed the function call and then stopped at the next line in the current function, at location 004094. We then hit ENTER (repeating another NEXT command), stopping at 0040ee. At this point, let’s check the values of those two variables:
ADB > PRINT dsname$
Cmd/Rep: PRINT/ACK
Vartyp: S,LOCAL (&h002101)
Size: 64
Value: STQ_BXHI
ADB > PRINT DynName$
Cmd/Rep: PRINT/ACK
Vartyp: S,STATIC (&h002201)
Size: 64
Value: STQ_BXHI
They both have the same value (STQ_BXHI) , so the body of the IF statement will be skipped.
Assuming that’s all we’re interested in as far as this function is concerned, we can use the CONT command to proceed to the next breakpoint:
ADB > CONT
Cmd/Rep: CONT/ACK Status: TIW / Running
Loc: ADDSVUE.RUN:003e59 Level: 2
ADB > LIST
003e4b close #rch
003e4f xcall EZTYP, rspec$
003e59
003e59 EndFunction
...
Note that the job status reported back from the NEXT command is “TIW / Running” (Terminal Input Wait), indicating that the program stopped at an input prompt before hitting a breakpoint (so it is technically "running", as opposed to waiting on ADB). And the LIST command shows us that the last instruction executed was a call to EZTYP. So the target is currently waiting for the user to exit from the EZTYP display. Some things we might do at this point:
| • | If we have keyboard access to the target session, we could just ESC and proceed. |
| • | > FORCE ^[ to force an ESC to the target to make it exit from EZTYP |
| • | > INFO BREAK to display the breakpoints |
[Breakpoint list:]
1) ADDSVUE.RUN:0039ab
[End of breakpoint list]
| • | > DELETE 1 to delete breakpoint #1 |
Cmd/Rep: CLEAR/ACK Status: TIW / Running
Loc: ADDSVUE.RUN:009cbc Level: 1
Msg: 1 breakpoint(s) deleted
Or we can just terminate the debugging session with the QUIT command:
ADB > QUIT
Cmd/Rep: QUIT/ACK Status: TIW / Running
Loc: ADDSVUE.RUN:009cbc Level: 1
Decoupling from debug session
Since the target job was already running when we launched ADB with the /TARGETJOB switch to connect to it, the QUIT command will completely disengage, leaving it running unaffected by the debugging activities. Although ADB can interrupt the target program flow, it can't change variables or execution logic, so shouldn't have any effect on the program outcome.