Debugging is an art form. Everybody does his or her own thing to get programs to work, and opinions vary widely as to just what the best debugging aid is. I probably won't change anyone's mind about that, but let me describe what I do and some of the tools I use to assist in debugging.
One of the great advances in programming productivity was the addition of a syntax checker when you are entering source. This eliminates a lot of dumb errors being found by the compiler. The AS/400 went a bit further with this concept by allowing commands to be prompted and checked with the command definition object. This provides a good deal of validity checking (including an optional validity checking program) when you enter a command into a source member. These functions help fix a lot of errors while you are keying rather than have the compiler or execution routines find them.
The CL compiler is going to find errors, such as when the variable is not declared, the GOTO label does not exist, or there are too many DOs or ENDDOs.
The missing DCL and label problems are usually easy to fix without a lot of searching in the code. However, the DO/ENDDO problem can be a real nightmare because the CL compiler listing doesn't give you any assistance on finding your DO groups (as compared to the RPG compiler listing).
If you just added some code and the program no longer compiles because of a DO group problem, you probably know where to look. However, in a more complex case, you may have to look at the whole program.
Several years ago, I did a tool in QUSRTOOL named Display CL Pgm DO Cmds (DSPCLPDO), which is also in the TAA Productivity Tools. It shows you the DO groups with a numbering scheme similar to what RPG uses to highlight the DO groups. The command prompt is shown in Figure 1, and some typical output is shown in Figure 2. Printed output occurs from the command, and Display Spooled File (DSPSPLF) is automatically used. I decided a better idea would be to indent the DO groups to get a pictorial view of what the code was doing. This is the
Print CLP DO Groups (PRTCLPDO) command, and it is in the TAA Productivity Tools product. The command prompt for PRTCLPDO is shown in Figure 3, and some typical output is shown in Figure 4. PRTCLPDO is what I use now. It is also helpful when you are trying to review the logic of a CL program.
These commands do not change the source code. If you prefer to indent DO groups in the source code itself, you can use the Indent CLP DO Groups (INDCLPDO) command in the TAA Productivity Tools, or you can use the Format CL Source (FMTCLSRC) command (see "Indent Your CL Code Automatically," MC, April 1997).
Ideally, a program should include a global Monitor Message (MONMSG) command to trap unexpected errors. During debugging, you may want to comment out the global MONMSG so the system will stop and ask you how you want to handle an expected error condition.
When your program encounters an unexpected error, you will probably see the CPA0701 inquiry message. Your choices will be C, D, I, and R (meaning cancel, dump, ignore, and retry, respectively). Move the cursor to the error and press the F1 key to see a further explanation of the error. This may tell you enough to fix the problem, but if it doesn't, press the F10 key to see the job log.
A simple but effective means of tracing what a CL program does is to turn on logging. This is controlled by the LOG parameter on CRTCLPGM. Most people take the default of *JOB, which means that the logging function can be requested by the job in which the program is running. You can request logging by using this command:
CHGJOB LOGCLPGM(*YES)
You can also turn off logging with LOGCLPGM(*NO). These commands are big messy things to key, and I was using them so often that I finally wrote a shorthand command, Log CL Program (LOGCL). The LOGCL command is in both QUSRTOOL and the TAA Productivity Tools. To get the same function as the CHGJOB mouthful, just key this:
LOGCL
The default is to cause logging, but you can also turn it off with LOG(*NO). After turning on logging, you call the program. The logging that is produced occurs in the form of low-level messages. The logging is not perfect, but two good things happen:
o Some statement numbers appear, so you get a reasonable trace of the program.
o Many of the commands are shown with the real data rather than variable names.
Figure 5 shows what happens if you request logging of the simple program shown previously.
Some major things are missing in the logging. CHGVAR commands are not shown. Also, CALL commands are shown, but not the parameter values.
If I am having trouble with a program, I usually try LOGCL first. However, if you use RMVMSG CLEAR(*ALL), it blows away all the messages received in your program message queue. This includes the logging messages. So if your program is completing normally and you have a RMVMSG CLEAR(*ALL) function, logging isn't going to help you.
Sometimes a "dump" of the variables of the program can be helpful. A dump is a report that
shows the messages on the program's message queue and the contents of all variables, in both character and hexadecimal formats.
You can request a dump in two ways. You can include the Dump CL Program (DMPCLPGM) command in a program, or you can select option D when you respond to the inquiry message for an unmonitored escape message. I don't use this too much, but sometimes it helps. On a rare occasion, I have temporarily placed a DMPCLPGM command into my program to try to figure out what was wrong.
If I still can't figure out what is wrong, I use debug commands. Sometimes, I even use the debug functions just to ensure my program is doing what it should be doing. There are several forms of debug commands you can use. What people actually do varies considerably. I do what I am accustomed to and what works well with my programming approach.
I usually program by using two group jobs. I use the Attn key to flip-flop back and forth. I use the ATNPGM tool in QUSRTOOL and the TAA Productivity Tools to handle the Attn key. I use one group job for source entry and to create programs. I use the other group job for command entry (including debug commands). A single keystroke lets me flip to the other job.
I write OPM CL programs. For several years, I used the system-supported Start Debug (STRDBG) command and associated commands such as Add Breakpoint (ADDBKP) and Add Trace (ADDTRC). These commands are awkward. The prompts are confusing, and you have to put quotes around the CL variables to be displayed.
Here's a typical series of commands:
STRDBG PGM(PGMA)
ADDTRC STMT((10100 12500)) +
PGMVAR(('&VARA' '&VARB'))
CALL PGM(PGMA)
For most of the CL programs I write, I don't need to go into debug mode. But occasionally I'll have a tough program to get debugged. When I do, I use the debug facility a lot. So for debug, either I don't use it at all or I use it a lot on a specific program. If I'm using debug, I generally have an iterative process:
o Make a source change
o Re-create the program
o Flip to my other group job
o Set up for debug
o Call the program
o Display the trace data
I may go through this same set of steps many times before the program is finally working. If you do a similar set of steps, the really horrible thing that can occur is you get distracted and miss the message that says the re-create step failed. I always assume the program is going to compile successfully once I am in this iterative mode. If you take the default on CRTCLPGM of REPLACE(*YES) and the re-create fails, the old program is still there. So if you just call the
program or try to debug it, you can waste a lot of time trying to figure out why your source change didn't do anything.
The REPLACE(*YES) function may be good for replacing production programs, but I began to hate it when testing. I finally fixed my test environment so that I always delete the program before attempting a re-create. If the re-create fails, the program isn't there, and I never waste time debugging the old version. If you'd like to use the same technique to delete the old version first, see the Submit Parameters (SBMPARMS) tool in QUSRTOOL or the TAA Productivity Tools.
The other problem I found with this iterative process is that the debug commands are frustrating and awkward to use. For example, if you use STRDBG, the system sets a pointer to the program. If you re-create the program, the pointer isn't good anymore. If you don't end debug and start the debug process again, you won't get any debug information. You can't even use the Change Debug (CHGDBG) command and rename the same program. So the whole process of iterating can be very painful because of what you have to do to make it work.
After getting confused and frustrated, I wrote a front-end to the system debug function. It's called the Breakpoint (BKP) tool, and it's in QUSRTOOL and the TAA Productivity Tools. The BKP tool supports two basic commands, BKP and TRACE.
Both commands cause a combination of End Debug (ENDDBG), which monitors if not in debug mode, STRDBG, and then either ADDBKP or ADDTRC. You always start clean every time you use BKP or TRACE. The CL variable names are entered without quotes or ampersands. So you would just say something like this:
TRACE STMT(10100 12500) +
PGMVAR(VARA VARB) PGM(PGMA)
Then, to iterate, you just say this:
TRACE STMT(xxx xxx)
CALL PGM(PGMA)
DSPTRCDTA
Both the BKP and TRACE commands are smart enough to remember what program you were working on and what variables you want displayed. All you have to do is vary the statement values. If you want to change the variables, you prompt for the command. They are shown by use of a prompt override program. The values are stored in a data area in QTEMP and extracted by the command processing programs.
I prefer the trace rather than a breakpoint or step function for several reasons:
o If I am smart enough to know where to put the breakpoint, I am usually smart enough to see what the problem is and just fix the code.
o If I am confused as to what is happening, the trace shows me what the program is doing by making a little log. I've used step functions, and the problem I have with them is that I get lulled to sleep pressing the Enter key. Then, when I finally realize I am in the wrong place, I can't back up, and there is no log to show how I got to the wrong place or to review what happened. Many times when debugging, I fall into the trap of thinking I understand the problem when I really don't. Having the log of what the program really has done helps solve some of this.
o Because I use group jobs and delete the program as soon as I submit the re-create, there is a
horrible thing I can do to myself if I use a breakpoint. If I stop at a breakpoint, realize what the problem is, flip to the other side, fix the program, re-create, and then flip back, I am suddenly at a breakpoint of a program that does not exist anymore. Sometimes, I can get out successfully with F3, but sometimes, I wind up taking the job down because the program got deleted while I was using it. This problem does not occur when you use a trace because you have ended the program when you are looking at the trace data.
o Sometimes, when it is a very tough problem, I may generate a lot of trace output. Then, I use DSPTRCDTA OUTPUT(*PRINT) and the scan facility within DSPSPLF to find what I am looking for.
o Sometimes, I use the trace function even if my program looks like it is working correctly just to ensure it is executing the instructions I think it should be. You can sometimes find surprises with this technique.
Sample trace output is shown in Figure 6.
The BKP tool also works with any OPM program, and I use it for RPG as well.
You can use the new debugger with OPM CL programs. When you create the program, specify OPTION(*SRCDBG). Then, use the STRDBG command and specify OPMSRC(*YES). You can then use the functions of watch a variable, step, and so on. While there are some real pluses for the new debugger, having to specify the extra parameters is a pain, and it does not have a trace facility. Since I am most likely going to change the source anyway, I prefer the group job approach and the old debug function.
I sometimes place statements in the source to help me debug. For CL programs, I may send messages or use Copy File (CPYF) to QTEMP or *PRINT. For RPG, I may output some extra print lines or add a print file. While this is a good technique, you have to remember to get these debug statements out of your code before you go into production. I try to follow a convention of using a comment with the letters TSTJFS when I add this kind of code. (JFS are my initials.) So when I am ready to go into production, I just scan the source for TSTJFS.
If I have a CL program that is building up a command to execute using QCMDEXC, I like to send myself a message about what the command is, such as this:
CHGVAR VAR(&CMD) +
VALUE('OPNQRYF FILE(' +
*CAT &xxx *TCAT ...
SNDPGMMSG MSG(&CMD) TOPGMQ(*SAME)
CALL PGM(QCMDEXC) PARM(&CMD 500)
RMVMSG CLEAR(*ALL)
When the message is sent to your own program message queue by the function TOPGMQ(*SAME), the message exists along with all the other messages sent to your program. I usually clear all these messages at the end of my program with the Remove Message (RMVMSG) command. However, if the CALL to QCMDEXC ends abnormally, I have a message in the job log that tells me what the command was that I tried to execute. With complex commands like OPNQRYF, this can be very helpful.
Sometimes, I use another form of these audit trail messages in batch programs. I will usually front-end the message with some specific characters to help me find where I am in the job log.
For example, I might send a message as follows:
SNDPGMMSG MSG('%-% Beginning the update program') +
TOPGMQ(*SAME)
If the job aborts, I can scan the job log for %-% and get a good idea of where the program blew up.
Most of the time, I don't have a lot of trouble debugging CL programs. I think this is a combination of the CL language and the type of coding that is normally done in CL. But sometimes, there is a real doozy to solve, and I appreciate some of the good debug things you can do. You have a lot of choices as to how you debug. Get familiar with one you like, and it will have a good payoff for you.
Jim Sloan, president of Jim Sloan, Inc., is a software vendor and consultant. A retired IBMer, Sloan was a software planner on the S/38 when it began as a piece of paper. He also worked on the planning and early releases of the AS/400. In addition, Sloan wrote the TAA tools that exist in QUSRTOOL and is now the owner of the TAA Productivity Tools product. He has been a speaker at COMMON for many years.
Every copy of OS/400 comes with the QUSRTOOL library, which contains the source code for many useful programmer/operator tools. The QUSRTOOL library must be unpackaged, and each tool within the library must be created. For information about how to unpackage the library and create the tools, see source member TTTINFO in source file QATTINFO in library QUSRTOOL.
Many of the tools in the QUSRTOOL library have been removed in OS/400 releases beyond V3R1 (e.g., V3R2 and V3R6), including a large group of tools known as the TAA Tools. For a complete list of the tools that have been removed, see source member AAAAREADME in source file QATTINFO in library QUSRTOOL.
If you are at a release beyond V3R1, you may not be able to find the tools mentioned in this article in the QUSRTOOL library. The tools, however, can be obtained from TAA Productivity Tools, a licensed product from Jim Sloan, Inc.
For more information about TAA Productivity Tools, contact
Jim Sloan, Inc.
c/o Barsa Consulting Group, Inc.
Tel: 914-251-9494; Fax: 914-253-9413
Figure 1: DSPCLPDO command
Display CL Pgm DO Cmds - TAA (DSPCLPDO)
Member............. Name
CLsourcefile......... QCLSRC Name
Library ........... *LIBL Name,*LIBL,*CURLIB
Output(printoutput) ..... *NO *YES,*NO,*,*PRINT
Figure 2: DSPCLPDO output
Display Spooled File
File .....: TSTDBG
Control.....
Find ......
*...+....1....+....2....+....3....+....4....+....5....+....6....+....7....+....8....+....9....+....
10/21/97 11:55:23 TAASYS5 DSPCLPDO Command
File-QCLSRC Library-SLOANT Member-TSTDBG
DO Grp Err Seq Nbr Statement
1.00 PGM
2.00 DCL VAR(&VARA) TYPE(*CHAR) LEN(5)
3.00 DCL VAR(&JOBD) TYPE(*CHAR) LEN(10)
4.00 DCL VAR(&OUTPUT) TYPE(*CHAR) LEN(6)
5.00 CHGVAR &VARA 'ABCD'
6.00 CHGVAR &OUTPUT '*PRINT'
7.00 CHGVAR &JOBD 'QBATCH'
8.00 CHKOBJ OBJ(&JOBD) OBJTYPE(*JOBD)
B 1 9.00 MONMSG MSGID(CPF9801) EXEC(DO) /* Not found */
B 2 10.00 IF (&VARA *EQ 'XXX') DO /* IF XXX */
2 11.00 CHGVAR &OUTPUT '*'
E 2 12.00 ENDDO /* If XXX */
E 1 13.00 ENDDO /* Not found */
14.00 CALL PGMX PARM(&VARA)
15.00 DSPJOBD JOBD(&JOBD) OUTPUT(&OUTPUT)
16.00 ENDPGM
F3=Exit F12=Cancel F19=Left F20=Right F24=More keys Print CLP DO Groups - TAA (PRTCLPDO)
Member............. Name
CLP source file . . . . . . . . QCLSRC Name
Library ........... *LIBL Name,*LIBL,*CURLIB
Display spooled file . . . . . . *YES *YES, *NO Display Spooled File
File .....: TSTDBG
Control.....
Find ......
*...+....1....+....2....+....3....+....4....+....5....+....6....+....7....+....8....+....9....+....
10/21/97 11:57:13 TAASYS5 PRTCLPDO - TAA - Print CLP Do Groups Member-TSTDBG
File-QCLSRC Library-SLOANT DSPSPLF-*YES
Seq ErrNte
1.00 PGM
2.00 DCL VAR(&VARA) TYPE(*CHAR) LEN(5)
3.00 DCL VAR(&JOBD) TYPE(*CHAR) LEN(10)
4.00 DCL VAR(&OUTPUT) TYPE(*CHAR) LEN(6)
5.00 CHGVAR &VARA 'ABCD'
6.00 CHGVAR &OUTPUT '*PRINT'
7.00 CHGVAR &JOBD 'QBATCH'
8.00 CHKOBJ OBJ(&JOBD) OBJTYPE(*JOBD)
9.00 MONMSG MSGID(CPF9801) EXEC(DO) /* Not fo
10.00 * IF (&VARA *EQ 'XXX') DO /* IF XXX */
11.00 * * CHGVAR &OUTPUT '*'
12.00 * ENDDO /* If XXX */
13.00 ENDDO /* Not found */
14.00 CALL PGMX PARM(&VARA)
15.00 DSPJOBD JOBD(&JOBD) OUTPUT(&OUTPUT)
16.00 ENDPGM
F3=Exit F12=Cancel F19=Left F20=Right F24=More keys
Figure 3: PRTCLPDO command
Figure 4: PRTCLPDO output
Figure 5: Logging output
800 - CHKOBJ OBJ(QBATCH) OBJTYPE(*JOBD)
1400 - CALL PGM(PGMX) /* The CALL command contains parameters */
1500 - DSPJOBD JOBD(QBATCH) OUTPUT('*PRINT')
- RETURN /* RETURN due to end of CL program */
Display Trace Data
Statement/
Program Instruction Recursion Level Sequence Number
TSTDBG 500 1 1
Startposition ............:1
Length ................:*DCL
Format ................:*CHAR
Variable ...............:&VARA
Type ................: CHARACTER
Length ...............: 5
*...+....1....+....2....+....3....+....4....+....5
''
Statement/
Program Instruction Recursion Level Sequence Number
TSTDBG 600 1 2
Startposition ............:1
Length ................:*DCL
Format ................:*CHAR
*Variable ...............:&VARA
Type ................: CHARACTER
Length ...............: 5
*...+....1....+....2....+....3....+....4....+....5
'ABCD '
Statement/
Program Instruction Recursion Level Sequence Number
TSTDBG 700 1 3
TSTDBG 800 1 4
TSTDBG 1400 1 5
TSTDBG 1500 1 6
TSTDBG 1600 1 7
LATEST COMMENTS
MC Press Online