diff --git a/.gitattributes b/.gitattributes index 190db596..263d9388 100644 --- a/.gitattributes +++ b/.gitattributes @@ -1,4 +1,4 @@ testdata/perf_map/* filter=lfs diff=lfs merge=lfs -text *.gif binary filter=lfs diff=lfs merge=lfs -text src/executor/wall_time/perf/snapshots/codspeed_runner__executor__wall_time__perf__debug_info__tests__ruff_debug_info.snap filter=lfs diff=lfs merge=lfs -text -src/executor/wall_time/perf/snapshots/codspeed_runner__executor__wall_time__perf__perf_map__tests__ruff_symbols.snap filter=lfs diff=lfs merge=lfs -text +src/executor/wall_time/perf/snapshots/codspeed_runner__executor__wall_time__perf__module_symbols__tests__ruff_symbols.snap filter=lfs diff=lfs merge=lfs -text diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 89fd406c..b5f3e3c2 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -11,6 +11,7 @@ repos: - id: check-yaml - id: check-toml - id: check-added-large-files + args: ["--maxkb=1000"] - repo: https://github.com/doublify/pre-commit-rust rev: v1.0 hooks: diff --git a/AGENTS.md b/AGENTS.md index e6c6ac11..fed8d8ec 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -17,21 +17,16 @@ cargo build # Build in release mode cargo build --release -# Run tests (prefer nextest if available) -cargo nextest run # preferred if installed -cargo test # fallback if nextest is not available +# Run tests +cargo test # Run specific test -cargo nextest run # with nextest -cargo test # with cargo test +cargo test # Run tests with output -cargo nextest run --nocapture # with nextest -cargo test -- --nocapture # with cargo test +cargo test -- -nocapture ``` -**Note**: Always check if `cargo nextest` is available first (with `cargo nextest --version` or `which cargo-nextest`). If available, use it instead of `cargo test` as it provides faster and more reliable test execution. - ### Running the Application ```bash @@ -99,7 +94,7 @@ The core functionality for running benchmarks: The project uses: -- `cargo nextest` (preferred) or standard Rust `cargo test` +- `cargo test` - `insta` for snapshot testing - `rstest` for parameterized tests - `temp-env` for environment variable testing @@ -108,5 +103,4 @@ Test files include snapshots in `snapshots/` directories for various run environ **Important**: -- Always prefer `cargo nextest run` over `cargo test` when running tests, as it provides better performance and reliability. -- Some walltime executor tests require `sudo` access and will fail in non-interactive environments (e.g., `test_walltime_executor::*`). These failures are expected if sudo is not available. +- Some tests require `sudo` access. They are skipped by default unless the `GITHUB_ACTIONS` env var is set. diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 564126f4..2e56cca5 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -96,3 +96,7 @@ This ensures only stable runner releases are marked as "latest" in GitHub. ## Known issue - If one of the crates is currenlty in beta version, for example the runner is in beta version 4.4.2-beta.1, any alpha release will fail for the any crate, saying that only minor, major or patch releases is supported. + +## Testing + +- Some tests require `sudo` access. They are skipped by default unless the `GITHUB_ACTIONS` env var is set. diff --git a/Cargo.toml b/Cargo.toml index 8be093bf..cbddb940 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -10,7 +10,6 @@ default-run = "codspeed" name = "codspeed" path = "src/main.rs" - [dependencies] anyhow = { workspace = true } clap = { workspace = true, features = ["env", "color"] } diff --git a/build.rs b/build.rs new file mode 100644 index 00000000..2750b7e6 --- /dev/null +++ b/build.rs @@ -0,0 +1,6 @@ +fn main() { + // Force a rebuild of the test target to be able to run the full test suite locally just by + // setting GITHUB_ACTIONS=1 in the environment. + // This is because `test_with` is evaluated at build time + println!("cargo::rerun-if-env-changed=GITHUB_ACTIONS"); +} diff --git a/crates/memtrack/build.rs b/crates/memtrack/build.rs index 3f9b2b24..a9227d13 100644 --- a/crates/memtrack/build.rs +++ b/crates/memtrack/build.rs @@ -3,6 +3,11 @@ use std::{env, path::PathBuf}; #[cfg(feature = "ebpf")] fn build_ebpf() { + // Force a rebuild of the test target to be able to run the full test suite locally just by + // setting GITHUB_ACTIONS=1 in the environment. + // This is because `test_with` is evaluated at build time + println!("cargo::rerun-if-env-changed=GITHUB_ACTIONS"); + use libbpf_cargo::SkeletonBuilder; println!("cargo:rerun-if-changed=src/ebpf/c"); diff --git a/crates/runner-shared/src/debug_info.rs b/crates/runner-shared/src/debug_info.rs index 2fcbf696..0b7f7ae7 100644 --- a/crates/runner-shared/src/debug_info.rs +++ b/crates/runner-shared/src/debug_info.rs @@ -21,6 +21,13 @@ impl std::fmt::Debug for DebugInfo { } } +/// Per-pid mounting info referencing a deduplicated debug info entry. +#[derive(Serialize, Deserialize, Clone, Debug)] +pub struct MappedProcessDebugInfo { + pub debug_info_key: String, + pub load_bias: u64, +} + #[derive(Debug, Clone, Serialize, Deserialize)] pub struct ModuleDebugInfo { /// The path to the object file on disk (e.g. `/usr/lib/libc.so.6`) diff --git a/crates/runner-shared/src/lib.rs b/crates/runner-shared/src/lib.rs index 381a8a04..7b59a2a8 100644 --- a/crates/runner-shared/src/lib.rs +++ b/crates/runner-shared/src/lib.rs @@ -3,5 +3,6 @@ pub mod debug_info; pub mod fifo; pub mod metadata; pub mod perf_event; +pub mod perf_map; pub mod unwind_data; pub mod walltime_results; diff --git a/crates/runner-shared/src/metadata.rs b/crates/runner-shared/src/metadata.rs index 13beae77..8f8a3fb4 100644 --- a/crates/runner-shared/src/metadata.rs +++ b/crates/runner-shared/src/metadata.rs @@ -1,10 +1,15 @@ use anyhow::Context; +use libc::pid_t; use serde::{Deserialize, Serialize}; +use std::collections::HashMap; use std::io::BufWriter; use std::path::Path; +use std::path::PathBuf; -use crate::debug_info::ModuleDebugInfo; +use crate::debug_info::{MappedProcessDebugInfo, ModuleDebugInfo}; use crate::fifo::MarkerType; +use crate::perf_map::MappedProcessModuleSymbols; +use crate::unwind_data::MappedProcessUnwindData; #[derive(Serialize, Deserialize)] pub struct PerfMetadata { @@ -25,9 +30,31 @@ pub struct PerfMetadata { #[deprecated(note = "Use ExecutionTimestamps in the 'artifacts' module instead")] pub markers: Vec, - /// Debug info for all modules across all processes, mapping PID to module debug info - #[serde(default, skip_serializing_if = "std::collections::HashMap::is_empty")] - pub debug_info_by_pid: std::collections::HashMap>, + /// Kept for backward compatibility, was used before deduplication of debug info entries. + #[serde(default, skip_serializing_if = "HashMap::is_empty")] + #[deprecated(note = "Use 'debug_info' + 'mapped_process_debug_info_by_pid' instead")] + pub debug_info_by_pid: HashMap>, + + /// Deduplicated debug info entries, keyed by semantic key + #[serde(default, skip_serializing_if = "HashMap::is_empty")] + pub debug_info: HashMap, + + /// Per-pid debug info references, mapping PID to list of debug info index + load bias + #[serde(default, skip_serializing_if = "HashMap::is_empty")] + pub mapped_process_debug_info_by_pid: HashMap>, + + /// Per-pid unwind data references, mapping PID to list of unwind data index + mounting info + #[serde(default, skip_serializing_if = "HashMap::is_empty")] + pub mapped_process_unwind_data_by_pid: HashMap>, + + /// Per-pid symbol references, mapping PID to list of perf map index + load bias + #[serde(default, skip_serializing_if = "HashMap::is_empty")] + pub mapped_process_module_symbols: HashMap>, + + /// Mapping from semantic `path_key` to original binary path on host disk + /// Kept for traceability, and if we ever need to reconstruct the original paths from the keys + #[serde(default, skip_serializing_if = "HashMap::is_empty")] + pub path_key_to_path: HashMap, } impl PerfMetadata { diff --git a/crates/runner-shared/src/perf_map.rs b/crates/runner-shared/src/perf_map.rs new file mode 100644 index 00000000..73742fb5 --- /dev/null +++ b/crates/runner-shared/src/perf_map.rs @@ -0,0 +1,11 @@ +use serde::{Deserialize, Serialize}; + +/// File suffix used when registering module symbols in a PID agnostic way. +pub const SYMBOLS_MAP_SUFFIX: &str = "symbols.map"; + +/// Per-pid mounting info referencing a deduplicated perf map entry. +#[derive(Serialize, Deserialize, Clone, Debug)] +pub struct MappedProcessModuleSymbols { + pub perf_map_key: String, + pub load_bias: u64, +} diff --git a/crates/runner-shared/src/unwind_data.rs b/crates/runner-shared/src/unwind_data.rs index ed4cb029..e0eeb48a 100644 --- a/crates/runner-shared/src/unwind_data.rs +++ b/crates/runner-shared/src/unwind_data.rs @@ -8,92 +8,39 @@ use std::{hash::DefaultHasher, ops::Range}; pub const UNWIND_FILE_EXT: &str = "unwind_data"; -pub type UnwindData = UnwindDataV2; - +pub type UnwindData = UnwindDataV3; impl UnwindData { pub fn parse(reader: &[u8]) -> anyhow::Result { let compat: UnwindDataCompat = bincode::deserialize(reader)?; match compat { - UnwindDataCompat::V1(v1) => Ok(v1.into()), - UnwindDataCompat::V2(v2) => Ok(v2), + UnwindDataCompat::V1(_) => { + anyhow::bail!("Cannot parse V1 unwind data as V3 (breaking changes)") + } + UnwindDataCompat::V2(_) => { + anyhow::bail!("Cannot parse V2 unwind data as V3 (breaking changes)") + } + UnwindDataCompat::V3(v3) => Ok(v3), } } - pub fn save_to>(&self, folder: P, pid: i32) -> anyhow::Result<()> { - let unwind_data_path = folder.as_ref().join(format!( - "{}_{:x}_{:x}_{}.{UNWIND_FILE_EXT}", - pid, - self.avma_range.start, - self.avma_range.end, - self.timestamp.unwrap_or_default() - )); - self.to_file(unwind_data_path)?; - - Ok(()) - } - - pub fn to_file>(&self, path: P) -> anyhow::Result<()> { - if let Ok(true) = std::fs::exists(path.as_ref()) { - // This happens in CI for the root `systemd-run` process which execs into bash which - // also execs into bash, each process reloading common libraries like `ld-linux.so`. - // We detect this when we harvest unwind_data by parsing the perf data (exec-harness). - // Until we properly handle the process tree and deduplicate unwind data, just debug - // log here - // Any relevant occurence should have other symptoms reported by users. - log::debug!( - "{} already exists, file will be truncated", - path.as_ref().display() - ); - log::debug!("{} {:x?}", self.path, self.avma_range); - } - - let compat = UnwindDataCompat::V2(self.clone()); - let file = std::fs::File::create(path.as_ref())?; - const BUFFER_SIZE: usize = 256 * 1024 /* 256 KB */; - + pub fn save_to>(&self, folder: P, key: &str) -> anyhow::Result<()> { + let path = folder.as_ref().join(format!("{key}.{UNWIND_FILE_EXT}")); + let compat = UnwindDataCompat::V3(self.clone()); + let file = std::fs::File::create(&path)?; + const BUFFER_SIZE: usize = 256 * 1024; let writer = BufWriter::with_capacity(BUFFER_SIZE, file); bincode::serialize_into(writer, &compat)?; - Ok(()) } } -impl Debug for UnwindData { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - let eh_frame_hdr_hash = { - let mut hasher = DefaultHasher::new(); - self.eh_frame_hdr.hash(&mut hasher); - hasher.finish() - }; - let eh_frame_hash = { - let mut hasher = DefaultHasher::new(); - self.eh_frame.hash(&mut hasher); - hasher.finish() - }; - - f.debug_struct("UnwindData") - .field("path", &self.path) - .field("timestamp", &self.timestamp) - .field("avma_range", &format_args!("{:x?}", self.avma_range)) - .field("base_avma", &format_args!("{:x}", self.base_avma)) - .field("base_svma", &format_args!("{:x}", self.base_svma)) - .field( - "eh_frame_hdr_svma", - &format_args!("{:x?}", self.eh_frame_hdr_svma), - ) - .field("eh_frame_hdr_hash", &format_args!("{eh_frame_hdr_hash:x}")) - .field("eh_frame_hash", &format_args!("{eh_frame_hash:x}")) - .field("eh_frame_svma", &format_args!("{:x?}", self.eh_frame_svma)) - .finish() - } -} - /// A versioned enum for `UnwindData` to allow for future extensions while maintaining backward compatibility. #[derive(Serialize, Deserialize)] enum UnwindDataCompat { V1(UnwindDataV1), V2(UnwindDataV2), + V3(UnwindDataV3), } #[doc(hidden)] @@ -113,7 +60,7 @@ struct UnwindDataV1 { } #[doc(hidden)] -#[derive(Serialize, Deserialize, Clone)] +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq)] pub struct UnwindDataV2 { pub path: String, @@ -132,6 +79,21 @@ pub struct UnwindDataV2 { pub eh_frame_svma: Range, } +impl UnwindDataV2 { + /// Parse unwind data bytes, converting V1 to V2 but erroring on V3 + /// (since V3 doesn't have the per-pid fields needed for V2). + pub fn parse(reader: &[u8]) -> anyhow::Result { + let compat: UnwindDataCompat = bincode::deserialize(reader)?; + match compat { + UnwindDataCompat::V1(v1) => Ok(v1.into()), + UnwindDataCompat::V2(v2) => Ok(v2), + UnwindDataCompat::V3(_) => { + anyhow::bail!("Cannot parse V3 unwind data as V2 (missing per-pid fields)") + } + } + } +} + impl From for UnwindDataV2 { fn from(v1: UnwindDataV1) -> Self { Self { @@ -147,3 +109,171 @@ impl From for UnwindDataV2 { } } } + +/// Pid-agnostic unwind data. +/// Contains only the data that is common across all PIDs loading the same shared library. +#[derive(Serialize, Deserialize, Clone, PartialEq, Eq, Hash)] +pub struct UnwindDataV3 { + pub path: String, + pub base_svma: u64, + pub eh_frame_hdr: Vec, + pub eh_frame_hdr_svma: Range, + pub eh_frame: Vec, + pub eh_frame_svma: Range, +} + +impl From for UnwindDataV3 { + fn from(v2: UnwindDataV2) -> Self { + Self { + path: v2.path, + base_svma: v2.base_svma, + eh_frame_hdr: v2.eh_frame_hdr, + eh_frame_hdr_svma: v2.eh_frame_hdr_svma, + eh_frame: v2.eh_frame, + eh_frame_svma: v2.eh_frame_svma, + } + } +} + +impl Debug for UnwindDataV3 { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + let eh_frame_hdr_hash = { + let mut hasher = DefaultHasher::new(); + self.eh_frame_hdr.hash(&mut hasher); + hasher.finish() + }; + let eh_frame_hash = { + let mut hasher = DefaultHasher::new(); + self.eh_frame.hash(&mut hasher); + hasher.finish() + }; + + f.debug_struct("UnwindData") + .field("path", &self.path) + .field("base_svma", &format_args!("{:x}", self.base_svma)) + .field( + "eh_frame_hdr_svma", + &format_args!("{:x?}", self.eh_frame_hdr_svma), + ) + .field("eh_frame_hdr_hash", &format_args!("{eh_frame_hdr_hash:x}")) + .field("eh_frame_hash", &format_args!("{eh_frame_hash:x}")) + .field("eh_frame_svma", &format_args!("{:x?}", self.eh_frame_svma)) + .finish() + } +} + +/// Per-pid mounting info referencing a deduplicated unwind data entry. +#[derive(Debug, Serialize, Deserialize, Clone)] +pub struct MappedProcessUnwindData { + pub unwind_data_key: String, + #[serde(flatten)] + pub inner: ProcessUnwindData, +} + +#[derive(Serialize, Deserialize, Clone)] +pub struct ProcessUnwindData { + pub timestamp: Option, + pub avma_range: Range, + pub base_avma: u64, +} + +impl Debug for ProcessUnwindData { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("ProcessUnwindData") + .field("timestamp", &self.timestamp) + .field("avma_range", &format_args!("{:x?}", self.avma_range)) + .field("base_avma", &format_args!("{:x}", self.base_avma)) + .finish() + } +} + +#[cfg(test)] +mod tests { + use super::*; + + const V2_BINARY: &[u8] = include_bytes!("../testdata/unwind_data_v2.bin"); + const V3_BINARY: &[u8] = include_bytes!("../testdata/unwind_data_v3.bin"); + + fn create_sample_v2() -> UnwindDataV2 { + UnwindDataV2 { + path: "/lib/test.so".to_string(), + timestamp: Some(12345), + avma_range: 0x1000..0x2000, + base_avma: 0x1000, + base_svma: 0x0, + eh_frame_hdr: vec![1, 2, 3, 4], + eh_frame_hdr_svma: 0x100..0x200, + eh_frame: vec![5, 6, 7, 8], + eh_frame_svma: 0x200..0x300, + } + } + + fn create_sample_v3() -> UnwindDataV3 { + UnwindDataV3 { + path: "/lib/test.so".to_string(), + base_svma: 0x0, + eh_frame_hdr: vec![1, 2, 3, 4], + eh_frame_hdr_svma: 0x100..0x200, + eh_frame: vec![5, 6, 7, 8], + eh_frame_svma: 0x200..0x300, + } + } + + #[test] + fn test_parse_v2_as_v3_should_error() { + // Try to parse V2 binary artifact as V3 using UnwindData::parse + let result = UnwindData::parse(V2_BINARY); + + // Should error due to breaking changes between V2 and V3 + assert!(result.is_err()); + let err = result.unwrap_err(); + assert!( + err.to_string() + .contains("Cannot parse V2 unwind data as V3"), + "Expected error message about V2->V3 incompatibility, got: {err}" + ); + } + + #[test] + fn test_parse_v3_as_v2_should_error() { + // Try to parse V3 binary artifact as V2 using UnwindDataV2::parse + let result = UnwindDataV2::parse(V3_BINARY); + + // Should error with specific message about missing per-pid fields + assert!(result.is_err()); + let err = result.unwrap_err(); + assert!( + err.to_string() + .contains("Cannot parse V3 unwind data as V2"), + "Expected error message about V3->V2 incompatibility, got: {err}" + ); + } + + #[test] + fn test_parse_v3_as_v3() { + // Parse V3 binary artifact as V3 using UnwindData::parse + let parsed_v3 = UnwindData::parse(V3_BINARY).expect("Failed to parse V3 data as V3"); + + // Should match expected V3 data + let expected_v3 = create_sample_v3(); + assert_eq!(parsed_v3, expected_v3); + } + + #[test] + fn test_parse_v2_as_v2() { + // Parse V2 binary artifact as V2 using UnwindDataV2::parse + let parsed_v2 = UnwindDataV2::parse(V2_BINARY).expect("Failed to parse V2 data as V2"); + + // Should match expected V2 data + let expected_v2 = create_sample_v2(); + assert_eq!(parsed_v2.path, expected_v2.path); + assert_eq!(parsed_v2.timestamp, expected_v2.timestamp); + assert_eq!(parsed_v2.avma_range, expected_v2.avma_range); + assert_eq!(parsed_v2.base_avma, expected_v2.base_avma); + assert_eq!(parsed_v2.base_svma, expected_v2.base_svma); + assert_eq!(parsed_v2.eh_frame_hdr, expected_v2.eh_frame_hdr); + assert_eq!(parsed_v2.eh_frame_hdr_svma, expected_v2.eh_frame_hdr_svma); + assert_eq!(parsed_v2.eh_frame, expected_v2.eh_frame); + assert_eq!(parsed_v2.eh_frame_svma, expected_v2.eh_frame_svma); + } +} diff --git a/crates/runner-shared/testdata/unwind_data_v2.bin b/crates/runner-shared/testdata/unwind_data_v2.bin new file mode 100644 index 00000000..f26e8277 Binary files /dev/null and b/crates/runner-shared/testdata/unwind_data_v2.bin differ diff --git a/crates/runner-shared/testdata/unwind_data_v3.bin b/crates/runner-shared/testdata/unwind_data_v3.bin new file mode 100644 index 00000000..74b42b68 Binary files /dev/null and b/crates/runner-shared/testdata/unwind_data_v3.bin differ diff --git a/src/executor/shared/fifo.rs b/src/executor/shared/fifo.rs index 723f921c..39b9533b 100644 --- a/src/executor/shared/fifo.rs +++ b/src/executor/shared/fifo.rs @@ -69,6 +69,15 @@ impl GenericFifo { pub struct FifoBenchmarkData { /// Name and version of the integration pub integration: Option<(String, String)>, + pub bench_pids: HashSet, +} + +impl FifoBenchmarkData { + pub fn is_exec_harness(&self) -> bool { + self.integration + .as_ref() + .is_some_and(|(name, _)| name == "exec-harness") + } } pub struct RunnerFifo { @@ -254,7 +263,10 @@ impl RunnerFifo { ); let marker_result = ExecutionTimestamps::new(&bench_order_by_timestamp, &markers); - let fifo_data = FifoBenchmarkData { integration }; + let fifo_data = FifoBenchmarkData { + integration, + bench_pids, + }; return Ok((marker_result, fifo_data, exit_status)); } Err(e) => return Err(anyhow::Error::from(e)), diff --git a/src/executor/tests.rs b/src/executor/tests.rs index d4940a81..c640f2d0 100644 --- a/src/executor/tests.rs +++ b/src/executor/tests.rs @@ -1,9 +1,6 @@ use super::Config; use crate::executor::ExecutionContext; use crate::executor::Executor; -use crate::executor::memory::executor::MemoryExecutor; -use crate::executor::valgrind::executor::ValgrindExecutor; -use crate::executor::wall_time::executor::WallTimeExecutor; use crate::runner_mode::RunnerMode; use crate::system::SystemInfo; use rstest_reuse::{self, *}; @@ -109,16 +106,6 @@ const ENV_TESTS: [(&str, &str); 8] = [ #[case(TESTS[5])] fn test_cases(#[case] cmd: &str) {} -// Exec-harness currently does not support the inline multi command scripts -#[template] -#[rstest::rstest] -#[case(TESTS[0])] -#[case(TESTS[1])] -#[case(TESTS[2])] -fn exec_harness_test_cases() -> Vec<&'static str> { - EXEC_HARNESS_COMMANDS.to_vec() -} - #[template] #[rstest::rstest] #[case(ENV_TESTS[0])] @@ -182,6 +169,7 @@ async fn acquire_bpf_instrumentation_lock() -> SemaphorePermit<'static> { mod valgrind { use super::*; + use crate::executor::valgrind::executor::ValgrindExecutor; async fn get_valgrind_executor() -> (SemaphorePermit<'static>, &'static ValgrindExecutor) { static VALGRIND_EXECUTOR: OnceCell = OnceCell::const_new(); @@ -240,8 +228,10 @@ mod valgrind { } } +#[test_with::env(GITHUB_ACTIONS)] mod walltime { use super::*; + use crate::executor::wall_time::executor::WallTimeExecutor; async fn get_walltime_executor() -> (SemaphorePermit<'static>, WallTimeExecutor) { static WALLTIME_INIT: OnceCell<()> = OnceCell::const_new(); @@ -358,6 +348,16 @@ fi }) .await; } + // + // Exec-harness currently does not support the inline multi command scripts + #[template] + #[rstest::rstest] + #[case(TESTS[0])] + #[case(TESTS[1])] + #[case(TESTS[2])] + fn exec_harness_test_cases() -> Vec<&'static str> { + EXEC_HARNESS_COMMANDS.to_vec() + } // Ensure that the walltime executor works with the exec-harness #[apply(exec_harness_test_cases)] @@ -391,8 +391,10 @@ fi } } +#[test_with::env(GITHUB_ACTIONS)] mod memory { use super::*; + use crate::executor::memory::executor::MemoryExecutor; async fn get_memory_executor() -> ( SemaphorePermit<'static>, diff --git a/src/executor/wall_time/perf/debug_info.rs b/src/executor/wall_time/perf/debug_info.rs index e6dd737e..37c92d8d 100644 --- a/src/executor/wall_time/perf/debug_info.rs +++ b/src/executor/wall_time/perf/debug_info.rs @@ -1,14 +1,19 @@ -use crate::executor::wall_time::perf::perf_map::ModuleSymbols; +use crate::executor::wall_time::perf::module_symbols::ModuleSymbols; use crate::prelude::*; use addr2line::{fallible_iterator::FallibleIterator, gimli}; use object::{Object, ObjectSection}; +use rayon::prelude::*; use runner_shared::debug_info::{DebugInfo, ModuleDebugInfo}; use std::path::Path; type EndianRcSlice = gimli::EndianRcSlice; pub trait ModuleDebugInfoExt { - fn from_symbols>(path: P, symbols: &ModuleSymbols) -> anyhow::Result + fn from_symbols>( + path: P, + symbols: &ModuleSymbols, + load_bias: u64, + ) -> anyhow::Result where Self: Sized; @@ -36,12 +41,15 @@ pub trait ModuleDebugInfoExt { impl ModuleDebugInfoExt for ModuleDebugInfo { /// Create debug info from existing symbols by looking up file/line in DWARF - fn from_symbols>(path: P, symbols: &ModuleSymbols) -> anyhow::Result { + fn from_symbols>( + path: P, + symbols: &ModuleSymbols, + load_bias: u64, + ) -> anyhow::Result { let content = std::fs::read(path.as_ref())?; let object = object::File::parse(&*content)?; let ctx = Self::create_dwarf_context(&object).context("Failed to create DWARF context")?; - let load_bias = symbols.load_bias(); let (mut min_addr, mut max_addr) = (None, None); let debug_infos = symbols .symbols() @@ -96,34 +104,27 @@ impl ModuleDebugInfoExt for ModuleDebugInfo { } } -/// Represents all the modules inside a process and their debug info. -pub struct ProcessDebugInfo { - modules: Vec, -} - -impl ProcessDebugInfo { - pub fn new( - process_symbols: &crate::executor::wall_time::perf::perf_map::ProcessSymbols, - ) -> Self { - let mut modules = Vec::new(); - for (path, module_symbols) in process_symbols.modules_with_symbols() { - match ModuleDebugInfo::from_symbols(path, module_symbols) { - Ok(module_debug_info) => { - modules.push(module_debug_info); - } +/// Compute debug info once per unique ELF path from deduplicated symbols. +/// Returns a map of path -> ModuleDebugInfo with `load_bias: 0` (load bias is per-pid). +pub fn debug_info_by_path( + mounted_modules_by_path: &std::collections::HashMap< + std::path::PathBuf, + crate::executor::wall_time::perf::parse_perf_file::MountedModule, + >, +) -> std::collections::HashMap { + mounted_modules_by_path + .par_iter() + .filter_map(|(path, mounted_module)| { + let module_symbols = mounted_module.module_symbols.as_ref()?; + match ModuleDebugInfo::from_symbols(path, module_symbols, 0) { + Ok(module_debug_info) => Some((path.clone(), module_debug_info)), Err(error) => { trace!("Failed to load debug info for module {path:?}: {error}"); + None } } - } - - Self { modules } - } - - /// Returns the debug info modules for this process - pub fn modules(self) -> Vec { - self.modules - } + }) + .collect() } #[cfg(test)] @@ -134,15 +135,20 @@ mod tests { fn test_golang_debug_info() { let (start_addr, end_addr, file_offset) = (0x0000000000402000_u64, 0x000000000050f000_u64, 0x2000); - let module_symbols = ModuleSymbols::new( + let module_symbols = ModuleSymbols::from_elf("testdata/perf_map/go_fib.bin").unwrap(); + let load_bias = ModuleSymbols::compute_load_bias( "testdata/perf_map/go_fib.bin", start_addr, end_addr, file_offset, ) .unwrap(); - let module_debug_info = - ModuleDebugInfo::from_symbols("testdata/perf_map/go_fib.bin", &module_symbols).unwrap(); + let module_debug_info = ModuleDebugInfo::from_symbols( + "testdata/perf_map/go_fib.bin", + &module_symbols, + load_bias, + ) + .unwrap(); insta::assert_debug_snapshot!(module_debug_info.debug_infos); } @@ -150,7 +156,9 @@ mod tests { fn test_cpp_debug_info() { let (start_addr, end_addr, file_offset) = (0x0000000000400000_u64, 0x0000000000459000_u64, 0x0); - let module_symbols = ModuleSymbols::new( + let module_symbols = + ModuleSymbols::from_elf("testdata/perf_map/cpp_my_benchmark.bin").unwrap(); + let load_bias = ModuleSymbols::compute_load_bias( "testdata/perf_map/cpp_my_benchmark.bin", start_addr, end_addr, @@ -160,6 +168,7 @@ mod tests { let mut module_debug_info = ModuleDebugInfo::from_symbols( "testdata/perf_map/cpp_my_benchmark.bin", &module_symbols, + load_bias, ) .unwrap(); @@ -172,11 +181,16 @@ mod tests { fn test_rust_divan_debug_info() { const MODULE_PATH: &str = "testdata/perf_map/divan_sleep_benches.bin"; - let module_symbols = - ModuleSymbols::new(MODULE_PATH, 0x00005555555a2000, 0x0000555555692000, 0x4d000) - .unwrap(); + let module_symbols = ModuleSymbols::from_elf(MODULE_PATH).unwrap(); + let load_bias = ModuleSymbols::compute_load_bias( + MODULE_PATH, + 0x00005555555a2000, + 0x0000555555692000, + 0x4d000, + ) + .unwrap(); let module_debug_info = - ModuleDebugInfo::from_symbols(MODULE_PATH, &module_symbols).unwrap(); + ModuleDebugInfo::from_symbols(MODULE_PATH, &module_symbols, load_bias).unwrap(); insta::assert_debug_snapshot!(module_debug_info.debug_infos); } @@ -184,7 +198,8 @@ mod tests { fn test_the_algorithms_debug_info() { const MODULE_PATH: &str = "testdata/perf_map/the_algorithms.bin"; - let module_symbols = ModuleSymbols::new( + let module_symbols = ModuleSymbols::from_elf(MODULE_PATH).unwrap(); + let load_bias = ModuleSymbols::compute_load_bias( MODULE_PATH, 0x00005573e59fe000, 0x00005573e5b07000, @@ -192,7 +207,7 @@ mod tests { ) .unwrap(); let module_debug_info = - ModuleDebugInfo::from_symbols(MODULE_PATH, &module_symbols).unwrap(); + ModuleDebugInfo::from_symbols(MODULE_PATH, &module_symbols, load_bias).unwrap(); insta::assert_debug_snapshot!(module_debug_info.debug_infos); } @@ -202,10 +217,12 @@ mod tests { let (start_addr, end_addr, file_offset) = (0x0000555555e6d000_u64, 0x0000555556813000_u64, 0x918000); - let module_symbols = - ModuleSymbols::new(MODULE_PATH, start_addr, end_addr, file_offset).unwrap(); + let module_symbols = ModuleSymbols::from_elf(MODULE_PATH).unwrap(); + let load_bias = + ModuleSymbols::compute_load_bias(MODULE_PATH, start_addr, end_addr, file_offset) + .unwrap(); let module_debug_info = - ModuleDebugInfo::from_symbols(MODULE_PATH, &module_symbols).unwrap(); + ModuleDebugInfo::from_symbols(MODULE_PATH, &module_symbols, load_bias).unwrap(); insta::assert_debug_snapshot!(module_debug_info.debug_infos); } } diff --git a/src/executor/wall_time/perf/elf_helper.rs b/src/executor/wall_time/perf/elf_helper.rs index c6db81a2..8690355a 100644 --- a/src/executor/wall_time/perf/elf_helper.rs +++ b/src/executor/wall_time/perf/elf_helper.rs @@ -185,18 +185,6 @@ pub fn relative_address_base(object_file: &object::File) -> u64 { object_file.relative_address_base() } -pub fn compute_base_avma( - runtime_start_addr: u64, - runtime_end_addr: u64, - runtime_file_offset: u64, - object: &object::File, -) -> anyhow::Result { - let bias = compute_load_bias( - runtime_start_addr, - runtime_end_addr, - runtime_file_offset, - object, - )?; - let base_svma = relative_address_base(object); - Ok(base_svma.wrapping_add(bias)) +pub fn compute_base_avma(base_svma: u64, load_bias: u64) -> u64 { + base_svma.wrapping_add(load_bias) } diff --git a/src/executor/wall_time/perf/jit_dump.rs b/src/executor/wall_time/perf/jit_dump.rs index 9f92c4f4..f306de08 100644 --- a/src/executor/wall_time/perf/jit_dump.rs +++ b/src/executor/wall_time/perf/jit_dump.rs @@ -1,11 +1,11 @@ use crate::{ - executor::wall_time::perf::perf_map::{ModuleSymbols, Symbol}, + executor::wall_time::perf::module_symbols::{ModuleSymbols, Symbol}, prelude::*, }; use linux_perf_data::jitdump::{JitDumpReader, JitDumpRecord}; -use runner_shared::unwind_data::UnwindData; +use runner_shared::unwind_data::{ProcessUnwindData, UnwindData}; use std::{ - collections::HashSet, + collections::{HashMap, HashSet}, path::{Path, PathBuf}, }; @@ -39,19 +39,19 @@ impl JitDump { } debug!("Extracted {} JIT symbols", symbols.len()); - Ok(ModuleSymbols::from_symbols(symbols)) + Ok(ModuleSymbols::new(symbols)) } - /// Parses the JIT dump file and converts it into a list of `UnwindData`. + /// Parses the JIT dump file and converts it into deduplicated unwind data + pid mappings. /// /// The JIT dump file contains synthetic `eh_frame` data for jitted functions. This can be parsed and - /// then converted to `UnwindData` which is used for stack unwinding. + /// then converted to `UnwindData` + `UnwindDataPidMappingWithFullPath` which is used for stack unwinding. /// /// See: https://github.com/python/cpython/blob/main/Python/perf_jit_trampoline.c - pub fn into_unwind_data(self) -> Result> { + pub fn into_unwind_data(self) -> Result> { let file = std::fs::File::open(self.path)?; - let mut jit_unwind_data = Vec::new(); + let mut harvested_unwind_data = Vec::new(); let mut current_unwind_info: Option<(Vec, Vec)> = None; let mut reader = JitDumpReader::new(file)?; @@ -59,6 +59,13 @@ impl JitDump { // The first recording is always the unwind info, followed by the code load event // (see `perf_map_jit_write_entry` in https://github.com/python/cpython/blob/9743d069bd53e9d3a8f09df899ec1c906a79da24/Python/perf_jit_trampoline.c#L1163C13-L1163C37) match raw_record.parse()? { + JitDumpRecord::CodeUnwindingInfo(record) => { + // Store unwind info for the next code loads + current_unwind_info = Some(( + record.eh_frame.as_slice().to_vec(), + record.eh_frame_hdr.as_slice().to_vec(), + )); + } JitDumpRecord::CodeLoad(record) => { let name = record.function_name.as_slice(); let name = String::from_utf8_lossy(&name); @@ -72,24 +79,24 @@ impl JitDump { continue; }; - jit_unwind_data.push(UnwindData { - path: format!("jit_{name}"), - timestamp: Some(raw_record.timestamp), - avma_range: avma_start..avma_end, - base_avma: 0, + let path = format!("jit_{name}"); + + let unwind_data = UnwindData { + path, + base_svma: 0, eh_frame_hdr, eh_frame_hdr_svma: 0..0, eh_frame, eh_frame_svma: 0..0, - base_svma: 0, - }); - } - JitDumpRecord::CodeUnwindingInfo(record) => { - // Store unwind info for the next code loads - current_unwind_info = Some(( - record.eh_frame.as_slice().to_vec(), - record.eh_frame_hdr.as_slice().to_vec(), - )); + }; + + let process_unwind_data = ProcessUnwindData { + timestamp: Some(raw_record.timestamp), + avma_range: avma_start..avma_end, + base_avma: 0, + }; + + harvested_unwind_data.push((unwind_data, process_unwind_data)); } _ => { warn!("Unhandled JIT dump record: {raw_record:?}"); @@ -97,15 +104,24 @@ impl JitDump { } } - Ok(jit_unwind_data) + Ok(harvested_unwind_data) } } -/// Converts all the `jit-.dump` into unwind data and copies it to the profile folder. -pub async fn harvest_perf_jit_for_pids( +/// Converts all the `jit-.dump` into a perf-.map with symbols, and collects the unwind data +/// +/// # Symbols +/// Since a jit dump is by definition specific to a single pid, we append the harvested symbols +/// into a perf-.map instead of writing a specific jit.symbols.map +/// +/// # Unwind data +/// Unwind data is generated as a list +pub async fn save_symbols_and_harvest_unwind_data_for_pids( profile_folder: &Path, pids: &HashSet, -) -> Result<()> { +) -> Result>> { + let mut jit_unwind_data_by_path = HashMap::new(); + for pid in pids { let name = format!("jit-{pid}.dump"); let path = PathBuf::from("/tmp").join(&name); @@ -115,7 +131,6 @@ pub async fn harvest_perf_jit_for_pids( } debug!("Found JIT dump file: {path:?}"); - // Append the symbols to the existing perf map file let symbols = match JitDump::new(path.clone()).into_perf_map() { Ok(symbols) => symbols, Err(error) => { @@ -123,20 +138,20 @@ pub async fn harvest_perf_jit_for_pids( continue; } }; + + // Also write to perf-.map for harvested Python perf maps compatibility symbols.append_to_file(profile_folder.join(format!("perf-{pid}.map")))?; - let unwind_data = match JitDump::new(path).into_unwind_data() { - Ok(unwind_data) => unwind_data, + let jit_unwind_data = match JitDump::new(path).into_unwind_data() { + Ok(data) => data, Err(error) => { warn!("Failed to convert jit dump into unwind data: {error:?}"); continue; } }; - for module in unwind_data { - module.save_to(profile_folder, *pid)?; - } + jit_unwind_data_by_path.insert(*pid, jit_unwind_data); } - Ok(()) + Ok(jit_unwind_data_by_path) } diff --git a/src/executor/wall_time/perf/mod.rs b/src/executor/wall_time/perf/mod.rs index 6792ba99..1c1996ca 100644 --- a/src/executor/wall_time/perf/mod.rs +++ b/src/executor/wall_time/perf/mod.rs @@ -4,15 +4,11 @@ use crate::cli::UnwindingMode; use crate::executor::Config; use crate::executor::helpers::command::CommandBuilder; use crate::executor::helpers::env::is_codspeed_debug_enabled; -use crate::executor::helpers::harvest_perf_maps_for_pids::harvest_perf_maps_for_pids; use crate::executor::helpers::run_command_with_log_pipe::run_command_with_log_pipe_and_callback; use crate::executor::helpers::run_with_sudo::run_with_sudo; use crate::executor::helpers::run_with_sudo::wrap_with_sudo; use crate::executor::shared::fifo::FifoBenchmarkData; use crate::executor::shared::fifo::RunnerFifo; -use crate::executor::valgrind::helpers::ignored_objects_path::get_objects_path_to_ignore; -use crate::executor::wall_time::perf::debug_info::ProcessDebugInfo; -use crate::executor::wall_time::perf::jit_dump::harvest_perf_jit_for_pids; use crate::executor::wall_time::perf::perf_executable::get_working_perf_executable; use crate::prelude::*; use anyhow::Context; @@ -20,29 +16,29 @@ use fifo::PerfFifo; use parse_perf_file::MemmapRecordsOutput; use perf_executable::get_compression_flags; use perf_executable::get_event_flags; -use rayon::prelude::*; use runner_shared::artifacts::ArtifactExt; use runner_shared::artifacts::ExecutionTimestamps; -use runner_shared::debug_info::ModuleDebugInfo; use runner_shared::fifo::Command as FifoCommand; use runner_shared::fifo::IntegrationMode; use runner_shared::metadata::PerfMetadata; use std::path::Path; use std::path::PathBuf; -use std::{cell::OnceCell, collections::HashMap, process::ExitStatus}; +use std::{cell::OnceCell, process::ExitStatus}; mod jit_dump; +mod naming; mod parse_perf_file; +mod save_artifacts; mod setup; pub mod debug_info; pub mod elf_helper; pub mod fifo; +pub mod module_symbols; pub mod perf_executable; -pub mod perf_map; pub mod unwind_data; -const PERF_METADATA_CURRENT_VERSION: u64 = 1; +const PERF_METADATA_CURRENT_VERSION: u64 = 3; const PERF_PIPEDATA_FILE_NAME: &str = "perf.pipedata"; pub struct PerfRunner { @@ -268,7 +264,6 @@ pub struct BenchmarkData { pub enum BenchmarkDataSaveError { MissingIntegration, FailedToParsePerfFile, - FailedToHarvestPerfMaps, FailedToHarvestJitDumps, } @@ -283,11 +278,16 @@ impl BenchmarkData { let path_ref = path.as_ref(); debug!("Reading perf data from file for mmap extraction"); + let pid_filter = if self.fifo_data.is_exec_harness() { + parse_perf_file::PidFilter::All + } else { + parse_perf_file::PidFilter::TrackedPids(self.fifo_data.bench_pids.clone()) + }; let MemmapRecordsOutput { - symbols_by_pid, - unwind_data_by_pid, + mounted_modules_by_path, + tracked_pids, } = { - parse_perf_file::parse_for_memmap2(perf_file_path).map_err(|e| { + parse_perf_file::parse_for_memmap2(perf_file_path, pid_filter).map_err(|e| { error!("Failed to parse perf file: {e}"); BenchmarkDataSaveError::FailedToParsePerfFile })? @@ -296,38 +296,20 @@ impl BenchmarkData { // Harvest the perf maps generated by python. This will copy the perf // maps from /tmp to the profile folder. We have to write our own perf // maps to these files AFTERWARDS, otherwise it'll be overwritten! - let bench_pids = symbols_by_pid.keys().copied().collect(); - debug!("Harvesting perf maps and jit dumps for pids: {bench_pids:?}"); - harvest_perf_maps_for_pids(path_ref, &bench_pids) - .await - .map_err(|e| { - error!("Failed to harvest perf maps: {e}"); - BenchmarkDataSaveError::FailedToHarvestPerfMaps - })?; - harvest_perf_jit_for_pids(path_ref, &bench_pids) - .await - .map_err(|e| { - error!("Failed to harvest jit dumps: {e}"); - BenchmarkDataSaveError::FailedToHarvestJitDumps - })?; - - debug!("Saving symbols addresses"); - symbols_by_pid.par_iter().for_each(|(_, proc_sym)| { - proc_sym.save_to(path_ref).unwrap(); - }); - - // Collect debug info for each process by looking up file/line for symbols - debug!("Saving debug_info"); - let debug_info_by_pid: HashMap> = symbols_by_pid - .par_iter() - .map(|(pid, proc_sym)| (*pid, ProcessDebugInfo::new(proc_sym).modules())) - .collect(); - - unwind_data_by_pid.par_iter().for_each(|(pid, modules)| { - modules.iter().for_each(|module| { - module.save_to(path_ref, *pid).unwrap(); - }); - }); + debug!("Harvesting perf maps and jit dumps for pids: {tracked_pids:?}"); + let jit_unwind_data_by_pid = + jit_dump::save_symbols_and_harvest_unwind_data_for_pids(path_ref, &tracked_pids) + .await + .map_err(|e| { + error!("Failed to harvest jit dumps: {e}"); + BenchmarkDataSaveError::FailedToHarvestJitDumps + })?; + + let artifacts = save_artifacts::save_artifacts( + path_ref, + &mounted_modules_by_path, + &jit_unwind_data_by_pid, + ); debug!("Saving metadata"); #[allow(deprecated)] @@ -339,52 +321,14 @@ impl BenchmarkData { .clone() .ok_or(BenchmarkDataSaveError::MissingIntegration)?, uri_by_ts: self.marker_result.uri_by_ts.clone(), - ignored_modules: { - let mut to_ignore = vec![]; - - // Check if any of the ignored modules has been loaded in the process - for ignore_path in get_objects_path_to_ignore() { - for proc in symbols_by_pid.values() { - if let Some(mapping) = proc.module_mapping(&ignore_path) { - let (Some((base_addr, _)), Some((_, end_addr))) = ( - mapping.iter().min_by_key(|(base_addr, _)| base_addr), - mapping.iter().max_by_key(|(_, end_addr)| end_addr), - ) else { - continue; - }; - - to_ignore.push((ignore_path.clone(), *base_addr, *end_addr)); - } - } - } - - // When python is statically linked, we'll not find it in the ignored modules. Add it manually: - let python_modules = symbols_by_pid.values().filter_map(|proc| { - proc.loaded_modules().find(|path| { - path.file_name() - .map(|name| name.to_string_lossy().starts_with("python")) - .unwrap_or(false) - }) - }); - for path in python_modules { - if let Some(mapping) = symbols_by_pid - .values() - .find_map(|proc| proc.module_mapping(path)) - { - let (Some((base_addr, _)), Some((_, end_addr))) = ( - mapping.iter().min_by_key(|(base_addr, _)| base_addr), - mapping.iter().max_by_key(|(_, end_addr)| end_addr), - ) else { - continue; - }; - to_ignore.push((path.to_string_lossy().into(), *base_addr, *end_addr)); - } - } - - to_ignore - }, + ignored_modules: artifacts.ignored_modules, markers: self.marker_result.markers.clone(), - debug_info_by_pid, + debug_info: artifacts.debug_info, + mapped_process_debug_info_by_pid: artifacts.mapped_process_debug_info_by_pid, + mapped_process_unwind_data_by_pid: artifacts.mapped_process_unwind_data_by_pid, + mapped_process_module_symbols: artifacts.symbol_pid_mappings_by_pid, + path_key_to_path: artifacts.key_to_path, + debug_info_by_pid: Default::default(), // No longer used }; metadata.save_to(&path).unwrap(); diff --git a/src/executor/wall_time/perf/perf_map.rs b/src/executor/wall_time/perf/module_symbols.rs similarity index 60% rename from src/executor/wall_time/perf/perf_map.rs rename to src/executor/wall_time/perf/module_symbols.rs index f5bea6f0..7d374cdb 100644 --- a/src/executor/wall_time/perf/perf_map.rs +++ b/src/executor/wall_time/perf/module_symbols.rs @@ -1,12 +1,10 @@ use crate::executor::wall_time::perf::elf_helper; -use crate::prelude::*; -use libc::pid_t; use object::{Object, ObjectSymbol, ObjectSymbolTable}; +use runner_shared::perf_map::SYMBOLS_MAP_SUFFIX; use std::{ - collections::HashMap, fmt::Debug, io::{BufWriter, Write}, - path::{Path, PathBuf}, + path::Path, }; #[derive(Hash, PartialEq, Eq, Clone)] @@ -27,33 +25,26 @@ impl Debug for Symbol { } #[derive(Debug, Clone)] +/// Symbols for a module, extracted from an ELF file. +/// The addresses are raw ELF addresses, meaning they represent where the symbols request to be loaded in memory. +/// To resolve actual addresses in the callstack during runtime, these addresses need to be +/// adjusted by the `load_bias` which is applied when the module is actually loaded in memory for a +/// specific process. pub struct ModuleSymbols { - load_bias: u64, symbols: Vec, } impl ModuleSymbols { - pub fn from_symbols(symbols: Vec) -> Self { - Self { - symbols, - load_bias: 0, - } + pub fn new(symbols: Vec) -> Self { + Self { symbols } } pub fn symbols(&self) -> &[Symbol] { &self.symbols } - pub fn load_bias(&self) -> u64 { - self.load_bias - } - - pub fn new>( - path: P, - runtime_start_addr: u64, - runtime_end_addr: u64, - runtime_offset: u64, - ) -> anyhow::Result { + /// Extract symbols from an ELF file (pid-agnostic, load_bias = 0). + pub fn from_elf>(path: P) -> anyhow::Result { let content = std::fs::read(path.as_ref())?; let object = object::File::parse(&*content)?; @@ -126,16 +117,28 @@ impl ModuleSymbols { return Err(anyhow::anyhow!("No symbols found")); } - let load_bias = elf_helper::compute_load_bias( + Ok(Self { symbols }) + } + + /// Compute the load_bias for this module given runtime addresses. + /// This reads the ELF file again to find the matching PT_LOAD segment. + pub fn compute_load_bias>( + path: P, + runtime_start_addr: u64, + runtime_end_addr: u64, + runtime_offset: u64, + ) -> anyhow::Result { + let content = std::fs::read(path.as_ref())?; + let object = object::File::parse(&*content)?; + elf_helper::compute_load_bias( runtime_start_addr, runtime_end_addr, runtime_offset, &object, - )?; - - Ok(Self { load_bias, symbols }) + ) } + /// Write symbols to a file applying the given load_bias. pub fn append_to_file>(&self, path: P) -> anyhow::Result<()> { let file = std::fs::OpenOptions::new() .create(true) @@ -148,89 +151,17 @@ impl ModuleSymbols { writeln!( writer, "{:x} {:x} {}", - symbol.addr.wrapping_add(self.load_bias), - symbol.size, - symbol.name + symbol.addr, symbol.size, symbol.name )?; } Ok(()) } -} - -/// Represents all the modules inside a process and their symbols. -pub struct ProcessSymbols { - pid: pid_t, - module_mappings: HashMap>, - modules: HashMap, -} - -impl ProcessSymbols { - pub fn new(pid: pid_t) -> Self { - Self { - pid, - module_mappings: HashMap::new(), - modules: HashMap::new(), - } - } - - pub fn add_mapping>( - &mut self, - pid: pid_t, - module_path: P, - start_addr: u64, - end_addr: u64, - file_offset: u64, - ) { - if self.pid != pid { - warn!("pid mismatch: {} != {}", self.pid, pid); - return; - } - - let path = module_path.as_ref().to_path_buf(); - match ModuleSymbols::new(module_path, start_addr, end_addr, file_offset) { - Ok(symbol) => { - self.modules.entry(path.clone()).or_insert(symbol); - } - Err(error) => { - debug!("Failed to load symbols for module {path:?}: {error}"); - } - } - - self.module_mappings - .entry(path.clone()) - .or_default() - .push((start_addr, end_addr)); - } - pub fn loaded_modules(&self) -> impl Iterator { - self.modules.keys() - } - - pub fn modules_with_symbols(&self) -> impl Iterator { - self.modules.iter() - } - - pub fn module_mapping>( - &self, - module_path: P, - ) -> Option<&[(u64, u64)]> { - self.module_mappings - .get(module_path.as_ref()) - .map(|bounds| bounds.as_slice()) - } - - pub fn save_to>(&self, folder: P) -> anyhow::Result<()> { - if self.modules.is_empty() { - return Ok(()); - } - - let symbols_path = folder.as_ref().join(format!("perf-{}.map", self.pid)); - for module in self.modules.values() { - module.append_to_file(&symbols_path)?; - } - - Ok(()) + /// Save symbols (at raw ELF addresses, no bias) to a keyed file. + pub fn save_to_keyed_file>(&self, folder: P, key: &str) -> anyhow::Result<()> { + let path = folder.as_ref().join(format!("{key}.{SYMBOLS_MAP_SUFFIX}")); + self.append_to_file(path) } } @@ -240,29 +171,14 @@ mod tests { #[test] fn test_golang_symbols() { - let (start_addr, end_addr, file_offset) = - (0x0000000000402000_u64, 0x000000000050f000_u64, 0x2000); - let module_symbols = ModuleSymbols::new( - "testdata/perf_map/go_fib.bin", - start_addr, - end_addr, - file_offset, - ) - .unwrap(); + let module_symbols = ModuleSymbols::from_elf("testdata/perf_map/go_fib.bin").unwrap(); insta::assert_debug_snapshot!(module_symbols); } #[test] fn test_cpp_symbols() { - let (start_addr, end_addr, file_offset) = - (0x0000000000400000_u64, 0x0000000000459000_u64, 0x0); - let module_symbols = ModuleSymbols::new( - "testdata/perf_map/cpp_my_benchmark.bin", - start_addr, - end_addr, - file_offset, - ) - .unwrap(); + let module_symbols = + ModuleSymbols::from_elf("testdata/perf_map/cpp_my_benchmark.bin").unwrap(); insta::assert_debug_snapshot!(module_symbols); } @@ -282,34 +198,21 @@ mod tests { // 0x0000555555692000 0x000055555569d000 0xb000 0x13c000 r--p // 0x000055555569d000 0x000055555569f000 0x2000 0x146000 rw-p // - let module_symbols = - ModuleSymbols::new(MODULE_PATH, 0x00005555555a2000, 0x0000555555692000, 0x4d000) - .unwrap(); + let module_symbols = ModuleSymbols::from_elf(MODULE_PATH).unwrap(); insta::assert_debug_snapshot!(module_symbols); } #[test] fn test_the_algorithms_symbols() { const MODULE_PATH: &str = "testdata/perf_map/the_algorithms.bin"; - - let module_symbols = ModuleSymbols::new( - MODULE_PATH, - 0x00005573e59fe000, - 0x00005573e5b07000, - 0x00052000, - ) - .unwrap(); + let module_symbols = ModuleSymbols::from_elf(MODULE_PATH).unwrap(); insta::assert_debug_snapshot!(module_symbols); } #[test] fn test_ruff_symbols() { const MODULE_PATH: &str = "testdata/perf_map/ty_walltime"; - - let (start_addr, end_addr, file_offset) = - (0x0000555555e6d000_u64, 0x0000555556813000_u64, 0x918000); - let module_symbols = - ModuleSymbols::new(MODULE_PATH, start_addr, end_addr, file_offset).unwrap(); + let module_symbols = ModuleSymbols::from_elf(MODULE_PATH).unwrap(); insta::assert_debug_snapshot!(module_symbols); } } diff --git a/src/executor/wall_time/perf/naming.rs b/src/executor/wall_time/perf/naming.rs new file mode 100644 index 00000000..a1be38f7 --- /dev/null +++ b/src/executor/wall_time/perf/naming.rs @@ -0,0 +1,42 @@ +use std::path::Path; + +/// Build a semantic key from a global index and a path. +/// +/// The key is `{index}__{basename}` where `basename` is the last component +/// of the path. The index ensures uniqueness across all artifact types. +pub fn indexed_semantic_key(index: usize, path: &Path) -> String { + format!( + "{index}__{}", + path.file_name().unwrap_or_default().to_string_lossy() + ) +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_normal_path() { + let key = indexed_semantic_key(0, Path::new("/usr/lib/libc.so.6")); + assert_eq!(key, "0__libc.so.6"); + } + + #[test] + fn test_jit_path() { + let key = indexed_semantic_key(5, Path::new("/tmp/jit-12345.so")); + assert_eq!(key, "5__jit-12345.so"); + } + + #[test] + fn test_same_basename_different_paths() { + let key1 = indexed_semantic_key(0, Path::new("/usr/lib/libc.so.6")); + let key2 = indexed_semantic_key(1, Path::new("/opt/lib/libc.so.6")); + assert_ne!(key1, key2); + } + + #[test] + fn test_bare_filename() { + let key = indexed_semantic_key(3, Path::new("libfoo.so")); + assert_eq!(key, "3__libfoo.so"); + } +} diff --git a/src/executor/wall_time/perf/parse_perf_file.rs b/src/executor/wall_time/perf/parse_perf_file.rs index 729c7b4a..51538653 100644 --- a/src/executor/wall_time/perf/parse_perf_file.rs +++ b/src/executor/wall_time/perf/parse_perf_file.rs @@ -1,23 +1,58 @@ -use super::perf_map::ProcessSymbols; -use super::unwind_data::UnwindDataExt; +use super::module_symbols::ModuleSymbols; +use super::unwind_data::unwind_data_from_elf; use crate::prelude::*; use libc::pid_t; use linux_perf_data::PerfFileReader; use linux_perf_data::PerfFileRecord; use linux_perf_data::linux_perf_event_reader::EventRecord; use linux_perf_data::linux_perf_event_reader::RecordType; +use runner_shared::unwind_data::ProcessUnwindData; use runner_shared::unwind_data::UnwindData; use std::collections::HashMap; +use std::collections::HashSet; use std::path::Path; +use std::path::PathBuf; + +#[derive(Default)] +pub struct MountedModule { + /// Symbols extracted from the mapped ELF file + pub module_symbols: Option, + /// Unwind data extracted from the mapped ELF file + pub unwind_data: Option, + /// Per-process mounting information + pub process_mounted_module: HashMap, +} + +#[derive(Default)] +pub struct ProcessMountedModule { + /// Load bias used to adjust declared elf addresses to their actual runtime addresses + /// The bias is the difference between where the segment *actually* is in memory versus where the ELF file *preferred* it to be + pub symbols_load_bias: Option, + /// Unwind data specific to the process mounting, derived from both load bias and the actual unwind data + pub process_unwind_data: Option, +} + +impl MountedModule { + pub fn pids(&self) -> impl Iterator { + self.process_mounted_module.keys().copied() + } +} pub struct MemmapRecordsOutput { - pub symbols_by_pid: HashMap, - pub unwind_data_by_pid: HashMap>, + /// Module symbols and the computed load bias for each pid that maps the ELF path. + pub mounted_modules_by_path: HashMap, + pub tracked_pids: HashSet, } -pub(super) fn parse_for_memmap2>(perf_file_path: P) -> Result { - let mut symbols_by_pid = HashMap::::new(); - let mut unwind_data_by_pid = HashMap::>::new(); +/// Parse the perf file at `perf_file_path` and look for MMAP2 records for the given `pids`. +/// If the pids filter is empty, all MMAP2 records will be parsed. +/// +/// Returns process symbols and unwind data for the executable mappings found in the perf file. +pub fn parse_for_memmap2>( + perf_file_path: P, + mut pid_filter: PidFilter, +) -> Result { + let mut mounted_modules_by_path = HashMap::::new(); // 1MiB buffer let reader = std::io::BufReader::with_capacity( @@ -37,85 +72,179 @@ pub(super) fn parse_for_memmap2>(perf_file_path: P) -> Result { + // Process fork events to track children (and children of children) of filtered PIDs + let Ok(parsed_record) = record.parse() else { + continue; + }; + + let EventRecord::Fork(fork_record) = parsed_record else { + continue; + }; + + if pid_filter.add_child_if_parent_tracked(fork_record.ppid, fork_record.pid) { + trace!( + "Fork: Tracking child PID {} from parent PID {}", + fork_record.pid, fork_record.ppid + ); + } + } + RecordType::MMAP2 => { + let Ok(parsed_record) = record.parse() else { + continue; + }; + + // Should never fail since we already checked the type in the raw record + let EventRecord::Mmap2(mmap2_record) = parsed_record else { + continue; + }; + + // Filter on pid early to avoid string allocation for unwanted records + if !pid_filter.should_include(mmap2_record.pid) { + continue; + } + + process_mmap2_record(mmap2_record, &mut mounted_modules_by_path); + } + _ => continue, } + } - let Ok(parsed_record) = record.parse() else { - continue; - }; + // Retrieve the set of PIDs we ended up tracking after processing all records + let tracked_pids: HashSet = match pid_filter { + PidFilter::All => mounted_modules_by_path + .iter() + .flat_map(|(_, mounted)| mounted.pids()) + .collect(), + PidFilter::TrackedPids(tracked) => tracked, + }; - // Should never fail since we already checked the type in the raw record - let EventRecord::Mmap2(record) = parsed_record else { - continue; - }; + Ok(MemmapRecordsOutput { + mounted_modules_by_path, + tracked_pids, + }) +} - // Check PROT_EXEC early to avoid string allocation for non-executable mappings - if record.protection as i32 & libc::PROT_EXEC == 0 { - continue; - } +/// PID filter for parsing perf records +pub enum PidFilter { + /// Parse records for all PIDs + All, + /// Parse records only for specific PIDs and their children + TrackedPids(HashSet), +} - // Filter on raw bytes before allocating a String - let path_slice: &[u8] = &record.path.as_slice(); +impl PidFilter { + /// Check if a PID should be included in parsing + fn should_include(&self, pid: pid_t) -> bool { + match self { + PidFilter::All => true, + PidFilter::TrackedPids(tracked_pids) => tracked_pids.contains(&pid), + } + } - // Skip anonymous mappings - if path_slice == b"//anon" { - continue; + /// Add a child PID to the filter if we're tracking its parent + /// Returns true if the child was added + fn add_child_if_parent_tracked(&mut self, parent_pid: pid_t, child_pid: pid_t) -> bool { + match self { + PidFilter::All => false, // Already tracking all PIDs + PidFilter::TrackedPids(tracked_pids) => { + if tracked_pids.contains(&parent_pid) { + tracked_pids.insert(child_pid) + } else { + false + } + } } + } +} - // Skip special mappings like [vdso], [heap], etc. - if path_slice.first() == Some(&b'[') && path_slice.last() == Some(&b']') { - continue; +/// Process a single MMAP2 record and add it to the symbols and unwind data maps +fn process_mmap2_record( + record: linux_perf_data::linux_perf_event_reader::Mmap2Record, + mounted_modules_by_path: &mut HashMap, +) { + // Check PROT_EXEC early to avoid string allocation for non-executable mappings + if record.protection as i32 & libc::PROT_EXEC == 0 { + return; + } + + // Filter on raw bytes before allocating a String + let path_slice: &[u8] = &record.path.as_slice(); + + // Skip anonymous mappings + if path_slice == b"//anon" { + return; + } + + // Skip special mappings like [vdso], [heap], etc. + if path_slice.first() == Some(&b'[') && path_slice.last() == Some(&b']') { + return; + } + + let record_path_string = String::from_utf8_lossy(path_slice).into_owned(); + let record_path = PathBuf::from(&record_path_string); + let end_addr = record.address + record.length; + + trace!( + "Mapping: Pid {}: {:016x}-{:016x} {:08x} {:?} (Prot {:?})", + record.pid, + record.address, + end_addr, + record.page_offset, + record_path_string, + record.protection, + ); + + let load_bias = match ModuleSymbols::compute_load_bias( + &record_path, + record.address, + end_addr, + record.page_offset, + ) { + Ok(load_bias) => load_bias, + Err(e) => { + debug!("Failed to compute load bias for {record_path_string}: {e}"); + return; } + }; - let record_path_string = String::from_utf8_lossy(path_slice).into_owned(); - let end_addr = record.address + record.length; - - trace!( - "Mapping: Pid {}: {:016x}-{:016x} {:08x} {:?} (Prot {:?})", - record.pid, - record.address, - end_addr, - record.page_offset, - record_path_string, - record.protection, - ); - symbols_by_pid - .entry(record.pid) - .or_insert(ProcessSymbols::new(record.pid)) - .add_mapping( - record.pid, - &record_path_string, - record.address, - end_addr, - record.page_offset, - ); - - match UnwindData::new( - record_path_string.as_bytes(), - record.page_offset, - record.address, - end_addr, - None, - ) { - Ok(unwind_data) => { - unwind_data_by_pid - .entry(record.pid) - .or_default() - .push(unwind_data); - trace!( - "Added unwind data for {record_path_string} ({:x} - {:x})", - record.address, end_addr - ); - } + let mounted_module = mounted_modules_by_path + .entry(record_path.clone()) + .or_default(); + + let process_mounted_module = mounted_module + .process_mounted_module + .entry(record.pid) + .or_default(); + + // Extract module symbols if it's no module symbol from path + if mounted_module.module_symbols.is_none() { + match ModuleSymbols::from_elf(&record_path) { + Ok(symbols) => mounted_module.module_symbols = Some(symbols), Err(error) => { - debug!("Failed to create unwind data for module {record_path_string}: {error}"); + debug!("Failed to load symbols for module {record_path_string}: {error}"); } } } - Ok(MemmapRecordsOutput { - symbols_by_pid, - unwind_data_by_pid, - }) + // Store load bias for this process mounting + process_mounted_module.symbols_load_bias = Some(load_bias); + + // Extract unwind_data + match unwind_data_from_elf( + record_path_string.as_bytes(), + record.address, + end_addr, + None, + load_bias, + ) { + Ok((unwind_data, process_unwind_data)) => { + mounted_module.unwind_data = Some(unwind_data); + process_mounted_module.process_unwind_data = Some(process_unwind_data); + } + Err(error) => { + debug!("Failed to load unwind data for module {record_path_string}: {error}"); + } + }; } diff --git a/src/executor/wall_time/perf/save_artifacts.rs b/src/executor/wall_time/perf/save_artifacts.rs new file mode 100644 index 00000000..9d8aab15 --- /dev/null +++ b/src/executor/wall_time/perf/save_artifacts.rs @@ -0,0 +1,284 @@ +use crate::executor::valgrind::helpers::ignored_objects_path::get_objects_path_to_ignore; +use crate::executor::wall_time::perf::debug_info::debug_info_by_path; +use crate::executor::wall_time::perf::naming; +use crate::executor::wall_time::perf::parse_perf_file::MountedModule; +use crate::prelude::*; +use libc::pid_t; +use rayon::prelude::*; +use runner_shared::debug_info::{MappedProcessDebugInfo, ModuleDebugInfo}; +use runner_shared::perf_map::MappedProcessModuleSymbols; +use runner_shared::unwind_data::{MappedProcessUnwindData, ProcessUnwindData, UnwindData}; +use std::collections::HashMap; +use std::path::{Path, PathBuf}; + +pub struct SavedArtifacts { + pub symbol_pid_mappings_by_pid: HashMap>, + pub debug_info: HashMap, + pub mapped_process_debug_info_by_pid: HashMap>, + pub mapped_process_unwind_data_by_pid: HashMap>, + pub ignored_modules: Vec<(String, u64, u64)>, + pub key_to_path: HashMap, +} + +/// Save all artifacts (symbols, debug info, unwind data) from mounted modules and JIT data. +pub fn save_artifacts( + profile_folder: &Path, + mounted_modules_by_path: &HashMap, + jit_unwind_data_by_pid: &HashMap>, +) -> SavedArtifacts { + let mut path_to_key = HashMap::::new(); + + register_paths(&mut path_to_key, mounted_modules_by_path); + + let symbol_pid_mappings_by_pid = + save_symbols(profile_folder, mounted_modules_by_path, &path_to_key); + + let (debug_info, mapped_process_debug_info_by_pid) = + save_debug_info(mounted_modules_by_path, &mut path_to_key); + + let mapped_process_unwind_data_by_pid = save_unwind_data( + profile_folder, + mounted_modules_by_path, + jit_unwind_data_by_pid, + &mut path_to_key, + ); + + let ignored_modules = collect_ignored_modules(mounted_modules_by_path); + + let key_to_path = path_to_key + .into_iter() + .map(|(path, key)| (key, path)) + .collect(); + + SavedArtifacts { + symbol_pid_mappings_by_pid, + debug_info, + mapped_process_debug_info_by_pid, + mapped_process_unwind_data_by_pid, + ignored_modules, + key_to_path, + } +} + +/// Register a path in the map if absent, assigning a new unique key. +/// Returns the assigned key. +fn get_or_insert_key(path_to_key: &mut HashMap, path: &Path) -> String { + if let Some(key) = path_to_key.get(path) { + return key.clone(); + } + let key = naming::indexed_semantic_key(path_to_key.len(), path); + path_to_key.insert(path.to_owned(), key.clone()); + key +} + +/// Pre-register all paths from the mounted modules map. +fn register_paths( + path_to_key: &mut HashMap, + mounted_modules_by_path: &HashMap, +) { + for path in mounted_modules_by_path.keys() { + get_or_insert_key(path_to_key, path); + } +} + +/// Save deduplicated symbol files to disk and build per-pid mappings. +fn save_symbols( + profile_folder: &Path, + mounted_modules_by_path: &HashMap, + path_to_key: &HashMap, +) -> HashMap> { + let symbols_count = mounted_modules_by_path + .values() + .filter(|m| m.module_symbols.is_some()) + .count(); + debug!("Saving symbols ({symbols_count} unique entries)"); + + mounted_modules_by_path.par_iter().for_each(|(path, m)| { + if let Some(ref symbols) = m.module_symbols { + let key = &path_to_key[path]; + symbols.save_to_keyed_file(profile_folder, key).unwrap(); + } + }); + + let mut mappings_by_pid: HashMap> = HashMap::new(); + for (path, m) in mounted_modules_by_path { + if m.module_symbols.is_none() { + continue; + } + let key = &path_to_key[path]; + for (&pid, pm) in &m.process_mounted_module { + if let Some(load_bias) = pm.symbols_load_bias { + mappings_by_pid + .entry(pid) + .or_default() + .push(MappedProcessModuleSymbols { + perf_map_key: key.clone(), + load_bias, + }); + } + } + } + for mappings in mappings_by_pid.values_mut() { + mappings.sort_by(|a, b| a.perf_map_key.cmp(&b.perf_map_key)); + } + mappings_by_pid +} + +/// Compute debug info from symbols and build per-pid debug info mappings. +fn save_debug_info( + mounted_modules_by_path: &HashMap, + path_to_key: &mut HashMap, +) -> ( + HashMap, + HashMap>, +) { + debug!("Saving debug_info"); + + let debug_info_by_elf_path = debug_info_by_path(mounted_modules_by_path); + + for path in debug_info_by_elf_path.keys() { + get_or_insert_key(path_to_key, path); + } + + let debug_info: HashMap = debug_info_by_elf_path + .into_iter() + .filter_map(|(path, info)| { + let key = path_to_key.get(&path)?.clone(); + Some((key, info)) + }) + .collect(); + + let mut mappings_by_pid: HashMap> = HashMap::new(); + for (path, m) in mounted_modules_by_path { + if m.module_symbols.is_none() { + continue; + } + let Some(key) = path_to_key.get(path) else { + continue; + }; + for (&pid, pm) in &m.process_mounted_module { + if let Some(load_bias) = pm.symbols_load_bias { + mappings_by_pid + .entry(pid) + .or_default() + .push(MappedProcessDebugInfo { + debug_info_key: key.clone(), + load_bias, + }); + } + } + } + for mappings in mappings_by_pid.values_mut() { + mappings.sort_by(|a, b| a.debug_info_key.cmp(&b.debug_info_key)); + } + + (debug_info, mappings_by_pid) +} + +/// Save deduplicated unwind data files to disk and build per-pid mappings, +/// including JIT unwind data. +fn save_unwind_data( + profile_folder: &Path, + mounted_modules_by_path: &HashMap, + jit_unwind_data_by_pid: &HashMap>, + path_to_key: &mut HashMap, +) -> HashMap> { + let unwind_data_count = mounted_modules_by_path + .values() + .filter(|m| m.unwind_data.is_some()) + .count(); + debug!("Saving unwind data ({unwind_data_count} unique entries)"); + + mounted_modules_by_path.par_iter().for_each(|(path, m)| { + if let Some(ref unwind_data) = m.unwind_data { + let key = &path_to_key[path]; + unwind_data.save_to(profile_folder, key).unwrap(); + } + }); + + let mut mappings_by_pid: HashMap> = HashMap::new(); + for (path, m) in mounted_modules_by_path { + if m.unwind_data.is_none() { + continue; + } + let key = &path_to_key[path]; + for (&pid, pm) in &m.process_mounted_module { + if let Some(ref pud) = pm.process_unwind_data { + mappings_by_pid + .entry(pid) + .or_default() + .push(MappedProcessUnwindData { + unwind_data_key: key.clone(), + inner: runner_shared::unwind_data::ProcessUnwindData { + timestamp: pud.timestamp, + avma_range: pud.avma_range.clone(), + base_avma: pud.base_avma, + }, + }); + } + } + } + + // Add JIT unwind data mappings + for (&pid, jit_entries) in jit_unwind_data_by_pid { + for (unwind_data, process_unwind_data) in jit_entries { + let jit_path = PathBuf::from(&unwind_data.path); + let key = get_or_insert_key(path_to_key, &jit_path); + unwind_data.save_to(profile_folder, &key).unwrap(); + mappings_by_pid + .entry(pid) + .or_default() + .push(MappedProcessUnwindData { + unwind_data_key: key, + inner: runner_shared::unwind_data::ProcessUnwindData { + timestamp: process_unwind_data.timestamp, + avma_range: process_unwind_data.avma_range.clone(), + base_avma: process_unwind_data.base_avma, + }, + }); + } + } + + for mappings in mappings_by_pid.values_mut() { + mappings.sort_by(|a, b| a.unwind_data_key.cmp(&b.unwind_data_key)); + } + + mappings_by_pid +} + +/// Collect ignored modules by finding known-ignored and python modules in the mounted modules. +fn collect_ignored_modules( + mounted_modules_by_path: &HashMap, +) -> Vec<(String, u64, u64)> { + let mut to_ignore = vec![]; + + let ignore_paths = get_objects_path_to_ignore(); + + for (path, m) in mounted_modules_by_path { + let path_str = path.to_string_lossy(); + + let is_ignored = ignore_paths + .iter() + .any(|ip| path_str.as_ref() == ip.as_str()); + let is_python = path + .file_name() + .map(|name| name.to_string_lossy().starts_with("python")) + .unwrap_or(false); + + if !is_ignored && !is_python { + continue; + } + + for pm in m.process_mounted_module.values() { + if let Some(ref pud) = pm.process_unwind_data { + to_ignore.push(( + path_str.to_string(), + pud.avma_range.start, + pud.avma_range.end, + )); + } + } + } + + to_ignore +} diff --git a/src/executor/wall_time/perf/snapshots/codspeed_runner__executor__wall_time__perf__perf_map__tests__cpp_symbols.snap b/src/executor/wall_time/perf/snapshots/codspeed_runner__executor__wall_time__perf__module_symbols__tests__cpp_symbols.snap similarity index 99% rename from src/executor/wall_time/perf/snapshots/codspeed_runner__executor__wall_time__perf__perf_map__tests__cpp_symbols.snap rename to src/executor/wall_time/perf/snapshots/codspeed_runner__executor__wall_time__perf__module_symbols__tests__cpp_symbols.snap index 35fc3575..8456dd05 100644 --- a/src/executor/wall_time/perf/snapshots/codspeed_runner__executor__wall_time__perf__perf_map__tests__cpp_symbols.snap +++ b/src/executor/wall_time/perf/snapshots/codspeed_runner__executor__wall_time__perf__module_symbols__tests__cpp_symbols.snap @@ -1,9 +1,8 @@ --- -source: src/executor/wall_time/perf/perf_map.rs +source: src/executor/wall_time/perf/module_symbols.rs expression: module_symbols --- ModuleSymbols { - load_bias: 0, symbols: [ Symbol { offset: 0, size: 4002f8, name: __gmon_start__ }, Symbol { offset: 4002f8, size: 20, name: __abi_tag }, diff --git a/src/executor/wall_time/perf/snapshots/codspeed_runner__executor__wall_time__perf__perf_map__tests__golang_symbols.snap b/src/executor/wall_time/perf/snapshots/codspeed_runner__executor__wall_time__perf__module_symbols__tests__golang_symbols.snap similarity index 99% rename from src/executor/wall_time/perf/snapshots/codspeed_runner__executor__wall_time__perf__perf_map__tests__golang_symbols.snap rename to src/executor/wall_time/perf/snapshots/codspeed_runner__executor__wall_time__perf__module_symbols__tests__golang_symbols.snap index a1b9c006..84138e7b 100644 --- a/src/executor/wall_time/perf/snapshots/codspeed_runner__executor__wall_time__perf__perf_map__tests__golang_symbols.snap +++ b/src/executor/wall_time/perf/snapshots/codspeed_runner__executor__wall_time__perf__module_symbols__tests__golang_symbols.snap @@ -1,9 +1,8 @@ --- -source: src/executor/wall_time/perf/perf_map.rs +source: src/executor/wall_time/perf/module_symbols.rs expression: module_symbols --- ModuleSymbols { - load_bias: 0, symbols: [ Symbol { offset: 0, size: 8, name: runtime.tlsg }, Symbol { offset: 0, size: 402000, name: seteuid }, diff --git a/src/executor/wall_time/perf/snapshots/codspeed_runner__executor__wall_time__perf__module_symbols__tests__ruff_symbols.snap b/src/executor/wall_time/perf/snapshots/codspeed_runner__executor__wall_time__perf__module_symbols__tests__ruff_symbols.snap new file mode 100644 index 00000000..2c2ba3db --- /dev/null +++ b/src/executor/wall_time/perf/snapshots/codspeed_runner__executor__wall_time__perf__module_symbols__tests__ruff_symbols.snap @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:2773ce550303b2ca7fbdd01adf1ed0be2355f6bf173e3fa29f576b46372634ee +size 5033496 diff --git a/src/executor/wall_time/perf/snapshots/codspeed_runner__executor__wall_time__perf__perf_map__tests__rust_divan_symbols.snap b/src/executor/wall_time/perf/snapshots/codspeed_runner__executor__wall_time__perf__module_symbols__tests__rust_divan_symbols.snap similarity index 99% rename from src/executor/wall_time/perf/snapshots/codspeed_runner__executor__wall_time__perf__perf_map__tests__rust_divan_symbols.snap rename to src/executor/wall_time/perf/snapshots/codspeed_runner__executor__wall_time__perf__module_symbols__tests__rust_divan_symbols.snap index adb45415..839039f3 100644 --- a/src/executor/wall_time/perf/snapshots/codspeed_runner__executor__wall_time__perf__perf_map__tests__rust_divan_symbols.snap +++ b/src/executor/wall_time/perf/snapshots/codspeed_runner__executor__wall_time__perf__module_symbols__tests__rust_divan_symbols.snap @@ -1,9 +1,8 @@ --- -source: src/executor/wall_time/perf/perf_map.rs +source: src/executor/wall_time/perf/module_symbols.rs expression: module_symbols --- ModuleSymbols { - load_bias: 93824992231424, symbols: [ Symbol { offset: 0, size: 20, name: _ZN3std3sys12thread_local11destructors4list5DTORS17hc534bbd2e2244af8E }, Symbol { offset: 0, size: 20, name: __tls_get_addr }, diff --git a/src/executor/wall_time/perf/snapshots/codspeed_runner__executor__wall_time__perf__perf_map__tests__the_algorithms_symbols.snap b/src/executor/wall_time/perf/snapshots/codspeed_runner__executor__wall_time__perf__module_symbols__tests__the_algorithms_symbols.snap similarity index 99% rename from src/executor/wall_time/perf/snapshots/codspeed_runner__executor__wall_time__perf__perf_map__tests__the_algorithms_symbols.snap rename to src/executor/wall_time/perf/snapshots/codspeed_runner__executor__wall_time__perf__module_symbols__tests__the_algorithms_symbols.snap index 63cd5218..fec3e280 100644 --- a/src/executor/wall_time/perf/snapshots/codspeed_runner__executor__wall_time__perf__perf_map__tests__the_algorithms_symbols.snap +++ b/src/executor/wall_time/perf/snapshots/codspeed_runner__executor__wall_time__perf__module_symbols__tests__the_algorithms_symbols.snap @@ -1,9 +1,8 @@ --- -source: src/executor/wall_time/perf/perf_map.rs +source: src/executor/wall_time/perf/module_symbols.rs expression: module_symbols --- ModuleSymbols { - load_bias: 93956261720064, symbols: [ Symbol { offset: 0, size: 20, name: _ZN3std3sys12thread_local11destructors4list5DTORS17hc534bbd2e2244af8E }, Symbol { offset: 0, size: 20, name: __tls_get_addr }, diff --git a/src/executor/wall_time/perf/snapshots/codspeed_runner__executor__wall_time__perf__perf_map__tests__ruff_symbols.snap b/src/executor/wall_time/perf/snapshots/codspeed_runner__executor__wall_time__perf__perf_map__tests__ruff_symbols.snap deleted file mode 100644 index bb56b521..00000000 --- a/src/executor/wall_time/perf/snapshots/codspeed_runner__executor__wall_time__perf__perf_map__tests__ruff_symbols.snap +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:b66645f8ee52271ff108bc73b69558cac3ed642c4ab9e101f99c4354b762f803 -size 5033521 diff --git a/src/executor/wall_time/perf/snapshots/codspeed_runner__executor__wall_time__perf__unwind_data__tests__cpp_unwind_data.snap b/src/executor/wall_time/perf/snapshots/codspeed_runner__executor__wall_time__perf__unwind_data__tests__cpp_unwind_data.snap index 8f06aff1..7cec793c 100644 --- a/src/executor/wall_time/perf/snapshots/codspeed_runner__executor__wall_time__perf__unwind_data__tests__cpp_unwind_data.snap +++ b/src/executor/wall_time/perf/snapshots/codspeed_runner__executor__wall_time__perf__unwind_data__tests__cpp_unwind_data.snap @@ -1,17 +1,21 @@ --- source: src/executor/wall_time/perf/unwind_data.rs -expression: "UnwindData::new(MODULE_PATH.as_bytes(), file_offset, start_addr, end_addr,\nNone,)" +expression: "unwind_data_from_elf(MODULE_PATH.as_bytes(), start_addr, end_addr, None,\nload_bias,)" --- Ok( - UnwindData { - path: "testdata/perf_map/cpp_my_benchmark.bin", - timestamp: None, - avma_range: 400000..459000, - base_avma: 400000, - base_svma: 400000, - eh_frame_hdr_svma: 4577bc..458b30, - eh_frame_hdr_hash: 4b4eac90f7f5e60d, - eh_frame_hash: 233bdd4ae9fe4ba4, - eh_frame_svma: 451098..4577bc, - }, + ( + UnwindData { + path: "testdata/perf_map/cpp_my_benchmark.bin", + base_svma: 400000, + eh_frame_hdr_svma: 4577bc..458b30, + eh_frame_hdr_hash: 4b4eac90f7f5e60d, + eh_frame_hash: 233bdd4ae9fe4ba4, + eh_frame_svma: 451098..4577bc, + }, + ProcessUnwindData { + timestamp: None, + avma_range: 400000..459000, + base_avma: 400000, + }, + ), ) diff --git a/src/executor/wall_time/perf/snapshots/codspeed_runner__executor__wall_time__perf__unwind_data__tests__golang_unwind_data.snap b/src/executor/wall_time/perf/snapshots/codspeed_runner__executor__wall_time__perf__unwind_data__tests__golang_unwind_data.snap index e3774e60..65ae6ad5 100644 --- a/src/executor/wall_time/perf/snapshots/codspeed_runner__executor__wall_time__perf__unwind_data__tests__golang_unwind_data.snap +++ b/src/executor/wall_time/perf/snapshots/codspeed_runner__executor__wall_time__perf__unwind_data__tests__golang_unwind_data.snap @@ -1,17 +1,21 @@ --- source: src/executor/wall_time/perf/unwind_data.rs -expression: "UnwindData::new(MODULE_PATH.as_bytes(), file_offset, start_addr, end_addr,\nNone)" +expression: "unwind_data_from_elf(MODULE_PATH.as_bytes(), start_addr, end_addr, None,\nload_bias,)" --- Ok( - UnwindData { - path: "testdata/perf_map/go_fib.bin", - timestamp: None, - avma_range: 402000..50f000, - base_avma: 400000, - base_svma: 400000, - eh_frame_hdr_svma: 6498b0..649b94, - eh_frame_hdr_hash: f1f69beb959a08d7, - eh_frame_hash: a8727039dd21b51c, - eh_frame_svma: 649b98..64aa70, - }, + ( + UnwindData { + path: "testdata/perf_map/go_fib.bin", + base_svma: 400000, + eh_frame_hdr_svma: 6498b0..649b94, + eh_frame_hdr_hash: f1f69beb959a08d7, + eh_frame_hash: a8727039dd21b51c, + eh_frame_svma: 649b98..64aa70, + }, + ProcessUnwindData { + timestamp: None, + avma_range: 402000..50f000, + base_avma: 400000, + }, + ), ) diff --git a/src/executor/wall_time/perf/snapshots/codspeed_runner__executor__wall_time__perf__unwind_data__tests__ruff_unwind_data.snap b/src/executor/wall_time/perf/snapshots/codspeed_runner__executor__wall_time__perf__unwind_data__tests__ruff_unwind_data.snap index 8304f0ea..91111c04 100644 --- a/src/executor/wall_time/perf/snapshots/codspeed_runner__executor__wall_time__perf__unwind_data__tests__ruff_unwind_data.snap +++ b/src/executor/wall_time/perf/snapshots/codspeed_runner__executor__wall_time__perf__unwind_data__tests__ruff_unwind_data.snap @@ -1,17 +1,21 @@ --- source: src/executor/wall_time/perf/unwind_data.rs -expression: "UnwindData::new(MODULE_PATH.as_bytes(), file_offset, start_addr, end_addr,\nNone)" +expression: "unwind_data_from_elf(MODULE_PATH.as_bytes(), start_addr, end_addr, None,\nload_bias,)" --- Ok( - UnwindData { - path: "testdata/perf_map/ty_walltime", - timestamp: None, - avma_range: 555555e6d000..555556813000, - base_avma: 555555554000, - base_svma: 0, - eh_frame_hdr_svma: 7ec298..80f67c, - eh_frame_hdr_hash: 6d6dd1e2c782318a, - eh_frame_hash: ee27244db791265a, - eh_frame_svma: 80f680..918a9c, - }, + ( + UnwindData { + path: "testdata/perf_map/ty_walltime", + base_svma: 0, + eh_frame_hdr_svma: 7ec298..80f67c, + eh_frame_hdr_hash: 6d6dd1e2c782318a, + eh_frame_hash: ee27244db791265a, + eh_frame_svma: 80f680..918a9c, + }, + ProcessUnwindData { + timestamp: None, + avma_range: 555555e6d000..555556813000, + base_avma: 555555554000, + }, + ), ) diff --git a/src/executor/wall_time/perf/snapshots/codspeed_runner__executor__wall_time__perf__unwind_data__tests__rust_divan_unwind_data.snap b/src/executor/wall_time/perf/snapshots/codspeed_runner__executor__wall_time__perf__unwind_data__tests__rust_divan_unwind_data.snap index ea13d0d6..0a007739 100644 --- a/src/executor/wall_time/perf/snapshots/codspeed_runner__executor__wall_time__perf__unwind_data__tests__rust_divan_unwind_data.snap +++ b/src/executor/wall_time/perf/snapshots/codspeed_runner__executor__wall_time__perf__unwind_data__tests__rust_divan_unwind_data.snap @@ -1,17 +1,21 @@ --- source: src/executor/wall_time/perf/unwind_data.rs -expression: "UnwindData::new(MODULE_PATH.as_bytes(), file_offset, start_addr, end_addr,\nNone)" +expression: "unwind_data_from_elf(MODULE_PATH.as_bytes(), start_addr, end_addr, None,\nload_bias,)" --- Ok( - UnwindData { - path: "testdata/perf_map/divan_sleep_benches.bin", - timestamp: None, - avma_range: 5555555a2000..555555692000, - base_avma: 555555554000, - base_svma: 0, - eh_frame_hdr_svma: 2ac74..2ea60, - eh_frame_hdr_hash: f579da4368e627c1, - eh_frame_hash: 791501d5a9c438d, - eh_frame_svma: 11540..2ac74, - }, + ( + UnwindData { + path: "testdata/perf_map/divan_sleep_benches.bin", + base_svma: 0, + eh_frame_hdr_svma: 2ac74..2ea60, + eh_frame_hdr_hash: f579da4368e627c1, + eh_frame_hash: 791501d5a9c438d, + eh_frame_svma: 11540..2ac74, + }, + ProcessUnwindData { + timestamp: None, + avma_range: 5555555a2000..555555692000, + base_avma: 555555554000, + }, + ), ) diff --git a/src/executor/wall_time/perf/snapshots/codspeed_runner__executor__wall_time__perf__unwind_data__tests__the_algorithms_unwind_data.snap b/src/executor/wall_time/perf/snapshots/codspeed_runner__executor__wall_time__perf__unwind_data__tests__the_algorithms_unwind_data.snap index 6ddc5f8b..60be9b07 100644 --- a/src/executor/wall_time/perf/snapshots/codspeed_runner__executor__wall_time__perf__unwind_data__tests__the_algorithms_unwind_data.snap +++ b/src/executor/wall_time/perf/snapshots/codspeed_runner__executor__wall_time__perf__unwind_data__tests__the_algorithms_unwind_data.snap @@ -1,17 +1,21 @@ --- source: src/executor/wall_time/perf/unwind_data.rs -expression: "UnwindData::new(MODULE_PATH.as_bytes(), file_offset, start_addr, end_addr,\nNone)" +expression: "unwind_data_from_elf(MODULE_PATH.as_bytes(), start_addr, end_addr, None,\nload_bias,)" --- Ok( - UnwindData { - path: "testdata/perf_map/the_algorithms.bin", - timestamp: None, - avma_range: 5555555a7000..5555556b0000, - base_avma: 555555554000, - base_svma: 0, - eh_frame_hdr_svma: 2f0ec..33590, - eh_frame_hdr_hash: 277fbdb59e6decaa, - eh_frame_hash: 21d8b0c8da0d1029, - eh_frame_svma: 128a8..2f0ec, - }, + ( + UnwindData { + path: "testdata/perf_map/the_algorithms.bin", + base_svma: 0, + eh_frame_hdr_svma: 2f0ec..33590, + eh_frame_hdr_hash: 277fbdb59e6decaa, + eh_frame_hash: 21d8b0c8da0d1029, + eh_frame_svma: 128a8..2f0ec, + }, + ProcessUnwindData { + timestamp: None, + avma_range: 5555555a7000..5555556b0000, + base_avma: 555555554000, + }, + ), ) diff --git a/src/executor/wall_time/perf/unwind_data.rs b/src/executor/wall_time/perf/unwind_data.rs index 045962ef..9d08046d 100644 --- a/src/executor/wall_time/perf/unwind_data.rs +++ b/src/executor/wall_time/perf/unwind_data.rs @@ -5,120 +5,109 @@ use anyhow::{Context, bail}; use debugid::CodeId; use object::Object; use object::ObjectSection; +use runner_shared::unwind_data::ProcessUnwindData; use runner_shared::unwind_data::UnwindData; use std::ops::Range; -pub trait UnwindDataExt { - fn new( - path_slice: &[u8], - runtime_file_offset: u64, - runtime_start_addr: u64, - runtime_end_addr: u64, - build_id: Option<&[u8]>, - ) -> anyhow::Result - where - Self: Sized; -} - -impl UnwindDataExt for UnwindData { - // Based on this: https://github.com/mstange/linux-perf-stuff/blob/22ca6531b90c10dd2a4519351c843b8d7958a451/src/main.rs#L747-L893 - fn new( - path_slice: &[u8], - runtime_file_offset: u64, - runtime_start_addr: u64, - runtime_end_addr: u64, - build_id: Option<&[u8]>, - ) -> anyhow::Result { - let avma_range = runtime_start_addr..runtime_end_addr; - - let path = String::from_utf8_lossy(path_slice).to_string(); - let Some(file) = std::fs::File::open(&path).ok() else { - bail!("Could not open file {path}"); - }; - - let mmap = unsafe { memmap2::MmapOptions::new().map(&file)? }; - let file = object::File::parse(&mmap[..])?; - - // Verify the build id (if we have one) - match (build_id, file.build_id()) { - (Some(build_id), Ok(Some(file_build_id))) => { - if build_id != file_build_id { - let file_build_id = CodeId::from_binary(file_build_id); - let expected_build_id = CodeId::from_binary(build_id); - bail!( - "File {path:?} has non-matching build ID {file_build_id} (expected {expected_build_id})" - ); - } - } - (Some(_), Err(_)) | (Some(_), Ok(None)) => { - bail!("File {path:?} does not contain a build ID, but we expected it to have one"); - } - _ => { - // No build id to check +// Based on: https://github.com/mstange/linux-perf-stuff/blob/22ca6531b90c10dd2a4519351c843b8d7958a451/src/main.rs#L747-L893 +pub fn unwind_data_from_elf( + path_slice: &[u8], + runtime_start_addr: u64, + runtime_end_addr: u64, + build_id: Option<&[u8]>, + load_bias: u64, +) -> anyhow::Result<(UnwindData, ProcessUnwindData)> { + let avma_range = runtime_start_addr..runtime_end_addr; + + let path = String::from_utf8_lossy(path_slice).to_string(); + let Some(file) = std::fs::File::open(&path).ok() else { + bail!("Could not open file {path}"); + }; + + let mmap = unsafe { memmap2::MmapOptions::new().map(&file)? }; + let file = object::File::parse(&mmap[..])?; + + // Verify the build id (if we have one) + match (build_id, file.build_id()) { + (Some(build_id), Ok(Some(file_build_id))) => { + if build_id != file_build_id { + let file_build_id = CodeId::from_binary(file_build_id); + let expected_build_id = CodeId::from_binary(build_id); + bail!( + "File {path:?} has non-matching build ID {file_build_id} (expected {expected_build_id})" + ); } - }; - - let base_avma = elf_helper::compute_base_avma( - runtime_start_addr, - runtime_end_addr, - runtime_file_offset, - &file, - )?; - let base_svma = elf_helper::relative_address_base(&file); - let eh_frame = file.section_by_name(".eh_frame"); - let eh_frame_hdr = file.section_by_name(".eh_frame_hdr"); - - fn section_data<'a>(section: &impl ObjectSection<'a>) -> Option> { - section.data().ok().map(|data| data.to_owned()) } + (Some(_), Err(_)) | (Some(_), Ok(None)) => { + bail!("File {path:?} does not contain a build ID, but we expected it to have one"); + } + _ => { + // No build id to check + } + }; - let eh_frame_data = eh_frame.as_ref().and_then(section_data); - let eh_frame_hdr_data = eh_frame_hdr.as_ref().and_then(section_data); + let base_svma = elf_helper::relative_address_base(&file); + let base_avma = elf_helper::compute_base_avma(base_svma, load_bias); + let eh_frame = file.section_by_name(".eh_frame"); + let eh_frame_hdr = file.section_by_name(".eh_frame_hdr"); - fn svma_range<'a>(section: &impl ObjectSection<'a>) -> Range { - section.address()..section.address() + section.size() - } + fn section_data<'a>(section: &impl ObjectSection<'a>) -> Option> { + section.data().ok().map(|data| data.to_owned()) + } - Ok(UnwindData { - path, - timestamp: None, - avma_range, - base_avma, - base_svma, - eh_frame_hdr: eh_frame_hdr_data.context("Failed to find eh_frame hdr data")?, - eh_frame_hdr_svma: eh_frame_hdr - .as_ref() - .map(svma_range) - .context("Failed to find eh_frame hdr section")?, - eh_frame: eh_frame_data.context("Failed to find eh_frame data")?, - eh_frame_svma: eh_frame - .as_ref() - .map(svma_range) - .context("Failed to find eh_frame section")?, - }) + let eh_frame_data = eh_frame.as_ref().and_then(section_data); + let eh_frame_hdr_data = eh_frame_hdr.as_ref().and_then(section_data); + + fn svma_range<'a>(section: &impl ObjectSection<'a>) -> Range { + section.address()..section.address() + section.size() } + + let v3 = UnwindData { + path: path.clone(), + base_svma, + eh_frame_hdr: eh_frame_hdr_data.context("Failed to find eh_frame hdr data")?, + eh_frame_hdr_svma: eh_frame_hdr + .as_ref() + .map(svma_range) + .context("Failed to find eh_frame hdr section")?, + eh_frame: eh_frame_data.context("Failed to find eh_frame data")?, + eh_frame_svma: eh_frame + .as_ref() + .map(svma_range) + .context("Failed to find eh_frame section")?, + }; + + let mapping = ProcessUnwindData { + // We do not support timestamp in elf unwind data for now + timestamp: None, + avma_range, + base_avma, + }; + + Ok((v3, mapping)) } #[cfg(test)] mod tests { use super::*; - macro_rules! assert_elf_load_bias { - ($start_addr:expr, $end_addr:expr, $file_offset:expr, $module_path:expr, $expected_load_bias:expr) => { - let expected_load_bias = $expected_load_bias as u64; - - let file_data = std::fs::read($module_path).expect("Failed to read test binary"); - let object = object::File::parse(&file_data[..]).expect("Failed to parse test binary"); - let load_bias = - elf_helper::compute_load_bias($start_addr, $end_addr, $file_offset, &object) - .unwrap(); - println!("Load bias for {}: 0x{:x}", $module_path, load_bias); - assert_eq!( - load_bias, expected_load_bias, - "Invalid load bias: {:x} != {:x}", - load_bias, expected_load_bias - ); - }; + fn assert_and_get_load_bias( + start_addr: u64, + end_addr: u64, + file_offset: u64, + module_path: &str, + expected_load_bias: u64, + ) -> u64 { + let file_data = std::fs::read(module_path).expect("Failed to read test binary"); + let object = object::File::parse(&file_data[..]).expect("Failed to parse test binary"); + let load_bias = + elf_helper::compute_load_bias(start_addr, end_addr, file_offset, &object).unwrap(); + println!("Load bias for {module_path}: 0x{load_bias:x}"); + assert_eq!( + load_bias, expected_load_bias, + "Invalid load bias: {load_bias:x} != {expected_load_bias:x}" + ); + load_bias } // Note: You can double-check the values by getting the /proc//maps via gdb: @@ -145,13 +134,14 @@ mod tests { let (start_addr, end_addr, file_offset) = (0x0000000000402000_u64, 0x000000000050f000_u64, 0x2000); - assert_elf_load_bias!(start_addr, end_addr, file_offset, MODULE_PATH, 0x0); - insta::assert_debug_snapshot!(UnwindData::new( + let load_bias = + assert_and_get_load_bias(start_addr, end_addr, file_offset, MODULE_PATH, 0x0); + insta::assert_debug_snapshot!(unwind_data_from_elf( MODULE_PATH.as_bytes(), - file_offset, start_addr, end_addr, - None + None, + load_bias, )); } @@ -166,14 +156,15 @@ mod tests { let (start_addr, end_addr, file_offset) = (0x0000000000400000_u64, 0x0000000000459000_u64, 0x0); - assert_elf_load_bias!(start_addr, end_addr, file_offset, MODULE_PATH, 0x0); + let load_bias = + assert_and_get_load_bias(start_addr, end_addr, file_offset, MODULE_PATH, 0x0); - insta::assert_debug_snapshot!(UnwindData::new( + insta::assert_debug_snapshot!(unwind_data_from_elf( MODULE_PATH.as_bytes(), - file_offset, start_addr, end_addr, None, + load_bias, )); } @@ -183,19 +174,19 @@ mod tests { let (start_addr, end_addr, file_offset) = (0x00005555555a2000_u64, 0x0000555555692000_u64, 0x4d000); - assert_elf_load_bias!( + let load_bias = assert_and_get_load_bias( start_addr, end_addr, file_offset, MODULE_PATH, - 0x555555554000 + 0x555555554000, ); - insta::assert_debug_snapshot!(UnwindData::new( + insta::assert_debug_snapshot!(unwind_data_from_elf( MODULE_PATH.as_bytes(), - file_offset, start_addr, end_addr, - None + None, + load_bias, )); } @@ -211,19 +202,19 @@ mod tests { const MODULE_PATH: &str = "testdata/perf_map/the_algorithms.bin"; let (start_addr, end_addr, file_offset) = (0x00005555555a7000, 0x00005555556b0000, 0x52000); - assert_elf_load_bias!( + let load_bias = assert_and_get_load_bias( start_addr, end_addr, file_offset, MODULE_PATH, - 0x555555554000 + 0x555555554000, ); - insta::assert_debug_snapshot!(UnwindData::new( + insta::assert_debug_snapshot!(unwind_data_from_elf( MODULE_PATH.as_bytes(), - file_offset, start_addr, end_addr, - None + None, + load_bias, )); } @@ -239,20 +230,20 @@ mod tests { const MODULE_PATH: &str = "testdata/perf_map/ty_walltime"; let (start_addr, end_addr, file_offset) = (0x0000555555e6d000_u64, 0x0000555556813000_u64, 0x918000); - assert_elf_load_bias!( + let load_bias = assert_and_get_load_bias( start_addr, end_addr, file_offset, MODULE_PATH, - 0x555555554000 + 0x555555554000, ); - insta::assert_debug_snapshot!(UnwindData::new( + insta::assert_debug_snapshot!(unwind_data_from_elf( MODULE_PATH.as_bytes(), - file_offset, start_addr, end_addr, - None + None, + load_bias, )); } }