Experiments with gcc auto instrumentration of C/C++ source code for run-time function call tree tracing
Imagine a simple C project. Here is a very small one as an example:
#include <stdio.h>
extern int bar(int a);
static int foo(int a);
int baz(int a) { return a ; }
extern int bar(int a) { return baz(a); }
static int foo(int a) { return bar(a); }
int main() {
printf("Hello World\n");
int b = baz(1);
return bar(foo(0));
}
The following comments illustrate example code you might add to output useful info at certain places in the code:
#include <stdio.h>
extern int bar(int a);
static int foo(int a);
int baz(int a) { /* if (a) cwrap_log("- a=%d", a); */ return a ; }
extern int bar(int a) { return baz(a); }
static int foo(int a) { return bar(a); }
int main() {
printf("Hello World\n");
int b = baz(1);
// cwrap_log("- b=%d", b);
return bar(foo(0));
}
With such a small project it's easy to see what's happening. Which function is calling which other function, etc. However, as the project gets bigger then it's more and more difficult to comprehend the source code by just reading it, or adding a few log lines here and there. Eventually with hundreds or thousands of source files it will be unpractical to quickly get into the source base by just reading the source code. Wouldn't it be great if we could compile a project and it would automatically generate a human friendly log at run-time showing us which functions called which other functions, e.g.:
T28106 0.000000s + baz() {
T28106 0.000080s - a=1
T28106 0.000108s } = 1 // baz() #1
T28106 0.000115s - b=1
T28106 0.000122s + foo() {
T28106 0.000123s + bar() {
T28106 0.000130s + baz() {} = 0 // #2
T28106 0.000143s } = 0 // bar() #1
T28106 0.000150s } = 0 // foo() #1
T28106 0.000157s + bar() {
T28106 0.000158s + baz() {} = 0 // #3
T28106 0.000171s } = 0 // bar() #2
Explanation of the above output log:
- Each line starts with a (T)hread ID, in case the program is multi-threaded.
- Next is the elapsed (s)econds since the start of the program.
- Two log lines are generated for each function call; an entry line, and an exit line.
- The exit line shows the integer result of the function.
- The exit line shows the number of calls made to that function so far.
- The entry and exit lines are collapsed to a single line if no log lines occur between them.
Other data could be added to the output too, such as human friendly function parameters, etc.
- One way is to use gcc's
-finstrument-functions
command line switch [1]. - This causes gcc to automatically call a generic entry and exit function for every function called at run-time.
- The generic entry and exit functions are called
__cyg_profile_func_enter()
and__cyg_profile_func_exit()
. - This method is well documented with lots of examples that can be Googled, e.g. [2].
- However, the main disadvantage is run-time speed:
- Every function gets instrumented whether you like it or not, which comes with a performance penality due to extra code overhead at run-time.
- Even if the instrumentation function does not output to the log, it likely still must run an
if(verbosity)
statement etc, which bloats and slows down the code at run-time. - There are gcc command line options to exclude files and functions from instrumentation, but it can then be annoying to have to re-compile every time instrumentation is changed.
- Another disadvantage is that the generic instrumentation functions know nothing about the callers function parameters or return type.
[1] -finstrument-functions [2] etrace
There are two ideas at play here:
- Idea #1: Pretend to be gcc so when the makefile calls gcc it's actually running our script which implements extra business logic as well as running gcc :-)
- In this way, we can potentially instrument a project without changing a single line of source code or makefile!
- Idea #2: If we create a wrapper function for function
foo()
, and callfoo_wrap()
instead offoo()
, then no matter how manyreturn
statementsfoo()
has, control is always returned tofoo_wrap()
whichfoo()
returns. - This means we need to somehow auto generate the wrapper functions which is easier said than done.
- Idea #3: gcc generates assembly
call
statements to call functionfoo()
but on Intel CPUs it's 'easy' to doctor these call statements into indirect call statements. - Yes, indirect call statements are slightly slower than direct call statements, but it's still much faster than the extra
if(verbosity)
statements and generally bloating code using the gcc built in intrumentation. - Because the indirect functions calls can be changed at run-time, this means many functions that we end up not being interested in, can be dynamically switched off without having to re-compile again.
This is the idea which we will experiment with further!
- If the makefile correcty uses the environment variable
$CC
to invoke the compiler we can hijack it. - E.g.
make CC="perl cwrap.pl gcc"
will cause make to invokeperl cwrap.pl gcc
whenever it would normally invokegcc
. - Any of the gcc line parameters normally passed to gcc will also be passed to the hijack script.
- This means the hijack script can call the real gcc with the real command line parameters.
- However, it can also do a bunch of other stuff like changing the command line parameters and/or changing the source code!
- One way to do this for C code is to use the gcc command line parameter
-aux-info
[1] which generates a list of all function prototypes. - The function prototypes can be used to create the wrapper functions.
[1] -aux-info
- By pretending to be gcc, instead of compiling
.c
files to.o
files, we can compile.c
files to.s
assembly files, and then to.o
object files. - After the
.s
assembly file is generated then we can change the Intelcall foo
assembly statements intocall *indirect_foo(%rip)
statements. - After the call statements have been doctored then the
.o
object files are created.
$ make -f c-example.mak clean ; make -f c-example.mak && ./c-example ; ll c-example*
rm --force --verbose c-example c-example*cwrap.* c-example.o
cc -g -c c-example.c -o c-example.o
cc -g -o c-example c-example.o
Hello World
-rwxrwxr-x 1 simon simon 9992 Oct 26 15:21 c-example*
-rw-rw-r-- 1 simon simon 421 Oct 26 15:19 c-example.c
-rw-rw-r-- 1 simon simon 508 Oct 26 15:16 c-example.mak
-rw-rw-r-- 1 simon simon 4032 Oct 26 15:21 c-example.o
- Note: The same makefile and source file are used without any changes!
$ make -f c-example.mak clean ; make CC="perl cwrap.pl gcc" -f c-example.mak && ./c-example ; ll c-example* ; cat c-example.cwrap.out
rm --force --verbose c-example c-example*cwrap.* c-example.o
perl cwrap.pl gcc -g -c c-example.c -o c-example.o
Sat Oct 26 15:16:45 2019 0.056485 cwrap: gcc .. > c-example.o.cwrap.cc.out; 4 functions, 0 warnings
perl cwrap.pl gcc -g -o c-example c-example.o
Sat Oct 26 15:16:45 2019 0.070839 cwrap: gcc .. > .cwrap.cc.out; 1 objects , 0 libs
Hello World
-rwxrwxr-x 1 simon simon 18208 Oct 26 15:19 c-example*
-rw-rw-r-- 1 simon simon 421 Oct 26 15:19 c-example.c
-rw-rw-r-- 1 simon simon 8848 Oct 26 15:19 c-example.cwrap.1.aux
-rw-rw-r-- 1 simon simon 5776 Oct 26 15:19 c-example.cwrap.2.c
-rw-rw-r-- 1 simon simon 23401 Oct 26 15:19 c-example.cwrap.3.s
-rw-rw-r-- 1 simon simon 23567 Oct 26 15:19 c-example.cwrap.4.s
-rw-rw-r-- 1 simon simon 3793 Oct 26 15:19 c-example.cwrap.c
-rw-rw-r-- 1 simon simon 6792 Oct 26 15:19 c-example.cwrap.o
-rw-rw-r-- 1 simon simon 390 Oct 26 15:19 c-example.cwrap.out
-rw-rw-r-- 1 simon simon 508 Oct 26 15:16 c-example.mak
-rw-rw-r-- 1 simon simon 11584 Oct 26 15:19 c-example.o
-rw-rw-r-- 1 simon simon 2176 Oct 26 15:19 c-example.o.cwrap.cc.out
T03551 0.000000s + baz() {
T03551 0.000082s - a=1
T03551 0.000108s } = 1 // baz() #1
T03551 0.000117s - b=1
T03551 0.000125s + foo() {
T03551 0.000126s + bar() {
T03551 0.000134s + baz() {} = 0 // #2
T03551 0.000149s } = 0 // bar() #1
T03551 0.000157s } = 0 // foo() #1
T03551 0.000165s + bar() {
T03551 0.000166s + baz() {} = 0 // #3
T03551 0.000181s } = 0 // bar() #2
- We currently ignore the imported system API prototypes, but in theory these can also be instrumented via
-aux-info
.
$ cat c-example.cwrap.1.aux
/* compiled from: . */
/* /usr/include/libio.h:385:NC */ extern int __underflow (_IO_FILE *);
/* /usr/include/libio.h:386:NC */ extern int __uflow (_IO_FILE *);
/* /usr/include/libio.h:387:NC */ extern int __overflow (_IO_FILE *, int);
...
/* /usr/include/stdio.h:912:NC */ extern void flockfile (FILE *);
/* /usr/include/stdio.h:916:NC */ extern int ftrylockfile (FILE *);
/* /usr/include/stdio.h:919:NC */ extern void funlockfile (FILE *);
/* c-example.c:3:NC */ extern int bar (int);
/* c-example.c:4:NC */ static int foo (int);
/* c-example.c:6:NF */ extern int baz (int a); /* (a) int a; */
/* c-example.c:7:NF */ extern int bar (int a); /* (a) int a; */
/* c-example.c:8:NF */ static int foo (int a); /* (a) int a; */
/* c-example.c:10:OF */ extern int main (void); /* () */
- Each source file gets appended with the instrumented functions.
- Comments containing
cwrap_log
are automatically 'uncommented' :-) - And the function addresses of the original function, and wrapper function are also saved, together with the address which will actually be used at run-time.
- In this example, instrumentation output is auto capped at 10,000 calls so that small, frequently called functions do not become log hogs :-)
- There's lots of features which could be added on here in the future, e.g.:
- Function parameters and return values if complicated types, e.g. by user provided helper functions?
- Ability to call a mock function for dynamic run-time testing.
- Keep statistics e.g. function call frequency and performance, and output these upon process exit.
$ cat c-example.cwrap.2.c
#include <stdio.h>
extern int bar(int a);
static int foo(int a);
int baz(int a) { if (a) cwrap_log("- a=%d", a); return a ; }
extern int bar(int a) { return baz(a); }
static int foo(int a) { return bar(a); }
int main() {
printf("Hello World\n");
int b = baz(1);
cwrap_log("- b=%d", b);
return bar(foo(0));
}
extern unsigned long long cwrap_count_call_baz;
extern int (* cwrap_indirect_orig_baz) (int a);
extern int (* cwrap_indirect_wrap_baz) (int a);
extern int (* cwrap_indirect_mock_baz) (int a);
static int cwrap_wrap_baz (int a)
{
cwrap_count_call_baz ++;
// todo: add per binary/file/function verbosity checking
// todo: show function parameters for simple types, e.g. foo=123, bar=7
if (cwrap_count_call_baz <= 10000) cwrap_log_push(1, 1, "+ baz() {");
int lines = cwrap_log_get_lines();
int cwrap_result = (* cwrap_indirect_orig_baz)(a); // call wrapped function
if (cwrap_count_call_baz <= 10000) {
if (0 == (cwrap_log_get_lines() - lines)) { cwrap_log_pop(-1, 0 /* append */, "} = %d // " "#%llu", cwrap_result, cwrap_count_call_baz); }
else { cwrap_log_pop(-1, 1 /* no append */, "} = %d // baz() #%llu", cwrap_result, cwrap_count_call_baz); }
}
// todo: show return value for simple types
return cwrap_result;
}
int (* cwrap_indirect_orig_baz) (int a) = baz;
int (* cwrap_indirect_wrap_baz) (int a) = cwrap_wrap_baz;
int (* cwrap_indirect_mock_baz) (int a) = cwrap_wrap_baz;
unsigned long long cwrap_count_call_baz = 0;
extern unsigned long long cwrap_count_call_bar;
extern int (* cwrap_indirect_orig_bar) (int a);
extern int (* cwrap_indirect_wrap_bar) (int a);
extern int (* cwrap_indirect_mock_bar) (int a);
static int cwrap_wrap_bar (int a)
{
cwrap_count_call_bar ++;
// todo: add per binary/file/function verbosity checking
// todo: show function parameters for simple types, e.g. foo=123, bar=7
if (cwrap_count_call_bar <= 10000) cwrap_log_push(1, 1, "+ bar() {");
int lines = cwrap_log_get_lines();
int cwrap_result = (* cwrap_indirect_orig_bar)(a); // call wrapped function
if (cwrap_count_call_bar <= 10000) {
if (0 == (cwrap_log_get_lines() - lines)) { cwrap_log_pop(-1, 0 /* append */, "} = %d // " "#%llu", cwrap_result, cwrap_count_call_bar); }
else { cwrap_log_pop(-1, 1 /* no append */, "} = %d // bar() #%llu", cwrap_result, cwrap_count_call_bar); }
}
// todo: show return value for simple types
return cwrap_result;
}
int (* cwrap_indirect_orig_bar) (int a) = bar;
int (* cwrap_indirect_wrap_bar) (int a) = cwrap_wrap_bar;
int (* cwrap_indirect_mock_bar) (int a) = cwrap_wrap_bar;
unsigned long long cwrap_count_call_bar = 0;
extern unsigned long long cwrap_count_call_foo_static_in_c_example_c;
extern int (* cwrap_indirect_orig_foo_static_in_c_example_c) (int a);
extern int (* cwrap_indirect_wrap_foo_static_in_c_example_c) (int a);
extern int (* cwrap_indirect_mock_foo_static_in_c_example_c) (int a);
static int cwrap_wrap_foo_static_in_c_example_c (int a)
{
cwrap_count_call_foo_static_in_c_example_c ++;
// todo: add per binary/file/function verbosity checking
// todo: show function parameters for simple types, e.g. foo=123, bar=7
if (cwrap_count_call_foo_static_in_c_example_c <= 10000) cwrap_log_push(1, 1, "+ foo() {");
int lines = cwrap_log_get_lines();
int cwrap_result = (* cwrap_indirect_orig_foo_static_in_c_example_c)(a); // call wrapped function
if (cwrap_count_call_foo_static_in_c_example_c <= 10000) {
if (0 == (cwrap_log_get_lines() - lines)) { cwrap_log_pop(-1, 0 /* append */, "} = %d // " "#%llu", cwrap_result, cwrap_count_call_foo_static_in_c_example_c); }
else { cwrap_log_pop(-1, 1 /* no append */, "} = %d // foo() #%llu", cwrap_result, cwrap_count_call_foo_static_in_c_example_c); }
}
// todo: show return value for simple types
return cwrap_result;
}
int (* cwrap_indirect_orig_foo_static_in_c_example_c) (int a) = foo;
int (* cwrap_indirect_wrap_foo_static_in_c_example_c) (int a) = cwrap_wrap_foo_static_in_c_example_c;
int (* cwrap_indirect_mock_foo_static_in_c_example_c) (int a) = cwrap_wrap_foo_static_in_c_example_c;
unsigned long long cwrap_count_call_foo_static_in_c_example_c = 0;
extern unsigned long long cwrap_count_call_main;
extern int (* cwrap_indirect_orig_main) (void);
extern int (* cwrap_indirect_wrap_main) (void);
extern int (* cwrap_indirect_mock_main) (void);
static int cwrap_wrap_main (void)
{
cwrap_count_call_main ++;
// todo: add per binary/file/function verbosity checking
// todo: show function parameters for simple types, e.g. foo=123, bar=7
if (cwrap_count_call_main <= 10000) cwrap_log_push(1, 1, "+ main() {");
int lines = cwrap_log_get_lines();
int cwrap_result = (* cwrap_indirect_orig_main)(); // call wrapped function
if (cwrap_count_call_main <= 10000) {
if (0 == (cwrap_log_get_lines() - lines)) { cwrap_log_pop(-1, 0 /* append */, "} = %d // " "#%llu", cwrap_result, cwrap_count_call_main); }
else { cwrap_log_pop(-1, 1 /* no append */, "} = %d // main() #%llu", cwrap_result, cwrap_count_call_main); }
}
// todo: show return value for simple types
return cwrap_result;
}
int (* cwrap_indirect_orig_main) (void) = main;
int (* cwrap_indirect_wrap_main) (void) = cwrap_wrap_main;
int (* cwrap_indirect_mock_main) (void) = cwrap_wrap_main;
unsigned long long cwrap_count_call_main = 0;
- The only changes in the assembly file are the type of
call
statements:
$ diff c-example.cwrap.3.s c-example.cwrap.4.s
56c56
< call baz@PLT
---
> call *cwrap_indirect_mock_baz@PLT(%rip)
78c78
< call bar@PLT
---
> call *cwrap_indirect_mock_bar@PLT(%rip)
108c108
< call baz@PLT
---
> call *cwrap_indirect_mock_baz@PLT(%rip)
118c118
< call foo
---
> call *cwrap_indirect_mock_foo_static_in_c_example_c@PLT(%rip)
120c120
< call bar@PLT
---
> call *cwrap_indirect_mock_bar@PLT(%rip)
- This file is currently a minimalist, boilerplate logging function.
- There's lots of features which could be added on here in the future, e.g.:
- High resolution time stamps.
- Multi-threaded logging to an in-memory buffer for faster output.
- Ability for developer supplied log lines to be integrated with auto entry and exit instrumentation.
$ cat c-example.cwrap.c
#define _GNU_SOURCE /* See feature_test_macros(7) */
#include <unistd.h>
#include <sys/syscall.h> /* for syscall(), SYS_gettid */
#include <stdio.h>
#include <stdarg.h>
#include <sys/time.h>
#define CWRAP_LOG_LINE_MAX (4096)
static char cwrap_log_spaces[] = " ";
static __thread char cwrap_log_line[CWRAP_LOG_LINE_MAX];
static __thread int cwrap_log_line_pos = 0;
__thread int cwrap_log_lines = 0;
static __thread int cwrap_log_indent = 0;
static FILE * cwrap_log_file = NULL;
static int cwrap_log_fd;
static double cwrap_log_time;
int cwrap_log_get_lines(void)
{
return cwrap_log_lines;
}
double cwrap_get_time_in_seconds(void)
{
struct timeval tv;
gettimeofday(&tv, NULL);
return (double)tv.tv_sec + 1.e-6 * (double)tv.tv_usec;
}
void cwrap_log_flush(void)
{
fprintf(cwrap_log_file, "%s\n", &cwrap_log_line[0]);
cwrap_log_line_pos = 0;
fflush(cwrap_log_file);
//fsync(cwrap_log_fd); // todo: fsync intermittantly?
}
void cwrap_log_push_v(int indent_direction, int no_append, const char * format, va_list argument_list)
{
double time = cwrap_get_time_in_seconds();
if (no_append && (cwrap_log_line_pos > 0)) {
cwrap_log_flush();
}
if (NULL == cwrap_log_file) { // todo: make thread safe
cwrap_log_file = fopen("c-example.cwrap.out","w+");
cwrap_log_fd = fileno(cwrap_log_file);
cwrap_log_time = time;
}
double time_elapsed = time - cwrap_log_time;
if (no_append) {
// if (cwrap_log_line_pos < CWRAP_LOG_LINE_MAX) { cwrap_log_line_pos += snprintf(&cwrap_log_line[cwrap_log_line_pos], CWRAP_LOG_LINE_MAX - cwrap_log_line_pos, "%u ", cwrap_log_lines ); }
if (cwrap_log_line_pos < CWRAP_LOG_LINE_MAX) { cwrap_log_line_pos += snprintf(&cwrap_log_line[cwrap_log_line_pos], CWRAP_LOG_LINE_MAX - cwrap_log_line_pos, "T%05ld ", syscall(SYS_gettid) ); } // linux; cat /proc/sys/kernel/pid_max
if (cwrap_log_line_pos < CWRAP_LOG_LINE_MAX) { cwrap_log_line_pos += snprintf(&cwrap_log_line[cwrap_log_line_pos], CWRAP_LOG_LINE_MAX - cwrap_log_line_pos, "%fs ", time_elapsed ); }
if (cwrap_log_line_pos < CWRAP_LOG_LINE_MAX) { cwrap_log_line_pos += snprintf(&cwrap_log_line[cwrap_log_line_pos], CWRAP_LOG_LINE_MAX - cwrap_log_line_pos, "%.*s", 2 * cwrap_log_indent, &cwrap_log_spaces[0]); } // note: % because of Perl
}
if (cwrap_log_line_pos < CWRAP_LOG_LINE_MAX) { cwrap_log_line_pos += vsnprintf(&cwrap_log_line[cwrap_log_line_pos], CWRAP_LOG_LINE_MAX - cwrap_log_line_pos, format, argument_list ); }
cwrap_log_lines ++;
cwrap_log_indent += indent_direction;
}
void cwrap_log_push(int indent_direction, int no_append, const char * format, ...)
{
va_list argument_list;
va_start(argument_list, format);
cwrap_log_push_v(indent_direction, no_append, format, argument_list);
va_end(argument_list);
}
void cwrap_log_pop(int indent_direction, int no_append, const char * format, ...)
{
va_list argument_list;
va_start(argument_list, format);
cwrap_log_push_v(indent_direction, no_append, format, argument_list);
cwrap_log_flush();
va_end(argument_list);
}
void cwrap_log(const char * format, ...)
{
va_list argument_list;
va_start(argument_list, format);
cwrap_log_push_v(0 /* no indent_direction */, 1 /* no append */, format, argument_list);
cwrap_log_flush();
va_end(argument_list);
}
- During the build then extra commands are generated, ran, and logged.
- For a 'cwrap' build then
CWRAP
is defined. - And the auto generated header file
cwrap.h
is included by force on the command line.
$ cat c-example.o.cwrap.cc.out
Sat Oct 26 15:22:40 2019 0.000246s cwrap: debug: arguments : gcc -g -c c-example.c -o c-example.o
Sat Oct 26 15:22:40 2019 0.000310s cwrap: debug: deps_file_stem :
Sat Oct 26 15:22:40 2019 0.000336s cwrap: debug: input_file :c-example.c
Sat Oct 26 15:22:40 2019 0.000358s cwrap: debug: input_file_stem :c-example
Sat Oct 26 15:22:40 2019 0.000379s cwrap: debug: input_file_ext :.c
Sat Oct 26 15:22:40 2019 0.000400s cwrap: debug: output_file :c-example.o
Sat Oct 26 15:22:40 2019 0.000420s cwrap: debug: output_file_stem:c-example
Sat Oct 26 15:22:40 2019 0.000440s cwrap: debug: output_file_ext :.o
Sat Oct 26 15:22:40 2019 0.000526s cwrap: step 1: c->aux: running: gcc -g -c c-example.c -aux-info c-example.cwrap.1.aux -o /dev/null -DCWRAP -include .cwrap.h >> c-example.o.cwrap.cc.out 2>&1
Sat Oct 26 15:22:40 2019 0.021158s cwrap: step 2: c->c : read bytes of aux file: 8848 < c-example.cwrap.1.aux
Sat Oct 26 15:22:40 2019 0.021222s cwrap: step 2: c->c : read bytes of c file: 421 < c-example.c
Sat Oct 26 15:22:40 2019 0.022171s cwrap: step 2: c->c : wrote bytes to c file: 5776 > c-example.cwrap.2.c
Sat Oct 26 15:22:40 2019 0.022197s cwrap: step 3: c->s : running: gcc -g -c c-example.cwrap.2.c -fPIC -S -o c-example.cwrap.3.s -DCWRAP -include .cwrap.h >> c-example.o.cwrap.cc.out 2>&1
Sat Oct 26 15:22:40 2019 0.045515s cwrap: step 4: ->Tpo: skipping .Tpo
Sat Oct 26 15:22:40 2019 0.046002s cwrap: step 5: s->s : read bytes of s file: 23401 bytes / 1333 lines < c-example.cwrap.3.s
Sat Oct 26 15:22:40 2019 0.046307s cwrap: step 5: s->s : unique function ignored: 1 times puts
Sat Oct 26 15:22:40 2019 0.046343s cwrap: step 5: s->s : found 6 call lines, replaced 5 calls with 3 unique functions, 1 unique functions ignored
Sat Oct 26 15:22:40 2019 0.046605s cwrap: step 5: s->s : wrote bytes to s file: 23567 > c-example.cwrap.4.s
Sat Oct 26 15:22:40 2019 0.046665s cwrap: step 6: s->o : running: as c-example.cwrap.4.s -o c-example.o >> c-example.o.cwrap.cc.out 2>&1
Sat Oct 26 15:22:40 2019 0.052439s cwrap: gcc .. > c-example.o.cwrap.cc.out; 4 functions, 0 warnings
$ cat .cwrap.h
#ifdef __cplusplus
extern "C" {
#endif
extern void cwrap_log(const char * format, ...);
extern void cwrap_log_pop(int indent_direction, int no_append, const char * format, ...);
extern void cwrap_log_push(int indent_direction, int no_append, const char * format, ...);
extern int cwrap_log_get_lines(void);
#ifdef __cplusplus
}
#endif
- Auto instrumenting C++ projects using the above technique is a little more difficult.
- Todo: Get this working!
#include <iostream>
int baz(int a) { /* if (a) cwrap_log("- a=%d", a); */ return a ; }
int bar(int a) { return baz(a); }
class Foo
{
private:
int doitPrivate(int a) { return bar(a); }
public:
Foo() {
std::cout << "constructing Foo\n";
}
int doitPublic(int a) { return doitPrivate(a); }
~Foo() {
std::cout << "deconstructing Foo\n";
}
};
int main()
{
std::cout << "Hello World\n";
int b = baz(1);
// cwrap_log("- b=%d", b);
Foo foo;
return bar(foo.doitPublic(0));
}
- Unfortunately the gcc command line parameter
-aux-info
only works with C and not C++ source code. - One possibility is to use
ctags
to discover the function prototypes, e.g.:
$ ctags -f - --format=2 --c++-kinds=+p --fields=+iaS --language-force=C c-example.c
bar c-example.c /^int bar(int a) { return baz(a); }$/;" f signature:(int a)
baz c-example.c /^int baz(int a) { return a ; }$/;" f signature:(int a)
foo c-example.c /^int foo(int a) { return bar(a); }$/;" f signature:(int a)
main c-example.c /^int main()$/;" f
$ ctags -f - --format=2 --c++-kinds=+p --fields=+iaS --language-force=C++ cpp-example.cpp
Foo cpp-example.cpp /^ Foo() {$/;" f class:Foo access:public signature:()
Foo cpp-example.cpp /^class Foo$/;" c file:
bar cpp-example.cpp /^int bar(int a) { return baz(a); }$/;" f signature:(int a)
baz cpp-example.cpp /^int baz(int a) { return a ; }$/;" f signature:(int a)
doitPrivate cpp-example.cpp /^ int doitPrivate(int a) { return bar(a); }$/;" f class:Foo file: access:private signature:(int a)
doitPublic cpp-example.cpp /^ int doitPublic(int a) { return doitPrivate(a); }$/;" f class:Foo access:public signature:(int a)
main cpp-example.cpp /^int main()$/;" f signature:()
~Foo cpp-example.cpp /^ ~Foo() {$/;" f class:Foo access:public signature:()
- Here are examples of names being mangled in C++, versus non-mangled names in C:
$ egrep "^\s+"call c-example.cwrap.3.s | egrep -i "(foo|bar|baz)"
call baz
call bar
call foo
call bar
$ egrep "^\s+"call cpp-example.cwrap.3.s | egrep -i "(foo|bar|baz)"
call _Z3bazi
call _Z3bari
call _ZN3Foo11doitPrivateEi
call _ZN3FooC1Ev
call _ZN3Foo10doitPublicEi
call _Z3bari
call _ZN3FooD1Ev