9 个版本 (破坏性)

0.23.0 2024 年 6 月 28 日
0.22.2 2024 年 6 月 28 日
0.22.0 2024 年 4 月 15 日
0.21.0 2024 年 2 月 12 日
0.17.1 2022 年 7 月 21 日

#185 in 性能分析

Download history 530/week @ 2024-04-14 207/week @ 2024-04-21 220/week @ 2024-04-28 265/week @ 2024-05-05 201/week @ 2024-05-12 304/week @ 2024-05-19 282/week @ 2024-05-26 406/week @ 2024-06-02 259/week @ 2024-06-09 427/week @ 2024-06-16 485/week @ 2024-06-23 283/week @ 2024-06-30 187/week @ 2024-07-07 206/week @ 2024-07-14 158/week @ 2024-07-21 196/week @ 2024-07-28

每月 755 次下载
用于 5 个包(直接使用 2 个)

MIT/Apache

370KB
7.5K SLoC

samply-symbols

此包允许从二进制文件和编译工件中获取符号信息。

您可能希望使用 wholesym 代替。 wholesym 提供了一个更加便捷的 API;它是 samply-symbols 的包装器。

更具体地说,samply-symbols 提供了 wholesym 的底层实现,同时满足本地和 WebAssembly 客户端的需求,而 wholesym 仅关注本地客户端。

此包的主要入口点是 SymbolManager 结构及其异步 load_symbol_map 方法。有了 SymbolMap,您可以将原始代码地址解析为函数名字符串,如果有的话,还可以解析为文件名 + 行号信息和内联堆栈。

设计约束

此包在以下设计约束下运行

  • 必须可用于 JavaScript / WebAssembly:Firefox 分析器在 WebAssembly 环境中运行此代码,由 Firefox 内部特权 JavaScript 代码调用。这种设置允许我们按需下载 wasm 打包,而不是将其与 Firefox 一起分发,这将增加 Firefox 下载大小,而大多数 Firefox 用户不需要这项功能。
  • 性能:我们希望能够尽快从本地编译的 Firefox 实例的新构建中获取符号数据,而不需要昂贵的预处理步骤。从“编译完成”到“返回符号数据”的时间应尽可能短。这意味着需要直接从编译工件而不是,例如,从 dSYM 打包或 Breakpad .sym 文件中获取符号数据。
  • 必须扩展到大型输入:这适用于API请求的大小以及需要解析的对象文件的大小:Firefox分析器在单个符号化请求中可以提供数万到数十万个不同的代码地址。Firefox构建工件(如libxul.so)可以大达数GB,包含大约300000个函数符号。我们希望在几秒钟内或更短的时间内处理此类请求。
  • “尽力而为”的原则:如果只有有限的符号信息可用,例如来自系统库,我们希望返回我们所拥有的任何有限信息。

WebAssembly的要求意味着这个crate不能包含任何直接文件访问。相反,所有文件访问都通过一个必须由调用者实现的FileAndPathHelper特质进行调解。我们甚至不能使用std::path::Path / PathBuf类型来表示路径,因为WASM包可以在Windows上运行,而Path / PathBuf类型有!Rust编译到WebAssembly的Unix路径语义。

此外,调用者需要能够根据有关库的信息子集找到正确的符号文件,例如仅基于其调试名称和调试ID。当使用此类信息子集调用SymbolManager::load_symbol_map时,这被使用。更具体地说,此功能在处理JSON符号化API调用时由samply-api使用,该调用仅包含库的调试名称和调试ID。

支持的格式和数据

此crate支持从PE二进制文件(Windows)、PDB文件(Windows)、mach-o二进制文件(包括fat二进制文件)(macOS & iOS)和ELF二进制文件(Linux、Android等)获取符号数据。对于mach-o文件,它还支持通过遵循OSO stabs条目在外部对象中查找调试信息。它支持收集基本符号信息(函数名字符串)以及基于调试数据的信息,即具有函数名、文件名和行号的内联调用栈。对于调试数据,我们支持DWARF调试数据(在mach-o和ELF二进制文件中)和PDB调试数据。

示例

use samply_symbols::debugid::DebugId;
use samply_symbols::{
    CandidatePathInfo, FileAndPathHelper, FileAndPathHelperResult, FileLocation,
    FramesLookupResult, LibraryInfo, OptionallySendFuture, SymbolManager,
};

async fn run_query() {
    let this_dir = std::path::PathBuf::from(env!("CARGO_MANIFEST_DIR"));
    let helper = ExampleHelper {
        artifact_directory: this_dir.join("..").join("fixtures").join("win64-ci"),
    };

    let symbol_manager = SymbolManager::with_helper(&helper);

    let library_info = LibraryInfo {
        debug_name: Some("firefox.pdb".to_string()),
        debug_id: DebugId::from_breakpad("AA152DEB2D9B76084C4C44205044422E1").ok(),
        ..Default::default()
    };
    let symbol_map = match symbol_manager.load_symbol_map(&library_info).await {
        Ok(symbol_map) => symbol_map,
        Err(e) => {
            println!("Error while loading the symbol map: {:?}", e);
            return;
        }
    };

    // Look up the symbol for an address.
    let lookup_result = symbol_map.lookup_relative_address(0x1f98f);

    match lookup_result {
        Some(address_info) => {
            // Print the symbol name for this address:
            println!("0x1f98f: {}", address_info.symbol.name);

            // See if we have debug info (file name + line, and inlined frames):
            match address_info.frames {
                FramesLookupResult::Available(frames) => {
                    println!("Debug info:");
                    for frame in frames {
                        println!(
                            " - {:?} ({:?}:{:?})",
                            frame.function, frame.file_path, frame.line_number
                        );
                    }
                }
                FramesLookupResult::External(ext_address) => {
                    // Debug info is located in a different file.
                    if let Some(frames) =
                        symbol_manager.lookup_external(&symbol_map.debug_file_location(), &ext_address).await
                    {
                        println!("Debug info:");
                        for frame in frames {
                            println!(
                                " - {:?} ({:?}:{:?})",
                                frame.function, frame.file_path, frame.line_number
                            );
                        }
                    }
                }
                FramesLookupResult::Unavailable => {}
            }
        }
        None => {
            println!("No symbol was found for address 0x1f98f.")
        }
    }
}

struct ExampleHelper {
    artifact_directory: std::path::PathBuf,
}

impl FileAndPathHelper for ExampleHelper {
    type F = Vec<u8>;
    type FL = ExampleFileLocation;
    type OpenFileFuture = std::pin::Pin<
        Box<dyn OptionallySendFuture<Output = FileAndPathHelperResult<Self::F>> + 'h>,
    >;

    fn get_candidate_paths_for_debug_file(
        &self,
        library_info: &LibraryInfo,
    ) -> FileAndPathHelperResult<Vec<CandidatePathInfo<ExampleFileLocation>>> {
        if let Some(debug_name) = library_info.debug_name.as_deref() {
            Ok(vec![CandidatePathInfo::SingleFile(ExampleFileLocation(
                self.artifact_directory.join(debug_name),
            ))])
        } else {
            Ok(vec![])
        }
    }

    fn get_candidate_paths_for_binary(
        &self,
        library_info: &LibraryInfo,
    ) -> FileAndPathHelperResult<Vec<CandidatePathInfo<ExampleFileLocation>>> {
        if let Some(name) = library_info.name.as_deref() {
            Ok(vec![CandidatePathInfo::SingleFile(ExampleFileLocation(
                self.artifact_directory.join(name),
            ))])
        } else {
            Ok(vec![])
        }
    }

   fn get_dyld_shared_cache_paths(
       &self,
       _arch: Option<&str>,
   ) -> FileAndPathHelperResult<Vec<ExampleFileLocation>> {
       Ok(vec![])
   }

    fn load_file(
        &'h self,
        location: ExampleFileLocation,
    ) -> std::pin::Pin<
        Box<dyn OptionallySendFuture<Output = FileAndPathHelperResult<Self::F>> + 'h>,
    > {
        async fn load_file_impl(path: std::path::PathBuf) -> FileAndPathHelperResult<Vec<u8>> {
            Ok(std::fs::read(&path)?)
        }

        Box::pin(load_file_impl(location.0))
    }
}

#[derive(Clone, Debug)]
struct ExampleFileLocation(std::path::PathBuf);

impl std::fmt::Display for ExampleFileLocation {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        self.0.to_string_lossy().fmt(f)
    }
}

impl FileLocation for ExampleFileLocation {
    fn location_for_dyld_subcache(&self, suffix: &str) -> Option<Self> {
        let mut filename = self.0.file_name().unwrap().to_owned();
        filename.push(suffix);
        Some(Self(self.0.with_file_name(filename)))
    }

    fn location_for_external_object_file(&self, object_file: &str) -> Option<Self> {
        Some(Self(object_file.into()))
    }

    fn location_for_pdb_from_binary(&self, pdb_path_in_binary: &str) -> Option<Self> {
        Some(Self(pdb_path_in_binary.into()))
    }

    fn location_for_source_file(&self, source_file_path: &str) -> Option<Self> {
        Some(Self(source_file_path.into()))
    }

    fn location_for_breakpad_symindex(&self) -> Option<Self> {
        Some(Self(self.0.with_extension("symindex")))
    }
}

依赖项

~10MB
~204K SLoC