In the project I'm working on, we agreed on using only functions returning error codes (no exceptions) to handle errors.
In order not to "pollute" my code with debug messages I was working on a solution based on instrumentation (inspired by this post) .
Instead of using ints as the error code, I'm encapsulating it in a class as:
// File rc.h
class rc
{
public:
rc(int) __attribute__((no_instrument_function));
rc& operator=(rc);
private:
int value;
};
Then defining these operators and the instrumentation functions as:
// File rc.cc
#include <stdio.h>
#include <time.h>
#include "rc.h"
static FILE *fp_trace;
static int isError;
extern "C"
{
void __attribute__ ((constructor)) trace_begin (void)
{
fp_trace = fopen("trace.out", "w");
isError = 0;
}
void __attribute__ ((destructor)) trace_end (void)
{
if(fp_trace != NULL) {
fclose(fp_trace);
}
}
void __cyg_profile_func_enter (void *func, void *caller) __attribute__((no_instrument_function));
void __cyg_profile_func_exit (void *func, void *caller) __attribute__((no_instrument_function));
void __cyg_profile_func_enter (void *func, void *caller)
{
}
void __cyg_profile_func_exit (void *func, void *caller)
{
if ((fp_trace != NULL) && (isError == 1)) {
fprintf(fp_trace, "x %p %p %lu\n", func, caller, time(NULL));
isError = 0;
}
}
}
rc::rc(int valueIn) :
value(valueIn)
{
}
rc& rc::operator=(rc rcIn)
{
value = rcIn.value;
if (value != 0)
{
isError = 1;
fprintf(fp_trace, "%d\n", value);
}
return *this;
}
However, in order not to print too much things, I don't want to log function calls that returns return code 0 thus the isError
flag.
This can be used with the following example:
#include <cstdlib>
#include <ctime>
#include "rc.h"
rc bar(void)
{
rc result(std::rand() % 3);
return result;
}
int main(void)
{
std::srand (std::time(NULL));
rc returnCode(0);
for (int i=0; i<10; ++i)
{
returnCode = bar();
}
return 0;
}
Compile and run with
g++ -finstrument-functions -g -c -o rc.o rc.cc
g++ -g -c -o test.o test.cc
g++ test.o rc.o -o a.out
./a.out
Reading the output with the script given in the previously mentioned post will result in something like:
Error code 2
rc::operator=(rc) at 2016-07-03T18:32:09+0200, called from main (test.cc:32)
This is almost what I could have wished. But compared with a solution where I would simply add a test in rc foo(void)
to test whether the output is non zero and then log the error, that will add overhead only (plus the overhead due to the check) in the case where an error occurs (hopefully not too often), I am going to add overhead due to the instrumentation (plus the possible overhead due to the rc wrapper but I'm OK with it) at each call…
Is there a solution, that I couldn't thought off, that wouldn't instrument the operator=
in the case where the argument is zero? Since rc::value
is known only at runtime, I don't think a template with argument the value that would be specialized for value=0
and wouldn't instrument that case would work. Would it?
There is an extra constraint: it is important the instrumented function is the assignment operator since I want to know the caller, hence I can't add some extra proxy level.
edit on this last point, I've thought of making the assignment operator inline (and not instrumented) and call an instrumented function in the case the return code isn't zero but that can't work as finstrument-function doc page states that:
This instrumentation is also done for functions expanded inline in other functions. The profiling calls will indicate where, conceptually, the inline function is entered and exited.
So that it wasn't pointing to the real caller.
The solution isn't to rely on instrumentation but on the __builtin_return_address(unsigned int)
function that is used by instrumentation.
You can use this function to get the return pointer of the caller:
rc& rc::operator=(rc rcIn)
{
value = rcIn.value;
if (value != 0)
{
fprintf(fp_trace, "%d %p\n", value, __builtin_return_address(1));
}
return *this;
}
That way you don't need to rely on instrumentation to get the caller address and will add a really little overhead over using simply an int.