Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Tracing of function calls in C

I'm developing some modules for an automation system written in C and I need to perform lots of work with hardware. And I see no simple way (like traditional) to debugging things instead of trace logs. So I'm looking for a good practice to log function calls. At least the sequence of calls and return values.

The way it is performed in application is quite straightforward and actually pollutes the code with irrelevant constructions like

int function (int param){
   if(trace_level & LOG_FCALLS){
      writelog("Entering function()");
   }

   /* something useful */

   if(trace_level & LOG_FCALLS){
      writelog("Exit from function()=%d", ret);
   }
}

I decided to use a macro that will do all the dirty work. Now it looks like this

#define LOG_E(fn) const char *__fname=fn;  printf("LOG: Entry to %s\n",__fname)
#define return(ret)     printf("LOG: Exit from %s()=%d\n",__fname,ret)

int testFunc(){
   LOG_E("testFunc");

   /*do useful things */

   return(ret);
}

I see the problems with this code

  1. I'm overriding return statement, and it is requires to write return(ret) all the time instead of return ret. It is easy to forget this issue.

  2. I'm defining string variable within my macro. I'm aware that __func__ macro exists in C99, but my compiler, unfortunately, doesn't support this macro or any other relevant macros.

  3. How to log the values of function arguments?

I'm pretty sure that it is not a new problem and I'm not the first one who faced with it. I'm also aware about AOP thing, but the code instrumentation is not acceptable solution for my system and I haven't found any possibility to do it with my compiler.

So I'm looking for a good ideas how to implement tracing in the most elegant way.

My environment: Legacy code, C, Watcom 10.x, real-time OS

like image 226
evrdrkgrn0 Avatar asked Mar 11 '15 12:03

evrdrkgrn0


People also ask

Where does the compiler insert calls to the tracing function?

The compiler inserts calls to the tracing function at the exit point of a function. The line number that is passed to the function is the line number of the statement causing the exit in the instrumented function.

How to define a tracing function in C++?

For example, a call to longjmp () that leaves function1 and returns from setjmp () in function2 will have a missing call to __func_trace_exit in function1 and a missing a call to __func_trace_enter in function2. To define tracing functions in C++ programs, use the extern "C" linkage directive before your function definition.

What does the -qfunctrace option do in C++?

Using the -qfunctrace option causes the compiler to insert calls to these tracing functions at key points in the function body; however you are responsible for defining these tracing functions. The following list describes at which points the tracing functions are called:

Are the function calls to the tracing functions thread safe?

The function calls are only inserted into the function definition, and if a function is inlined, no tracing is done within the inlined code. If you develop a multithreaded program, make sure the tracing functions have the proper synchronization and do not cause deadlock. Calls to the tracing functions are not thread-safe.


2 Answers

The super-serious, professional way to do this is to make a separate debug/test project, which is separate from the production code entirely. It goes like this:

  • Make sure to have a backup/commit on the production code.
  • Make a hard-copy of the production code on the hard drive. This will become your test project.
  • Create a .txt log file where you write the full signature of each function you want to log, for example:

    int function (int param)
    float function2 (void)
    ...
    
  • Create a little PC program/script that takes the above .txt file as input, then searches through the source code for matching lines of function definitions. The PC program will then generate a new .c file based on the original code, where it inserts the debug logging code inside the desired functions, after { and before }. It will take a few hours of your time to make such a program.
  • Link your test project by using the modified source code created by your script.

The above method is how I do it myself on mission-critical software, where you have requirements from safety standards (MISRA, code coverage etc) saying that no code which is not executed in the final product is allowed.

This method ensures the integrity of the production code and guarantees that no accidental bugs are added to the program by the test/debug code. It also leaves the clutter of compile switches etc out of the production code. And you won't have any old debug code remains in your project that you forgot to delete (otherwise I always forget some snippet of debug code somewhere in my programs).

like image 51
Lundin Avatar answered Sep 28 '22 15:09

Lundin


#if defined(DEBUG_BUILD)
#  define START_FUNCTION if(trace_level & LOG_FCALLS){writelog("+++ %s()", __func__)
   }
#  define END_FUNCTION if(trace_level & LOG_FCALLS){writelog("--- %s()", __func__)
#elif defined (TIMING_BUILD)
#  define START_FUNCTION  WRITE_TIMED_LOG("+++")
#  define END_FUNCTION WRITE_TIMED_LOG("---")
#else
#  define START_FUNCTION
#  define END_FUNCTION
#endif
int function (int param){
   START_FUNCTION;
   ...
   if(error_occurred) {
     END_FUNCTION;
     return errror_code;
   }
   ...
   END_FUNCTION;
   return 42;
}
like image 21
Gyapti Jain Avatar answered Sep 28 '22 16:09

Gyapti Jain