Main Page | Class Hierarchy | Class List | Directories | File List | Class Members

traceString

Introduction

This tool is designed to read C and C++ files and place inside each function the name of the function. Typically, this name is assigned to a variable that can then be used inside the function. This variable can be useful in several areas, including function tracing, performance monitoring and messages used with exception handling. The way the name is assigned is very flexible and can be tailored on a per-function basis via a pattern file.

Much thanks goes to Alfred Lorber and Bill Barth for many ideas and suggestions for improvement.

The manpage for traceString is available online.

This package also includes tracer.C and tracer.h which applications can incorporate in to take full advantage of traceString. See the example section for more details

Downloading

This tool has been developed by Robert McLay mclay@zaniahgroup.com. Many thanks to SourceForge for hosting the project. The source can be download from here

Other choices in tools.

traceString provides two functions: 1) The ability to place code at the beginning (or end) of the routines and 2) find the name of the routine so that it can be placed at the top of the routine. There is not much than can do the first but some compilers offer non-portable help for the second.

The C99 standard support a macro like name __func which expands to the name of the routine. This may or may not be available currently. The g++/gcc and the and the intel compiler offer builtin function __PRETTY_FUNCTION__ with return the name including arguments and true template class names. However this not universally available. Modern C++ compilers also offer runtime type identification (RTTI) via the typeid(...).name() function. However all the current standard requires is that the return char* string be unique. It does not require human readable output. Currently g++ and the intel compiler icc/ecc produce a mangled version of the name.

How It Works

The functionality of traceString can best be seen using a small example in C++. Given the following raw code:

       int Foo::bar(int a)
       {
         // rest of routine
       }

It can be transformed by traceString to be:

       int Foo::bar(int a)
       { /* %TRACE% */ static const char* myName = "Foo::Bar(int a)"; TRACER(t,myName,"Foo"); /* %TRACE% */
         // rest of routine
       }

Here the idea is that TRACER is a developer supplied macro that, when turned on, will print the name of routine to aid debugging.

A code instrumented by traceString can be re-instrumented repeatly. In this way it can be used as development continues so that when "int Foo::Bar(int a)" becomes "int Foo::Bar(int* a)" the tool can make the changes to the variable "myName" rather than the developer.

A developer has control over what traceString places in the source code via a pattern file. This file describes how the function name should be expanded. The above pattern was:

       static const char* myName = "$(FQNAME)"; TRACER(t,myName,"$(CLASS)");
       

Where "\$(FQNAME)" is the "fully qualified" name of the routine, that is the name of the routine and its arguments. The name of the routine with out is arguements is available ($(NAME): "Foo::bar") as well the class name (: "Foo"). If the pattern line was

       FUNC_TRACE("$(NAME)");
       

then the expansion would be:

       int Foo::bar(int a)
       { /* %TRACE% */ FUNC_TRACE("Foo::bar"); /* %TRACE% */
         // rest of routine
       }

There can be more than one pattern line in the pattern file. There is a default pattern and other named patterns. So the pattern file might look like:

       default: static const char* myName = "$(FQNAME)";TRACER(t,myName,"$(CLASS)");
       ifdef  : \n#ifdef DEBUG\nstatic const char* myName = "$(FQNAME)";\n#endif\n
       FT     : FUNC_TRACE("$(NAME)");
       FC     : FT_wClass("$(FQNAME)","$(CLASS)");
       None   :
       

If the source code originally looked like this:

       int Foo::bar(int a)
       {
         // rest of routine
       }
       int Foo::baz(double b)
       { /* %TRACE[FT]% */ /* %TRACE% */
         // rest of routine
       }
       int Foo::biz(string b)
       { /* %TRACE[FC]% */ /* %TRACE% */
         // rest of routine
       }
       int Foo::getSomething()
       { /* %TRACE[None]% */ /* %TRACE% */
         // rest of routine
       }
       int Foo::foo()
       { 
         /* %TRACE[ifdef]% */ /* %TRACE% */
         // rest of routine
       }

       

It would be converted to this:

       int Foo::bar(int a)
       { /* %TRACE% */ static const char* myName = "Foo::Bar(int a)"; TRACER(t,myName,"$(CLASS)"); /* %TRACE% */
         // rest of routine
       }
       int Foo::baz(double b)
       { /* %TRACE[FT]% */ FUNC_TRACE("Foo::baz"); /* %TRACE% */ 
         // rest of routine
       }
       int Foo::biz(string b)
       { /* %TRACE[FC]% */ FT_wCLASS("Foo::biz(string b)","Foo"); /* %TRACE% */
         // rest of routine
       }
       int Foo::getSomething()
       { /* %TRACE[None]% */ /* %TRACE% */
         // rest of routine
       }
       int Foo::foo()
       { 
         /* %TRACE[ifdef]% */ 



         /* %TRACE% */
         // rest of routine
       }
       

Thus, if there is no trace string already in the function, the default pattern is used. A named pattern is specified with:

      /* %TRACE[FT]% */ /* %TRACE% */
      

where the string in the square brackets controls which pattern is used. The "Foo::getSomething()" shows the use of the "None" pattern where no extra code was placed that function.

traceString will also expand `
' in the pattern file. In this way a single line pattern can expand to multi-line code. In this way C-preprocessor directives like #ifdef can be used.

The tool "traceString" is fast. It is written in C++ and flex. On a Pentium 4 it converted 23000 lines of code in 61 files in 0.1 seconds.

Tracer Example

This package also contains a tracer package to control tracing. Applications can freely include this if they choose to help will tracing the applications tracing program execution.

"tracer" depends on C++ ability to have user code construct objects that are automatically deleted at their end of scope. So we construct a tracer object at the beginning of a routine and at the end of the tracer object's lifetime the destructor is called and we print a message that we are leaving the routine.

We may wish to have a finer control on which routines that print out. There are four tests that must be true in order that a routine prints an entering/leaving message. (ignoring watch lists, see watch list discussion that follows)

a) The static member variable must be turned on. Printing will only occur if it is true.

b) The routine name must not be in the always_excluded list

c) There is a routine list which is either an include or an exclude list. To be printed the routine name must be on include list or not on the exclude list. This list of routines can only be either an include list or an exclude list not both. If there is no list then this is ignored.

d) There is a class list which is either an include or an exclude list. To be printed the class name must be on include list or not on the exclude list. This list of classes can only be either an include list or an exclude list not both. If there is no list then this is ignored.

Watch list rules:

The purpose of the watch list to print out the call tree execution starting at watched routine. That is a routine or class listed in a watch list will print out the routine and all routines called until the execution returns back to that routine. To print following has to be true.

a) Tracing must be turned on

b) A routine or class must be on the appropriate watch list (FUNCTION_WATCH for functions and KLASS_WATCH for classes)

OR

called by a watched routine

c) the regular class and function include/exclude lists are ignored.

d) The always exclude list is obeyed.

      \#include <iostream>
      \#include "tracer.h"

      void c(int i)
      {
        /* %TRACE% */ static const char* myName = "c(int i)";  TRACER(tr, myName,""); /* %TRACE% */
        std::cout << "In middle of c " << i << "\n";
      }

      void b(int i)
      {
        /* %TRACE% */ static const char* myName = "b(int i)";  TRACER(tr, myName,""); /* %TRACE% */ 
        std::cout << "In middle of b " << i << "\n";
        c(i+1);
      }

      void a(int i)
      {
        /* %TRACE% */ static const char* myName = "a(int i)";  TRACER(tr, myName,""); /* %TRACE% */ 
        std::cout << "In middle of a " << i << "\n";
        b(i+1);
      }


      int main()
      {
        /* %TRACE% */ static const char* myName = "main()";  TRACER(tr, myName,""); /* %TRACE% */ 
        NamesList nl;

        nl.push_back("e(int i)");
        nl.push_back("d(int i)");
        nl.push_back("c(int i)");
        nl.push_back("f(int i)");

        Tracer::turnOnEnv(&std::cout);

        std::cout << "\nTesting Function exclude list\n";
        Tracer::applyList(nl,Tracer::FUNCTION, Tracer::EXCLUDE);
        a(1);

        std::cout << "\nTesting Function watch list\n";
        watchList.push_back("b(int i)");
        Tracer::applyList(watchList,Tracer::FUNCTION_WATCH, Tracer::INCLUDE);
        a(1);
      }
      

The above code shows the general behavior. The main routine builts a list of routines to ignore then it calls "Tracer::turnOnEnv(&std::cout);" to check to see if the environment variable TRACER_ON is defined with some value. If so then program execution is printed. In the above execution when traceing is on is:

      Testing Function exclude list

      > a(int i)
      In middle of a 1

        > b(int i)
      In middle of b 2
      In middle of c 3

        < b(int i)

      < a(int i)

      Testing Function watch list
      In middle of a 1

      > b(int i)
      In middle of b 2

        > c(int i)
      In middle of c 3

        < c(int i)

      < b(int i)
      

Note that the entering and leaving messages are indented and that the "c(int i)" routine did not print because it was on the exclude list for the first call to a(1).

In the watch list execution, a is ignored because it is not on the watch list but "b(int i)" is. Note that "c(int i)" is printed because it is called by "b(int i)" and a watch list ignores the "FUNCTION" and "KLASS" include/exclude lists.




Project Hosted By:
SourceForge.net Logo