diff --git a/.gitattributes b/.gitattributes index 7b70484..8bffb31 100644 --- a/.gitattributes +++ b/.gitattributes @@ -1,3 +1,4 @@ Dockerfile linguist-vendored # Exclude installer scripts from language statistics scripts/*.sh linguist-vendored +ghostscope-process/ebpf/build_sysmon_bpf.sh linguist-vendored diff --git a/docs/configuration.md b/docs/configuration.md index 57b6ac9..7c57999 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -10,6 +10,8 @@ Configuration follows this priority order (highest to lowest): 3. Configuration file (~/.ghostscope/config.toml or ./ghostscope.toml) 4. Default values +> **Note**: GhostScope typically runs under sudo to access tracing functionality. When executed as root, `$HOME` points to `/root` by default, so the user-level config at `~/.ghostscope/config.toml` will not be picked up automatically. Use `ghostscope --config /home//.ghostscope/config.toml` to load your personal configuration when running with sudo. + ## Command-Line Arguments ### Target Selection diff --git a/docs/roadmap.md b/docs/roadmap.md index ecf3932..c5c4f14 100644 --- a/docs/roadmap.md +++ b/docs/roadmap.md @@ -1,24 +1,32 @@ # GhostScope Roadmap (Milestones) -## Global variables in `-t ` mode (planned) - - Background: resolving globals requires per-module ASLR offsets computed from `/proc//maps`, while `-t` mode has no PID context. - - Direction: introduce PID discovery/subscription in the attach flow, or compute and populate offsets once a PID is known at trigger time; ensure safety and ordering. - - Until this is available, globals remain disabled in `-t` mode (see “Limitations”). +GhostScope is still evolving quickly. The milestones below are ordered from “strengthen core capabilities” to “polish experience” and finally “expand language and deployment coverage”. -## Chained array access (`a.b[idx].c`) and dynamic indices (planned) - - Support constant indices first; later extend to expression-based indices within eBPF verifier limits. +## Chained array access & dynamic indices +- First unlock constant-index access such as `a.b[idx].c`. +- Once verifier limits are well understood, roll out expression-based/dynamic indices in staged fashion. + +## Container tracing enhancements +- PID-based tracing inside containerized environments (Docker/WSL) still faces soft limitations; see [Limitations](limitations.md#9-container--wsl-limitations-for--p-pid-mode). +- Once foundational work settles, we’ll revisit improving compatibility in these isolated setups. ## Stack Unwinding - - Capture full call stacks at trace points, implemented via `.eh_frame` parsing. - - Reference: https://lesenechal.fr/en/linux/unwinding-the-stack-the-hard-way#h5.1-parsing-eh_frame-and-eh_frame_hdr-with-gimli +- Capture full call stacks at each trace point by parsing `.eh_frame`/`.eh_frame_hdr`. +- Surface the stack in the TUI with symbol/source awareness. + Reference: + +## Stability & accuracy +- Keep fixing defects, hardening error handling, and ensuring data consistency. +- Grow automated and regression coverage so the core workflows stay dependable. -## Stability and Accuracy Improvements - - As a debugging tool, continue fixing defects, improving error handling and data consistency, and raising overall reliability. +## Performance via bpftime +- Evaluate switching from kernel uprobes to userspace eBPF with [bpftime](https://github.com/eunomia-bpf/bpftime) to cut context-switch overhead. -## Performance Optimization with bpftime - - Evaluate migrating from kernel uprobe to userspace eBPF via [bpftime](https://github.com/eunomia-bpf/bpftime) to reduce context switches. +## Advanced language features +- Compiled languages: prioritize modern Rust features (async functions, trait objects, etc.). +- Interpreted languages: explore cooperation with runtimes such as Lua to surface variables/stack state. -## Advanced Language Features - - Compiled direction: prioritize Rust advanced features (async functions, trait objects, etc.) - - Interpreted direction: explore tracing support for specific interpreted languages (e.g., Lua) +## Client-server execution model +- Typical scenario: sources and debug info live in the cloud, while binaries run on test rigs or local hosts. +- Goal: keep GhostScope’s TUI on the control side and run the eBPF agent on the target machine, similar to `gdb`/`gdbserver`. +- This will come after the core functionality stabilizes to avoid diluting near-term focus. diff --git a/docs/zh/configuration.md b/docs/zh/configuration.md index cf6b7de..577a2b2 100644 --- a/docs/zh/configuration.md +++ b/docs/zh/configuration.md @@ -10,6 +10,8 @@ GhostScope 可以通过命令行参数、配置文件和环境变量进行配置 3. 配置文件(~/.ghostscope/config.toml 或 ./ghostscope.toml) 4. 默认值 +> **注意**:GhostScope 通常需要以 sudo/root 身份运行以访问跟踪能力。当以 root 运行时,默认的 `$HOME` 指向 `/root`,因此用户目录下的 `~/.ghostscope/config.toml` 不会自动被读取。请使用 `ghostscope --config /home/<用户名>/.ghostscope/config.toml` 来显式指定个人配置文件。 + ## 命令行参数 ### 目标选择 diff --git a/docs/zh/roadmap.md b/docs/zh/roadmap.md index 019a7c4..3e3be61 100644 --- a/docs/zh/roadmap.md +++ b/docs/zh/roadmap.md @@ -1,22 +1,33 @@ # GhostScope 路线图(里程碑) -## `-t ` 模式支持全局变量(计划中): - - 背景:全局变量需要结合 `/proc//maps` 计算模块偏移,`-t` 启动时尚无 PID 上下文。 - - 方向:在 attach 流程中引入 PID 发现/订阅机制,或在触发时机得到 PID 后再计算并下发偏移;同时确保安全与时序正确。 - - 在该能力落地前,`-t` 模式下全局变量保持禁用(见“使用限制”)。 +GhostScope 仍处在快速演进阶段,以下里程碑按照“优先修补基础能力 → 打磨使用体验 → 拓展更多语言与部署形态”的顺序规划。 -## 链式数组访问(`a.b[idx].c`)与动态下标(计划中): - - 先支持常量下标,后续扩展到表达式下标,在验证 eBPF 验证器可接受的范围内分阶段推出。 +## 链式数组访问与动态下标 +- 先解锁 `a.b[idx].c` 这类链式访问的常量下标能力。 +- 在验证 eBPF 验证器限制后,逐步引入表达式/动态下标,确保性能与安全达标。 -## 栈回溯(Stack Unwinding): - - 在追踪点捕获完整调用栈,基于 `.eh_frame` 解析实现。 +## 容器追踪增强 +- 容器(Docker/WSL 等)环境下的 `-p ` 模式仍存在软限制,详见[限制列表](limitations.md#9-容器-wsl-场景下--pid-pid-模式的软限制)。 +- 待核心能力稳定后,会评估如何改进在这些隔离场景中的兼容性。 -## 稳定性与准确性改进: - - 作为调试工具,持续修复缺陷、改进错误处理与数据一致性,提升可用性。 +## 栈回溯(Stack Unwinding) +- 在每个追踪点捕获完整调用栈,基于 `.eh_frame`/`.eh_frame_hdr` 信息做好解析。 +- 结合符号/源信息,TUI 中提供直观的栈帧浏览。 + 参考资料:[Unwinding the stack the hard way](https://lesenechal.fr/en/linux/unwinding-the-stack-the-hard-way#h5.1-parsing-eh_frame-and-eh_frame_hdr-with-gimli) -## 使用 bpftime 优化性能: - - 评估从内核 uprobe 迁移到用户态 eBPF(bpftime)的可行性,以降低上下文切换开销。 +## 稳定性与准确性 +- 作为调试工具,持续修复缺陷、改进错误处理,确保数据一致性。 +- 加强自动化测试与回归验证,让关键路径“可依赖”。 -## 高级语言特性: - - 编译型方向:优先支持 Rust 高级特性(async、trait objects 等) - - 解释型方向:探索对特定解释型语言(如 Lua)的追踪支持 +## 利用 bpftime 优化性能 +- 评估从内核 uprobe 迁移到用户态 eBPF([bpftime](https://github.com/eunomia-bpf/bpftime))的可行性。 +- 目标是降低上下文切换与 probe 开销,为高频追踪提供更好的采样能力。 + +## 高级语言特性 +- 编译型语言方向:优先支持 Rust 的 async/trait 对象等高级语义。 +- 解释型语言方向:探索与 Lua 等运行时协同抓取变量/栈信息的方案。 + +## Client-Server 调用模式 +- 典型场景:代码与调试信息在云端,目标二进制运行在测试机或本地。 +- 规划让 GhostScope TUI 仅运行在控制端,eBPF Agent 独立驻留在目标主机,参考 `gdb/gdbserver` 模式。 +- 待核心能力稳定后再启动该特性,以免分散目前的开发精力。 diff --git a/ghostscope-loader/src/kernel_caps.rs b/ghostscope-loader/src/kernel_caps.rs index e51b592..db5da05 100644 --- a/ghostscope-loader/src/kernel_caps.rs +++ b/ghostscope-loader/src/kernel_caps.rs @@ -4,11 +4,32 @@ use aya_obj::{ maps::PinningType, EbpfSectionKind, Map, }; -use std::sync::OnceLock; +use std::{fmt, sync::OnceLock}; use tracing::{error, info, warn}; /// Global kernel capabilities cache -static KERNEL_CAPS: OnceLock = OnceLock::new(); +static KERNEL_CAPS: OnceLock> = OnceLock::new(); + +#[derive(Debug, Clone)] +pub struct KernelCapabilityError { + message: String, +} + +impl KernelCapabilityError { + fn new(message: impl Into) -> Self { + Self { + message: message.into(), + } + } +} + +impl fmt::Display for KernelCapabilityError { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{}", self.message) + } +} + +impl std::error::Error for KernelCapabilityError {} /// Kernel eBPF capabilities detection #[derive(Debug, Clone, Copy)] @@ -21,76 +42,88 @@ pub struct KernelCapabilities { impl KernelCapabilities { /// Get global kernel capabilities (detected once on first call) - /// Panics if neither RingBuf nor PerfEventArray is supported - pub fn get() -> &'static Self { - KERNEL_CAPS.get_or_init(|| { - let supports_ringbuf = detect_ringbuf_support(); - let supports_perf_event_array = if !supports_ringbuf { - // Only check PerfEventArray if RingBuf failed - detect_perf_event_array_support() - } else { - // Assume PerfEventArray is supported if RingBuf is (5.8 > 4.3) - true - }; - - if supports_ringbuf { - info!("✓ Kernel supports RingBuf (>= 5.8)"); - } else if supports_perf_event_array { - warn!("⚠️ Kernel does not support RingBuf (< 5.8)"); - warn!("⚠️ Will use PerfEventArray as fallback"); - info!("✓ Kernel supports PerfEventArray (>= 4.3)"); - } else { - error!("❌ Kernel supports neither RingBuf nor PerfEventArray"); - error!("❌ GhostScope requires kernel >= 4.3 for eBPF event output"); - error!("❌ Current kernel appears to be older or eBPF is disabled"); - panic!( - "Unsupported kernel: Neither RingBuf nor PerfEventArray is available. \ - Please upgrade to Linux kernel >= 4.3 or enable eBPF support." - ); - } - - Self { - supports_ringbuf, - supports_perf_event_array, - } - }) + /// Returns an error if neither RingBuf nor PerfEventArray is supported + pub fn get() -> Result<&'static Self, KernelCapabilityError> { + match KERNEL_CAPS.get_or_init(detect_full_capabilities) { + Ok(capabilities) => Ok(capabilities), + Err(err) => Err(err.clone()), + } } /// Get kernel capabilities with PerfEventArray-only detection (for testing mode) /// Skips RingBuf detection and only validates PerfEventArray support - /// Panics if PerfEventArray is not supported - pub fn get_perf_only() -> &'static Self { - KERNEL_CAPS.get_or_init(|| { - info!("Testing mode: Only detecting PerfEventArray support"); - let supports_perf_event_array = detect_perf_event_array_support(); - - if !supports_perf_event_array { - error!("❌ Kernel does not support PerfEventArray"); - error!("❌ GhostScope requires kernel >= 4.3 for eBPF event output"); - panic!( - "Unsupported kernel: PerfEventArray is not available. \ - Please upgrade to Linux kernel >= 4.3 or enable eBPF support." - ); - } - - info!("✓ Kernel supports PerfEventArray (>= 4.3)"); - - Self { - supports_ringbuf: false, // Not detected in testing mode - supports_perf_event_array, - } - }) + /// Returns an error if PerfEventArray is not supported + pub fn get_perf_only() -> Result<&'static Self, KernelCapabilityError> { + match KERNEL_CAPS.get_or_init(detect_perf_only_capabilities) { + Ok(capabilities) => Ok(capabilities), + Err(err) => Err(err.clone()), + } } /// Check if RingBuf is supported (convenience method) pub fn ringbuf_supported() -> bool { - Self::get().supports_ringbuf + Self::get() + .map(|caps| caps.supports_ringbuf) + .unwrap_or(false) } /// Check if PerfEventArray is supported (convenience method) pub fn perf_event_array_supported() -> bool { - Self::get().supports_perf_event_array + Self::get() + .map(|caps| caps.supports_perf_event_array) + .unwrap_or(false) + } +} + +fn detect_full_capabilities() -> Result { + let supports_ringbuf = detect_ringbuf_support(); + let supports_perf_event_array = if !supports_ringbuf { + detect_perf_event_array_support() + } else { + true + }; + + if supports_ringbuf { + info!("✓ Kernel supports RingBuf (>= 5.8)"); + } else if supports_perf_event_array { + warn!("⚠️ Kernel does not support RingBuf (< 5.8)"); + warn!("⚠️ Will use PerfEventArray as fallback"); + info!("✓ Kernel supports PerfEventArray (>= 4.3)"); + } else { + error!("❌ Kernel supports neither RingBuf nor PerfEventArray"); + error!("❌ GhostScope requires kernel >= 4.3 for eBPF event output"); + error!("❌ Current kernel appears to be older or eBPF is disabled"); + return Err(KernelCapabilityError::new( + "Kernel lacks both RingBuf (>=5.8) and PerfEventArray (>=4.3) support. \ + Please upgrade the kernel or enable eBPF features.", + )); + } + + Ok(KernelCapabilities { + supports_ringbuf, + supports_perf_event_array, + }) +} + +fn detect_perf_only_capabilities() -> Result { + info!("Testing mode: Only detecting PerfEventArray support"); + let supports_perf_event_array = detect_perf_event_array_support(); + + if !supports_perf_event_array { + error!("❌ Kernel does not support PerfEventArray"); + error!("❌ GhostScope requires kernel >= 4.3 for eBPF event output"); + return Err(KernelCapabilityError::new( + "Kernel lacks PerfEventArray support (>=4.3 required). \ + Please upgrade the kernel or enable eBPF features.", + )); } + + info!("✓ Kernel supports PerfEventArray (>= 4.3)"); + + Ok(KernelCapabilities { + supports_ringbuf: false, + supports_perf_event_array, + }) } /// Detect RingBuf support by attempting to create a minimal map diff --git a/ghostscope-loader/src/lib.rs b/ghostscope-loader/src/lib.rs index b052faa..c6e00bc 100644 --- a/ghostscope-loader/src/lib.rs +++ b/ghostscope-loader/src/lib.rs @@ -37,7 +37,7 @@ use tracing::{debug, error, info, warn}; // Export kernel capabilities detection mod kernel_caps; -pub use kernel_caps::KernelCapabilities; +pub use kernel_caps::{KernelCapabilities, KernelCapabilityError}; // Export error types mod error; diff --git a/ghostscope/src/config/args.rs b/ghostscope/src/config/args.rs index 5d7d2f0..60d84ca 100644 --- a/ghostscope/src/config/args.rs +++ b/ghostscope/src/config/args.rs @@ -442,9 +442,11 @@ impl Args { impl ParsedArgs { /// Validate command line arguments for consistency and completeness pub fn validate(&self) -> Result<()> { - // Must have either PID or target path for meaningful operation + // Require either PID (-p) or target file (-t) if self.pid.is_none() && self.target_path.is_none() { - warn!("No target PID or target file specified - running in standalone mode"); + return Err(anyhow::anyhow!( + "No target specified. Please provide either --pid or --target ." + )); } // Target path validation diff --git a/ghostscope/src/config/settings.rs b/ghostscope/src/config/settings.rs index 102715d..7f2d6bb 100644 --- a/ghostscope/src/config/settings.rs +++ b/ghostscope/src/config/settings.rs @@ -1,5 +1,6 @@ use anyhow::Result; use serde::{Deserialize, Serialize}; +use std::env; use std::fs; use std::path::{Path, PathBuf}; use tracing::{debug, info}; @@ -709,7 +710,12 @@ impl Config { let mut paths = Vec::new(); // 1. ~/.ghostscope/config.toml (user-level config) - if let Some(home_dir) = dirs::home_dir() { + if let Some(home_dir) = env::var("HOME") + .ok() + .filter(|p| !p.is_empty()) + .map(PathBuf::from) + .or_else(dirs::home_dir) + { paths.push(home_dir.join(".ghostscope").join("config.toml")); } diff --git a/ghostscope/src/main.rs b/ghostscope/src/main.rs index 305ba71..c34b3d7 100644 --- a/ghostscope/src/main.rs +++ b/ghostscope/src/main.rs @@ -8,76 +8,14 @@ mod tracing; mod util; use anyhow::Result; -use crossterm::execute; -use crossterm::terminal::{disable_raw_mode, LeaveAlternateScreen}; -use std::io::{self, Write}; - // Use external tracing crate (not the local tracing module) use ::tracing::{info, warn}; use libc as c; -extern "C" fn cleanup_pinned_maps_on_exit() { - // Best effort: ignore errors - let _ = ghostscope_process::maps::cleanup_pinned_proc_offsets(); -} - -fn setup_panic_hook() { - // Use existing RUST_BACKTRACE setting from environment - - let original_hook = std::panic::take_hook(); - std::panic::set_hook(Box::new(move |panic_info| { - // Flush any pending output before terminal restore - let _ = io::stdout().flush(); - let _ = io::stderr().flush(); - - // Attempt to restore terminal state - let _ = disable_raw_mode(); - let _ = execute!(io::stdout(), LeaveAlternateScreen); - let _ = io::stdout().flush(); - - // Print panic information to stderr with immediate flushing - eprintln!("\n=== GHOSTSCOPE PANIC ==="); - let _ = io::stderr().flush(); - - eprintln!( - "Location: {}", - panic_info - .location() - .unwrap_or_else(|| std::panic::Location::caller()) - ); - let _ = io::stderr().flush(); - - if let Some(s) = panic_info.payload().downcast_ref::<&str>() { - eprintln!("Message: {s}"); - } else if let Some(s) = panic_info.payload().downcast_ref::() { - eprintln!("Message: {s}"); - } else { - eprintln!("Message: (no message available)"); - } - let _ = io::stderr().flush(); - - // Print backtrace if available - eprintln!("\nBacktrace:"); - let _ = io::stderr().flush(); - - let backtrace = std::backtrace::Backtrace::force_capture(); - eprintln!("{backtrace}"); - let _ = io::stderr().flush(); - - eprintln!("======================"); - eprintln!("Terminal state has been restored. You can now see this panic message."); - eprintln!("Please report this issue at: https://github.com/swananan/ghostscope/issues"); - let _ = io::stderr().flush(); - - // Call the original hook to preserve any additional panic handling - original_hook(panic_info); - })); -} - #[tokio::main] async fn main() -> Result<()> { // Setup panic hook before doing anything else - setup_panic_hook(); + crate::util::setup_panic_hook(); // Pre-clean any stale per-process pinned offsets map from a previous crashed session. // This prevents PID reuse collisions leaving an old map affecting the new instance. @@ -117,34 +55,62 @@ async fn main() -> Result<()> { // Register atexit cleanup for pinned maps (per-process path) unsafe { - c::atexit(cleanup_pinned_maps_on_exit); + c::atexit(crate::util::cleanup_pinned_maps_on_exit); } // Log which configuration file was loaded (after logging is initialized) if let Some(config_path) = &merged_config.config_file_path { info!("Configuration loaded from: {}", config_path.display()); } else { - info!("Using default configuration (no config file found)"); + let home_hint = std::env::var("HOME").unwrap_or_else(|_| "(unset)".into()); + info!( + "Using built-in defaults (no config found at {}/.ghostscope/config.toml or ./ghostscope.toml)", + home_hint + ); } + // Ensure we have the privileges needed for eBPF interaction + crate::util::ensure_privileges(); + // Detect kernel eBPF capabilities once at startup if merged_config.ebpf_config.force_perf_event_array { warn!("⚠️ TESTING MODE: force_perf_event_array=true - will use PerfEventArray"); info!("Skipping RingBuf detection, validating PerfEventArray support..."); - let kernel_caps = ghostscope_loader::KernelCapabilities::get_perf_only(); - info!("Kernel eBPF capabilities:"); - info!( - " PerfEventArray support: {}", - kernel_caps.supports_perf_event_array - ); + match ghostscope_loader::KernelCapabilities::get_perf_only() { + Ok(kernel_caps) => { + info!("Kernel eBPF capabilities:"); + info!( + " PerfEventArray support: {}", + kernel_caps.supports_perf_event_array + ); + } + Err(err) => { + eprintln!("Error: {err}"); + eprintln!("GhostScope requires Linux kernel >= 4.3 with PerfEventArray enabled."); + std::process::exit(1); + } + } } else { - let kernel_caps = ghostscope_loader::KernelCapabilities::get(); - info!("Kernel eBPF capabilities:"); - info!(" RingBuf support: {}", kernel_caps.supports_ringbuf); - - if !kernel_caps.supports_ringbuf { - warn!("⚠️ Kernel does not support RingBuf (requires >= 5.8)"); - warn!("⚠️ GhostScope will use PerfEventArray as fallback"); + match ghostscope_loader::KernelCapabilities::get() { + Ok(kernel_caps) => { + info!("Kernel eBPF capabilities:"); + info!(" RingBuf support: {}", kernel_caps.supports_ringbuf); + + if !kernel_caps.supports_ringbuf { + warn!("⚠️ Kernel does not support RingBuf (requires >= 5.8)"); + warn!("⚠️ GhostScope will use PerfEventArray as fallback"); + } + } + Err(err) => { + eprintln!("Error: {err}"); + eprintln!( + "GhostScope requires Linux kernel >= 4.3 with either RingBuf (>= 5.8) or PerfEventArray support." + ); + eprintln!( + "Hint: ensure CONFIG_BPF, CONFIG_BPF_SYSCALL and CONFIG_UPROBE_EVENTS are enabled in your kernel." + ); + std::process::exit(1); + } } } diff --git a/ghostscope/src/util.rs b/ghostscope/src/util.rs index 9ea3413..c28c7bd 100644 --- a/ghostscope/src/util.rs +++ b/ghostscope/src/util.rs @@ -1,4 +1,7 @@ use crate::core::session::GhostSession; +use crossterm::execute; +use crossterm::terminal::{disable_raw_mode, LeaveAlternateScreen}; +use std::io::{self, Write}; /// Derive a short binary path hint for compiler options and logging. /// Priority: @@ -35,3 +38,102 @@ pub fn derive_binary_path_hint(session: &GhostSession) -> Option { // Final fallback to maintain previous behavior Some("unknown".to_string()) } + +const CAP_SYS_ADMIN: i32 = 21; +const CAP_SYS_PTRACE: i32 = 19; +const CAP_BPF: i32 = 39; + +fn has_capability(mask: u64, cap: i32) -> bool { + if !(0..64).contains(&cap) { + return false; + } + let bit = 1u64 << (cap as u64); + mask & bit != 0 +} + +fn effective_capabilities() -> Option { + let status = std::fs::read_to_string("/proc/self/status").ok()?; + status + .lines() + .find_map(|line| line.strip_prefix("CapEff:\t")) + .and_then(|hex| u64::from_str_radix(hex.trim(), 16).ok()) +} + +/// Ensure the current process has the privileges required for eBPF interaction. +/// Exits with an error message if neither root nor sufficient capabilities are present. +pub fn ensure_privileges() { + let euid = unsafe { libc::geteuid() }; + if euid == 0 { + return; + } + + let has_caps = effective_capabilities().is_some_and(|mask| { + has_capability(mask, CAP_SYS_ADMIN) + || (has_capability(mask, CAP_BPF) && has_capability(mask, CAP_SYS_PTRACE)) + }); + + if has_caps { + return; + } + + eprintln!("GhostScope needs elevated privileges to load eBPF programs."); + eprintln!("Options:"); + eprintln!(" • Run with sudo: sudo ghostscope ..."); + eprintln!(" • Or grant capabilities:"); + eprintln!(" sudo setcap cap_bpf,cap_sys_ptrace,cap_sys_admin+ep $(command -v ghostscope)"); + eprintln!( + "Hint: verify that your kernel has CONFIG_BPF, CONFIG_BPF_SYSCALL, and CONFIG_UPROBE_EVENTS enabled." + ); + std::process::exit(1); +} +/// Install a panic hook that restores terminal state and prints friendly diagnostics. +pub fn setup_panic_hook() { + let original_hook = std::panic::take_hook(); + std::panic::set_hook(Box::new(move |panic_info| { + let _ = io::stdout().flush(); + let _ = io::stderr().flush(); + + let _ = disable_raw_mode(); + let _ = execute!(io::stdout(), LeaveAlternateScreen); + let _ = io::stdout().flush(); + + eprintln!("\n=== GHOSTSCOPE PANIC ==="); + let _ = io::stderr().flush(); + + eprintln!( + "Location: {}", + panic_info + .location() + .unwrap_or_else(|| std::panic::Location::caller()) + ); + let _ = io::stderr().flush(); + + if let Some(s) = panic_info.payload().downcast_ref::<&str>() { + eprintln!("Message: {s}"); + } else if let Some(s) = panic_info.payload().downcast_ref::() { + eprintln!("Message: {s}"); + } else { + eprintln!("Message: (no message available)"); + } + let _ = io::stderr().flush(); + + eprintln!("\nBacktrace:"); + let _ = io::stderr().flush(); + + let backtrace = std::backtrace::Backtrace::force_capture(); + eprintln!("{backtrace}"); + let _ = std::io::stderr().flush(); + + eprintln!("======================"); + eprintln!("Terminal state has been restored. You can now see this panic message."); + eprintln!("Please report this issue at: https://github.com/swananan/ghostscope/issues"); + let _ = std::io::stderr().flush(); + + original_hook(panic_info); + })); +} + +/// Perform cleanup of pinned maps when the process exits. +pub extern "C" fn cleanup_pinned_maps_on_exit() { + let _ = ghostscope_process::maps::cleanup_pinned_proc_offsets(); +} diff --git a/scripts/install.sh b/scripts/install.sh index 5ea6244..519de85 100755 --- a/scripts/install.sh +++ b/scripts/install.sh @@ -284,7 +284,16 @@ download_and_unpack() { local archive="$tmpdir/$archive_name" log "Downloading ${download_url}" - if ! curl -fsSL -o "$archive" "$download_url" 2>/dev/null; then + if ! curl -fSL --progress-bar -o "$archive" "$download_url"; then + log "curl progress-bar mode failed; retrying without progress display" + if ! curl -fSL -o "$archive" "$download_url"; then + echo "error: failed to download release asset from ${download_url}" >&2 + echo " hint: verify that the requested version and architecture are published" >&2 + return 1 + fi + fi + + if [[ ! -f "$archive" ]]; then echo "error: failed to download release asset from ${download_url}" >&2 echo " hint: verify that the requested version and architecture are published" >&2 return 1