use idakit_sys as sys;
use serde::{Deserialize, Serialize};
use crate::Database;
use crate::address::Address;
use crate::decompiler::ctree::ExtractError;
use crate::error::{Error, Result};
use crate::types::{SinkAdapter, TypeBuilder, TypeId, TypeSink, TypeTable, TypeValue, tid};
#[derive(Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub enum StackSlotKind {
Variable {
name: String,
ty: Option<TypeId>,
},
#[doc(alias("is_retaddr"))]
ReturnAddress,
#[doc(alias("is_savregs"))]
SavedRegisters,
}
impl StackSlotKind {
fn from_parts(flags: sys::FrameVarFlags, name: String, ty: Option<TypeId>) -> Self {
if flags.contains(sys::FrameVarFlags::RETADDR) {
Self::ReturnAddress
} else if flags.contains(sys::FrameVarFlags::SAVREGS) {
Self::SavedRegisters
} else {
Self::Variable { name, ty }
}
}
}
#[derive(Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
#[doc(alias("udm_t"))]
pub struct StackSlot {
offset: i64,
size: u64,
kind: StackSlotKind,
}
impl StackSlot {
#[inline]
#[must_use]
pub const fn offset(&self) -> i64 {
self.offset
}
#[inline]
#[must_use]
pub const fn size(&self) -> u64 {
self.size
}
#[inline]
#[must_use]
pub const fn kind(&self) -> &StackSlotKind {
&self.kind
}
#[inline]
#[must_use]
pub fn name(&self) -> Option<&str> {
match &self.kind {
StackSlotKind::Variable { name, .. } => Some(name),
_ => None,
}
}
#[inline]
#[must_use]
pub fn ty(&self) -> Option<TypeId> {
match &self.kind {
StackSlotKind::Variable { ty, .. } => *ty,
_ => None,
}
}
#[inline]
#[must_use]
pub const fn is_special(&self) -> bool {
!matches!(self.kind, StackSlotKind::Variable { .. })
}
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[doc(alias("get_func_frame"))]
pub struct StackFrame {
size: u64,
types: TypeTable,
slots: Vec<StackSlot>,
}
impl StackFrame {
#[inline]
#[must_use]
#[doc(alias("get_frame_size"))]
pub const fn size(&self) -> u64 {
self.size
}
#[inline]
#[must_use]
pub const fn types(&self) -> &TypeTable {
&self.types
}
#[inline]
#[must_use]
pub fn type_of(&self, id: TypeId) -> &TypeValue {
self.types.get(id)
}
#[inline]
#[must_use]
pub fn slots(&self) -> &[StackSlot] {
&self.slots
}
pub fn variables(&self) -> impl Iterator<Item = &StackSlot> {
self.slots().iter().filter(|s| !s.is_special())
}
#[inline]
#[must_use]
pub fn len(&self) -> usize {
self.slots.len()
}
#[inline]
#[must_use]
pub fn is_empty(&self) -> bool {
self.slots.is_empty()
}
}
struct FrameTypes {
types: TypeBuilder,
}
impl TypeSink for FrameTypes {
fn type_builder(&mut self) -> &mut TypeBuilder {
&mut self.types
}
}
impl Database {
pub fn frame(&self, address: Address) -> Result<Option<StackFrame>> {
crate::claim::ensure_kernel_thread();
let mut ft = FrameTypes {
types: TypeBuilder::new(),
};
let Some(walk) = sys::walk_frame_type(address.get(), &mut SinkAdapter(&mut ft)) else {
return Ok(None);
};
if let Some(bytes) = ft.types.too_wide() {
return Err(Error::Extract {
address: address.get(),
source: ExtractError::ScalarTooWide { bytes },
});
}
let unfilled = ft.types.unfilled();
if unfilled != 0 {
return Err(Error::Extract {
address: address.get(),
source: ExtractError::UnfilledType { count: unfilled },
});
}
let slots = walk
.vars
.into_iter()
.map(|v| StackSlot {
offset: v.offset,
size: v.size,
kind: StackSlotKind::from_parts(
sys::FrameVarFlags::from_bits_retain(v.flags),
v.name,
(v.ty != sys::NONE).then(|| tid(v.ty)),
),
})
.collect();
Ok(Some(StackFrame {
size: walk.size,
types: ft.types.into_table(),
slots,
}))
}
}
#[cfg(test)]
mod tests {
use std::collections::HashSet;
use assert2::assert;
use super::*;
use crate::types::TypeShape;
const fn assert_send<T: Send>() {}
const _: () = assert_send::<StackFrame>();
#[test]
fn kind_from_parts() {
let ty = Some(tid(0));
assert!(
StackSlotKind::from_parts(sys::FrameVarFlags::empty(), "var_18".to_owned(), ty)
== StackSlotKind::Variable {
name: "var_18".to_owned(),
ty,
}
);
assert!(
StackSlotKind::from_parts(sys::FrameVarFlags::RETADDR, "r".to_owned(), ty)
== StackSlotKind::ReturnAddress
);
assert!(
StackSlotKind::from_parts(sys::FrameVarFlags::SAVREGS, "s".to_owned(), None)
== StackSlotKind::SavedRegisters
);
assert!(
StackSlotKind::from_parts(
sys::FrameVarFlags::RETADDR | sys::FrameVarFlags::SAVREGS,
String::new(),
None
) == StackSlotKind::ReturnAddress
);
}
#[test]
fn accessors_follow_the_kind() {
let ty = Some(tid(3));
let var = StackSlot {
offset: -0x18,
size: 4,
kind: StackSlotKind::Variable {
name: "var_18".to_owned(),
ty,
},
};
assert!(!var.is_special());
assert!(var.name() == Some("var_18"));
assert!(var.ty() == ty);
assert!(var.offset() == -0x18);
assert!(var.size() == 4);
let retaddr = StackSlot {
offset: 0x8,
size: 0x10,
kind: StackSlotKind::ReturnAddress,
};
assert!(retaddr.is_special());
assert!(retaddr.name().is_none());
assert!(retaddr.ty().is_none());
assert!(retaddr.offset() == 0x8);
assert!(retaddr.size() == 0x10);
}
#[test]
fn stack_slot_kind_hash_and_serde() {
let kinds = [
StackSlotKind::Variable {
name: "var_18".to_owned(),
ty: Some(tid(0)),
},
StackSlotKind::ReturnAddress,
StackSlotKind::SavedRegisters,
];
let set: HashSet<_> = kinds.iter().cloned().collect();
assert!(set.len() == kinds.len());
for kind in &kinds {
let json = serde_json::to_string(kind).expect("serialize");
let back: StackSlotKind = serde_json::from_str(&json).expect("deserialize");
assert!(back == *kind);
}
}
#[test]
fn stack_slot_hash_and_serde() {
let slots = [
StackSlot {
offset: -0x18,
size: 4,
kind: StackSlotKind::Variable {
name: "var_18".to_owned(),
ty: Some(tid(0)),
},
},
StackSlot {
offset: 0,
size: 8,
kind: StackSlotKind::ReturnAddress,
},
];
let set: HashSet<_> = slots.iter().cloned().collect();
assert!(set.len() == slots.len());
for slot in &slots {
let json = serde_json::to_string(slot).expect("serialize");
let back: StackSlot = serde_json::from_str(&json).expect("deserialize");
assert!(back == *slot);
}
}
#[test]
fn stack_frame_clone_eq_and_serde() {
let mut types = TypeTable::new();
let ty = types.intern(TypeValue {
shape: TypeShape::Int {
bytes: 4,
signed: true,
},
size: Some(4),
});
let frame = StackFrame {
size: 0x20,
types,
slots: vec![
StackSlot {
offset: -0x18,
size: 4,
kind: StackSlotKind::Variable {
name: "var_18".to_owned(),
ty: Some(ty),
},
},
StackSlot {
offset: 0,
size: 8,
kind: StackSlotKind::ReturnAddress,
},
],
};
let cloned = frame.clone();
assert!(cloned == frame);
let json = serde_json::to_string(&frame).expect("serialize");
let back: StackFrame = serde_json::from_str(&json).expect("deserialize");
assert!(back == frame);
}
fn slot(offset: i64, size: u64, kind: StackSlotKind) -> StackSlot {
StackSlot { offset, size, kind }
}
fn frame_with(slots: Vec<StackSlot>) -> StackFrame {
StackFrame {
size: 0x40,
types: TypeTable::new(),
slots,
}
}
#[test]
fn frame_size_returns_stored_value() {
assert!(frame_with(Vec::new()).size() == 0x40);
}
#[test]
fn len_and_is_empty_reflect_slot_count() {
let populated = frame_with(vec![
slot(
-0x8,
4,
StackSlotKind::Variable {
name: "var_8".to_owned(),
ty: None,
},
),
slot(0x8, 8, StackSlotKind::ReturnAddress),
]);
assert!(populated.len() == 2);
assert!(!populated.is_empty());
assert!(frame_with(Vec::new()).is_empty());
}
#[test]
fn variables_excludes_reserved_slots() {
let var = slot(
-0x10,
4,
StackSlotKind::Variable {
name: "var_10".to_owned(),
ty: None,
},
);
let frame = frame_with(vec![
var.clone(),
slot(0x8, 8, StackSlotKind::ReturnAddress),
slot(0x10, 8, StackSlotKind::SavedRegisters),
]);
let vars: Vec<&StackSlot> = frame.variables().collect();
assert!(vars == [&var]);
}
}