summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorRubens Brandao <git@rubens.io>2024-08-14 18:12:52 -0300
committerMason Reed <mason@vector35.com>2024-08-30 10:17:29 -0400
commitfe0d4ae2c3baa26fca84a9a8cb9670955bdfea12 (patch)
tree6b502bbc8dcad7e71725afcd2924085c59a54fda
parent564f2a065fae5e08990c660926b147bca7c29226 (diff)
Initial IDB/TIL import plugin
-rw-r--r--rust/Cargo.lock66
-rw-r--r--rust/Cargo.toml1
-rw-r--r--rust/examples/idb_import/CMakeLists.txt91
-rw-r--r--rust/examples/idb_import/Cargo.toml14
-rw-r--r--rust/examples/idb_import/src/lib.rs863
5 files changed, 1023 insertions, 12 deletions
diff --git a/rust/Cargo.lock b/rust/Cargo.lock
index 2b1afe26..7738ecd6 100644
--- a/rust/Cargo.lock
+++ b/rust/Cargo.lock
@@ -79,9 +79,9 @@ dependencies = [
[[package]]
name = "anyhow"
-version = "1.0.82"
+version = "1.0.86"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f538837af36e6f6a9be0faa67f9a314f8119e4e4b5867c6ab40ed60360142519"
+checksum = "b3d1d046238990b9cf5bcde22a3fb3584ee5cf65fb2765f454ed428c7a0063da"
[[package]]
name = "autocfg"
@@ -115,6 +115,15 @@ dependencies = [
]
[[package]]
+name = "bincode"
+version = "1.3.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b1f45e9417d87227c7a56d22e471c6206462cba514c7590c09aff4cf6d1ddcad"
+dependencies = [
+ "serde",
+]
+
+[[package]]
name = "bindgen"
version = "0.69.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -398,9 +407,9 @@ checksum = "2acce4a10f12dc2fb14a218589d4f1f62ef011b2d0cc4b3cb1bba8e94da14649"
[[package]]
name = "flate2"
-version = "1.0.28"
+version = "1.0.31"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "46303f565772937ffe1d394a4fac6f411c6013172fadde9dcdb1e147a086940e"
+checksum = "7f211bbe8e69bbd0cfdea405084f128ae8b4aaa6b0b522fc8f2b009084797920"
dependencies = [
"crc32fast",
"miniz_oxide",
@@ -469,6 +478,28 @@ dependencies = [
]
[[package]]
+name = "idb-rs"
+version = "0.1.0"
+source = "git+https://github.com/Vector35/idb-rs#5f32597906d1dea26e403def7006c3ecc0370d66"
+dependencies = [
+ "anyhow",
+ "bincode",
+ "flate2",
+ "serde",
+ "serde_repr",
+]
+
+[[package]]
+name = "idb_import"
+version = "0.1.0"
+dependencies = [
+ "anyhow",
+ "binaryninja",
+ "idb-rs",
+ "log",
+]
+
+[[package]]
name = "indexmap"
version = "2.2.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -535,9 +566,9 @@ checksum = "01cda141df6706de531b6c46c3a33ecca755538219bd484262fa09410c13539c"
[[package]]
name = "log"
-version = "0.4.21"
+version = "0.4.22"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "90ed8c1e510134f979dbc4f070f87d4313098b704861a105fe34231c70a3901c"
+checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24"
[[package]]
name = "lzxd"
@@ -875,18 +906,29 @@ dependencies = [
[[package]]
name = "serde"
-version = "1.0.197"
+version = "1.0.205"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "3fb1c873e1b9b056a4dc4c0c198b24c3ffa059243875552b2bd0933b1aee4ce2"
+checksum = "e33aedb1a7135da52b7c21791455563facbbcc43d0f0f66165b42c21b3dfb150"
dependencies = [
"serde_derive",
]
[[package]]
name = "serde_derive"
-version = "1.0.197"
+version = "1.0.205"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "692d6f5ac90220161d6774db30c662202721e64aed9058d2c394f451261420c1"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.52",
+]
+
+[[package]]
+name = "serde_repr"
+version = "0.1.19"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "7eb0b34b42edc17f6b7cac84a52a1c5f0e1bb2227e997ca9011ea3dd34e8610b"
+checksum = "6c64451ba24fc7a6a2d60fc75dd9c83c90903b19028d4eff35e88fc1e86564e9"
dependencies = [
"proc-macro2",
"quote",
@@ -924,9 +966,9 @@ checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f"
[[package]]
name = "strsim"
-version = "0.11.0"
+version = "0.11.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "5ee073c9e4cd00e28217186dbe12796d692868f432bf2e97ee73bed0c56dfa01"
+checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f"
[[package]]
name = "syn"
diff --git a/rust/Cargo.toml b/rust/Cargo.toml
index 5ffc5ba4..789a4224 100644
--- a/rust/Cargo.toml
+++ b/rust/Cargo.toml
@@ -27,6 +27,7 @@ members = [
"examples/dwarf/dwarf_import",
"examples/dwarf/dwarfdump",
"examples/dwarf/shared",
+ "examples/idb_import",
"examples/flowgraph",
"examples/minidump",
"examples/mlil_visitor",
diff --git a/rust/examples/idb_import/CMakeLists.txt b/rust/examples/idb_import/CMakeLists.txt
new file mode 100644
index 00000000..5ae51716
--- /dev/null
+++ b/rust/examples/idb_import/CMakeLists.txt
@@ -0,0 +1,91 @@
+cmake_minimum_required(VERSION 3.9 FATAL_ERROR)
+
+project(idb_import)
+
+file(GLOB_RECURSE PLUGIN_SOURCES CONFIGURE_DEPENDS
+ ${PROJECT_SOURCE_DIR}/Cargo.toml
+ ${PROJECT_SOURCE_DIR}/src/*.rs)
+
+file(GLOB_RECURSE API_SOURCES CONFIGURE_DEPENDS
+ ${PROJECT_SOURCE_DIR}/../../../binaryninjacore.h
+ ${PROJECT_SOURCE_DIR}/../../binaryninjacore-sys/build.rs
+ ${PROJECT_SOURCE_DIR}/../../binaryninjacore-sys/Cargo.toml
+ ${PROJECT_SOURCE_DIR}/../../binaryninjacore-sys/src/*
+ ${PROJECT_SOURCE_DIR}/../../Cargo.toml
+ ${PROJECT_SOURCE_DIR}/../../src/*.rs)
+
+if(CMAKE_BUILD_TYPE MATCHES Debug)
+ set(TARGET_DIR ${PROJECT_BINARY_DIR}/target/debug)
+ set(CARGO_OPTS --target-dir=${PROJECT_BINARY_DIR}/target)
+else()
+ set(TARGET_DIR ${PROJECT_BINARY_DIR}/target/release)
+ set(CARGO_OPTS --target-dir=${PROJECT_BINARY_DIR}/target --release)
+ set(OUTPUT_PDB_NAME ${CMAKE_SHARED_LIBRARY_PREFIX}idb_import.pdb)
+endif()
+
+set(OUTPUT_FILE ${CMAKE_STATIC_LIBRARY_PREFIX}idb_import${CMAKE_SHARED_LIBRARY_SUFFIX})
+set(PLUGIN_PATH ${TARGET_DIR}/${OUTPUT_FILE})
+
+add_custom_target(idb_import ALL DEPENDS ${PLUGIN_PATH})
+add_dependencies(idb_import binaryninjaapi)
+
+find_program(RUSTUP_PATH rustup REQUIRED HINTS ~/.cargo/bin)
+if(CARGO_API_VERSION)
+ set(RUSTUP_COMMAND ${RUSTUP_PATH} run ${CARGO_API_VERSION} cargo build)
+else()
+ set(RUSTUP_COMMAND ${RUSTUP_PATH} run ${CARGO_STABLE_VERSION} cargo build)
+endif()
+
+if(APPLE)
+ if(UNIVERSAL)
+ if(CMAKE_BUILD_TYPE MATCHES Debug)
+ set(AARCH64_LIB_PATH ${PROJECT_BINARY_DIR}/target/aarch64-apple-darwin/debug/${OUTPUT_FILE})
+ set(X86_64_LIB_PATH ${PROJECT_BINARY_DIR}/target/x86_64-apple-darwin/debug/${OUTPUT_FILE})
+ else()
+ set(AARCH64_LIB_PATH ${PROJECT_BINARY_DIR}/target/aarch64-apple-darwin/release/${OUTPUT_FILE})
+ set(X86_64_LIB_PATH ${PROJECT_BINARY_DIR}/target/x86_64-apple-darwin/release/${OUTPUT_FILE})
+ endif()
+
+ add_custom_command(
+ OUTPUT ${PLUGIN_PATH}
+ COMMAND ${CMAKE_COMMAND} -E env
+ MACOSX_DEPLOYMENT_TARGET=10.14 BINARYNINJADIR=${BN_CORE_OUTPUT_DIR}
+ ${RUSTUP_COMMAND} --target=aarch64-apple-darwin ${CARGO_OPTS}
+ COMMAND ${CMAKE_COMMAND} -E env
+ MACOSX_DEPLOYMENT_TARGET=10.14 BINARYNINJADIR=${BN_CORE_OUTPUT_DIR}
+ ${RUSTUP_COMMAND} --target=x86_64-apple-darwin ${CARGO_OPTS}
+ COMMAND mkdir -p ${TARGET_DIR}
+ COMMAND lipo -create ${AARCH64_LIB_PATH} ${X86_64_LIB_PATH} -output ${PLUGIN_PATH}
+ COMMAND ${CMAKE_COMMAND} -E copy ${PLUGIN_PATH} ${BN_CORE_PLUGIN_DIR}
+ WORKING_DIRECTORY ${PROJECT_SOURCE_DIR}
+ DEPENDS ${PLUGIN_SOURCES} ${API_SOURCES})
+ else()
+ if(CMAKE_BUILD_TYPE MATCHES Debug)
+ set(LIB_PATH ${PROJECT_BINARY_DIR}/target/debug/${OUTPUT_FILE})
+ else()
+ set(LIB_PATH ${PROJECT_BINARY_DIR}/target/release/${OUTPUT_FILE})
+ endif()
+
+ add_custom_command(
+ OUTPUT ${PLUGIN_PATH}
+ COMMAND ${CMAKE_COMMAND} -E env MACOSX_DEPLOYMENT_TARGET=10.14 BINARYNINJADIR=${BN_CORE_OUTPUT_DIR} ${RUSTUP_COMMAND} ${CARGO_OPTS}
+ COMMAND ${CMAKE_COMMAND} -E copy ${PLUGIN_PATH} ${BN_CORE_PLUGIN_DIR}
+ WORKING_DIRECTORY ${PROJECT_SOURCE_DIR}
+ DEPENDS ${PLUGIN_SOURCES} ${API_SOURCES})
+ endif()
+elseif(WIN32)
+ add_custom_command(
+ OUTPUT ${PLUGIN_PATH}
+ COMMAND ${CMAKE_COMMAND} -E env BINARYNINJADIR=${BN_CORE_OUTPUT_DIR} ${RUSTUP_COMMAND} ${CARGO_OPTS}
+ COMMAND ${CMAKE_COMMAND} -E copy ${PLUGIN_PATH} ${BN_CORE_PLUGIN_DIR}
+ COMMAND ${CMAKE_COMMAND} -E copy ${TARGET_DIR}/${OUTPUT_PDB_NAME} ${BN_CORE_PLUGIN_DIR}
+ WORKING_DIRECTORY ${PROJECT_SOURCE_DIR}
+ DEPENDS ${PLUGIN_SOURCES} ${API_SOURCES})
+else()
+ add_custom_command(
+ OUTPUT ${PLUGIN_PATH}
+ COMMAND ${CMAKE_COMMAND} -E env BINARYNINJADIR=${BN_CORE_OUTPUT_DIR} ${RUSTUP_COMMAND} ${CARGO_OPTS}
+ COMMAND ${CMAKE_COMMAND} -E copy ${PLUGIN_PATH} ${BN_CORE_PLUGIN_DIR}
+ WORKING_DIRECTORY ${PROJECT_SOURCE_DIR}
+ DEPENDS ${PLUGIN_SOURCES} ${API_SOURCES})
+endif() \ No newline at end of file
diff --git a/rust/examples/idb_import/Cargo.toml b/rust/examples/idb_import/Cargo.toml
new file mode 100644
index 00000000..c54ba93b
--- /dev/null
+++ b/rust/examples/idb_import/Cargo.toml
@@ -0,0 +1,14 @@
+[package]
+name = "idb_import"
+version = "0.1.0"
+authors = ["Rubens Brandao <git@rubens.io>"]
+edition = "2021"
+
+[lib]
+crate-type = ["cdylib"]
+
+[dependencies]
+anyhow = "1.0.86"
+binaryninja = { path = "../../" }
+idb-rs = { git = "https://github.com/Vector35/idb-rs" }
+log = "0.4.20"
diff --git a/rust/examples/idb_import/src/lib.rs b/rust/examples/idb_import/src/lib.rs
new file mode 100644
index 00000000..34fe433a
--- /dev/null
+++ b/rust/examples/idb_import/src/lib.rs
@@ -0,0 +1,863 @@
+use std::collections::HashMap;
+
+use binaryninja::architecture::CoreArchitecture;
+use binaryninja::binaryninjacore_sys::{BNMemberAccess, BNMemberScope};
+use binaryninja::binaryview::{BinaryView, BinaryViewBase, BinaryViewExt};
+use binaryninja::debuginfo::{CustomDebugInfoParser, DebugInfo, DebugInfoParser};
+use binaryninja::logger;
+use binaryninja::rc::Ref;
+use binaryninja::types::{
+ Conf, EnumerationBuilder, FunctionParameter, NamedTypeReferenceClass, StructureBuilder,
+ StructureType, Type,
+};
+
+use idb_rs::til::{
+ array::Array as TILArray, function::Function as TILFunction, r#enum::Enum as TILEnum,
+ r#struct::Struct as TILStruct, r#struct::StructMember as TILStructMember, section::TILSection,
+ union::Union as TILUnion, TILTypeInfo, Type as TILType, Typedef as TILTypedef,
+};
+
+use log::{error, trace, warn, LevelFilter};
+
+use anyhow::Result;
+
+#[derive(Debug, Clone)]
+enum BnTypeError {
+ // TODO delete this and make this verification during the TIL/IDB parsing, translating the ordinal
+ // into a kind of type_idx
+ OrdinalNotFound(u32),
+ NameNotFound(String),
+
+ Typedef(Box<BnTypeError>),
+ Function(FunctionError),
+ Array(Box<BnTypeError>),
+ Pointer(Box<BnTypeError>),
+ /// Error for members
+ Struct(Vec<(usize, BnTypeError)>),
+ Union(Vec<(usize, BnTypeError)>),
+}
+
+#[derive(Default, Debug, Clone)]
+struct FunctionError {
+ ret: Option<Box<BnTypeError>>,
+ args: Vec<(usize, BnTypeError)>,
+}
+
+impl std::fmt::Display for BnTypeError {
+ fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+ match self {
+ BnTypeError::OrdinalNotFound(i) => write!(f, "Reference to non existing Ordinal {i}"),
+ BnTypeError::NameNotFound(name) => write!(f, "Reference to non existing name {name}"),
+ BnTypeError::Typedef(error) => write!(f, "Typedef: {error}"),
+ BnTypeError::Function(FunctionError { ret, args }) => {
+ if let Some(error) = ret {
+ write!(f, "Function return: {error} ")?;
+ }
+ for (i, error) in args {
+ write!(f, "Function argument {i}: {error} ")?;
+ }
+ Ok(())
+ }
+ BnTypeError::Array(error) => write!(f, "Array: {error}"),
+ BnTypeError::Struct(errors) => {
+ for (i, error) in errors {
+ write!(f, "Struct Member {i}: {error} ")?;
+ }
+ Ok(())
+ }
+ BnTypeError::Union(errors) => {
+ for (i, error) in errors {
+ write!(f, "Union Member {i}: {error} ")?;
+ }
+ Ok(())
+ }
+ BnTypeError::Pointer(error) => write!(f, "Pointer: {error}"),
+ }
+ }
+}
+
+struct IDBDebugInfoParser;
+impl CustomDebugInfoParser for IDBDebugInfoParser {
+ fn is_valid(&self, view: &BinaryView) -> bool {
+ view.file().filename().as_str().ends_with(".i64")
+ || view.file().filename().as_str().ends_with(".idb")
+ }
+
+ fn parse_info(
+ &self,
+ debug_info: &mut DebugInfo,
+ _bv: &BinaryView,
+ debug_file: &BinaryView,
+ progress: Box<dyn Fn(usize, usize) -> Result<(), ()>>,
+ ) -> bool {
+ match parse_idb_info(debug_info, debug_file, progress) {
+ Ok(()) => true,
+ Err(error) => {
+ error!("Unable to parse IDB file: {error}");
+ false
+ }
+ }
+ }
+}
+
+struct TILDebugInfoParser;
+impl CustomDebugInfoParser for TILDebugInfoParser {
+ fn is_valid(&self, view: &BinaryView) -> bool {
+ view.file().filename().as_str().ends_with(".til")
+ }
+
+ fn parse_info(
+ &self,
+ debug_info: &mut DebugInfo,
+ _bv: &BinaryView,
+ debug_file: &BinaryView,
+ progress: Box<dyn Fn(usize, usize) -> Result<(), ()>>,
+ ) -> bool {
+ match parse_til_info(debug_info, debug_file, progress) {
+ Ok(()) => true,
+ Err(error) => {
+ error!("Unable to parse TIL file: {error}");
+ false
+ }
+ }
+ }
+}
+
+struct BinaryViewReader<'a> {
+ bv: &'a BinaryView,
+ offset: u64,
+}
+impl std::io::Read for BinaryViewReader<'_> {
+ fn read(&mut self, buf: &mut [u8]) -> std::io::Result<usize> {
+ if !self.bv.offset_valid(self.offset) {
+ return Err(std::io::Error::new(std::io::ErrorKind::UnexpectedEof, ""));
+ }
+ let len = self.bv.read(buf, self.offset);
+ self.offset += u64::try_from(len).unwrap();
+ Ok(len)
+ }
+}
+
+impl std::io::Seek for BinaryViewReader<'_> {
+ fn seek(&mut self, pos: std::io::SeekFrom) -> std::io::Result<u64> {
+ let new_offset = match pos {
+ std::io::SeekFrom::Start(offset) => Some(offset),
+ std::io::SeekFrom::End(end) => u64::try_from(self.bv.len())
+ .unwrap()
+ .checked_add_signed(end),
+ std::io::SeekFrom::Current(next) => self.offset.checked_add_signed(next),
+ };
+ let new_offset =
+ new_offset.ok_or_else(|| std::io::Error::new(std::io::ErrorKind::UnexpectedEof, ""))?;
+ if !self.bv.offset_valid(new_offset) {
+ return Err(std::io::Error::new(std::io::ErrorKind::UnexpectedEof, ""));
+ }
+ self.offset = new_offset;
+ Ok(new_offset)
+ }
+}
+
+fn parse_idb_info(
+ debug_info: &mut DebugInfo,
+ debug_file: &BinaryView,
+ progress: Box<dyn Fn(usize, usize) -> Result<(), ()>>,
+) -> Result<()> {
+ trace!("Opening a IDB file");
+ let file = BinaryViewReader {
+ bv: debug_file,
+ offset: 0,
+ };
+ trace!("Parsing a IDB file");
+ let file = std::io::BufReader::new(file);
+ let mut parser = idb_rs::IDBParser::new(file)?;
+ let Some(til_section) = parser.til_section_offset() else {
+ return Ok(());
+ };
+ trace!("Parsing the TIL section");
+ let til = parser.read_til_section(til_section)?;
+ parse_til_section_info(debug_info, debug_file, til, progress)
+}
+
+fn translate_enum(members: &[(Option<String>, u64)], bytesize: u64) -> Ref<Type> {
+ let eb = EnumerationBuilder::new();
+ for (i, (name, bytesize)) in members.iter().enumerate() {
+ let name = name.to_owned().unwrap_or_else(|| format!("member_{i}"));
+ eb.insert(name, *bytesize);
+ }
+ Type::enumeration(
+ &eb.finalize(),
+ usize::try_from(bytesize).unwrap(),
+ Conf::new(false, 0),
+ )
+}
+
+fn translate_basic(mdata: &idb_rs::til::Basic) -> Ref<Type> {
+ match *mdata {
+ idb_rs::til::Basic::Void => Type::void(),
+ idb_rs::til::Basic::Unknown { bytes: 0 } => Type::void(),
+ idb_rs::til::Basic::Unknown { bytes } => Type::array(&Type::char(), bytes.into()),
+ idb_rs::til::Basic::Bool { bytes } if bytes.get() == 1 => Type::bool(),
+ // NOTE Binja don't have any representation for bool other then the default
+ idb_rs::til::Basic::Bool { bytes } => Type::int(bytes.get().into(), false),
+ idb_rs::til::Basic::Char => Type::char(),
+ // TODO what exacly is Segment Register?
+ idb_rs::til::Basic::SegReg => Type::char(),
+ idb_rs::til::Basic::Int { bytes, is_signed } => {
+ // default into signed
+ let is_signed = is_signed.as_ref().copied().unwrap_or(true);
+ Type::int(bytes.get().into(), is_signed)
+ }
+ idb_rs::til::Basic::Float { bytes } => Type::float(bytes.get().into()),
+ }
+}
+
+fn parse_til_info(
+ debug_info: &mut DebugInfo,
+ debug_file: &BinaryView,
+ progress: Box<dyn Fn(usize, usize) -> Result<(), ()>>,
+) -> Result<()> {
+ trace!("Opening a TIL file");
+ let file = BinaryViewReader {
+ bv: debug_file,
+ offset: 0,
+ };
+ let file = std::io::BufReader::new(file);
+ trace!("Parsing the TIL section");
+ let til = TILSection::parse(file)?;
+ parse_til_section_info(debug_info, debug_file, til, progress)
+}
+
+#[derive(Default)]
+enum TranslateTypeResult {
+ #[default]
+ NotYet,
+ /// Unable to solve type, there is no point in trying again
+ Error(BnTypeError),
+ /// a type that is not final, but equivalent to the final type, if error, there is no
+ /// point in trying again
+ PartiallyTranslated(Ref<Type>, Option<BnTypeError>),
+ Translated(Ref<Type>),
+}
+
+impl From<Result<Ref<Type>, BnTypeError>> for TranslateTypeResult {
+ fn from(value: Result<Ref<Type>, BnTypeError>) -> Self {
+ match value {
+ Ok(ty) => Self::Translated(ty),
+ Err(error) => Self::Error(error),
+ }
+ }
+}
+
+struct TranslatesIDBType<'a> {
+ // sanitized name form IDB
+ name: String,
+ // class, just to make easy to create named_type
+ _class: NamedTypeReferenceClass,
+ // the result, if converted
+ ty: TranslateTypeResult,
+ og_ty: &'a TILTypeInfo,
+ is_symbol: bool,
+}
+
+struct TranslateIDBTypes<'a> {
+ arch: CoreArchitecture,
+ debug_info: &'a mut DebugInfo,
+ _debug_file: &'a BinaryView,
+ progress: Box<dyn Fn(usize, usize) -> Result<(), ()>>,
+ til: &'a TILSection,
+ // note it's mapped 1:1 with the same index from til types.chain(symbols)
+ types: Vec<TranslatesIDBType<'a>>,
+ // ordinals with index to types
+ types_by_ord: HashMap<u64, usize>,
+ // original names with index to types
+ types_by_name: HashMap<String, usize>,
+}
+
+impl TranslateIDBTypes<'_> {
+ fn find_typedef_by_ordinal(&self, ord: u64) -> Option<TranslateTypeResult> {
+ self.types_by_ord
+ .get(&ord)
+ .map(|idx| self.find_typedef(&self.types[*idx]))
+ }
+
+ fn find_typedef_by_name(&self, name: &str) -> Option<TranslateTypeResult> {
+ if name.is_empty() {
+ // TODO this is my assumption, maybe an empty names Typedef means something else.
+ return Some(TranslateTypeResult::Translated(Type::void()));
+ }
+
+ if let Some(other_ty) = self
+ .types_by_name
+ .get(name)
+ .map(|idx| self.find_typedef(&self.types[*idx]))
+ {
+ return Some(other_ty);
+ }
+
+ // check for types that ar usually not defined directly
+ match name {
+ "Unkown" | "uint8_t" => Some(TranslateTypeResult::Translated(Type::int(1, false))),
+ "IUnkown" | "int8_t" => Some(TranslateTypeResult::Translated(Type::int(1, true))),
+ "SHORT" | "USHORT" => Some(TranslateTypeResult::Translated(Type::int(
+ self.til
+ .sizes
+ .map(|x| x.size_short.get())
+ .unwrap_or(2)
+ .into(),
+ name == "SHORT",
+ ))),
+ "int16_t" => Some(TranslateTypeResult::Translated(Type::int(2, true))),
+ "uint16_t" => Some(TranslateTypeResult::Translated(Type::int(2, false))),
+ "int32_t" => Some(TranslateTypeResult::Translated(Type::int(4, true))),
+ "uint32_t" => Some(TranslateTypeResult::Translated(Type::int(4, false))),
+ "int64_t" => Some(TranslateTypeResult::Translated(Type::int(8, true))),
+ "uint64_t" => Some(TranslateTypeResult::Translated(Type::int(8, false))),
+ "int128_t" => Some(TranslateTypeResult::Translated(Type::int(16, true))),
+ "uint128_t" => Some(TranslateTypeResult::Translated(Type::int(16, false))),
+ _ => None,
+ }
+ }
+
+ fn find_typedef(&self, ty: &TranslatesIDBType) -> TranslateTypeResult {
+ // only return a typedef, if it's solved, at least partially
+ match &ty.ty {
+ TranslateTypeResult::NotYet => TranslateTypeResult::NotYet,
+ TranslateTypeResult::Error(error) => {
+ TranslateTypeResult::Error(BnTypeError::Typedef(Box::new(error.to_owned())))
+ }
+ TranslateTypeResult::PartiallyTranslated(og_ty, error) => {
+ TranslateTypeResult::PartiallyTranslated(
+ Type::named_type_from_type(ty.name.as_str(), &og_ty),
+ error
+ .as_ref()
+ .map(|x| BnTypeError::Typedef(Box::new(x.clone())))
+ .clone(),
+ )
+ }
+ TranslateTypeResult::Translated(og_ty) => TranslateTypeResult::Translated(
+ Type::named_type_from_type(ty.name.as_str(), &og_ty),
+ ),
+ }
+ }
+
+ fn translate_pointer(&self, ty: &TILType) -> TranslateTypeResult {
+ match self.translate_type(ty) {
+ TranslateTypeResult::Translated(trans) => {
+ TranslateTypeResult::Translated(Type::pointer(&self.arch, &trans))
+ }
+ TranslateTypeResult::PartiallyTranslated(trans, error) => {
+ TranslateTypeResult::PartiallyTranslated(
+ Type::pointer(&self.arch, &trans),
+ error.map(|e| BnTypeError::Pointer(Box::new(e))),
+ )
+ }
+ TranslateTypeResult::Error(error) => TranslateTypeResult::PartiallyTranslated(
+ Type::pointer(&self.arch, &Type::void()),
+ Some(error),
+ ),
+ TranslateTypeResult::NotYet => TranslateTypeResult::PartiallyTranslated(
+ Type::pointer(&self.arch, &Type::void()),
+ None,
+ ),
+ }
+ }
+
+ fn translate_function(&self, fun: &TILFunction) -> TranslateTypeResult {
+ let mut is_partial = false;
+ let mut errors: FunctionError = Default::default();
+ // funtions are always 0 len, so it's translated or partial(void)
+ let return_ty = match self.translate_type(&fun.ret) {
+ TranslateTypeResult::Translated(trans) => trans,
+ TranslateTypeResult::PartiallyTranslated(trans, error) => {
+ is_partial |= true;
+ errors.ret = error.map(Box::new);
+ trans
+ }
+ TranslateTypeResult::Error(error) => {
+ errors.ret = Some(Box::new(error));
+ return TranslateTypeResult::PartiallyTranslated(
+ Type::void(),
+ Some(BnTypeError::Function(errors)),
+ );
+ }
+ TranslateTypeResult::NotYet => {
+ return TranslateTypeResult::PartiallyTranslated(Type::void(), None)
+ }
+ };
+ let mut partial_error_args = vec![];
+ let mut bn_args = Vec::with_capacity(fun.args.len());
+ for (i, (arg_name, arg_type, _arg_loc)) in fun.args.iter().enumerate() {
+ let arg = match self.translate_type(arg_type) {
+ TranslateTypeResult::Translated(trans) => trans,
+ TranslateTypeResult::PartiallyTranslated(trans, error) => {
+ is_partial = true;
+ if let Some(error) = error {
+ errors.args.push((i, error));
+ }
+ trans
+ }
+ TranslateTypeResult::NotYet => {
+ return TranslateTypeResult::PartiallyTranslated(Type::void(), None)
+ }
+ TranslateTypeResult::Error(error) => {
+ partial_error_args.push((i, error));
+ return TranslateTypeResult::PartiallyTranslated(
+ Type::void(),
+ Some(BnTypeError::Function(errors)),
+ );
+ }
+ };
+ // TODO create location from `arg_loc`?
+ let loc = None;
+ let name = arg_name.to_owned().unwrap_or_else(|| format!("arg_{i}"));
+ bn_args.push(FunctionParameter::new(arg, name, loc));
+ }
+
+ let ty = Type::function(&return_ty, &bn_args, false);
+ if is_partial {
+ let error = (errors.ret.is_some() || !errors.args.is_empty())
+ .then(|| BnTypeError::Function(errors));
+ TranslateTypeResult::PartiallyTranslated(ty, error)
+ } else {
+ assert!(errors.ret.is_none() && errors.args.is_empty());
+ TranslateTypeResult::Translated(ty)
+ }
+ }
+
+ fn translate_array(&self, array: &TILArray) -> TranslateTypeResult {
+ match self.translate_type(&*array.elem_type) {
+ TranslateTypeResult::NotYet => TranslateTypeResult::NotYet,
+ TranslateTypeResult::Translated(ty) => {
+ TranslateTypeResult::Translated(Type::array(&ty, array.nelem.into()))
+ }
+ TranslateTypeResult::PartiallyTranslated(ty, error) => {
+ TranslateTypeResult::PartiallyTranslated(
+ Type::array(&ty, array.nelem.into()),
+ error.map(Box::new).map(BnTypeError::Array),
+ )
+ }
+ TranslateTypeResult::Error(error) => {
+ TranslateTypeResult::Error(BnTypeError::Array(Box::new(error)))
+ }
+ }
+ }
+
+ fn translate_bitfields_into_struct(
+ &self,
+ offset: usize,
+ members_slice: &[TILStructMember],
+ struct_builder: &StructureBuilder,
+ ) {
+ if members_slice.is_empty() {
+ unreachable!()
+ }
+ let mut members = members_slice
+ .iter()
+ .map(|ty| match &ty.member_type {
+ TILType::Bitfield(b) => b,
+ _ => unreachable!(),
+ })
+ .enumerate();
+ let (_, first_field) = members.next().unwrap();
+ let mut current_field_bytes = first_field.nbytes;
+ let mut current_field_bits: u32 = first_field.width.into();
+ let mut start_idx = 0;
+
+ let create_field = |start_idx, i, bytes| {
+ let name = if start_idx == i - 1 {
+ let member: &TILStructMember = &members_slice[i - 1];
+ let name: &Option<String> = &member.name;
+ name.to_owned()
+ .unwrap_or_else(|| format!("bitfield_{}", offset + start_idx))
+ } else {
+ format!("bitfield_{}_{}", offset + start_idx, offset + (i - 1))
+ };
+ let field = field_from_bytes(bytes);
+ struct_builder.append(
+ &field,
+ name,
+ BNMemberAccess::NoAccess,
+ BNMemberScope::NoScope,
+ );
+ };
+
+ for (i, member) in members {
+ // starting a new field
+ let max_bits = u32::try_from(current_field_bytes).unwrap() * 8;
+ // this bitfield start a a new field, or can't contain other bitfields
+ // finish the previous and start a new
+ if current_field_bytes != member.nbytes
+ || max_bits < current_field_bits + u32::from(member.width)
+ {
+ create_field(start_idx, i, current_field_bytes);
+ current_field_bytes = member.nbytes;
+ current_field_bits = 0;
+ start_idx = i;
+ }
+
+ // just add the current bitfield into the field
+ current_field_bits += u32::from(member.width);
+ }
+
+ if current_field_bits != 0 {
+ create_field(start_idx, members_slice.len(), current_field_bytes);
+ }
+ }
+
+ fn translate_struct(
+ &self,
+ members: &[TILStructMember],
+ effective_alignment: u16,
+ ) -> TranslateTypeResult {
+ if members.is_empty() {
+ // binary ninja crashes if you create an empty struct, because it divide by 0
+ return TranslateTypeResult::Translated(Type::void());
+ }
+ let mut is_partial = false;
+ let structure = StructureBuilder::new();
+ structure.set_alignment(effective_alignment.into());
+
+ let mut errors = vec![];
+ let mut first_bitfield_seq = None;
+ for (i, member) in members.iter().enumerate() {
+ match (&member.member_type, first_bitfield_seq) {
+ // accumulate the bitfield to be condensated
+ (TILType::Bitfield(_bit), None) => {
+ first_bitfield_seq = Some(i);
+ continue;
+ }
+ (TILType::Bitfield(_bit), Some(_)) => continue,
+
+ // condensate the bitfields into byte-wide fields
+ (_, Some(start_idx)) => {
+ first_bitfield_seq = None;
+ let members_bitrange = &members[start_idx..i];
+ self.translate_bitfields_into_struct(start_idx, members_bitrange, &structure);
+ }
+
+ (_, None) => {}
+ }
+
+ let mem = match self.translate_type(&member.member_type) {
+ TranslateTypeResult::Translated(ty) => ty,
+ TranslateTypeResult::PartiallyTranslated(partial_ty, error) => {
+ is_partial |= true;
+ if let Some(error) = error {
+ errors.push((i, error));
+ }
+ partial_ty
+ }
+ TranslateTypeResult::NotYet => return TranslateTypeResult::NotYet,
+ TranslateTypeResult::Error(error) => {
+ errors.push((i, error));
+ return TranslateTypeResult::Error(BnTypeError::Struct(errors));
+ }
+ };
+ let name = member
+ .name
+ .to_owned()
+ .unwrap_or_else(|| format!("member_{i}"));
+ structure.append(&mem, name, BNMemberAccess::NoAccess, BNMemberScope::NoScope);
+ }
+ if let Some(start_idx) = first_bitfield_seq {
+ let members_bitrange = &members[start_idx..];
+ self.translate_bitfields_into_struct(start_idx, members_bitrange, &structure);
+ }
+ let bn_ty = Type::structure(&structure.finalize());
+ if is_partial {
+ let partial_error = (!errors.is_empty()).then_some(BnTypeError::Struct(errors));
+ TranslateTypeResult::PartiallyTranslated(bn_ty, partial_error)
+ } else {
+ assert!(errors.is_empty());
+ TranslateTypeResult::Translated(bn_ty)
+ }
+ }
+
+ fn translate_union(
+ &self,
+ members: &[(Option<String>, TILType)],
+ _effective_alignment: u16,
+ ) -> TranslateTypeResult {
+ let mut is_partial = false;
+ let structure = StructureBuilder::new();
+ structure.set_structure_type(StructureType::UnionStructureType);
+ let mut errors = vec![];
+ for (i, (member_name, member_type)) in members.iter().enumerate() {
+ // bitfields can be translated into complete fields
+ let mem = match member_type {
+ TILType::Bitfield(field) => field_from_bytes(field.nbytes),
+ member_type => match self.translate_type(member_type) {
+ TranslateTypeResult::Translated(ty) => ty,
+ TranslateTypeResult::Error(error) => {
+ errors.push((i, error));
+ return TranslateTypeResult::Error(BnTypeError::Union(errors));
+ }
+ TranslateTypeResult::NotYet => return TranslateTypeResult::NotYet,
+ TranslateTypeResult::PartiallyTranslated(partial, error) => {
+ is_partial |= true;
+ if let Some(error) = error {
+ errors.push((i, error));
+ }
+ partial
+ }
+ },
+ };
+
+ let name = member_name
+ .to_owned()
+ .unwrap_or_else(|| format!("member_{i}"));
+ structure.append(&mem, name, BNMemberAccess::NoAccess, BNMemberScope::NoScope);
+ }
+ let str_ref = structure.finalize();
+
+ let bn_ty = Type::structure(&str_ref);
+ if is_partial {
+ let partial_error = (!errors.is_empty()).then_some(BnTypeError::Struct(errors));
+ TranslateTypeResult::PartiallyTranslated(bn_ty, partial_error)
+ } else {
+ assert!(errors.is_empty());
+ TranslateTypeResult::Translated(bn_ty)
+ }
+ }
+
+ fn translate_type(&self, ty: &TILType) -> TranslateTypeResult {
+ match &ty {
+ // types that are always translatable
+ TILType::Basic(meta) => TranslateTypeResult::Translated(translate_basic(meta)),
+ TILType::Bitfield(bit) => TranslateTypeResult::Translated(field_from_bytes(bit.nbytes)),
+ TILType::Enum(TILEnum::NonRef {
+ members, bytesize, ..
+ }) => TranslateTypeResult::Translated(translate_enum(members, *bytesize)),
+ TILType::Typedef(TILTypedef::Ordinal(ord)) => self
+ .find_typedef_by_ordinal((*ord).into())
+ .unwrap_or_else(|| TranslateTypeResult::Error(BnTypeError::OrdinalNotFound(*ord))),
+ TILType::Typedef(TILTypedef::Name(name)) => {
+ self.find_typedef_by_name(name).unwrap_or_else(|| {
+ TranslateTypeResult::Error(BnTypeError::NameNotFound(name.to_owned()))
+ })
+ }
+
+ // may not be translatable imediatly, but the size is known and can be
+ // updated after alBasicers are finished
+ TILType::Union(TILUnion::Ref { ref_type, .. })
+ | TILType::Struct(TILStruct::Ref { ref_type, .. })
+ | TILType::Enum(TILEnum::Ref { ref_type, .. }) => self.translate_pointer(&**ref_type),
+ TILType::Pointer(ty) => self.translate_pointer(&ty.typ),
+ TILType::Function(fun) => self.translate_function(fun),
+
+ // can only be partially solved if all fields are solved or partially solved
+ TILType::Array(array) => self.translate_array(array),
+ TILType::Struct(TILStruct::NonRef {
+ members,
+ effective_alignment,
+ ..
+ }) => self.translate_struct(members, *effective_alignment),
+ TILType::Union(TILUnion::NonRef {
+ members,
+ effective_alignment,
+ ..
+ }) => self.translate_union(members, *effective_alignment),
+ }
+ }
+}
+
+fn find_typedef_named_type_class(ty: &TILType) -> NamedTypeReferenceClass {
+ match ty {
+ TILType::Typedef(_) => NamedTypeReferenceClass::TypedefNamedTypeClass,
+ TILType::Struct(_) => NamedTypeReferenceClass::StructNamedTypeClass,
+ TILType::Union(_) => NamedTypeReferenceClass::UnionNamedTypeClass,
+ TILType::Enum(_) => NamedTypeReferenceClass::EnumNamedTypeClass,
+ _ => NamedTypeReferenceClass::UnknownNamedTypeClass,
+ }
+}
+
+fn field_from_bytes(bytes: i32) -> Ref<Type> {
+ match bytes {
+ 0 => unreachable!(),
+ num @ (1 | 2 | 4 | 8 | 16) => Type::int(num.try_into().unwrap(), false),
+ nelem => Type::array(&Type::char(), nelem.try_into().unwrap()),
+ }
+}
+
+fn parse_til_section_info(
+ debug_info: &mut DebugInfo,
+ debug_file: &BinaryView,
+ til: TILSection,
+ progress: Box<dyn Fn(usize, usize) -> Result<(), ()>>,
+) -> Result<()> {
+ let total = til.symbols.len() + til.types.len();
+ let mut types = Vec::with_capacity(total);
+ let mut types_by_ord = HashMap::with_capacity(total);
+ let mut types_by_name = HashMap::with_capacity(total);
+ let all_types = til.types.iter().zip(core::iter::repeat(false));
+ // TODO: it's unclear how the demangle symbols and types names/ord, for now only parse types
+ //let all_types = all_types.chain(til.symbols.iter().zip(core::iter::repeat(true)));
+ for (i, (ty, is_symbol)) in all_types.enumerate() {
+ // TODO sanitized the input
+ // TODO find out how the namespaces used by TIL works
+ let name = ty.name.to_owned();
+ types.push(TranslatesIDBType {
+ name,
+ is_symbol,
+ og_ty: ty,
+ _class: find_typedef_named_type_class(&ty.tinfo),
+ ty: TranslateTypeResult::NotYet,
+ });
+ if ty.ordinal != 0 && !is_symbol {
+ let dup1 = types_by_ord.insert(ty.ordinal, i);
+ if let Some(old) = dup1 {
+ let old_type = &types[old];
+ let new_type = types.last().unwrap();
+ // TODO error?
+ panic!(
+ "dup ord {}:{} {}:\n{:?}\n{:?}",
+ old_type.is_symbol,
+ new_type.is_symbol,
+ ty.ordinal,
+ &old_type.og_ty,
+ &new_type.og_ty,
+ )
+ }
+ }
+ if ty.name != "" {
+ let dup2 = types_by_name.insert(ty.name.to_owned(), i);
+ if let Some(old) = dup2 {
+ let old_type = &types[old];
+ let new_type = types.last().unwrap();
+ // TODO error?
+ panic!(
+ "dup name {}:{}: {}:\n{:?}\n{:?}",
+ old_type.is_symbol,
+ new_type.is_symbol,
+ &ty.name,
+ &old_type.og_ty,
+ &new_type.og_ty,
+ )
+ }
+ }
+ }
+
+ let mut translator = TranslateIDBTypes {
+ debug_info,
+ _debug_file: debug_file,
+ arch: debug_file.default_arch().unwrap(/* TODO */),
+ progress,
+ til: &til,
+ types,
+ types_by_ord,
+ types_by_name,
+ };
+ if (translator.progress)(0, total).is_err() {
+ warn!("IDB import aborted");
+ return Ok(());
+ }
+
+ // solve types until there is nothing else being solved
+ loop {
+ // is something was solved, mark this variable as true
+ let mut did_something = false;
+ let mut num_translated = 0usize;
+ for i in 0..translator.types.len() {
+ match &translator.types[i].ty {
+ TranslateTypeResult::NotYet => {
+ let result = translator.translate_type(&translator.types[i].og_ty.tinfo);
+ did_something |= !matches!(&result, TranslateTypeResult::NotYet);
+ translator.types[i].ty = result;
+ // if originaly NotKnow and now translated, update the result on bn
+ match &translator.types[i].ty {
+ // ignore partial errors here, they will be printed below
+ TranslateTypeResult::PartiallyTranslated(bn_ty, _)
+ | TranslateTypeResult::Translated(bn_ty) => {
+ let name = &translator.types[i].name;
+ let success =
+ translator.debug_info.add_type(name, &bn_ty, &[/* TODO */]);
+ if !success {
+ error!("Unable to add type `{}`", name)
+ }
+ }
+ _ => {}
+ }
+ }
+ TranslateTypeResult::PartiallyTranslated(_, None) => {
+ let result = translator.translate_type(&translator.types[i].og_ty.tinfo);
+ assert!(!matches!(&result, TranslateTypeResult::NotYet));
+ did_something |=
+ !matches!(&result, TranslateTypeResult::PartiallyTranslated(_, None));
+ translator.types[i].ty = result;
+ // don't need to add again they will be fixed on the loop below
+ }
+ // if an error was produced, there is no point in try again
+ TranslateTypeResult::PartiallyTranslated(_, Some(_)) => {}
+ // NOTE for now we are just accumulating errors, just try to translate the max number
+ // of types as possible
+ TranslateTypeResult::Error(_) => {}
+ // already translated, nothing do to here
+ TranslateTypeResult::Translated(_) => {}
+ }
+
+ // count the number of finished types
+ match &translator.types[i].ty {
+ TranslateTypeResult::Translated(_) => num_translated += 1,
+ _ => {}
+ }
+ }
+
+ if !did_something {
+ // means we acomplilshed nothing during this loop, there is no point in trying again
+ break;
+ }
+ if (translator.progress)(num_translated, total).is_err() {
+ // error means the user aborted the progress
+ break;
+ }
+ }
+
+ // print any errors
+ for ty in &translator.types {
+ match &ty.ty {
+ TranslateTypeResult::NotYet => {
+ panic!(
+ "type could not be processed `{}`: {:#?}",
+ &ty.name, &ty.og_ty
+ );
+ }
+ TranslateTypeResult::Error(error) => {
+ error!("Unable to parse type `{}`: {error}", &ty.name);
+ }
+ TranslateTypeResult::PartiallyTranslated(_, error) => {
+ if let Some(error) = error {
+ error!("Unable to parse type `{}` correctly: {error}", &ty.name);
+ } else {
+ warn!("Type `{}` maybe not be fully translated", &ty.name);
+ }
+ }
+ TranslateTypeResult::Translated(_) => {}
+ };
+ }
+
+ // add a second time to fix the references LOL
+ for ty in &translator.types {
+ match &ty.ty {
+ TranslateTypeResult::Translated(bn_ty)
+ | TranslateTypeResult::PartiallyTranslated(bn_ty, _) => {
+ let success = translator
+ .debug_info
+ .add_type(&ty.name, &bn_ty, &[/* TODO */]);
+ if !success {
+ error!("Unable to fix type `{}`", &ty.name)
+ }
+ }
+ _ => {}
+ }
+ }
+
+ Ok(())
+}
+
+#[no_mangle]
+pub extern "C" fn CorePluginInit() -> bool {
+ let _logger = logger::init(LevelFilter::Error);
+ DebugInfoParser::register("IDB Parser", IDBDebugInfoParser);
+ DebugInfoParser::register("TIL Parser", TILDebugInfoParser);
+ true
+}