OSE - C++ Library User Guide

Graham Dumpleton
Dumpleton Software Consulting Pty Limited
PO BOX 3150
Parramatta, 2124
N.S.W, Australia
email: grahamd@nms.otc.com.au

Table of Contents

Module Debugging
OSE - C++ Library User GuideModule Debugging

Module Debugging

1 Tracing Module Execution

The OTC_Tracer class, and the OTCLIB_MARKBLOCK() and OTCLIB_TRACER() macros provide a general facility only, for tracing program execution. In particular, they do not provide a way of tracing execution while in a specific module, or part of an application. To provide a mechanism for tracing specific portions of an application only, additional classes and macros are provided.

2 On/Off Switches

The simplest mechanism which can be used to enable and disable trace output while in specific modules, is to have a global variable containing a trace level, associated with each module. In particular, for each module, if the global variable has a negative value, trace output would be disabled. If the global variable has a value of zero, trace output would be enabled. If the global variable has some other positive value, whether trace output was enabled would depend on the current trace level. An example of a program using this scheme is given below:

  int PROGRAM = -1;
int LIBRARY = -1;

main()
{
// ... set global variables

OTCLIB_MARKBLOCK(PROGRAM,"main()");

OTCLIB_TRACER(PROGRAM) << "PROGRAM" << endl;
OTCLIB_TRACER(LIBRARY) << "LIBRARY" << endl;

return 0;
}
A limitation of this approach is that you are required to provide code to set the global variables appropriately. Also, due to the variables using reverse logic, if you wanted a particular message to be output if either of two variables is enabled, non intuitive arithmetic is required. For example, even if we ignored the current trace level and only enabled output if a variable was set to zero, we would still have to write:

  OTCLIB_TRACER(!(!PROGRAM || !LIBRARY))
<< "PROGRAM || LIBRARY" << endl;
To make the task of performing arithmetic on these variables easier, their functionality is incorporated into the OTC_TraceSwitch class. This class hides the fact that reverse logic is required and allows you to use standard logical expressions. For example:

  OTC_TraceSwitch PROGRAM;
OTC_TraceSwitch LIBRARY;

main()
{
// ... set value of global switches

OTCLIB_MARKBLOCK(PROGRAM,"main()");

OTCLIB_TRACER(PROGRAM) << "PROGRAM" << endl;
OTCLIB_TRACER(LIBRARY) << "LIBRARY" << endl;
OTCLIB_TRACER(PROGRAM || LIBRARY)
<< "PROGRAM || LIBRARY" << endl;
OTCLIB_TRACER(LIBRARY && !PROGRAM)
<< "LIBRARY && !PROGRAM" << endl;

return 0;
}
When created, trace switches will be disabled unless explicitly initialised. To enable or disable trace switches once created, you can assign to them a boolean value. A value of OTCLIB_TRUE will enable a trace switch, a value of OTCLIB_FALSE will disable a trace switch. One way of enabling the trace switches would be through command line options. For example:

  OTC_TraceSwitch PROGRAM;
OTC_TraceSwitch LIBRARY;

main(int arcg, char* argv[])
{
OUX_Program::initialise(argc,argv);

while (OUX_Program::numOptions() != 0)
{
if (OUX_Program::option(1) == "-trace")
{
OUX_Program::shift(1);
if (OUX_Program::numOptions() == 0)
{
usage();
return 1;
}
else
{
if (OUX_Program::option(1) == "PROGRAM")
PROGRAM = OTCLIB_TRUE;
else if (OUX_Program::option(1) == "LIBRARY")
LIBRARY = OTCLIB_TRUE;
OUX_Program::shift(1);
}
}
else if (OUX_Program::option(1) == "-traceall")
{
OTC_Tracer::enableGlobalTrace();
OUX_Program::shift(1);
}
else
{
// ...

OUX_Program::shift(1);
}
}

OUX_Program::restore();

OTCLIB_MARKBLOCK(PROGRAM,"main()");

OTCLIB_TRACER(PROGRAM) << "PROGRAM" << endl;
OTCLIB_TRACER(LIBRARY) << "LIBRARY" << endl;
OTCLIB_TRACER(PROGRAM || LIBRARY)
<< "PROGRAM || LIBRARY" << endl;
OTCLIB_TRACER(LIBRARY && !PROGRAM)
<< "LIBRARY && !PROGRAM" << endl;

return 0;
}
Since instances of the OTC_TraceSwitch class are lightweight, it is easier to leave them in code when other trace code is not being compiled into your program. If however, you do want them removed when other trace code is not being included, macros are provided for creating instances of the OTC_TraceSwitch class, as well as disabling them and enabling them, which when used will only result in the code being included into your program when the preprocessor symbol OTCLIB_TRACE is defined.

The macro for creating an instance of the OTC_TraceSwitch class is OTCLIB_TRACESWITCH(). This macro accepts two arguments. The first argument is the name of the trace switch and the second argument is the initial value to be assigned to the trace switch. The initial value can be a boolean value, or a logical expression formed from other trace switches. For example:

  OTCLIB_TRACESWITCH(PROGRAM,OTCLIB_FALSE);
OTCLIB_TRACESWITCH(LIBRARY,OTCLIB_FALSE);

OTCLIB_TRACESWITCH(HERE,PROGRAM||LIBRARY);
As the OTCLIB_TRACESWITCH() macro initialises the instance of the OTC_TraceSwitch class, as well as providing the declaration, it can only be used at either function or global scope. If you wish to have an instance of the OTC_TraceSwitch class as a member variable of a class, you will need to use the OTC_TraceSwitch class explicitly and initialise it in the member initialiser list of the class' constructor.

To set the value of a trace switch, you can use the OTCLIB_SETTRACESWITCH() macro. This accepts two arguments. The first argument is the name of the trace switch. The second argument is either a boolean value, or a logical expression formed from other trace switches, which the trace switch should be set to. Alternatively, you could use the macros OTCLIB_ENABLETRACESWITCH() and OTCLIB_DISABLETRACESWITCH(). These macros accept a single argument of the name of the trace switch. The macros will enable and disable the trace switch respectively. For example:

  // ... enable trace switch

OTCLIB_SETTRACESWITCH(PROGRAM,OTCLIB_TRUE);
OTCLIB_ENABLETRACESWITCH(PROGRAM);

// ... disable trace switch

OTCLIB_SETTRACESWITCH(LIBRARY,OTCLIB_FALSE);
OTCLIB_DISABLETRACESWITCH(LIBRARY);

3 Tags and Trace Levels

The trace switch facility described above, only allows tracing to be turned on or off for a certain section of code. The concept of a trace level and different levels of verbosity of trace output has been lost. If different levels of trace information need to be generated at different times, the OTC_TraceTag class can be used instead. This class derives from the OTC_TraceSwitch class, therefore you can use instances of the class in logical expressions involving other instances of the OTC_TraceTag class as well as the OTC_TraceSwitch class.

In addition to being able to be enabled and disabled, instances of the OTC_TraceTag class can have a trace level associated with them. Whether trace information is displayed or not, is determined by using comparison operators in conjunction with the instance of the trace tag. For example, differing levels of verbosity of trace information would be realised as follows:

  OTC_TraceTag PROGRAM("PROGRAM");

main()
{
OTCLIB_MARKBLOCK(PROGRAM,"main()");

OTCLIB_TRACER(PROGRAM)
<< "trace level 0 or higher" << endl;
OTCLIB_TRACER(PROGRAM >= 1)
<< "trace level 1 or higher" << endl;
OTCLIB_TRACER(PROGRAM >= 2)
<< "trace level 2 or higher" << endl;

return 0;
}
To enable a tag, the environment variable OTCLIB_TRACETAGS should be defined before you run your program, to be a list of the tag names to enable. The name you use should correspond to the name given to the constructor of the OTC_TraceTag class. When you enable a trace tag, you may optionally assign a value to it to represent the trace level. If you do not assign the tag a trace level, it will be given a trace level of `0'. For example, if the OTCLIB_TRACETAGS environment variable was set to:

  OTCLIB_TRACETAGS="PROGRAM"
the following would be output for the above example:

  @enter - main()
trace level 0 or higher
@exit - main()
If however the OTCLIB_TRACETAGS environment variable was set to:

  OTCLIB_TRACETAGS="PROGRAM=1"
the following would be output for the above example:

  @enter - main()
trace level 0 or higher
trace level 1 or higher
@exit - main()
The full range of comparison operators may be used with the OTC_TraceTag class, as well as the logical operators implemented by the OTC_TraceSwitch base class. This allows for quite complicated expressions to be used to determine if trace information should be displayed. For example:

  OTC_TraceTag PROGRAM("PROGRAM");
OTC_TraceSwitch LIBRARY;

main()
{
// ... set global trace switches

OTCLIB_MARKBLOCK(PROGRAM,"main()");

OTCLIB_TRACER(PROGRAM >= 0 || LIBRARY)
<< "PROGRAM || LIBRARY" << endl;
OTCLIB_TRACER(PROGRAM == 1)
<< "trace level 1" << endl;
OTCLIB_TRACER(PROGRAM >= 1)
<< "trace level 1 or higher" << endl;
OTCLIB_TRACER(PROGRAM >= 2)
<< "trace level 2 or higher" << endl;
OTCLIB_TRACER(PROGRAM > 1 && PROGRAM < 5)
<< "trace level between 1 and 5" << endl;

return 0; }
As well as enabling tags through the environment variable OTCLIB_TRACETAGS, you can enable and set the value of trace tags from within your program. The function you should call to set the value of a trace tag is OTC_TraceTag::set(). This function takes two arguments. The first argument should be the name of the tag. The second argument should be the trace level for the tag. If you want to disable a trace tag, a value of `-1' should be passed as the second argument.

In addition to being able to set the value of a trace tag you know the name of, you can find out the list of trace tags the system currently knows about. In conjunction with the ability to set the value of a trace tag, this can be used to set trace tags through a mechanism other than the OTCLIB_TRACETAGS environment variable. For example, the following disables all trace tags currently active, and then sets the values of certain trace tags from a configuration database.

  OTC_Map<OTC_String,int> configuration;

// ...

// Disable all the tags first.

OTC_TagInfo* theInfo = OTC_TraceTag::tags();
while (theInfo != 0)
{
theInfo->setValue(-1);
theInfo = theInfo->next();
}

// Now set the values of the tags. The set() function
// automatically creates a new info objects for a tag
// which doesn't exist.

OTC_PairIterator<OTC_String,int> iter = 0;
iter = configuration.keys();
for (iter.reset(); iter.isValid(); iter.next())
OTC_TraceTag::set(iter.key(),iter.item());
Similarly to the OTC_TraceSwitch class, macros are provided which allow you to create instances of the OTC_TraceTag class and set the values of active tags. When using these macros, the code will only be compiled into your application when the preprocessor symbol OTCLIB_TRACE is defined.

The name of the macro used to create an instance of OTC_TraceTag class is OTCLIB_TRACETAG. This accepts a single argument to be used as the name of the instance of the class. The name is also converted into a string and passed to the constructor of the class for use as the trace tag name. If it is required that the instance of the OTC_TraceTag class have static extent, a variant of the above macro, called OTCLIB_STATIC_TRACETAG(), can be used instead. Both macros can only be used at global or function scope. If you wish to have a instance of the OTC_TraceTag class as a member variable of a class, you will need to use the OTC_TraceTag class explicitly.

A second macro, OTCLIB_SETTRACETAG(), is also provided. This takes two arguments and is equivalent to calling the function OTC_TraceTag::set(). The first argument to the macro should be the name of trace tag. The name should not be quoted. The second argument to the macro should be the value to which the trace tag is being set. For example:

  OTCLIB_TRACETAG(PROGRAM);

main()
{
OTCLIB_SETTRACETAG(PROGRAM,0);

OTCLIB_MARKBLOCK(PROGRAM,"main()");

OTCLIB_TRACER(PROGRAM)
<< "trace level 0 or higher" << endl;
OTCLIB_TRACER(PROGRAM >= 1)
<< "trace level 1 or higher" << endl;
OTCLIB_TRACER(PROGRAM >= 2)
<< "trace level 2 or higher" << endl;

return 0;
}

4 Pattern Matching

A further variant of the OTC_TraceSwitch class is the OTC_TracePattern class. This derives from OTC_TraceSwitch, and an instance of the class is regarded as being enabled, when the string with which the instance is initialised, matches a defined pattern. The pattern used to perform the match can be defined by setting the OTCLIB_TRACEPATTERN environment variable before you run your program, or it can be set from within your program. The pattern is a regular expression as used by the UNIX program `egrep', as implemented by the OTC_Regexp class.

Using a pattern to enable trace output, can be useful where you wish to enable tracing in all code files contained in a particular source directory. This can be achieved by using a string generated by SCCS or RCS source code control systems, to initialise an instance of the OTC_TracePattern class in each object file. For example, the following would be appropriate if RCS were being used. Note that the instance of the OTC_TracePattern class is made static so that its name is local to that object file.

  static OTC_TracePattern MODULE =
"$Source: /home/ose/examples/_tracepat.cc $";

void function()
{
OTCLIB_TRACER(MODULE) << "this module" << endl;

// ...
}
If it was now required to trace all code contained in the examples directory, the environment variable OTCLIB_TRACEPATTERN would be defined as follows:

  OTCLIB_TRACEPATTERN="Source: .*/examples/.*"
Alternatively, the trace pattern could be set from within your program by calling the function OTC_TracePattern::set(). This function takes a single argument consisting of the pattern which should be used to match against all pattern tags. Calling the function will cause all active pattern tags to be re-evaluated immediately.

As with the previous classes, macros are provided such that instances of the class, and code setting the trace pattern, will only be compiled into your program when the preprocessor symbol OTCLIB_TRACE is defined. The two macros available for creating instances of the OTC_TracePattern class are OTCLIB_TRACEPATTERN() and OTCLIB_STATIC_TRACEPATTERN(). These both take two arguments. The first argument is the name to use for the instance of the class, and the second argument is the text string to match against the trace pattern. The instance of the OTC_TracePattern class created using the OTCLIB_STATIC_TRACEPATTERN() macro will have static extent.

To change the trace pattern from within your program, the OTCLIB_SETTRACEPATTERN() macro can be used. This takes a single argument consisting of the new trace pattern. Using this macro is equivalent to calling the function OTC_TracePattern::set().

As with the OTC_TraceTag class, the OTC_TracePattern class can be used in conjunction with instances of the OTC_TraceSwitch class in logical expressions. You can also use the class in conjunction with instances of the OTC_TraceTag class. For example:

  OTCLIB_STATIC_TRACEPATTERN(MODULE,
"$Source: /home/ose/examples/_tracepat.cc $");

OTCLIB_TRACETAG(LIBRARY);

void function()
{
OTCLIB_TRACESWITCH(HERE,MODULE||LIBRARY);

OTCLIB_TRACER(HERE) << "this module" << endl;

// ...
}