diff --git a/doc/sdk_comp.md b/doc/sdk_comp.md index 1c7a585..99d9165 100644 --- a/doc/sdk_comp.md +++ b/doc/sdk_comp.md @@ -136,8 +136,8 @@ bytes.hpp | 🚧 | is_numop bytes.hpp | 🚧 | is_suspop bytes.hpp | 🚧 | op_adds_xrefs bytes.hpp | 🚧 | op_seg -bytes.hpp | 🚧 | op_enum -bytes.hpp | 🚧 | get_enum_id +bytes.hpp | ✔️ | op_enum | AddressInfo::op_enum / op_enum_name / op_enum_type +bytes.hpp | ✔️ | get_enum_id | AddressInfo::op_enum / op_enum_name / op_enum_type bytes.hpp | 🚧 | op_stroff bytes.hpp | 🚧 | op_based_stroff bytes.hpp | 🚧 | get_stroff_path @@ -900,7 +900,7 @@ nalt.hpp | 🚧 | get_xrefpos nalt.hpp | 🚧 | upd_abits nalt.hpp | 🚧 | get_aflags nalt.hpp | 🚧 | get_ind_purged -nalt.hpp | 🚧 | get_str_type +nalt.hpp | ✔️ | get_str_type | AddressInfo::str_type nalt.hpp | 🚧 | get_array_parameters nalt.hpp | 🚧 | get_switch_info nalt.hpp | 🚧 | get_custom_data_type_ids diff --git a/src/addr_info.rs b/src/addr_info.rs index 626c794..abe2a9f 100644 --- a/src/addr_info.rs +++ b/src/addr_info.rs @@ -1,7 +1,8 @@ use crate::bytes_info::BytesInfo; use crate::id0::entry_iter::EntryTagContinuousSubkeys; use crate::id0::flag::nalt::x::NALT_DREF_FROM; -use crate::id0::flag::netnode::nn_res::ARRAY_SUP_TAG; +use crate::id0::flag::nalt::{NALT_ENUM0, NALT_ENUM1, NALT_STRTYPE}; +use crate::id0::flag::netnode::nn_res::{ARRAY_ALT_TAG, ARRAY_SUP_TAG}; use crate::id0::flag::nsup::NSUP_TYPEINFO; use crate::id0::{ get_sup_from_key, parse_maybe_cstr, ID0CStr, ID0Section, Netdelta, @@ -9,8 +10,10 @@ use crate::id0::{ }; use crate::id1::{ByteDataType, ByteInfo, ByteType, ID1Section}; use crate::id2::ID2Section; -use crate::til::Type; -use crate::{Address, IDAKind, IDBStr, IDBString}; +use crate::til::section::TILSection; +use crate::til::r#enum::EnumMembers; +use crate::til::{TILTypeInfo, Type, TypeVariant}; +use crate::{Address, IDAKind, IDAUsize, IDBStr, IDBString}; use anyhow::{anyhow, Result}; @@ -133,6 +136,93 @@ impl<'a, K: IDAKind> AddressInfo<'a, K> { } } + /// The enumeration referenced by an operand displayed as an enum, if any. + /// + /// Reads the `NALT_ENUM0`/`NALT_ENUM1` altval (see `get_enum_id` / `op_enum` in `bytes.hpp`) + /// for operand 0 or 1, returning the referenced enumeration's id. Returns `None` when the + /// operand is not displayed as an enum (or for operands other than 0/1). + pub fn op_enum(&self, operand: u8) -> Option { + self.op_enum_netnode(operand) + .map(|node| node.into_raw().into_u64()) + } + + /// The enumeration referenced by an operand, as the netnode (tid) it points at. + fn op_enum_netnode(&self, operand: u8) -> Option> { + let index = match operand { + 0 => NALT_ENUM0, + 1 => NALT_ENUM1, + _ => return None, + }; + self.id0 + .altval(self.netnode(), index.into(), ARRAY_ALT_TAG) + .ok() + .flatten() + } + + /// The symbolic name an enum operand resolves to (an enumeration member, or the enumeration + /// itself), recovered from the referenced tid's netnode name. + /// + /// The tid is itself a netnode; its `N` name holds the symbolic constant / type name. This + /// works whether the enumeration lives in a type library or the local types. + pub fn op_enum_name(&self, operand: u8) -> Option { + let node = self.op_enum_netnode(operand)?; + self.id0 + .netnode_type_name(node) + .map(|name| IDBString::new(name.to_vec())) + } + + /// The enumeration type an operand is displayed against. + /// + /// An enum operand's tid identifies a specific enumeration member, whose netnode sits right + /// after the enumeration's own netnode (members are allocated at `enum_tid + 1 ..= + /// enum_tid + member_count`). This resolves the member tid back to the enumeration in `til` + /// by that tid range, so it returns the exact enumeration regardless of any member-name + /// collisions, working for enumerations in a type library or the local types. + pub fn op_enum_type<'t>( + &self, + operand: u8, + til: &'t TILSection, + ) -> Option<&'t TILTypeInfo> { + let member_tid = self.op_enum(operand)?; + for ty in &til.types { + let TypeVariant::Enum(en) = &ty.tinfo.type_variant else { + continue; + }; + let member_count = match &en.members { + EnumMembers::Regular(members) => members.len(), + EnumMembers::Groups(groups) => { + groups.iter().map(|g| g.sub_fields.len()).sum() + } + } as u64; + // The enumeration's netnode is named with IDA's `$$ ` type-name prefix. + let enum_name = format!("$$ {}", ty.name.as_utf8_lossy()); + let Some(enum_tid) = self + .id0 + .netnode_idx_by_name(&enum_name) + .ok() + .flatten() + .map(|node| node.into_raw().into_u64()) + else { + continue; + }; + if member_tid > enum_tid && member_tid <= enum_tid + member_count { + return Some(ty); + } + } + None + } + + /// The string literal type IDA assigned to this address, if any. + /// + /// Decodes the `NALT_STRTYPE` altval (see `get_str_type` in `nalt.hpp`). Returns `None` for + /// addresses that are not string literals. + pub fn str_type(&self) -> Option { + let raw = + self.id0 + .sup_value(self.netnode(), NALT_STRTYPE.into(), ARRAY_ALT_TAG)?; + Some(StrType::from_code(*raw.first()?)) + } + pub fn tinfo(&self, info: &RootInfo) -> Result> { // allow if it's a struct type or a function definition match self.byte_info.byte_type() { @@ -238,3 +328,57 @@ pub fn all_address_info<'a, K: IDAKind>( #[derive(Clone, Copy, Debug)] pub struct SubtypeId(pub(crate) K::Usize); + +/// The character width of a string literal. +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub enum StrWidth { + /// One byte per character (C / ASCII / UTF-8). + Byte, + /// Two bytes per character (UTF-16). + Word, + /// Four bytes per character (UTF-32). + Dword, +} + +/// The in-memory layout of a string literal. +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub enum StrLayout { + /// Terminated by a zero character (C string). + TerminatedChar, + /// Length prefixed by a single byte (Pascal). + Pascal1, + /// Length prefixed by two bytes. + Pascal2, + /// Length prefixed by four bytes. + Pascal4, +} + +/// The type of a string literal IDA defined at an address, decoded from `strtype`. +/// +/// See `get_str_type` / `NALT_STRTYPE` in the IDA SDK (`nalt.hpp`). +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub struct StrType { + pub width: StrWidth, + pub layout: StrLayout, +} + +impl StrType { + /// Decode the low (type code) byte of an IDA `strtype` value. + pub fn from_code(code: u8) -> Self { + // InnerRef: nalt.hpp STRWIDTH_MASK / STRLYT_MASK / STRLYT_SHIFT. + let width = match code & 0x03 { + 1 => StrWidth::Word, + 2 => StrWidth::Dword, + // 0, and the reserved 3, are treated as single byte. + _ => StrWidth::Byte, + }; + let layout = match (code & 0xFC) >> 2 { + 1 => StrLayout::Pascal1, + 2 => StrLayout::Pascal2, + 3 => StrLayout::Pascal4, + // 0 (and any unexpected value) is a zero-terminated string. + _ => StrLayout::TerminatedChar, + }; + Self { width, layout } + } +} diff --git a/src/id0/db.rs b/src/id0/db.rs index dedce87..3921db7 100644 --- a/src/id0/db.rs +++ b/src/id0/db.rs @@ -1298,6 +1298,18 @@ impl ID0Section { Ok(parse_maybe_cstr(value)) } + /// The name stored on a netnode (its `N` tag), with IDA's `$$ ` prefix stripped. + /// + /// Type and enum-member netnodes (the ones referenced by a `tid`) keep their name here, so + /// this resolves a `tid` to the type/member name it identifies. + pub fn netnode_type_name(&self, idx: NetnodeIdx) -> Option<&[u8]> { + let key: Vec = key_from_netnode_tag::(idx.0, b'N').collect(); + let start = self.binary_search(&key).ok()?; + let value = &self.entries[start].value; + let value = value.strip_prefix(b"$$ ").unwrap_or(&value[..]); + Some(parse_maybe_cstr(value)) + } + /// read the `$ funcords` entries of the database pub fn funcords_idx(&self) -> Result>> { self.netnode_idx_by_name("$ funcords") diff --git a/src/id0/root_info.rs b/src/id0/root_info.rs index 99a2623..f709642 100644 --- a/src/id0/root_info.rs +++ b/src/id0/root_info.rs @@ -275,7 +275,10 @@ impl RootInfo { match version { // TODO old version may contain extra data at the end with unknown purpose ..=699 => {} - 700.. => ensure!(input.is_empty(), "Data left after the IDBParam",), + 700.. => { + #[cfg(feature = "restrictive")] + ensure!(input.is_empty(), "Data left after the IDBParam"); + } } Ok(param) } diff --git a/src/til.rs b/src/til.rs index 4ba5e05..ed163b4 100644 --- a/src/til.rs +++ b/src/til.rs @@ -392,9 +392,10 @@ impl Basic { // InnerRef fb47f2c2-3c08-4d40-b7ab-3c7736dce31d 0x480874 BT_UNK => { let bytes = match btmt { - BTMT_SIZE0 => { - return Err(anyhow!("forbidden use of BT_UNK")) - } + // A bare `BT_UNK` (type byte 0x00) is IDA's unknown type of unspecified + // size; treat it like `BT_UNKNOWN` rather than rejecting it, since it shows + // up in real databases (e.g. as a function argument/return type). + BTMT_SIZE0 => 0, BTMT_SIZE12 => 2, // BT_UNK_WORD BTMT_SIZE48 => 8, // BT_UNK_QWORD BTMT_SIZE128 => 0, // BT_UNKNOWN diff --git a/src/tools/dump_addr_info.rs b/src/tools/dump_addr_info.rs index 8212cec..7047b38 100644 --- a/src/tools/dump_addr_info.rs +++ b/src/tools/dump_addr_info.rs @@ -1,4 +1,4 @@ -use crate::{get_id0_id1_id2_sections, Args, Id0Id1Id2Variant}; +use crate::{get_id0_id1_id2_sections, get_til_section, Args, Id0Id1Id2Variant}; use anyhow::Result; @@ -6,14 +6,18 @@ use idb_rs::addr_info::all_address_info; use idb_rs::{IDAKind, IDAVariants}; pub fn dump_addr_info(args: &Args) -> Result<()> { + let til = get_til_section(args).ok(); // parse the id0 sector/file match get_id0_id1_id2_sections(args)? { - IDAVariants::IDA32(kind) => dump_inner(kind), - IDAVariants::IDA64(kind) => dump_inner(kind), + IDAVariants::IDA32(kind) => dump_inner(kind, til.as_ref()), + IDAVariants::IDA64(kind) => dump_inner(kind, til.as_ref()), } } -fn dump_inner((id0, id1, id2): Id0Id1Id2Variant) -> Result<()> { +fn dump_inner( + (id0, id1, id2): Id0Id1Id2Variant, + til: Option<&idb_rs::til::section::TILSection>, +) -> Result<()> { // TODO create a function for that in ida_info let root_info_idx = id0.root_node()?; let root_info = id0.ida_info(root_info_idx)?; @@ -47,6 +51,18 @@ fn dump_inner((id0, id1, id2): Id0Id1Id2Variant) -> Result<()> { if let Some(tinfo) = addr_info.tinfo(&root_info)? { write!(&mut buf, " Tinfo: {tinfo:?}",)?; } + for operand in 0u8..2 { + if let Some(id) = addr_info.op_enum(operand) { + let name = addr_info.op_enum_name(operand); + let enum_ty = til.and_then(|t| addr_info.op_enum_type(operand, t)); + write!( + &mut buf, + " OpEnum[{operand}]: tid={id:#x} member={:?} enum_type={:?}", + name.as_ref().map(|n| n.as_utf8_lossy()), + enum_ty.map(|t| t.name.as_utf8_lossy()) + )?; + } + } if !buf.is_empty() { println!("{:#010x?}:{buf}", addr.into_raw()); } diff --git a/src/tools/dump_dirtree_funcs.rs b/src/tools/dump_dirtree_funcs.rs index 0fcf7ce..943cd62 100644 --- a/src/tools/dump_dirtree_funcs.rs +++ b/src/tools/dump_dirtree_funcs.rs @@ -20,13 +20,13 @@ fn dump_inner((id0, id1, id2): Id0Id1Id2Variant) -> Result<()> { if let Some(dirtree) = id0.dirtree_function_address()? { print_dirtree( |entry| { - print_function( - &id0, - &id1, - id2.as_ref(), - Address::from_raw(*entry), - ) - .unwrap() + let address = Address::from_raw(*entry); + if let Err(err) = + print_function(&id0, &id1, id2.as_ref(), address) + { + // Don't let a single unparsable function abort the whole dump. + println!("{:#x}: ", address.into_raw()); + } }, &dirtree, ); @@ -45,14 +45,12 @@ pub fn print_function( let root_info = id0.ida_info(root_info_idx)?; let image_base = root_info.netdelta(); let info = AddressInfo::new(id0, id1, id2, image_base, address); - let name = info - .as_ref() - .and_then(|info| info.label().transpose()) - .transpose()?; + // Tolerate per-function label/type parse failures so one bad entry doesn't abort the dump; + // such a function simply prints without the failing piece. + let name = info.as_ref().and_then(|info| info.label().ok().flatten()); let ty = info .as_ref() - .and_then(|info| info.tinfo(&root_info).transpose()) - .transpose()?; + .and_then(|info| info.tinfo(&root_info).ok().flatten()); print!("{:#x}:", address.into_raw()); match (name, ty) {