Skip to content

Commit

Permalink
feat: add symbolizer
Browse files Browse the repository at this point in the history
  • Loading branch information
noneback committed Nov 28, 2024
1 parent f0b4f52 commit 22defbd
Show file tree
Hide file tree
Showing 26 changed files with 381 additions and 291 deletions.
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ target/
**/.vscode/
.vim/
.vscode/
*.txt
tests/


# Remove Cargo.lock from gitignore if creating an executable, leave it for libraries
Expand Down
10 changes: 10 additions & 0 deletions .rustfmt.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@

edition = "2018"
newline_style = "unix"
# comments
normalize_comments=true
wrap_comments=true
# imports
imports_granularity="Crate"
group_imports="StdExternalCrate"

8 changes: 6 additions & 2 deletions doctor/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -13,12 +13,16 @@ env_logger = "0.10"
libc = "0.2"
log = "0.4"
tokio = { version = "1.25", features = ["macros", "rt", "rt-multi-thread", "net", "signal", "time"] }
blazesym = "0.2.0-rc.0"
thiserror = "1.0.63"
procfs = "0.16.0"
clap = "4.5.9"
goblin = {version = "0.8.2", features = ["elf32","elf32"]}
moka = { version = "0.12.8", features = ["future"] }
moka = { version = "0.12.8", features = ["future","sync"] }
sled = { version = "0.34.7" }
gimli = "0.31.0"
memmap2 = "0.9.4"
wholesym = "0.7.0"
symbolic = { version = "12.12.3", features = ["demangle"] }

[[bin]]
name = "doctor"
Expand Down
2 changes: 1 addition & 1 deletion doctor/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
pub mod profiler;
pub mod symbolizer;
// pub mod symbolizer;
1 change: 1 addition & 0 deletions doctor/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,7 @@ async fn main() -> Result<(), anyhow::Error> {
running.store(false, Ordering::SeqCst);
});


let mut translator = Translator::new("/".into());
while running_clone.load(Ordering::SeqCst) {
match stacks.pop(0) {
Expand Down
65 changes: 0 additions & 65 deletions doctor/src/profiler/dso.rs

This file was deleted.

12 changes: 12 additions & 0 deletions doctor/src/profiler/error.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
use thiserror::Error;

use super::symbolizer::error::SymbolizerError;

#[derive(Error, Debug)]
pub enum TranslateError {
#[error("data not found")]
Expand All @@ -10,4 +12,14 @@ pub enum TranslateError {
Internal,
#[error(transparent)]
Io(#[from] std::io::Error), // 使用`from`属性自动从`std::io::Error`转换
#[error("symbolize {0}")]
Symbolize(SymbolizerError),
#[error("anyhow {0}")]
AnyError(#[from] anyhow::Error),
}

impl From<SymbolizerError> for TranslateError {
fn from(err: SymbolizerError) -> Self {
TranslateError::Symbolize(err)
}
}
4 changes: 0 additions & 4 deletions doctor/src/profiler/formater.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1 @@





2 changes: 1 addition & 1 deletion doctor/src/profiler/mod.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
pub mod dso;
pub mod error;
pub mod formater;
pub mod perf_record;
pub mod process;
pub mod profiler;
pub mod symbolizer;
pub mod translator;
6 changes: 4 additions & 2 deletions doctor/src/profiler/perf_record.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@ pub struct PerfRecord {
pub ts: u64,
pub cycle: u64,
pub frames: Vec<PerfStackFrame>, // pub kframes: Option<Vec<PerfStackFrame>>,
// pub uframes: Option<Vec<PerfStackFrame>>,
}

pub struct PerfStackFrame {
Expand All @@ -33,7 +32,10 @@ impl PerfStackFrame {

impl fmt::Display for PerfRecord {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
let mut format_str = format!("{} {} {}\n", self.pid, self.cpu_id, self.cmdline);
let mut format_str = format!(
"{} {} {} {}\n",
self.pid, self.cpu_id, self.tgid, self.cmdline
);
for frame in &self.frames {
format_str.push_str(
format!(
Expand Down
4 changes: 2 additions & 2 deletions doctor/src/profiler/process.rs
Original file line number Diff line number Diff line change
Expand Up @@ -46,8 +46,8 @@ impl ProcessMetadata {
match &mapper.pathname {
procfs::process::MMapPath::Path(p) => {
let path = match p.to_str().unwrap().strip_suffix(" (deleted)") {
Some(striped) => self.rootfs.join(striped),
None => self.rootfs.join(p),
Some(striped) => PathBuf::from(striped),
None => p.clone(),
};

Ok((path, mapper.offset + (v_addr - mapper.address.0)))
Expand Down
175 changes: 175 additions & 0 deletions doctor/src/profiler/symbolizer/elf.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,175 @@
use std::{
collections::BTreeSet,
fs::{metadata, File},
os::linux::fs::MetadataExt,
path::PathBuf,
};
use symbolic::{
common::Name,
demangle::{Demangle, DemangleOptions},
};

use super::{error::SymbolizerError, symbol::Symbol};

use goblin::elf::{program_header::PT_LOAD, Elf, ProgramHeader};
use log::debug;
use memmap2::MmapOptions;
use wholesym::{SymbolManager, SymbolManagerConfig};

#[derive(Debug, Clone)]
pub struct ElfMetadata {
path: PathBuf,
debug_info: BTreeSet<Symbol>,
pt_loads: Vec<ProgramHeader>,
inode: u64,
}

impl ElfMetadata {
pub fn load_sym_from_elf(
path: &PathBuf,
) -> Result<(BTreeSet<Symbol>, Vec<ProgramHeader>), SymbolizerError> {
let file = File::open(path.clone())
.map_err(|e| SymbolizerError::SymbolStoreIOFailed(path.clone(), e))?;
let mmap = unsafe {
MmapOptions::new()
.map(&file)
.map_err(|e| SymbolizerError::MMapIOFailed(path.clone(), e))?
};

let elf = Elf::parse(&mmap).expect("Failed to parse ELF file");

let pt_loads = elf
.program_headers
.clone()
.into_iter()
.filter(|h| h.p_type == PT_LOAD)
.collect::<Vec<ProgramHeader>>();

let mut debug_info = BTreeSet::new();
tokio::task::block_in_place(|| {
let symbol_manager = SymbolManager::with_config(SymbolManagerConfig::default());
let symbol_map_f = symbol_manager.load_symbol_map_for_binary_at_path(path, None);
let symbol_map = tokio::runtime::Handle::current()
.block_on(symbol_map_f)
.unwrap(); // TODO: deal with

debug!("eso path: {:?}, build {}", &path, symbol_map.debug_id());
let opt = DemangleOptions::name_only();
symbol_map.iter_symbols().for_each(|s| {
let demangled = Name::from(s.1).try_demangle(opt).to_string();

debug_info.insert(Symbol {
addr: s.0 as u64,
name: Some(demangled),
});
});

symbol_map.debug_id()
});

Ok((debug_info, pt_loads))
}
pub fn load_sym_from_dwarf(path: &PathBuf) -> Result<BTreeSet<Symbol>, SymbolizerError> {
let mut debug_info = BTreeSet::new();
tokio::task::block_in_place(|| {
let symbol_manager = SymbolManager::with_config(SymbolManagerConfig::default());
let symbol_map_f = symbol_manager.load_symbol_map_for_binary_at_path(path, None);
let symbol_map = tokio::runtime::Handle::current()
.block_on(symbol_map_f)
.unwrap();

debug!("eso path: {:?}, build {}", &path, symbol_map.debug_id());
symbol_map.iter_symbols().for_each(|s| {
debug_info.insert(Symbol {
addr: s.0 as u64,
name: Some(s.1.to_string()),
});
});
});

Ok(debug_info)
}

pub fn get_inode(path: &PathBuf) -> Result<u64, SymbolizerError> {
let f_meta = metadata(path).map_err(SymbolizerError::GetInodeFailed)?;
Ok(f_meta.st_ino())
}

pub fn new(path: PathBuf) -> Result<ElfMetadata, SymbolizerError> {
let (debug_info, pt_loads) = ElfMetadata::load_sym_from_elf(&path)?;
let inode = Self::get_inode(&path)?;
Ok(Self {
path,
debug_info,
pt_loads,
inode,
})
}

// translate file offset -> relative offset
fn translate(&self, file_offset: u64) -> Option<u64> {
if !self.pt_loads.is_empty() {
Some(file_offset - self.pt_loads[0].p_offset)
} else {
None
}
}

pub fn find_symbol(&self, offset: u64) -> Result<Symbol, SymbolizerError> {
match self.translate(offset) {
Some(relative_offset) => {
let target = Symbol {
addr: relative_offset,
name: None,
};
match self.debug_info.range(..target).next_back() {
Some(sym) => Ok(sym.clone()),
None => Err(SymbolizerError::SymbolNotFound(
self.path.clone(),
relative_offset,
)),
}
}

None => Err(SymbolizerError::TranslateVirtOffsetFailed(
self.path.clone(),
offset,
)),
}
}
}

// #[cfg(test)]
// mod tests {
// use super::*;
// #[tokio::main]
// #[test]
// async fn test_sysm() {
// let elf = ElfMetadata::new(
// "/root/.vscode-server/bin/8b3775030ed1a69b13e4f4c628c612102e30a681/node".into(),
// )
// .unwrap();
// println!(
// "debug info {}, syms {}",
// elf.debug_info.len(),
// elf.syms.len()
// );
// let mut debug_info: Vec<_> = elf.debug_info.iter().map(|s| s).collect();
// let mut syms: Vec<_> = elf.syms.iter().map(|s| s).collect();
// debug_info.sort_by_key(|s| s.addr);
// syms.sort_by_key(|s| s.addr);
// // let mut miss = 0;

// // elf.syms.iter().for_each(|s| {
// // if !debug_info.contains(s) {
// // miss += 1;
// // }
// // println!("sym {:?}", s);
// // });
// println!(
// "debug info {:#?}, syms {:#?}",
// &debug_info[0..10],
// &syms[0..10]
// );
// }
// }
22 changes: 22 additions & 0 deletions doctor/src/profiler/symbolizer/error.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
use std::path::PathBuf;
use thiserror::Error;

#[derive(Error, Debug)]
pub enum SymbolizerError {
#[error("fail to open symbol store: {0}")]
OpenSymbolStoreDiskDBFailed(#[from] sled::Error),
#[error("failed to fetch elf: {0}")]
FetchElfFailed(PathBuf),
#[error("load {0}, elf failed: {1}")]
SymbolStoreIOFailed(PathBuf, std::io::Error),
#[error("symbol not found in {0}, off {1}")]
SymbolNotFound(PathBuf, u64),
#[error("translate {0} f_offset {1} to virt_offset failed")]
TranslateVirtOffsetFailed(PathBuf, u64),
#[error("load {0}, elf failed: {1}")]
MMapIOFailed(PathBuf, std::io::Error),
#[error("get inode: {0}")]
GetInodeFailed(std::io::Error),
#[error("gmili load: {0}")]
GmiliFailed(#[from] gimli::Error),
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
pub mod elf;
pub mod error;
pub mod symbol;
pub mod symbol_store;
pub mod symbolizer;
Loading

0 comments on commit 22defbd

Please sign in to comment.