Wednesday, March 20, 2019

API to check if debug is active

determine if debug running

There are some APIs I stumble across and I am left wondering why they were created? The API I will be writing about today, QteRetrieveDebugAttribute, is one of those. This API is used to determine what the debug attribute values are for a job when it is being debugged. I am not going to list which debug attributes they are as it is not relevant to what I am trying to determine. If you are interested in learning what they are click on the link to IBM's documentation at the bottom of this post.

In this example I am just interest to know if the job is being debugged.

I am going to have a procedure within a module call the API, this way I can either bind it into the calling program or included it in a service program, which is my preference. Let me start with the module:

01  **free
02  ctl-opt nomain ;

03  /define CheckDebug
04  /include mylib/mysrc,prototypes
05  /undefine CheckDebug

06  /include qsysinc/qrpglesrc,qusec ;

07  dcl-proc CheckDebug export ;
08    dcl-pi *n char(1) ;
09    end-pi ;

10    dcl-pr DebugAttribute extproc('QteRetrieveDebugAttribute') ;
11      *n char(10) const ;
12      *n char(10) const ;
13      *n likeds(QUSEC) ;
14    end-pr ;

15    QUSBPRV = %size(QUSEC) ; //Bytes provided

16    DebugAttribute('*UPDPROD':'':QUSEC) ;

17    if ((QUSBAVL > 0) and (QUSEI = 'CPF9541')) ;
18      return '0' ;
19    else ;
20      return '1' ;
21    endif ;
22  end-proc ;

Line 1: We all know I only code in totally free RPG.

Line 2: NOMAIN means that when this is compiled as there is no main procedure, therefore, this cannot be called as a program. This means that this object will be a collection of procedures.

Lines 3 – 5: I have put the procedure prototypes into another source member, this allows me to either use the /INCLUDE or /COPY compiler directive to copy code into this source member. Only the pieces of code I have defined in the other source member will be included. The rest will be ignored.

Line 6: The API uses the QUSEC data area to contain information about the errors returned from the API. Rather than define my own data structure I am copying the one defined in the QSYSINC library.

Line 7: Start of the procedure. Notice that there is EXPORT, this means that the procedure is exported to be called in other programs and procedures in other modules and service programs.

Lines 8 and 9: I never bother to name my procedure interfaces, therefore, I have to use *N. As I have CHAR(1) on the same line as the start of the interface declaration this means that the procedure returns a single character value. This procedure has no incoming parameters, therefore, the end of the interface definition comes on the line following the start.

Lines 10 – 14: This is the definition of the external procedure I will be using to determine if the program is being debugged. I have decided to use my own name for the external procedure, line 10, as its real name is just too long. As I have not used the external procedures name I need to give the API's name in the EXTPROC keyword.

This API has three parameters:

  1. Input: Debug attribute
  2. Output: Value of that attribute
  3. Output: Error data structure

I have coded the first two parameters as CONST so that these values do not get changed by the API, and I can use a string rather than a variable when I call it. The third, the error data structure, I need, and I have defined to be like the QUSEC data structure I included into this source member, on line 6.

Line 15: The bigger the number I put in the QUSEC's subfield QUSBPRV the more information it will return. If I have moved zero to QUSBPRV no error information would have been returned in the QUSEC data structure. Here I want all the information returned, therefore, I move the value that is the size of the error data structure to the subfield.

Line 16: Here is where I call the API. I could have used any of the allowed values in the first parameter, I just chose to use *UPDPROD. I don't care about having a value returned in the second parameter, therefore, I have used null ( '' ) in that position. The third parameter is the error data structure, which I have returned into the error data structure.

Lines 17 - 21: This is where I can determine if debug is active and return a single character to whatever called this procedure. If the error data structure contains any data the bytes available, QUSBAVL, will be greater than zero, and the returned error message id is CPF9541, "Not in debug mode.", then debug is not active and I return zero. If the number of bytes is zero then I return one as a flag that debug is active.

I put the procedure prototype in a different source member called PROTOTYPES in the source file MYSRC, see lines 3-5 of the procedure above. This is what the source member looks like:

01  **free

02  /if defined(CheckDebug)
03  dcl-pr CheckDebug char(1) ;
04  end-pr ;
05  /endif

Line 1: I need the free as my code starts in the first column.

Line 2: The best way I can explain it is that this line gives a section of the source member a name. All of the code between /IF and the next /ENDIF will be copied into another source member if I use the matching /DEFINE in that source member. The section can contain just a procedure prototype, as this example does, or it can contain many lines of code. If the procedure returns a value I want in a data structure I will define the data structure here too, so all the programs and procedures use the same procedure prototype and "result" data structure.

Lines 3 and 4: The prototype for this procedure.

Line 5: The end of this "definition area".

I compiled the procedure as a module, rather than create a service program as I would usually do, as this is just an example.

I always put my service programs and modules into a binding directory, you will see below why I do this. Here are the commands I would use to create a binding directory, and then add this module to it.

CRTBNDDIR BNDDIR(MYLIB/MYBNDDIR)
ADDBNDDIRE BNDDIR(MYLIB/MYBNDDIR) OBJ((MODULE0001 *MODULE))

Next is my very short program that calls this procedure:

01  **free
02  ctl-opt main(Main)
            option(*srcstmt)
            bnddir('MYLIB/MYBNDDIR') ;

03  /define CheckDebug
04  /include mylib/devsrc,prototypes
05  /undefine CheckDebug

06  dcl-proc Main ;
07    dcl-s Returned char(1) ;

08    Returned = CheckDebug() ;
09    dsply ('Debug = ' + Returned) ;
10  end-proc ;

Line 1: It should come as no surprise that this program is written in totally free RPG.

Line 2: I have broken the control options over several lines to make it easier to see what each one is.

  • main(Main) program uses a main procedure
  • option(*srcstmt) I always use this option as it will make the compiler use the source statement number in the final object
  • bnddir('MYLIB/MYBNDDIR') I can include the name of the binding directory, I created above, therefore, no one has to worry about entering the right binding directory when creating the program

Lines 3 – 5: Like I did in the procedure's source member I am copying the code for the procedure prototype from the external source member.

Line 6: The start of the very short Main procedure. As no parameters are passed to the program I do not need a procedure interface.

Line 7: This is variable the value from the procedure will be moved to when the procedure is called.

Line 8: The procedure is called with no parameters, and the returned value is moved to the variable I defined on line 7.

Line 9: I add this line so that I can see the value returned from the procedure.

The program compiled without error. Now I can see what this API does. The trouble is how can I see the values in the QUSEC data structure without using debug? I added this between lines 16 and 17 of the procedure.

    dump(a) ;

The DUMP operation code will produce a program dump spool file, that I can then search for QUSEC.

I first ran the program without debug, and the program dump showed this:

QUSEC      DS
  QUSBAVL    16
  QUSBPRV    16
  QUSEI      'CPF9541'
  QUSERVED   ' '

And the DSPLY operation code shows a zero, to denote that debug is not active.

DSPLY  Debug = 0

Before calling the program again I started debug:

STRDBG PGM(*LIBL/PGM0001)

I did not add any breakpoints. I just called the program. The generated program dump shows that there was no error information returned by the API in the error data structure:

QUSEC      DS
  QUSBAVL    0
  QUSBPRV    16
  QUSEI      '       '
  QUSERVED   ' '

And the DSPLY operation code shows a value of one, to show that debug is active.

DSPLY  Debug = 1

 

I have thought long and hard about how I could use this API. Perhaps I would condition a DUMP statement to be to be executed if the program is running in debug. I am sure you'll be able to think of some ways you could use it too.

 

You can learn more about the QteRetrieveDebugAttribute API from the IBM website here.

 

This article was written for IBM i 7.3, and should work for some earlier releases too.

6 comments:

  1. If it were in CL you could as well use
    DSPDBG and check for CPD0039

    ReplyDelete
  2. I am running my Job in batch mode and try to call QteRetrieveDebugAttribute API then I did the following steps.
    -STRSRVJOB
    -STRDBG on the program where this API gets called then always getting CPF9541.

    However, If I execute the same program in Interactive mode by enabling debug session then its working fine.

    Please Let me know if you have an possible solution to use this API in Batch mode execution

    ReplyDelete
    Replies
    1. I created a new test program almost identical to the program given above, except the DSPLY is replaced with writing to a data area.

      I used the steps described here, and the program completed without error.

      CPF9541 = "Not in debug mode"

      Delete
  3. Hi Simon, Do you get any workaround for this situation. We are still struggling with this situation and not getting any breakthrough.
    Thanks in advance.
    IBMi User

    ReplyDelete
    Replies
    1. I am not sure a "work around" is needed. As i said I ran this API successfully in batch as part of a server job without error.

      You will have to check your own code, start with a very simple example program and work up from there.

      Delete
  4. Hi Simon,
    thanks for that tip - it works great when using STRDBG. But when debugging with RDi, the result is always CPF9541 - so do you know if any way to "detect" RDi debug?

    ReplyDelete

To prevent "comment spam" all comments are moderated.
Learn about this website's comments policy here.

Some people have reported that they cannot post a comment using certain computers and browsers. If this is you feel free to use the Contact Form to send me the comment and I will post it for you, please include the title of the post so I know which one to post the comment to.