Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,13 @@ use symbolicator_sources::SourceConfig;
use thiserror::Error;
use url::Url;

pub(crate) use self::variables::Value as VariableValue;
pub use self::variables::{Variable, VariableKind, Variables};
use crate::memory::MemoryAccess;
pub use crate::metrics::StacktraceOrigin;

mod variables;

#[derive(Debug, Clone)]
/// A request for symbolication of multiple stack traces.
pub struct SymbolicateStacktraces {
Expand Down Expand Up @@ -356,8 +360,8 @@ pub struct RawFrame {
/// Mapping of local variables and expression names that were available in this frame.
///
/// Current format is heavily work in progress.
#[serde(skip_serializing_if = "Option::is_none")]
pub vars: Option<BTreeMap<String, serde_json::Value>>,
#[serde(default, skip_serializing_if = "Variables::is_empty")]
pub vars: Variables,
Comment thread
szokeasaurusrex marked this conversation as resolved.

/// Information about how the raw frame was created.
#[serde(default, skip_serializing_if = "is_default_value")]
Expand Down
94 changes: 94 additions & 0 deletions crates/symbolicator-native/src/interface/variables.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
//! Contains types for representing variable values.

use std::collections::BTreeMap;

use serde::{Deserialize, Serialize};
use symbolic::debuginfo::VariableKind as SymbolicVariableKind;

/// Variables keyed by their names.
#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
#[serde(transparent)]
pub struct Variables(BTreeMap<String, Variable>);

impl Variables {
pub fn is_empty(&self) -> bool {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think depending on usage we could also impl Deref

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think that is a good idea. I intentionally encapsulated this because we may want to switch to a different type that would allow multiple variables with the same name, which HashMap of course does not allow.

Implementing Deref would mean we might end up relying on HashMap methods.

self.0.is_empty()
}
}

impl<S> FromIterator<(S, Variable)> for Variables
where
S: Into<String>,
{
fn from_iter<T>(iter: T) -> Self
where
T: IntoIterator<Item = (S, Variable)>,
{
Self(iter.into_iter().map(|(k, v)| (k.into(), v)).collect())
}
}

/// A variable extracted from a memory dump.
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct Variable {
kind: VariableKind,
#[serde(rename = "type")]
ty: String,
#[serde(flatten)]
value: Value,
}
Comment thread
cursor[bot] marked this conversation as resolved.

/// The variable's kind (e.g. local or parameter).
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum VariableKind {
Local,
Parameter,
}

/// A value of a variable or field within a variable (e.g. list item, pointee, struct field).
///
/// This struct allows for multiple co-existing representations.
#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
pub struct Value {
/// A fully-formatted, source-specific representation of a variable value.
///
/// This should typically be used for primitive types like integers, booleans, chars.
value: Option<String>,
}

impl Value {
/// Create a new empty [`Value`].
pub fn new() -> Self {
Self::default()
}

/// Provide a fully-formatted, source-specific representation of the [`Value`].
///
/// This should typically be used for primitive types like integers, booleans, and chars.
pub fn value(self, value: Option<String>) -> Self {
Self { value }
}

/// Convert this [`Value`] into a [`Variable`] with the given type and kind.
pub fn into_variable<T, K>(self, ty: T, kind: K) -> Variable
where
T: Into<String>,
K: Into<VariableKind>,
{
Variable {
kind: kind.into(),
ty: ty.into(),
value: self,
}
}
}

impl From<SymbolicVariableKind> for VariableKind {
fn from(value: SymbolicVariableKind) -> Self {
match value {
SymbolicVariableKind::Parameter => VariableKind::Parameter,
SymbolicVariableKind::Local => VariableKind::Local,
}
}
}
59 changes: 24 additions & 35 deletions crates/symbolicator-native/src/symbolication/native.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
use std::collections::BTreeMap;

use minidump::CpuContext;
use symbolic::common::{CpuFamily, InstructionInfo, Language, split_path};
use symbolic::symcache::{
Expand All @@ -10,6 +8,7 @@ use symbolicator_service::utils::hex::HexValue;

use crate::interface::{
AdjustInstructionAddr, FrameStatus, RawFrame, Registers, Signal, SymbolicatedFrame,
VariableValue, Variables,
};
use crate::memory::{MemoryAccess, MemoryAccessExt};

Expand Down Expand Up @@ -69,10 +68,9 @@ pub fn symbolicate_native_frame(
frame.filename.clone()
};

let mut vars = None;
if let Some(memory) = memory {
vars = do_extract_variables(&source_location, symcache, &frame.registers, memory);
}
let vars = memory.map_or_default(|memory| {
do_extract_variables(&source_location, symcache, &frame.registers, memory)
});

rv.push(SymbolicatedFrame {
status: FrameStatus::Symbolicated,
Expand Down Expand Up @@ -185,35 +183,26 @@ fn do_extract_variables<'data, 'cache>(
cache: &SymCache<'cache>,
registers: &Registers,
memory: &dyn MemoryAccess,
) -> Option<BTreeMap<String, serde_json::Value>> {
let mut result = BTreeMap::new();

for variable in source_location.variables() {
let Some(name) = variable.name() else {
continue;
};

let mut ty = String::new();
resolve_type_name(&mut ty, cache, variable.ty(), 0);

let value = variable
.locations()
.find_map(|loc| resolve_variable_value(cache, registers, memory, loc, variable.ty()));

// This doesn't handle name collisions currently.
result.insert(
name.to_owned(),
match value {
Some(value) => format!("{value} ({ty})").into(),
None => ty.into(),
},
);
}

match result.is_empty() {
false => Some(result),
true => None,
}
) -> Variables {
source_location
.variables()
.flat_map(|variable| {
let name = variable.name()?;

let mut ty = String::new();
resolve_type_name(&mut ty, cache, variable.ty(), 0);

let value = variable.locations().find_map(|loc| {
resolve_variable_value(cache, registers, memory, loc, variable.ty())
});

let interface_variable = VariableValue::new()
.value(value)
.into_variable(ty, variable.kind());

Some((name, interface_variable))
})
.collect()
}

fn resolve_variable_value(
Expand Down
6 changes: 3 additions & 3 deletions crates/symbolicli/src/event.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
use std::collections::BTreeMap;
use std::sync::Arc;

use anyhow::bail;
Expand All @@ -9,7 +8,7 @@ use symbolicator_js::interface::{
};
use symbolicator_native::interface::{
AddrMode, CompleteObjectInfo, FrameTrust, RawFrame, RawStacktrace, Signal, StacktraceOrigin,
SymbolicateStacktraces,
SymbolicateStacktraces, Variables,
};
use symbolicator_service::types::{FrameOrder, Platform, RawObjectInfo, Scope, ScrapingConfig};
use symbolicator_service::utils::hex::HexValue;
Expand Down Expand Up @@ -268,7 +267,8 @@ struct Frame {
post_context: Vec<String>,
module: Option<String>,
source_link: Option<String>,
vars: Option<BTreeMap<String, serde_json::Value>>,
#[serde(default)]
vars: Variables,
#[serde(default)]
trust: FrameTrust,
#[serde(default)]
Expand Down
Loading