diff --git a/rust-crates/symblib-capi/Cargo.toml b/rust-crates/symblib-capi/Cargo.toml index 60f098db1..0ab380bb5 100644 --- a/rust-crates/symblib-capi/Cargo.toml +++ b/rust-crates/symblib-capi/Cargo.toml @@ -3,6 +3,7 @@ name = "symblib-capi" edition = "2021" version.workspace = true rust-version.workspace = true +license.workspace = true [lib] crate-type = ["staticlib", "cdylib"] diff --git a/rust-crates/symblib/Cargo.toml b/rust-crates/symblib/Cargo.toml index d17664b5b..de49ef93f 100644 --- a/rust-crates/symblib/Cargo.toml +++ b/rust-crates/symblib/Cargo.toml @@ -3,6 +3,7 @@ name = "symblib" edition = "2021" version.workspace = true rust-version.workspace = true +license.workspace = true [dependencies] base64.workspace = true diff --git a/rust-crates/symblib/src/gosym/mod.rs b/rust-crates/symblib/src/gosym/mod.rs index 860860e81..6f8394a4d 100644 --- a/rust-crates/symblib/src/gosym/mod.rs +++ b/rust-crates/symblib/src/gosym/mod.rs @@ -104,6 +104,22 @@ impl<'obj> GoRuntimeInfo<'obj> { iter: self.func_table.index_iter()?, }) } + + /// Locates the Go function containing the given virtual address. + /// + /// Returns: + /// - `Ok(Some(Func))` if a function is found containing the address + /// - `Ok(None)` if no function contains the address + /// - `Err` if there was an error reading the function table + pub fn find_func<'rt>(&'rt self, addr: VirtAddr) -> Result>> { + Ok(self + .func_table + .func_by_addr(self.text_start, addr)? + .map(|raw_func| Func { + rt: self, + raw: raw_func, + })) + } } /// Internal helpers. diff --git a/rust-crates/symblib/src/gosym/raw/regions.rs b/rust-crates/symblib/src/gosym/raw/regions.rs index 4b7daf1b3..af8800bdb 100644 --- a/rust-crates/symblib/src/gosym/raw/regions.rs +++ b/rust-crates/symblib/src/gosym/raw/regions.rs @@ -108,6 +108,88 @@ impl<'obj> FuncTable<'obj> { pub fn func(&self, offs: FuncTabOffset) -> Result> { Func::read(self.reader.sub_reader(offs.0 as usize..)?) } + + // Look up function information for a virtual address using binary search. + pub fn func_by_addr(&self, text_start: VirtAddr, addr: VirtAddr) -> Result>> { + let sz = FuncTabIndexEntry::size_of(self.reader.header()); + let mut left = 0; + let mut right = self.num_funcs as usize * sz; + + while left < right { + let mid = (left + (right - left) / 2) / sz * sz; + let mut reader = self.reader.sub_reader(mid..)?; + let entry = FuncTabIndexEntry::read(&mut reader)?; + + let entry_addr = match entry.entry { + CodePtr::Addr(addr) => addr, + CodePtr::Offs(offset) => text_start + offset.0, + }; + + if addr < entry_addr { + right = mid; + } else if mid + sz < right { + let mut next_reader = self.reader.sub_reader(mid + sz..)?; + let next_entry = FuncTabIndexEntry::read(&mut next_reader)?; + let next_addr = match next_entry.entry { + CodePtr::Addr(addr) => addr, + CodePtr::Offs(offset) => text_start + offset.0, + }; + + if addr >= next_addr { + left = mid + sz; + } else { + return Ok(Some(self.func(entry.funcoff)?)); + } + } else { + return Ok(Some(self.func(entry.funcoff)?)); + } + } + + Ok(None) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::gosym::GoRuntimeInfo; + use crate::tests::testdata; + + #[test] + fn test_func_by_addr() -> Result<()> { + for test_file in ["go-1.20.14", "go-1.22.12", "go-1.24.0"] { + let obj = objfile::File::load(&testdata(test_file))?; + let obj = obj.parse()?; + + let runtime_info = GoRuntimeInfo::open(&obj)?; + + let text_start = obj.load_section(b".text")?.unwrap().virt_addr(); + + for pc in (text_start..text_start + 0x10000).step_by(19) { + let bin_search_result = runtime_info.find_func(pc).unwrap(); + + let mut fn_iter = runtime_info.funcs().unwrap().peekable(); + let mut found = None; + while let Some(func) = fn_iter.next().unwrap() { + let Some(next) = fn_iter.peek().unwrap() else { + break; + }; + + let pc_rng = func.start_addr()..next.start_addr(); + if pc_rng.contains(&pc) { + found = Some(func); + break; + } + } + + assert_eq!( + bin_search_result.map(|x| x.start_addr()), + found.map(|x| x.start_addr()) + ); + } + } + Ok(()) + } } /// Iterator over the index in `.gopclntab`:`runtime.functab`. diff --git a/rust-crates/symblib/testdata/Makefile b/rust-crates/symblib/testdata/Makefile index 8b584f332..170f68eba 100644 --- a/rust-crates/symblib/testdata/Makefile +++ b/rust-crates/symblib/testdata/Makefile @@ -1,8 +1,8 @@ .PHONY: inline inline-compressed-dwarf inline-split-dwarf inline-big-fake-compressed-dwarf \ - inline-no-tco clean inline-compressed-dwarf-zstd + inline-no-tco clean inline-compressed-dwarf-zstd go-toolchains all: inline inline-compressed-dwarf inline-split-dwarf inline-big-fake-compressed-dwarf \ - inline-no-tco inline-compressed-dwarf-zstd + inline-no-tco inline-compressed-dwarf-zstd go-toolchains inline: inline.c cc $< -o $@ -O2 -g @@ -27,5 +27,10 @@ inline-split-dwarf: inline cp inline inline-split-dwarf dwz -M meow -m inline-split-dwarf.dwp inline-split-dwarf inline-split-dwarf +go-toolchains: + GOTOOLCHAIN=go1.20.14 go build -o go-1.20.14 main.go + GOTOOLCHAIN=go1.22.12 go build -o go-1.22.12 main.go + GOTOOLCHAIN=go1.24.0 go build -o go-1.24.0 main.go + clean: echo "not deleting anything: executables are meant to be kept under VC" diff --git a/rust-crates/symblib/testdata/go-1.20.14 b/rust-crates/symblib/testdata/go-1.20.14 new file mode 100755 index 000000000..610f1fc1d Binary files /dev/null and b/rust-crates/symblib/testdata/go-1.20.14 differ diff --git a/rust-crates/symblib/testdata/go-1.22.12 b/rust-crates/symblib/testdata/go-1.22.12 new file mode 100755 index 000000000..45704ab16 Binary files /dev/null and b/rust-crates/symblib/testdata/go-1.22.12 differ diff --git a/rust-crates/symblib/testdata/go-1.24.0 b/rust-crates/symblib/testdata/go-1.24.0 new file mode 100755 index 000000000..b8c7fd084 Binary files /dev/null and b/rust-crates/symblib/testdata/go-1.24.0 differ diff --git a/rust-crates/symblib/testdata/go.mod b/rust-crates/symblib/testdata/go.mod new file mode 100644 index 000000000..47051f50b --- /dev/null +++ b/rust-crates/symblib/testdata/go.mod @@ -0,0 +1,3 @@ +module github.com/open-telemetry/opentelemetry-ebpf-profiler/rust-crates/symblib/testdata + +go 1.20 diff --git a/rust-crates/symblib/testdata/main.go b/rust-crates/symblib/testdata/main.go new file mode 100644 index 000000000..078ddff8f --- /dev/null +++ b/rust-crates/symblib/testdata/main.go @@ -0,0 +1,7 @@ +package main + +import "fmt" + +func main() { + fmt.Println("Hello, 世界") +}