Runtime SDKsC++

C++

Attach eBPF uprobes to a compiled C++ service on Linux x86-64. No library to link, no rebuild against a LiveProbe SDK.

How it works

There is no package to add to your build. A host-level agent attaches eBPF uprobes to the binary you already deployed, so your service is untouched and never links against LiveProbe.

That makes this a per-host install, not a per-service dependency. Set it up once on a machine and it covers every compiled service running there.

Two processes run per host:

ProcessRuns asJob
liveprobe-bpf-loaderrootLoads the BPF program and attaches uprobes. The only privileged part.
liveprobe-native-agentliveprobeTalks to the broker, streams evidence. No BPF privileges.
Never give the agent BPF privileges

The loader is the only process that should hold CAP_BPF or CAP_SYS_ADMIN. They talk over a root-owned Unix socket, and that split is what stops a compromised agent from loading arbitrary kernel programs.

Debug info you need

Probes are placed at source lines, so the binary has to explain itself to the agent. Two things must survive your build:

WhatWhyCheck
DWARFMaps source lines to addresses, and names local variables.readelf --sections app | grep debug_info
GNU build IDIdentifies which exact binary is running.readelf --notes app | grep 'Build ID:'

A stripped binary cannot be probed. Optimized release builds are fine — inlined code resolves to its inlined site. If you ship stripped binaries, keep the separate debug files and point symbolDirectories at them, or serve them from a debuginfod.

Setup

Linux x86-64 only, and about ten minutes on a fresh host. Every step you need is below.

1. Check the kernel

shell
uname -m                                   # x86_64
test -r /sys/kernel/btf/vmlinux && echo "BTF ok"
mountpoint -q /sys/kernel/tracing ||
  sudo mount -t tracefs tracefs /sys/kernel/tracing
sudo test -e /sys/kernel/tracing/uprobe_events && echo "uprobes ok"

Most current distro kernels pass. Containers usually do not, so run the agent on the host.

2. Build your service with debug info

Compile with -g and ask the linker for a build ID:

shell
g++ -std=c++20 -O2 -g -fno-omit-frame-pointer \
  -Wl,--build-id=sha1 main.cpp -o my-service

readelf --notes my-service | grep 'Build ID:'

--build-id is not always on by default, so pass it explicitly.

3. Install the agent

No published package yet — build both binaries from the repository on a machine matching the target host. Needs Rust 1.88+.

shell
sudo apt-get install -y --no-install-recommends \
  build-essential pkg-config clang llvm lld bpftool \
  libbpf-dev libelf-dev zlib1g-dev dwarves

make native-release
sudo make native-install

Installs both binaries to /usr/local/bin and creates /etc/liveprobe. Override with NATIVE_PREFIX or stage a package with DESTDIR.

4. Create the unprivileged account

shell
sudo useradd --system --no-create-home --shell /usr/sbin/nologin liveprobe
sudo install -d -o root -g liveprobe -m 0750 /run/liveprobe

5. Get a credential

Native agents use their own credential, not your operator key. It is shown once, and it only works for the services you list here.

shell
umask 077
curl --fail --silent --show-error \
  -H "Authorization: Bearer $LIVEPROBE_API_KEY" \
  -H "Content-Type: application/json" \
  --data '{"agentId":"native-host-1","allowedServiceIds":["my-service"],"label":"Prod host 1"}' \
  https://liveprobe.tryastrea.tech/v1/native-credentials

6. Write the config

Save this as /etc/liveprobe/native-agent.json. Use the real executable path, not a symlink or wrapper script.

json
{
  "agentId": "native-host-1",
  "brokerUrl": "https://liveprobe.tryastrea.tech",
  "loaderSocket": "/run/liveprobe/loader.sock",
  "services": [
    { "serviceId": "my-service", "language": "cpp", "executablePath": "/opt/app/bin/my-service" }
  ],
  "redactKeys": ["tenantSecret"],
  "redactValues": [],
  "symbolDirectories": ["/usr/lib/debug"],
  "debuginfodUrl": null,
  "symbolCacheDirectory": null
}

7. Start the loader, then the agent

shell
# Loader first. The trailing paths are the allowlist -- it will
# attach to nothing else, so add a service here to probe it.
sudo /usr/local/bin/liveprobe-bpf-loader \
  /run/liveprobe/loader.sock \
  "$(id -u liveprobe)" "$(id -g liveprobe)" \
  /opt/app/bin/my-service

# Then the agent, as the unprivileged account.
sudo -u liveprobe env \
  LIVEPROBE_NATIVE_CREDENTIAL="lp_native_<secret>" \
  /usr/local/bin/liveprobe-native-agent /etc/liveprobe/native-agent.json

Both are long-running host daemons, so in production run them under systemd with the loader ordered first, and load the credential from an EnvironmentFile only root can read.

8. Verify

shell
curl --fail --silent \
  -H "Authorization: Bearer $LIVEPROBE_API_KEY" \
  https://liveprobe.tryastrea.tech/v1/services | jq '.'

Your serviceId appears once the agent registers and finds a running process. Now place a probe from your MCP client and you are done.

Troubleshooting

SymptomFix
no such user: liveprobeRun step 4.
Service never appearsNo running process matches executablePath, or it differs from the loader allowlist.
Probe stays pendingNo DWARF for that line. The binary was stripped or built without -g / debug = 2.
Loader refuses a pathThat executable was not in the allowlist the loader started with.
Permission denied on the socket/run/liveprobe ownership does not match the UID/GID passed to the loader.

Native probes are read-only: capture never writes to target memory, and a probe that hits its limit detaches instead of silently re-arming.