Tek-Tips is the largest IT community on the Internet today!

Members share and learn making Tek-Tips Forums the best source of peer-reviewed technical information on the Internet!

  • Congratulations strongm on being selected by the Tek-Tips community for having the most helpful posts in the forums last week. Way to Go!

Profiling, tracing function flow in a C++ program?

Status
Not open for further replies.

gpsmith

Programmer
Jan 4, 2003
5
US
Is there a Utility (that will run on an HP UNIX box) that will trace the function flow in a C++ program?

I thought that ctrace might work but it is not on my HP Unix box. And, apparently even if it was there it works by inserting fprintf statments into the code (i.e. the generated .c files) and the function trace is only generated when you execute the compliled code.
 
I'm not sure what you mean by function flow. As you said, if you put fprintfs, they will only print if that part of the code is executed. Do you want it to print the parts of code that aren't executed???
 
xyb - By functin flow I mean I wan see the structure of a program. What function can call other function, drilling down from the top (main) to the all functions under main that can be called.

For example, ctrace will produce something like the following:


main() <mySrc.c>
foo1() <anotherSrc1.c>
foo1.1() <anotherSrc2.c>
foo1.1.1() <anotherSr3c.c>
foo1.1.2() <anotherSrc3.c>
foo1.2() <anotherSrc1.c>
.
.
.
exit()

In the above, main is in file mySrc.c and calls foo1() (which is in file anotherSrc1.C)
and foo1() calls foo1.1() ( which is in file anotherSrc2.C) and foo1.1() calls
foo1.1.1() and foo1.1.2() ... , etc.

To generate this manually when you have an application spread over 400 source files that contain over 500K line of code is not the way to go. I find it hard to belive that there is not some utility out there that will do the above?

thanks
 
gdb, with break points... You can use the marcos (#line, #promag, ect.) and their tools.
Code:
#include <iostream>
#line 2 &quot;main.cpp&quot;


int main(int argc,char * argv[])
{
  cout << __LINE__ << &quot; &quot; <<__FILE__ <<endl;
}
Granted that it doesn't give you the function calls, but you can add it to the cout statement, then when delivering, grep &quot;__LINE__ << &quot; &quot; << __FILE__&quot; and comment them all out.

You could also add a debug class and add a commandline switch to show the debug information. For instance, have a static string that you add a tab-char on to every time a debug object is created, a static boolean value that tells it weather or not to print the debug information, and such.

Code:
class Debug
{
public:

   Debug(string func)
   {
      tabs += &quot;\t&quot;;
      if(ison == true)
          cout << tabs << func << endl;
      trace.push_back(tabs+func+'\n');
   }

   ~Debug()
   {
      tabs.replace(tabs.size());
      trace.pop_back();
   }

   Static void doTrace()
   {
       for_each(trace.begin(),trace.end(),printf);
   }

private:
   static bool ison;
   static vector<string> trace;
   static string tabs;
};

I have seen a debug class used a ton of times, but never looked at the code.... This is just an idea I threw together off the top of my head. I'm sure there are some tools avalible on Sourceforge...

Easiest is to compile with g++ -g, and then use gdb. The best way is to use a debug class built into the app, but gdb has a trace... There is a tool, I can't remember the name of, that goes as far as to draw pathways and such... Perhaps Argo does that too?

 
I have done something similar myself in the past in a very large C++ program suite.

You define a class which logs function entry on creation and function exit in the destructor. NOTE: you don't really want it to log to stdout since it will get all mixed up with your own output. Best to make it log to a file or some logging service.

e.g. (this is off the top of my head so you may have to modify it a bit...)

class FlowLogger
{
public:

FlowLogger(char *FuncName) : mFuncName(FuncName)
{
Log(&quot;Enter&quot;);
mIndent++;
}

~FlowLogger()
{
mIndent--;
Log(&quot;Exit&quot;);
}

static Initialise(char *filename)
{
mIndent = 0;
mOutf.open(filename);
}
private:
// If you call FlowLogger from multiple threads then
// you should put a mutex around the Log function
Log(char *what)
{
// Do the indent
for (int i=0; i<mIndentLevel; i++)
{
mOutf << &quot; &quot;;
}

// Note: could add __FILE__ and __LINE__ as well as
// getpid() here (best to store getpid in Initialise
// method and output it here).
mOutf << what << &quot; &quot; << mFuncName << endl;
}

string mFuncName; // stores function name for logging

static int mIndent; // global indent level
static ofstream mOutf; // global log file
};

Somewhere you have to initialise the static stuff:

FlowLogger::Initialise(&quot;LOGFILE&quot;);

Also I suggest you do the following

#ifdef DEBUG
#define TRACE(s) FlowLogger(s)
#else
#define TRACE(s)
#endif

Then to use it, just add it at the start of every function (ok this is the tedious bit - especially to go back and do all your old code, but once you get used to it it becomes second nature). When you compile with DEBUG defined, you will get the trace. You could change this to check an environment variable in UNIX or the registry in windows as an alternative. You could even send the log messages to some logging or message service rather than a file.

int foo()
{
TRACE(&quot;Foo&quot;);
}

int bar()
{
TRACE(&quot;Bar&quot;);
}

int xyzzy()
{
TRACE(&quot;xyzzy&quot;);
return foo() + bar();
}

int main(int argc, char*argv[])
{
xyzzy();
}
 
aha - I just re-read this and think I know what you are after. I think you want a think called a static analyzer. This will process your code and show you a function/class call tree
fnA--+fnB
|
+fnC--+fnD

etc. I don't know of any off the top of my head but if you are prepared to pay for them - there are adverts for this kind of thing in Dr Dobbs Journal (a programming mag from the US). Otherwise, search for a free static analyzer on the web and see what you come up with....
 
Typically I defince another stream to a slaved terminal window, that way I can watch it as I approach a place it's been crashing. So I suppose the cout's would be sout's and the clas would have a ostream (or ostream pointer)... I suppose other than that, GMTA.
 
On some OS's there is the command 'cflow' which 'Generates a C and C++ flow graph of external references'. Some versions do not support C++, some do.

HTH
 
The above posts are all interesting solutions but .

I was hoping to find a utility that would operate someting like the linker only it would generate parent function names (and the file it was in) and then the functions that they called (and the file they were in) and the functions that they called, etc.


Any more ideas?

Thanks all,
George
 
On Solaris there's a program called 'truss', which allows you to run and follow order in which the functions are called. It's possible to enable tracing on sytem calls (default) and also on function calls in libraries and your program.

It may not be what you're looking for. Perhaps there are some reverse engineering tools available that might help..
 
ok - hackey way to get at the info. This works on solaris and should work on HPUX too.

use &quot;dis -C <exe name>&quot; to get a disassembly of the executable. &quot;dis&quot; is the disassember, the -C option outputs C++ calls in a de-mangled format (otherwise you get them in a mangled C format with the parameters mashed into the name, which is ok but not easy to read).

You can then use egrep on the output to find for each function/method what it calls. Note that this only will help for information in the file that you disassemble. You will have to disassemble each library and/or object separately to get their call tree. However this is an advantage as it allows you to target the dissasembly.

Solaris output is of the format :
main()
10ef8: 94 10 20 00 clr %o2
11228: 91 3a 20 18 sra %o0, 24, %o0
1122c: 80 a2 20 00 cmp %o0, 0
11014: 7f ff fe eb call Outputter::print(char*) [__0fJOutputterFPrintPc]


Outputter::print(char*) [__0fJOutputterFPrintPc]()
10bc0: 9d e3 bf 90 save %sp, -112, %sp
10bc4: 40 00 00 02 call 0x10bcc
10bc8: 13 00 00 42 sethi %hi(0x10800), %o1

so the following should do the trick IF YOUR DISASSEMBLY IS IN THE SAME SORT OF FORMAT AND IF HP PROCESSORS USE &quot;call&quot; IN THEIR ASSEMBLY LANGUAGE TO CALL SUBROUTINES.:

dis -C myprog > dis.txt
egrep &quot;^[^ ]|call&quot; dis.txt

where the spaces in the egrep command are a space followed by a tab. (the first ^ means at the start of the line, the second ^ means NOT any of the characters in between the [ and ] i.e. show any lines
1) NOT starting with a space or tab
2) which contain the text &quot;call&quot;

The sort of output I get is as follows (you can use awk to pretty things up)

DoHello(Outputter&) [__0FHDoHelloR6JOutputter]()
10fec: 40 00 00 02 call 0x10ff4
11014: 7f ff fe eb call Outputter::print(char*) [__0fJOutputterFPrintPc]
DoGoodbye(Outputter&) [__0FJDoGoodbyeR6JOutputter]()
11030: 40 00 00 02 call 0x11038
11058: 7f ff fe da call Outputter::print(char*) [__0fJOutputterFPrintPc]
main()
11074: 40 00 00 02 call 0x1107c
11084: 7f ff fe c9 call Outputter::Outputter(void) [__0oJOutputterctv]
1108c: 7f ff ff d7 call DoHello(Outputter&) [__0FHDoHelloR6JOutputter]
11094: 7f ff ff e6 call DoGoodbye(Outputter&) [__0FJDoGoodbyeR6JOutputter]
110ec: 40 00 00 b3 call stream_locker::do_lock(void) [__0fNstream_lockerHdo_lockv]
110f4: 40 00 00 af call unsafe_ios::rdbuf(void) [__0fKunsafe_iosFrdbufv]
1112c: 40 00 00 9a call stream_locker::lock(void) [__0fNstream_lockerElockv]
1114c: 40 00 00 73 call stream_locker::~stream_locker(void) [__0oNstream_lockerdtv]
11158: 40 00 00 70 call stream_locker::~stream_locker(void) [__0oNstream_lockerdtv]
11160: 7f ff fe eb call Outputter::GetCounter(void) [__0fJOutputterKGetCounterv]
1116c: 40 00 00 33 call ostream::eek:perator <<(int) [__0oHostreamlsi]
11190: 40 00 00 62 call stream_locker::~stream_locker(void) [__0oNstream_lockerdtv]
1119c: 40 00 00 5f call stream_locker::~stream_locker(void) [__0oNstream_lockerdtv]
111a4: 40 00 41 4b call _ex_rethrow_q
ostream::eek:perator <<(ostream& (*)(ostream&)) [__0oHostreamlsPFR6Hostream_R6Hostream]()
111f8: 40 00 00 67 call stream_locker::lock(void) [__0fNstream_lockerElockv]
1120c: 40 00 00 43 call stream_locker::~stream_locker(void) [__0oNstream_lockerdtv]

You can pretty this up a bit on solaris with

egrep &quot;^[^ ]|call&quot; dis.txt | sed 's/^.*[ ]call[ ]/ /'

where again all the spaces in the [ ] are a single space followed by a single tab. This replaces all the addresses and assembly language junk and the &quot;call&quot; with a couple of spaces.

Hope this is finally what you need!!!!!!!

Philip

 
Status
Not open for further replies.

Part and Inventory Search

Sponsor

Back
Top