For value failure and fallback messages and next steps, see value diagnostics.
This document describes conditions outside the supported operating envelope or cases that degrade explicitly. The guarantees that remain mandatory inside the supported envelope are defined in the Design Guarantees and Trust Model.
The underlying eBPF mechanism requires elevated privileges to access kernel tracing infrastructure. Root access or CAP_BPF/CAP_SYS_ADMIN capabilities are needed.
Both uprobes and GDB install breakpoint-based instrumentation in the target process. Using them together may lead to conflicts and unpredictable behavior.
GhostScope's script and compiler surface is observational: it does not provide operations that intentionally modify application-visible state, variable values, or control flow. This does not mean the target is untouched. Uprobes install trap points, and each hit synchronously adds uprobe and eBPF execution to the target thread.
Official builds and correctness testing currently support Linux x86_64 (AMD64) only because GhostScope depends on Linux eBPF and uprobes and currently implements x86_64 register, ABI, TLS, and unwind behavior. The runtime build fails on other build targets, the installer rejects non-Linux systems and other architectures, and trace setup rejects target objects that are not 64-bit little-endian x86_64 ELF files.
Primary focus is on C, which has the best end-to-end coverage. Inline C structs, arrays, unions, enums, typedefs, and qualifiers are formatted recursively from DWARF. Pointer members are shown as addresses unless the DSL or a read plan explicitly follows them; GhostScope does not automatically walk arbitrary C pointer graphs or linked data structures. Recursive inline formatting has a maximum depth of 32.
Rust has targeted semantic support for common standard-library value families. It is selected from the concrete target DWARF rather than from a promised Rust ABI. Nested adapters compose for projected wrappers, bounded sequences, and hash-table entries under configured depth and collection-width limits plus a shared byte budget. B-Tree key/value adapters do not yet compose. See Rust Value Presentation for the current type list, formatting behavior, and nesting boundary.
C++ remains primarily DWARF-layout-oriented. Simple C-like layouts work best; broader standard-library and language-specific semantics are not modeled yet.
For interpreted languages (Lua, Python, Ruby, etc.), only the interpreter itself can be traced because interpreters are typically implemented in compiled languages. Tracing script code is technically feasible but requires substantial development time. JIT language support is an even more distant goal.
In traditional non-sleepable probe paths, helpers such as bpf_probe_read_user cannot resolve user-space page faults, so reads from a target virtual address may still fail if the page is not resident or otherwise faults on access.
This is no longer an absolute eBPF limitation. GhostScope can emit sleepable uprobe.s programs when [ebpf].sleepable_uprobe = true; fixed-size user-memory reads then use bpf_copy_from_user_task() for fault-capable reads. The option is disabled by default and requires Linux 5.18+ because servicing a fault can increase the traced thread's latency. NUL-terminated string reads retain bpf_probe_read_user_str() semantics, so sleepable mode does not guarantee that every user-memory access can fault in a page.
References:
- https://lists.iovisor.org/g/iovisor-dev/topic/accessing_user_memory_and/21386221
- https://docs.kernel.org/bpf/libbpf/program_types.html
- https://man7.org/linux/man-pages/man7/bpf-helpers.7.html
Each uprobe hit synchronously runs trap handling and the generated eBPF program. Even though per-event work is bounded, probes on hot paths may significantly affect the monitored process. GhostScope does not guarantee unchanged timing or a fixed total overhead; use targeted probes and include hit rate in the performance budget.
GhostScope uses RingBuf when available and PerfEventArray as a fallback. Either transport can reject output when event generation exceeds available capacity. Generated programs increment a per-trace output-failure counter, and the CLI and TUI periodically report the interval and cumulative loss totals.
This provides loss visibility, not a lossless guarantee. The counters cover failures at the eBPF output helper; they cannot identify which events were lost or prove that no loss occurred outside that measurement point. Treat any nonzero loss report as an incomplete observation interval and avoid placing large or numerous probes on high-frequency paths.
Primarily tested and validated with DWARF 5 format. Theoretically supports DWARF 2-5, but other versions may have compatibility issues. Some DWARF expression instructions are not yet supported for conversion to eBPF (purely due to implementation not being completed yet) and will provide clear error messages when encountered.
GhostScope recognizes DW_OP_form_tls_address, but the runtime TLS address resolver currently handles only x86_64 executable static TLS. GhostScope resolves the current thread's TLS base at probe time, so a trace running on different pthreads reads each thread's own TLS instance for that supported executable case. The same DWARF operation is also used for dynamic/shared-library TLS; those cases require DTV/module TLS lookup and are not modeled yet, so GhostScope rejects shared-object TLS instead of guessing an address.
GhostScope strictly checks any available .gnu_debuglink CRC and comparable Build ID for separate debug files by default. Enabling --allow-loose-debug-match permits a mismatch with a warning and weakens the source-semantic evidence guarantee. An explicitly supplied debug file with neither an available CRC nor a comparable Build ID is also accepted with a warning, but its identity is then a user-supplied trust assumption rather than a verified match.
bt uses DWARF CFI only. GhostScope does not fall back to kernel stack helpers or frame-pointer walking, and it reports an explicit stop status when CFI is unavailable, not supported by the compact eBPF fast path, or a user-stack memory read fails. Cross-module frames can be matched to module offsets from their raw IPs when the process module map is available. For newly mapped modules referenced by observed frames, GhostScope loads only compact CFI, one module at a time in the background; it does not build or retain full debug/source indexes for those modules. Work is bounded by backtrace_runtime_modules_max, backtrace_runtime_module_timeout_ms, and backtrace_unwind_rows_max_entries, while current events continue with available symbols, module offsets, or raw addresses. A trace event that fires before the map-change refresh reaches userspace can still stop at the newly loaded module until a later event observes the appended rows. Deep DWARF unwinding is split through an eBPF tail-call step program so the default backtrace_depth = 128 avoids LLVM branch-distance and verifier-size limits; status=truncated means the configured depth or the tail-call unwind budget was reached before a natural stop.
Runtime mode also affects bt coverage. -p <pid> is a process-level view, so GhostScope loads the modules already mapped in that PID's /proc/<pid>/maps; cross-module unwinding and symbolization are usually best in this mode. Standalone -t <path> is a target-file, multi-process trace view that primarily guarantees probes and variables in the target module. Once the call stack leaves that module, backtrace quality is best-effort and depends on runtime module mappings, maintained proc_module_offsets, and compact DWARF CFI being available for the other modules. If you need both target-module scoping and a fuller single-process backtrace, prefer -t <path> -p <pid>.
Compiler optimizations (-O2, -O3) can cause variables to be optimized away or generate complex DWARF expressions. GhostScope will attempt to parse them, including inline function support, but some variables may be inaccessible (shown as OptimizedOut) because the compiler optimized them away.
GhostScope scans /proc/PID/maps at startup, and runtime map-change monitoring refreshes module mappings for -p <pid> and standalone -t <path> runs while sysmon is enabled. For bt/backtrace, a later event can request compact CFI for the specific dlopen module it reached; GhostScope neither eagerly indexes every new mapping nor retains full runtime-module DWARF. This remains subject to the configured runtime module count/time limits, backtrace_unwind_rows_max_entries, and the map-change race noted above. Set backtrace_runtime_modules = false or pass --no-backtrace-runtime-modules to keep module-offset/raw-address degradation without automatic CFI loading.
-p <pid> has one startup edge case: trace setup snapshots the PID's
current /proc/<pid>/maps before resolving function-name targets. If
GhostScope attaches immediately after launching a process and the dynamic
loader has not mapped a shared library yet, a trace whose target function
lives in that library can fail setup before runtime map-change monitoring has
anything to refresh. This mainly affects launch-and-immediately-attach
workflows; attaching to an already-running process, waiting until the library
appears in /proc/<pid>/maps, or using -t <path> -p <pid> for a known
library target avoids the race.
This runtime refresh does not automatically create new trace probes or make print/global-variable targets available for a library that was unknown when the script was compiled and attached. Those targets still depend on the target module and debug information being available during trace setup.
- Executable targets: When
-tpoints to an executable (-t /path/to/app), GhostScope treats that binary as the primary module and globals are supported by default. - Shared-library targets (existing processes): If GhostScope starts after the library has already been mapped (e.g., tracing a running process that loaded
libfoo.soearlier), globals work without extra steps. - Shared-library targets (new or later-mapped processes): Standalone
-tstarts sysmon by default so globals can be resolved for later-started processes and for processes that map the target library later throughdlopen. This incurs extra system-wide work, so expect higher overhead on hosts with frequent process churn or frequent memory-map changes; setenable_sysmon_for_target = falsein config to disable it. - Target-scoped PID runs (
-t ... -p ...):-tchooses the module used for function/source/address target resolution, while-psupplies the concrete process mappings, PID filter, and watched-PID module refresh. Target-mode lifecycle sysmon is not used.
In -t mode, globals depend on proc_module_offsets, which tracks runtime address offsets by (pid, module). Standalone -t maintains target-module offsets for multiple PIDs, so it is suitable for observing globals in that module; it is not a complete process view, and bt after leaving the target module is not equivalent to -p. Use -p <pid> when you need the full module context for one process. Use -t <path> -p <pid> when you need target-module trace resolution while keeping that process context.
- See Container Environments for the full explanation of container / WSL scenarios, PID namespace terminology, the scenario matrix, and current implementation limits.
- See PID namespaces manual, WSL issue #12408, and WSL issue #12115 for background.
A trace event is not a process-wide atomic snapshot. Register values and the current thread's frame correspond to the uprobe hit, but other threads can modify shared memory while GhostScope reads multiple fields. Values collected in one event can therefore represent a short observation interval rather than one globally synchronized instant.