A Call-Tree Generator For C Programs



October 01, 1991
URL:http://www.drdobbs.com/a-call-tree-generator-for-c-programs/184402421

October 1991/A Call-Tree Generator For C Programs

Eric Bergman-Terrell is an author of astronomy software and articles about astronomy and computer science. You may contact him at Personal MicroCosms, 8547 E. Arapahoe Rd., Suite J-147, Greenwood Village, CO 80112.

Introduction

A call tree shows the hierarchy of function calls in a program. For example, the call tree in Figure 1 shows:

While graphic dispays of call trees, such as Figure 1, are clearest, call trees can also be displayed using text and indentation, as in Table 1. Note that Figure 1 and Table 1 give the same information.

C_CALLS

Since call trees can clarify the function interaction of complex programs, a utility to generate call trees belongs in every programmer's toolbox. This article describes a program named C_CALLS that generates text call trees for Turbo C or Microsoft C programs. C_CALLS can be compiled by either the Turbo C or the Microsoft C compiler.

C_CALLS uses the assembly output from the compiler, so if you don't use the Turbo C or the Microsoft C compiler you may need to modify C_CALLS to work with your compiler's output. The section on customizing C_CALLS provides further details.

Recursive Function Calls

When a function calls itself either directly or indirectly, that call is said to be "recursive." In C_CALLS output, recursive calls are marked by an ellipsis ("..."). In Table 1, function three (line 8) calls itself directly on line 13, and indirectly (through function one) on line 11.

When a program runs, recursive calls bottom out with some base case (arguments that don't require a recursive call), but the static call tree contains a loop back to a previous node, creating an infinite branch in the tree (since the call tree doesn't have access to any arguments). So, when a function makes a recursive call (such as in line 13 of Table 1) , that branch of the function call tree is abandoned and an ellipsis is printed.

How C_CALLS Works

The following sections present the C_CALLS source code and discuss its implementation and use.

A Directed Graph

When you compile a program using Turbo C or Microsoft C, you can specify an option that saves the assembly language translation of the program to a disk file. Using the assembly language file as input, C_CALLS builds a directed graph representing the function calls in the program.

C_CALLS.H

The data structures used for the directed graph (CELL and LIST) are defined in the C_CALLS.H header file shown in Listing 1. Note that "function" is abbreviated as "fcn" in the code.

BLD_GRA.C

Function build_graph creates the directed graph by parsing the assembly language file. build_graph is defined in file BLD_GRA.C shown in Listing 2.

LISTS.C

The directed graph structure uses several linked lists. These lists are manipulated by functions insert_cell, delete_cell, and find_cell, which are defined in file LISTS.C shown in Listing 3.

PRINT.C

Once the directed graph has been built, the call tree is printed by calling print_all_calls, defined in PRINT.C.

C_CALLS "expands" a function, say main, by printing the function name "main" followed by the names of all functions called by that function (one, three, two).

To avoid infinite loops while expanding recursive functions, a list named fcns is used. fcns contains the names of the functions currently being expanded. If print_calls is called for a function already in fcns, that function name is printed followed by an ellipsis ("...") and that function is not expanded further. See Listing 4.

C_CALLS.C

The main program, C_CALLS (defined in C_CALLS.C), takes one command-line argument, the name of an assembly language file. C_CALLS parses the file, builds the directed graph, and prints out the call tree. See Listing 5.

Compiling C_CALLS

The following commands show the appropriate options for compiling the C_CALLS modules (when they are the only.c files in the current directory).

Turbo C: tcc -mh -N *.c
Microsoft C: cl /AH /F 8000
/Fec_calls.exe *.c

Using C_CALLS

To generate a call tree with C_CALLS first compile the target program using an option to create an assembly language file, then run C_CALLS with the assembly language filename as an argument.

The MS-DOS batch files shown in Listing 6 and Listing 7 generate the assembly language file and run C_CALLS. Turbo C users can use TC_CALLS.BAT, and Microsoft C users can use MS_CALLS.BAT. The batch files expect only the target files in the current directory. For further details on generating assembly language files, see your compiler's user guide.

Customizing C_CALLS

Users may want to customize C_CALLS. Possible extensions include excluding standard library functions from call trees or supporting additional compilers. To support another compiler, the first step requires studying the format of assembly language files generated by the compiler. The remaining step is to modify the function build_graph, adjusting its parsing process to the new format.

Caveats

Since optimizing compilers can change the function calling hierarchy of a program by removing or changing function calls, you should disable optimization when generating assembly language files for C_CALLS. For details on optimization and possible interaction with call trees, see your compiler's user guide.

If your compiler's assembly-language file format changes in subsequent releases, function build_graph may need to be modified.

C_CALLS has been tested with Turbo C v1.5 and Microsoft C v5.1.

October 1991/A Call-Tree Generator For C Programs/Figure 1

Figure 1: Call Tree (Graphical Representation)

October 1991/A Call-Tree Generator For C Programs/Listing 1

Listing 1

/* line read from .asm file */
#define MAX_LINE_LEN  1024
typedef char LINE[MAX_LINE_LEN + 1];

/* set of tokens extracted from list in .asm file */
#define MAX_TOKENS       10
#define MAX_TOKEN_LEN    80
typedef char TOKENS[MAX_TOKENS][MAX_TOKEN_LEN + 1];

/* fcn name */
#define MAX_NAME_LEN     80
typedef char NAME[MAX_NAME_LEN + 1];

/* fcn call list */
typedef struct CELL *LIST;

typedef struct CELL
{
NAME      name;
LIST      calls, called_from, next;
} CELL;

/* Function prototypes. */

void build_graph(LIST *fcn_list, const char *filename);
void insert_cell(LIST *list, const char *name);
void delete_cell(LIST *list, const char *name);
LIST find_cell(LIST list, const char *name);
void print_calls(LIST list, const char *name, int depth, LIST *fcns);
void print_all_calls(LIST list);
void error(const char *message);
int namecmp(const char *name1, const char *name2);
/* End of File */
October 1991/A Call-Tree Generator For C Programs/Listing 2

Listing 2

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "c_calls.h"

void build_graph(LIST *fcn_list, const char *filename)

/*
Build a graph which shows the calling relationships of all fcns in
a C program by parsing the program's assembly language file.
*/

{
FILE      *input_file;
LINE      line;
TOKENS    toks;
char      *tp;
NAME      caller_name, called_name;
int       i;
LIST      caller_ptr = NULL, called_ptr;

if ((input_file = fopen(filename, "r")) == NULL)
    error("cannot open input file");

while (!feof(input_file))
    {
    fgets(line, sizeof(line), input_file);
    line[strlen(line) - 1] = '\0';  /* Remove \n from end of line. */
    
    memset(toks, 0, sizeof(toks));
    
    /* Extract the fields from the current line. */
    tp = strtok(line, "\t ");  /* Token separator is a tab or space. */
    
    for (i = 0; (i < MAX_TOKENS) && (tp != NULL); i++)
        {
        strcpy(toks[i], tp);
        tp = strtok(NULL, "\t ");  /* Get next token. */
        }
    
    i-; /* i now points to last token on current line.  */
    
    /* If current line is the start of a fcn... */
    if (stricmp(toks[1], "proc") == O)
        {
        strcpy(caller_name, &toks[O] [1]);
        
        /* Insert fcn into fcn list.  */
        insert_cell (fcn_list, caller_name);
        
        /* Make the new fcn the calling fcn.  */
        caller_ptr = find_cell(*fcn_list, caller_name);
        
        }
    
    /* If current line is a user-defined fcn call...  */
    if ((stricmp(toks[0], "call") == 0) &&
       (toks[i][strlen(toks[i]) - 1] != '@') && (toks[i][1] != '_'))
        {
        strcpy (called_name, &toks  [1] );
        
        /* Insert called fcn into the fcn list.  */
        insert_cell (fcn_list, called_name);
        
        /* Record that the calling fcn is calling this fcn.  */
        insert_cell(&caller_ptr->calls, called_name);
        
        /* Record that this fcn is called by the calling fcn.  */
        called_ptr = find_cell (*fcn_list, called_name);
        insert_cell (&called_ptr->called_from, caller_name);
        }
    }

if (fclose(input_file) != 0)
    error("cannot close input file");
}
/* End of File */
October 1991/A Call-Tree Generator For C Programs/Listing 3

Listing 3

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "c_calls.h"

void insert_cell(LIST *list, const char *name)

/*
Insert a new cell in the list with the specified name and return a
pointer to the head of the list. The new cell is inserted in
alphabetical order.
*/

{
LIST new_cell;

if ((*list == NULL) || (namecmp(name, (*list)->name) < 0))
    {
    if ((new_cell = malloc(sizeof(CELL))) == NULL)
        error("out of memory");
    
    new_cell->calls = new_cell->called_from = NULL;
    strcpy(new_cell->name, name);
    
    if (*list == NULL)
        new_cell->next = NULL;
    else
        new_cell->next = *list;
    
    *list = new_cell;
    }
else
    if (stricmp(name, (*list)->name) != O)
        insert_cell((&(*list)->next), name);
}
void delete_cell (LIST *list, const char *name)

/* Delete the cell with the specified name from the list. */

{
LIST ptr;

/* If cell to be deleted is at head of list... */
if ((*list != NULL) && (stricmp{name, (*list)->name) == 0))
    {
    /* Delete the cell. */
    ptr = *list;
    *list = (*list)->next;
    free(ptr);
    }

else
    /* Try to delete the cell from the rest of the list. */
    if (*list != NULL)
        delete_cell(&(*list)->next, name);
}

LIST find_cell(LIST list, const char *name)

/*
Return a pointer to the cell containing the specified name
if it is found in the list. Otherwise return NULL.
*/

{
for (; list != NULL; list = list->next)
    if (stricmp(name, list->name) == 0)
        return list;

return NULL;
}
/* End of File */
October 1991/A Call-Tree Generator For C Programs/Listing 4

Listing 4

#include <stdio.h>
#include "c_calls.h"

void print_calls(LIST list, const char *name, int depth, LIST *fcns)

/*
Print out the name of the current fcn, and recursively print the names
of all fcns called by the current fcn (i.e. "expand" the current fcn).
*/

{
LIST tmp;
int i;

for (i = 1; i <= depth; i++)
    printf("%1d   ", (i - 1) % 10);  /* Print name of current fcn. */

if (find_cell{*fcns, name) != NULL)   /* Don't continue recursive call. */
    printf("%s...\n", name);
else
    {
    printf("%s\n", name);
    
    /* Save the name of the current fcn to avoid infinite expansion. */
    insert_cell(fcns, name);
    
    /* Print out names of all fcns called by the current fcn. */
    tmp = find_cell(list, name);
    
    for (tmp = tmp->calls; tmp != NULL; tmp = tmp->next)
        print_calls(list, tmp->name, depth + 1, fcns);

    /* Delete name of current fcn after completely expanding it. */
    delete_cell(fcns, name);
    }
}

void print_all_calls(LIST list)

/* Print the fcn call tree. */

{
LIST tmp1, tmp2, fcns = NULL;

for (tmp1 = list; tmp1 != NULL; tmp1 = tmp1->next)
    {
    print_calls(list, tmp1->name, 0, &fcns);
    printf("\n%s is called by:\n", tmp1->name);
    
    for (tmp2 = tmp1->called_from; tmp2 != NULL; tmp2 = tmp2->next)
        printf(" %s\n", tmp2->name);
    printf("\n\n");
    }
}
/* End of File */
October 1991/A Call-Tree Generator For C Programs/Listing 5

Listing 5

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "c_calls.h"

/* Allocate enough stack space to support recursion. */
#ifdef __TURBOC__
extern unsigned_stklen = 32000;
#endif

void main(int argc, char *argv[])
{
LIST fcn_list = NULL;

if (argc ! = 2)
    error("c_calls: usage: c_calls <.asm filename>");

build_graph(&fcn_list, argv[1]); /* Determine calling relationships. */

if (fcn_list != NULL)
    print_all_calls(fcn_list); /* Print the fcn call tree. */
}

void error(const char *message)

/* Exit after giving an error message. */

{
printf("Error: %s\n", message);
exit(-1);
}

int namecmp(const char *name1, const char *name2)

/*
Identical to stricmp, except that "main" is considered
less than any other string.
*/

{
if (stricmp(name1, "main") == 0)
    return -1;
else
    if (stricmp(name2, "main') == 0)
        return 1;
    else return stricmp(name1, name2);
}
/* End of File */
October 1991/A Call-Tree Generator For C Programs/Listing 6

Listing 6

tcc -S -c *.c

del asm.tmp

for %%f in (*.asm) do type %%f >> asm.tmp

c_calls asm.tmp

/* End of File */
October 1991/A Call-Tree Generator For C Programs/Listing 7

Listing 7

cl /0d /c /Fa *.c

del asm.tmp

for %%f in (*.asm) do type %%f >> asm.tmp

c_calls asm.tmp

/* End of File */

October 1991/A Call-Tree Generator For C Programs/Table 1

Table 1 Call Tree (Textual Representation)

 1:  main
 2:  0     one
 3:  0     1      four
 4:  0     1      three     one...
 5:  0     1      2         seven
 6:  0     1      2         three...
 7:  0     1      2
 8:  0     three
 9:  0     1      one
10:  0     1      2         four
11:  0     1      2         three...
12:  0     1      seven
13:  0     1      three...
14:  0     two
15:  0     1      four
16:  0     1      six
17:  0     1      two...

Terms of Service | Privacy Statement | Copyright © 2024 UBM Tech, All rights reserved.