0

NOTE: I realise my question was not clear. I have modified it now, and apologise for making the mistake in the first place.

I have a large C project that is to be run on an embedded system. I use the ARM compiler. The code is spread across multiple sub-folders, and consists of .c and .h files.

I wish to take stock of what function is called how many times, so that I can identify dead code and also speed up the most frequently used functions. The ARM compiler has a few options for removing unreachable code, it fails when function pointers come into play So, I'd like to go through every branch of the code and keep a count of number of calls to a function.

For example (this is a very simple program to demonstrate what I am looking for, not the original code):

void foo1(void)
{
    printf("Number is divisible by 5");
}
void foo2(void)
{
    printf("Number is divisible by 10");
}
void foo3(void)
{
    printf("Number is divisible by neither 10 nor 5");
}
void foo4(void)
{
    printf("foo4 called");
}


int main (void)
{
    int x;

    x = rand (11);

    if (! x%10)
    {
        foo2();
        foo1();
    }
    else if (! x%5)       foo1();
    else             foo3();

    return 0;
}

I'd like to run through the ENTIRE code so as to access all sections of the if branch (i.e foo1, foo2, and foo3). This will help me identify which function is called how many times. In the above example foo1() is called more frequently than foo2(), and foo4() is never called. Thus it would make sense to identify and remove foo4() and optimise foo1().

Any ideas/tools to run the ENTIRE code ?

One of the ways I thought was to modify the main function like so:

int main (void)
{
    int x ;

    x = rand (11);

    //*************************************************
    //starting modification
    int a = 1; //added by me
    if_1: //added by me
    if (a == 1)
    {
        foo1(); //original code
        foo2(); //original code

        a=2; //added by me
        goto if_1; //added by me
    }

    else if (a==2)
    {
        foo2(); //original code
        a=3; //added by me
        goto if_1: //added by me
    }
    else             foo3(); //original code
    //end of modification
    //********************************************************        

    return 0;
}

This way it runs through the original code. Any idea how to this type of modification?

tdk
  • 342
  • 1
  • 13

3 Answers3

2
int main (void)
{
    int x = 5;
#ifdef DEBUG
    int debug_i, debug_data[] = { 5, 10, 20 };
    for(debug_i = 0;debug_i<sizeof debug_data / sizeof *debug_data;++debug_i){
        x = debug_data[debug_i];
#endif
    if (x==5)        foo1();
    else if (x==10)  foo2();
    else             foo3();
#ifdef DEBUG
    }
#endif

    return 0;
}
BLUEPIXY
  • 39,699
  • 7
  • 33
  • 70
  • Apologies for not making the original question clear enough. Added more details and explanations. – tdk Jul 12 '13 at 08:47
0

You can change your main() function in this way too

int main (void)
{
    int x = 5;
    while () {
       if (x==5)        { foo1(); x=10;}
       else if (x==10)  { foo2(); x=0; }
       else             { foo3(); break;}
    }
    return 0;
}
MOHAMED
  • 41,599
  • 58
  • 163
  • 268
0

You are basically asking for what is called profiling (how many times is each function called) and code coverage (go through every branch of the code). If you are using gcc you should have a look at the -p option for profiling and the --coverage option for code coverage. Both options add code to the program to make measurements while running the program. The program gprof is then used to view the profiling results and the program gcov to view the code coverage.

I don't fully understand why you want to change the original program. The above methods rely on carefully constructed test inputs of the program to give representative executions (for profiling) and high code coverage. In your example program the compiler is likely to optimize away the calls to foo2 and foo3 because there is no possible program input that would trigger these calls. If instead your program would have looked like:

#include <stdio.h>

void foo1(void)
{
    printf("Value is 5");
}

void foo2(void)
{
    printf("Number is 10");
}

void foo3(void)
{
    printf("Number is neither 10 nor 5");
}

int main (int argc, char ** argv)
{
    switch (argc)
    {
    case 5: foo1(); break;
    case 10: foo2(); break;
    default: foo3(); break;
    }

    return 0;
}

Then running the program as a.out 1, a.out 1 2 3 4 and a.out 1 2 3 4 5 6 7 8 9 would make the program go through all branches. These three runs would give full coverage of the program.

It should be noted that profiling and code coverage are an art by itself. A lot of research has been done in the area of generating test sets to obtain high code coverage, but I don't think there are widely established tools to generate such test sets. Mainly because it is not trivial to do. Manually making test sets that give high code coverage is however a standard procedure in software testing.

Bryan Olivier
  • 5,207
  • 2
  • 16
  • 18
  • I work on ARM compiler as I am working on embedded systems. Any idea on how to do the above for ARM? – tdk Jul 12 '13 at 08:18
  • Apologies for not making the original question clear enough. Added more details and explanations. – tdk Jul 12 '13 at 08:48
  • @user1928721 With these changes I think my answer is even more appropriate. You are really looking at profiling and code coverage. If you indeed have a random generator in there, you may want to replace it with one that enumerates all relevant values, to gain some determinism. – Bryan Olivier Jul 12 '13 at 11:41