The Linux perf tool can be used to (among many other things!) instrument user-space code. Dynamic probes can be placed in arbitrary locations, but in my usage, I almost always place them at function entry and exit points. Since perf comes from the Linux kernel, it supports C well. But sometimes I need to deal with C++, and perf's incomplete support is annoying. Today I figured out how to make it sorta work, so I'm writing it up here.

For the record, I'm using perf 4.19.37 from linux-base=4.6 on Debian, on amd64.

Let's say I have this not-very-interesting C++ program in tst.cc:

#include <stdio.h>
namespace N
{
    void f(int x)
    {
        printf("%d\n", x);
    }
}

int main(int argc, char* argv[])
{
    N::f(argc);
    return 0;
}

It just calls the C++ function N::f(). I build this:

$ g++ -o tst tst.cc

And I ask perf about what functions are instrument-able:

$ perf probe -x tst --funcs

N::f
completed.7326
data_start
deregister_tm_clones
frame_dummy
main
printf@plt
register_tm_clones

Here perf says it can see N::f (it demangled the name, even), but if I try to add a probe there, it barfs:

$ sudo perf probe -x tst --add N::f

Semantic error :There is non-digit char in line number.

The reason is that perf's probe syntax uses the : character for line numbers, and this conflicts with the C++ scope syntax. perf could infer that a :: is not a line number, but nobody has written that yet. I generally avoid C++, so I'm going to stop at this post.

So how do we add a probe? Since perf can't handle mangled names, we can ask it to skip the demangling:

$ perf probe -x tst --funcs --no-demangle

completed.7326
data_start
deregister_tm_clones
frame_dummy
main
printf@plt
register_tm_clones

But now my function is gone! Apparently there's a function filter that by default throws out all functions that start with _. Let's disabled that filter:

$ perf probe -x tst --funcs --no-demangle --filter '*'

_DYNAMIC
_GLOBAL_OFFSET_TABLE_
_IO_stdin_used
_ZN1N1fEi
__FRAME_END__
__TMC_END__
__data_start
__do_global_dtors_aux
__do_global_dtors_aux_fini_array_entry
__dso_handle
__frame_dummy_init_array_entry
__libc_csu_fini
__libc_csu_init
_edata
_fini
_init
_start
completed.7326
data_start
deregister_tm_clones
frame_dummy
main
printf@plt
register_tm_clones

Aha. There's my function: _ZN1N1fEi. Can I add a probe there?

# perf probe -x tst --add _ZN1N1fEi

Failed to find symbol _ZN1N1fEi in /tmp/tst
  Error: Failed to add events.

Nope. Apparently I need to explicitly tell it that I'm dealing with unmangled symbols:

# perf probe -x tst --add _ZN1N1fEi --no-demangle

Added new event:
  probe_tst:_ZN1N1fEi  (on _ZN1N1fEi in /tmp/tst)

You can now use it in all perf tools, such as:

        perf record -e probe_tst:_ZN1N1fEi -aR sleep 1

There it goes! Now let's add another probe, at the function exit:

# sudo perf probe -x tst --add _ZN1N1fEi_ret=_ZN1N1fEi%return --no-demangle 

Added new event:
  probe_tst:_ZN1N1fEi_ret__return (on _ZN1N1fEi%return in /tmp/tst)

You can now use it in all perf tools, such as:

        perf record -e probe_tst:_ZN1N1fEi_ret__return -aR sleep 1

And now I should be able to run the instrumented program, and to see all the crossings of my probes:

# perf record -eprobe_tst:_ZN1N1fEi{,_ret__return} ./tst                  

1
[ perf record: Woken up 1 times to write data ]
[ perf record: Captured and wrote 0.017 MB perf.data (2 samples) ]


# perf script

             tst  6216 [001] 834490.086097:             probe_tst:_ZN1N1fEi: (559833acb135)
             tst  6216 [001] 834490.086196: probe_tst:_ZN1N1fEi_ret__return: (559833acb135 <- 559833acb172)

Sweet! Again, note that this is perf version 4.19.37, and other versions may behave differently.