use crate::debugger::TypeDeclaration;
use crate::debugger::debugee::dwarf::eval::EvaluationContext;
use crate::debugger::debugee::dwarf::r#type::{
ArrayType, ComplexType, ScalarType, StructureMember, TypeId,
};
use crate::debugger::variable::value::specialization::VariableParserExtension;
use crate::debugger::variable::value::{
ArrayItem, ArrayValue, CEnumValue, CModifiedValue, Member, PointerValue, RustEnumValue,
ScalarValue, SpecializedValue, StructValue, SubroutineValue, SupportedScalar, Value,
};
use crate::debugger::variable::{Identity, ObjectBinaryRepr};
use crate::version::Version;
use crate::version_switch;
use bytes::Bytes;
use gimli::{
DW_ATE_ASCII, DW_ATE_UTF, DW_ATE_address, DW_ATE_boolean, DW_ATE_float, DW_ATE_signed,
DW_ATE_signed_char, DW_ATE_unsigned, DW_ATE_unsigned_char,
};
use indexmap::IndexMap;
use log::warn;
use std::collections::HashMap;
use std::fmt::Display;
#[derive(Default)]
pub struct ValueModifiers {
tls: bool,
tls_const: bool,
const_tls_duplicate: bool,
}
impl ValueModifiers {
pub fn from_identity(pcx: &ParseContext, ident: Identity) -> ValueModifiers {
let mut this = ValueModifiers::default();
let ver = pcx.evcx.rustc_version().unwrap_or_default();
if ver >= Version((1, 80, 0)) {
this.tls = ident.name.as_deref() == Some("VAL")
|| ident.name.as_deref() == Some("__RUST_STD_INTERNAL_VAL");
if ident.namespace.contains(&["thread_local_const_init"])
&& !ident.namespace.contains(&["{closure#0}"])
{
this.const_tls_duplicate = true;
}
} else {
let var_name_is_tls = ident.namespace.contains(&["__getit"])
&& (ident.name.as_deref() == Some("VAL") || ident.name.as_deref() == Some("__KEY"));
if var_name_is_tls {
this.tls = true;
if ident.name.as_deref() == Some("VAL") {
this.tls_const = true
}
}
}
this
}
}
pub struct ParseContext<'a> {
pub evcx: &'a EvaluationContext<'a>,
pub type_graph: &'a ComplexType,
}
#[derive(Default)]
pub struct ValueParser;
impl ValueParser {
pub fn new() -> Self {
ValueParser
}
fn parse_scalar(
&self,
data: Option<ObjectBinaryRepr>,
type_id: TypeId,
r#type: &ScalarType,
) -> ScalarValue {
fn render_scalar<S: Copy + Display>(data: Option<ObjectBinaryRepr>) -> Option<S> {
data.as_ref().map(|v| scalar_from_bytes::<S>(&v.raw_data))
}
let in_debugee_loc = data.as_ref().and_then(|d| d.address);
#[allow(non_upper_case_globals)]
let value_view = r#type.encoding.and_then(|encoding| match encoding {
DW_ATE_address => render_scalar::<usize>(data).map(SupportedScalar::Usize),
DW_ATE_signed_char => render_scalar::<i8>(data).map(SupportedScalar::I8),
DW_ATE_unsigned_char => render_scalar::<u8>(data).map(SupportedScalar::U8),
DW_ATE_signed => match r#type.byte_size.unwrap_or(0) {
0 => Some(SupportedScalar::Empty()),
1 => render_scalar::<i8>(data).map(SupportedScalar::I8),
2 => render_scalar::<i16>(data).map(SupportedScalar::I16),
4 => render_scalar::<i32>(data).map(SupportedScalar::I32),
8 => {
if r#type.name.as_deref() == Some("isize") {
render_scalar::<isize>(data).map(SupportedScalar::Isize)
} else {
render_scalar::<i64>(data).map(SupportedScalar::I64)
}
}
16 => render_scalar::<i128>(data).map(SupportedScalar::I128),
_ => {
warn!(
"parse scalar: unexpected signed size: {size:?}",
size = r#type.byte_size
);
None
}
},
DW_ATE_unsigned => match r#type.byte_size.unwrap_or(0) {
0 => Some(SupportedScalar::Empty()),
1 => render_scalar::<u8>(data).map(SupportedScalar::U8),
2 => render_scalar::<u16>(data).map(SupportedScalar::U16),
4 => render_scalar::<u32>(data).map(SupportedScalar::U32),
8 => {
if r#type.name.as_deref() == Some("usize") {
render_scalar::<usize>(data).map(SupportedScalar::Usize)
} else {
render_scalar::<u64>(data).map(SupportedScalar::U64)
}
}
16 => render_scalar::<u128>(data).map(SupportedScalar::U128),
_ => {
warn!(
"parse scalar: unexpected unsigned size: {size:?}",
size = r#type.byte_size
);
None
}
},
DW_ATE_float => match r#type.byte_size.unwrap_or(0) {
4 => render_scalar::<f32>(data).map(SupportedScalar::F32),
8 => render_scalar::<f64>(data).map(SupportedScalar::F64),
_ => {
warn!(
"parse scalar: unexpected float size: {size:?}",
size = r#type.byte_size
);
None
}
},
DW_ATE_boolean => render_scalar::<bool>(data).map(SupportedScalar::Bool),
DW_ATE_UTF => render_scalar::<char>(data).map(|char| {
if String::from_utf8(char.to_string().into_bytes()).is_err() {
SupportedScalar::Char('?')
} else {
SupportedScalar::Char(char)
}
}),
DW_ATE_ASCII => render_scalar::<char>(data).map(SupportedScalar::Char),
_ => {
warn!("parse scalar: unexpected base type encoding: {encoding}");
None
}
});
ScalarValue {
type_ident: r#type.identity(),
type_id: Some(type_id),
value: value_view,
raw_address: in_debugee_loc,
}
}
fn parse_struct_variable(
&self,
pcx: &ParseContext,
data: Option<ObjectBinaryRepr>,
type_id: TypeId,
type_params: IndexMap<String, Option<TypeId>>,
members: &[StructureMember],
) -> StructValue {
let children = members
.iter()
.filter_map(|member| self.parse_struct_member(pcx, member, data.as_ref()))
.collect();
StructValue {
type_id: Some(type_id),
type_ident: pcx.type_graph.identity(type_id),
members: children,
type_params,
raw_address: data.and_then(|d| d.address),
}
}
fn parse_struct_member(
&self,
pcx: &ParseContext,
member: &StructureMember,
parent_data: Option<&ObjectBinaryRepr>,
) -> Option<Member> {
let name = member.name.clone();
let Some(type_ref) = member.type_ref else {
warn!(
"parse structure: unknown type for member {}",
name.as_deref().unwrap_or_default()
);
return None;
};
let member_val = parent_data.and_then(|data| member.value(pcx.evcx, pcx.type_graph, data));
let value = self.parse_inner(pcx, member_val, type_ref)?;
Some(Member {
field_name: member.name.clone(),
value,
})
}
fn parse_array(
&self,
pcx: &ParseContext,
data: Option<ObjectBinaryRepr>,
type_id: TypeId,
array_decl: &ArrayType,
) -> ArrayValue {
let items = array_decl.bounds(pcx.evcx).and_then(|bounds| {
let len = bounds.1 - bounds.0;
if len == 0 {
return Some(vec![]);
}
if len < 0 {
warn!(
"array `len` less than 0 for type: {}",
pcx.type_graph.identity(type_id)
);
return None;
};
let data = data.as_ref()?;
let el_size =
(array_decl.size_in_bytes(pcx.evcx, pcx.type_graph)? / len as u64) as usize;
let bytes = &data.raw_data;
let el_type_id = array_decl.element_type()?;
let (mut bytes_chunks, mut empty_chunks);
let raw_items_iter: &mut dyn Iterator<Item = (usize, &[u8])> = if el_size != 0 {
bytes_chunks = bytes.chunks(el_size).enumerate();
&mut bytes_chunks
} else {
let v: Vec<&[u8]> = vec![&[]; len as usize];
empty_chunks = v.into_iter().enumerate();
&mut empty_chunks
};
Some(
raw_items_iter
.filter_map(|(i, chunk)| {
let offset = i * el_size;
let data = ObjectBinaryRepr {
raw_data: bytes.slice_ref(chunk),
address: data.address.map(|addr| addr + offset),
size: el_size,
};
let value = self.parse_inner(pcx, Some(data), el_type_id)?;
Some(ArrayItem {
index: bounds.0 + i as i64,
value,
})
})
.collect::<Vec<_>>(),
)
});
ArrayValue {
items,
type_id: Some(type_id),
type_ident: pcx.type_graph.identity(type_id),
raw_address: data.and_then(|d| d.address),
}
}
fn parse_c_enum(
&self,
pcx: &ParseContext,
data: Option<ObjectBinaryRepr>,
type_id: TypeId,
discr_type: Option<TypeId>,
enumerators: &HashMap<i64, String>,
) -> CEnumValue {
let in_debugee_loc = data.as_ref().and_then(|d| d.address);
let mb_discr = discr_type.and_then(|type_id| self.parse_inner(pcx, data, type_id));
let value = mb_discr.and_then(|discr| {
if let Value::Scalar(scalar) = discr {
scalar.try_as_number()
} else {
None
}
});
CEnumValue {
type_ident: pcx.type_graph.identity(type_id),
type_id: Some(type_id),
value: value.and_then(|val| enumerators.get(&val).cloned()),
raw_address: in_debugee_loc,
}
}
fn parse_rust_enum(
&self,
pcx: &ParseContext,
data: Option<ObjectBinaryRepr>,
type_id: TypeId,
discr_member: Option<&StructureMember>,
enumerators: &HashMap<Option<i64>, StructureMember>,
) -> RustEnumValue {
let discr_value = discr_member.and_then(|member| {
let discr = self.parse_struct_member(pcx, member, data.as_ref())?.value;
if let Value::Scalar(scalar) = discr {
return scalar.try_as_number();
}
None
});
let enumerator =
discr_value.and_then(|v| enumerators.get(&Some(v)).or_else(|| enumerators.get(&None)));
let enumerator = enumerator.and_then(|member| {
Some(Box::new(self.parse_struct_member(
pcx,
member,
data.as_ref(),
)?))
});
RustEnumValue {
type_id: Some(type_id),
type_ident: pcx.type_graph.identity(type_id),
value: enumerator,
raw_address: data.and_then(|d| d.address),
}
}
fn parse_pointer(
&self,
pcx: &ParseContext,
data: Option<ObjectBinaryRepr>,
type_id: TypeId,
target_type: Option<TypeId>,
) -> PointerValue {
let mb_ptr = data
.as_ref()
.map(|v| scalar_from_bytes::<*const ()>(&v.raw_data));
let mut type_ident = pcx.type_graph.identity(type_id);
if type_ident.is_unknown()
&& let Some(target_type) = target_type
{
type_ident = pcx.type_graph.identity(target_type).as_deref_type();
}
PointerValue {
type_id: Some(type_id),
type_ident,
value: mb_ptr,
target_type,
target_type_size: None,
raw_address: data.and_then(|d| d.address),
}
}
fn parse_inner_with_modifiers(
&self,
pcx: &ParseContext,
data: Option<ObjectBinaryRepr>,
type_id: TypeId,
modifiers: &ValueModifiers,
) -> Option<Value> {
let type_graph = pcx.type_graph;
match &type_graph.types[&type_id] {
TypeDeclaration::Scalar(scalar_type) => {
Some(Value::Scalar(self.parse_scalar(data, type_id, scalar_type)))
}
TypeDeclaration::Structure {
namespaces: type_ns_h,
members,
type_params,
name: struct_name,
..
} => {
let struct_var =
self.parse_struct_variable(pcx, data, type_id, type_params.clone(), members);
let parser_ext = VariableParserExtension::new(self);
if struct_name.as_deref() == Some("&str") {
return Some(Value::Specialized {
value: parser_ext.parse_str(pcx, &struct_var),
original: struct_var,
});
};
if struct_name.as_deref() == Some("String") {
return Some(Value::Specialized {
value: parser_ext.parse_string(pcx, &struct_var),
original: struct_var,
});
};
if struct_name.as_ref().map(|name| name.starts_with("Vec")) == Some(true)
&& type_ns_h.contains(&["vec"])
{
return Some(Value::Specialized {
value: parser_ext.parse_vector(pcx, &struct_var, type_params),
original: struct_var,
});
};
let rust_version = pcx.evcx.rustc_version().unwrap_or_default();
let type_is_tls = version_switch!(
rust_version,
.. (1 . 77) => type_ns_h.contains(&["std", "sys", "common", "thread_local", "fast_local"]),
(1 . 77) .. (1 . 78) => type_ns_h.contains(&["std", "sys", "pal", "common", "thread_local", "fast_local"]),
(1 . 78) .. (1 . 89) => type_ns_h.contains(&["std", "sys", "thread_local", "fast_local"]),
(1 . 89) .. => type_ns_h.contains(&["std", "sys", "thread_local", "native"]),
).unwrap_or_default();
if type_is_tls || modifiers.tls {
return if rust_version >= Version((1, 80, 0)) {
match parser_ext.parse_tls(pcx, &struct_var, type_params, rust_version) {
Ok(Some(value)) => Some(Value::Specialized {
value: Some(SpecializedValue::Tls(value)),
original: struct_var,
}),
Ok(None) => None,
Err(e) => {
warn!(target: "debugger", "{:#}", e);
Some(Value::Specialized {
value: None,
original: struct_var,
})
}
}
} else {
Some(Value::Specialized {
value: parser_ext.parse_tls_old(
pcx,
&struct_var,
type_params,
modifiers.tls_const,
),
original: struct_var,
})
};
}
if struct_name.as_ref().map(|name| name.starts_with("HashMap")) == Some(true)
&& type_ns_h.contains(&["collections", "hash", "map"])
{
return Some(Value::Specialized {
value: parser_ext.parse_hashmap(pcx, &struct_var),
original: struct_var,
});
};
if struct_name.as_ref().map(|name| name.starts_with("HashSet")) == Some(true)
&& type_ns_h.contains(&["collections", "hash", "set"])
{
return Some(Value::Specialized {
value: parser_ext.parse_hashset(pcx, &struct_var),
original: struct_var,
});
};
if struct_name
.as_ref()
.map(|name| name.starts_with("BTreeMap"))
== Some(true)
&& type_ns_h.contains(&["collections", "btree", "map"])
{
return Some(Value::Specialized {
value: parser_ext.parse_btree_map(pcx, &struct_var, type_id, type_params),
original: struct_var,
});
};
if struct_name
.as_ref()
.map(|name| name.starts_with("BTreeSet"))
== Some(true)
&& type_ns_h.contains(&["collections", "btree", "set"])
{
return Some(Value::Specialized {
value: parser_ext.parse_btree_set(&struct_var),
original: struct_var,
});
};
if struct_name
.as_ref()
.map(|name| name.starts_with("VecDeque"))
== Some(true)
&& type_ns_h.contains(&["collections", "vec_deque"])
{
return Some(Value::Specialized {
value: parser_ext.parse_vec_dequeue(pcx, &struct_var, type_params),
original: struct_var,
});
};
if struct_name.as_ref().map(|name| name.starts_with("Cell")) == Some(true)
&& type_ns_h.contains(&["cell"])
{
return Some(Value::Specialized {
value: parser_ext.parse_cell(&struct_var),
original: struct_var,
});
};
if struct_name.as_ref().map(|name| name.starts_with("RefCell")) == Some(true)
&& type_ns_h.contains(&["cell"])
{
return Some(Value::Specialized {
value: parser_ext.parse_refcell(&struct_var),
original: struct_var,
});
};
if struct_name
.as_ref()
.map(|name| name.starts_with("Rc<") | name.starts_with("Weak<"))
== Some(true)
&& type_ns_h.contains(&["rc"])
{
return Some(Value::Specialized {
value: parser_ext.parse_rc(&struct_var),
original: struct_var,
});
};
if struct_name
.as_ref()
.map(|name| name.starts_with("Arc<") | name.starts_with("Weak<"))
== Some(true)
&& type_ns_h.contains(&["sync"])
{
return Some(Value::Specialized {
value: parser_ext.parse_arc(&struct_var),
original: struct_var,
});
};
if struct_name.as_ref().map(|name| name == "Uuid") == Some(true)
&& type_ns_h.contains(&["uuid"])
{
return Some(Value::Specialized {
value: parser_ext.parse_uuid(&struct_var),
original: struct_var,
});
};
if struct_name.as_ref().map(|name| name == "Instant") == Some(true)
&& type_ns_h.contains(&["std", "time"])
{
return Some(Value::Specialized {
value: parser_ext.parse_instant(&struct_var),
original: struct_var,
});
};
if struct_name.as_ref().map(|name| name == "SystemTime") == Some(true)
&& type_ns_h.contains(&["std", "time"])
{
return Some(Value::Specialized {
value: parser_ext.parse_sys_time(&struct_var),
original: struct_var,
});
};
Some(Value::Struct(struct_var))
}
TypeDeclaration::Array(decl) => {
Some(Value::Array(self.parse_array(pcx, data, type_id, decl)))
}
TypeDeclaration::CStyleEnum {
discr_type,
enumerators,
..
} => Some(Value::CEnum(self.parse_c_enum(
pcx,
data,
type_id,
*discr_type,
enumerators,
))),
TypeDeclaration::RustEnum {
discr_type,
enumerators,
..
} => Some(Value::RustEnum(self.parse_rust_enum(
pcx,
data,
type_id,
discr_type.as_ref().map(|t| t.as_ref()),
enumerators,
))),
TypeDeclaration::Pointer { target_type, .. } => Some(Value::Pointer(
self.parse_pointer(pcx, data, type_id, *target_type),
)),
TypeDeclaration::Union { members, .. } => {
let struct_var =
self.parse_struct_variable(pcx, data, type_id, IndexMap::new(), members);
Some(Value::Struct(struct_var))
}
TypeDeclaration::Subroutine { return_type, .. } => {
let ret_type = return_type.map(|t_id| pcx.type_graph.identity(t_id));
let fn_var = SubroutineValue {
type_id: Some(type_id),
return_type_ident: ret_type,
address: data.and_then(|d| d.address),
};
Some(Value::Subroutine(fn_var))
}
TypeDeclaration::ModifiedType {
inner, modifier, ..
} => {
let in_debugee_loc = data.as_ref().and_then(|d| d.address);
Some(Value::CModifiedVariable(CModifiedValue {
type_id: Some(type_id),
type_ident: pcx.type_graph.identity(type_id),
modifier: *modifier,
value: inner.and_then(|inner_type| {
Some(Box::new(self.parse_inner(pcx, data, inner_type)?))
}),
address: in_debugee_loc,
}))
}
}
}
pub(super) fn parse_inner(
&self,
pcx: &ParseContext,
data: Option<ObjectBinaryRepr>,
type_id: TypeId,
) -> Option<Value> {
self.parse_inner_with_modifiers(pcx, data, type_id, &ValueModifiers::default())
}
pub fn parse(
self,
pcx: &ParseContext,
bin_data: Option<ObjectBinaryRepr>,
modifiers: &ValueModifiers,
) -> Option<Value> {
if modifiers.const_tls_duplicate {
return None;
}
self.parse_inner_with_modifiers(pcx, bin_data, pcx.type_graph.root(), modifiers)
}
}
#[inline(never)]
fn scalar_from_bytes<T: Copy>(bytes: &Bytes) -> T {
let ptr = bytes.as_ptr();
unsafe { std::ptr::read_unaligned::<T>(ptr as *const T) }
}