I have the following c++ code in 4 files: test.h, test.cpp, main.cpp,
tracer.c
<test.h>
class test {
int function1 (long l);
int function2 (int i);
public:
int function3 (char c);
};
<test.cpp>
#include <iostream>
#include "test.h"
using namespace std;
int test::function1 (long l) {
cout << "in function 1" << endl;
return l;
}
int test::function2 (int i) {
cout << "in function 2" << endl;
return test::function1 (i) + 1;
}
int test::function3 (char c) {
cout << "in function 3" << endl;
return test::function2 (c) + 1;
}
<main.cpp>
#include <iostream>
#include "test.h"
using namespace std;
int main () {
class test *ptest;
ptest = new test();
return ptest->function3 (1);
}
<tracer.c>
#ifdef __cplusplus
extern "C"
{
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
void __cyg_profile_func_enter(void *this_fn, void *call_site)
__attribute__((no_instrument_function));
void __cyg_profile_func_exit(void *this_fn, void *call_site)
__attribute__((no_instrument_function));
}
#endif
static FILE *fp;
int call_level=0;
void * last_fn;
void __cyg_profile_func_enter(void *this_fn, void *call_site)
{
if (fp == NULL) fp = fopen( "trace.txt", "w" );
if (fp == NULL) exit(-1);
if ( this_fn!=last_fn) ++call_level;
for (int i=0;i<=call_level;i++) fprintf(fp,"\t");
fprintf(fp, "entering %p\n", (int *)this_fn);
(void)call_site;
last_fn = this_fn;
}
void __cyg_profile_func_exit(void *this_fn, void *call_site)
{
--call_level;
for (int i=0;i<=call_level;i++) fprintf(fp,"\t");
fprintf(fp, "exiting %p\n", (int *)this_fn);
(void)call_site;
}
Next I build using the following commands:
g++ -fPIC -g -finstrument-functions -c test.cpp tracer.c
g++ -shared -Wall,soname,libtest.so.0 -o libtest.so.0.0 libtest.o
tracer.o
ln -sf libtest.so.0.0 libtest.so
setenv LD_LIBRARY_PATH ${LD_LIBRARY_PATH}:.
g++ -g -finstrument-functions -o main -ltest -L./ main.cpp
when I run ./main I get the following output from the
__cyg_profile_func_enter and __cyg_profile_func_exit funtions
>cat tracer.txt
entering 0xb75e4e0e
entering 0xb75e4d92
exiting 0xb75e4d92
exiting 0xb75e4e0e
entering 0x80487ca
entering 0x8048766
exiting 0x8048766
exiting 0x80487ca
entering 0x8048704
entering 0x80485d0
entering 0xb75e4c7c
entering 0xb75e4c04
exiting 0xb75e4c04
exiting 0xb75e4c7c
exiting 0x80485d0
exiting 0x8048704
entering 0x804880c
entering 0x8048766
exiting 0x8048766
exiting 0x804880c
entering 0xb75e4e64
entering 0xb75e4d92
exiting 0xb75e4d92
exiting 0xb75e4e64
running the nm command gives :
> nm -C main
U test::function3(char)
indicating as expected that the symbol test::func is undefined in this
compilation unit.
However, running the nm command on libtest.so gives
> nm -DC libtest.so
00000c94 T test::function1(long)
00000d0c T test::function2(int)
00000d94 T test::function3(char)
I'm trying to match the callee function address output in
__cyg_profile_func_enter with the function names output by nm for the
libtest.so shared library. However, these do not match the addresses
of the
3 dynamic functions (test::function1, test::function2 and
test::function3) .
Is there some way to get nm to output the same function address in
shared objects as the callee function address passed to
__cyg_profile_func_enter?
> I'm running gcc v3.3.
On Win32, AIX, Linux, Solaris, HP-UX, Irix, or somewhere else?
[I know the answer, but you shouldn't under-specify your environment.]
> g++ -g -finstrument-functions -o main -ltest -L./ main.cpp
The command line above is "backwards", and works only by coincidence.
Correct command line is:
g++ -g -finstrument-functions -o main main.cpp -L./ -ltest
> entering 0xb75e4e0e
> entering 0xb75e4d92
> exiting 0xb75e4d92
...
> However, running the nm command on libtest.so gives
>
>> nm -DC libtest.so
>
> 00000c94 T test::function1(long)
> 00000d0c T test::function2(int)
> 00000d94 T test::function3(char)
>
> I'm trying to match the callee function address output in
> __cyg_profile_func_enter with the function names output by nm for the
> libtest.so shared library. However, these do not match the addresses
> of the 3 dynamic functions (test::function1, test::function2 and
> test::function3) .
>
> Is there some way to get nm to output the same function address in
> shared objects as the callee function address passed to
> __cyg_profile_func_enter?
No: the addresses that you print depend on where the in memory the
library is loaded, and that address can (and does on newer Linux
distributions) change from one run to the next.
Since nm can't possibly know where the library will be loaded,
it follows that nm can't possibly print the same address.
But when your __cyg_profile_func_enter is executing, it *can*
ask dynamic loader for the base address, and print the address that
will match output from nm.
Even better, it can simply ask dynamic loader for the name, and
print *that*, so you wouldn't have to "match" by hand at all.
Here is the code that does that. I only did the 'entering' part:
$ diff -u tracer.c.orig tracer.c
--- tracer.c.orig 2007-12-04 20:09:08.969400312 -0800
+++ tracer.c 2007-12-04 20:06:33.036105776 -0800
@@ -7,6 +7,7 @@
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
+ #include <dlfcn.h>
void __cyg_profile_func_enter(void *this_fn, void *call_site) __attribute__((no_instrument_function));
void __cyg_profile_func_exit(void *this_fn, void *call_site) __attribute__((no_instrument_function));
@@ -18,12 +19,17 @@
void * last_fn;
void __cyg_profile_func_enter(void *this_fn, void *call_site)
{
+ Dl_info di;
if (fp == NULL) fp = fopen( "trace.txt", "w" );
if (fp == NULL) exit(-1);
if ( this_fn!=last_fn) ++call_level;
for (int i=0;i<=call_level;i++) fprintf(fp,"\t");
- fprintf(fp, "entering %p\n", (int *)this_fn);
+ fprintf(fp, "entering %p", (int *)this_fn);
+ if (dladdr(this_fn, &di)) {
+ fprintf(fp, " %s (%s)", di.dli_sname ? di.dli_sname : "<unknown>", di.dli_fname);
+ }
+ fputs("\n", fp);
(void)call_site;
last_fn = this_fn;
You'll need to link main with -ldl.
And here is a snipet of resulting output:
entering 0x80488d0 __gxx_personality_v0 (./main)
entering 0xd4fade _ZN4test9function3Ec (./libtest.so)
entering 0xd4fa5a _ZN4test9function2Ei (./libtest.so)
entering 0xd4f9e4 _ZN4test9function1El (./libtest.so)
exiting 0xd4f9e4
If you want demangled names, there is a function for that as well
(cplus_demangle() in libiberty).
Cheers,
--
In order to understand recursion you must first understand recursion.
Remove /-nsp/ for email.
Thank you very much for your help. This works great.
Now I'm trying to output the c++ source file names and line numbers
along with the demangled names similar to what addr2line does from the
command line. Do you know if this can be done from the dl loader? Do
I need to write some code similar to add2line.c which can get the
filename/line numbers from the debugging information?
Cheers
> Now I'm trying to output the c++ source file names and line numbers
> along with the demangled names similar to what addr2line does from the
> command line.
This is much much trickier: parsing debug info is quite involved,
and non-trivial effort is required to map PC values to file and line.
Google for DWARF3 standard, start reading it, and you'll quickly
get a feeling that this is much more effort than you are (probably)
willing to extend.
> Do you know if this can be done from the dl loader?
No: the loader doesn't need to use any debug info, and doesn't even
read it.
> Do
> I need to write some code similar to add2line.c which can get the
> filename/line numbers from the debugging information?
You have several options:
A) use libbfd to do the heavy lifting (it knows how read the debug info)
B) open a pipe to 'addr2line', and ask it to do the same.
A) restricts your program to GPL, and understanding how to call
libbfd is itself quite difficult. But you can "steal" most of the
code from addr2line itself.
B) is easiest to write, and will give you correct correct mapping
for the main executable, but not for shared libraries, due to the
same runtime relocation issue. But if you adjust your 'this_fn'
by the runtime relocation (found in di.dli_fbase), then it will
also work for shared libraries; and you are done :)