Sometimes you want to see functions of a library, as they’re called. I know of two ways of doing this.

Let’s have a super simple test program:

#include<iostream>
#include<unistd.h>

void func1() {}
void func2() {}

int main()
{
  std::cout << "Hello world\n";
  func1();
  func2();
  func1();

  // Wait a bit for bpftrace to be able to aquire the function name.
  // Not applicable for something that doesn't exist.
  sleep(1);
}

bpftrace

Start a bpftrace in one terminal, and run the program in another.

$ sudo bpftrace -e 'uprobe:./a.out:func* { print(func); }'
Attaching 2 probes...
func1()
func2()
func1()

GDB

$ gdb a.out
[…]
(gdb) rbreak func.*
[…]
(gdb) commands
Type commands for breakpoint(s) 1-3, one per line.
End with a line saying just "end".
>silent
>bt 1
>cont
>end
(gdb) r
Starting program: […]/a.out
[Thread debugging using libthread_db enabled]
Using host libthread_db library "/lib/x86_64-linux-gnu/libthread_db.so.1".
#0  0x0000555555555215 in _GLOBAL__sub_I__Z5func1v ()
Hello world
#0  0x000055555555516d in func1() ()
#0  0x0000555555555174 in func2() ()
#0  0x000055555555516d in func1() ()
[Inferior 1 (process 424744) exited normally]
(gdb)

Which to use?

bpftrace is lower (but not zero) overhead, but in my experience sometimes just fails to trace some things. It also has a maximum number of functions it can trace.

If the function is in a library then you may need to add filtering on the pid, or you’ll get every process’s use of this library.

gdb to me seems to always just work. But it’s higher overhead.