Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 11 additions & 2 deletions docs/input-commands.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ t <target> [index] # Short form
**Parameters:**
- `<target>`: Can be:
- Function name: `function_name`
- Function-name prefix wildcard: `function_prefix*` (one trailing `*` only)
- File and line: `file:line` where file can be:
- Full path: `/path/to/file.c:42`
- Relative path: `src/file.c:42`
Expand All @@ -34,6 +35,7 @@ t <target> [index] # Short form
- `-t <binary> -p <pid>`: defaults to `<binary>`; `-p` only limits events to that PID
- This is not a raw ELF file offset or a runtime ASLR-adjusted address from `/proc/<pid>/maps`.
- `module_suffix:0xADDR`: Address in a specific module. The module part supports full path or unique suffix matching; ambiguous suffixes will be reported with candidates. In `-t -p` sessions, the module must match the `-t` target.
- `[index]`: Optional 1-based concrete-address selector for non-wildcard targets. Wildcard targets always trace every match and do not accept an index.

When `-t` and `-p` are both present, function, source-line, and address trace targets are all resolved inside the `-t` module. `-p` only limits runtime events to that process.

Expand All @@ -42,6 +44,7 @@ When `-t` and `-p` are both present, function, source-line, and address trace ta
trace main # Trace main function
trace main 2 # Trace only the 2nd address of 'main' (see 'info function main')
trace calculate_something # Trace specific function
trace get_* # Trace every function whose name starts with get_
trace /home/user/src/sample.c:42 # Full path
trace /home/user/src/sample.c:42 1 # Trace only the 1st address for that line
trace src/sample.c:42 # Relative path
Expand Down Expand Up @@ -102,6 +105,12 @@ trace main # Your previous script is restored!
- Inline: selects the inline instance start (low_pc semantics of the inline DIE); this is an entry-like point and may not align to a statement boundary. Use a line target if you need precise statement alignment.
- Multiple inline instances: if a function is inlined at multiple call sites, you’ll see one address per instance (plus one for the non-inline definition, if present).

- Function-name wildcard target (`trace <function_prefix*> { ... }`):
- Performs literal prefix matching; only one trailing `*` is supported, so it is not a general glob or regular expression.
- Expands to concrete function addresses in stable function-name/module/address order and deduplicates aliases at the same module address.
- A wildcard is limited to 64 concrete addresses. Use a narrower prefix or an exact function name if it exceeds the limit.
- PID-only mode searches all modules known at setup. With `-t`, matching is restricted to that target module.

- Source line target (`trace <file:line> { ... }`):
- Resolves to the statement boundary on that line for each occurrence (one per inline instance across callers), i.e., statement-level semantics.
- Typically yields one address per instance where that source line is active.
Expand Down Expand Up @@ -181,7 +190,7 @@ s t [file] # Short form

**Behavior:**
- Saves each trace as a `trace <target> { ... }` block with metadata.
- If a trace was created with `trace <target> [index]`, the selected address index is preserved in the save file and restored on load.
- If a non-wildcard trace was created with `trace <target> [index]`, the selected address index is preserved in the save file and restored on load.

**Examples:**
```
Expand Down Expand Up @@ -279,7 +288,7 @@ s <file> # Short form (but not "s t")

**Behavior:**
- Loads all `trace <target> { ... }` blocks in the file.
- If a block includes an address index (saved from a prior session), only that indexed address is reattached for the target.
- If a non-wildcard block includes an address index (saved from a prior session), only that indexed address is reattached for the target.

**Examples:**
```
Expand Down
25 changes: 24 additions & 1 deletion docs/scripting.md
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,7 @@ trace <pattern> {
### Trace Patterns

#### Function Name

```ghostscope
trace main {
print "Main called";
Expand All @@ -80,7 +81,28 @@ trace calculate_something {
}
```

#### Function-Name Prefix Wildcard

```ghostscope
trace get_* {
print "A get_ function was called";
}
```

The wildcard syntax is a literal function-name prefix followed by one trailing
`*`. For example, `get_*` matches `get_random_value` and
`get_string_length`; it is not a general glob or regular expression. GhostScope
creates one probe for every concrete resolved address, ordered by function
name, module path, and address. Aliases at the same module address are attached
only once.

A wildcard can expand to at most 64 concrete addresses. Narrow the prefix or
use an exact function name if it exceeds that safety limit. In PID-only mode,
matching covers all modules known when tracing starts; add `-t <binary>` to
scope a broad prefix to one module.

#### Source Line

```ghostscope
// Trace a specific file and line
trace sample.c:42 {
Expand All @@ -94,6 +116,7 @@ trace /home/user/project/src/utils.c:100 {
```

#### Address

```ghostscope
// Module-relative virtual address (DWARF/symbol PC)
trace 0x401234 {
Expand All @@ -107,7 +130,7 @@ trace libc.so.6:0x1234 {
```

Notes:
- When `-t` and `-p` are both present, all trace patterns (function, source line, bare address, and module-qualified address) are resolved inside the `-t` target. `-p` only limits runtime events to that process.
- When `-t` and `-p` are both present, all trace patterns (function, function-name wildcard, source line, bare address, and module-qualified address) are resolved inside the `-t` target. `-p` only limits runtime events to that process.
- For `0xADDR`, the default module depends on startup mode: `-t <binary>` uses `<binary>`; `-p <pid>` uses the main executable. If `-t` and `-p` are both present, `-t` wins and `-p` only limits runtime events to that PID.
- `module_suffix:0xADDR` allows selecting a module by full path or unique suffix; ambiguous suffixes will list candidates. In `-t -p` sessions, the module must match the `-t` target.
- Address trace targets always use the module's DWARF/symbol virtual address. Do not pass a raw ELF file offset or a runtime ASLR-adjusted address from `/proc/<pid>/maps`; GhostScope converts the virtual address to the uprobe file offset internally.
Expand Down
13 changes: 11 additions & 2 deletions docs/zh/input-commands.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ t <target> [index] # 缩写形式
**参数:**
- `<target>`: 可以是:
- 函数名:`function_name`
- 函数名前缀通配符:`function_prefix*`(只支持一个末尾 `*`)
- 文件和行号:`file:line`,其中文件可以是:
- 完整路径:`/path/to/file.c:42`
- 相对路径:`src/file.c:42`
Expand All @@ -32,6 +33,7 @@ t <target> [index] # 缩写形式
- `-t <binary> -p <pid>`:默认模块为 `<binary>`;`-p` 只把事件限制到该 PID
- 这里不是原始 ELF 文件偏移,也不是 `/proc/<pid>/maps` 中 ASLR 后的运行时地址。
- `模块后缀:0xADDR`:指定模块 + 地址。模块部分支持“全路径”或“唯一后缀”匹配;若后缀不唯一,会报歧义并列出候选。在 `-t -p` 会话中,该模块必须匹配 `-t` 目标。
- `[index]`:非通配符目标可选的具体地址序号,从 1 开始。通配符目标始终追踪全部匹配项,不接受序号。

当 `-t` 和 `-p` 同时出现时,函数、源码行和地址 trace 目标都在 `-t` 模块内解析。`-p` 只把运行时事件限制到该进程。

Expand All @@ -40,6 +42,7 @@ t <target> [index] # 缩写形式
trace main # 追踪 main 函数
trace main 2 # 只追踪 main 的第 2 个地址(参考 info function main 的编号)
trace calculate_something # 追踪特定函数
trace get_* # 追踪所有名称以 get_ 开头的函数
trace /home/user/src/sample.c:42 # 完整路径
trace /home/user/src/sample.c:42 1 # 只追踪该行的第 1 个地址
trace src/sample.c:42 # 相对路径
Expand Down Expand Up @@ -100,6 +103,12 @@ trace main # 你之前的脚本会被恢复!
- 内联:选择“内联实例的起始位置”(inline DIE 的 low_pc 语义),这是“入口类”的落点,未必对齐语句边界;若需要精确落在语句起始,建议使用行号目标。
- 多个内联实例:若函数在多个调用点被内联,会为每个内联实例各返回一个地址(若另有非内联定义,另加 1 个)。

- 函数名通配符目标(`trace <function_prefix*> { ... }`):
- 按字面前缀匹配;只支持一个末尾 `*`,不是通用 glob 或正则表达式。
- 按函数名、模块路径、地址的稳定顺序展开为具体函数地址,并对同一模块地址的别名去重。
- 一个通配符最多展开 64 个具体地址。超过上限时请缩小前缀或使用完整函数名。
- 仅使用 `-p` 时会搜索 setup 阶段已知的所有模块;使用 `-t` 时,匹配范围仅限该目标模块。

- 源码行号目标(`trace <file:line> { ... }`):
- 对应“语句级”语义:解析到该行的语句边界(is_stmt)并在每个出现处各给一个地址(每个内联实例各一处)。
- 通常会在所有出现该行的实例处各有 1 个地址。
Expand Down Expand Up @@ -179,7 +188,7 @@ s t [file] # 缩写形式

**行为:**
- 以 `trace <target> { ... }` 区块形式保存每条追踪及元信息。
- 若追踪是通过 `trace <target> [index]` 创建的,会将所选“地址序号”一并保存,并在加载时恢复该序号。
- 若非通配符追踪是通过 `trace <target> [index]` 创建的,会将所选“地址序号”一并保存,并在加载时恢复该序号。

**示例:**
```
Expand Down Expand Up @@ -277,7 +286,7 @@ s <file> # 缩写形式(但不包括 "s t")

**行为:**
- 加载文件中的所有 `trace <target> { ... }` 区块。
- 若区块内包含保存的“地址序号”,则仅为该目标重新挂载对应序号的地址。
- 若非通配符区块内包含保存的“地址序号”,则仅为该目标重新挂载对应序号的地址。

**示例:**
```
Expand Down
22 changes: 21 additions & 1 deletion docs/zh/scripting.md
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,7 @@ trace <模式> {
### 追踪模式

#### 函数名

```ghostscope
trace main {
print "Main 函数被调用";
Expand All @@ -74,7 +75,25 @@ trace calculate_something {
}
```

#### 函数名前缀通配符

```ghostscope
trace get_* {
print "命中了 get_ 前缀函数";
}
```

通配符语法是字面函数名前缀加一个末尾 `*`。例如,`get_*` 会匹配
`get_random_value` 和 `get_string_length`;它不是通用 glob 或正则表达式。
GhostScope 会为每个解析出的具体地址创建一个探针,顺序固定为函数名、模块路径、
地址;同一模块地址的别名只会 attach 一次。

一个通配符最多展开为 64 个具体地址;超过安全上限时请缩小前缀或使用完整函数名。
仅使用 `-p` 时会匹配开始追踪时已知的所有模块;可添加 `-t <binary>`,把较宽的
前缀限定到一个模块。

#### 源代码行

```ghostscope
// 追踪特定文件和行号
trace sample.c:42 {
Expand All @@ -88,6 +107,7 @@ trace /home/user/project/src/utils.c:100 {
```

#### 地址

```ghostscope
// 按模块相对虚拟地址(DWARF/符号表 PC)追踪
trace 0x401234 {
Expand All @@ -101,7 +121,7 @@ trace libc.so.6:0x1234 {
```

说明:
- 当 `-t` 和 `-p` 同时出现时,所有 trace pattern(函数、源码行、裸地址、模块限定地址)都在 `-t` 目标内解析。`-p` 只把运行时事件限制到该进程。
- 当 `-t` 和 `-p` 同时出现时,所有 trace pattern(函数、函数名通配符、源码行、裸地址、模块限定地址)都在 `-t` 目标内解析。`-p` 只把运行时事件限制到该进程。
- `0xADDR` 的默认模块取决于启动模式:`-t <binary>` 使用 `<binary>`;`-p <pid>` 使用主可执行文件。如果 `-t` 和 `-p` 同时出现,目标解析以 `-t` 为准,`-p` 只把运行时事件限制到该 PID。
- `模块后缀:0xADDR` 可通过“全路径”或“唯一后缀”选中模块;若后缀不唯一,会提示候选项。在 `-t -p` 会话中,该模块必须匹配 `-t` 目标。
- 地址 trace 目标始终使用该模块的 DWARF/符号表虚拟地址。不要填写原始 ELF 文件偏移,也不要填写 `/proc/<pid>/maps` 中 ASLR 后的运行时地址;GhostScope 会在内部把虚拟地址转换为 uprobe 需要的文件偏移。
Expand Down
122 changes: 122 additions & 0 deletions e2e-tests/tests/script_execution.rs
Original file line number Diff line number Diff line change
Expand Up @@ -517,6 +517,128 @@ async fn run_ghostscope_with_script(
run_ghostscope_with_script_opt(script_content, timeout_secs, OptimizationLevel::Debug).await
}

#[tokio::test]
async fn test_wildcard_function_tracing_compiles_prefix() -> anyhow::Result<()> {
init();

let binary_path = FIXTURES.get_test_binary("sample_program")?;
let analyzer = ghostscope_dwarf::DwarfAnalyzer::from_exec_path(&binary_path)
.await
.map_err(|e| anyhow::anyhow!("failed to load DWARF for sample_program: {e}"))?;
let script_content = r#"
trace get_* {
print "WILDCARD_COMPILE";
}
"#;
let binary_path_string = binary_path.to_string_lossy().into_owned();
let compile_options = ghostscope_compiler::CompileOptions {
binary_path_hint: Some(binary_path_string.clone()),
target_binary_path: Some(binary_path_string),
..Default::default()
};
let result = ghostscope_compiler::compile_script(
script_content,
&analyzer,
None,
Some(1),
&compile_options,
)
.map_err(|e| anyhow::anyhow!("wildcard compile_script failed: {e}"))?;

let function_names: std::collections::HashSet<_> = result
.uprobe_configs
.iter()
.filter_map(|config| config.function_name.as_deref())
.collect();
assert!(function_names.contains("get_random_value"));
assert!(function_names.contains("get_string_length"));
assert!(function_names.iter().all(|name| name.starts_with("get_")));
assert!(result.uprobe_configs.iter().all(|config| matches!(
&config.trace_pattern,
ghostscope_compiler::script::TracePattern::Wildcard(pattern) if pattern == "get_*"
)));
assert_eq!(
result
.uprobe_configs
.iter()
.filter_map(|config| config.resolved_address_index)
.collect::<Vec<_>>(),
(1..=result.uprobe_configs.len()).collect::<Vec<_>>()
);

let indexed_options = ghostscope_compiler::CompileOptions {
selected_index: Some(2),
..compile_options.clone()
};
let indexed_error = ghostscope_compiler::compile_script(
script_content,
&analyzer,
None,
Some(1),
&indexed_options,
)
.expect_err("an indexed wildcard must be rejected");
let indexed_message = indexed_error.user_message();
assert!(indexed_message.contains("does not support an address index"));
assert!(indexed_message.contains("exact function name"));

let error = ghostscope_compiler::compile_script(
r#"trace wildcard_target_that_does_not_exist_* { print "UNREACHABLE"; }"#,
&analyzer,
None,
Some(1),
&compile_options,
)
.expect_err("a wildcard without matching functions must fail cleanly");
let message = error.user_message();
assert!(message.contains("wildcard_target_that_does_not_exist_*"));
assert!(message.contains("No addresses resolved for wildcard"));
Ok(())
}

#[tokio::test]
async fn test_wildcard_function_tracing_expands_prefix() -> anyhow::Result<()> {
init();
ensure_global_cleanup_registered();

let script_content = r#"
trace get_* {
print "WILDCARD_PC={:p}", cast($pc, "unsigned char *");
}
"#;
let target = get_global_test_target_with_opt(OptimizationLevel::Debug).await?;
let binary_path = FIXTURES.get_test_binary("sample_program")?;
let (exit_code, stdout, stderr) = common::runner::GhostscopeRunner::new()
.with_script(script_content)
.with_target(binary_path)
.attach_to(&target)
.timeout_secs(6)
.enable_sysmon_for_target(false)
.run()
.await?;
assert_eq!(
exit_code, 0,
"wildcard tracing failed: stderr={stderr}\nstdout={stdout}"
);

let pcs: std::collections::HashSet<String> = stdout
.lines()
.filter_map(|line| line.split_once("WILDCARD_PC=").map(|(_, value)| value))
.map(|value| {
value
.chars()
.take_while(|ch| ch.is_ascii_hexdigit() || *ch == 'x')
.collect::<String>()
})
.filter(|pc| !pc.is_empty())
.collect();
assert!(
pcs.len() >= 2,
"expected events from at least two get_* functions, got {pcs:?}; stdout={stdout}"
);
Ok(())
}

#[tokio::test]
async fn test_capture_len_uses_scalar_script_var_from_dwarf_expr() -> anyhow::Result<()> {
init();
Expand Down
6 changes: 4 additions & 2 deletions ghostscope-compiler/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -204,8 +204,10 @@ pub struct CompileOptions {
pub max_trace_event_size: u32,
/// Max DWARF-unwound frames captured by each `bt`/`backtrace` instruction.
pub backtrace_depth: u8,
/// Optional single-address filter: if set, only the Nth (1-based) address
/// resolved for a target will be compiled. When None, compile all.
/// Optional single-address filter for non-wildcard targets: if set, only
/// the Nth (1-based) resolved address will be compiled. Wildcard targets
/// intentionally reject this option because their expansion has no
/// user-visible address index. When None, compile all.
pub selected_index: Option<usize>,
/// Optional PID filter strategy override.
/// When None, compiler falls back to HostTgid using compile_script(pid).
Expand Down
Loading
Loading