Imagix 4D User Guide


Analysis Issues

Analyzing Header Files

Generally, there is no need to include the header files in the list of files you pass to the Imagix 4D analyzer. By specifying the .c and .cpp files and the include directories, the header files are automatically analyzed if they are directly or indirectly included through #include statements. The only reason to list the header files is to cause them to be added to your project even if they're not transitively included by a C or C++ source file.

System vs. Application Include Directories

With the Imagix 4D analyzer, as with a compiler, you specify a list of include directories. When the analyzer encounters a #include preprocessor directive, it searches the include directories, in the order that they are specified, until it finds a file matching the name specified in the #include statement. By default, the Imagix 4D analyzer distinguishes between these two include statements:

#include "fileA.h"
#include <fileA.h>
in only one way. In the first statement, where the file name is surrounded by quotation marks, the analyzer first searches for fileA.h in the current directory. Then, with either statement, the analyzer continues to look through the specified include directories. This search continues until the first fileA.h is found.

You can specify that certain include directories be considered system include directories. This is done by using the -Sdirname rather than the -Idirname option when invoking imagix-csrc.

There are two possible effects of using -S rather than -I, and both involve source analyzer options. The first is tied to the -nosys option. When the -nosys option is set, header files located in directories designated as system include directories are still parsed, but no data file is generated, and you can't explore their contents. So it is recommended that you use -S for specifying include directories containing header files, such as stdio.h, that you generally aren't interested in. This avoids cluttering up your project's database with unimportant data.

The second difference between -S and -I occurs if the -sysincfirst option is applied. The option -sysincfirst causes all of the -S directories to be searched before any of the -I directories for any header files specified with < >. If you follow the convention of using

#include "appfile.h"
for specifying your own application header files and

#include <sysfile.h>
for including the system header files, you can use the -sysincfirst option. It may be useful in resolving header file name conflicts, in those cases where some of your application header files and some of your system header files have the same name.

Path Names

Windows supports the use of spaces in path names, and is not case-sensitive. This has certain implications for invoking the Imagix 4D analyzer. Consider the following examples:

(1) imagix-csrc -IC:\Program Files\Include -ox File.c
(2) imagix-csrc "-IC:\Program Files\Include" -ox File.c
(3) imagix-csrc "-IC:\PROGRAM FILES\INCLUDE" -ox File.c
The include directory C:\Program Files\Include has a space in it. Arguments containing such spaces need to be surrounded with double quotes. Thus, example (1) will generate a analysis error, while example (2) will analyze correctly. Examples (2) and (3) are equivalent because of Windows' case-insensitivity.

Function Pointers

C and C++ support the use of function pointers, a pointer to a function's entry address in physical memory, to call a function. One of the areas in which Imagix 4D source code analysis transcends standard parsing is the information that the analyzer captures about the static aspects of function pointers.

Any variable that gets set with data containing function names is marked as a function pointer. All the functions in its static initializer are recorded as being (potentially) called from this function pointer. If a function makes a call through the function pointer, then the function is shown as calling the function pointer variable. As a result, you can see the potential call tree by following the call relationship from the function to the function pointer variable and then to all the functions potentially being assigned to this variable. The analyzer will also track assignments from one function pointer to another function pointer and it will recognize function pointers or functions passed as parameters.

This approach works well for function pointer variables that are statically initialized, or arrays of function pointers, or structs / unions / classes that have function pointer members. The analyzer will also track assignments. However, if the variable gets dynamically updated (i.e. if another function name is assigned in a statement), or if there is a pointer involved in getting to the function pointer variable, or if the function names are passed through parameters, then the graph will generalize and show all possible functions and function pointers that can be called. It will not try to assess the control logic to eliminate functions that would be precluded by certain if-statements.

The following examples show what is handled.

// function pointer relationships
int foo1();
int foo2();
int foo3();
typedef int (*fpT)();
fpT x1 = foo1;                  // handled: direct fptr
fpT *x4 = &x1, x2 = x1, x3;     // handled: pointer to fptr
int *v;
fpT a1[] = {foo1, foo2, foo3};  // handled: array of fptrs
fpT (*a2)[] = &a1;              // handled: pointer to array of fptrs
struct s1 {
    fpT fp;
    double w;
    struct {
	int cnt;
	fpT fp2;
    } si[3];
} s1 = {foo3, 2.0},              // handled: struct containing fptrs
  s2 = {foo2, 3.0, {{2, foo1}}}, // handled: struct c. arrays of fptrs
  *sp1 = &s1;                    // handled: pointer to struct containing fptrs
struct s1 s3 = {foo1, 0.4, {{3, foo2},{4,foo3}}};  // handled
fpT goo1()
{
    fpT r = &foo1;
    if (v) return r;     // handled: return values
    else   return foo2;  // handled: return values
}
void goo2(fpT &fp)
{
    fp = foo3;           // handled: out parameters
}
int bar1()
{
    fpT locfp;
    (*x1)();             // handled -> foo1,foo2
    goo1()();            // handled -> foo4,foo5
    *v = 2;
    (s1.fp)();           // handled -> foo1,foo2,foo3
    x3 = s1.fp;          // handled
    *x4 = foo3;          // handled
    (*a2)[1]();          // handled -> foo1,foo2,foo3
    goo2(&locfp);
    (*locfp)();          // handled -> foo2
}
int bar2(fpT p, fpT q)
{
    if (v) p = foo3;     // handled
    if (*v) p = x1;      // handled
    (*p)();              // handled -> foo1,foo2,foo3
    x1 = foo2;           // handled
    x2 = sp1->fp;        // handled
    s1.fp = foo1;        // handled
    x3 = goo1();         // handled
    q();                 // handled -> foo2
}
int bar3()
{
    (*a1[*v])();         // handled -> foo1,foo2,foo3
    (*sp1->fp)();        // handled -> foo1,foo2,foo3
    bar2(foo1,foo2);     // handled
    (*x2)();             // handled -> foo1,foo2,foo3
    (*x3)();             // handled -> foo1,foo2,foo3,foo4,foo5
    s2.si[0].fp2();      // handled -> foo1,foo2,foo3,foo4,foo5
    s3.fp();             // handled -> foo1,foo2,foo3
    (*s3.si[1].fp2)();   // handled -> foo1,foo2,foo3,foo4,foo5
    (**x4)();            // handled -> foo1,foo2,foo3
    while (*x4) (*x4++)(); // handled -> foo1,foo2,foo3
    (sp1++->fp)();       // handled -> foo1,foo2,foo3
    (x2=foo1)();         // handled -> foo1,foo2,foo3
}
Note that you can add more knowledge into Imagix 4D's database by indicating which functions are potentially called by a function pointer. One way to do this is to create datafiles containing this information (see `Adding Your Own Data - vdb files'). Another approach is to modify your source code. When you know which functions are called by a function pointer, you can add source lines like the following, and Imagix 4D will then be able to complete the call trees.

#ifdef IMAGIX_ONLY
    FunctionPointerType fp = {func1, func2, ..., funcN};
#else
    FunctionPointerType fp;  // normal definition for fp
#endif
Also note that in the case of structs, the functions are associated with the members. Hence, two struct variables of the same struct type that are completely distinct will be merged together. Same declaration cross-references are not captured (e.g. "FPTR x = foo, y = x;").

The handling of void* as a potential function pointer is controlled through the -voidfptr option (see `Analyzer Syntax and Options'). Cases where memcpy is used to copy function pointers can be tracked by incorporating libc.inc located in the ../imagix/user/cc_lib directory. See the comments in libc.inc itself for instructions.