diff --git a/e2e-tests/tests/fixtures/static_scope_program/Makefile b/e2e-tests/tests/fixtures/static_scope_program/Makefile
index dafd233f..aae20f47 100644
--- a/e2e-tests/tests/fixtures/static_scope_program/Makefile
+++ b/e2e-tests/tests/fixtures/static_scope_program/Makefile
@@ -2,15 +2,19 @@ CC ?= gcc
CFLAGS ?= -Wall -Wextra -g -O0
BINARY ?= static_scope_program
OBJ ?= $(BINARY).o
+EXTRA_OBJ = $(OBJ:.o=_other.o)
all: $(BINARY)
-$(BINARY): $(OBJ)
+$(BINARY): $(OBJ) $(EXTRA_OBJ)
$(CC) $(CFLAGS) -o $@ $^
$(OBJ): static_scope_program.c
$(CC) $(CFLAGS) -c -o $@ $<
+$(EXTRA_OBJ): other.c
+ $(CC) $(CFLAGS) -c -o $@ $<
+
clean:
rm -f *.o static_scope_program static_scope_program_clang_dwarf5
diff --git a/e2e-tests/tests/fixtures/static_scope_program/other.c b/e2e-tests/tests/fixtures/static_scope_program/other.c
new file mode 100644
index 00000000..6325418d
--- /dev/null
+++ b/e2e-tests/tests/fixtures/static_scope_program/other.c
@@ -0,0 +1,6 @@
+static struct { int other; int common; } cfg = {99, 2};
+int binding_state = 11;
+
+__attribute__((noinline)) int binding_scope_two(void) {
+ return cfg.other + cfg.common;
+}
diff --git a/e2e-tests/tests/fixtures/static_scope_program/static_scope_program.c b/e2e-tests/tests/fixtures/static_scope_program/static_scope_program.c
index 8ba88ff5..d64f56eb 100644
--- a/e2e-tests/tests/fixtures/static_scope_program/static_scope_program.c
+++ b/e2e-tests/tests/fixtures/static_scope_program/static_scope_program.c
@@ -20,9 +20,16 @@ static int bump_counters(int seed) {
return function_scope_static_counter + file_scope_static_counter + regular_local;
}
+static int binding_scope_one(void);
+static int binding_scope_unrelated(void);
+extern int binding_scope_two(void);
+extern int binding_state;
+
int main(void) {
while (1) {
int snapshot = bump_counters(2);
+ snapshot += binding_scope_one() + binding_scope_two();
+ snapshot += binding_scope_unrelated();
if (snapshot == -1) {
return 1;
}
@@ -30,3 +37,15 @@ int main(void) {
}
return 0;
}
+
+static struct { int own; int common; } cfg = {11, 1};
+
+__attribute__((noinline)) static int binding_scope_one(void) {
+ return cfg.own + cfg.common + binding_state;
+}
+
+__attribute__((noinline)) static int binding_scope_unrelated(void) {
+ static int binding_state = 999;
+ static struct { int own; int common; } cfg = {777, 778};
+ return binding_state + cfg.own + cfg.common;
+}
diff --git a/e2e-tests/tests/static_scope_execution.rs b/e2e-tests/tests/static_scope_execution.rs
index 2232e1d4..8b94771e 100644
--- a/e2e-tests/tests/static_scope_execution.rs
+++ b/e2e-tests/tests/static_scope_execution.rs
@@ -4,6 +4,62 @@ use common::{fixture_compiler_available, init, FixtureCompiler, FIXTURES};
use std::path::Path;
use std::time::Duration;
+#[tokio::test]
+async fn test_static_global_binding_uses_the_current_compilation_unit() -> anyhow::Result<()> {
+ init();
+ let binary = FIXTURES.get_test_binary("static_scope_program")?;
+ let target = spawn_static_scope_program(&binary).await?;
+ let script = r#"
+trace binding_scope_one { print "FIRST_CU:{}:{}:{}", cfg.own, cfg.common, binding_state; }
+trace binding_scope_two { print "SECOND_CU:{}:{}", cfg.other, cfg.common; }
+trace binding_scope_unrelated { print "LOCAL_SCOPE:{}:{}:{}", cfg.own, cfg.common, binding_state; }
+"#;
+ let (code, stdout, stderr) = run_ghostscope_with_script_for_target(script, 3, &target).await?;
+ target.terminate().await?;
+ assert_eq!(code, 0, "stderr={stderr} stdout={stdout}");
+ assert!(stdout.contains("FIRST_CU:11:1:11"), "{stdout}");
+ assert!(stdout.contains("SECOND_CU:99:2"), "{stdout}");
+ assert!(stdout.contains("LOCAL_SCOPE:777:778:999"), "{stdout}");
+ Ok(())
+}
+
+#[tokio::test]
+async fn test_global_binding_rejects_static_local_outside_its_scope() -> anyhow::Result<()> {
+ init();
+ let binary = FIXTURES.get_test_binary("static_scope_program")?;
+ let target = spawn_static_scope_program(&binary).await?;
+ let (code, stdout, stderr) = run_ghostscope_with_script_for_target(
+ r#"trace binding_scope_one { print "WRONG_LOCAL:{}", function_scope_static_counter; }"#,
+ 3,
+ &target,
+ )
+ .await?;
+ target.terminate().await?;
+ assert_ne!(code, 0, "stderr={stderr} stdout={stdout}");
+ assert!(!stdout.contains("WRONG_LOCAL:"), "{stdout}");
+ assert!(stderr.contains("function_scope_static_counter"), "{stderr}");
+ Ok(())
+}
+
+#[tokio::test]
+async fn test_invalid_static_field_cannot_bind_a_different_compilation_unit() -> anyhow::Result<()>
+{
+ init();
+ let binary = FIXTURES.get_test_binary("static_scope_program")?;
+ let target = spawn_static_scope_program(&binary).await?;
+ let (code, stdout, stderr) = run_ghostscope_with_script_for_target(
+ r#"trace binding_scope_one { print "WRONG_SCOPE:{}", cfg.other; }"#,
+ 3,
+ &target,
+ )
+ .await?;
+ target.terminate().await?;
+ assert_ne!(code, 0, "stderr={stderr} stdout={stdout}");
+ assert!(!stdout.contains("WRONG_SCOPE:"), "{stdout}");
+ assert!(stderr.contains("other"), "{stderr}");
+ Ok(())
+}
+
async fn run_ghostscope_with_script_for_target(
script_content: &str,
timeout_secs: u64,
diff --git a/ghostscope-compiler/src/ebpf/dwarf_bridge.rs b/ghostscope-compiler/src/ebpf/dwarf_bridge.rs
index 9a725a44..58af23cd 100644
--- a/ghostscope-compiler/src/ebpf/dwarf_bridge.rs
+++ b/ghostscope-compiler/src/ebpf/dwarf_bridge.rs
@@ -1522,7 +1522,11 @@ impl<'ctx, 'dw> EbpfContext<'ctx, 'dw> {
}
if let Some((_global_module, plan)) = analyzer
- .plan_global_access_read_plan(&prefer_module, var_name, &VariableAccessPath::default())
+ .plan_global_access_read_plan_at_address(
+ &module_address,
+ var_name,
+ &VariableAccessPath::default(),
+ )
.map_err(|err| CodeGenError::DwarfError(err.to_string()))?
{
debug!("Found DWARF global '{}' via variable read plan", var_name);
@@ -1608,7 +1612,7 @@ impl<'ctx, 'dw> EbpfContext<'ctx, 'dw> {
}
if let Some((_module_path, plan)) = analyzer
- .plan_global_access_read_plan(&prefer_module, base_name, access_path)
+ .plan_global_access_read_plan_at_address(&module_address, base_name, access_path)
.map_err(|err| CodeGenError::DwarfError(err.to_string()))?
{
debug!("Found DWARF global access '{path_text}' via variable read plan");
diff --git a/ghostscope-dwarf/src/analyzer/plan_global.rs b/ghostscope-dwarf/src/analyzer/plan_global.rs
index 99e35de3..c042f629 100644
--- a/ghostscope-dwarf/src/analyzer/plan_global.rs
+++ b/ghostscope-dwarf/src/analyzer/plan_global.rs
@@ -1,27 +1,28 @@
use super::DwarfAnalyzer;
use crate::{
core::{GlobalVariableInfo, Provenance, Result},
- semantics::{VariableAccessPath, VariableReadPlan},
+ semantics::{PcContext, VariableAccessPath, VariableReadPlan},
};
use std::path::{Path, PathBuf};
impl DwarfAnalyzer {
- pub(super) fn select_unambiguous_global_plan(
+ pub(super) fn select_unambiguous_global_binding(
base: &str,
- mut candidates: Vec<(PathBuf, VariableReadPlan)>,
- ) -> Result