Skip to content

Commit 321c836

Browse files
committed
feat: support special variables
1 parent fbc745c commit 321c836

6 files changed

Lines changed: 178 additions & 63 deletions

File tree

docs/scripting.md

Lines changed: 18 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -457,27 +457,29 @@ trace globals_program.c:32 {
457457
```
458458
```
459459
460-
### Special Variables (In Progress)
460+
### Special Variables
461461
462-
Special variables start with `$` and provide access to runtime information:
462+
Special variables start with `$` and expose runtime info from the kernel.
463463
464-
```ghostscope
465-
// Function arguments (x86_64 calling convention)
466-
$arg0, $arg1, $arg2, $arg3, $arg4, $arg5
464+
Supported now:
465+
466+
- `$pid` — current process ID (tgid), from `bpf_get_current_pid_tgid` lower 32 bits.
467+
- `$tid` — current thread ID, from `bpf_get_current_pid_tgid` upper 32 bits.
468+
- `$timestamp` — monotonic timestamp in nanoseconds, from `bpf_ktime_get_ns`.
467469
468-
// Process information
469-
$pid // Process ID
470-
$tid // Thread ID
471-
$comm // Process name
470+
All behave as integers and can be used in comparisons and arithmetic.
472471
473-
// Return value (in return probes)
474-
$retval
472+
Examples
475473
476-
// CPU registers (architecture-specific)
477-
$pc // Program counter
478-
$sp // Stack pointer
474+
```ghostscope
475+
trace sample.c:42 {
476+
if $pid == 12345 { print "match"; }
477+
print "PID:{} TID:{} TS:{}", $pid, $tid, $timestamp;
478+
}
479479
```
480480

481+
Note: Only `$pid`, `$tid` and `$timestamp` are supported at present. Additional register-related specials may be added later.
482+
481483
### Variable Lookup Order
482484

483485
When a variable is encountered in a script, GhostScope searches in this order:
@@ -510,25 +512,12 @@ trace main {
510512
}
511513
```
512514

513-
### Monitoring Function Arguments
514-
515-
```ghostscope
516-
trace calculate {
517-
print "calculate({}, {})", $arg0, $arg1;
518-
519-
if $arg0 > 1000 {
520-
print "Large input detected";
521-
bt;
522-
}
523-
}
524-
```
525-
526515
### Conditional Tracing
527516

528517
```ghostscope
529518
trace malloc {
530-
if $arg0 > 1048576 { // 1 MB
531-
print "Large allocation: {} bytes", $arg0;
519+
if size > 1048576 { // 1 MB
520+
print "Large allocation: {} bytes", size;
532521
backtrace;
533522
}
534523
}

docs/zh/scripting.md

Lines changed: 18 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -174,27 +174,29 @@ print arr[0].name;
174174
- 目前“局部变量、参数、全局变量”均已支持自动解引用(无需显式 `*ptr`,也不需要 `->`,统一使用 `.`,在安全范围内会自动加载并解引用指针值,类似于 Rust 的自动解引用)。
175175
- 数组访问:已支持顶层 `arr[常量]` 与“链尾”`a.b.c[常量]`。暂不支持:链中间索引(如 `a.b[2].c`)、动态下标(`arr[i]`)和多维数组。
176176

177-
### 特殊变量(实现中)
177+
### 特殊变量
178178

179-
特殊变量以 `$` 开头,提供运行时信息访问
179+
特殊变量以 `$` 开头,提供运行时信息访问
180180

181-
```ghostscope
182-
// 函数参数(x86_64 调用约定)
183-
$arg0, $arg1, $arg2, $arg3, $arg4, $arg5
181+
当前已支持:
182+
183+
- `$pid` — 当前进程 ID(tgid),来自 `bpf_get_current_pid_tgid` 的低 32 位。
184+
- `$tid` — 当前线程 ID(tid),来自 `bpf_get_current_pid_tgid` 的高 32 位。
185+
- `$timestamp` — 单调时间戳(纳秒),来自 `bpf_ktime_get_ns`
184186

185-
// 进程信息
186-
$pid // 进程 ID
187-
$tid // 线程 ID
188-
$comm // 进程名称
187+
以上均作为整数参与比较与计算。
189188

190-
// 返回值(在返回探针中)
191-
$retval
189+
示例
192190

193-
// CPU 寄存器(架构特定)
194-
$pc // 程序计数器
195-
$sp // 栈指针
191+
```ghostscope
192+
trace sample.c:42 {
193+
if $pid == 12345 { print "match"; }
194+
print "PID:{} TID:{} TS:{}", $pid, $tid, $timestamp;
195+
}
196196
```
197197

198+
提示:目前仅支持 `$pid``$tid``$timestamp`。后续可能按需加入“寄存器相关”的特殊变量。
199+
198200
### 变量查找顺序
199201

200202
当脚本中遇到变量时,GhostScope 按以下顺序查找:
@@ -511,25 +513,12 @@ trace main {
511513
}
512514
```
513515

514-
### 监控函数参数
515-
516-
```ghostscope
517-
trace calculate {
518-
print "calculate({}, {})", $arg0, $arg1;
519-
520-
if $arg0 > 1000 {
521-
print "检测到大输入";
522-
bt;
523-
}
524-
}
525-
```
526-
527516
### 条件追踪
528517

529518
```ghostscope
530519
trace malloc {
531-
if $arg0 > 1048576 { // 1 MB
532-
print "大内存分配: {} 字节", $arg0;
520+
if size > 1048576 { // 1 MB
521+
print "大内存分配: {} 字节", size;
533522
backtrace;
534523
}
535524
}

ghostscope-compiler/src/ebpf/expression.rs

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -543,7 +543,11 @@ impl<'ctx> EbpfContext<'ctx> {
543543
);
544544
self.compile_dwarf_expression(expr)
545545
}
546-
Expr::SpecialVar(name) => self.handle_special_variable(name),
546+
Expr::SpecialVar(name) => {
547+
// Accept both "$pid" and "pid" forms from the parser
548+
let sanitized = name.trim_start_matches('$');
549+
self.handle_special_variable(sanitized)
550+
}
547551
Expr::BuiltinCall { name, args } => match name.as_str() {
548552
"memcmp" => {
549553
if args.len() != 3 {
@@ -850,10 +854,13 @@ impl<'ctx> EbpfContext<'ctx> {
850854
let ts = self.get_current_timestamp()?;
851855
Ok(ts.into())
852856
}
853-
_ => Err(CodeGenError::NotImplemented(format!(
854-
"Special variable ${} not implemented",
855-
name
856-
))),
857+
_ => {
858+
let supported = ["$pid", "$tid", "$timestamp"].join(", ");
859+
Err(CodeGenError::NotImplemented(format!(
860+
"Unknown special variable '${}'. Supported: {}",
861+
name, supported
862+
)))
863+
}
857864
}
858865
}
859866

ghostscope/tests/complex_types_execution.rs

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -515,6 +515,44 @@ trace complex_types_program.c:25 {
515515
Ok(())
516516
}
517517

518+
#[tokio::test]
519+
async fn test_special_vars_pid_tid_timestamp_complex() -> anyhow::Result<()> {
520+
init();
521+
522+
// Build and start complex_types_program (Debug)
523+
let binary_path =
524+
FIXTURES.get_test_binary_with_opt("complex_types_program", OptimizationLevel::Debug)?;
525+
let mut prog = Command::new(&binary_path)
526+
.stdout(Stdio::null())
527+
.stderr(Stdio::null())
528+
.spawn()?;
529+
let pid = prog
530+
.id()
531+
.ok_or_else(|| anyhow::anyhow!("Failed to get PID"))?;
532+
tokio::time::sleep(Duration::from_millis(500)).await;
533+
534+
let script = format!(
535+
"trace complex_types_program.c:25 {{\n print \"PID={} TID={} TS={}\", $pid, $tid, $timestamp;\n if $pid == {} {{ print \"PID_OK\"; }}\n}}\n",
536+
"{}", "{}", "{}", pid
537+
);
538+
539+
let (exit_code, stdout, stderr) = run_ghostscope_with_script_for_pid(&script, 3, pid).await?;
540+
let _ = prog.kill().await;
541+
assert_eq!(exit_code, 0, "stderr={} stdout={}", stderr, stdout);
542+
assert!(
543+
stdout.contains("PID_OK"),
544+
"Expected PID_OK. STDOUT: {}",
545+
stdout
546+
);
547+
assert!(
548+
stdout.contains("PID=") || stdout.contains("PID:"),
549+
"Expected PID field in output. STDOUT: {}",
550+
stdout
551+
);
552+
553+
Ok(())
554+
}
555+
518556
#[tokio::test]
519557
async fn test_if_else_if_and_bare_expr_local() -> anyhow::Result<()> {
520558
init();

ghostscope/tests/globals_execution.rs

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -320,6 +320,44 @@ async fn run_ghostscope_with_script_for_target(
320320
Ok((exit_code, stdout_content, stderr_content))
321321
}
322322

323+
#[tokio::test]
324+
async fn test_special_vars_pid_tid_timestamp_globals() -> anyhow::Result<()> {
325+
init();
326+
327+
let binary_path = FIXTURES.get_test_binary("globals_program")?;
328+
let bin_dir = binary_path.parent().unwrap().to_path_buf();
329+
let mut prog = Command::new(&binary_path)
330+
.current_dir(&bin_dir)
331+
.stdout(Stdio::null())
332+
.stderr(Stdio::null())
333+
.spawn()?;
334+
let pid = prog
335+
.id()
336+
.ok_or_else(|| anyhow::anyhow!("Failed to get PID"))?;
337+
tokio::time::sleep(Duration::from_millis(500)).await;
338+
339+
let script = format!(
340+
"trace globals_program.c:32 {{\n print \"PID={} TID={} TS={}\", $pid, $tid, $timestamp;\n if $pid == {} {{ print \"PID_EQ\"; }}\n}}\n",
341+
"{}", "{}", "{}", pid
342+
);
343+
344+
let (exit_code, stdout, stderr) = run_ghostscope_with_script_for_pid(&script, 3, pid).await?;
345+
let _ = prog.kill().await;
346+
assert_eq!(exit_code, 0, "stderr={} stdout={}", stderr, stdout);
347+
assert!(
348+
stdout.contains("PID_EQ"),
349+
"Expected PID_EQ. STDOUT: {}",
350+
stdout
351+
);
352+
assert!(
353+
stdout.contains("PID=") || stdout.contains("PID:"),
354+
"Expected PID print. STDOUT: {}",
355+
stdout
356+
);
357+
358+
Ok(())
359+
}
360+
323361
#[tokio::test]
324362
async fn test_trace_address_with_target_shared_library() -> anyhow::Result<()> {
325363
// Verify address tracing works when session is started with -t <libgvars.so>

ghostscope/tests/script_execution.rs

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -174,6 +174,60 @@ pub async fn cleanup_global_test_process() -> anyhow::Result<()> {
174174
Ok(())
175175
}
176176

177+
#[tokio::test]
178+
async fn test_special_pid_in_if_condition() -> anyhow::Result<()> {
179+
init();
180+
ensure_global_cleanup_registered();
181+
182+
let opt_level = OptimizationLevel::Debug;
183+
let test_pid = get_global_test_pid_with_opt(opt_level).await?;
184+
185+
// Use $pid in an expression: it should equal the traced process PID
186+
let script_content = format!(
187+
"trace sample_program.c:16 {{\n if $pid == {} {{ print \"PID_OK\"; }} else {{ print \"PID_BAD\"; }}\n}}\n",
188+
test_pid
189+
);
190+
191+
let (exit_code, stdout, stderr) =
192+
run_ghostscope_with_script_opt(&script_content, 3, opt_level).await?;
193+
194+
assert_eq!(exit_code, 0, "stderr={}", stderr);
195+
assert!(
196+
stdout.contains("PID_OK"),
197+
"Expected PID_OK in output. STDOUT: {}",
198+
stdout
199+
);
200+
201+
Ok(())
202+
}
203+
204+
#[tokio::test]
205+
async fn test_special_tid_and_timestamp_print() -> anyhow::Result<()> {
206+
init();
207+
ensure_global_cleanup_registered();
208+
209+
let opt_level = OptimizationLevel::Debug;
210+
let _ = get_global_test_pid_with_opt(opt_level).await?;
211+
212+
// Just print them to ensure they compile, evaluate and render
213+
let script_content = r#"
214+
trace sample_program.c:16 {
215+
print "TST:{} {}", $tid, $timestamp;
216+
}
217+
"#;
218+
219+
let (exit_code, stdout, stderr) =
220+
run_ghostscope_with_script_opt(script_content, 3, opt_level).await?;
221+
222+
assert_eq!(exit_code, 0, "stderr={}", stderr);
223+
assert!(
224+
stdout.contains("TST:"),
225+
"Expected TST: with tid/timestamp in output. STDOUT: {}",
226+
stdout
227+
);
228+
Ok(())
229+
}
230+
177231
// Global cleanup registration - only runs once when the first test calls it
178232
static GLOBAL_CLEANUP_REGISTERED: Once = Once::new();
179233

0 commit comments

Comments
 (0)