Example: Using source debugger APIs

The ILE source debugger APIs allow an application developer to write a debugger for ILE programs.

You might ask why this would ever be done when an ILE debugger is provided with the IBM® i operating system. There are several reasons why an application developer might want to use these APIs to write a different ILE debugger:

  • A debugger running on a workstation can be built to debug ILE programs running on the system. This allows a debugger to take advantage of Windows and other easy-to-use interfaces available on the workstation. The workstation debugger can communicate with the code running on the system. The code running on the system can use the debugger APIs.
  • The writer of an ILE compiler might want to write a debugger to take advantage of the ILE languages. The IBM i debugger is a more general-purpose debugger that is made for all ILE languages.
  • A debugger can be written with functions not available on the IBM i ILE debugger.
Note: By using the code examples, you agree to the terms of the Code license and disclaimer information.

Source debugger APIs overview

The ILE source debugger APIs can be divided into several groups. These include APIs that:

  • Start and end the debug session
  • Add programs and modules to debug
  • Manipulate text views in a program
  • Add and remove breakpoints, steps, and so on

Besides APIs, there are two user exits that get called:

  • The Source Debug program gets called when the Start Debug (STRDBG), Display Module Source (DSPMODSRC), and End Debug (ENDDBG) CL commands are entered.
  • The Program Stop Handler gets called when an ILE program being debugged hits a breakpoint, step, and so on.

To demonstrate how these APIs are used, this topic presents an example debugger with complete code examples and an explanation of what the APIs do.

The ILE debugger that comes with IBM i uses the debugger APIs just as a user-written debugger would. There is nothing special about the IBM i debugger. Its functions could be done by an application developer using the debugger APIs and other IBM i APIs.

Scenario: A simple debugger

Consider a simple scenario in which the user wishes to debug an ILE program.

  1. From the command entry screen, the user enters the Start Debug (STRDBG) command, passing it the name of an ILE program to debug.
    STRDBG P1
  2. The ILE debugger screen is displayed, showing the source of a module in the ILE program being debugged. From this screen, the user adds a breakpoint and then exits.
  3. Back at the command entry screen, the user runs the ILE program that is being debugged.
    CALL P1
  4. The ILE program hits the breakpoint previously set. The ILE debugger screen is displayed, highlighting in the source where the program has stopped at the breakpoint.
  5. The user displays a variable in the program being debugged.
  6. The user exits the ILE debugger, allowing the ILE program to run to completion. The program ends.
  7. Back at the command entry screen, the user ends the debug session.
    ENDDBG

This is the simplest of debug scenarios, but it illustrates how IBM i, the debugger user exits, and the debugger APIs interact.

The following figure shows the various interactions.

Debug scenarios

A detailed explanation of the scenario follows:

  1. The Start Debug (STRDBG) CL command is used to start the debug session. By default, if an ILE program is specified on the command, the IBM i ILE debugger user exit is called. A different user exit (called the Source Debug program) can be specified on the Start Debug command by specifying a program name on the SRCDBGPGM parameter.

    When the Source Debug program is called, it is passed a reason field, which indicates why it was called. The *START reason is passed to it by the Start Debug command, indicating that the ILE debugger is to start itself and do any necessary initialization. When the *START reason is indicated, the names of any ILE programs on the Start Debug command are also passed to the Source Debug program.

  2. In this scenario, the system Source Debug program initializes itself. It calls the QteStartSourceDebug API, which tells the system that ILE debugging is to be done. The name of a program stop handler program is passed to this API. The stop handler is a program that the system calls when an ILE program hits a breakpoint, step, or other condition where the system stops the program for the debugger.

    The Source Debug program must indicate to the system that the ILE programs specified on the Start Debug command are to be debugged. To do this, the QteRetrieveModuleViews API is called, once for each ILE program specified on the Start Debug command. In this scenario, the API is called, passing it the name of program P1. The purpose of the API is to return information about the ILE program, including the modules and views of the program. A view is the source text that is displayed by the debugger for a particular module.

    Once information about the ILE program is obtained, one or more views of the program must be registered. Once a view is registered, the system can perform various functions on that view in behalf of the debugger application. For performance reasons, only the views the user is interested in displaying should be registered.

    The Source Debug program is now done performing the function for the *START reason. It exits, returning control to the Start Debug command.

  3. By default, if an ILE program is specified on the Start Debug command, the ILE debug screen is displayed. To indicate to the ILE debugger that a screen is to be put up, the Source Debug program is called by the command again, this time with a reason of *DISPLAY.

    Because this is the first time any views for P1 are to be displayed, the ILE debugger must retrieve the text to display. The first view of the first module of the program is selected as the default view to display.

    The Source Debug program calls the QteRetrieveViewText API to retrieve the text associated with the default view. Next, in case this program is already on the stack and stopped, the QteRetrieveStoppedPosition API is called to check. If the program were on the stack, the source would be positioned to the statement where the program was stopped, and that line would be highlighted. In this scenario, the program is not yet on the stack, so the first line of the source will appear at the top of the screen, and no line will be highlighted.

    The Source Debug program next calls User Interface Manager (UIM) APIs to display the source on the screen.

  4. At this point, the source screen is displayed showing the text of the first view in the first module of the first ILE program specified on the Start Debug command. From this screen, the user can enter debug commands or do other options provided by the debugger application.

    In this scenario, the user adds a breakpoint to a line in the ILE program P1 being debugged. When a command is entered, the UIM APIs call a program which is part of the ILE debugger to process the command.

    To process the breakpoint, the QteAddBreakpoint is called. It is passed a view number which indicates the view being displayed, and a line number in that view. A breakpoint is added to the program by the API.

  5. Back to the UIM screen, the user exits the ILE debugger. Once at the command entry screen, the user then runs the program P1 which has the breakpoint.
  6. When P1 hits the breakpoint, the system calls the program stop handler defined by the QteStartSourceDebug API. The Program Stop Handler calls UIM to put up the source for the module where the program has stopped because of the breakpoint. The line is highlighted to show the user exactly where the program has stopped.
  7. From the source debugger screen, the user displays a variable in program P1 which is stopped at the breakpoint. UIM calls the debugger to process the command. The debugger calls the QteSubmitDebugCommand API, which retrieves the value of the variable to be displayed. The debugger then displays this value on the screen.
  8. The user now exits from the source debugger screen. This allows P1, which was stopped at a breakpoint, to continue running. When P1 ends, the user is back at the command entry screen.
  9. The user ends the debug session by entering the End Debug (ENDDBG) CL command. The system calls the Source Debug program, passing it a reason of *STOP. The Source Debug program calls the QteEndSourceDebug API to indicate to the system that ILE debugging has ended. It then tears down its own environment (closes files, frees space, and so on) and then ends. The End Debug command completes, and the user is back to the command entry, the debug session having ended.

Example: Source debugger

This section discusses an example ILE debugger that demonstrates the use of some of the ILE debugger APIs. Each function in the C program is discussed along with the APIs that they call. Although the entire program listing is printed later (see Debugger code sample), each function or piece of code is printed with the section where it is discussed to make reading the code easier.

The example debugger does not use all ILE debugger APIs. Its function is limited. After the discussion of the code, the APIs and some functions not covered are discussed.

Compiling the debugger

The Create C Module (CRTCMOD) command compiles the source code of the debugger. It is compiled into module DEBUG.

The Create Program (CRTPGM) command creates program DEBUG from module DEBUG. It is necessary to bind to service program QTEDBGS so that the calls to the debugger APIs are resolved. It is also important to use activation group QTEDBGAG. This is an activation group that cannot be destroyed while the job is in debug mode. Thus, all static variables in program DEBUG remain intact throughout the debugging of the ILE program. Only when ENDDBG is entered can the activation group be destroyed, even if the Reclaim Resources (RCLRSC) CL command is entered.

Starting the debugger

The example debugger consists of a single program called DEBUG. The program is used as the Source Debug program as well as the Program Stop Handler. The program determines how many parameters it is being called with, and with this information it does the function of one or the other of the user exits.

The debugger can debug only one ILE program. This program is specified on the Start Debug CL command. The program cannot be removed from debug until ENDDBG is done. No new programs can be added.

To debug an ILE program P1 with this sample debugger, the following CL command could be entered:

STRDBG P1 SRCDBGPGM(DEBUG)

Note that DEBUG must be in the library list when STRDBG is done.

If the command is done, P1 is called twice, once as a Source Debug program given a reason of *START, and again as a Source Debug program given a reason of *DISPLAY.

Other variations of the Start Debug command can be given with different results. For example, the following CL command causes DEBUG to be called only once with a reason of *START:

STRDBG P1 SRCDBGPGM(DEBUG) DSPMODSRC(*NO)

This is because STRDBG has been told not to display the debug screen, so the *DISPLAY reason is not given until the user does the Display Module Source (DSPMODSRC) CL command.

The following example does not even call DEBUGGER:

STRDBG SRCDBGPGM(DEBUG)

This is because no ILE program is specified. If an ILE program receives an unmonitored message and the ILE debugger needs to be called, DEBUG is first called with *START as a Source Debug program. Also, if Display Module Source is entered, the *START and then the *DISPLAY reason is passed to DEBUG.

Using the debugger

When the debugger is started, it allows simple debugging commands to be entered. The C session manager is put up, which scrolls the users commands and the debugger output. To see a list of the allowable commands, enter HELP.

The "list views" command shows all of the views available in the program being debugged. The text description of the view is listed, with a sequential number. This number is used by the "switch" command to switch to that view.

The "list text" command prints out the text of the current view. Text has a line number next to it. The line number is used when setting breakpoints or other debug commands.

The switch command switches the current view. The current view is the view used when setting breakpoints, displaying variables, viewing text, and so on.

The "quit" command exits the debugger.

Other commands are interpreted by the QteSubmitDebugCommand API. This API will be discussed later. An example command that can be entered is "break n", where n is the line number in the current view. These commands are similar to the ones allowed in the ILE debugger shipped with IBM i.

Header files used in debugger


#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <qtedbgs.h>

Besides the normal C library header files, an API header file, qtedbgs.h is included. This file defines the functions exported from service program QTEDBGS. This service program contains the ILE debugger APIs.

Global variables

static _TE_VEWL0100_T *pgm_dbg_dta = NULL;
static long current_view = 0;       /* current view - defaults to 1st*/
static _TE_OBJLIB_T program_lib;    /* name and lib of pgm debugged  */

These are global variables that hold information about the program being debugged. These variables do not go away when program DEBUG exits, because they are stored in the activation group which is not destroyed until the debug session has completed.

The name and library of the program are stored, as is the current view being debugged. Also, a pointer to a structure returned by the QteRetrieveModuleViews is saved, as this information is needed when debugging the various views of the program.

PgmList_t


typedef struct {
    _TE_OBJLIB_T PgmLib;            /* Name and Library of program   */
    _TE_NAME_T PgmType;             /* program type, *PGM or *SRVPGM */
} PgmList_t;

This is the structure of the name, library, and type of the program being debugged.

main()

main (int argc, char *argv[]) {
  if (argc == 4)                    /* called as source debug program*/
    HandleSession(argv[1], (PgmList_t *)argv[2], *(int
*)argv[3]);
  else if (argc == 8)               /* called as program stop handler*/
    HandleStop((_TE_OBJLIB_T *)argv[1], argv[2],
argv[3], argv[4],
               (long *)argv[5], *(int *)argv[6], 
argv[7]);
}

Program DEBUG can be called in two ways. When it is called by the STRDBG, DSPMODSRC, and ENDDBG CL commands, it is called as the Source Debug program user exit. It is passed three parameters.

DEBUG can also be called when a program being debugged hits a breakpoint or step. In this case, it is passed seven parameters.

DEBUG therefore can determine why it was called by counting the number of parameters it was passed. Remember that argc includes the program name as the first argument passed.

If argc is 4 (three parameters passed to DEBUG), function HandleSession is called, and the three parameters passed to DEBUG are passed to it, typecasted as needed.

If argc is 8 (seven parameters passed to DEBUG), function HandleStop is called, and the seven parameters passed to DEBUG are passed to it, typecasted as needed.

If any other number of parameters are passed to DEBUG, it cannot have been called from the IBM i debug support, so DEBUG will just exit.

HandleSession()

void HandleSession(char reason[10],
                   PgmList_t ProgramList[],
                   int ProgramListCount) {

  if (memcmp(reason,"*START    ",10) == 0) /* reason is *START       */
    StartUpDebugger(ProgramList, ProgramListCount);
  else if ( memcmp(reason,"*STOP     ",10) == 0) /* reason is *STOP  */
    TearDownDebugger();
  else if ( memcmp(reason,"*DISPLAY  ",10) == 0) /* reason *DISPLAY  */
    ProcessCommands();
}

When DEBUG is called as a session handler, it is passed three parameters. The first parameter is a 10-character array containing a reason field. This contains the reason why the session handler is called.

When DEBUG is first called, it is passed a reason of *START, indicating that the debugger is to initialize for an ILE debug session. When this reason is given, the second parameter contains a list of ILE programs specified on the STRDBG command, and the third parameter contains the number of programs specified on parameter two. From 0 to 10 ILE programs can be specified.

When the user wishes to see the ILE debugger screen, either from STRDBG or DSPMODSRC, a reason of *DISPLAY is passed. When the user enters ENDDBG, the *STOP reason is passed, indicating that the ILE debug session is ending. The second and third parameters are not used when the reason is *DISPLAY or *STOP.

The code tests for a reason and calls the appropriate function. There is one function for each reason that can be passed.

TearDownDebugger()

void TearDownDebugger(void) {
  _TE_ERROR_CODE_T errorCode = {8}; /* errors will be ignored        */

  /* Call EndSourceDebug to get out of ILE debug mode                */
  QteEndSourceDebug(&errorCode);

  exit(0);                          /* destroy activation group      */
}

This function is called when the user enters ENDDBG. The debugger calls the QteEndSourceDebug API which ends ILE debugging. Since an 8 is passed as the number of bytes provided, the message ID and error data from an error are not returned to the caller. Thus, any errors from this API (there should not be any) are ignored.

The exit() function is called, which destroys the activation group. Thus, all global data defined in the program's variables are lost. This is ok, since the debug session is ending at this point.

StartUpDebugger()

void StartUpDebugger(PgmList_t ProgramList[],
                     int ProgramListCount) {

  _TE_ERROR_CODE_T errorCode = {0}; /* exceptions are generated      */
  _TE_OBJLIB_T StopHandler = {"DEBUG     ", "*LIBL     "};
  int i;

  if (ProgramListCount!=1) {        /* is only 1 pgm passed on STRDBG*/
    printf("Exactly ONE program must be specified on STRDBG.\n");
    TearDownDebugger();             /* end debugger                  */
  }

  /* Copy program name to global variables                           */
  memcpy(&program_lib, &ProgramList->PgmLib, 20);

  /* Call StartSourceDebug: giving the name and library of the       */
  /* stop handler.  This will start ILE debug mode                   */
  QteStartSourceDebug(&StopHandler, &errorCode);

  AddProgram();                     /* add program to debug          */
}

This function is passed the second and third parameters which were passed from the system when it called DEBUG with a reason of *START. These parameters are the list of programs to be added to debug and the number of programs in the list. This simple example debugger can only debug one program, so if any other number of programs were specified on STRDBG, the debugger just exits.

StartUpDebugger first stores the program/library element passed to it in a global variable available to all functions. This is the name and library of the program being debugged. It then calls the QteStartSourceDebug API to tell the system that an ILE debug session is to begin. The name and library of program DEBUG are passed to this API as the Program Stop Handler. Thus, whenever the program being debugged is stopped by the debugger, program DEBUG will be called.

Finally, the function calls AddProgram to add the single program to debug.

AddProgram()


void AddProgram(void) {

  _TE_ERROR_CODE_T errorCode = {0};    /* Signal exceptions on error */
  _TE_NAME_T        Library;           /* Lib returned               */
  _TE_TIMESTAMP_T   TimeStamp;         /* TimeStamp returned         */
  int viewIndex;
  long int          iViewID;
  long int          iViewLines;
  long rtvModViewDataLength = 8;       /* size of receiver buffer    */
  char tempBuffer[8];        /* enough room for header only*/
  int i, tempModuleCount;

  /* Call QteRetrieveModuleViews to determine the number of bytes    */
  /* the receiver variable needs to be to hold all of the views for  */
  /* the program.                                                    */
  pgm_dbg_dta = (_TE_VEWL0100_T *)tempBuffer;
  QteRetrieveModuleViews((char *)pgm_dbg_dta, &rtvModViewDataLength,
                         "VEWL0100", &program_lib,
                         "*PGM      ", "*ALL      ", Library,
                         &errorCode);

  /* Get a buffer large enough to hold all view information          */
  rtvModViewDataLength = pgm_dbg_dta->BytesAvailable;
  pgm_dbg_dta = (_TE_VEWL0100_T *)malloc(rtvModViewDataLength);

  /* Call QteRetrieveModuleViews again, passing a big enough buffer. */
  QteRetrieveModuleViews((char *)pgm_dbg_dta, &rtvModViewDataLength,
                         "VEWL0100", &program_lib,
                         "*PGM      ", "*ALL      ", Library,
                         &errorCode);

  /* If number of elements is zero, program is not debuggable.       */
  if (pgm_dbg_dta->NumberElements == 0) {
    printf("Program %.10s in Library %.10s cannot be debugged.",
           program_lib.obj, program_lib.lib);
    TearDownDebugger();
  }

  /* Put the library returned by Retrieve Module Views in PgmLib     */
  memcpy(program_lib.lib, Library, sizeof(_TE_NAME_T));

  /* Register all views in the program                               */
  for (i=0; i < pgm_dbg_dta->NumberElements; i++) {
    QteRegisterDebugView(&iViewID, &iViewLines, Library, TimeStamp,
                         &program_lib, "*PGM      ",
                         pgm_dbg_dta->Element[i].ModuleName,
                         &pgm_dbg_dta->Element[i].ViewNumber,
                         &errorCode);

    /* overwrite unneeded ViewNumber with obtained view id           */
    pgm_dbg_dta->Element[i].ViewNumber = iViewID;
  }
}

The heart of this function is the two calls to the QteRetrieveModuleViews API and the call to QteRegisterDebugView API.

The QteRetrieveModuleViews API returns information about an ILE program. It returns this information in a structure of type _TE_VEWL0100_T. This is a fairly complex structure that has the following fields:


typedef _Packed struct {            /* format VEWL0100               */
  long int BytesReturned;           /* number of bytes returned      */
  long int BytesAvailable;          /* number of bytes available     */
  long int NumberElements;          /* number of elements returned   */
  _Packed struct {                  /* one element                   */
    _TE_NAME_T ModuleName;          /* name of module in program     */
    _TE_NAME_T ViewType;            /* type of view:                 */
    _TE_COMPILER_ID_T CompilerID;   /* compiler ID                   */
    _TE_NAME_T MainIndicator;       /* main indicator                */
    _TE_TIMESTAMP_T TimeStamp;      /* time view was created         */
    _TE_TEXTDESC_T ViewDescription; /* view description              */
    char Reserved[3];
    long int ViewNumber;            /* view number within module     */
    long int NumViews;              /* number of views in this module*/
  } Element[1];                     /* one element         */
} _TE_VEWL0100_T;

This structure has a header portion which holds the number of bytes returned by the API (BytesReturned), the number of bytes that can be returned by the API, used when there is not enough room for the API to return all of its data (BytesAvailable), and the number of elements (views) returned by the API (NumberElements).

Since there is no way to know in advance how many views a program has, the QteRetrieveModuleViews API should be called once with only enough storage to return the number of bytes that the API needs to return all of its information. Thus, the first call to the API provides only 8 bytes of storage for the API to return its data. This allows the API to fill in the BytesAvailable field.

QteRetrieveModuleViews is passed a buffer to hold the receiver variable and the length of that buffer (in this case, 8 bytes). It is also passed a format name which identifies the structure of the receiver variable. The only allowable format name at this time is VEWL0100. A structure containing the program name and library name of the ILE program is passed. Also, the program type is passed. In this example debugger, only *PGM objects can be debugged, but it is possible to debug *SRVPGM objects using the ILE debugger APIs.

The name of the module is provided, in which case information about that module is returned. *ALL indicates that information about all modules in the program is to be returned. A return library variable is passed. This is so that when *LIBL is passed as a library name, the real library name can be obtained, making subsequent API calls faster because the library list won't have to be searched again.

Finally an error code structure is passed to the API. This structure is initialized with a zero, indicating that the API is not to fill in any error code data. Instead, the API issues an exception if an error occurs. No errors are expected, so this should not matter.

Before QteRetrieveModuleViews is called again, a buffer large enough to hold all of the information is created. The API is called again with the same parameters, but this time the entire information will be stored by the API in the allocated buffer.

If the API does not return any elements, this means that none of the modules has debug data. In this case, the program cannot be debugged, so the debug session is ended.

Now that a list of views has been retrieved, it is time to register all of the views to the system, making it possible to do debug operations against them. In a real debugger, only the views requested to be seen by the user would be registered to save processing time, but in this example, all views will be registered at once.

Not all of the fields in the VEWL0100 structure are needed by this debugger. However, they are described here. The API returns one element for each view in the program. Each module in the program might have several views. All views for a particular module are contiguous in the list.

View Description
ModuleName This is the name of the module in the program which this particular view is for.
ViewType This indicates the type of view. A *TEXT view contains the text retrieved from source files on the system. The text contains sequence information from these files that the debugger might not want to display. A *LISTING view contains the text that is stored with the program object itself. A *STATEMENT view contains the information about HLL statements in the module, and this information is not generally displayed to the user but is used by the debugger. In the case of this debugger, all views are displayed exactly as the text for the views is retrieved.
CompilerID This indicates the language that the particular module is written in. This is not used by the example debugger.
MainIndicator Only one module in a program is the module with the program entry procedure (main() in the case of ILE C programs). If a particular view in the list comes from this module, then this field indicates that the module contains this procedure. This field is not used by the example debugger.
TimeStamp This indicates when the view was created. This is useful in allowing the debugger to detect if a program has been recompiled and the debugger has down-level view information. This field is not used by the example debugger.
ViewDescription This is text given to the view by the compiler creating the view. It is a description of the view which can be displayed by the debugger.
ViewNumber This is a sequence number of the view in a particular module. When registering a view, the program name, module name, and view number must be provided.
NumViews This is how many views are in the module. All elements for views in a given module have the same value for this field. This field is not used by the example debugger.

A loop through all the views returned by QteRetrieveModuleViews is done, registering the view using the QteRegisterDebugView API. The program name, program type, module name, and view number of the module are passed as inputs to the API. The API returns the library of the program (in case *LIBL) is passed in as the program library), the timestamp of the view (in case the program has been recompiled between the time the view information was retrieved and the time the view was registered), the number of lines of text in the view, and a view ID. The view ID is a handle, and it is used in identifying the registered view to various APIs. For example, when retrieving text for a particular view, the view must be registered, and the view ID returned when registering the view is passed to the QteRetrieveViewText API.

The structure that held the views retrieved by QteRetrieveModuleViews is also used by the debugger. The view number is no longer needed, since it is just a sequence number passed to QteRegisterDebugView. Thus, this number is overwritten and will hold the view ID, which is needed by other debugger APIs.

ProcessCommands()

void ProcessCommands(void) {
  char InputBuffer[80];
  char *token;
  int i;
  int step=0;                       /* do an exit for step when 1    */

  if (pgm_dbg_dta == NULL) {        /* if no debug data              */
    printf("Debug session has ended.\n");
    exit(0);                        /* end the debugger              */
  }

  while(!step) {                    /* read until step or quit cmd   */
    ReadLine(InputBuffer,sizeof(InputBuffer));
    token = strtok(InputBuffer," ");

    if (token==NULL) continue;      /* ignore blank lines            */
    else if (strcmp(token,"quit") == 0) /* the quit command?         */
      return;                       /* exit debugger                 */
    else if (strcmp(token,"list") == 0) /* the list command?         */
      ProcessListCommand();         /* process command               */
    else if (strcmp(token,"switch") == 0) { /* switch command?       */
      token = strtok(NULL," ");     /* get view number token         */
      if (token == NULL)
        printf("'switch' must be followed by a view number.\n");
      else
        current_view = atoi(token);   /* switch current view         */
    }
    else if (strcmp(token,"help") == 0) {
      printf("The following are the allowed debugger commands:\n");
      printf("  list views - lists all views in the program\n");
      printf("  list text - lists the text of the current view\n");
      printf("  switch n - changes current view to view n\n");
      printf("  help - displays this help text\n");
      printf("  quit - ends the debug session\n");
      printf("Other commands are interpreted by the debug support.\n");
    }
    else {                          /* pass command to API           */
      /* Undo modifications that strtok did                          */
      InputBuffer[strlen(InputBuffer)] = ' ';

      step = ProcessDbgCommand(InputBuffer);
    }
  }
}

This function reads an input line from the user and processes it. If it is a command recognized by the debugger, it process it. If not, it calls ProcessDebugCommand which lets QteSubmitDebugCommand process the command.

The first test is to make sure that the pointer to the debug data is not null. This is here for safety reasons. If program DEBUG is compiled with the wrong activation group name or no name at all, its global variables can be destroyed when the program exits, causing problems when the program is called again. This test prevents debug commands from being entered if the activation group has been destroyed, wiping out the global view data.

The function loops until the quit command is entered or until a step is done. It calls the appropriate function based on the command entered, or displays an error message if a syntax error is detected. If the command is unknown, it is processed by ProcessDbgCommand.

The switch command is processed directly by the function. It changes the current view to a number provided. There is no error checking in this sample debugger.

ReadLine()

void ReadLine(char *Buffer, int length) {
  int i;                            /* loop counter                  */

  printf("Enter a debugger command or 'help'.\n");
  fgets(Buffer,length,stdin);       /* read line of text             */

  /* Blank out line from \n to the end of the string.                */
  for (i=0; i<length; i++) {        /* loop, searching for newline   */
    if (Buffer[i] == '\n') {        /* if newline character found
*/
      break;                        /* end loop searching for newline*/
    }
  }

  memset(Buffer+i,' ',length-i);     /* blank remainder of line      */
}

This function reads a line of text from the user and fills the input buffer with trailing blanks.

ProcessListCommand()

void ProcessListCommand(void) {
  char *token;                      /* pointer to next token of input*/

  token = strtok(NULL," ");         /* get next token in input buffer*/

  if (token==NULL)                  /* list not followed by anything */
    printf("'list' must be followed by 'views' or 'text'.\n");
  else if (strcmp(token,"views") == 0)/* if list views               */
    PrintViews();
  else if (strcmp(token,"text") == 0) /* if list text                */
    PrintText();
  else                              /* list <something-else>         */
    printf("'list' must be followed by 'views' or 'text'.\n");
}

This routine process the list command. There are two versions of the list command, list views and list text. The appropriate function is called depending on the type of list command entered, or a syntax error message is issued.

PrintViews

void PrintViews(void) {
  int k;

  /* loop through views printing view#, module, and view desc. text  */
  for (k=0; k< pgm_dbg_dta->NumberElements; k++) {
    printf("%d) %.10s:%.50s",
           k,
           pgm_dbg_dta->Element[k].ModuleName,
           pgm_dbg_dta->Element[k].ViewDescription);
    if (current_view == k)         /* indicate if view is current   */
      printf("<---Current\n");
    else
      printf("\n");
  }
}

This routine lists all of the views available in the program being debugged. The information about the views is stored in the buffer that was passed to QteRetrieveModuleViews.

The module name and view descriptive text is printed for each view. If the current view being printed is also the current view, this is noted by printing this fact next to the view information.

A view number is printed next to each view. This is not the view ID returned by the QteRegisterDebugView. It is a number allowing the user to change the current view to one of the views in the list.

PrintText()


void PrintText(void) {

  long LineLength = 92;             /* length of lines of text       */
  long NumberOfLines = 0;           /* lines to retrieve - 0 = all   */
  long StartLine=1;                 /* retrieve from line 1 (first)  */
  long bufferLength = 100000;       /* size of retrieved text buffer */
  long viewID;                      /* view ID of text to retrieve   */
  _TE_TEXT_BUFFER_T *buffer;        /* text retrieved by API         */
  _TE_ERROR_CODE_T errorCode = {0}; /* Exceptions will be signaled   */
  int i;                            /* points to start of each line  */
  int line_number;                  /* line number counter for loop  */

  /* Get View ID of current view                                     */
  viewID = pgm_dbg_dta->Element[current_view].ViewNumber;

  buffer = malloc(bufferLength);    /* malloc space for big text buf */

  /* Call Retrieve_View_Text for the current view.                   */
  QteRetrieveViewText((char *)buffer, &bufferLength, &viewID,
                      &StartLine, &NumberOfLines, &LineLength,
                      &errorCode);

  /* Print out the text                                              */
  for (i=0,line_number=1;
       line_number <= buffer->NumLines;
       line_number++,i+=LineLength) {
     printf("%3d) %.70s\n", line_number, buffer->Text+i);
  }

  free(buffer);                     /* free memory for buffer        */
}

This function retrieves the text associated with the current view and prints it. This text is the source of the program and is the heart of a source debugger screen.

The text of the current view is retrieved, so the view ID of that view is determined. It is this view that is passed to QteRetrieveViewText.

In the sample debugger, a large buffer is allocated, and as much text as will fit in this buffer is retrieved. The QteRetrieveViewText API returns the text and the number of lines that fit in the buffer.

Once the text is retrieved, it is printed out along with the line number. The line number is needed when setting breakpoints based on the view.

ProcessDbgCommand()

int ProcessDbgCommand(char InputBuffer[80]) {
  _TE_ERROR_CODE_T errorCode = {64}; /* fill in bytes provided       */
  char OutputBuffer[4096];
  struct _TE_RESULT_BUFFER_T *Results;
  long InputBufferLength = 80;
  long OutputBufferLength = sizeof(OutputBuffer);
  long view_ID;
  _TE_COMPILER_ID_T *CompilerID;
  int i;
  int return_value = 0;

  view_ID = pgm_dbg_dta->Element[current_view].ViewNumber;
  CompilerID = &pgm_dbg_dta->Element[current_view].CompilerID;

  /* Give command to QteSubmitDebugCommand                           */
  QteSubmitDebugCommand(OutputBuffer, &OutputBufferLength,
                        &view_ID, InputBuffer, &InputBufferLength,
                        *CompilerID, &errorCode);

  if (errorCode.BytesAvailable != 0) {
    printf("Error = %.7s\n",errorCode.ExceptionID);
    return return_value;
  }

  /* Process results from QteSubmitDebugCommand                      */
  Results = (_TE_RESULT_BUFFER_T *) OutputBuffer;

  /* Loop through Results array                                      */
  for (i=0; i<Results->Header.EntryCount; i++) {
    switch (Results->Data[i].ResultKind)
    {
      case _TE_kStepR            :
        printf("Step set\n");
        return_value=1;             /* indicate step is to be done   */
        break;
      case _TE_kBreakR           :
        printf("Breakpoint set");
        break;
      case _TE_kBreakPositionR   :
        printf(" at line %d\n",
               Results->Data[i].V.BreakPosition.Line);
        break;
      case _TE_kExpressionTextR  :
        printf("%s",
               ((char *)Results) + Results->Data[i].V.
               ExpressionText.oExpressionText);
        break;
      case _TE_kExpressionValueR :
        printf(" = %s\n",
               ((char *)Results) + Results->Data[i].V.
               ExpressionValue.oExpressionValue);
        break;
      case _TE_kQualifyR         :
        printf("Qual set\n");
        break;
      case _TE_kClearBreakpointR :
        printf("Breakpoint cleared\n");
        break;
      case _TE_kClearPgmR :
        printf("All breakpoints cleared\n");
        break;
      default:                      /* ignore all other record types */
        break;
    }                               /* switch                        */
  }                                 /* loop through results array    */
  return return_value;
}

This function is called to process all commands not known by the debugger. It calls the QteSubmitDebugCommand API which is passed a view ID, compiler ID, and a command. The API needs the compiler ID because each programming language used in compiling a particular module has different debug commands or command syntax, and the API needs to know which language was used when compiling the module.

The API returns back a series of result records which indicate what was done by the API. Most of this function reads the results of the records returned and prints an appropriate response message.

Some results records indicate that a particular function has been performed. These include:

Result record Description
_TE_kStepR The step command was successfully done.
_TE_kBreakR The break command was successfully done.
_TE_kQualifyR The qual command was successfully done.
_TE_kClearBreakpointR The clear breakpoint command was successfully done.
_TE_kClearPgmR The clear pgm command was successfully done.

Other results records contain numeric data useful by the debugger.

Result record Description
_TE_kBreakPositionR Contains the line number where a breakpoint was set. It is possible that a breakpoint set on two different lines will correspond to the same HLL statement. In this case, only one breakpoint is really set. To determine if this is the case, it is necessary to map the position in the view where the breakpoint is set to a position in the statement view.

Still other results records contain string data. In this case, the record contains an offset into the string space returned by the API as well as a string length.

Result record Description
_TE_kExpressionTextR This points to the expression entered in the eval command.
_TE_kExpressionValueR This points to the value of the evaluated expression.

There are other kinds of results records than processed by the sample debugger. The QteSubmitDebugCommand API discusses in detail each result record and the data it contains.

The API description also discusses the syntax of the debug command that must be passed to it. The commands and their syntax will not be discussed in depth here, but a few example commands will be shown:

  • break 5 when x == 3

    This is a conditional breakpoint. The debugger will stop the program indicated by the view ID passed to the API when it reaches line 5 of the view and when the expression "x == 3" is true. The "when" part of the break statement is optional, in which case an unconditional breakpoint is set.

  • step 1 into

    The step command instructs the debug support to stop the a program when it has executed one or more statements. In this example, the program is stopped after 1 statement has been executed. The "into" means that statements in procedures are counted when stepping. "over" means that statements in called procedures are skipped over and not counted. The default step type is "into", and the default step count is 1.

  • qual 13

    The qual command is necessary when there are blocks of code with the same variable name. In this case, the user indicates where the variable is searched for in the program. Normally, this command is not used.

  • clear 8

    A conditional or unconditional breakpoint is removed from line 8 of the view indicated by the view ID parameter.

HandleStop()


void HandleStop(_TE_OBJLIB_T *ProgramLib,
                _TE_NAME_T ProgramType,
                _TE_NAME_T Module,
                char reason[10],
                long Statements[],
                int StatementsCount,
                char *message) {
  int i;
  _TE_MAPP0100_T Map_Return_Structure;
  long Column = 1;
  long MapLength = sizeof(Map_Return_Structure);
  _TE_ERROR_CODE_T errorCode = {64};
  long stmt_view;

  /* If current view is for a different module than the one that is  */
  /* stopped, change current view to first view in the stopped module*/
  if (memcmp(Module,
             pgm_dbg_dta->Element[current_view].ModuleName,
             sizeof(_TE_NAME_T)) != 0) { /* a different module?      */
    for (i=0; i<pgm_dbg_dta->NumberElements; i++) {
      if (memcmp(Module,
                 pgm_dbg_dta->Element[i].ModuleName,
                 sizeof(_TE_NAME_T)) == 0) { /* found module         */
        current_view = i;           /* change current view to module */
        printf("Current view changed to %d.\n",current_view);
        break;                      /* exit search loop              */
      }                             /* module found                  */
    }                               /* loop through views            */
  }                                 /* current view to be changed    */

  /* Get number of statement view for module stopped                 */
  for (i=0; i<pgm_dbg_dta->NumberElements; i++) {
    if ((memcmp(Module,
               pgm_dbg_dta->Element[i].ModuleName,
               sizeof(_TE_NAME_T)) == 0) &&
        (memcmp("*STATEMENT",
                 pgm_dbg_dta->Element[i].ViewType,
                 sizeof(_TE_NAME_T)) == 0))
      stmt_view = i;
  }


  /* Call QteMapViewPosition to map the stopped location (which      */
  /* is in terms of the *STATEMENT view) to the current view of      */
  /* the module                                                      */
  QteMapViewPosition((char *)&Map_Return_Structure, &MapLength,
                     &pgm_dbg_dta-> Element[stmt_view].ViewNumber,
                     &Statements[0], &Column,
                     &pgm_dbg_dta->Element[current_view].ViewNumber,
                     &errorCode);

  /* Tell the user about the program that stopped.                   */
  for (i=0;i<4;i++) {               /* See why program stopped       */
    if (reason[i] == '1') {
      switch(i) {
        case 0: printf("Unmonitored exception");
          break;
        case 1: printf("Breakpoint");
          break;
        case 2: printf("Step completed");
          break;
        case 3: printf("Breakpoint condition error");
          break;
      }
    }
  }
  printf(" in module %.10s at line %d.\n",
         Module,
         Map_Return_Structure.MapElem[0].LineNumber);

  ProcessCommands();                /* put user into debugger        */
}

This function is called when program DEBUG is called as a Program Stop Handler. It is passed the name, library, and type of the program stopped, the line number in the statement view where it has stopped, a count of line numbers stopped in, if the system cannot determine exactly where the program has stopped (this is the case for optimized code), and an array of character flags indicating why the program was stopped.

The first thing the function does is determine if the current view is set to the module where the program stopped. If not, then it needs to be reset to the first view in the module where the program has stopped.

Next, the statement view ID for the module stopped needs to be determined. This is necessary because the stopped position is given in terms of the statement view, and this position needs to be converted to a position in the current view.

The QteMapViewPosition API maps a position in the statement view to a statement in another view in that module. This allows the debugger to determine the source line of the current view where the program has stopped, even though the program is only told the line number in the statement view.

Finally, the character flags are checked to see why the program was stopped. Note that the program can be stopped for more than one reason, so every flag is checked, and if it is on, a message for that flag is printed.

Finally, the ProcessCommands function is called, allowing the user to enter debug commands.

Other APIs

This section discusses other APIs not covered in this example debugger. Some or all of these APIs could be used in a real ILE source-level debugger. All of them are used in the debugger shipped with IBM i.

QteRetrieveDebugAttributes

This API allows a debugger to retrieve information about the debug session. This includes the value of the Update Production Files, set on the Start Debug command, as well as an indication of whether the job where the debugger is running is servicing and debugging another job.

QteSetDebugAttributes

The only attribute that can be set is the value of the Update Production Files. This can also be accomplished using the Change Debug (CHGDBG) CL command.

QteRemoveDebugView

Views that are registered can be removed from debug. This is desirable if a program is to be removed from debug so that it can be recompiled and added again. It is not necessary to remove views from debug when ending the debug session, as QteEndSourceDebug will do this automatically.

QteRetrieveStoppedPosition

This indicates if a program is currently stopped and on the stack, and whether this stopped position is anywhere in a given view. This is useful whenever a source debugger is about to put up a source screen. If the program is stopped somewhere within the source to be displayed, this can be indicated to the user.

This is necessary because a program can be stopped by other means than the debugger. For example, an ILE program could have put up a command entry screen, and the debugger could be displayed from there. In this case, it is nice to indicate to the user that the program being debugged is stopped.

QteAddBreakpoint

This and the following APIs are not really needed, as their function can be done with the QteSubmitDebugCommand. However, this API is much faster, since a debug language command does not need to be parsed and interpreted. In cases where the debugger knows the information without needing to specify a debug command to the API, these "shortcut" APIs should be used.

This API performs the same function as the break n debug language command.

QteRemoveBreakpoint

This API performs the same function as the clear n debug language command.

QteRemoveAllBreakpoints

This API performs the same function as the clear pgm debug language command.

QteStep

This API performs the same function as the step n into and step n over debug language commands.

Debugger code sample

Here is the entire program listing for the ILE C program that contains the example debugger:


/*******************************************************************/
/*******************************************************************/
/*                                                                 */
/* FUNCTION:  The entire program listing for the program           */
/*            containing the example debugger discussed in the     */
/*            preceding sections.                                  */
/*                                                                 */
/* LANGUAGE:  ILE C                                                */
/*                                                                 */
/* APIs USED: QteRetrieveViewText, QteSubmitDebugCommand,          */
/*            QteEndSourceDebug, QteRetrieveModuleViews,           */
/*            QteRegisterDebugView, QteStartSourceDebug,           */
/*            QteMapViewPosition                                   */
/*                                                                 */
/*******************************************************************/
/*******************************************************************/

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <qtedbgs.h>

/* Global variables holding information about a program in debug mode*/
static _TE_VEWL0100_T *pgm_dbg_dta = NULL;
static long current_view = 0;       /* current view - defaults to 1st*/
static _TE_OBJLIB_T program_lib;    /* name and lib of pgm debugged  */

/* ReadLine: Reads a line of input and stores it in a string.        */
void ReadLine(char *Buffer, int length) {
  int i;                            /* loop counter                  */

  printf("Enter a debugger command or 'help'.\n");
  fgets(Buffer,length,stdin);       /* read line of text             */

  /* Blank out line from \n to the end of the string.                */
  for (i=0; i<length; i++) {        /* loop, searching for newline   */
    if (Buffer[i] == '\n') {        /* if newline character found */
      break;                        /* end loop searching for newline*/
    }
  }

  memset(Buffer+i,' ',length-i);     /* blank remainder of line      */
}

/* PrintText: This function will print the text for the current view */
void PrintText(void) {

  long LineLength = 92;             /* length of lines of text       */
  long NumberOfLines = 0;           /* lines to retrieve - 0 = all   */
  long StartLine=1;                 /* retrieve from line 1 (first)  */
  long bufferLength = 100000;       /* size of retrieved text buffer */
  long viewID;                      /* view ID of text to retrieve   */
  _TE_TEXT_BUFFER_T *buffer;        /* text retrieved by API         */
  _TE_ERROR_CODE_T errorCode = {0}; /* Exceptions will be signaled   */
  int i;                            /* points to start of each line  */
  int line_number;                  /* line number counter for loop  */

  /* Get View ID of current view                                     */
  viewID = pgm_dbg_dta->Element[current_view].ViewNumber;

  buffer = malloc(bufferLength);    /* malloc space for big text buf */

  /* Call Retrieve_View_Text for the current view.                   */
  QteRetrieveViewText((char *)buffer, &bufferLength, &viewID,
                      &StartLine, &NumberOfLines, &LineLength,
                      &errorCode);

  /* Print out the text                                              */
  for (i=0,line_number=1;
       line_number <= buffer->NumLines;
       line_number++,i+=LineLength) {
     printf("%3d) %.70s\n", line_number, buffer->Text+i);
  }

  free(buffer);                     /* free memory for buffer        */
}

/* PrintViews: Prints all the views of the program being debugged.   */
void PrintViews(void) {
  int k;

  /* loop through views printing view#, module, and view desc. text  */
  for (k=0; k< pgm_dbg_dta->NumberElements; k++) {
    printf("%d) %.10s:%.50s",
           k,
           pgm_dbg_dta->Element[k].ModuleName,
           pgm_dbg_dta->Element[k].ViewDescription);
    if (current_view == k)         /* indicate if view is current   */
      printf("<---Current\n");
    else
      printf("\n");
  }
}

/* ProcessListCommand:  Process list command to list views or text   */
void ProcessListCommand(void) {
  char *token;                      /* pointer to next token of input*/

  token = strtok(NULL," ");         /* get next token in input buffer*/

  if (token==NULL)                  /* list not followed by anything */
    printf("'list' must be followed by 'views' or 'text'.\n");
  else if (strcmp(token,"views") == 0)/* if list views               */
    PrintViews();
  else if (strcmp(token,"text") == 0) /* if list text                */
    PrintText();
  else                              /* list <something-else>         */
    printf("'list' must be followed by 'views' or 'text'.\n");
}

/* ProcessDbgCommand:  This function will process commands sent to   */
/*                     the QteSubmitDebugCommand API.                */
int ProcessDbgCommand(char InputBuffer[80]) {
  _TE_ERROR_CODE_T errorCode = {64}; /* fill in bytes provided       */
  char OutputBuffer[4096];
  struct _TE_RESULT_BUFFER_T *Results;
  long InputBufferLength = 80;
  long OutputBufferLength = sizeof(OutputBuffer);
  long view_ID;
  _TE_COMPILER_ID_T *CompilerID;
  int i;
  int return_value = 0;

  view_ID = pgm_dbg_dta->Element[current_view].ViewNumber;
  CompilerID = &pgm_dbg_dta->Element[current_view].CompilerID;

  /* Give command to QteSubmitDebugCommand                           */
  QteSubmitDebugCommand(OutputBuffer, &OutputBufferLength,
                        &view_ID, InputBuffer, &InputBufferLength,
                        *CompilerID, &errorCode);

  if (errorCode.BytesAvailable != 0) {
    printf("Error = %.7s\n",errorCode.ExceptionID);
    return return_value;
  }

  /* Process results from QteSubmitDebugCommand                      */
  Results = (_TE_RESULT_BUFFER_T *) OutputBuffer;

  /* Loop through Results array                                      */
  for (i=0; i<Results->Header.EntryCount; i++) {
    switch (Results->Data[i].ResultKind)
    {
      case _TE_kStepR            :
        printf("Step set\n");
        return_value=1;             /* indicate step is to be done   */
        break;
      case _TE_kBreakR           :
        printf("Breakpoint set");
        break;
      case _TE_kBreakPositionR   :
        printf(" at line %d\n",
               Results->Data[i].V.BreakPosition.Line);
        break;
      case _TE_kExpressionTextR  :
        printf("%s",
               ((char *)Results) + Results->Data[i].V.
               ExpressionText.oExpressionText);
        break;
      case _TE_kExpressionValueR :
        printf(" = %s\n",
               ((char *)Results) + Results->Data[i].V.
               ExpressionValue.oExpressionValue);
        break;
      case _TE_kQualifyR         :
        printf("Qual set\n");
        break;
      case _TE_kClearBreakpointR :
        printf("Breakpoint cleared\n");
        break;
      case _TE_kClearPgmR :
        printf("All breakpoints cleared\n");
        break;
      default:                      /* ignore all other record types */
        break;
    }                               /* switch                        */
  }                                 /* loop through results array    */
  return return_value;
}

/* ProcessCommands:  Read input from user and process commands.      */
void ProcessCommands(void) {
  char InputBuffer[80];
  char *token;
  int i;
  int step=0;                       /* do an exit for step when 1    */

  if (pgm_dbg_dta == NULL) {        /* if no debug data              */
    printf("Debug session has ended.\n");
    exit(0);                        /* end the debugger              */
  }

  while(!step) {                    /* read until step or quit cmd   */
    ReadLine(InputBuffer,sizeof(InputBuffer));
    token = strtok(InputBuffer," ");

    if (token==NULL) continue;      /* ignore blank lines            */
    else if (strcmp(token,"quit") == 0) /* the quit command?         */
      return;                       /* exit debugger                 */
    else if (strcmp(token,"list") == 0) /* the list command?         */
      ProcessListCommand();         /* process command               */
    else if (strcmp(token,"switch") == 0) { /* switch command?       */
      token = strtok(NULL," ");     /* get view number token         */
      if (token == NULL)
        printf("'switch' must be followed by a view number.\n");
      else
        current_view = atoi(token);   /* switch current view         */
    }
    else if (strcmp(token,"help") == 0) {
      printf("The following are the allowed debugger commands:\n");
      printf("  list views - lists all views in the program\n");
      printf("  list text - lists the text of the current view\n");
      printf("  switch n - changes current view to view n\n");
      printf("  help - displays this help text\n");
      printf("  quit - ends the debug session\n");
      printf("Other commands are interpreted by the debug support.\n");
    }
    else {                          /* pass command to API           */
      /* Undo modifications that strtok did                          */
      InputBuffer[strlen(InputBuffer)] = ' ';

      step = ProcessDbgCommand(InputBuffer);
    }
  }
}

/* TearDownDebugger: End the debugger.                               */
void TearDownDebugger(void) {
  _TE_ERROR_CODE_T errorCode = {8}; /* errors will be ignored        */

  /* Call EndSourceDebug to get out of ILE debug mode                */
  QteEndSourceDebug(&errorCode);

  exit(0);                          /* destroy activation group      */
}

/* AddProgram: Add a program to debug mode.                          */
void AddProgram(void) {

  _TE_ERROR_CODE_T errorCode = {0};    /* Signal exceptions on error */
  _TE_NAME_T        Library;           /* Lib returned               */
  _TE_TIMESTAMP_T   TimeStamp;         /* TimeStamp returned         */
  int viewIndex;
  long int          iViewID;
  long int          iViewLines;
  long rtvModViewDataLength = 8;       /* size of receiver buffer    */
  char tempBuffer[8];                  /* Temp storage               */
  int i, tempModuleCount;

  /* Call QteRetrieveModuleViews to determine the number of bytes    */
  /* the receiver variable needs to be to hold all of the views for  */
  /* the program.                                                    */
  pgm_dbg_dta = (_TE_VEWL0100_T *)tempBuffer;
  QteRetrieveModuleViews((char *)pgm_dbg_dta, &rtvModViewDataLength,
                         "VEWL0100", &program_lib,
                         "*PGM      ", "*ALL      ", Library,
                         &errorCode);

  /* Get a buffer large enough to hold all view information          */
  rtvModViewDataLength = pgm_dbg_dta->BytesAvailable;
  pgm_dbg_dta = (_TE_VEWL0100_T *)malloc(rtvModViewDataLength);

  /* Call QteRetrieveModuleViews again, passing a big enough buffer. */
  QteRetrieveModuleViews((char *)pgm_dbg_dta, &rtvModViewDataLength,
                         "VEWL0100", &program_lib,
                         "*PGM      ", "*ALL      ", Library,
                         &errorCode);

  /* If number of elements is zero, program is not debuggable.       */
  if (pgm_dbg_dta->NumberElements == 0) {
    printf("Program %.10s in Library %.10s cannot be debugged.",
           program_lib.obj, program_lib.lib);
    TearDownDebugger();
  }

  /* Put the library returned by Retrieve Module Views in PgmLib     */
  memcpy(program_lib.lib, Library, sizeof(_TE_NAME_T));

  /* Register all views in the program                               */
  for (i=0; i < pgm_dbg_dta->NumberElements; i++) {
    QteRegisterDebugView(&iViewID, &iViewLines, Library, TimeStamp,
                         &program_lib, "*PGM      ",
                         pgm_dbg_dta->Element[i].ModuleName,
                         &pgm_dbg_dta->Element[i].ViewNumber,
                         &errorCode);

    /* overwrite unneeded ViewNumber with obtained view id           */
    pgm_dbg_dta->Element[i].ViewNumber = iViewID;
  }
}

/* Typedef for program list passed to this program at STRDBG time    */
typedef struct {
    _TE_OBJLIB_T PgmLib;            /* Name and Library of program   */
    _TE_NAME_T PgmType;             /* program type, *PGM or *SRVPGM */
} PgmList_t;

/* StartUpDebugger: Initialize the debugger.                         */
void StartUpDebugger(PgmList_t ProgramList[],
                     int ProgramListCount) {

  _TE_ERROR_CODE_T errorCode = {0}; /* exceptions are generated      */
  _TE_OBJLIB_T StopHandler = {"DEBUG     ", "*LIBL     "};
  int i;

  if (ProgramListCount!=1) {        /* is only 1 pgm passed on STRDBG*/
    printf("Exactly ONE program must be specified on STRDBG.\n");
    TearDownDebugger();             /* end debugger                
 */
  }

  /* Copy program name to global variables                           */
  memcpy(&program_lib, &ProgramList->PgmLib, 20);

  /* Call StartSourceDebug: giving the name and library of the       */
  /* stop handler.  This will start ILE debug mode                   */
  QteStartSourceDebug(&StopHandler, &errorCode);

  AddProgram();                     /* add program to debug          */
}

/* HandleSession:  This function is called to handle the session     */
/* events STRDBG, DSPMODSRC and ENDDBG.                              */
void HandleSession(char reason[10],
                   PgmList_t ProgramList[],
                   int ProgramListCount) {

  if (memcmp(reason,"*START    ",10) == 0) /* reason is *START       */
    StartUpDebugger(ProgramList, ProgramListCount);
  else if ( memcmp(reason,"*STOP     ",10) == 0) /* reason is *STOP  */
    TearDownDebugger();
  else if ( memcmp(reason,"*DISPLAY  ",10) == 0) /* reason *DISPLAY  */
    ProcessCommands();
}

/* HandleStop: This function is called to handle stop events like    */
/* breakpoint, step, unmonitored exception, etc.                     */
void HandleStop(_TE_OBJLIB_T *ProgramLib,
                _TE_NAME_T ProgramType,
                _TE_NAME_T Module,
                char reason[10],
                long Statements[],
                int StatementsCount,
                char *message) {
  int i;
  _TE_MAPP0100_T Map_Return_Structure;
  long Column = 1;
  long MapLength = sizeof(Map_Return_Structure);
  _TE_ERROR_CODE_T errorCode = {64};
  long stmt_view;

  /* If current view is for a different module than the one that is  */
  /* stopped, change current view to first view in the stopped module*/
  if (memcmp(Module,
             pgm_dbg_dta->Element[current_view].ModuleName,
             sizeof(_TE_NAME_T)) != 0) { /* a different module?      */
    for (i=0; i<pgm_dbg_dta->NumberElements; i++) {
      if (memcmp(Module,
                 pgm_dbg_dta->Element[i].ModuleName,
                 sizeof(_TE_NAME_T)) == 0) { /* found module         */
        current_view = i;           /* change current view to module */
        printf("Current view changed to %d.\n",current_view);
        break;                      /* exit search loop              */
      }                             /* module found                  */
    }                               /* loop through views            */
  }                                 /* current view to be changed    */

  /* Get number of statement view for module stopped                 */
  for (i=0; i<pgm_dbg_dta->NumberElements; i++) {
    if ((memcmp(Module,
               pgm_dbg_dta->Element[i].ModuleName,
               sizeof(_TE_NAME_T)) == 0) &&
        (memcmp("*STATEMENT",
                 pgm_dbg_dta->Element[i].ViewType,
                 sizeof(_TE_NAME_T)) == 0))
      stmt_view = i;
  }


  /* Call QteMapViewPosition to map the stopped location (which      */
  /* is in terms of the *STATEMENT view) to the current view of      */
  /* the module                                                      */
  QteMapViewPosition((char *)&Map_Return_Structure, &MapLength,
                     &pgm_dbg_dta-> Element[stmt_view].ViewNumber,
                     &Statements[0], &Column,
                     &pgm_dbg_dta->Element[current_view].ViewNumber,
                     &errorCode);

  /* Tell the user about the program that stopped.                   */
  for (i=0;i<4;i++) {               /* See why program stopped       */
    if (reason[i] == '1') {
      switch(i) {
        case 0: printf("Unmonitored exception");
          break;
        case 1: printf("Breakpoint");
          break;
        case 2: printf("Step completed");
          break;
        case 3: printf("Breakpoint condition error");
          break;
      }
    }
  }
  printf(" in module %.10s at line %d.\n",
         Module,
         Map_Return_Structure.MapElem[0].LineNumber);

  ProcessCommands();                /* put user into debugger        */
}

/* main: Entry point for the debugger (session or stop handler)      */
main (int argc, char *argv[]) {
  if (argc == 4)                    /* called as source debug program*/
    HandleSession(argv[1], (PgmList_t *)argv[2], *(int
*)argv[3]);
  else if (argc == 8)             /* called as program stop handler */
    HandleStop((_TE_OBJLIB_T *)argv[1], argv[2],
argv[3], argv[4],
               (long *)argv[5], *(int *)argv[6],
argv[7]);
}