use crate::{
container::{Arch, Container},
rtti::v2::{read_cstring_at_va, read_ptr, va_to_offset},
};
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum NimKind {
None,
Bool,
Char,
Enum,
Array,
Object,
Tuple,
Set,
Range,
Ptr,
Ref,
Sequence,
Proc,
Pointer,
String,
Cstring,
Int,
Int8,
Int16,
Int32,
Int64,
Float,
Float32,
Float64,
Float128,
UInt,
UInt8,
UInt16,
UInt32,
UInt64,
Other(u8),
}
impl NimKind {
pub fn from_raw(raw: u8) -> Self {
match raw {
0 => Self::None,
1 => Self::Bool,
2 => Self::Char,
14 => Self::Enum,
16 => Self::Array,
17 => Self::Object,
18 => Self::Tuple,
19 => Self::Set,
20 => Self::Range,
21 => Self::Ptr,
22 => Self::Ref,
24 => Self::Sequence,
25 => Self::Proc,
26 => Self::Pointer,
28 => Self::String,
29 => Self::Cstring,
31 => Self::Int,
32 => Self::Int8,
33 => Self::Int16,
34 => Self::Int32,
35 => Self::Int64,
36 => Self::Float,
37 => Self::Float32,
38 => Self::Float64,
39 => Self::Float128,
40 => Self::UInt,
41 => Self::UInt8,
42 => Self::UInt16,
43 => Self::UInt32,
44 => Self::UInt64,
n => Self::Other(n),
}
}
pub fn as_str(&self) -> &'static str {
match self {
Self::None => "None",
Self::Bool => "Bool",
Self::Char => "Char",
Self::Enum => "Enum",
Self::Array => "Array",
Self::Object => "Object",
Self::Tuple => "Tuple",
Self::Set => "Set",
Self::Range => "Range",
Self::Ptr => "Ptr",
Self::Ref => "Ref",
Self::Sequence => "Sequence",
Self::Proc => "Proc",
Self::Pointer => "Pointer",
Self::String => "String",
Self::Cstring => "Cstring",
Self::Int => "Int",
Self::Int8 => "Int8",
Self::Int16 => "Int16",
Self::Int32 => "Int32",
Self::Int64 => "Int64",
Self::Float => "Float",
Self::Float32 => "Float32",
Self::Float64 => "Float64",
Self::Float128 => "Float128",
Self::UInt => "UInt",
Self::UInt8 => "UInt8",
Self::UInt16 => "UInt16",
Self::UInt32 => "UInt32",
Self::UInt64 => "UInt64",
Self::Other(_) => "Other",
}
}
}
impl core::fmt::Display for NimKind {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
f.write_str(self.as_str())
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum NimTypeFlag {
NoRefs,
Acyclic,
EnumHole,
}
#[derive(Debug, Clone)]
pub struct TNimTypeFields {
pub size: u64,
pub align: u64,
pub kind: NimKind,
pub kind_raw: u8,
pub flags_raw: u8,
pub flags: Vec<NimTypeFlag>,
pub base_addr: Option<u64>,
pub node_addr: Option<u64>,
pub finalizer_addr: Option<u64>,
pub marker_addr: Option<u64>,
pub deepcopy_addr: Option<u64>,
pub name: Option<String>,
pub node_fields: Vec<NodeField>,
}
#[derive(Debug, Clone)]
pub struct NodeField {
pub name: String,
pub offset: u64,
pub type_addr: Option<u64>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
enum NodeKind {
None, Slot, List, Case, }
impl NodeKind {
fn from_raw(raw: u8) -> Self {
match raw {
0 => Self::None,
1 => Self::Slot,
2 => Self::List,
3 => Self::Case,
_ => Self::None,
}
}
}
pub fn read(container: &Container<'_>, va: u64) -> Option<TNimTypeFields> {
let is_64 = matches!(
container.arch(),
Arch::Amd64 | Arch::Aarch64 | Arch::PowerPc64 | Arch::Riscv64
);
let ptr_size: usize = if is_64 { 8 } else { 4 };
let bytes = container.bytes();
let offset = va_to_offset(container, va)?;
let min = ptr_size.saturating_mul(7).saturating_add(2);
if offset.checked_add(min)? > bytes.len() {
return None;
}
let mut pos = offset;
let size = read_ptr(bytes, pos, is_64);
pos = pos.saturating_add(ptr_size);
let align = read_ptr(bytes, pos, is_64);
pos = pos.saturating_add(ptr_size);
let kind_raw = bytes.get(pos).copied().unwrap_or(0);
pos = pos.saturating_add(1);
let flags_raw = bytes.get(pos).copied().unwrap_or(0);
pos = pos.saturating_add(1);
let misalign = pos.wrapping_sub(offset).checked_rem(ptr_size).unwrap_or(0);
if misalign != 0 {
pos = pos.saturating_add(ptr_size.saturating_sub(misalign));
}
let base = read_ptr(bytes, pos, is_64);
pos = pos.saturating_add(ptr_size);
let node = read_ptr(bytes, pos, is_64);
pos = pos.saturating_add(ptr_size);
let finalizer = read_ptr(bytes, pos, is_64);
pos = pos.saturating_add(ptr_size);
let marker = read_ptr(bytes, pos, is_64);
pos = pos.saturating_add(ptr_size);
let deepcopy = read_ptr(bytes, pos, is_64);
pos = pos.saturating_add(ptr_size);
let name = try_read_name(container, bytes, pos, is_64).or_else(|| {
pos.checked_add(ptr_size)
.and_then(|p| try_read_name(container, bytes, p, is_64))
});
let mut flags = Vec::new();
if flags_raw & (1 << 0) != 0 {
flags.push(NimTypeFlag::NoRefs);
}
if flags_raw & (1 << 1) != 0 {
flags.push(NimTypeFlag::Acyclic);
}
if flags_raw & (1 << 2) != 0 {
flags.push(NimTypeFlag::EnumHole);
}
let node_fields = if node != 0 {
walk_node_tree(container, bytes, node, is_64, ptr_size, 0)
} else {
Vec::new()
};
Some(TNimTypeFields {
size,
align,
kind: NimKind::from_raw(kind_raw),
kind_raw,
flags_raw,
flags,
base_addr: if base != 0 { Some(base) } else { None },
node_addr: if node != 0 { Some(node) } else { None },
finalizer_addr: if finalizer != 0 {
Some(finalizer)
} else {
None
},
marker_addr: if marker != 0 { Some(marker) } else { None },
deepcopy_addr: if deepcopy != 0 { Some(deepcopy) } else { None },
name,
node_fields,
})
}
fn try_read_name(
container: &Container<'_>,
bytes: &[u8],
pos: usize,
is_64: bool,
) -> Option<String> {
let name_ptr = read_ptr(bytes, pos, is_64);
let name = read_cstring_at_va(container, bytes, name_ptr)?;
if name.bytes().all(|b| (0x20..0x7F).contains(&b)) && !name.is_empty() {
Some(name)
} else {
None
}
}
fn walk_node_tree(
container: &Container<'_>,
bytes: &[u8],
node_va: u64,
is_64: bool,
ptr_size: usize,
depth: usize,
) -> Vec<NodeField> {
if depth > 16 {
return Vec::new();
}
let Some(off) = va_to_offset(container, node_va) else {
return Vec::new();
};
let min = ptr_size.saturating_mul(5).saturating_add(1);
let Some(end) = off.checked_add(min) else {
return Vec::new();
};
if end > bytes.len() {
return Vec::new();
}
let mut pos = off;
let kind_raw = bytes.get(pos).copied().unwrap_or(0);
let kind = NodeKind::from_raw(kind_raw);
pos = pos.saturating_add(1);
let misalign = pos.wrapping_sub(off).checked_rem(ptr_size).unwrap_or(0);
if misalign != 0 {
pos = pos.saturating_add(ptr_size.saturating_sub(misalign));
}
let field_offset = read_ptr(bytes, pos, is_64);
pos = pos.saturating_add(ptr_size);
let typ = read_ptr(bytes, pos, is_64);
pos = pos.saturating_add(ptr_size);
let name_ptr = read_ptr(bytes, pos, is_64);
pos = pos.saturating_add(ptr_size);
let len = read_ptr(bytes, pos, is_64) as usize;
pos = pos.saturating_add(ptr_size);
let sons_ptr = read_ptr(bytes, pos, is_64);
let mut result = Vec::new();
match kind {
NodeKind::Slot => {
if let Some(name) = read_cstring_at_va(container, bytes, name_ptr) {
result.push(NodeField {
name,
offset: field_offset,
type_addr: if typ != 0 { Some(typ) } else { None },
});
}
}
NodeKind::List => {
if sons_ptr != 0
&& len > 0
&& len <= 4096
&& let Some(sons_off) = va_to_offset(container, sons_ptr)
{
for i in 0..len {
let Some(child_ptr_off) = i
.checked_mul(ptr_size)
.and_then(|d| sons_off.checked_add(d))
else {
break;
};
let child_va = read_ptr(bytes, child_ptr_off, is_64);
if child_va != 0 {
let child_fields = walk_node_tree(
container,
bytes,
child_va,
is_64,
ptr_size,
depth.saturating_add(1),
);
result.extend(child_fields);
}
}
}
}
NodeKind::Case => {
if sons_ptr != 0
&& len > 0
&& len <= 4096
&& let Some(sons_off) = va_to_offset(container, sons_ptr)
{
for i in 0..len {
let Some(child_ptr_off) = i
.checked_mul(ptr_size)
.and_then(|d| sons_off.checked_add(d))
else {
break;
};
let child_va = read_ptr(bytes, child_ptr_off, is_64);
if child_va != 0 {
let child_fields = walk_node_tree(
container,
bytes,
child_va,
is_64,
ptr_size,
depth.saturating_add(1),
);
result.extend(child_fields);
}
}
}
}
NodeKind::None => {}
}
result
}