use std::collections::HashMap;
use std::num::NonZeroU32;
use samply_symbols::{
FileAndPathHelper, FramesLookupResult, LibraryInfo, LookupAddress, SymbolManager,
};
use crate::api_file_path::to_api_file_path;
use crate::error::Error;
use crate::to_debug_id;
pub mod looked_up_addresses;
pub mod request_json;
pub mod response_json;
use looked_up_addresses::{AddressResults, LookedUpAddresses};
use request_json::Lib;
use serde_json::json;
pub struct SymbolicateApi<'a, H: FileAndPathHelper> {
symbol_manager: &'a SymbolManager<H>,
}
impl<'a, H: FileAndPathHelper> SymbolicateApi<'a, H> {
pub fn new(symbol_manager: &'a SymbolManager<H>) -> Self {
Self { symbol_manager }
}
pub async fn query_api_json(&self, request_json: &str) -> String {
match self.query_api_fallible_json(request_json).await {
Ok(response_json) => response_json,
Err(err) => json!({ "error": err.to_string() }).to_string(),
}
}
pub async fn query_api_fallible_json(&self, request_json: &str) -> Result<String, Error> {
let request: request_json::Request = serde_json::from_str(request_json)?;
let response = self.query_api(&request).await?;
Ok(serde_json::to_string(&response)?)
}
pub async fn query_api(
&self,
request: &request_json::Request,
) -> Result<response_json::Response, Error> {
let requested_addresses = gather_requested_addresses(request)?;
let symbolicated_addresses = self
.symbolicate_requested_addresses(requested_addresses)
.await;
Ok(create_response(request, symbolicated_addresses))
}
async fn symbolicate_requested_addresses(
&self,
requested_addresses: HashMap<Lib, Vec<u32>>,
) -> HashMap<Lib, Result<LookedUpAddresses, samply_symbols::Error>> {
let mut symbolicated_addresses = HashMap::new();
for (lib, addresses) in requested_addresses.into_iter() {
let address_results = self
.symbolicate_requested_addresses_for_lib(&lib, addresses)
.await;
symbolicated_addresses.insert(lib, address_results);
}
symbolicated_addresses
}
async fn symbolicate_requested_addresses_for_lib(
&self,
lib: &Lib,
mut addresses: Vec<u32>,
) -> Result<LookedUpAddresses, samply_symbols::Error> {
addresses.sort_unstable();
addresses.dedup();
let debug_id = to_debug_id(&lib.breakpad_id)?;
let mut symbolication_result = LookedUpAddresses::for_addresses(&addresses);
let mut external_addresses = Vec::new();
let info = LibraryInfo {
debug_name: Some(lib.debug_name.to_string()),
debug_id: Some(debug_id),
..Default::default()
};
let symbol_map = self.symbol_manager.load_symbol_map(&info).await?;
symbolication_result.set_total_symbol_count(symbol_map.symbol_count() as u32);
for &address in &addresses {
if let Some(address_info) = symbol_map.lookup_sync(LookupAddress::Relative(address)) {
symbolication_result.add_address_symbol(
address,
address_info.symbol.address,
address_info.symbol.name,
address_info.symbol.size,
);
match address_info.frames {
Some(FramesLookupResult::Available(frames)) => {
symbolication_result.add_address_debug_info(address, frames)
}
Some(FramesLookupResult::External(ext_address)) => {
external_addresses.push((address, ext_address));
}
None => {}
}
}
}
external_addresses.sort_unstable_by(|(_, a), (_, b)| a.cmp(b));
for (address, ext_address) in external_addresses {
if let Some(frames) = symbol_map.lookup_external(&ext_address).await {
symbolication_result.add_address_debug_info(address, frames);
}
}
Ok(symbolication_result)
}
}
fn gather_requested_addresses(
request: &request_json::Request,
) -> Result<HashMap<Lib, Vec<u32>>, Error> {
let mut requested_addresses: HashMap<Lib, Vec<u32>> = HashMap::new();
for job in request.jobs() {
let mut requested_addresses_by_module_index: HashMap<u32, Vec<u32>> = HashMap::new();
for stack in &job.stacks {
for frame in &stack.0 {
requested_addresses_by_module_index
.entry(frame.module_index)
.or_default()
.push(frame.address);
}
}
for (module_index, addresses) in requested_addresses_by_module_index {
let lib = job.memory_map.get(module_index as usize).ok_or(
Error::ParseRequestErrorContents("Stack frame module index beyond the memoryMap"),
)?;
requested_addresses
.entry((*lib).clone())
.or_default()
.extend(addresses);
}
}
Ok(requested_addresses)
}
fn create_response(
request: &request_json::Request,
symbolicated_addresses: HashMap<Lib, Result<LookedUpAddresses, samply_symbols::Error>>,
) -> response_json::Response {
use response_json::{DebugInfo, FrameDebugInfo, Response, Stack, StackFrame, Symbol};
fn result_for_job(
job: &request_json::Job,
symbolicated_addresses: &HashMap<Lib, Result<LookedUpAddresses, samply_symbols::Error>>,
) -> response_json::Result {
let mut found_modules = HashMap::new();
let mut module_errors = HashMap::new();
let mut symbols_by_module_index = HashMap::new();
for (module_index, lib) in job.memory_map.iter().enumerate() {
if let Some(symbol_result) = symbolicated_addresses.get(lib) {
let module_key = format!("{}/{}", lib.debug_name, lib.breakpad_id);
match symbol_result {
Ok(symbols) => {
symbols_by_module_index
.insert(module_index as u32, &symbols.address_results);
}
Err(err) => {
module_errors.insert(module_key.clone(), vec![err.into()]);
}
}
found_modules.insert(module_key, symbol_result.is_ok());
}
}
let stacks = job.stacks.iter().map(|stack| {
response_stack_for_request_stack(stack, &job.memory_map, &symbols_by_module_index)
});
response_json::Result {
stacks: stacks.collect(),
found_modules,
module_errors,
}
}
fn response_stack_for_request_stack(
stack: &request_json::Stack,
memory_map: &[Lib],
symbols_by_module_index: &HashMap<u32, &AddressResults>,
) -> Stack {
let frames = stack.0.iter().enumerate().map(|(frame_index, frame)| {
response_frame_for_request_frame(
frame,
frame_index as u32,
memory_map,
symbols_by_module_index,
)
});
Stack(frames.collect())
}
fn response_frame_for_request_frame(
frame: &request_json::StackFrame,
frame_index: u32,
memory_map: &[Lib],
symbols_by_module_index: &HashMap<u32, &AddressResults>,
) -> StackFrame {
let symbol = symbols_by_module_index
.get(&frame.module_index)
.and_then(|symbol_map| {
symbol_map
.get(&frame.address)
.unwrap()
.as_ref()
.map(|address_result| Symbol {
function: address_result.symbol_name.clone(),
function_offset: frame.address - address_result.symbol_address,
function_size: address_result.function_size,
debug_info: address_result.inline_frames.as_ref().map(|frames| {
let (outer, inlines) = frames
.split_last()
.expect("inline_frames should always have at least one element");
DebugInfo {
file: outer.file_path.as_ref().map(to_api_file_path),
line: outer.line_number.and_then(NonZeroU32::new),
inlines: inlines
.iter()
.map(|inline_frame| FrameDebugInfo {
function: inline_frame.function.clone(),
file: inline_frame.file_path.as_ref().map(to_api_file_path),
line: inline_frame.line_number.and_then(NonZeroU32::new),
})
.collect(),
}
}),
})
});
StackFrame {
frame: frame_index,
module_offset: frame.address,
module: memory_map[frame.module_index as usize].debug_name.clone(),
symbol,
}
}
Response {
results: request
.jobs()
.map(|job| result_for_job(job, &symbolicated_addresses))
.collect(),
}
}