Skip to content

Commit

Permalink
Add support for debuginfod resolver
Browse files Browse the repository at this point in the history
Signed-off-by: Daniel Hodges <hodges.daniel.scott@gmail.com>
  • Loading branch information
hodgesds committed Dec 12, 2023
1 parent 31935e2 commit 6dad983
Show file tree
Hide file tree
Showing 6 changed files with 389 additions and 0 deletions.
1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ members = [
".",
"capi",
"cli",
"debuginfod",
]

[package]
Expand Down
12 changes: 12 additions & 0 deletions debuginfod/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
[package]
name = "blazesym-debuginfod"
version = "0.0.0"
edition = "2021"


[dependencies]
anyhow = "1.0.68"
dirs = "5.0.1"
reqwest = {version = "0.11.18", features = ["blocking", "gzip"]}
blazesym = {path = ".."}

102 changes: 102 additions & 0 deletions debuginfod/examples/debuginfod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
use std::env;

use anyhow::bail;
use anyhow::Context as _;
use anyhow::Result;

use blazesym::symbolize::CodeInfo;
use blazesym::symbolize::Input;
use blazesym::symbolize::Sym;
use blazesym::symbolize::Symbolized;
use blazesym::symbolize::Symbolizer;
use blazesym::Addr;
use blazesym_debuginfod::client::DebugInfodClient;
use blazesym_debuginfod::resolver::DebugInfodResolver;

const ADDR_WIDTH: usize = 16;


fn print_frame(name: &str, addr_info: Option<(Addr, Addr, usize)>, code_info: &Option<CodeInfo>) {
let code_info = code_info.as_ref().map(|code_info| {
let path = code_info.to_path();
let path = path.display();

match (code_info.line, code_info.column) {
(Some(line), Some(col)) => format!(" {path}:{line}:{col}"),
(Some(line), None) => format!(" {path}:{line}"),
(None, _) => format!(" {path}"),
}
});

if let Some((input_addr, addr, offset)) = addr_info {
// If we have various address information bits we have a new symbol.
println!(
"{input_addr:#0width$x}: {name} @ {addr:#x}+{offset:#x}{code_info}",
code_info = code_info.as_deref().unwrap_or(""),
width = ADDR_WIDTH
)
} else {
// Otherwise we are dealing with an inlined call.
println!(
"{:width$} {name}{code_info} [inlined]",
" ",
code_info = code_info
.map(|info| format!(" @{info}"))
.as_deref()
.unwrap_or(""),
width = ADDR_WIDTH
)
}
}


fn main() -> Result<()> {
let args = env::args().collect::<Vec<_>>();

if args.len() != 3 {
bail!(
"Usage: {} <build-id> <address>",
args.first().map(String::as_str).unwrap_or("addr2ln_pid")
);
}
let val = env::var("DEBUGINFOD_URLS")?;
let urls = DebugInfodClient::parse_debuginfo_urls(val)?;

let build_id = &args[1];
let addr_str = &args[2][..];
let resolver = DebugInfodResolver::default_resolver(urls)?;
let build_id_str = build_id.as_str();
let src = resolver.debug_info_from(build_id_str.as_bytes(), None)?;
let symbolizer = Symbolizer::new();

let addr = Addr::from_str_radix(addr_str.trim_start_matches("0x"), 16)
.with_context(|| format!("failed to parse address: {addr_str}"))?;

let addrs = [addr];
let syms = symbolizer
.symbolize(&src, Input::VirtOffset(&addrs))
.with_context(|| format!("failed to symbolize address {addr:#x}"))?;

for (input_addr, sym) in addrs.iter().copied().zip(syms) {
match sym {
Symbolized::Sym(Sym {
name,
addr,
offset,
code_info,
inlined,
..
}) => {
print_frame(&name, Some((input_addr, addr, offset)), &code_info);
for frame in inlined.iter() {
print_frame(&frame.name, None, &frame.code_info);
}
}
Symbolized::Unknown => {
println!("{input_addr:#0width$x}: <no-symbol>", width = ADDR_WIDTH)
}
}
}

Ok(())
}
117 changes: 117 additions & 0 deletions debuginfod/src/client.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
use std::io::Write;

use anyhow::anyhow;
use anyhow::Result;
use reqwest::blocking::Client;
use reqwest::blocking::Request;
use reqwest::Method;
use reqwest::Url;

#[derive(Debug)]
pub struct DebugInfodClient {
// See:
// https://github.com/seanmonstar/reqwest/issues/988
base_url: Url,
client: Client,
}

impl DebugInfodClient {
pub fn new(client: Client, base_url: Url) -> DebugInfodClient {
DebugInfodClient { base_url, client }
}

/// Helper method to return build_id as a String.
pub fn format_build_id(build_id: &[u8]) -> Result<String> {
Ok(String::from_utf8(build_id.to_vec())?)
}

pub fn base_path_for(build_id: &[u8]) -> Result<String> {
Ok(format!("{}/", DebugInfodClient::format_build_id(build_id)?))
}

/// Returns the debug info from a debuginfod source. Writes are buffered into the writer.
pub fn get_debug_info(&self, build_id: &[u8], dest: &mut impl Write) -> Result<()> {
// /buildid/<BUILDID>/debuginfo
let url = self
.base_url
.clone()
.join("buildid/")?
.join(DebugInfodClient::base_path_for(build_id)?.as_str())?
.join("debuginfo")?;
let mut res = self.client.execute(Request::new(Method::GET, url))?;
if res.status().is_success() {
res.copy_to(dest)?;
Ok(dest.flush()?)
} else {
Err(anyhow!(format!("request error {}", res.status())))
}
}

/// Returns an executable from a debuginfod source. Writes are buffered into
/// the writer.
pub fn get_executable(&self, build_id: &[u8], dest: &mut impl Write) -> Result<()> {
// /buildid/<BUILDID>/executable
let url = self
.base_url
.join("buildid/")?
.join(DebugInfodClient::base_path_for(build_id)?.as_str())?
.join("executable")?;
let mut res = self.client.execute(Request::new(Method::GET, url))?;
res.copy_to(dest)?;
Ok(dest.flush()?)
}

/// Returns a vector of URLs based on format of the DEBUGINFOD_URLS
/// environment variable value, which is either a comma separated or
/// whitespace separated list of URLs.
pub fn parse_debuginfo_urls(var: String) -> Result<Vec<Url>> {
let res: Vec<String> = var.split([',', ' ']).map(|v| v.to_string()).collect();
let urls: Vec<Url> = res.iter().filter_map(|v| Url::parse(v).ok()).collect();
Ok(urls)
}

/// Returns a set of default clients based on the get_default_servers method.
pub fn get_default_clients(urls: Vec<Url>) -> Result<Vec<DebugInfodClient>> {
Ok(urls
.into_iter()
.map(|base_url| DebugInfodClient::new(Client::new(), base_url))
.collect::<Vec<DebugInfodClient>>())
}
}

#[cfg(test)]
mod tests {
use super::*;
#[test]
fn build_id() {
let bytes = b"\x47\x91\x72\x34\xfb\xbd\x1c\x72\x88\x68\x4d\x92\x32\xfe\x2a\x27\x9e\x96\xfa";
let slice: &[u8] = bytes;
let expected = "47917234fbfbd1c7288684d9232fe2a279e96fa4";
let formatted_build_id = DebugInfodClient::format_build_id(slice).unwrap();
assert_eq!(formatted_build_id, expected);
}
#[test]
fn parse_debuginfo_urls_ws_separated() {
let vars = "https://debug.infod https://de.bug.info.d";
let parsed_urls = DebugInfodClient::parse_debuginfo_urls(vars.to_string()).unwrap();
assert_eq!(
parsed_urls,
vec![
Url::parse("https://debug.infod").ok().unwrap(),
Url::parse("https://de.bug.info.d").ok().unwrap(),
],
);
}
#[test]
fn parse_debuginfo_urls_comma_separated() {
let vars = "https://debug.infod,https://de.bug.info.d";
let parsed_urls = DebugInfodClient::parse_debuginfo_urls(vars.to_string()).unwrap();
assert_eq!(
parsed_urls,
vec![
Url::parse("https://debug.infod").ok().unwrap(),
Url::parse("https://de.bug.info.d").ok().unwrap(),
],
);
}
}
2 changes: 2 additions & 0 deletions debuginfod/src/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
pub mod client;
pub mod resolver;
Loading

0 comments on commit 6dad983

Please sign in to comment.