Build1 distinct publisher3 min readUpdated
eBPF uprobes can rebuild Go traces with no SDK in the call path. The cost moves rather than disappears: goroutine correlation, per-version register layouts, and inlined symbols.
The Engineer · Build desk
Compiled by The EngineerSomething wrong?How this is made
A writeup on dev.to by Neeraj Singh argues that attaching eBPF uprobes to Go runtime symbols and to net/http and gRPC entry points is now an operationally viable way to reconstruct distributed traces with no code change in the target binary [2]. That matters because the cost it removes is real -- every SDK call site, context propagation branch and baggage extraction is code that can drift, be omitted in a hot path, or add measurable CPU at high RPS [1] -- but the same piece names three Go-specific failure modes that replace it [18].
The mechanism is old and blunt. A uprobe patches a breakpoint instruction at an offset in a running binary; when execution reaches it the kernel pauses the thread, runs the BPF program, and resumes [3]. For C or Rust that maps cleanly onto a function prologue [4]. Go does not cooperate.
First, correlation. The M:N scheduler multiplexes goroutines onto OS threads, so a request handled by goroutine G on thread M1 when the entry probe fires may be rescheduled onto M2 before the response is written [5]. A probe that keys on pthread_self() or the current PID/TID loses continuity across that yield [6]. According to the author, the correct anchor is the goroutine ID in the runtime.g struct, which means either a BTF-aware map keyed by goid pulled off the goroutine stack, or a fixed-offset computation against the g pointer held in the thread-local storage register: FS on amd64, R28 on arm64 [7]. That is not a one-time integration. It is a structural dependency on runtime internals.
Second, the ABI. Go 1.17 moved function arguments into registers -- AX, BX, CX, DI, SI, R8 through R11 on amd64 -- instead of the stack [8]. A probe written against the 1.16 convention that reads ctx from sp+8 returns garbage on 1.17 and later [9]. The stated options are shipping ABI-aware probe logic per Go minor version or resolving DWARF location expressions from the binary's debug info at each probe site [10]. Choose the second one, or you have quietly signed up for a compatibility matrix.
Third, the compiler. Go inlines small functions aggressively, so if http.(*Transport).roundTrip is partially inlined the symbol may not exist where the tracer expects [11], and probing the wrong offset yields missed spans or corrupted argument reads with no error [12]. Silent is the operative word.
The plumbing is the least interesting part: probes on HTTP and gRPC entry and exit, on runtime.newproc1 and runtime.goexit for goroutine lifecycle, and on crypto/tls handshake functions for latency attribution [13], feeding a BPF ring buffer -- preferred over perf buffers on kernels 5.8 and above for overhead and ordering [14] -- into a cilium/ebpf consumer whose event struct must match the C definition's alignment padding byte for byte, or every field after the first decodes wrong [15].
Then the hard problem: propagating context across service boundaries when you cannot inject a header in application code [16]. The option described attaches a tc hook and writes W3C traceparent bytes into the packet with bpf_skb_store_bytes, which needs CAP_NET_ADMIN and works only for cleartext HTTP/1.1, because TLS terminates above the socket layer and the program sees ciphertext [17]. So the propagation path excludes exactly the TLS-heavy services the crypto/tls probes were added to measure [19].
Watch three things before this goes into a production stack: whether your tracer resolves offsets via DWARF or hardcodes them per Go version [10], whether your kernel floor is 5.8 [14], and whether span counts on a service you have also instrumented by hand agree, since inlining misses do not raise errors [12]. The source material available here breaks off at the start of the second propagation option, so the non-packet path is unresolved.
Follow any of these and your For You feed starts watching them — no settings page required.
Ranked by verification strength, evidence, and original report placement.
Manual OpenTelemetry instrumentation in Go microservices carries a compounding cost: every SDK call site, every context propagation branch, every baggage extraction is code that can drift, be omitted in a hot path, or impose measurable CPU overhead at high RPS.
eBPF uprobes work by patching a breakpoint instruction at a specified offset in a running binary; when execution hits that offset the kernel pauses the thread, runs the attached BPF program, and resumes.
For C or Rust binaries, uprobe attachment maps cleanly onto function prologues.
Go's M:N scheduler multiplexes goroutines onto OS threads: a single HTTP request may be handled by goroutine G on thread M1 at the point an uprobe fires, then be rescheduled to M2 before the response is written.
A naive uprobe reading pthread_self() or the current PID/TID will lose continuity across a goroutine yield to another thread.
The correct correlation anchor is the goroutine ID (runtime.g struct field goid), which requires either a BTF-aware map keyed by goid extracted from the goroutine stack, or a fixed offset computation against the g pointer stored in the thread-local storage register (FS on amd64, R28 on arm64).
Evidence-backed comparisons of source perspectives and observed adoption signals. Read the methodology
Which Builder, Operator, and Investor concerns the observed source mix emphasized—not a truth score.
Evidence, demonstrated adoption, hype gap, incentives, and confidence are assessed independently, each on its own current evidence. How these are measured.
Single-source mechanism detail, no measurements
One publisher, one article. Its mechanism-level claims are specific and internally checkable (register ABI, TLS register per architecture, ring buffer kernel threshold, BPF helper names), which lifts evidence above the floor. But there is no benchmark, no independent corroboration, no reproducible test artifact, and the body is truncated before the rolling-deploy reattachment argument completes, so nothing in the cluster verifies the practical claims.
No adoption signal in cluster
The supplied source reports no release, deployment, benchmark, pricing, licensing, or usage disclosure. It references cilium/ebpf and Envoy as building blocks but gives no user, version, or production-footprint data, so adoption cannot be measured without inferring facts the material does not contain.
Mildly overstated, largely self-corrected
The framing that zero-instrumentation eBPF tracing is 'operationally viable' and that a described stack is 'production-grade' outruns the cluster's evidence, which contains no benchmark or deployment. The overstatement is small because the same article foregrounds the countervailing costs - goid correlation, per-version ABI offsets, silent inlining failures, cleartext-only packet propagation - and the headline itself concedes you are now maintaining ABI offsets.
Practitioner post, no product being sold
The source is an individual practitioner article on a developer publishing platform. It promotes no commercial product, names only open-source components (cilium/ebpf, Envoy) and an open standard (W3C traceparent), and recommends against the vendor-friendly framing by detailing failure modes. Residual incentive is the ordinary reputational and audience-growth benefit of publishing an authoritative-sounding deep dive, plus an unstated affiliation risk the cluster does not resolve.
Mechanism credible, outcomes unproven
Confidence is moderate-low. The structural claims about Go's scheduler, register ABI, inlining and BPF transport are coherent and specific enough to act on when designing a probe, so the technical core is likely sound. But with one publisher, zero adoption evidence, no measured overhead, and a truncated ending, the claim that this approach is ready for production stacks cannot be relied upon from this cluster alone.
build
Metrics live in RAM: why one observability pipeline hides three different failure modes1 distinct publisher
build
Three boxes, seven hops: a container diagram is not an incident map1 distinct publisher
product
OpenTelemetry is free; the collector fleet, the retention policy and the on-call rota are not1 distinct publisher
build
Your meter now runs on someone else's machine: signed receipts, fsync, and failing open1 distinct publisher
Distinct publishers with included, body-backed reporting in this cluster.
dev.to
1 article · August 15, 2026