4. Working with dynamic user probes
In this lab you will experiment with probing dynamic user probes (uprobes). These are locations in userland applications and libraries that are made available on-demand by the kernel uprobe subsystem.
The uprobe is very similar to the kernel kfunc/kprobe probes that we saw previously. For example, on an x86 architecture when a uprobe is armed, the
kernel will dynamically replace the target address with an INT3 (0xCC) instruction.
The original instruction is saved into a special part of the process address space and will later be single stepped there. Eventually if the breakpoint (INT3)
is hit, the uprobe subsystem will get notified and registered callbacks will be run.
With a uprobe, any instruction in a userland application can be traced. However, with great power comes great responsibility: for every traced instruction we must dive into the kernel to execute additional code to satisfy the required tracing scripts and this adds latency into the application being traced. While every effort is made to ensure this cost is kept to a minimum it is worth keeping this in mind when tracing latency sensitive parts of applications.
NOTE: This version of the lab is specific to the Scale 22x lab which is being ran on nix based systems. This means that the path to libraries will be in the nix store (i.e, paths beginning with /nix/store). Normally you'd expect your system libraries such as libc to be in more friendly looking locations :-) .
uprobe format
The format of a uprobe is:
uprobe:<library_name>:[cpp:]<function_name>
The optional :cpp component is specific to C++ application tracing and is discussed later. An important concept with uprobes is that the probing is applied to a file (an inode to be precise) and not specifically to a process. This means that we reference paths to files when specifying a probe to be enabled (e.g. /lib64/libc.so) and if we want to restrict the probe to a particular process we need to use -p flag or a comm filter.
probe discovery
To list the probe sites that are available we simply specify a path to a given library or executable. For example, all the function sites that can be probed in libc.so:
sudo bpftrace -l 'uprobe:/nix/store/nq*2.40-36/lib/libc.so*:*'
Example output:
uprobe:/nix/store/nqb2ns2d1lahnd5ncwmn6k84qfd7vx2k-glibc-2.40-36/lib/libc.so.6:_Exit
uprobe:/nix/store/nqb2ns2d1lahnd5ncwmn6k84qfd7vx2k-glibc-2.40-36/lib/libc.so.6:_Fork
uprobe:/nix/store/nqb2ns2d1lahnd5ncwmn6k84qfd7vx2k-glibc-2.40-36/lib/libc.so.6:_IO_adjust_column
uprobe:/nix/store/nqb2ns2d1lahnd5ncwmn6k84qfd7vx2k-glibc-2.40-36/lib/libc.so.6:_IO_adjust_wcolumn
uprobe:/nix/store/nqb2ns2d1lahnd5ncwmn6k84qfd7vx2k-glibc-2.40-36/lib/libc.so.6:_IO_cleanup
...
To list probes available in a running process we can simply specify a path to the procfs executable image for that process:
pgrep dhcpcd
Example output:
1531
sudo bpftrace -l 'uprobe:/proc/1531/exe:*'
Example output:
uprobe:/proc/1531/exe:MD5Final
uprobe:/proc/1531/exe:MD5Init
uprobe:/proc/1531/exe:MD5Transform
uprobe:/proc/1531/exe:MD5Update
uprobe:/proc/1531/exe:SHA256_Final
uprobe:/proc/1531/exe:SHA256_Init
uprobe:/proc/1531/exe:SHA256_Transform
uprobe:/proc/1531/exe:SHA256_Update
...