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
6 changes: 3 additions & 3 deletions doc/sdk_comp.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down
150 changes: 147 additions & 3 deletions src/addr_info.rs
Original file line number Diff line number Diff line change
@@ -1,16 +1,19 @@
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,
NetnodeIdx, RootInfo,
};

Check warning on line 10 in src/addr_info.rs

View workflow job for this annotation

GitHub Actions / cargo fmt

Diff in /home/runner/work/idb-rs/idb-rs/src/addr_info.rs
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};

Expand Down Expand Up @@ -133,6 +136,93 @@
}
}

/// 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<u64> {
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<NetnodeIdx<K>> {
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<IDBString> {
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

Check warning on line 217 in src/addr_info.rs

View workflow job for this annotation

GitHub Actions / cargo fmt

Diff in /home/runner/work/idb-rs/idb-rs/src/addr_info.rs
/// addresses that are not string literals.
pub fn str_type(&self) -> Option<StrType> {
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<K>) -> Result<Option<Type>> {
// allow if it's a struct type or a function definition
match self.byte_info.byte_type() {
Expand Down Expand Up @@ -238,3 +328,57 @@

#[derive(Clone, Copy, Debug)]
pub struct SubtypeId<K: IDAKind>(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 }
}
}
12 changes: 12 additions & 0 deletions src/id0/db.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1298,6 +1298,18 @@ impl<K: IDAKind> ID0Section<K> {
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<K>) -> Option<&[u8]> {
let key: Vec<u8> = key_from_netnode_tag::<K>(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<Option<FuncordsIdx<K>>> {
self.netnode_idx_by_name("$ funcords")
Expand Down
5 changes: 4 additions & 1 deletion src/id0/root_info.rs
Original file line number Diff line number Diff line change
Expand Up @@ -275,7 +275,10 @@ impl<K: IDAKind> RootInfo<K> {
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)
}
Expand Down
7 changes: 4 additions & 3 deletions src/til.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
24 changes: 20 additions & 4 deletions src/tools/dump_addr_info.rs
Original file line number Diff line number Diff line change
@@ -1,19 +1,23 @@
use crate::{get_id0_id1_id2_sections, Args, Id0Id1Id2Variant};
use crate::{get_id0_id1_id2_sections, get_til_section, Args, Id0Id1Id2Variant};

Check warning on line 1 in src/tools/dump_addr_info.rs

View workflow job for this annotation

GitHub Actions / cargo fmt

Diff in /home/runner/work/idb-rs/idb-rs/src/tools/dump_addr_info.rs

use anyhow::Result;

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<K: IDAKind>((id0, id1, id2): Id0Id1Id2Variant<K>) -> Result<()> {
fn dump_inner<K: IDAKind>(
(id0, id1, id2): Id0Id1Id2Variant<K>,
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)?;
Expand Down Expand Up @@ -47,6 +51,18 @@
if let Some(tinfo) = addr_info.tinfo(&root_info)? {
write!(&mut buf, " Tinfo: {tinfo:?}",)?;
}
for operand in 0u8..2 {

Check warning on line 54 in src/tools/dump_addr_info.rs

View workflow job for this annotation

GitHub Actions / cargo fmt

Diff in /home/runner/work/idb-rs/idb-rs/src/tools/dump_addr_info.rs
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());
}
Expand Down
24 changes: 11 additions & 13 deletions src/tools/dump_dirtree_funcs.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,13 +20,13 @@ fn dump_inner<K: IDAKind>((id0, id1, id2): Id0Id1Id2Variant<K>) -> 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}: <error: {err:#}>", address.into_raw());
}
},
&dirtree,
);
Expand All @@ -45,14 +45,12 @@ pub fn print_function<K: IDAKind>(
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) {
Expand Down
Loading