From 2167e0512a24c5b9d888f9c242030ddb25c89d85 Mon Sep 17 00:00:00 2001 From: Rairosu Date: Sat, 17 Aug 2024 16:20:28 +0200 Subject: [PATCH] update rust crate --- Cargo.lock | 198 +- Cargo.toml | 7 + binaryninjacore-sys/Cargo.toml | 2 +- binaryninjacore-sys/build.rs | 7 +- examples/basic_script/src/main.rs | 4 +- examples/decompile/src/main.rs | 2 +- examples/dwarf/dwarf_export/Cargo.toml | 2 +- examples/dwarf/dwarf_export/src/lib.rs | 22 +- examples/dwarf/dwarf_import/Cargo.toml | 5 +- .../dwarf/dwarf_import/src/die_handlers.rs | 90 +- .../dwarf/dwarf_import/src/dwarfdebuginfo.rs | 304 +- examples/dwarf/dwarf_import/src/functions.rs | 142 +- examples/dwarf/dwarf_import/src/helpers.rs | 428 ++- examples/dwarf/dwarf_import/src/lib.rs | 418 ++- examples/dwarf/dwarf_import/src/types.rs | 136 +- examples/dwarf/dwarfdump/Cargo.toml | 2 +- examples/dwarf/dwarfdump/src/lib.rs | 47 +- examples/dwarf/shared/Cargo.toml | 2 +- examples/dwarf/shared/src/lib.rs | 42 +- examples/flowgraph/src/lib.rs | 9 +- examples/hlil_visitor/src/main.rs | 132 +- examples/minidump/src/command.rs | 5 +- examples/minidump/src/view.rs | 32 +- examples/mlil_visitor/src/main.rs | 141 +- examples/pdb-ng/.gitignore | 1 + examples/pdb-ng/CMakeLists.txt | 138 + examples/pdb-ng/Cargo.lock | 540 ++++ examples/pdb-ng/Cargo.toml | 20 + examples/pdb-ng/demo/Cargo.lock | 555 ++++ examples/pdb-ng/demo/Cargo.toml | 21 + examples/pdb-ng/src/lib.rs | 937 +++++++ examples/pdb-ng/src/parser.rs | 508 ++++ examples/pdb-ng/src/struct_grouper.rs | 1164 ++++++++ examples/pdb-ng/src/symbol_parser.rs | 2061 ++++++++++++++ examples/pdb-ng/src/type_parser.rs | 2477 +++++++++++++++++ examples/template/src/main.rs | 4 +- src/architecture.rs | 161 +- src/backgroundtask.rs | 13 +- src/basicblock.rs | 20 +- src/binaryview.rs | 368 ++- src/callingconvention.rs | 102 +- src/command.rs | 38 +- src/component.rs | 295 ++ src/custombinaryview.rs | 31 +- src/database.rs | 654 +++++ src/databuffer.rs | 262 +- src/debuginfo.rs | 117 +- src/demangle.rs | 53 +- src/disassembly.rs | 109 +- src/downloadprovider.rs | 24 +- src/enterprise.rs | 196 ++ src/externallibrary.rs | 209 ++ src/filemetadata.rs | 71 +- src/flowgraph.rs | 10 +- src/function.rs | 1899 ++++++++++++- src/headless.rs | 40 +- src/hlil/function.rs | 41 + src/hlil/instruction.rs | 26 +- src/hlil/lift.rs | 138 +- src/hlil/operation.rs | 16 +- src/interaction.rs | 34 +- src/lib.rs | 169 +- src/linearview.rs | 10 +- src/llil/expression.rs | 34 + src/llil/instruction.rs | 2 +- src/llil/lifting.rs | 57 + src/llil/operation.rs | 131 +- src/logger.rs | 13 +- src/metadata.rs | 122 +- src/mlil/function.rs | 629 ++++- src/mlil/instruction.rs | 458 ++- src/mlil/lift.rs | 143 +- src/mlil/operation.rs | 6 +- src/operand_iter.rs | 3 +- src/platform.rs | 20 +- src/project.rs | 1464 ++++++++++ src/rc.rs | 117 +- src/references.rs | 24 +- src/relocation.rs | 31 +- src/section.rs | 17 +- src/segment.rs | 17 +- src/string.rs | 130 +- src/symbol.rs | 31 +- src/tags.rs | 76 +- src/typearchive.rs | 947 +++++++ src/typelibrary.rs | 367 +++ src/types.rs | 1427 ++++++++-- src/update.rs | 272 ++ 88 files changed, 20508 insertions(+), 1741 deletions(-) create mode 100644 examples/pdb-ng/.gitignore create mode 100644 examples/pdb-ng/CMakeLists.txt create mode 100644 examples/pdb-ng/Cargo.lock create mode 100644 examples/pdb-ng/Cargo.toml create mode 100644 examples/pdb-ng/demo/Cargo.lock create mode 100644 examples/pdb-ng/demo/Cargo.toml create mode 100644 examples/pdb-ng/src/lib.rs create mode 100644 examples/pdb-ng/src/parser.rs create mode 100644 examples/pdb-ng/src/struct_grouper.rs create mode 100644 examples/pdb-ng/src/symbol_parser.rs create mode 100644 examples/pdb-ng/src/type_parser.rs create mode 100644 src/component.rs create mode 100644 src/database.rs create mode 100644 src/enterprise.rs create mode 100644 src/externallibrary.rs create mode 100644 src/project.rs create mode 100644 src/typearchive.rs create mode 100644 src/typelibrary.rs create mode 100644 src/update.rs diff --git a/Cargo.lock b/Cargo.lock index b656b4b..2b1afe2 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -22,9 +22,9 @@ dependencies = [ [[package]] name = "aho-corasick" -version = "1.1.3" +version = "1.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916" +checksum = "b2969dcb958b36655471fc61f7e416fa76033bdd4bfed0678d8fee1e2d07a1f0" dependencies = [ "memchr", ] @@ -78,10 +78,16 @@ dependencies = [ ] [[package]] -name = "autocfg" -version = "1.2.0" +name = "anyhow" +version = "1.0.82" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f1fdabc7756949593fe60f30ec81974b613357de856987752631dea1e3394c80" +checksum = "f538837af36e6f6a9be0faa67f9a314f8119e4e4b5867c6ab40ed60360142519" + +[[package]] +name = "autocfg" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" [[package]] name = "basic_script" @@ -110,32 +116,32 @@ dependencies = [ [[package]] name = "bindgen" -version = "0.68.1" +version = "0.69.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "726e4313eb6ec35d2730258ad4e15b547ee75d6afaa1361a922e78e59b7d8078" +checksum = "a00dc851838a2120612785d195287475a3ac45514741da670b735818822129a0" dependencies = [ "bitflags", "cexpr", "clang-sys", + "itertools", "lazy_static", "lazycell", "log", - "peeking_take_while", "prettyplease", "proc-macro2", "quote", "regex", "rustc-hash", "shlex", - "syn 2.0.57", + "syn 2.0.52", "which", ] [[package]] name = "bitflags" -version = "2.5.0" +version = "2.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cf4b9d6a944f767f8e5e0db018570623c85f3d925ac718db4e06d0187adb21c1" +checksum = "ed570934406eb16438a4e976b1b4500774099c13b8cb96eec99f620f05090ddf" [[package]] name = "byteorder" @@ -143,6 +149,18 @@ version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" +[[package]] +name = "cab" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae6b4de23c7d39c0631fd3cc952d87951c86c75a13812d7247cb7a896e7b3551" +dependencies = [ + "byteorder", + "flate2", + "lzxd", + "time", +] + [[package]] name = "cexpr" version = "0.6.0" @@ -171,9 +189,9 @@ dependencies = [ [[package]] name = "clap" -version = "4.5.4" +version = "4.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "90bc066a67923782aa8515dbaea16946c5bcc5addbd668bb80af688e53e548a0" +checksum = "b230ab84b0ffdf890d5a10abdbc8b83ae1c4918275daea1ab8801f71536b2651" dependencies = [ "clap_builder", "clap_derive", @@ -193,14 +211,14 @@ dependencies = [ [[package]] name = "clap_derive" -version = "4.5.4" +version = "4.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "528131438037fd55894f62d6e9f068b8f45ac57ffa77517819645d10aed04f64" +checksum = "307bc0538d5f0f83b8248db3087aa92fe504e4691294d0c96c0eabc33f47ba47" dependencies = [ "heck", "proc-macro2", "quote", - "syn 2.0.57", + "syn 2.0.52", ] [[package]] @@ -215,6 +233,15 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "acbf1af155f9b9ef647e42cdc158db4b64a1b61f743629225fde6f3e0be2a7c7" +[[package]] +name = "cpp_demangle" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7e8227005286ec39567949b33df9896bcadfa6051bccca2488129f108ca23119" +dependencies = [ + "cfg-if", +] + [[package]] name = "crc32fast" version = "1.4.0" @@ -301,9 +328,12 @@ name = "dwarf_import" version = "0.1.0" dependencies = [ "binaryninja", + "cpp_demangle", "dwarfreader", "gimli", + "iset", "log", + "regex", ] [[package]] @@ -354,6 +384,12 @@ dependencies = [ "windows-sys", ] +[[package]] +name = "fallible-iterator" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4443176a9f2c162692bd3d352d745ef9413eec5782a80d8fd6f8a1ac692a07f7" + [[package]] name = "fallible-iterator" version = "0.3.0" @@ -379,11 +415,11 @@ dependencies = [ [[package]] name = "gimli" -version = "0.28.1" +version = "0.31.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4271d37baee1b8c7e4b708028c57d816cf9d2434acb33a549475f78c181f6253" +checksum = "32085ea23f3234fc7846555e85283ba4de91e21016dc0455a16286d87a292d64" dependencies = [ - "fallible-iterator", + "fallible-iterator 0.3.0", "indexmap", "stable_deref_trait", ] @@ -405,9 +441,9 @@ dependencies = [ [[package]] name = "heck" -version = "0.5.0" +version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" +checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" [[package]] name = "hlil_lifter" @@ -443,10 +479,25 @@ dependencies = [ ] [[package]] -name = "itoa" -version = "1.0.11" +name = "iset" +version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b" +checksum = "d0716a0d7080cb7b20b9426276315e6ff5ed537bd920af47417b16de07f9ac76" + +[[package]] +name = "itertools" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1c173a5686ce8bfa551b3563d0c2170bf24ca44da99c7ca4bfdab5418c3fe57" +dependencies = [ + "either", +] + +[[package]] +name = "itoa" +version = "1.0.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1a46d1a171d865aa5f83f92695765caa047a9b4cbae2cbf37dbd613a793fd4c" [[package]] name = "lazy_static" @@ -489,10 +540,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "90ed8c1e510134f979dbc4f070f87d4313098b704861a105fe34231c70a3901c" [[package]] -name = "memchr" -version = "2.7.2" +name = "lzxd" +version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6c8640c5d730cb13ebd907d8d04b52f55ac9a2eec55b440c8892f40d56c76c1d" +checksum = "784462f20dddd9dfdb45de963fa4ad4a288cb10a7889ac5d2c34fb6481c6b213" + +[[package]] +name = "memchr" +version = "2.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "523dc4f511e55ab87b694dc30d0f820d60906ef06413f93d4d7a1385599cc149" [[package]] name = "memmap2" @@ -599,7 +656,7 @@ checksum = "ed3955f1a9c7c0c15e092f9c887db08b1fc683305fdf6eb6684f22555355e202" dependencies = [ "proc-macro2", "quote", - "syn 2.0.57", + "syn 2.0.52", ] [[package]] @@ -632,16 +689,47 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" [[package]] -name = "peeking_take_while" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "19b17cddbe7ec3f8bc800887bab5e717348c95ea2ca0b1bf0837fb964dc67099" +name = "pdb" +version = "0.8.0" +dependencies = [ + "fallible-iterator 0.2.0", + "scroll", + "uuid", +] + +[[package]] +name = "pdb-import-plugin" +version = "0.1.0" +dependencies = [ + "anyhow", + "binaryninja", + "cab", + "home", + "itertools", + "log", + "pdb", + "regex", +] + +[[package]] +name = "pdb-import-plugin-static" +version = "0.1.0" +dependencies = [ + "anyhow", + "binaryninja", + "cab", + "home", + "itertools", + "log", + "pdb", + "regex", +] [[package]] name = "pin-project-lite" -version = "0.2.14" +version = "0.2.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bda66fc9667c18cb2758a2ac84d1167245054bcf85d5d1aaa6923f45801bdd02" +checksum = "8afb450f006bf6385ca15ef45d71d2288452bc3683ce2e2cacc0d18e4be60b58" [[package]] name = "powerfmt" @@ -651,12 +739,12 @@ checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" [[package]] name = "prettyplease" -version = "0.2.17" +version = "0.2.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8d3928fb5db768cb86f891ff014f0144589297e3c6a1aba6ed7cecfdace270c7" +checksum = "a41cf62165e97c7f814d2221421dbb9afcbcdb0a88068e5ea206e19951c2cbb5" dependencies = [ "proc-macro2", - "syn 2.0.57", + "syn 2.0.52", ] [[package]] @@ -688,9 +776,9 @@ dependencies = [ [[package]] name = "rayon" -version = "1.10.0" +version = "1.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b418a60154510ca1a002a752ca9714984e21e4241e804d32555251faf8b78ffa" +checksum = "e4963ed1bc86e4f3ee217022bd855b297cef07fb9eac5dfa1f788b220b49b3bd" dependencies = [ "either", "rayon-core", @@ -708,9 +796,9 @@ dependencies = [ [[package]] name = "regex" -version = "1.10.4" +version = "1.10.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c117dbdfde9c8308975b6a18d71f3f385c89461f7b3fb054288ecf2a2058ba4c" +checksum = "b62dbe01f0b06f9d8dc7d49e05a0785f153b00b2c227856282f671e0318c9b15" dependencies = [ "aho-corasick", "memchr", @@ -731,9 +819,9 @@ dependencies = [ [[package]] name = "regex-syntax" -version = "0.8.3" +version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "adad44e29e4c806119491a7f06f03de4d1af22c3a680dd47f1e6e179439d1f56" +checksum = "c08c74e62047bb2de4ff487b251e4a92e24f48745648451635cec7d591162d9f" [[package]] name = "rustc-hash" @@ -743,9 +831,9 @@ checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" [[package]] name = "rustix" -version = "0.38.32" +version = "0.38.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "65e04861e65f21776e67888bfbea442b3642beaa0138fdb1dd7a84a52dffdb89" +checksum = "6ea3e1a662af26cd7a3ba09c0297a31af215563ecf42817c98df621387f4e949" dependencies = [ "bitflags", "errno", @@ -782,7 +870,7 @@ checksum = "1db149f81d46d2deba7cd3c50772474707729550221e69588478ebf9ada425ae" dependencies = [ "proc-macro2", "quote", - "syn 2.0.57", + "syn 2.0.52", ] [[package]] @@ -802,7 +890,7 @@ checksum = "7eb0b34b42edc17f6b7cac84a52a1c5f0e1bb2227e997ca9011ea3dd34e8610b" dependencies = [ "proc-macro2", "quote", - "syn 2.0.57", + "syn 2.0.52", ] [[package]] @@ -819,7 +907,7 @@ checksum = "0eb01866308440fc64d6c44d9e86c5cc17adfe33c4d6eed55da9145044d0ffc1" dependencies = [ "proc-macro2", "quote", - "syn 2.0.57", + "syn 2.0.52", ] [[package]] @@ -853,9 +941,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.57" +version = "2.0.52" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "11a6ae1e52eb25aab8f3fb9fca13be982a373b8f1157ca14b897a825ba4a2d35" +checksum = "b699d15b36d1f02c3e7c69f8ffef53de37aefae075d8488d4ba1a7788d574a07" dependencies = [ "proc-macro2", "quote", @@ -886,7 +974,7 @@ checksum = "c61f3ba182994efc43764a46c018c347bc492c79f024e705f46567b418f6d4f7" dependencies = [ "proc-macro2", "quote", - "syn 2.0.57", + "syn 2.0.52", ] [[package]] @@ -940,7 +1028,7 @@ checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7" dependencies = [ "proc-macro2", "quote", - "syn 2.0.57", + "syn 2.0.52", ] [[package]] @@ -976,9 +1064,9 @@ checksum = "711b9620af191e0cdc7468a8d14e709c3dcdb115b36f838e601583af800a370a" [[package]] name = "uuid" -version = "1.8.0" +version = "1.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a183cf7feeba97b4dd1c0d46788634f6221d87fa961b305bed08c851829efcc0" +checksum = "f00cc9702ca12d3c81455259621e676d0f7251cec66a21e98fe2e9a37db93b2a" [[package]] name = "version_check" @@ -1081,5 +1169,5 @@ checksum = "9ce1b18ccd8e73a9321186f97e46f9f04b778851177567b1975109d26a08d2a6" dependencies = [ "proc-macro2", "quote", - "syn 2.0.57", + "syn 2.0.52", ] diff --git a/Cargo.toml b/Cargo.toml index 8e174e6..5ffc5ba 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -3,6 +3,7 @@ name = "binaryninja" version = "0.1.0" authors = ["Ryan Snyder ", "Kyle Martin "] edition = "2021" +rust-version = "1.77.0" [features] noexports = [] @@ -14,6 +15,10 @@ libc = "0.2" rayon = { version = "1.8", optional = true } binaryninjacore-sys = { path = "binaryninjacore-sys" } +[patch.crates-io] +# Patched pdb crate to implement some extra structures +pdb = { path = "./examples/pdb-ng/pdb-0.8.0-patched" } + [workspace] members = [ "examples/basic_script", @@ -28,6 +33,8 @@ members = [ "examples/mlil_lifter", "examples/hlil_visitor", "examples/hlil_lifter", + "examples/pdb-ng", + "examples/pdb-ng/demo", "examples/template" ] diff --git a/binaryninjacore-sys/Cargo.toml b/binaryninjacore-sys/Cargo.toml index 95d21be..6faafaa 100644 --- a/binaryninjacore-sys/Cargo.toml +++ b/binaryninjacore-sys/Cargo.toml @@ -5,4 +5,4 @@ authors = ["Ryan Snyder ", "Kyle Martin "] build = "build.rs" [build-dependencies] -bindgen = "^0.68" +bindgen = "^0.69.2" diff --git a/binaryninjacore-sys/build.rs b/binaryninjacore-sys/build.rs index de7c5cd..7e2b4f3 100644 --- a/binaryninjacore-sys/build.rs +++ b/binaryninjacore-sys/build.rs @@ -83,9 +83,9 @@ fn main() { for line in BufReader::new(file).lines() { let line = line.unwrap(); if let Some(version) = line.strip_prefix(current_line) { - current_version = version.to_owned(); + version.clone_into(&mut current_version); } else if let Some(version) = line.strip_prefix(minimum_line) { - minimum_version = version.to_owned(); + version.clone_into(&mut minimum_version); } } @@ -96,6 +96,9 @@ fn main() { .clang_arg("c++") .size_t_is_usize(true) .generate_comments(false) + .derive_default(true) + .generate_comments(true) + .clang_arg("-fparse-all-comments") .allowlist_function("BN.*") .allowlist_var("BN_CURRENT_CORE_ABI_VERSION") .allowlist_var("BN_MINIMUM_CORE_ABI_VERSION") diff --git a/examples/basic_script/src/main.rs b/examples/basic_script/src/main.rs index b979c15..c5c1414 100644 --- a/examples/basic_script/src/main.rs +++ b/examples/basic_script/src/main.rs @@ -26,9 +26,7 @@ fn main() { .get_data(), addr, ) { - tokens - .iter() - .for_each(|token| print!("{}", token.text().as_str())); + tokens.iter().for_each(|token| print!("{}", token.text())); println!(); } } diff --git a/examples/decompile/src/main.rs b/examples/decompile/src/main.rs index 0865538..cac5ae0 100644 --- a/examples/decompile/src/main.rs +++ b/examples/decompile/src/main.rs @@ -26,7 +26,7 @@ fn decompile_to_c(view: &BinaryView, func: &Function) { let last = view.get_next_linear_disassembly_lines(&mut cursor.duplicate()); let first = view.get_previous_linear_disassembly_lines(&mut cursor); - let lines = first.into_iter().chain(last.into_iter()); + let lines = first.into_iter().chain(&last); for line in lines { println!("{}", line.as_ref()); diff --git a/examples/dwarf/dwarf_export/Cargo.toml b/examples/dwarf/dwarf_export/Cargo.toml index 5f537c6..715686f 100644 --- a/examples/dwarf/dwarf_export/Cargo.toml +++ b/examples/dwarf/dwarf_export/Cargo.toml @@ -8,6 +8,6 @@ crate-type = ["cdylib"] [dependencies] binaryninja = {path="../../../"} -gimli = "^0.28" +gimli = "^0.31" log = "^0.4" object = { version = "0.32.1", features = ["write"] } diff --git a/examples/dwarf/dwarf_export/src/lib.rs b/examples/dwarf/dwarf_export/src/lib.rs index 6af9afe..057abe2 100644 --- a/examples/dwarf/dwarf_export/src/lib.rs +++ b/examples/dwarf/dwarf_export/src/lib.rs @@ -522,13 +522,11 @@ fn export_data_vars( for data_variable in &bv.data_variables() { if let Some(symbol) = data_variable.symbol(bv) { - if symbol.sym_type() == SymbolType::External { - continue; - } else if symbol.sym_type() == SymbolType::Function { - continue; - } else if symbol.sym_type() == SymbolType::ImportedFunction { - continue; - } else if symbol.sym_type() == SymbolType::LibraryFunction { + if let SymbolType::External + | SymbolType::Function + | SymbolType::ImportedFunction + | SymbolType::LibraryFunction = symbol.sym_type() + { continue; } } @@ -551,7 +549,7 @@ fn export_data_vars( dwarf.unit.get_mut(var_die_uid).set( gimli::DW_AT_name, AttributeValue::String( - format!("data_{:x}", data_variable.address) + format!("data_{:x}", data_variable.address()) .as_bytes() .to_vec(), ), @@ -559,15 +557,15 @@ fn export_data_vars( } let mut variable_location = Expression::new(); - variable_location.op_addr(Address::Constant(data_variable.address)); + variable_location.op_addr(Address::Constant(data_variable.address())); dwarf.unit.get_mut(var_die_uid).set( gimli::DW_AT_location, AttributeValue::Exprloc(variable_location), ); if let Some(target_die_uid) = export_type( - format!("{}", data_variable.t.contents), - data_variable.t.contents.as_ref(), + format!("{}", data_variable.t()), + data_variable.t(), bv, defined_types, dwarf, @@ -739,7 +737,7 @@ fn export_dwarf(bv: &BinaryView) { } else { BnString::new("Unknown") }; - let responses = present_form(&arch_name); + let responses = present_form(arch_name.as_str()); let encoding = gimli::Encoding { format: gimli::Format::Dwarf32, diff --git a/examples/dwarf/dwarf_import/Cargo.toml b/examples/dwarf/dwarf_import/Cargo.toml index c8cea44..838e435 100644 --- a/examples/dwarf/dwarf_import/Cargo.toml +++ b/examples/dwarf/dwarf_import/Cargo.toml @@ -10,5 +10,8 @@ crate-type = ["cdylib"] [dependencies] dwarfreader = { path = "../shared/" } binaryninja = { path = "../../../" } -gimli = "0.28" +gimli = "0.31" log = "0.4.20" +iset = "0.2.2" +cpp_demangle = "0.4.3" +regex = "1" diff --git a/examples/dwarf/dwarf_import/src/die_handlers.rs b/examples/dwarf/dwarf_import/src/die_handlers.rs index 125038d..51d52d5 100644 --- a/examples/dwarf/dwarf_import/src/die_handlers.rs +++ b/examples/dwarf/dwarf_import/src/die_handlers.rs @@ -13,7 +13,7 @@ // limitations under the License. use crate::dwarfdebuginfo::{DebugInfoBuilder, DebugInfoBuilderContext, TypeUID}; -use crate::helpers::*; +use crate::{helpers::*, ReaderType}; use crate::types::get_type; use binaryninja::{ @@ -21,9 +21,11 @@ use binaryninja::{ types::{EnumerationBuilder, FunctionParameter, ReferenceType, Type, TypeBuilder}, }; -use gimli::{constants, AttributeValue::Encoding, DebuggingInformationEntry, Reader, Unit}; +use gimli::Dwarf; +use gimli::{constants, AttributeValue::Encoding, DebuggingInformationEntry, Unit}; -pub(crate) fn handle_base_type>( +pub(crate) fn handle_base_type( + dwarf: &Dwarf, unit: &Unit, entry: &DebuggingInformationEntry, debug_info_builder_context: &DebugInfoBuilderContext, @@ -37,7 +39,7 @@ pub(crate) fn handle_base_type>( // *Some indication of signedness? // * = Optional - let name = debug_info_builder_context.get_name(unit, entry)?; + let name = debug_info_builder_context.get_name(dwarf, unit, entry)?; let size = get_size_as_usize(entry)?; match entry.attr_value(constants::DW_AT_encoding) { Ok(Some(Encoding(encoding))) => { @@ -69,7 +71,8 @@ pub(crate) fn handle_base_type>( } } -pub(crate) fn handle_enum>( +pub(crate) fn handle_enum( + dwarf: &Dwarf, unit: &Unit, entry: &DebuggingInformationEntry, debug_info_builder_context: &DebugInfoBuilderContext, @@ -107,17 +110,18 @@ pub(crate) fn handle_enum>( let mut children = tree.root().unwrap().children(); while let Ok(Some(child)) = children.next() { if child.entry().tag() == constants::DW_TAG_enumerator { - let name = debug_info_builder_context.get_name(unit, child.entry())?; - let value = get_attr_as_u64( - &child - .entry() - .attr(constants::DW_AT_const_value) - .unwrap() - .unwrap(), - ) - .unwrap(); - - enumeration_builder.insert(name, value); + let name = debug_info_builder_context.get_name(dwarf, unit, child.entry())?; + let attr = &child + .entry() + .attr(constants::DW_AT_const_value) + .unwrap() + .unwrap(); + if let Some(value) = get_attr_as_u64(attr) { + enumeration_builder.insert(name, value); + } else { + log::error!("Unhandled enum member value type - please report this"); + return None; + } } } @@ -131,7 +135,7 @@ pub(crate) fn handle_enum>( pub(crate) fn handle_typedef( debug_info_builder: &mut DebugInfoBuilder, entry_type: Option, - typedef_name: String, + typedef_name: &String, ) -> (Option>, bool) { // All base types have: // DW_AT_name @@ -140,12 +144,8 @@ pub(crate) fn handle_typedef( // This will fail in the case where we have a typedef to a type that doesn't exist (failed to parse, incomplete, etc) if let Some(entry_type_offset) = entry_type { - if let Some((name, t)) = debug_info_builder.get_type(entry_type_offset) { - if typedef_name == name { - return (Some(t), false); - } else if typedef_name != name { - return (Some(t), true); - } + if let Some(t) = debug_info_builder.get_type(entry_type_offset) { + return (Some(t.get_type()), typedef_name != t.get_name()); } } @@ -153,7 +153,7 @@ pub(crate) fn handle_typedef( (None, false) } -pub(crate) fn handle_pointer>( +pub(crate) fn handle_pointer( entry: &DebuggingInformationEntry, debug_info_builder_context: &DebugInfoBuilderContext, debug_info_builder: &mut DebugInfoBuilder, @@ -172,7 +172,7 @@ pub(crate) fn handle_pointer>( if let Some(pointer_size) = get_size_as_usize(entry) { if let Some(entry_type_offset) = entry_type { - let parent_type = debug_info_builder.get_type(entry_type_offset).unwrap().1; + let parent_type = debug_info_builder.get_type(entry_type_offset).unwrap().get_type(); Some(Type::pointer_of_width( parent_type.as_ref(), pointer_size, @@ -190,7 +190,7 @@ pub(crate) fn handle_pointer>( )) } } else if let Some(entry_type_offset) = entry_type { - let parent_type = debug_info_builder.get_type(entry_type_offset).unwrap().1; + let parent_type = debug_info_builder.get_type(entry_type_offset).unwrap().get_type(); Some(Type::pointer_of_width( parent_type.as_ref(), debug_info_builder_context.default_address_size(), @@ -209,7 +209,7 @@ pub(crate) fn handle_pointer>( } } -pub(crate) fn handle_array>( +pub(crate) fn handle_array( unit: &Unit, entry: &DebuggingInformationEntry, debug_info_builder: &mut DebugInfoBuilder, @@ -228,7 +228,7 @@ pub(crate) fn handle_array>( // For multidimensional arrays, DW_TAG_subrange_type or DW_TAG_enumeration_type if let Some(entry_type_offset) = entry_type { - let parent_type = debug_info_builder.get_type(entry_type_offset).unwrap().1; + let parent_type = debug_info_builder.get_type(entry_type_offset).unwrap().get_type(); let mut tree = unit.entries_tree(Some(entry.offset())).unwrap(); let mut children = tree.root().unwrap().children(); @@ -255,7 +255,8 @@ pub(crate) fn handle_array>( } } -pub(crate) fn handle_function>( +pub(crate) fn handle_function( + dwarf: &Dwarf, unit: &Unit, entry: &DebuggingInformationEntry, debug_info_builder_context: &DebugInfoBuilderContext, @@ -289,29 +290,25 @@ pub(crate) fn handle_function>( debug_info_builder .get_type(entry_type_offset) .expect("Subroutine return type was not processed") - .1 + .get_type() } None => Type::void(), }; // Alias function type in the case that it contains itself - if let Some(name) = debug_info_builder_context.get_name(unit, entry) { + if let Some(name) = debug_info_builder_context.get_name(dwarf, unit, entry) { debug_info_builder.add_type( - get_uid(unit, entry), - name.clone(), + get_uid(dwarf, unit, entry), + &name, Type::named_type_from_type( - name, - &Type::function::( - return_type.as_ref(), - &[], - false, - ), + &name, + &Type::function::<&binaryninja::types::Type>(return_type.as_ref(), &[], false), ), false, ); } - let mut parameters: Vec> = vec![]; + let mut parameters: Vec = vec![]; let mut variable_arguments = false; // Get all the children and populate @@ -322,15 +319,16 @@ pub(crate) fn handle_function>( if let (Some(child_uid), Some(name)) = { ( get_type( + dwarf, unit, child.entry(), debug_info_builder_context, debug_info_builder, ), - debug_info_builder_context.get_name(unit, child.entry()), + debug_info_builder_context.get_name(dwarf, unit, child.entry()), ) } { - let child_type = debug_info_builder.get_type(child_uid).unwrap().1; + let child_type = debug_info_builder.get_type(child_uid).unwrap().get_type(); parameters.push(FunctionParameter::new(child_type, name, None)); } } else if child.entry().tag() == constants::DW_TAG_unspecified_parameters { @@ -338,8 +336,8 @@ pub(crate) fn handle_function>( } } - if debug_info_builder_context.get_name(unit, entry).is_some() { - debug_info_builder.remove_type(get_uid(unit, entry)); + if debug_info_builder_context.get_name(dwarf, unit, entry).is_some() { + debug_info_builder.remove_type(get_uid(dwarf, unit, entry)); } Some(Type::function( @@ -362,7 +360,7 @@ pub(crate) fn handle_const( // ?DW_AT_type if let Some(entry_type_offset) = entry_type { - let parent_type = debug_info_builder.get_type(entry_type_offset).unwrap().1; + let parent_type = debug_info_builder.get_type(entry_type_offset).unwrap().get_type(); Some((*parent_type).to_builder().set_const(true).finalize()) } else { Some(TypeBuilder::void().set_const(true).finalize()) @@ -382,7 +380,7 @@ pub(crate) fn handle_volatile( // ?DW_AT_type if let Some(entry_type_offset) = entry_type { - let parent_type = debug_info_builder.get_type(entry_type_offset).unwrap().1; + let parent_type = debug_info_builder.get_type(entry_type_offset).unwrap().get_type(); Some((*parent_type).to_builder().set_volatile(true).finalize()) } else { Some(TypeBuilder::void().set_volatile(true).finalize()) diff --git a/examples/dwarf/dwarf_import/src/dwarfdebuginfo.rs b/examples/dwarf/dwarf_import/src/dwarfdebuginfo.rs index 4b0f935..3e5cc1b 100644 --- a/examples/dwarf/dwarf_import/src/dwarfdebuginfo.rs +++ b/examples/dwarf/dwarf_import/src/dwarfdebuginfo.rs @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -use crate::helpers::{get_uid, resolve_specification, DieReference}; +use crate::{helpers::{get_uid, resolve_specification, DieReference}, ReaderType}; use binaryninja::{ binaryview::{BinaryView, BinaryViewBase, BinaryViewExt}, @@ -21,13 +21,14 @@ use binaryninja::{ rc::*, symbol::SymbolType, templatesimplifier::simplify_str_to_fqn, - types::{Conf, FunctionParameter, Type}, + types::{Conf, FunctionParameter, NamedTypedVariable, Type, Variable, VariableSourceType}, }; -use gimli::{DebuggingInformationEntry, Dwarf, Reader, Unit}; +use gimli::{DebuggingInformationEntry, Dwarf, Unit}; -use log::{error, warn}; +use log::{debug, error, warn}; use std::{ + cmp::Ordering, collections::{hash_map::Values, HashMap}, hash::Hash, }; @@ -46,6 +47,8 @@ pub(crate) struct FunctionInfoBuilder { pub(crate) address: Option, pub(crate) parameters: Vec>, pub(crate) platform: Option>, + pub(crate) variable_arguments: bool, + pub(crate) stack_variables: Vec, } impl FunctionInfoBuilder { @@ -55,7 +58,7 @@ impl FunctionInfoBuilder { raw_name: Option, return_type: Option, address: Option, - parameters: Vec>, + parameters: &Vec>, ) { if full_name.is_some() { self.full_name = full_name; @@ -75,13 +78,13 @@ impl FunctionInfoBuilder { for (i, new_parameter) in parameters.into_iter().enumerate() { match self.parameters.get(i) { - Some(None) => self.parameters[i] = new_parameter, + Some(None) => self.parameters[i] = new_parameter.clone(), Some(Some(_)) => (), // Some(Some((name, _))) if name.as_bytes().is_empty() => { // self.parameters[i] = new_parameter // } // Some(Some((_, uid))) if *uid == 0 => self.parameters[i] = new_parameter, // TODO : This is a placebo....void types aren't actually UID 0 - _ => self.parameters.push(new_parameter), + _ => self.parameters.push(new_parameter.clone()), } } } @@ -97,16 +100,27 @@ pub(crate) struct DebugType { commit: bool, } -pub(crate) struct DebugInfoBuilderContext> { - dwarf: Dwarf, +impl DebugType { + pub fn get_name(&self) -> &String { + &self.name + } + + pub fn get_type(&self) -> Ref { + self.t.clone() + } +} + +pub(crate) struct DebugInfoBuilderContext { units: Vec>, + sup_units: Vec>, names: HashMap, default_address_size: usize, pub(crate) total_die_count: usize, } -impl> DebugInfoBuilderContext { - pub(crate) fn new(view: &BinaryView, dwarf: Dwarf) -> Option { +impl DebugInfoBuilderContext { + pub(crate) fn new(view: &BinaryView, dwarf: &Dwarf) -> Option { + let mut units = vec![]; let mut iter = dwarf.units(); while let Ok(Some(header)) = iter.next() { @@ -118,40 +132,56 @@ impl> DebugInfoBuilderContext { } } + let mut sup_units = vec![]; + if let Some(sup_dwarf) = dwarf.sup() { + let mut sup_iter = sup_dwarf.units(); + while let Ok(Some(header)) = sup_iter.next() { + if let Ok(unit) = sup_dwarf.unit(header) { + sup_units.push(unit); + } else { + error!("Unable to read supplementary DWARF information. File may be malformed or corrupted. Not applying debug info."); + return None; + } + } + } + Some(Self { - dwarf, units, + sup_units, names: HashMap::new(), default_address_size: view.address_size(), total_die_count: 0, }) } - pub(crate) fn dwarf(&self) -> &Dwarf { - &self.dwarf - } - pub(crate) fn units(&self) -> &[Unit] { &self.units } + pub(crate) fn sup_units(&self) -> &[Unit] { + &self.sup_units + } + pub(crate) fn default_address_size(&self) -> usize { self.default_address_size } pub(crate) fn set_name(&mut self, die_uid: TypeUID, name: String) { + // die_uids need to be unique here assert!(self.names.insert(die_uid, name).is_none()); } pub(crate) fn get_name( &self, + dwarf: &Dwarf, unit: &Unit, entry: &DebuggingInformationEntry, ) -> Option { - match resolve_specification(unit, entry, self) { - DieReference::UnitAndOffset((entry_unit, entry_offset)) => self + match resolve_specification(dwarf, unit, entry, self) { + DieReference::UnitAndOffset((dwarf, entry_unit, entry_offset)) => self .names .get(&get_uid( + dwarf, entry_unit, &entry_unit.entry(entry_offset).unwrap(), )) @@ -166,19 +196,29 @@ impl> DebugInfoBuilderContext { // info and types to one DIE's UID (T) before adding the completed info to BN's debug info pub(crate) struct DebugInfoBuilder { functions: Vec, + raw_function_name_indices: HashMap, + full_function_name_indices: HashMap, types: HashMap, data_variables: HashMap, TypeUID)>, + range_data_offsets: iset::IntervalMap } impl DebugInfoBuilder { pub(crate) fn new() -> Self { Self { functions: vec![], + raw_function_name_indices: HashMap::new(), + full_function_name_indices: HashMap::new(), types: HashMap::new(), data_variables: HashMap::new(), + range_data_offsets: iset::IntervalMap::new(), } } + pub(crate) fn set_range_data_offsets(&mut self, offsets: iset::IntervalMap) { + self.range_data_offsets = offsets + } + #[allow(clippy::too_many_arguments)] pub(crate) fn insert_function( &mut self, @@ -186,32 +226,87 @@ impl DebugInfoBuilder { raw_name: Option, return_type: Option, address: Option, - parameters: Vec>, - ) { + parameters: &Vec>, + variable_arguments: bool, + ) -> Option { + // Returns the index of the function // Raw names should be the primary key, but if they don't exist, use the full name // TODO : Consider further falling back on address/architecture - if let Some(function) = self - .functions - .iter_mut() - .find(|func| func.raw_name.is_some() && func.raw_name == raw_name) - { - function.update(full_name, raw_name, return_type, address, parameters); - } else if let Some(function) = self.functions.iter_mut().find(|func| { - (func.raw_name.is_none() || raw_name.is_none()) - && func.full_name.is_some() - && func.full_name == full_name - }) { - function.update(full_name, raw_name, return_type, address, parameters); - } else { - self.functions.push(FunctionInfoBuilder { - full_name, - raw_name, - return_type, - address, - parameters, - platform: None, - }); + + /* + If it has a raw_name and we know it, update it and return + Else if it has a full_name and we know it, update it and return + Else Add a new entry if we don't know the full_name or raw_name + */ + + if let Some(ident) = &raw_name { + // check if we already know about this raw name's index + // if we do, and the full name will change, remove the known full index if it exists + // update the function + // if the full name exists, update the stored index for the full name + if let Some(idx) = self.raw_function_name_indices.get(ident) { + let function = self.functions.get_mut(*idx).unwrap(); + + if function.full_name.is_some() && function.full_name != full_name { + self.full_function_name_indices.remove(function.full_name.as_ref().unwrap()); + } + + function.update(full_name, raw_name, return_type, address, parameters); + + if function.full_name.is_some() { + self.full_function_name_indices.insert(function.full_name.clone().unwrap(), *idx); + } + + return Some(*idx); + } } + else if let Some(ident) = &full_name { + // check if we already know about this full name's index + // if we do, and the raw name will change, remove the known raw index if it exists + // update the function + // if the raw name exists, update the stored index for the raw name + if let Some(idx) = self.full_function_name_indices.get(ident) { + let function = self.functions.get_mut(*idx).unwrap(); + + if function.raw_name.is_some() && function.raw_name != raw_name { + self.raw_function_name_indices.remove(function.raw_name.as_ref().unwrap()); + } + + function.update(full_name, raw_name, return_type, address, parameters); + + if function.raw_name.is_some() { + self.raw_function_name_indices.insert(function.raw_name.clone().unwrap(), *idx); + } + + return Some(*idx); + } + } + else { + debug!("Function entry in DWARF without full or raw name."); + return None; + } + + let function = FunctionInfoBuilder { + full_name, + raw_name, + return_type, + address, + parameters: parameters.clone(), + platform: None, + variable_arguments, + stack_variables: vec![], + }; + + if let Some(n) = &function.full_name { + self.full_function_name_indices.insert(n.clone(), self.functions.len()); + } + + if let Some(n) = &function.raw_name { + self.raw_function_name_indices.insert(n.clone(), self.functions.len()); + } + + self.functions.push(function); + Some(self.functions.len()-1) } pub(crate) fn functions(&self) -> &[FunctionInfoBuilder] { @@ -222,13 +317,7 @@ impl DebugInfoBuilder { self.types.values() } - pub(crate) fn add_type( - &mut self, - type_uid: TypeUID, - name: String, - t: Ref, - commit: bool, - ) { + pub(crate) fn add_type(&mut self, type_uid: TypeUID, name: &String, t: Ref, commit: bool) { if let Some(DebugType { name: existing_name, t: existing_type, @@ -242,7 +331,7 @@ impl DebugInfoBuilder { }, ) { if existing_type != t && commit { - error!("DWARF info contains duplicate type definition. Overwriting type `{}` (named `{:?}`) with `{}` (named `{:?}`)", + warn!("DWARF info contains duplicate type definition. Overwriting type `{}` (named `{:?}`) with `{}` (named `{:?}`)", existing_type, existing_name, t, @@ -256,15 +345,76 @@ impl DebugInfoBuilder { self.types.remove(&type_uid); } - // TODO : Non-copy? - pub(crate) fn get_type(&self, type_uid: TypeUID) -> Option<(String, Ref)> { - self.types - .get(&type_uid) - .map(|type_ref_ref| (type_ref_ref.name.clone(), type_ref_ref.t.clone())) + pub(crate) fn get_type(&self, type_uid: TypeUID) -> Option<&DebugType> { + self.types.get(&type_uid) } pub(crate) fn contains_type(&self, type_uid: TypeUID) -> bool { - self.types.get(&type_uid).is_some() + self.types.contains_key(&type_uid) + } + + + pub(crate) fn add_stack_variable( + &mut self, + fn_idx: Option, + offset: i64, + name: Option, + type_uid: Option, + ) { + let name = match name { + Some(x) => { + if x.len() == 1 && x.chars().next() == Some('\x00') { + // Anonymous variable, generate name + format!("debug_var_{}", offset) + } + else { + x + } + }, + None => { + // Anonymous variable, generate name + format!("debug_var_{}", offset) + } + }; + + let Some(function_index) = fn_idx else { + // If we somehow lost track of what subprogram we're in or we're not actually in a subprogram + error!("Trying to add a local variable outside of a subprogram. Please report this issue."); + return; + }; + + // Either get the known type or use a 0 confidence void type so we at least get the name applied + let t = match type_uid { + Some(uid) => Conf::new(self.get_type(uid).unwrap().get_type(), 128), + None => Conf::new(Type::void(), 0) + }; + let function = &mut self.functions[function_index]; + + // TODO: If we can't find a known offset can we try to guess somehow? + + let Some(func_addr) = function.address else { + // If we somehow are processing a function's variables before the function is created + error!("Trying to add a local variable without a known function start. Please report this issue."); + return; + }; + + let Some(offset_adjustment) = self.range_data_offsets.values_overlap(func_addr).next() else { + // Unknown why, but this is happening with MachO + external dSYM + debug!("Refusing to add a local variable ({}@{}) to function at {} without a known CIE offset.", name, offset, func_addr); + return; + }; + + let adjusted_offset = offset - offset_adjustment; + + if adjusted_offset > 0 { + // If we somehow end up with a positive sp offset + error!("Trying to add a local variable at positive storage offset {}. Please report this issue.", adjusted_offset); + return; + } + + let var = Variable::new(VariableSourceType::StackVariableSourceType, 0, adjusted_offset); + function.stack_variables.push(NamedTypedVariable::new(var, name, t, false)); + } pub(crate) fn add_data_variable( @@ -276,14 +426,14 @@ impl DebugInfoBuilder { if let Some((_existing_name, existing_type_uid)) = self.data_variables.insert(address, (name, type_uid)) { - let existing_type = self.get_type(existing_type_uid).unwrap().1; - let new_type = self.get_type(type_uid).unwrap().1; + let existing_type = self.get_type(existing_type_uid).unwrap().get_type(); + let new_type = self.get_type(type_uid).unwrap().get_type(); if existing_type_uid != type_uid || existing_type != new_type { - error!("DWARF info contains duplicate data variable definition. Overwriting data variable at 0x{:08x} (`{}`) with `{}`", + warn!("DWARF info contains duplicate data variable definition. Overwriting data variable at 0x{:08x} (`{}`) with `{}`", address, - self.get_type(existing_type_uid).unwrap().1, - self.get_type(type_uid).unwrap().1 + existing_type, + new_type ); } } @@ -303,7 +453,7 @@ impl DebugInfoBuilder { for (&address, (name, type_uid)) in &self.data_variables { assert!(debug_info.add_data_variable( address, - &self.get_type(*type_uid).unwrap().1, + &self.get_type(*type_uid).unwrap().t, name.clone(), &[] // TODO : Components )); @@ -312,17 +462,17 @@ impl DebugInfoBuilder { fn get_function_type(&self, function: &FunctionInfoBuilder) -> Ref { let return_type = match function.return_type { - Some(return_type_id) => Conf::new(self.get_type(return_type_id).unwrap().1.clone(), 0), + Some(return_type_id) => Conf::new(self.get_type(return_type_id).unwrap().get_type(), 128), _ => Conf::new(binaryninja::types::Type::void(), 0), }; - let parameters: Vec> = function + let parameters: Vec = function .parameters .iter() .filter_map(|parameter| match parameter { Some((name, 0)) => Some(FunctionParameter::new(Type::void(), name.clone(), None)), Some((name, uid)) => Some(FunctionParameter::new( - self.get_type(*uid).unwrap().1, + self.get_type(*uid).unwrap().get_type(), name.clone(), None, )), @@ -330,10 +480,7 @@ impl DebugInfoBuilder { }) .collect(); - // TODO : Handle - let variable_parameters = false; - - binaryninja::types::Type::function(&return_type, ¶meters, variable_parameters) + binaryninja::types::Type::function(&return_type, ¶meters, function.variable_arguments) } fn commit_functions(&self, debug_info: &mut DebugInfo) { @@ -348,12 +495,12 @@ impl DebugInfoBuilder { function.address, function.platform.clone(), vec![], // TODO : Components + function.stack_variables.clone(), // TODO: local non-stack variables )); } } pub(crate) fn post_process(&mut self, bv: &BinaryView, _debug_info: &mut DebugInfo) -> &Self { - // TODO : We don't need post-processing if we process correctly the first time.... // When originally resolving names, we need to check: // If there's already a name from binja that's "more correct" than what we found (has more namespaces) // If there's no name for the DIE, but there's a linkage name that's resolved in binja to a usable name @@ -379,19 +526,22 @@ impl DebugInfoBuilder { if simplify_str_to_fqn(func_full_name, true).len() < simplify_str_to_fqn(symbol_full_name.clone(), true).len() { - func.full_name = - Some(symbol_full_name.to_string()); + func.full_name = Some(symbol_full_name.to_string()); } } } } - if let Some(address) = func.address { - let existing_functions = bv.functions_at(address); - if existing_functions.len() > 1 { - warn!("Multiple existing functions at address {address:08x}. One or more functions at this address may have the wrong platform information. Please report this binary."); - } else if existing_functions.len() == 1 { - func.platform = Some(existing_functions.get(0).platform()); + if let Some(address) = func.address.as_mut() { + let diff = bv.start() - bv.original_image_base(); + *address += diff; // rebase the address + let existing_functions = bv.functions_at(*address); + match existing_functions.len().cmp(&1) { + Ordering::Greater => { + warn!("Multiple existing functions at address {address:08x}. One or more functions at this address may have the wrong platform information. Please report this binary."); + } + Ordering::Equal => func.platform = Some(existing_functions.get(0).platform()), + Ordering::Less => {} } } } diff --git a/examples/dwarf/dwarf_import/src/functions.rs b/examples/dwarf/dwarf_import/src/functions.rs index 1d8879a..9ca01a2 100644 --- a/examples/dwarf/dwarf_import/src/functions.rs +++ b/examples/dwarf/dwarf_import/src/functions.rs @@ -12,67 +12,119 @@ // See the License for the specific language governing permissions and // limitations under the License. +use std::sync::OnceLock; + use crate::dwarfdebuginfo::{DebugInfoBuilder, DebugInfoBuilderContext, TypeUID}; -use crate::helpers::*; +use crate::{helpers::*, ReaderType}; use crate::types::get_type; -use gimli::{constants, DebuggingInformationEntry, Reader, Unit}; +use binaryninja::templatesimplifier::simplify_str_to_str; +use cpp_demangle::DemangleOptions; +use gimli::{constants, DebuggingInformationEntry, Dwarf, Unit}; +use log::debug; +use regex::Regex; -fn get_parameters>( +fn get_parameters( + dwarf: &Dwarf, unit: &Unit, entry: &DebuggingInformationEntry, debug_info_builder_context: &DebugInfoBuilderContext, debug_info_builder: &mut DebugInfoBuilder, -) -> Vec> { +) -> (Vec>, bool) { if !entry.has_children() { - vec![] - } else { - // We make a new tree from the current entry to iterate over its children - let mut sub_die_tree = unit.entries_tree(Some(entry.offset())).unwrap(); - let root = sub_die_tree.root().unwrap(); + return (vec![], false); + } - let mut result = vec![]; - let mut children = root.children(); - while let Some(child) = children.next().unwrap() { - match child.entry().tag() { - constants::DW_TAG_formal_parameter => { - let name = debug_info_builder_context.get_name(unit, child.entry()); - let type_ = get_type( - unit, - child.entry(), - debug_info_builder_context, - debug_info_builder, - ); - if let Some(parameter_name) = name { - if let Some(parameter_type) = type_ { - result.push(Some((parameter_name, parameter_type))); - } else { - result.push(Some((parameter_name, 0))) - } + // We make a new tree from the current entry to iterate over its children + let mut sub_die_tree = unit.entries_tree(Some(entry.offset())).unwrap(); + let root = sub_die_tree.root().unwrap(); + + let mut variable_arguments = false; + let mut result = vec![]; + let mut children = root.children(); + while let Some(child) = children.next().unwrap() { + match child.entry().tag() { + constants::DW_TAG_formal_parameter => { + //TODO: if the param type is a typedef to an anonymous struct (typedef struct {...} foo) then this is reoslved to an anonymous struct instead of foo + // We should still recurse to make sure we load all types this param type depends on, but + let name = debug_info_builder_context.get_name(dwarf, unit, child.entry()); + + let type_ = get_type( + dwarf, + unit, + child.entry(), + debug_info_builder_context, + debug_info_builder, + ); + if let Some(parameter_name) = name { + if let Some(parameter_type) = type_ { + result.push(Some((parameter_name, parameter_type))); } else { - result.push(None) + result.push(Some((parameter_name, 0))) } + } else { + result.push(None) + } + } + constants::DW_TAG_unspecified_parameters => variable_arguments = true, + _ => (), + } + } + (result, variable_arguments) +} + +pub(crate) fn parse_function_entry( + dwarf: &Dwarf, + unit: &Unit, + entry: &DebuggingInformationEntry, + debug_info_builder_context: &DebugInfoBuilderContext, + debug_info_builder: &mut DebugInfoBuilder, +) -> Option { + // Collect function properties (if they exist in this DIE) + let raw_name = get_raw_name(dwarf, unit, entry); + let return_type = get_type(dwarf, unit, entry, debug_info_builder_context, debug_info_builder); + let address = get_start_address(dwarf, unit, entry); + let (parameters, variable_arguments) = get_parameters(dwarf, unit, entry, debug_info_builder_context, debug_info_builder); + + // If we have a raw name, it might be mangled, see if we can demangle it into full_name + // raw_name should contain a superset of the info we have in full_name + let mut full_name = None; + if let Some(possibly_mangled_name) = &raw_name { + if possibly_mangled_name.starts_with('_') { + static OPTIONS_MEM: OnceLock = OnceLock::new(); + let demangle_options = OPTIONS_MEM.get_or_init(|| { + DemangleOptions::new() + .no_return_type() + .hide_expression_literal_types() + .no_params() + }); + + static ABI_REGEX_MEM: OnceLock = OnceLock::new(); + let abi_regex = ABI_REGEX_MEM.get_or_init(|| { + Regex::new(r"\[abi:v\d+\]").unwrap() + }); + if let Ok(sym) = cpp_demangle::Symbol::new(possibly_mangled_name) { + if let Ok(demangled) = sym.demangle(demangle_options) { + let cleaned = abi_regex.replace_all(&demangled, ""); + let simplified = simplify_str_to_str(&cleaned); + full_name = Some(simplified.to_string()); } - constants::DW_TAG_unspecified_parameters => (), - _ => (), } } - result } -} -pub(crate) fn parse_function_entry>( - unit: &Unit, - entry: &DebuggingInformationEntry, - debug_info_builder_context: &DebugInfoBuilderContext, - debug_info_builder: &mut DebugInfoBuilder, -) { - // Collect function properties (if they exist in this DIE) - let full_name = debug_info_builder_context.get_name(unit, entry); - let raw_name = get_raw_name(unit, entry, debug_info_builder_context); - let return_type = get_type(unit, entry, debug_info_builder_context, debug_info_builder); - let address = get_start_address(unit, entry, debug_info_builder_context); - let parameters = get_parameters(unit, entry, debug_info_builder_context, debug_info_builder); + // If we didn't demangle the raw name, fetch the name given + if full_name.is_none() { + full_name = debug_info_builder_context.get_name(dwarf, unit, entry) + } - debug_info_builder.insert_function(full_name, raw_name, return_type, address, parameters); + if raw_name.is_none() && full_name.is_none() { + debug!( + "Function entry in DWARF without full or raw name: .debug_info offset {:?}", + entry.offset().to_debug_info_offset(&unit.header) + ); + return None; + } + + debug_info_builder.insert_function(full_name, raw_name, return_type, address, ¶meters, variable_arguments) } diff --git a/examples/dwarf/dwarf_import/src/helpers.rs b/examples/dwarf/dwarf_import/src/helpers.rs index ed40d44..691c575 100644 --- a/examples/dwarf/dwarf_import/src/helpers.rs +++ b/examples/dwarf/dwarf_import/src/helpers.rs @@ -12,124 +12,179 @@ // See the License for the specific language governing permissions and // limitations under the License. -use crate::DebugInfoBuilderContext; +use std::path::PathBuf; +use std::{ + collections::HashMap, + ops::Deref, + sync::mpsc, + str::FromStr +}; +use crate::{DebugInfoBuilderContext, ReaderType}; +use binaryninja::binaryview::BinaryViewBase; +use binaryninja::filemetadata::FileMetadata; +use binaryninja::Endianness; +use binaryninja::{binaryview::{BinaryView, BinaryViewExt}, downloadprovider::{DownloadInstanceInputOutputCallbacks, DownloadProvider}, rc::Ref, settings::Settings}; +use gimli::Dwarf; use gimli::{ constants, Attribute, AttributeValue, - AttributeValue::{DebugInfoRef, UnitRef}, - DebuggingInformationEntry, Operation, Reader, Unit, UnitOffset, UnitSectionOffset, + AttributeValue::{DebugInfoRef, DebugInfoRefSup, UnitRef}, + DebuggingInformationEntry, Operation, Unit, UnitOffset, UnitSectionOffset, }; use log::warn; -pub(crate) fn get_uid>( +pub(crate) fn get_uid( + dwarf: &Dwarf, unit: &Unit, entry: &DebuggingInformationEntry, ) -> usize { - match entry.offset().to_unit_section_offset(unit) { + // We set a large gap between supplementary and main entries + let adj = dwarf.sup().map_or(0, |_| 0x1000000000000000); + let entry_offset = match entry.offset().to_unit_section_offset(unit) { UnitSectionOffset::DebugInfoOffset(o) => o.0, UnitSectionOffset::DebugTypesOffset(o) => o.0, - } + }; + entry_offset + adj } //////////////////////////////////// // DIE attr convenience functions -pub(crate) enum DieReference<'a, R: Reader> { - UnitAndOffset((&'a Unit, UnitOffset)), +pub(crate) enum DieReference<'a, R: ReaderType> { + UnitAndOffset((&'a Dwarf, &'a Unit, UnitOffset)), Err, } -pub(crate) fn get_attr_die<'a, R: Reader>( +pub(crate) fn get_attr_die<'a, R: ReaderType>( + dwarf: &'a Dwarf, unit: &'a Unit, entry: &DebuggingInformationEntry, debug_info_builder_context: &'a DebugInfoBuilderContext, attr: constants::DwAt, ) -> Option> { match entry.attr_value(attr) { - Ok(Some(UnitRef(offset))) => Some(DieReference::UnitAndOffset((unit, offset))), + Ok(Some(UnitRef(offset))) => Some(DieReference::UnitAndOffset((dwarf, unit, offset))), Ok(Some(DebugInfoRef(offset))) => { - for source_unit in debug_info_builder_context.units() { - if let Some(new_offset) = offset.to_unit_offset(&source_unit.header) { - return Some(DieReference::UnitAndOffset((source_unit, new_offset))); + if dwarf.sup().is_some() { + for source_unit in debug_info_builder_context.units() { + if let Some(new_offset) = offset.to_unit_offset(&source_unit.header) { + return Some(DieReference::UnitAndOffset((dwarf, source_unit, new_offset))); + } } } - warn!("Failed to fetch DIE. Debug information may be incomplete."); + else { + // This could either have no supplementary file because it is one or because it just doesn't have one + // operate on supplementary file if dwarf is a supplementary file, else self + + // It's possible this is a reference in the supplementary file to itself + for source_unit in debug_info_builder_context.sup_units() { + if let Some(new_offset) = offset.to_unit_offset(&source_unit.header) { + return Some(DieReference::UnitAndOffset((dwarf, source_unit, new_offset))); + } + } + + // ... or it just doesn't have a supplementary file + for source_unit in debug_info_builder_context.units() { + if let Some(new_offset) = offset.to_unit_offset(&source_unit.header) { + return Some(DieReference::UnitAndOffset((dwarf, source_unit, new_offset))); + } + } + } + None - } - // Ok(Some(DebugInfoRefSup(offset))) TODO - dwarf 5 stuff + }, + Ok(Some(DebugInfoRefSup(offset))) => { + for source_unit in debug_info_builder_context.sup_units() { + if let Some(new_offset) = offset.to_unit_offset(&source_unit.header) { + return Some(DieReference::UnitAndOffset((dwarf.sup().unwrap(), source_unit, new_offset))); + } + } + warn!("Failed to fetch DIE. Supplementary debug information may be incomplete."); + None + }, _ => None, } } -pub(crate) fn resolve_specification<'a, R: Reader>( +pub(crate) fn resolve_specification<'a, R: ReaderType>( + dwarf: &'a Dwarf, unit: &'a Unit, entry: &DebuggingInformationEntry, debug_info_builder_context: &'a DebugInfoBuilderContext, ) -> DieReference<'a, R> { if let Some(die_reference) = get_attr_die( + dwarf, unit, entry, debug_info_builder_context, constants::DW_AT_specification, ) { match die_reference { - DieReference::UnitAndOffset((entry_unit, entry_offset)) => { + DieReference::UnitAndOffset((dwarf, entry_unit, entry_offset)) => { if let Ok(entry) = entry_unit.entry(entry_offset) { - resolve_specification(entry_unit, &entry, debug_info_builder_context) + resolve_specification(dwarf, entry_unit, &entry, debug_info_builder_context) } else { - warn!("Failed to fetch DIE. Debug information may be incomplete."); + warn!("Failed to fetch DIE for attr DW_AT_specification. Debug information may be incomplete."); DieReference::Err } } DieReference::Err => DieReference::Err, } } else if let Some(die_reference) = get_attr_die( + dwarf, unit, entry, debug_info_builder_context, constants::DW_AT_abstract_origin, ) { match die_reference { - DieReference::UnitAndOffset((entry_unit, entry_offset)) => { - if entry_offset == entry.offset() { + DieReference::UnitAndOffset((dwarf, entry_unit, entry_offset)) => { + if entry_offset == entry.offset() && unit.header.offset() == entry_unit.header.offset() { warn!("DWARF information is invalid (infinite abstract origin reference cycle). Debug information may be incomplete."); DieReference::Err } else if let Ok(new_entry) = entry_unit.entry(entry_offset) { - resolve_specification(entry_unit, &new_entry, debug_info_builder_context) + resolve_specification(dwarf, entry_unit, &new_entry, debug_info_builder_context) } else { - warn!("Failed to fetch DIE. Debug information may be incomplete."); + warn!("Failed to fetch DIE for attr DW_AT_abstract_origin. Debug information may be incomplete."); DieReference::Err } } DieReference::Err => DieReference::Err, } } else { - DieReference::UnitAndOffset((unit, entry.offset())) + DieReference::UnitAndOffset((dwarf, unit, entry.offset())) } } // Get name from DIE, or referenced dependencies -pub(crate) fn get_name>( +pub(crate) fn get_name( + dwarf: &Dwarf, unit: &Unit, entry: &DebuggingInformationEntry, debug_info_builder_context: &DebugInfoBuilderContext, ) -> Option { - match resolve_specification(unit, entry, debug_info_builder_context) { - DieReference::UnitAndOffset((entry_unit, entry_offset)) => { + match resolve_specification(dwarf, unit, entry, debug_info_builder_context) { + DieReference::UnitAndOffset((dwarf, entry_unit, entry_offset)) => { if let Ok(Some(attr_val)) = entry_unit .entry(entry_offset) .unwrap() .attr_value(constants::DW_AT_name) { - if let Ok(attr_string) = debug_info_builder_context - .dwarf() - .attr_string(entry_unit, attr_val) + if let Ok(attr_string) = dwarf.attr_string(entry_unit, attr_val.clone()) { if let Ok(attr_string) = attr_string.to_string() { return Some(attr_string.to_string()); } } + else if let Some(dwarf) = &dwarf.sup { + if let Ok(attr_string) = dwarf.attr_string(entry_unit, attr_val) + { + if let Ok(attr_string) = attr_string.to_string() { + return Some(attr_string.to_string()); + } + } + } } // if let Some(raw_name) = get_raw_name(unit, entry, debug_info_builder_context) { @@ -146,26 +201,32 @@ pub(crate) fn get_name>( } // Get raw name from DIE, or referenced dependencies -pub(crate) fn get_raw_name>( +pub(crate) fn get_raw_name( + dwarf: &Dwarf, unit: &Unit, entry: &DebuggingInformationEntry, - debug_info_builder_context: &DebugInfoBuilderContext, ) -> Option { if let Ok(Some(attr_val)) = entry.attr_value(constants::DW_AT_linkage_name) { - if let Ok(attr_string) = debug_info_builder_context - .dwarf() - .attr_string(unit, attr_val) + if let Ok(attr_string) = dwarf.attr_string(unit, attr_val.clone()) { if let Ok(attr_string) = attr_string.to_string() { return Some(attr_string.to_string()); } } + else if let Some(dwarf) = dwarf.sup() { + if let Ok(attr_string) = dwarf.attr_string(unit, attr_val) + { + if let Ok(attr_string) = attr_string.to_string() { + return Some(attr_string.to_string()); + } + } + } } None } // Get the size of an object as a usize -pub(crate) fn get_size_as_usize>( +pub(crate) fn get_size_as_usize( entry: &DebuggingInformationEntry, ) -> Option { if let Ok(Some(attr)) = entry.attr(constants::DW_AT_byte_size) { @@ -178,7 +239,7 @@ pub(crate) fn get_size_as_usize>( } // Get the size of an object as a u64 -pub(crate) fn get_size_as_u64>( +pub(crate) fn get_size_as_u64( entry: &DebuggingInformationEntry, ) -> Option { if let Ok(Some(attr)) = entry.attr(constants::DW_AT_byte_size) { @@ -191,7 +252,7 @@ pub(crate) fn get_size_as_u64>( } // Get the size of a subrange as a u64 -pub(crate) fn get_subrange_size>( +pub(crate) fn get_subrange_size( entry: &DebuggingInformationEntry, ) -> u64 { if let Ok(Some(attr)) = entry.attr(constants::DW_AT_upper_bound) { @@ -206,35 +267,27 @@ pub(crate) fn get_subrange_size>( } // Get the start address of a function -pub(crate) fn get_start_address>( +pub(crate) fn get_start_address( + dwarf: &Dwarf, unit: &Unit, entry: &DebuggingInformationEntry, - debug_info_builder_context: &DebugInfoBuilderContext, ) -> Option { if let Ok(Some(attr_val)) = entry.attr_value(constants::DW_AT_low_pc) { - match debug_info_builder_context - .dwarf() - .attr_address(unit, attr_val) + match dwarf.attr_address(unit, attr_val) { Ok(Some(val)) => Some(val), _ => None, } } else if let Ok(Some(attr_val)) = entry.attr_value(constants::DW_AT_entry_pc) { - match debug_info_builder_context - .dwarf() - .attr_address(unit, attr_val) + match dwarf.attr_address(unit, attr_val) { Ok(Some(val)) => Some(val), _ => None, } } else if let Ok(Some(attr_value)) = entry.attr_value(constants::DW_AT_ranges) { - if let Ok(Some(ranges_offset)) = debug_info_builder_context - .dwarf() - .attr_ranges_offset(unit, attr_value) + if let Ok(Some(ranges_offset)) = dwarf.attr_ranges_offset(unit, attr_value) { - if let Ok(mut ranges) = debug_info_builder_context - .dwarf() - .ranges(unit, ranges_offset) + if let Ok(mut ranges) = dwarf.ranges(unit, ranges_offset) { if let Ok(Some(range)) = ranges.next() { return Some(range.begin); @@ -248,20 +301,26 @@ pub(crate) fn get_start_address>( } // Get an attribute value as a u64 if it can be coerced -pub(crate) fn get_attr_as_u64>(attr: &Attribute) -> Option { - if let Some(value) = attr.u8_value() { - Some(value.into()) - } else if let Some(value) = attr.u16_value() { - Some(value.into()) - } else if let Some(value) = attr.udata_value() { +pub(crate) fn get_attr_as_u64(attr: &Attribute) -> Option { + if let Some(value) = attr.udata_value() { Some(value) + } else if let Some(value) = attr.sdata_value() { + Some(value as u64) + } else if let AttributeValue::Block(mut data) = attr.value() { + match data.len() { + 1 => data.read_u8().map(u64::from).ok(), + 2 => data.read_u16().map(u64::from).ok(), + 4 => data.read_u32().map(u64::from).ok(), + 8 => data.read_u64().ok(), + _ => None + } } else { - attr.sdata_value().map(|value| value as u64) + None } } // Get an attribute value as a usize if it can be coerced -pub(crate) fn get_attr_as_usize>(attr: Attribute) -> Option { +pub(crate) fn get_attr_as_usize(attr: Attribute) -> Option { if let Some(value) = attr.u8_value() { Some(value.into()) } else if let Some(value) = attr.u16_value() { @@ -275,7 +334,7 @@ pub(crate) fn get_attr_as_usize>(attr: Attribute) - // Get an attribute value as a usize if it can be coerced // Parses DW_OP_address, DW_OP_const -pub(crate) fn get_expr_value>( +pub(crate) fn get_expr_value( unit: &Unit, attr: Attribute, ) -> Option { @@ -285,9 +344,252 @@ pub(crate) fn get_expr_value>( Ok(Operation::UnsignedConstant { value }) => Some(value), Ok(Operation::Address { address: 0 }) => None, Ok(Operation::Address { address }) => Some(address), - _ => None, + _ => None } } else { None } } + + +pub(crate) fn get_build_id(view: &BinaryView) -> Result { + let mut build_id: Option = None; + + if let Ok(raw_view) = view.raw_view() { + if let Ok(build_id_section) = raw_view.section_by_name(".note.gnu.build-id") { + // Name size - 4 bytes + // Desc size - 4 bytes + // Type - 4 bytes + // Name - n bytes + // Desc - n bytes + let build_id_bytes = raw_view.read_vec(build_id_section.start(), build_id_section.len()); + if build_id_bytes.len() < 12 { + return Err("Build id section must be at least 12 bytes".to_string()); + } + + let name_len: u32; + let desc_len: u32; + let note_type: u32; + match raw_view.default_endianness() { + Endianness::LittleEndian => { + name_len = u32::from_le_bytes(build_id_bytes[0..4].try_into().unwrap()); + desc_len = u32::from_le_bytes(build_id_bytes[4..8].try_into().unwrap()); + note_type = u32::from_le_bytes(build_id_bytes[8..12].try_into().unwrap()); + }, + Endianness::BigEndian => { + name_len = u32::from_be_bytes(build_id_bytes[0..4].try_into().unwrap()); + desc_len = u32::from_be_bytes(build_id_bytes[4..8].try_into().unwrap()); + note_type = u32::from_be_bytes(build_id_bytes[8..12].try_into().unwrap()); + } + }; + + if note_type != 3 { + return Err(format!("Build id section has wrong type: {}", note_type)); + } + + let expected_len = (12 + name_len + desc_len) as usize; + + if build_id_bytes.len() < expected_len { + return Err(format!("Build id section not expected length: expected {}, got {}", expected_len, build_id_bytes.len())); + } + + let desc: &[u8] = &build_id_bytes[(12+name_len as usize)..expected_len]; + build_id = Some(desc.iter().map(|b| format!("{:02x}", b)).collect()); + } + } + + if let Some(x) = build_id { + Ok(x) + } + else { + Err("Failed to get build id".to_string()) + } +} + + +pub(crate) fn download_debug_info(build_id: &String, view: &BinaryView) -> Result, String> { + let settings = Settings::new(""); + + let debug_server_urls = settings.get_string_list("network.debuginfodServers", Some(view), None); + + for debug_server_url in debug_server_urls.iter() { + let artifact_url = format!("{}/buildid/{}/debuginfo", debug_server_url, build_id); + + // Download from remote + let (tx, rx) = mpsc::channel(); + let write = move |data: &[u8]| -> usize { + if let Ok(_) = tx.send(Vec::from(data)) { + data.len() + } else { + 0 + } + }; + + let dp = DownloadProvider::try_default().map_err(|_| "No default download provider")?; + let mut inst = dp + .create_instance() + .map_err(|_| "Couldn't create download instance")?; + let result = inst + .perform_custom_request( + "GET", + artifact_url, + HashMap::::new(), + DownloadInstanceInputOutputCallbacks { + read: None, + write: Some(Box::new(write)), + progress: None, + }, + ) + .map_err(|e| e.to_string())?; + if result.status_code != 200 { + continue; + } + + let mut expected_length = None; + for (k, v) in result.headers.iter() { + if k.to_lowercase() == "content-length" { + expected_length = Some(usize::from_str(v).map_err(|e| e.to_string())?); + } + } + + let mut data = vec![]; + while let Ok(packet) = rx.try_recv() { + data.extend(packet.into_iter()); + } + + if let Some(length) = expected_length { + if data.len() != length { + return Err(format!( + "Bad length: expected {} got {}", + length, + data.len() + )); + } + } + + let options = "{\"analysis.debugInfo.internal\": false}"; + let bv = BinaryView::from_data(FileMetadata::new().deref(), &data) + .map_err(|_| "Unable to create binary view from downloaded data".to_string())?; + + return binaryninja::load_view(bv.deref(), false, Some(options)) + .ok_or("Unable to load binary view from downloaded data".to_string()); + } + return Err("Could not find a server with debug info for this file".to_string()); +} + + +pub(crate) fn find_local_debug_file_for_build_id(build_id: &String, view: &BinaryView) -> Option { + let settings = Settings::new(""); + let debug_dirs_enabled = settings.get_bool("analysis.debugInfo.enableDebugDirectories", Some(view), None); + + if !debug_dirs_enabled { + return None; + } + + let debug_info_paths = settings.get_string_list("analysis.debugInfo.debugDirectories", Some(view), None); + + if debug_info_paths.is_empty() { + return None + } + + for debug_info_path in debug_info_paths.into_iter() { + if let Ok(path) = PathBuf::from_str(&debug_info_path.to_string()) + { + let elf_path = path + .join(&build_id[..2]) + .join(&build_id[2..]) + .join("elf"); + + let debug_ext_path = path + .join(&build_id[..2]) + .join(format!("{}.debug", &build_id[2..])); + + let final_path = if debug_ext_path.exists() { + debug_ext_path + } + else if elf_path.exists() { + elf_path + } + else { + // No paths exist in this dir, try the next one + continue; + }; + return final_path + .to_str() + .and_then(|x| Some(x.to_string())); + } + } + None +} + + +pub(crate) fn load_debug_info_for_build_id(build_id: &String, view: &BinaryView) -> (Option>, bool) { + if let Some(debug_file_path) = find_local_debug_file_for_build_id(build_id, view) { + return + ( + binaryninja::load_with_options( + debug_file_path, + false, + Some("{\"analysis.debugInfo.internal\": false}") + ), + false + ); + } + else if Settings::new("").get_bool("network.enableDebuginfod", Some(view), None) { + return ( + download_debug_info(build_id, view).ok(), + true + ); + } + (None, false) +} + + +pub(crate) fn find_sibling_debug_file(view: &BinaryView) -> Option { + let settings = Settings::new(""); + let load_sibling_debug = settings.get_bool("analysis.debugInfo.loadSiblingDebugFiles", Some(view), None); + + if !load_sibling_debug { + return None; + } + + let filename = view.file().filename().to_string(); + + let debug_file = PathBuf::from(format!("{}.debug", filename)); + let dsym_folder = PathBuf::from(format!("{}.dSYM/", filename)); + if debug_file.exists() && debug_file.is_file() { + return Some(debug_file.to_string_lossy().to_string()); + } + + if dsym_folder.exists() && dsym_folder.is_dir() { + let dsym_file = dsym_folder + .join("Contents/Resources/DWARF/") + .join(filename); // TODO: should this just pull any file out? Can there be multiple files? + if dsym_file.exists() { + return Some(dsym_file.to_string_lossy().to_string()); + } + } + + None +} + + +pub(crate) fn load_sibling_debug_file(view: &BinaryView) -> (Option>, bool) { + let Some(debug_file) = find_sibling_debug_file(view) else { + return (None, false); + }; + + let load_settings = match view.default_platform() { + Some(plat) => format!("{{\"analysis.debugInfo.internal\": false, \"loader.platform\": \"{}\"}}", plat.name()), + None => "{\"analysis.debugInfo.internal\": false}".to_string() + }; + + ( + binaryninja::load_with_options( + debug_file, + false, + Some(load_settings) + ), + false + ) +} diff --git a/examples/dwarf/dwarf_import/src/lib.rs b/examples/dwarf/dwarf_import/src/lib.rs index 0680942..9815339 100644 --- a/examples/dwarf/dwarf_import/src/lib.rs +++ b/examples/dwarf/dwarf_import/src/lib.rs @@ -18,32 +18,60 @@ mod functions; mod helpers; mod types; +use std::collections::HashMap; + use crate::dwarfdebuginfo::{DebugInfoBuilder, DebugInfoBuilderContext}; use crate::functions::parse_function_entry; use crate::helpers::{get_attr_die, get_name, get_uid, DieReference}; -use crate::types::parse_data_variable; +use crate::types::parse_variable; +use binaryninja::binaryview::BinaryViewBase; use binaryninja::{ binaryview::{BinaryView, BinaryViewExt}, debuginfo::{CustomDebugInfoParser, DebugInfo, DebugInfoParser}, logger, + settings::Settings, templatesimplifier::simplify_str_to_str, }; use dwarfreader::{ create_section_reader, get_endian, is_dwo_dwarf, is_non_dwo_dwarf, is_raw_dwo_dwarf, }; -use gimli::{constants, DebuggingInformationEntry, Dwarf, DwarfFileType, Reader, SectionId, Unit}; +use gimli::{constants, DebuggingInformationEntry, Dwarf, DwarfFileType, Reader, Section, SectionId, Unit, UnwindSection}; +use helpers::{get_build_id, load_debug_info_for_build_id}; use log::{error, warn, LevelFilter}; -fn recover_names>( + +trait ReaderType: Reader {} +impl> ReaderType for T {} + + +fn recover_names( + dwarf: &Dwarf, debug_info_builder_context: &mut DebugInfoBuilderContext, progress: &dyn Fn(usize, usize) -> Result<(), ()>, ) -> bool { - let mut iter = debug_info_builder_context.dwarf().units(); + + let mut res = true; + if let Some(sup_dwarf) = dwarf.sup() { + res = recover_names_internal(sup_dwarf, debug_info_builder_context, progress); + } + + if res { + res = recover_names_internal(dwarf, debug_info_builder_context, progress); + } + res +} + +fn recover_names_internal( + dwarf: &Dwarf, + debug_info_builder_context: &mut DebugInfoBuilderContext, + progress: &dyn Fn(usize, usize) -> Result<(), ()>, +) -> bool { + let mut iter = dwarf.units(); while let Ok(Some(header)) = iter.next() { - let unit = debug_info_builder_context.dwarf().unit(header).unwrap(); + let unit = dwarf.unit(header).unwrap(); let mut namespace_qualifiers: Vec<(isize, String)> = vec![]; let mut entries = unit.entries(); let mut depth = 0; @@ -72,7 +100,8 @@ fn recover_names>( match entry.tag() { constants::DW_TAG_namespace => { - fn resolve_namespace_name>( + fn resolve_namespace_name( + dwarf: &Dwarf, unit: &Unit, entry: &DebuggingInformationEntry, debug_info_builder_context: &DebugInfoBuilderContext, @@ -80,18 +109,20 @@ fn recover_names>( depth: isize, ) { if let Some(namespace_qualifier) = - get_name(unit, entry, debug_info_builder_context) + get_name(dwarf, unit, entry, debug_info_builder_context) { namespace_qualifiers.push((depth, namespace_qualifier)); } else if let Some(die_reference) = get_attr_die( + dwarf, unit, entry, debug_info_builder_context, constants::DW_AT_extension, ) { match die_reference { - DieReference::UnitAndOffset((entry_unit, entry_offset)) => { + DieReference::UnitAndOffset((dwarf, entry_unit, entry_offset)) => { resolve_namespace_name( + dwarf, entry_unit, &entry_unit.entry(entry_offset).unwrap(), debug_info_builder_context, @@ -101,17 +132,17 @@ fn recover_names>( } DieReference::Err => { warn!( - "Failed to fetch DIE. Debug information may be incomplete." + "Failed to fetch DIE when resolving namespace. Debug information may be incomplete." ); } } } else { - namespace_qualifiers - .push((depth, "anonymous_namespace".to_string())); + namespace_qualifiers.push((depth, "anonymous_namespace".to_string())); } } resolve_namespace_name( + dwarf, &unit, entry, debug_info_builder_context, @@ -122,54 +153,54 @@ fn recover_names>( constants::DW_TAG_class_type | constants::DW_TAG_structure_type | constants::DW_TAG_union_type => { - if let Some(name) = get_name(&unit, entry, debug_info_builder_context) { + if let Some(name) = get_name(dwarf, &unit, entry, debug_info_builder_context) { namespace_qualifiers.push((depth, name)) } else { namespace_qualifiers.push(( depth, match entry.tag() { constants::DW_TAG_class_type => "anonymous_class".to_string(), - constants::DW_TAG_structure_type => "anonymous_structure".to_string(), + constants::DW_TAG_structure_type => { + "anonymous_structure".to_string() + } constants::DW_TAG_union_type => "anonymous_union".to_string(), _ => unreachable!(), - } + }, )) } debug_info_builder_context.set_name( - get_uid(&unit, entry), - simplify_str_to_str( - namespace_qualifiers - .iter() - .map(|(_, namespace)| namespace.to_owned()) - .collect::>() - .join("::"), - ) - .to_string(), + get_uid(dwarf, &unit, entry), + simplify_str_to_str( + namespace_qualifiers + .iter() + .map(|(_, namespace)| namespace.to_owned()) + .collect::>() + .join("::"), + ) + .to_string(), ); } constants::DW_TAG_typedef | constants::DW_TAG_subprogram | constants::DW_TAG_enumeration_type => { - if let Some(name) = get_name(&unit, entry, debug_info_builder_context) { + if let Some(name) = get_name(dwarf, &unit, entry, debug_info_builder_context) { debug_info_builder_context.set_name( - get_uid(&unit, entry), - simplify_str_to_str( - namespace_qualifiers - .iter() - .chain(vec![&(-1, name)].into_iter()) - .map(|(_, namespace)| { - namespace.to_owned() - }) - .collect::>() - .join("::"), - ) - .to_string(), + get_uid(dwarf, &unit, entry), + simplify_str_to_str( + namespace_qualifiers + .iter() + .chain(vec![&(-1, name)].into_iter()) + .map(|(_, namespace)| namespace.to_owned()) + .collect::>() + .join("::"), + ) + .to_string(), ); } } _ => { - if let Some(name) = get_name(&unit, entry, debug_info_builder_context) { - debug_info_builder_context.set_name(get_uid(&unit, entry), name); + if let Some(name) = get_name(dwarf, &unit, entry, debug_info_builder_context) { + debug_info_builder_context.set_name(get_uid(dwarf, &unit, entry), name); } } } @@ -179,7 +210,8 @@ fn recover_names>( true } -fn parse_unit>( +fn parse_unit( + dwarf: &Dwarf, unit: &Unit, debug_info_builder_context: &DebugInfoBuilderContext, debug_info_builder: &mut DebugInfoBuilder, @@ -188,9 +220,12 @@ fn parse_unit>( ) { let mut entries = unit.entries(); + let mut current_depth: isize = 0; + let mut functions_by_depth: Vec<(Option, isize)> = vec![]; + // Really all we care about as we iterate the entries in a given unit is how they modify state (our perception of the file) // There's a lot of junk we don't care about in DWARF info, so we choose a couple DIEs and mutate state (add functions (which adds the types it uses) and keep track of what namespace we're in) - while let Ok(Some((_, entry))) = entries.next_dfs() { + while let Ok(Some((depth_delta, entry))) = entries.next_dfs() { *current_die_number += 1; if (*progress)( *current_die_number, @@ -201,34 +236,147 @@ fn parse_unit>( return; // Parsing canceled } + current_depth = current_depth.saturating_add(depth_delta); + + loop { + if let Some((_fn_idx, depth)) = functions_by_depth.last() { + if current_depth <= *depth { + functions_by_depth.pop(); + } + else { + break + } + } + else { + break; + } + } + match entry.tag() { constants::DW_TAG_subprogram => { - parse_function_entry(unit, entry, debug_info_builder_context, debug_info_builder) - } + let fn_idx = parse_function_entry(dwarf, unit, entry, debug_info_builder_context, debug_info_builder); + functions_by_depth.push((fn_idx, current_depth)); + }, constants::DW_TAG_variable => { - parse_data_variable(unit, entry, debug_info_builder_context, debug_info_builder) - } + let current_fn_idx = functions_by_depth.last().and_then(|x| x.0); + parse_variable(dwarf, unit, entry, debug_info_builder_context, debug_info_builder, current_fn_idx) + }, + constants::DW_TAG_class_type | + constants::DW_TAG_enumeration_type | + constants::DW_TAG_structure_type | + constants::DW_TAG_union_type | + constants::DW_TAG_typedef => { + // Ensure types are loaded even if they're unused + types::get_type(dwarf, unit, entry, debug_info_builder_context, debug_info_builder); + }, _ => (), } } } -fn parse_dwarf( +fn parse_eh_frame( view: &BinaryView, + mut eh_frame: gimli::EhFrame, +) -> gimli::Result> { + eh_frame.set_address_size(view.address_size() as u8); + + let mut bases = gimli::BaseAddresses::default(); + if let Ok(section) = view.section_by_name(".eh_frame_hdr").or(view.section_by_name("__eh_frame_hdr")) { + bases = bases.set_eh_frame_hdr(section.start()); + } + if let Ok(section) = view.section_by_name(".eh_frame").or(view.section_by_name("__eh_frame")) { + bases = bases.set_eh_frame(section.start()); + } + if let Ok(section) = view.section_by_name(".text").or(view.section_by_name("__text")) { + bases = bases.set_text(section.start()); + } + if let Ok(section) = view.section_by_name(".got").or(view.section_by_name("__got")) { + bases = bases.set_got(section.start()); + } + + let mut cies = HashMap::new(); + let mut cie_data_offsets = iset::IntervalMap::new(); + + let mut entries = eh_frame.entries(&bases); + loop { + match entries.next()? { + None => return Ok(cie_data_offsets), + Some(gimli::CieOrFde::Cie(_cie)) => { + // TODO: do we want to do anything with standalone CIEs? + } + Some(gimli::CieOrFde::Fde(partial)) => { + let fde = match partial.parse(|_, bases, o| { + cies.entry(o) + .or_insert_with(|| eh_frame.cie_from_offset(bases, o)) + .clone() + }) { + Ok(fde) => fde, + Err(e) => { + error!("Failed to parse FDE: {}", e); + continue; + } + }; + + if fde.len() == 0 { + // This FDE is a terminator + return Ok(cie_data_offsets); + } + + // Store CIE offset for FDE range + cie_data_offsets.insert( + fde.initial_address()..fde.initial_address()+fde.len(), + fde.cie().data_alignment_factor() + ); + } + } + } +} + +fn get_supplementary_build_id(bv: &BinaryView) -> Option { + let raw_view = bv.raw_view().ok()?; + if let Ok(section) = raw_view.section_by_name(".gnu_debugaltlink") { + let start = section.start(); + let len = section.len(); + + if len < 20 { + // Not large enough to hold a build id + return None; + } + + raw_view + .read_vec(start, len) + .splitn(2, |x| *x == 0) + .last() + .map(|a| { + a.iter().map(|b| format!("{:02x}", b)).collect() + }) + } + else { + None + } +} + +fn parse_dwarf( + bv: &BinaryView, + debug_bv: &BinaryView, + supplementary_bv: Option<&BinaryView>, progress: Box Result<(), ()>>, -) -> DebugInfoBuilder { +) -> Result { + // TODO: warn if no supplementary file and .gnu_debugaltlink section present + // Determine if this is a DWO // TODO : Make this more robust...some DWOs follow non-DWO conventions - let dwo_file = is_dwo_dwarf(view) || is_raw_dwo_dwarf(view); // Figure out if it's the given view or the raw view that has the dwarf info in it - let raw_view = &view.raw_view().unwrap(); - let view = if is_dwo_dwarf(view) || is_non_dwo_dwarf(view) { - view + let raw_view = &debug_bv.raw_view().unwrap(); + let view = if is_dwo_dwarf(debug_bv) || is_non_dwo_dwarf(debug_bv) { + debug_bv } else { raw_view }; + let dwo_file = is_dwo_dwarf(view) || is_raw_dwo_dwarf(view); + // gimli setup let endian = get_endian(view); let mut section_reader = @@ -237,24 +385,60 @@ fn parse_dwarf( if dwo_file { dwarf.file_type = DwarfFileType::Dwo; } + else { + dwarf.file_type = DwarfFileType::Main; + } + + if let Some(sup_bv) = supplementary_bv { + let sup_endian = get_endian(sup_bv); + let sup_dwo_file = is_dwo_dwarf(sup_bv) || is_raw_dwo_dwarf(sup_bv); + let sup_section_reader = + |section_id: SectionId| -> _ { create_section_reader(section_id, sup_bv, sup_endian, sup_dwo_file) }; + if let Err(e) = dwarf.load_sup(sup_section_reader) { + error!("Failed to load supplementary file: {}", e); + } + } + + let eh_frame_endian = get_endian(bv); + let mut eh_frame_section_reader = + |section_id: SectionId| -> _ { create_section_reader(section_id, bv, eh_frame_endian, dwo_file) }; + let eh_frame = gimli::EhFrame::load(&mut eh_frame_section_reader).unwrap(); + + let range_data_offsets = parse_eh_frame(bv, eh_frame) + .map_err(|e| error!("Error parsing .eh_frame: {}", e))?; // Create debug info builder and recover name mapping first // Since DWARF is stored as a tree with arbitrary implicit edges among leaves, // it is not possible to correctly track namespaces while you're parsing "in order" without backtracking, // so we just do it up front let mut debug_info_builder = DebugInfoBuilder::new(); - if let Some(mut debug_info_builder_context) = DebugInfoBuilderContext::new(view, dwarf) { - if !recover_names(&mut debug_info_builder_context, &progress) + debug_info_builder.set_range_data_offsets(range_data_offsets); + + if let Some(mut debug_info_builder_context) = DebugInfoBuilderContext::new(view, &dwarf) { + if !recover_names(&dwarf, &mut debug_info_builder_context, &progress) || debug_info_builder_context.total_die_count == 0 { - return debug_info_builder; + return Ok(debug_info_builder); } // Parse all the compilation units let mut current_die_number = 0; + + for unit in debug_info_builder_context.sup_units() { + parse_unit( + dwarf.sup().unwrap(), + &unit, + &debug_info_builder_context, + &mut debug_info_builder, + &progress, + &mut current_die_number, + ); + } + for unit in debug_info_builder_context.units() { parse_unit( - unit, + &dwarf, + &unit, &debug_info_builder_context, &mut debug_info_builder, &progress, @@ -262,14 +446,28 @@ fn parse_dwarf( ); } } - debug_info_builder + + Ok(debug_info_builder) } struct DWARFParser; impl CustomDebugInfoParser for DWARFParser { fn is_valid(&self, view: &BinaryView) -> bool { - dwarfreader::is_valid(view) + if dwarfreader::is_valid(view) || dwarfreader::can_use_debuginfod(view) { + return true; + } + if dwarfreader::has_build_id_section(view) { + if let Ok(build_id) = get_build_id(view) { + if helpers::find_local_debug_file_for_build_id(&build_id, view).is_some() { + return true; + } + } + } + if helpers::find_sibling_debug_file(view).is_some() { + return true; + } + false } fn parse_info( @@ -279,10 +477,51 @@ impl CustomDebugInfoParser for DWARFParser { debug_file: &BinaryView, progress: Box Result<(), ()>>, ) -> bool { - parse_dwarf(debug_file, progress) - .post_process(bv, debug_info) - .commit_info(debug_info); - true + let (external_file, close_external) = if !dwarfreader::is_valid(bv) { + if let (Some(debug_view), x) = helpers::load_sibling_debug_file(bv) { + (Some(debug_view), x) + } + else if let Ok(build_id) = get_build_id(bv) { + helpers::load_debug_info_for_build_id(&build_id, bv) + } + else { + (None, false) + } + } + else { + (None, false) + }; + + let sup_bv = get_supplementary_build_id( + external_file + .as_deref() + .unwrap_or(debug_file) + ) + .and_then(|build_id| { + load_debug_info_for_build_id(&build_id, bv) + .0 + .map(|x| x.raw_view().unwrap()) + }); + + let result = match parse_dwarf( + bv, + external_file.as_deref().unwrap_or(debug_file), + sup_bv.as_deref(), + progress + ) + { + Ok(mut builder) => { + builder.post_process(bv, debug_info).commit_info(debug_info); + true + } + Err(_) => false, + }; + + if let (Some(ext), true) = (external_file, close_external) { + ext.file().close(); + } + + result } } @@ -290,6 +529,65 @@ impl CustomDebugInfoParser for DWARFParser { pub extern "C" fn CorePluginInit() -> bool { logger::init(LevelFilter::Debug).unwrap(); + let settings = Settings::new(""); + + settings.register_setting_json( + "network.enableDebuginfod", + r#"{ + "title" : "Enable Debuginfod Support", + "type" : "boolean", + "default" : false, + "description" : "Enable using Debuginfod servers to fetch DWARF debug info for files with a .note.gnu.build-id section.", + "ignore" : [] + }"#, + ); + + settings.register_setting_json( + "network.debuginfodServers", + r#"{ + "title" : "Debuginfod Server URLs", + "type" : "array", + "elementType" : "string", + "default" : [], + "description" : "Servers to use for fetching DWARF debug info for files with a .note.gnu.build-id section.", + "ignore" : [] + }"#, + ); + + settings.register_setting_json( + "analysis.debugInfo.enableDebugDirectories", + r#"{ + "title" : "Enable Debug File Directories", + "type" : "boolean", + "default" : true, + "description" : "Enable searching local debug directories for DWARF debug info.", + "ignore" : [] + }"#, + ); + + settings.register_setting_json( + "analysis.debugInfo.debugDirectories", + r#"{ + "title" : "Debug File Directories", + "type" : "array", + "elementType" : "string", + "default" : [], + "description" : "Paths to folder containing DWARF debug info stored by build id.", + "ignore" : [] + }"#, + ); + + settings.register_setting_json( + "analysis.debugInfo.loadSiblingDebugFiles", + r#"{ + "title" : "Enable Loading of Sibling Debug Files", + "type" : "boolean", + "default" : true, + "description" : "Enable automatic loading of X.debug and X.dSYM files next to a file named X.", + "ignore" : [] + }"#, + ); + DebugInfoParser::register("DWARF", DWARFParser {}); true } diff --git a/examples/dwarf/dwarf_import/src/types.rs b/examples/dwarf/dwarf_import/src/types.rs index 38bd36e..35794e3 100644 --- a/examples/dwarf/dwarf_import/src/types.rs +++ b/examples/dwarf/dwarf_import/src/types.rs @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -use crate::die_handlers::*; +use crate::{die_handlers::*, ReaderType}; use crate::dwarfdebuginfo::{DebugInfoBuilder, DebugInfoBuilderContext, TypeUID}; use crate::helpers::*; @@ -23,31 +23,52 @@ use binaryninja::{ }, }; -use gimli::{constants, DebuggingInformationEntry, Reader, Unit}; +use gimli::{constants, AttributeValue, DebuggingInformationEntry, Dwarf, Operation, Unit}; -use log::warn; +use log::{debug, error, warn}; -pub(crate) fn parse_data_variable>( +pub(crate) fn parse_variable( + dwarf: &Dwarf, unit: &Unit, entry: &DebuggingInformationEntry, debug_info_builder_context: &DebugInfoBuilderContext, debug_info_builder: &mut DebugInfoBuilder, + function_index: Option, ) { - let full_name = debug_info_builder_context.get_name(unit, entry); - let type_uid = get_type(unit, entry, debug_info_builder_context, debug_info_builder); + let full_name = debug_info_builder_context.get_name(dwarf, unit, entry); + let type_uid = get_type(dwarf, unit, entry, debug_info_builder_context, debug_info_builder); - let address = if let Ok(Some(attr)) = entry.attr(constants::DW_AT_location) { - get_expr_value(unit, attr) - } else { - None + let Ok(Some(attr)) = entry.attr(constants::DW_AT_location) else { + return }; - if let (Some(address), Some(type_uid)) = (address, type_uid) { - debug_info_builder.add_data_variable(address, full_name, type_uid); + let AttributeValue::Exprloc(mut expression) = attr.value() else { + return + }; + + match Operation::parse(&mut expression.0, unit.encoding()) { + Ok(Operation::FrameOffset { offset }) => { + debug_info_builder.add_stack_variable(function_index, offset, full_name, type_uid); + }, + //Ok(Operation::RegisterOffset { register: _, offset: _, base_type: _ }) => { + // //TODO: look up register by index (binja register indexes don't match processor indexes?) + // //TODO: calculate absolute stack offset + // //TODO: add by absolute offset + //}, + Ok(Operation::Address { address }) => { + if let Some(uid) = type_uid { + debug_info_builder.add_data_variable(address, full_name, uid) + } + }, + Ok(op) => { + debug!("Unhandled operation type for variable: {:?}", op); + }, + Err(e) => error!("Error parsing operation type for variable {:?}: {}", full_name, e) } } -fn do_structure_parse>( +fn do_structure_parse( + dwarf: &Dwarf, structure_type: StructureType, unit: &Unit, entry: &DebuggingInformationEntry, @@ -91,8 +112,8 @@ fn do_structure_parse>( return None; } - let full_name = if get_name(unit, entry, debug_info_builder_context).is_some() { - debug_info_builder_context.get_name(unit, entry) + let full_name = if get_name(dwarf, unit, entry, debug_info_builder_context).is_some() { + debug_info_builder_context.get_name(dwarf, unit, entry) } else { None }; @@ -109,8 +130,8 @@ fn do_structure_parse>( // it will also be how any other types refer to this struct if let Some(full_name) = &full_name { debug_info_builder.add_type( - get_uid(unit, entry), - full_name.clone(), + get_uid(dwarf, unit, entry), + &full_name, Type::named_type_from_type( full_name.clone(), &Type::structure(&structure_builder.finalize()), @@ -121,11 +142,11 @@ fn do_structure_parse>( // We _need_ to have initial typedefs or else we can enter infinite parsing loops // These get overwritten in the last step with the actual type, however, so this // is either perfectly fine or breaking a bunch of NTRs - let full_name = format!("anonymous_structure_{:x}", get_uid(unit, entry)); + let full_name = format!("anonymous_structure_{:x}", get_uid(dwarf, unit, entry)); debug_info_builder.add_type( - get_uid(unit, entry), - full_name.clone(), - Type::named_type_from_type(full_name, &Type::structure(&structure_builder.finalize())), + get_uid(dwarf, unit, entry), + &full_name, + Type::named_type_from_type(&full_name, &Type::structure(&structure_builder.finalize())), false, ); } @@ -136,14 +157,16 @@ fn do_structure_parse>( while let Ok(Some(child)) = children.next() { if child.entry().tag() == constants::DW_TAG_member { if let Some(child_type_id) = get_type( + dwarf, unit, child.entry(), debug_info_builder_context, debug_info_builder, ) { - if let Some((_, child_type)) = debug_info_builder.get_type(child_type_id) { + if let Some(t) = debug_info_builder.get_type(child_type_id) { + let child_type = t.get_type(); if let Some(child_name) = debug_info_builder_context - .get_name(unit, child.entry()) + .get_name(dwarf, unit, child.entry()) .map_or( if child_type.type_class() == TypeClass::StructureTypeClass { Some("".to_string()) @@ -188,32 +211,34 @@ fn do_structure_parse>( let finalized_structure = Type::structure(&structure_builder.finalize()); if let Some(full_name) = full_name { debug_info_builder.add_type( - get_uid(unit, entry) + 1, // TODO : This is super broke (uid + 1 is not guaranteed to be unique) - full_name, + get_uid(dwarf, unit, entry) + 1, // TODO : This is super broke (uid + 1 is not guaranteed to be unique) + &full_name, finalized_structure, true, ); } else { debug_info_builder.add_type( - get_uid(unit, entry), - format!("{}", finalized_structure), + get_uid(dwarf, unit, entry), + &format!("{}", finalized_structure), finalized_structure, false, // Don't commit anonymous unions (because I think it'll break things) ); } - Some(get_uid(unit, entry)) + Some(get_uid(dwarf, unit, entry)) } // This function iterates up through the dependency references, adding all the types along the way until there are no more or stopping at the first one already tracked, then returns the UID of the type of the given DIE -pub(crate) fn get_type>( +pub(crate) fn get_type( + dwarf: &Dwarf, unit: &Unit, entry: &DebuggingInformationEntry, debug_info_builder_context: &DebugInfoBuilderContext, debug_info_builder: &mut DebugInfoBuilder, ) -> Option { // If this node (and thus all its referenced nodes) has already been processed, just return the offset - if debug_info_builder.contains_type(get_uid(unit, entry)) { - return Some(get_uid(unit, entry)); + let entry_uid = get_uid(dwarf, unit, entry); + if debug_info_builder.contains_type(entry_uid) { + return Some(entry_uid); } // Don't parse types that are just declarations and not definitions @@ -222,6 +247,7 @@ pub(crate) fn get_type>( } let entry_type = if let Some(die_reference) = get_attr_die( + dwarf, unit, entry, debug_info_builder_context, @@ -229,25 +255,29 @@ pub(crate) fn get_type>( ) { // This needs to recurse first (before the early return below) to ensure all sub-types have been parsed match die_reference { - DieReference::UnitAndOffset((entry_unit, entry_offset)) => get_type( - entry_unit, - &entry_unit.entry(entry_offset).unwrap(), - debug_info_builder_context, - debug_info_builder, - ), + DieReference::UnitAndOffset((dwarf, entry_unit, entry_offset)) => { + get_type( + dwarf, + entry_unit, + &entry_unit.entry(entry_offset).unwrap(), + debug_info_builder_context, + debug_info_builder, + ) + } DieReference::Err => { - warn!("Failed to fetch DIE. Debug information may be incomplete."); + warn!("Failed to fetch DIE when getting type through DW_AT_type. Debug information may be incomplete."); None } } } else { // This needs to recurse first (before the early return below) to ensure all sub-types have been parsed - match resolve_specification(unit, entry, debug_info_builder_context) { - DieReference::UnitAndOffset((entry_unit, entry_offset)) + match resolve_specification(dwarf, unit, entry, debug_info_builder_context) { + DieReference::UnitAndOffset((dwarf, entry_unit, entry_offset)) if entry_unit.header.offset() != unit.header.offset() && entry_offset != entry.offset() => { get_type( + dwarf, entry_unit, &entry_unit.entry(entry_offset).unwrap(), debug_info_builder_context, @@ -256,7 +286,7 @@ pub(crate) fn get_type>( } DieReference::UnitAndOffset(_) => None, DieReference::Err => { - warn!("Failed to fetch DIE. Debug information may be incomplete."); + warn!("Failed to fetch DIE when getting type. Debug information may be incomplete."); None } } @@ -264,20 +294,21 @@ pub(crate) fn get_type>( // If this node (and thus all its referenced nodes) has already been processed, just return the offset // This check is not redundant because this type might have been processes in the recursive calls above - if debug_info_builder.contains_type(get_uid(unit, entry)) { - return Some(get_uid(unit, entry)); + if debug_info_builder.contains_type(entry_uid) { + return Some(entry_uid); } // Collect the required information to create a type and add it to the type map. Also, add the dependencies of this type to the type's typeinfo // Create the type, make a TypeInfo for it, and add it to the debug info let (type_def, mut commit): (Option>, bool) = match entry.tag() { constants::DW_TAG_base_type => ( - handle_base_type(unit, entry, debug_info_builder_context), + handle_base_type(dwarf, unit, entry, debug_info_builder_context), false, ), constants::DW_TAG_structure_type => { return do_structure_parse( + dwarf, StructureType::StructStructureType, unit, entry, @@ -287,6 +318,7 @@ pub(crate) fn get_type>( } constants::DW_TAG_class_type => { return do_structure_parse( + dwarf, StructureType::ClassStructureType, unit, entry, @@ -296,6 +328,7 @@ pub(crate) fn get_type>( } constants::DW_TAG_union_type => { return do_structure_parse( + dwarf, StructureType::UnionStructureType, unit, entry, @@ -306,13 +339,13 @@ pub(crate) fn get_type>( // Enum constants::DW_TAG_enumeration_type => { - (handle_enum(unit, entry, debug_info_builder_context), true) + (handle_enum(dwarf, unit, entry, debug_info_builder_context), true) } // Basic types constants::DW_TAG_typedef => { - if let Some(name) = debug_info_builder_context.get_name(unit, entry) { - handle_typedef(debug_info_builder, entry_type, name) + if let Some(name) = debug_info_builder_context.get_name(dwarf, unit, entry) { + handle_typedef(debug_info_builder, entry_type, &name) } else { (None, false) } @@ -356,6 +389,7 @@ pub(crate) fn get_type>( constants::DW_TAG_unspecified_type => (Some(Type::void()), false), constants::DW_TAG_subroutine_type => ( handle_function( + dwarf, unit, entry, debug_info_builder_context, @@ -375,8 +409,8 @@ pub(crate) fn get_type>( // Wrap our resultant type in a TypeInfo so that the internal DebugInfo class can manage it if let Some(type_def) = type_def { - let name = if get_name(unit, entry, debug_info_builder_context).is_some() { - debug_info_builder_context.get_name(unit, entry) + let name = if get_name(dwarf, unit, entry, debug_info_builder_context).is_some() { + debug_info_builder_context.get_name(dwarf, unit, entry) } else { None } @@ -385,8 +419,8 @@ pub(crate) fn get_type>( format!("{}", type_def) }); - debug_info_builder.add_type(get_uid(unit, entry), name, type_def, commit); - Some(get_uid(unit, entry)) + debug_info_builder.add_type(entry_uid, &name, type_def, commit); + Some(entry_uid) } else { None } diff --git a/examples/dwarf/dwarfdump/Cargo.toml b/examples/dwarf/dwarfdump/Cargo.toml index 0ede6af..3f1e25f 100644 --- a/examples/dwarf/dwarfdump/Cargo.toml +++ b/examples/dwarf/dwarfdump/Cargo.toml @@ -10,4 +10,4 @@ crate-type = ["cdylib"] [dependencies] dwarfreader = { path = "../shared/" } binaryninja = {path="../../../"} -gimli = "0.28" +gimli = "0.31" diff --git a/examples/dwarf/dwarfdump/src/lib.rs b/examples/dwarf/dwarfdump/src/lib.rs index d1a415d..2cfbbbc 100644 --- a/examples/dwarf/dwarfdump/src/lib.rs +++ b/examples/dwarf/dwarfdump/src/lib.rs @@ -17,7 +17,6 @@ use binaryninja::{ command::{register, Command}, disassembly::{DisassemblyTextLine, InstructionTextToken, InstructionTextTokenContents}, flowgraph::{BranchType, EdgeStyle, FlowGraph, FlowGraphNode, FlowGraphOption}, - string::BnString, }; use dwarfreader::is_valid; @@ -34,7 +33,7 @@ use gimli::{ UnitSectionOffset, }; -static PADDING: [&'static str; 23] = [ +static PADDING: [&str; 23] = [ "", " ", " ", @@ -77,14 +76,14 @@ fn get_info_string( let label_string = format!("#0x{:08x}", label_value); disassembly_lines.push(DisassemblyTextLine::from(vec![ InstructionTextToken::new( - BnString::new(label_string), + &label_string, InstructionTextTokenContents::GotoLabel(label_value), ), - InstructionTextToken::new(BnString::new(":"), InstructionTextTokenContents::Text), + InstructionTextToken::new(":", InstructionTextTokenContents::Text), ])); disassembly_lines.push(DisassemblyTextLine::from(vec![InstructionTextToken::new( - BnString::new(die_node.tag().static_string().unwrap()), + die_node.tag().static_string().unwrap(), InstructionTextTokenContents::TypeName, // TODO : KeywordToken? )])); @@ -92,7 +91,7 @@ fn get_info_string( while let Some(attr) = attrs.next().unwrap() { let mut attr_line: Vec = Vec::with_capacity(5); attr_line.push(InstructionTextToken::new( - BnString::new(" "), + " ", InstructionTextTokenContents::Indentation, )); @@ -100,14 +99,14 @@ fn get_info_string( if let Some(n) = attr.name().static_string() { len = n.len(); attr_line.push(InstructionTextToken::new( - BnString::new(n), + n, InstructionTextTokenContents::FieldName, )); } else { // This is rather unlikely, I think len = 1; attr_line.push(InstructionTextToken::new( - BnString::new("?"), + "?", InstructionTextTokenContents::FieldName, )); } @@ -115,25 +114,25 @@ fn get_info_string( // On command line the magic number that looks good is 22, but that's too much whitespace in a basic block, so I chose 18 (22 is the max with the current padding provided) if len < 18 { attr_line.push(InstructionTextToken::new( - BnString::new(PADDING[18 - len]), + PADDING[18 - len], InstructionTextTokenContents::Text, )); } attr_line.push(InstructionTextToken::new( - BnString::new(" = "), + " = ", InstructionTextTokenContents::Text, )); if let Ok(Some(addr)) = dwarf.attr_address(unit, attr.value()) { let addr_string = format!("0x{:08x}", addr); attr_line.push(InstructionTextToken::new( - BnString::new(addr_string), + &addr_string, InstructionTextTokenContents::Integer(addr), )); } else if let Ok(attr_reader) = dwarf.attr_string(unit, attr.value()) { if let Ok(attr_string) = attr_reader.to_string() { attr_line.push(InstructionTextToken::new( - BnString::new(attr_string.as_ref()), + attr_string.as_ref(), InstructionTextTokenContents::String({ let (_, id, offset) = dwarf.lookup_offset_id(attr_reader.offset_id()).unwrap(); @@ -142,13 +141,13 @@ fn get_info_string( )); } else { attr_line.push(InstructionTextToken::new( - BnString::new("??"), + "??", InstructionTextTokenContents::Text, )); } } else if let Encoding(type_class) = attr.value() { attr_line.push(InstructionTextToken::new( - BnString::new(type_class.static_string().unwrap()), + type_class.static_string().unwrap(), InstructionTextTokenContents::TypeName, )); } else if let UnitRef(offset) = attr.value() { @@ -159,17 +158,17 @@ fn get_info_string( .into_u64(); let addr_string = format!("#0x{:08x}", addr); attr_line.push(InstructionTextToken::new( - BnString::new(addr_string), + &addr_string, InstructionTextTokenContents::GotoLabel(addr), )); } else if let Flag(true) = attr.value() { attr_line.push(InstructionTextToken::new( - BnString::new("true"), + "true", InstructionTextTokenContents::Integer(1), )); } else if let Flag(false) = attr.value() { attr_line.push(InstructionTextToken::new( - BnString::new("false"), + "false", InstructionTextTokenContents::Integer(1), )); @@ -177,31 +176,31 @@ fn get_info_string( } else if let Some(value) = attr.u8_value() { let value_string = format!("{}", value); attr_line.push(InstructionTextToken::new( - BnString::new(value_string), + &value_string, InstructionTextTokenContents::Integer(value.into()), )); } else if let Some(value) = attr.u16_value() { let value_string = format!("{}", value); attr_line.push(InstructionTextToken::new( - BnString::new(value_string), + &value_string, InstructionTextTokenContents::Integer(value.into()), )); } else if let Some(value) = attr.udata_value() { let value_string = format!("{}", value); attr_line.push(InstructionTextToken::new( - BnString::new(value_string), - InstructionTextTokenContents::Integer(value.into()), + &value_string, + InstructionTextTokenContents::Integer(value), )); } else if let Some(value) = attr.sdata_value() { let value_string = format!("{}", value); attr_line.push(InstructionTextToken::new( - BnString::new(value_string), + &value_string, InstructionTextTokenContents::Integer(value as u64), )); } else { let attr_string = format!("{:?}", attr.value()); attr_line.push(InstructionTextToken::new( - BnString::new(attr_string), + &attr_string, InstructionTextTokenContents::Text, )); } @@ -282,7 +281,7 @@ fn dump_dwarf(bv: &BinaryView) { } } - view.show_graph_report("DWARF", graph); + view.show_graph_report("DWARF", &graph); } struct DWARFDump; diff --git a/examples/dwarf/shared/Cargo.toml b/examples/dwarf/shared/Cargo.toml index 521a705..c5b9a97 100644 --- a/examples/dwarf/shared/Cargo.toml +++ b/examples/dwarf/shared/Cargo.toml @@ -6,4 +6,4 @@ edition = "2021" [dependencies] binaryninja = {path="../../../"} -gimli = "0.28" +gimli = "0.31" diff --git a/examples/dwarf/shared/src/lib.rs b/examples/dwarf/shared/src/lib.rs index 718dcb8..7a957c6 100644 --- a/examples/dwarf/shared/src/lib.rs +++ b/examples/dwarf/shared/src/lib.rs @@ -20,6 +20,7 @@ use binaryninja::{ binaryview::{BinaryView, BinaryViewBase, BinaryViewExt}, databuffer::DataBuffer, Endianness, + settings::Settings, }; use std::{ffi::CString, rc::Rc}; @@ -52,6 +53,19 @@ pub fn is_raw_dwo_dwarf(view: &BinaryView) -> bool { } } +pub fn can_use_debuginfod(view: &BinaryView) -> bool { + has_build_id_section(view) && + Settings::new("") + .get_bool("network.enableDebuginfod", Some(view), None) +} + +pub fn has_build_id_section(view: &BinaryView) -> bool { + if let Ok(raw_view) = view.raw_view() { + return raw_view.section_by_name(".note.gnu.build-id").is_ok() + } + false +} + pub fn is_valid(view: &BinaryView) -> bool { is_non_dwo_dwarf(view) || is_raw_non_dwo_dwarf(view) @@ -88,23 +102,37 @@ pub fn create_section_reader<'a, Endian: 'a + Endianity>( if let Some(data_var) = view .data_variables() .iter() - .find(|var| var.address == symbol.address()) + .find(|var| var.address() == symbol.address()) { // TODO : This should eventually be wrapped by some DataView sorta thingy thing, like how python does it - let data_type = data_var.type_with_confidence().contents; - let data = view.read_vec(data_var.address, data_type.width() as usize); + let data_type = data_var.t(); + let data = view.read_vec(data_var.address(), data_type.width() as usize); let element_type = data_type.element_type().unwrap().contents; if let Some(current_section_header) = data .chunks(element_type.width() as usize) .find(|section_header| { - endian.read_u64(§ion_header[24..32]) == section.start() + if view.address_size() == 4 { + endian.read_u32(§ion_header[16..20]) as u64 == section.start() + } + else { + endian.read_u64(§ion_header[24..32]) == section.start() + } }) { - if (endian.read_u64(¤t_section_header[8..16]) & 2048) != 0 { + let section_flags = if view.address_size() == 4 { + endian.read_u32(¤t_section_header[8..12]) as u64 + } + else { + endian.read_u64(¤t_section_header[8..16]) + }; + // If the section has the compressed bit set + if (section_flags & 2048) != 0 { // Get section, trim header, decompress, return - let offset = section.start() + 24; - let len = section.len() - 24; + let compressed_header_size = view.address_size()*3; + + let offset = section.start() + compressed_header_size as u64; + let len = section.len() - compressed_header_size; if let Ok(buffer) = view.read_buffer(offset, len) { use std::ptr; diff --git a/examples/flowgraph/src/lib.rs b/examples/flowgraph/src/lib.rs index f80879e..2a03341 100644 --- a/examples/flowgraph/src/lib.rs +++ b/examples/flowgraph/src/lib.rs @@ -3,16 +3,15 @@ use binaryninja::{ command::register, disassembly::{DisassemblyTextLine, InstructionTextToken, InstructionTextTokenContents}, flowgraph::{BranchType, EdgePenStyle, EdgeStyle, FlowGraph, FlowGraphNode, ThemeColor}, - string::BnString, }; fn test_graph(view: &BinaryView) { let graph = FlowGraph::new(); let disassembly_lines_a = vec![DisassemblyTextLine::from(vec![ - InstructionTextToken::new(BnString::new("Li"), InstructionTextTokenContents::Text), - InstructionTextToken::new(BnString::new("ne"), InstructionTextTokenContents::Text), - InstructionTextToken::new(BnString::new(" 1"), InstructionTextTokenContents::Text), + InstructionTextToken::new("Li", InstructionTextTokenContents::Text), + InstructionTextToken::new("ne", InstructionTextTokenContents::Text), + InstructionTextToken::new(" 1", InstructionTextTokenContents::Text), ])]; let node_a = FlowGraphNode::new(&graph); @@ -37,7 +36,7 @@ fn test_graph(view: &BinaryView) { &EdgeStyle::default(), ); - view.show_graph_report("Rust Graph Title", graph); + view.show_graph_report("Rust Graph Title", &graph); } #[no_mangle] diff --git a/examples/hlil_visitor/src/main.rs b/examples/hlil_visitor/src/main.rs index 6d155ea..e1bdefb 100644 --- a/examples/hlil_visitor/src/main.rs +++ b/examples/hlil_visitor/src/main.rs @@ -2,9 +2,7 @@ use std::env; use binaryninja::binaryview::BinaryViewExt; use binaryninja::hlil::HighLevelILLiftedOperand; -use binaryninja::hlil::{ - HighLevelILFunction, HighLevelILLiftedInstruction, HighLevelILLiftedInstructionKind, -}; +use binaryninja::hlil::{HighLevelILFunction, HighLevelILLiftedInstruction}; use binaryninja::types::Variable; fn print_indent(indent: usize) { @@ -12,131 +10,7 @@ fn print_indent(indent: usize) { } fn print_operation(operation: &HighLevelILLiftedInstruction) { - use HighLevelILLiftedInstructionKind::*; - match &operation.kind { - Adc(_) => print!("Adc"), - Sbb(_) => print!("Sbb"), - Rlc(_) => print!("Rlc"), - Rrc(_) => print!("Rrc"), - Add(_) => print!("Add"), - Sub(_) => print!("Sub"), - And(_) => print!("And"), - Or(_) => print!("Or"), - Xor(_) => print!("Xor"), - Lsl(_) => print!("Lsl"), - Lsr(_) => print!("Lsr"), - Asr(_) => print!("Asr"), - Rol(_) => print!("Rol"), - Ror(_) => print!("Ror"), - Mul(_) => print!("Mul"), - MuluDp(_) => print!("MuluDp"), - MulsDp(_) => print!("MulsDp"), - Divu(_) => print!("Divu"), - DivuDp(_) => print!("DivuDp"), - Divs(_) => print!("Divs"), - DivsDp(_) => print!("DivsDp"), - Modu(_) => print!("Modu"), - ModuDp(_) => print!("ModuDp"), - Mods(_) => print!("Mods"), - ModsDp(_) => print!("ModsDp"), - CmpE(_) => print!("CmpE"), - CmpNe(_) => print!("CmpNe"), - CmpSlt(_) => print!("CmpSlt"), - CmpUlt(_) => print!("CmpUlt"), - CmpSle(_) => print!("CmpSle"), - CmpUle(_) => print!("CmpUle"), - CmpSge(_) => print!("CmpSge"), - CmpUge(_) => print!("CmpUge"), - CmpSgt(_) => print!("CmpSgt"), - CmpUgt(_) => print!("CmpUgt"), - TestBit(_) => print!("TestBit"), - AddOverflow(_) => print!("AddOverflow"), - Fadd(_) => print!("Fadd"), - Fsub(_) => print!("Fsub"), - Fmul(_) => print!("Fmul"), - Fdiv(_) => print!("Fdiv"), - FcmpE(_) => print!("FcmpE"), - FcmpNe(_) => print!("FcmpNe"), - FcmpLt(_) => print!("FcmpLt"), - FcmpLe(_) => print!("FcmpLe"), - FcmpGe(_) => print!("FcmpGe"), - FcmpGt(_) => print!("FcmpGt"), - FcmpO(_) => print!("FcmpO"), - FcmpUo(_) => print!("FcmpUo"), - ArrayIndex(_) => print!("ArrayIndex"), - ArrayIndexSsa(_) => print!("ArrayIndexSsa"), - Assign(_) => print!("Assign"), - AssignMemSsa(_) => print!("AssignMemSsa"), - AssignUnpack(_) => print!("AssignUnpack"), - AssignUnpackMemSsa(_) => print!("AssignUnpackMemSsa"), - Block(_) => print!("Block"), - Call(_) => print!("Call"), - Tailcall(_) => print!("Tailcall"), - CallSsa(_) => print!("CallSsa"), - Case(_) => print!("Case"), - Const(_) => print!("Const"), - ConstPtr(_) => print!("ConstPtr"), - Import(_) => print!("Import"), - ConstData(_) => print!("ConstData"), - Deref(_) => print!("Deref"), - AddressOf(_) => print!("AddressOf"), - Neg(_) => print!("Neg"), - Not(_) => print!("Not"), - Sx(_) => print!("Sx"), - Zx(_) => print!("Zx"), - LowPart(_) => print!("LowPart"), - BoolToInt(_) => print!("BoolToInt"), - UnimplMem(_) => print!("UnimplMem"), - Fsqrt(_) => print!("Fsqrt"), - Fneg(_) => print!("Fneg"), - Fabs(_) => print!("Fabs"), - FloatToInt(_) => print!("FloatToInt"), - IntToFloat(_) => print!("IntToFloat"), - FloatConv(_) => print!("FloatConv"), - RoundToInt(_) => print!("RoundToInt"), - Floor(_) => print!("Floor"), - Ceil(_) => print!("Ceil"), - Ftrunc(_) => print!("Ftrunc"), - DerefFieldSsa(_) => print!("DerefFieldSsa"), - DerefSsa(_) => print!("DerefSsa"), - ExternPtr(_) => print!("ExternPtr"), - FloatConst(_) => print!("FloatConst"), - For(_) => print!("For"), - ForSsa(_) => print!("ForSsa"), - Goto(_) => print!("Goto"), - Label(_) => print!("Label"), - If(_) => print!("If"), - Intrinsic(_) => print!("Intrinsic"), - IntrinsicSsa(_) => print!("IntrinsicSsa"), - Jump(_) => print!("Jump"), - MemPhi(_) => print!("MemPhi"), - Nop => print!("Nop"), - Break => print!("Break"), - Continue => print!("Continue"), - Noret => print!("Noret"), - Unreachable => print!("Unreachable"), - Bp => print!("Bp"), - Undef => print!("Undef"), - Unimpl => print!("Unimpl"), - Ret(_) => print!("Ret"), - Split(_) => print!("Split"), - StructField(_) => print!("StructField"), - DerefField(_) => print!("DerefField"), - Switch(_) => print!("Switch"), - Syscall(_) => print!("Syscall"), - SyscallSsa(_) => print!("SyscallSsa"), - Trap(_) => print!("Trap"), - VarDeclare(_) => print!("VarDeclare"), - Var(_) => print!("Var"), - VarInit(_) => print!("VarInit"), - VarInitSsa(_) => print!("VarInitSsa"), - VarPhi(_) => print!("VarPhi"), - VarSsa(_) => print!("VarSsa"), - While(_) => print!("While"), - DoWhile(_) => print!("DoWhile"), - WhileSsa(_) => print!("WhileSsa"), - DoWhileSsa(_) => print!("DoWhileSsa"), - } + print!("{}", operation.name()); } fn print_variable(func: &HighLevelILFunction, var: &Variable) { @@ -146,7 +20,7 @@ fn print_variable(func: &HighLevelILFunction, var: &Variable) { fn print_il_expr(instr: &HighLevelILLiftedInstruction, mut indent: usize) { print_indent(indent); print_operation(instr); - println!(""); + println!(); indent += 1; diff --git a/examples/minidump/src/command.rs b/examples/minidump/src/command.rs index 0b10c65..d33a8d0 100644 --- a/examples/minidump/src/command.rs +++ b/examples/minidump/src/command.rs @@ -5,14 +5,11 @@ use minidump::{Minidump, MinidumpMemoryInfoList}; use binaryninja::binaryview::{BinaryView, BinaryViewBase, BinaryViewExt}; -use crate::view::DataBufferWrapper; - pub fn print_memory_information(bv: &BinaryView) { debug!("Printing memory information"); if let Ok(minidump_bv) = bv.parent_view() { if let Ok(read_buffer) = minidump_bv.read_buffer(0, minidump_bv.len()) { - let read_buffer = DataBufferWrapper::new(read_buffer); - if let Ok(minidump_obj) = Minidump::read(read_buffer) { + if let Ok(minidump_obj) = Minidump::read(read_buffer.get_data()) { if let Ok(memory_info_list) = minidump_obj.get_stream::() { let mut memory_info_list_writer = Vec::new(); match memory_info_list.print(&mut memory_info_list_writer) { diff --git a/examples/minidump/src/view.rs b/examples/minidump/src/view.rs index 9eee6aa..bbbe0bd 100644 --- a/examples/minidump/src/view.rs +++ b/examples/minidump/src/view.rs @@ -1,6 +1,5 @@ use std::collections::HashMap; -use std::ops::{Deref, Range}; -use std::sync::Arc; +use std::ops::Range; use binaryninja::section::Section; use binaryninja::segment::Segment; @@ -16,37 +15,11 @@ use binaryninja::custombinaryview::{ BinaryViewType, BinaryViewTypeBase, CustomBinaryView, CustomBinaryViewType, CustomView, CustomViewBuilder, }; -use binaryninja::databuffer::DataBuffer; use binaryninja::platform::Platform; use binaryninja::Endianness; type BinaryViewResult = binaryninja::binaryview::Result; -/// A wrapper around a `binaryninja::databuffer::DataBuffer`, from which a `[u8]` buffer can be obtained -/// to pass to `minidump::Minidump::read`. -/// -/// This code is taken from [`dwarfdump`](https://github.com/Vector35/binaryninja-api/blob/9d8bc846bd213407fb1a7a19af2a96f17501ac3b/rust/examples/dwarfdump/src/lib.rs#L81) -/// in the Rust API examples. -#[derive(Clone)] -pub struct DataBufferWrapper { - inner: Arc, -} - -impl DataBufferWrapper { - pub fn new(buf: DataBuffer) -> Self { - DataBufferWrapper { - inner: Arc::new(buf), - } - } -} - -impl Deref for DataBufferWrapper { - type Target = [u8]; - fn deref(&self) -> &Self::Target { - self.inner.get_data() - } -} - /// The _Minidump_ binary view type, which the Rust plugin registers with the Binary Ninja core /// (via `binaryninja::custombinaryview::register_view_type`) as a possible binary view /// that can be applied to opened binaries. @@ -141,9 +114,8 @@ impl MinidumpBinaryView { fn init(&self) -> BinaryViewResult<()> { let parent_view = self.parent_view()?; let read_buffer = parent_view.read_buffer(0, parent_view.len())?; - let read_buffer = DataBufferWrapper::new(read_buffer); - if let Ok(minidump_obj) = Minidump::read(read_buffer) { + if let Ok(minidump_obj) = Minidump::read(read_buffer.get_data()) { // Architecture, platform information if let Ok(minidump_system_info) = minidump_obj.get_stream::() { if let Some(platform) = MinidumpBinaryView::translate_minidump_platform( diff --git a/examples/mlil_visitor/src/main.rs b/examples/mlil_visitor/src/main.rs index 63d2f50..752548c 100644 --- a/examples/mlil_visitor/src/main.rs +++ b/examples/mlil_visitor/src/main.rs @@ -1,10 +1,9 @@ use std::env; +use binaryninja::architecture::Intrinsic; use binaryninja::binaryview::BinaryViewExt; use binaryninja::mlil::MediumLevelILLiftedOperand; -use binaryninja::mlil::{ - MediumLevelILFunction, MediumLevelILLiftedInstruction, MediumLevelILLiftedInstructionKind, -}; +use binaryninja::mlil::{MediumLevelILFunction, MediumLevelILLiftedInstruction}; use binaryninja::types::Variable; fn print_indent(indent: usize) { @@ -12,139 +11,7 @@ fn print_indent(indent: usize) { } fn print_operation(operation: &MediumLevelILLiftedInstruction) { - use MediumLevelILLiftedInstructionKind::*; - match operation.kind { - Nop => print!("Nop"), - Noret => print!("Noret"), - Bp => print!("Bp"), - Undef => print!("Undef"), - Unimpl => print!("Unimpl"), - If(_) => print!("If"), - FloatConst(_) => print!("FloatConst"), - Const(_) => print!("Const"), - ConstPtr(_) => print!("ConstPtr"), - Import(_) => print!("Import"), - ExternPtr(_) => print!("ExternPtr"), - ConstData(_) => print!("ConstData"), - Jump(_) => print!("Jump"), - RetHint(_) => print!("RetHint"), - StoreSsa(_) => print!("StoreSsa"), - StoreStructSsa(_) => print!("StoreStructSsa"), - StoreStruct(_) => print!("StoreStruct"), - Store(_) => print!("Store"), - JumpTo(_) => print!("JumpTo"), - Goto(_) => print!("Goto"), - FreeVarSlot(_) => print!("FreeVarSlot"), - SetVarField(_) => print!("SetVarField"), - SetVar(_) => print!("SetVar"), - FreeVarSlotSsa(_) => print!("FreeVarSlotSsa"), - SetVarSsaField(_) => print!("SetVarSsaField"), - SetVarAliasedField(_) => print!("SetVarAliasedField"), - SetVarAliased(_) => print!("SetVarAliased"), - SetVarSsa(_) => print!("SetVarSsa"), - VarPhi(_) => print!("VarPhi"), - MemPhi(_) => print!("MemPhi"), - VarSplit(_) => print!("VarSplit"), - SetVarSplit(_) => print!("SetVarSplit"), - VarSplitSsa(_) => print!("VarSplitSsa"), - SetVarSplitSsa(_) => print!("SetVarSplitSsa"), - Add(_) => print!("Add"), - Sub(_) => print!("Sub"), - And(_) => print!("And"), - Or(_) => print!("Or"), - Xor(_) => print!("Xor"), - Lsl(_) => print!("Lsl"), - Lsr(_) => print!("Lsr"), - Asr(_) => print!("Asr"), - Rol(_) => print!("Rol"), - Ror(_) => print!("Ror"), - Mul(_) => print!("Mul"), - MuluDp(_) => print!("MuluDp"), - MulsDp(_) => print!("MulsDp"), - Divu(_) => print!("Divu"), - DivuDp(_) => print!("DivuDp"), - Divs(_) => print!("Divs"), - DivsDp(_) => print!("DivsDp"), - Modu(_) => print!("Modu"), - ModuDp(_) => print!("ModuDp"), - Mods(_) => print!("Mods"), - ModsDp(_) => print!("ModsDp"), - CmpE(_) => print!("CmpE"), - CmpNe(_) => print!("CmpNe"), - CmpSlt(_) => print!("CmpSlt"), - CmpUlt(_) => print!("CmpUlt"), - CmpSle(_) => print!("CmpSle"), - CmpUle(_) => print!("CmpUle"), - CmpSge(_) => print!("CmpSge"), - CmpUge(_) => print!("CmpUge"), - CmpSgt(_) => print!("CmpSgt"), - CmpUgt(_) => print!("CmpUgt"), - TestBit(_) => print!("TestBit"), - AddOverflow(_) => print!("AddOverflow"), - FcmpE(_) => print!("FcmpE"), - FcmpNe(_) => print!("FcmpNe"), - FcmpLt(_) => print!("FcmpLt"), - FcmpLe(_) => print!("FcmpLe"), - FcmpGe(_) => print!("FcmpGe"), - FcmpGt(_) => print!("FcmpGt"), - FcmpO(_) => print!("FcmpO"), - FcmpUo(_) => print!("FcmpUo"), - Fadd(_) => print!("Fadd"), - Fsub(_) => print!("Fsub"), - Fmul(_) => print!("Fmul"), - Fdiv(_) => print!("Fdiv"), - Adc(_) => print!("Adc"), - Sbb(_) => print!("Sbb"), - Rlc(_) => print!("Rlc"), - Rrc(_) => print!("Rrc"), - Call(_) => print!("Call"), - Tailcall(_) => print!("Tailcall"), - Syscall(_) => print!("Syscall"), - Intrinsic(_) => print!("Intrinsic"), - IntrinsicSsa(_) => print!("IntrinsicSsa"), - CallSsa(_) => print!("CallSsa"), - TailcallSsa(_) => print!("TailcallSsa"), - CallUntypedSsa(_) => print!("CallUntypedSsa"), - TailcallUntypedSsa(_) => print!("TailcallUntypedSsa"), - SyscallSsa(_) => print!("SyscallSsa"), - SyscallUntypedSsa(_) => print!("SyscallUntypedSsa"), - CallUntyped(_) => print!("CallUntyped"), - TailcallUntyped(_) => print!("TailcallUntyped"), - SyscallUntyped(_) => print!("SyscallUntyped"), - SeparateParamList(_) => print!("SeparateParamList"), - SharedParamSlot(_) => print!("SharedParamSlot"), - Neg(_) => print!("Neg"), - Not(_) => print!("Not"), - Sx(_) => print!("Sx"), - Zx(_) => print!("Zx"), - LowPart(_) => print!("LowPart"), - BoolToInt(_) => print!("BoolToInt"), - UnimplMem(_) => print!("UnimplMem"), - Fsqrt(_) => print!("Fsqrt"), - Fneg(_) => print!("Fneg"), - Fabs(_) => print!("Fabs"), - FloatToInt(_) => print!("FloatToInt"), - IntToFloat(_) => print!("IntToFloat"), - FloatConv(_) => print!("FloatConv"), - RoundToInt(_) => print!("RoundToInt"), - Floor(_) => print!("Floor"), - Ceil(_) => print!("Ceil"), - Ftrunc(_) => print!("Ftrunc"), - Load(_) => print!("Load"), - LoadStruct(_) => print!("LoadStruct"), - LoadStructSsa(_) => print!("LoadStructSsa"), - LoadSsa(_) => print!("LoadSsa"), - Ret(_) => print!("Ret"), - Var(_) => print!("Var"), - AddressOf(_) => print!("AddressOf"), - VarField(_) => print!("VarField"), - AddressOfField(_) => print!("AddressOfField"), - VarSsa(_) => print!("VarSsa"), - VarAliased(_) => print!("VarAliased"), - VarSsaField(_) => print!("VarSsaField"), - VarAliasedField(_) => print!("VarAliasedField"), - Trap(_) => print!("Trap"), - } + print!("{}", operation.name()); } fn print_variable(func: &MediumLevelILFunction, var: &Variable) { @@ -154,7 +21,7 @@ fn print_variable(func: &MediumLevelILFunction, var: &Variable) { fn print_il_expr(instr: &MediumLevelILLiftedInstruction, mut indent: usize) { print_indent(indent); print_operation(instr); - println!(""); + println!(); indent += 1; diff --git a/examples/pdb-ng/.gitignore b/examples/pdb-ng/.gitignore new file mode 100644 index 0000000..eb5a316 --- /dev/null +++ b/examples/pdb-ng/.gitignore @@ -0,0 +1 @@ +target diff --git a/examples/pdb-ng/CMakeLists.txt b/examples/pdb-ng/CMakeLists.txt new file mode 100644 index 0000000..c88d412 --- /dev/null +++ b/examples/pdb-ng/CMakeLists.txt @@ -0,0 +1,138 @@ +cmake_minimum_required(VERSION 3.9 FATAL_ERROR) + +project(pdb_import_plugin) + +file(GLOB 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) +endif() + +if(FORCE_COLORED_OUTPUT) + set(CARGO_OPTS ${CARGO_OPTS} --color always) +endif() + +if(DEMO) + set(CARGO_FEATURES --features demo --manifest-path ${PROJECT_SOURCE_DIR}/demo/Cargo.toml) + + set(OUTPUT_FILE_NAME ${CMAKE_STATIC_LIBRARY_PREFIX}${PROJECT_NAME}_static${CMAKE_STATIC_LIBRARY_SUFFIX}) + set(OUTPUT_PDB_NAME ${CMAKE_STATIC_LIBRARY_PREFIX}${PROJECT_NAME}.pdb) + set(OUTPUT_FILE_PATH ${CMAKE_BINARY_DIR}/${OUTPUT_FILE_NAME}) + set(OUTPUT_PDB_PATH ${CMAKE_BINARY_DIR}/${OUTPUT_PDB_NAME}) + + set(BINJA_LIB_DIR $) +else() + set(CARGO_FEATURES "") + + set(OUTPUT_FILE_NAME ${CMAKE_SHARED_LIBRARY_PREFIX}${PROJECT_NAME}${CMAKE_SHARED_LIBRARY_SUFFIX}) + set(OUTPUT_PDB_NAME ${CMAKE_SHARED_LIBRARY_PREFIX}${PROJECT_NAME}.pdb) + set(OUTPUT_FILE_PATH ${BN_CORE_PLUGIN_DIR}/${OUTPUT_FILE_NAME}) + set(OUTPUT_PDB_PATH ${BN_CORE_PLUGIN_DIR}/${OUTPUT_PDB_NAME}) + + set(BINJA_LIB_DIR ${BN_INSTALL_BIN_DIR}) +endif() + +add_custom_target(${PROJECT_NAME} ALL DEPENDS ${OUTPUT_FILE_PATH}) +add_dependencies(${PROJECT_NAME} binaryninjaapi) +get_target_property(BN_API_SOURCE_DIR binaryninjaapi SOURCE_DIR) +list(APPEND CMAKE_MODULE_PATH "${BN_API_SOURCE_DIR}/cmake") +find_package(BinaryNinjaCore REQUIRED) + +set_property(TARGET ${PROJECT_NAME} PROPERTY OUTPUT_FILE_PATH ${OUTPUT_FILE_PATH}) + +find_program(RUSTUP_PATH rustup REQUIRED HINTS ~/.cargo/bin) +set(RUSTUP_COMMAND ${RUSTUP_PATH} run ${CARGO_STABLE_VERSION} cargo) + +if(APPLE) + if(UNIVERSAL) + if(CMAKE_BUILD_TYPE MATCHES Debug) + set(AARCH64_LIB_PATH ${PROJECT_BINARY_DIR}/target/aarch64-apple-darwin/debug/${OUTPUT_FILE_NAME}) + set(X86_64_LIB_PATH ${PROJECT_BINARY_DIR}/target/x86_64-apple-darwin/debug/${OUTPUT_FILE_NAME}) + else() + set(AARCH64_LIB_PATH ${PROJECT_BINARY_DIR}/target/aarch64-apple-darwin/release/${OUTPUT_FILE_NAME}) + set(X86_64_LIB_PATH ${PROJECT_BINARY_DIR}/target/x86_64-apple-darwin/release/${OUTPUT_FILE_NAME}) + endif() + + add_custom_command( + OUTPUT ${OUTPUT_FILE_PATH} + COMMAND ${CMAKE_COMMAND} -E env + MACOSX_DEPLOYMENT_TARGET=10.14 BINARYNINJADIR=${BINJA_LIB_DIR} + ${RUSTUP_COMMAND} clean --target=aarch64-apple-darwin ${CARGO_OPTS} + COMMAND ${CMAKE_COMMAND} -E env + MACOSX_DEPLOYMENT_TARGET=10.14 BINARYNINJADIR=${BINJA_LIB_DIR} + ${RUSTUP_COMMAND} clean --target=x86_64-apple-darwin ${CARGO_OPTS} + COMMAND ${CMAKE_COMMAND} -E env + MACOSX_DEPLOYMENT_TARGET=10.14 BINARYNINJADIR=${BINJA_LIB_DIR} + ${RUSTUP_COMMAND} build --target=aarch64-apple-darwin ${CARGO_OPTS} ${CARGO_FEATURES} + COMMAND ${CMAKE_COMMAND} -E env + MACOSX_DEPLOYMENT_TARGET=10.14 BINARYNINJADIR=${BINJA_LIB_DIR} + ${RUSTUP_COMMAND} build --target=x86_64-apple-darwin ${CARGO_OPTS} ${CARGO_FEATURES} + COMMAND lipo -create ${AARCH64_LIB_PATH} ${X86_64_LIB_PATH} -output ${OUTPUT_FILE_PATH} + 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_NAME}) + else() + set(LIB_PATH ${PROJECT_BINARY_DIR}/target/release/${OUTPUT_FILE_NAME}) + endif() + + add_custom_command( + OUTPUT ${OUTPUT_FILE_PATH} + COMMAND ${CMAKE_COMMAND} -E env + MACOSX_DEPLOYMENT_TARGET=10.14 BINARYNINJADIR=${BINJA_LIB_DIR} + ${RUSTUP_COMMAND} clean ${CARGO_OPTS} + COMMAND ${CMAKE_COMMAND} -E env + MACOSX_DEPLOYMENT_TARGET=10.14 BINARYNINJADIR=${BINJA_LIB_DIR} + ${RUSTUP_COMMAND} build ${CARGO_OPTS} ${CARGO_FEATURES} + COMMAND ${CMAKE_COMMAND} -E copy ${LIB_PATH} ${OUTPUT_FILE_PATH} + WORKING_DIRECTORY ${PROJECT_SOURCE_DIR} + DEPENDS ${PLUGIN_SOURCES} ${API_SOURCES} + ) + endif() +elseif(WIN32) + if(DEMO) + add_custom_command( + OUTPUT ${OUTPUT_FILE_PATH} + COMMAND ${CMAKE_COMMAND} -E env BINARYNINJADIR=${BINJA_LIB_DIR} ${RUSTUP_COMMAND} clean ${CARGO_OPTS} + COMMAND ${CMAKE_COMMAND} -E env BINARYNINJADIR=${BINJA_LIB_DIR} ${RUSTUP_COMMAND} build ${CARGO_OPTS} ${CARGO_FEATURES} + COMMAND ${CMAKE_COMMAND} -E copy ${TARGET_DIR}/${OUTPUT_FILE_NAME} ${OUTPUT_FILE_PATH} + WORKING_DIRECTORY ${PROJECT_SOURCE_DIR} + DEPENDS ${PLUGIN_SOURCES} ${API_SOURCES} + ) + else() + add_custom_command( + OUTPUT ${OUTPUT_FILE_PATH} + COMMAND ${CMAKE_COMMAND} -E env BINARYNINJADIR=${BINJA_LIB_DIR} ${RUSTUP_COMMAND} clean ${CARGO_OPTS} + COMMAND ${CMAKE_COMMAND} -E env BINARYNINJADIR=${BINJA_LIB_DIR} ${RUSTUP_COMMAND} build ${CARGO_OPTS} ${CARGO_FEATURES} + COMMAND ${CMAKE_COMMAND} -E copy ${TARGET_DIR}/${OUTPUT_FILE_NAME} ${OUTPUT_FILE_PATH} + COMMAND ${CMAKE_COMMAND} -E copy ${TARGET_DIR}/${OUTPUT_PDB_NAME} ${OUTPUT_PDB_PATH} + WORKING_DIRECTORY ${PROJECT_SOURCE_DIR} + DEPENDS ${PLUGIN_SOURCES} ${API_SOURCES} + ) + endif() +else() + add_custom_command( + OUTPUT ${OUTPUT_FILE_PATH} + COMMAND ${CMAKE_COMMAND} -E env BINARYNINJADIR=${BINJA_LIB_DIR} ${RUSTUP_COMMAND} clean ${CARGO_OPTS} + COMMAND ${CMAKE_COMMAND} -E env BINARYNINJADIR=${BINJA_LIB_DIR} ${RUSTUP_COMMAND} build ${CARGO_OPTS} ${CARGO_FEATURES} + COMMAND ${CMAKE_COMMAND} -E copy ${TARGET_DIR}/${OUTPUT_FILE_NAME} ${OUTPUT_FILE_PATH} + WORKING_DIRECTORY ${PROJECT_SOURCE_DIR} + DEPENDS ${PLUGIN_SOURCES} ${API_SOURCES} + ) +endif() diff --git a/examples/pdb-ng/Cargo.lock b/examples/pdb-ng/Cargo.lock new file mode 100644 index 0000000..a820f88 --- /dev/null +++ b/examples/pdb-ng/Cargo.lock @@ -0,0 +1,540 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "adler" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" + +[[package]] +name = "aho-corasick" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2969dcb958b36655471fc61f7e416fa76033bdd4bfed0678d8fee1e2d07a1f0" +dependencies = [ + "memchr", +] + +[[package]] +name = "anyhow" +version = "1.0.81" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0952808a6c2afd1aa8947271f3a60f1a6763c7b912d210184c5149b5cf147247" + +[[package]] +name = "binaryninja" +version = "0.1.0" +dependencies = [ + "binaryninjacore-sys", + "lazy_static", + "libc", + "log", +] + +[[package]] +name = "binaryninjacore-sys" +version = "0.1.0" +dependencies = [ + "bindgen", +] + +[[package]] +name = "bindgen" +version = "0.68.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "726e4313eb6ec35d2730258ad4e15b547ee75d6afaa1361a922e78e59b7d8078" +dependencies = [ + "bitflags", + "cexpr", + "clang-sys", + "lazy_static", + "lazycell", + "log", + "peeking_take_while", + "prettyplease", + "proc-macro2", + "quote", + "regex", + "rustc-hash", + "shlex", + "syn", + "which", +] + +[[package]] +name = "bitflags" +version = "2.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed570934406eb16438a4e976b1b4500774099c13b8cb96eec99f620f05090ddf" + +[[package]] +name = "byteorder" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" + +[[package]] +name = "cab" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae6b4de23c7d39c0631fd3cc952d87951c86c75a13812d7247cb7a896e7b3551" +dependencies = [ + "byteorder", + "flate2", + "lzxd", + "time", +] + +[[package]] +name = "cexpr" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6fac387a98bb7c37292057cffc56d62ecb629900026402633ae9160df93a8766" +dependencies = [ + "nom", +] + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "clang-sys" +version = "1.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67523a3b4be3ce1989d607a828d036249522dd9c1c8de7f4dd2dae43a37369d1" +dependencies = [ + "glob", + "libc", + "libloading", +] + +[[package]] +name = "crc32fast" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b3855a8a784b474f333699ef2bbca9db2c4a1f6d9088a90a2d25b1eb53111eaa" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "deranged" +version = "0.3.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b42b6fa04a440b495c8b04d0e71b707c585f83cb9cb28cf8cd0d976c315e31b4" +dependencies = [ + "powerfmt", +] + +[[package]] +name = "either" +version = "1.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "11157ac094ffbdde99aa67b23417ebdd801842852b500e395a45a9c0aac03e4a" + +[[package]] +name = "errno" +version = "0.3.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a258e46cdc063eb8519c00b9fc845fc47bcfca4130e2f08e88665ceda8474245" +dependencies = [ + "libc", + "windows-sys", +] + +[[package]] +name = "fallible-iterator" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4443176a9f2c162692bd3d352d745ef9413eec5782a80d8fd6f8a1ac692a07f7" + +[[package]] +name = "flate2" +version = "1.0.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "46303f565772937ffe1d394a4fac6f411c6013172fadde9dcdb1e147a086940e" +dependencies = [ + "crc32fast", + "miniz_oxide", +] + +[[package]] +name = "glob" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2fabcfbdc87f4758337ca535fb41a6d701b65693ce38287d856d1674551ec9b" + +[[package]] +name = "home" +version = "0.5.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3d1354bf6b7235cb4a0576c2619fd4ed18183f689b12b006a0ee7329eeff9a5" +dependencies = [ + "windows-sys", +] + +[[package]] +name = "itertools" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1c173a5686ce8bfa551b3563d0c2170bf24ca44da99c7ca4bfdab5418c3fe57" +dependencies = [ + "either", +] + +[[package]] +name = "lazy_static" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" + +[[package]] +name = "lazycell" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "830d08ce1d1d941e6b30645f1a0eb5643013d835ce3779a5fc208261dbe10f55" + +[[package]] +name = "libc" +version = "0.2.153" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c198f91728a82281a64e1f4f9eeb25d82cb32a5de251c6bd1b5154d63a8e7bd" + +[[package]] +name = "libloading" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c2a198fb6b0eada2a8df47933734e6d35d350665a33a3593d7164fa52c75c19" +dependencies = [ + "cfg-if", + "windows-targets", +] + +[[package]] +name = "linux-raw-sys" +version = "0.4.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "01cda141df6706de531b6c46c3a33ecca755538219bd484262fa09410c13539c" + +[[package]] +name = "log" +version = "0.4.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "90ed8c1e510134f979dbc4f070f87d4313098b704861a105fe34231c70a3901c" + +[[package]] +name = "lzxd" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "784462f20dddd9dfdb45de963fa4ad4a288cb10a7889ac5d2c34fb6481c6b213" + +[[package]] +name = "memchr" +version = "2.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "523dc4f511e55ab87b694dc30d0f820d60906ef06413f93d4d7a1385599cc149" + +[[package]] +name = "minimal-lexical" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" + +[[package]] +name = "miniz_oxide" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d811f3e15f28568be3407c8e7fdb6514c1cda3cb30683f15b6a1a1dc4ea14a7" +dependencies = [ + "adler", +] + +[[package]] +name = "nom" +version = "7.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a" +dependencies = [ + "memchr", + "minimal-lexical", +] + +[[package]] +name = "num-conv" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9" + +[[package]] +name = "once_cell" +version = "1.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" + +[[package]] +name = "pdb" +version = "0.8.0" +dependencies = [ + "fallible-iterator", + "scroll", + "uuid", +] + +[[package]] +name = "pdb-import-plugin" +version = "0.1.0" +dependencies = [ + "anyhow", + "binaryninja", + "cab", + "home", + "itertools", + "log", + "pdb", + "regex", +] + +[[package]] +name = "peeking_take_while" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "19b17cddbe7ec3f8bc800887bab5e717348c95ea2ca0b1bf0837fb964dc67099" + +[[package]] +name = "powerfmt" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" + +[[package]] +name = "prettyplease" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a41cf62165e97c7f814d2221421dbb9afcbcdb0a88068e5ea206e19951c2cbb5" +dependencies = [ + "proc-macro2", + "syn", +] + +[[package]] +name = "proc-macro2" +version = "1.0.79" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e835ff2298f5721608eb1a980ecaee1aef2c132bf95ecc026a11b7bf3c01c02e" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "291ec9ab5efd934aaf503a6466c5d5251535d108ee747472c3977cc5acc868ef" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "regex" +version = "1.10.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b62dbe01f0b06f9d8dc7d49e05a0785f153b00b2c227856282f671e0318c9b15" +dependencies = [ + "aho-corasick", + "memchr", + "regex-automata", + "regex-syntax", +] + +[[package]] +name = "regex-automata" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "86b83b8b9847f9bf95ef68afb0b8e6cdb80f498442f5179a29fad448fcc1eaea" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] + +[[package]] +name = "regex-syntax" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c08c74e62047bb2de4ff487b251e4a92e24f48745648451635cec7d591162d9f" + +[[package]] +name = "rustc-hash" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" + +[[package]] +name = "rustix" +version = "0.38.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ea3e1a662af26cd7a3ba09c0297a31af215563ecf42817c98df621387f4e949" +dependencies = [ + "bitflags", + "errno", + "libc", + "linux-raw-sys", + "windows-sys", +] + +[[package]] +name = "scroll" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "04c565b551bafbef4157586fa379538366e4385d42082f255bfd96e4fe8519da" + +[[package]] +name = "serde" +version = "1.0.197" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3fb1c873e1b9b056a4dc4c0c198b24c3ffa059243875552b2bd0933b1aee4ce2" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.197" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7eb0b34b42edc17f6b7cac84a52a1c5f0e1bb2227e997ca9011ea3dd34e8610b" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "shlex" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" + +[[package]] +name = "syn" +version = "2.0.52" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b699d15b36d1f02c3e7c69f8ffef53de37aefae075d8488d4ba1a7788d574a07" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "time" +version = "0.3.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8248b6521bb14bc45b4067159b9b6ad792e2d6d754d6c41fb50e29fefe38749" +dependencies = [ + "deranged", + "num-conv", + "powerfmt", + "serde", + "time-core", +] + +[[package]] +name = "time-core" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef927ca75afb808a4d64dd374f00a2adf8d0fcff8e7b184af886c3c87ec4a3f3" + +[[package]] +name = "unicode-ident" +version = "1.0.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" + +[[package]] +name = "uuid" +version = "1.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f00cc9702ca12d3c81455259621e676d0f7251cec66a21e98fe2e9a37db93b2a" + +[[package]] +name = "which" +version = "4.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87ba24419a2078cd2b0f2ede2691b6c66d8e47836da3b6db8265ebad47afbfc7" +dependencies = [ + "either", + "home", + "once_cell", + "rustix", +] + +[[package]] +name = "windows-sys" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" +dependencies = [ + "windows-targets", +] + +[[package]] +name = "windows-targets" +version = "0.52.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7dd37b7e5ab9018759f893a1952c9420d060016fc19a472b4bb20d1bdd694d1b" +dependencies = [ + "windows_aarch64_gnullvm", + "windows_aarch64_msvc", + "windows_i686_gnu", + "windows_i686_msvc", + "windows_x86_64_gnu", + "windows_x86_64_gnullvm", + "windows_x86_64_msvc", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.52.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bcf46cf4c365c6f2d1cc93ce535f2c8b244591df96ceee75d8e83deb70a9cac9" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.52.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da9f259dd3bcf6990b55bffd094c4f7235817ba4ceebde8e6d11cd0c5633b675" + +[[package]] +name = "windows_i686_gnu" +version = "0.52.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b474d8268f99e0995f25b9f095bc7434632601028cf86590aea5c8a5cb7801d3" + +[[package]] +name = "windows_i686_msvc" +version = "0.52.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1515e9a29e5bed743cb4415a9ecf5dfca648ce85ee42e15873c3cd8610ff8e02" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.52.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5eee091590e89cc02ad514ffe3ead9eb6b660aedca2183455434b93546371a03" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.52.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77ca79f2451b49fa9e2af39f0747fe999fcda4f5e241b2898624dca97a1f2177" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.52.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32b752e52a2da0ddfbdbcc6fceadfeede4c939ed16d13e648833a61dfb611ed8" diff --git a/examples/pdb-ng/Cargo.toml b/examples/pdb-ng/Cargo.toml new file mode 100644 index 0000000..8636bce --- /dev/null +++ b/examples/pdb-ng/Cargo.toml @@ -0,0 +1,20 @@ +[package] +name = "pdb-import-plugin" +version = "0.1.0" +edition = "2021" + +[lib] +crate-type = ["cdylib"] + +[dependencies] +anyhow = "^1.0" +binaryninja = {path = "../../"} +home = "^0.5.5" +itertools = "^0.11" +log = "^0.4" +pdb = "^0.8" +cab = "^0.4" +regex = "1" + +[features] +demo = [] diff --git a/examples/pdb-ng/demo/Cargo.lock b/examples/pdb-ng/demo/Cargo.lock new file mode 100644 index 0000000..201c27d --- /dev/null +++ b/examples/pdb-ng/demo/Cargo.lock @@ -0,0 +1,555 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "adler" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" + +[[package]] +name = "aho-corasick" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2969dcb958b36655471fc61f7e416fa76033bdd4bfed0678d8fee1e2d07a1f0" +dependencies = [ + "memchr", +] + +[[package]] +name = "anyhow" +version = "1.0.75" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4668cab20f66d8d020e1fbc0ebe47217433c1b6c8f2040faf858554e394ace6" + +[[package]] +name = "binaryninja" +version = "0.1.0" +dependencies = [ + "binaryninjacore-sys", + "lazy_static", + "libc", + "log", +] + +[[package]] +name = "binaryninjacore-sys" +version = "0.1.0" +dependencies = [ + "bindgen", +] + +[[package]] +name = "bindgen" +version = "0.68.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "726e4313eb6ec35d2730258ad4e15b547ee75d6afaa1361a922e78e59b7d8078" +dependencies = [ + "bitflags", + "cexpr", + "clang-sys", + "lazy_static", + "lazycell", + "log", + "peeking_take_while", + "prettyplease", + "proc-macro2", + "quote", + "regex", + "rustc-hash", + "shlex", + "syn", + "which", +] + +[[package]] +name = "bitflags" +version = "2.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "327762f6e5a765692301e5bb513e0d9fef63be86bbc14528052b1cd3e6f03e07" + +[[package]] +name = "byteorder" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" + +[[package]] +name = "cab" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae6b4de23c7d39c0631fd3cc952d87951c86c75a13812d7247cb7a896e7b3551" +dependencies = [ + "byteorder", + "flate2", + "lzxd", + "time", +] + +[[package]] +name = "cexpr" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6fac387a98bb7c37292057cffc56d62ecb629900026402633ae9160df93a8766" +dependencies = [ + "nom", +] + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "clang-sys" +version = "1.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c688fc74432808e3eb684cae8830a86be1d66a2bd58e1f248ed0960a590baf6f" +dependencies = [ + "glob", + "libc", + "libloading", +] + +[[package]] +name = "crc32fast" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b540bd8bc810d3885c6ea91e2018302f68baba2129ab3e88f32389ee9370880d" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "deranged" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0f32d04922c60427da6f9fef14d042d9edddef64cb9d4ce0d64d0685fbeb1fd3" +dependencies = [ + "powerfmt", +] + +[[package]] +name = "either" +version = "1.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a26ae43d7bcc3b814de94796a5e736d4029efb0ee900c12e2d54c993ad1a1e07" + +[[package]] +name = "errno" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3e13f66a2f95e32a39eaa81f6b95d42878ca0e1db0c7543723dfe12557e860" +dependencies = [ + "libc", + "windows-sys", +] + +[[package]] +name = "fallible-iterator" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4443176a9f2c162692bd3d352d745ef9413eec5782a80d8fd6f8a1ac692a07f7" + +[[package]] +name = "flate2" +version = "1.0.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "46303f565772937ffe1d394a4fac6f411c6013172fadde9dcdb1e147a086940e" +dependencies = [ + "crc32fast", + "miniz_oxide", +] + +[[package]] +name = "glob" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2fabcfbdc87f4758337ca535fb41a6d701b65693ce38287d856d1674551ec9b" + +[[package]] +name = "home" +version = "0.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5444c27eef6923071f7ebcc33e3444508466a76f7a2b93da00ed6e19f30c1ddb" +dependencies = [ + "windows-sys", +] + +[[package]] +name = "itertools" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1c173a5686ce8bfa551b3563d0c2170bf24ca44da99c7ca4bfdab5418c3fe57" +dependencies = [ + "either", +] + +[[package]] +name = "lazy_static" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" + +[[package]] +name = "lazycell" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "830d08ce1d1d941e6b30645f1a0eb5643013d835ce3779a5fc208261dbe10f55" + +[[package]] +name = "libc" +version = "0.2.149" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a08173bc88b7955d1b3145aa561539096c421ac8debde8cbc3612ec635fee29b" + +[[package]] +name = "libloading" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b67380fd3b2fbe7527a606e18729d21c6f3951633d0500574c4dc22d2d638b9f" +dependencies = [ + "cfg-if", + "winapi", +] + +[[package]] +name = "linux-raw-sys" +version = "0.4.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da2479e8c062e40bf0066ffa0bc823de0a9368974af99c9f6df941d2c231e03f" + +[[package]] +name = "log" +version = "0.4.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b5e6163cb8c49088c2c36f57875e58ccd8c87c7427f7fbd50ea6710b2f3f2e8f" + +[[package]] +name = "lzxd" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "784462f20dddd9dfdb45de963fa4ad4a288cb10a7889ac5d2c34fb6481c6b213" + +[[package]] +name = "memchr" +version = "2.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f665ee40bc4a3c5590afb1e9677db74a508659dfd71e126420da8274909a0167" + +[[package]] +name = "minimal-lexical" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" + +[[package]] +name = "miniz_oxide" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7810e0be55b428ada41041c41f32c9f1a42817901b4ccf45fa3d4b6561e74c7" +dependencies = [ + "adler", +] + +[[package]] +name = "nom" +version = "7.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a" +dependencies = [ + "memchr", + "minimal-lexical", +] + +[[package]] +name = "once_cell" +version = "1.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd8b5dd2ae5ed71462c540258bedcb51965123ad7e7ccf4b9a8cafaa4a63576d" + +[[package]] +name = "pdb" +version = "0.8.0" +dependencies = [ + "fallible-iterator", + "scroll", + "uuid", +] + +[[package]] +name = "pdb-import-plugin" +version = "0.1.0" +dependencies = [ + "anyhow", + "binaryninja", + "cab", + "home", + "itertools", + "log", + "pdb", + "regex", +] + +[[package]] +name = "peeking_take_while" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "19b17cddbe7ec3f8bc800887bab5e717348c95ea2ca0b1bf0837fb964dc67099" + +[[package]] +name = "powerfmt" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" + +[[package]] +name = "prettyplease" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae005bd773ab59b4725093fd7df83fd7892f7d8eafb48dbd7de6e024e4215f9d" +dependencies = [ + "proc-macro2", + "syn", +] + +[[package]] +name = "proc-macro2" +version = "1.0.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "134c189feb4956b20f6f547d2cf727d4c0fe06722b20a0eec87ed445a97f92da" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.33" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5267fca4496028628a95160fc423a33e8b2e6af8a5302579e322e4b520293cae" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "regex" +version = "1.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "380b951a9c5e80ddfd6136919eef32310721aa4aacd4889a8d39124b026ab343" +dependencies = [ + "aho-corasick", + "memchr", + "regex-automata", + "regex-syntax", +] + +[[package]] +name = "regex-automata" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f804c7828047e88b2d32e2d7fe5a105da8ee3264f01902f796c8e067dc2483f" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] + +[[package]] +name = "regex-syntax" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c08c74e62047bb2de4ff487b251e4a92e24f48745648451635cec7d591162d9f" + +[[package]] +name = "rustc-hash" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" + +[[package]] +name = "rustix" +version = "0.38.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "745ecfa778e66b2b63c88a61cb36e0eea109e803b0b86bf9879fbc77c70e86ed" +dependencies = [ + "bitflags", + "errno", + "libc", + "linux-raw-sys", + "windows-sys", +] + +[[package]] +name = "scroll" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "04c565b551bafbef4157586fa379538366e4385d42082f255bfd96e4fe8519da" + +[[package]] +name = "serde" +version = "1.0.189" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e422a44e74ad4001bdc8eede9a4570ab52f71190e9c076d14369f38b9200537" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.189" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e48d1f918009ce3145511378cf68d613e3b3d9137d67272562080d68a2b32d5" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "shlex" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7cee0529a6d40f580e7a5e6c495c8fbfe21b7b52795ed4bb5e62cdf92bc6380" + +[[package]] +name = "syn" +version = "2.0.38" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e96b79aaa137db8f61e26363a0c9b47d8b4ec75da28b7d1d614c2303e232408b" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "time" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c4a34ab300f2dee6e562c10a046fc05e358b29f9bf92277f30c3c8d82275f6f5" +dependencies = [ + "deranged", + "powerfmt", + "serde", + "time-core", +] + +[[package]] +name = "time-core" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef927ca75afb808a4d64dd374f00a2adf8d0fcff8e7b184af886c3c87ec4a3f3" + +[[package]] +name = "unicode-ident" +version = "1.0.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" + +[[package]] +name = "uuid" +version = "1.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "79daa5ed5740825c40b389c5e50312b9c86df53fccd33f281df655642b43869d" + +[[package]] +name = "which" +version = "4.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87ba24419a2078cd2b0f2ede2691b6c66d8e47836da3b6db8265ebad47afbfc7" +dependencies = [ + "either", + "home", + "once_cell", + "rustix", +] + +[[package]] +name = "winapi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" + +[[package]] +name = "windows-sys" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" +dependencies = [ + "windows-targets", +] + +[[package]] +name = "windows-targets" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" +dependencies = [ + "windows_aarch64_gnullvm", + "windows_aarch64_msvc", + "windows_i686_gnu", + "windows_i686_msvc", + "windows_x86_64_gnu", + "windows_x86_64_gnullvm", + "windows_x86_64_msvc", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" + +[[package]] +name = "windows_i686_gnu" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" + +[[package]] +name = "windows_i686_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" diff --git a/examples/pdb-ng/demo/Cargo.toml b/examples/pdb-ng/demo/Cargo.toml new file mode 100644 index 0000000..656d1c6 --- /dev/null +++ b/examples/pdb-ng/demo/Cargo.toml @@ -0,0 +1,21 @@ +[package] +name = "pdb-import-plugin-static" +version = "0.1.0" +edition = "2021" + +[lib] +crate-type = ["staticlib"] +path = "../src/lib.rs" + +[dependencies] +anyhow = "^1.0" +binaryninja = {path = "../../../"} +home = "^0.5.5" +itertools = "^0.11" +log = "^0.4" +pdb = "^0.8" +cab = "^0.4" +regex = "1" + +[features] +demo = [] diff --git a/examples/pdb-ng/src/lib.rs b/examples/pdb-ng/src/lib.rs new file mode 100644 index 0000000..e2e3ec4 --- /dev/null +++ b/examples/pdb-ng/src/lib.rs @@ -0,0 +1,937 @@ +// Copyright 2022-2024 Vector 35 Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +use std::collections::HashMap; +use std::env::{current_dir, current_exe, temp_dir}; +use std::io::Cursor; +use std::path::PathBuf; +use std::str::FromStr; +use std::sync::mpsc; +use std::{env, fs}; + +use anyhow::{anyhow, Result}; +use log::{debug, error, info, LevelFilter}; +use pdb::PDB; + +use binaryninja::binaryview::{BinaryView, BinaryViewBase, BinaryViewExt}; +use binaryninja::debuginfo::{CustomDebugInfoParser, DebugInfo, DebugInfoParser}; +use binaryninja::downloadprovider::{DownloadInstanceInputOutputCallbacks, DownloadProvider}; +use binaryninja::interaction::{MessageBoxButtonResult, MessageBoxButtonSet}; +use binaryninja::settings::Settings; +use binaryninja::string::BnString; +use binaryninja::{add_optional_plugin_dependency, interaction, logger, user_directory}; +use parser::PDBParserInstance; + +/// PDB Parser!! +/// +/// General project structure: +/// - lib.rs: Interaction with DebugInfoParser and plugin actions +/// - parser.rs: PDB Parser base functionality, puts the internal structures into the DebugInfo +/// - type_parser.rs: Parses all the TPI type stream information into both named and indexed types +/// - symbol_parser.rs: Parses, one module at a time, symbol information into named symbols +/// - struct_grouper.rs: Ugly algorithm for handling union and structure members +mod parser; +mod struct_grouper; +mod symbol_parser; +mod type_parser; + +// struct PDBLoad; +// struct PDBLoadFile; +// struct PDBSetSymbolPath; + +#[allow(dead_code)] +struct PDBInfo { + path: String, + file_name: String, + age: u32, + guid: Vec, + guid_age_string: String, +} + +fn is_pdb(view: &BinaryView) -> bool { + let pdb_magic_bytes = "Microsoft C/C++ MSF 7.00\r\n\x1A\x44\x53\x00\x00\x00"; + if let Ok(raw_view) = view.raw_view() { + raw_view.read_vec(0, pdb_magic_bytes.len()) == pdb_magic_bytes.as_bytes() + } else { + false + } +} + +fn default_local_cache() -> Result { + // The default value is a directory named "sym" immediately below the program directory + // of the calling application. This is sometimes referred to as the default local cache. + let current_path = current_exe()?; + let parent_path = current_path + .parent() + .ok_or_else(|| anyhow!("No parent to current exe"))?; + let mut cache_path = PathBuf::from(parent_path); + cache_path.push("sym"); + return Ok(cache_path + .to_str() + .ok_or_else(|| anyhow!("Could not convert cache path to string"))? + .to_string()); +} + +fn active_local_cache(view: Option<&BinaryView>) -> Result { + // Check the local symbol store + let mut local_store_path = Settings::new("") + .get_string("pdb.files.localStoreAbsolute", view, None) + .to_string(); + if local_store_path.is_empty() { + local_store_path = match user_directory() { + Ok(mut dir) => { + dir.push( + Settings::new("") + .get_string("pdb.files.localStoreRelative", view, None) + .to_string(), + ); + match dir.to_str() { + Some(s) => s.to_string(), + _ => "".to_string(), + } + } + _ => "".to_string(), + }; + } + if !local_store_path.is_empty() { + Ok(local_store_path) + } else if let Ok(default_cache) = default_local_cache() { + Ok(default_cache) + } else if let Ok(current) = current_dir().map(|d| { + d.to_str() + .expect("Expected current dir to be a valid string") + .to_string() + }) { + Ok(current) + } else { + Ok(temp_dir() + .to_str() + .expect("Expected temp dir to be a valid string") + .to_string()) + } +} + +fn parse_sym_srv( + symbol_path: &String, + default_store: &String, +) -> Result>> { + // https://docs.microsoft.com/en-us/windows/win32/debug/using-symsrv + // Why + + // ... the symbol path (_NT_SYMBOL_PATH environment variable) can be made up of several path + // elements separated by semicolons. If any one or more of these path elements begins with + // the text "srv*", then the element is a symbol server and will use SymSrv to locate + // symbol files. + + // If the "srv*" text is not specified but the actual path element is a symbol server store, + // then the symbol handler will act as if "srv*" were specified. The symbol handler makes + // this determination by searching for the existence of a file called "pingme.txt" in + // the root directory of the specified path. + + // ... symbol servers are made up of symbol store elements separated by asterisks. There can + // be up to 10 symbol stores after the "srv*" prefix. + + let mut sym_srv_results = vec![]; + + // 'path elements separated by semicolons' + for path_element in symbol_path.split(';') { + // 'begins with the text "srv*"' + if path_element.to_lowercase().starts_with("srv*") { + // 'symbol store elements separated by asterisks' + for store_element in path_element[4..].split('*') { + if store_element.is_empty() { + sym_srv_results.push(default_store.clone()); + } else { + sym_srv_results.push(store_element.to_string()); + } + } + } else if PathBuf::from(path_element).exists() { + // 'searching for the existence of a file called "pingme.txt" in the root directory' + let pingme_txt = path_element.to_string() + "/" + "pingme.txt"; + if PathBuf::from(pingme_txt).exists() { + sym_srv_results.push(path_element.to_string()); + } + } + } + + Ok(Box::new(sym_srv_results.into_iter())) +} + +fn read_from_sym_store(path: &String) -> Result<(bool, Vec)> { + info!("Read file: {}", path); + if !path.contains("://") { + // Local file + let conts = fs::read(path)?; + return Ok((false, conts)); + } + + if !Settings::new("").get_bool("network.pdbAutoDownload", None, None) { + return Err(anyhow!("Auto download disabled")); + } + + // Download from remote + let (tx, rx) = mpsc::channel(); + let write = move |data: &[u8]| -> usize { + if let Ok(_) = tx.send(Vec::from(data)) { + data.len() + } else { + 0 + } + }; + + info!("GET: {}", path); + + let dp = + DownloadProvider::try_default().map_err(|_| anyhow!("No default download provider"))?; + let mut inst = dp + .create_instance() + .map_err(|_| anyhow!("Couldn't create download instance"))?; + let result = inst + .perform_custom_request( + "GET", + path.clone(), + HashMap::::new(), + DownloadInstanceInputOutputCallbacks { + read: None, + write: Some(Box::new(write)), + progress: None, + }, + ) + .map_err(|e| anyhow!(e.to_string()))?; + if result.status_code != 200 { + return Err(anyhow!("Path does not exist")); + } + + let mut expected_length = None; + for (k, v) in result.headers.iter() { + if k.to_lowercase() == "content-length" { + expected_length = Some(usize::from_str(v)?); + } + } + + let mut data = vec![]; + while let Ok(packet) = rx.try_recv() { + data.extend(packet.into_iter()); + } + + if let Some(length) = expected_length { + if data.len() != length { + return Err(anyhow!(format!( + "Bad length: expected {} got {}", + length, + data.len() + ))); + } + } + + Ok((true, data)) +} + +fn search_sym_store(store_path: &String, pdb_info: &PDBInfo) -> Result>> { + // https://www.technlg.net/windows/symbol-server-path-windbg-debugging/ + // For symbol servers, to identify the files path easily, Windbg uses the format + // binaryname.pdb/GUID + + // Doesn't actually say what the format is, just gives an example: + // https://docs.microsoft.com/en-us/windows/win32/debug/using-symstore + // In this example, the lookup path for the acpi.dbg symbol file might look something + // like this: \\mybuilds\symsrv\acpi.dbg\37cdb03962040. + let base_path = + store_path.clone() + "/" + &pdb_info.file_name + "/" + &pdb_info.guid_age_string; + + // Three files may exist inside the lookup directory: + // 1. If the file was stored, then acpi.dbg will exist there. + // 2. If a pointer was stored, then a file called file.ptr will exist and contain the path + // to the actual symbol file. + // 3. A file called refs.ptr, which contains a list of all the current locations for + // acpi.dbg with this timestamp and image size that are currently added to the + // symbol store. + + // We don't care about #3 because it says we don't + + let direct_path = base_path.clone() + "/" + &pdb_info.file_name; + if let Ok((_remote, conts)) = read_from_sym_store(&direct_path) { + return Ok(Some(conts)); + } + + let file_ptr = base_path.clone() + "/" + "file.ptr"; + if let Ok((_remote, conts)) = read_from_sym_store(&file_ptr) { + let path = String::from_utf8(conts)?; + // PATH:https://full/path + if path.starts_with("PATH:") { + if let Ok((_remote, conts)) = read_from_sym_store(&path[5..].to_string()) { + return Ok(Some(conts)); + } + } + } + + return Ok(None); +} + +fn parse_pdb_info(view: &BinaryView) -> Option { + match view.get_metadata::("DEBUG_INFO_TYPE") { + Some(Ok(0x53445352 /* 'SDSR' */)) => {} + _ => return None, + } + + // This is stored in the BV by the PE loader + let file_path = match view.get_metadata::("PDB_FILENAME") { + Some(Ok(md)) => md, + _ => return None, + }; + let mut guid = match view.get_metadata::, _>("PDB_GUID") { + Some(Ok(md)) => md, + _ => return None, + }; + let age = match view.get_metadata::("PDB_AGE") { + Some(Ok(md)) => md as u32, + _ => return None, + }; + + if guid.len() != 16 { + return None; + } + + // struct _GUID { + // uint32_t Data1; + // uint16_t Data2; + // uint16_t Data3; + // uint8_t Data4[8]; + // }; + + // Endian swap + // Data1 + guid.swap(0, 3); + guid.swap(1, 2); + // Data2 + guid.swap(4, 5); + // Data3 + guid.swap(6, 7); + + let guid_age_string = guid + .iter() + .take(16) + .map(|ch| format!("{:02X}", ch)) + .collect::>() + .join("") + + &format!("{:X}", age); + + // Just assume all the paths are / + let file_path = if cfg!(windows) { + file_path + } else { + file_path.replace("\\", "/") + }; + let path = file_path; + let file_name = if let Some(idx) = path.rfind("\\") { + path[(idx + 1)..].to_string() + } else if let Some(idx) = path.rfind("/") { + path[(idx + 1)..].to_string() + } else { + path.clone() + }; + + Some(PDBInfo { + path, + file_name, + age, + guid, + guid_age_string, + }) +} + +struct PDBParser; +impl PDBParser { + fn load_from_file( + &self, + conts: &Vec, + debug_info: &mut DebugInfo, + view: &BinaryView, + progress: &Box Result<(), ()>>, + check_guid: bool, + did_download: bool, + ) -> Result<()> { + let mut pdb = PDB::open(Cursor::new(&conts))?; + + let settings = Settings::new(""); + + if let Some(info) = parse_pdb_info(view) { + let pdb_info = &pdb.pdb_information()?; + if info.guid.as_slice() != pdb_info.guid.as_ref() { + if check_guid { + return Err(anyhow!("PDB GUID does not match")); + } else { + let ask = settings.get_string( + "pdb.features.loadMismatchedPDB", + Some(view), + None, + ); + + match ask.as_str() { + "true" => {}, + "ask" => { + if interaction::show_message_box( + "Mismatched PDB", + "This PDB does not look like it matches your binary. Do you want to load it anyway?", + MessageBoxButtonSet::YesNoButtonSet, + binaryninja::interaction::MessageBoxIcon::QuestionIcon + ) == MessageBoxButtonResult::NoButton { + return Err(anyhow!("User cancelled mismatched load")); + } + } + _ => { + return Err(anyhow!("PDB GUID does not match")); + } + } + } + } + + // Microsoft's symbol server sometimes gives us a different version of the PDB + // than what we ask for. It's weird, but if they're doing it, I trust it will work. + if info.age != pdb_info.age { + if info.age > pdb_info.age { + // Have not seen this case, so I'm not sure if this is fatal + info!("PDB age is older than our binary! Loading it anyway, but there may be missing information."); + } else { + info!("PDB age is newer than our binary! Loading it anyway, there probably shouldn't be any issues."); + } + } + + if did_download && settings.get_bool("pdb.files.localStoreCache", None, None) { + match active_local_cache(Some(view)) { + Ok(cache) => { + let mut cab_path = PathBuf::from(&cache); + cab_path.push(&info.file_name); + cab_path.push( + pdb_info + .guid + .as_ref() + .iter() + .map(|ch| format!("{:02X}", ch)) + .collect::>() + .join("") + + &format!("{:X}", pdb_info.age), + ); + let has_dir = if cab_path.is_dir() { + true + } else { + match fs::create_dir_all(&cab_path) { + Ok(_) => true, + Err(e) => { + error!("Could not create PDB cache dir: {}", e); + false + } + } + }; + if has_dir { + cab_path.push(&info.file_name); + match fs::write(&cab_path, &conts) { + Ok(_) => { + info!("Downloaded to: {}", cab_path.to_string_lossy()); + } + Err(e) => error!("Could not write PDB to cache: {}", e), + } + } + + // Also write with the age we expect in our binary view + if info.age < pdb_info.age { + let mut cab_path = PathBuf::from(&cache); + cab_path.push(&info.file_name); + cab_path.push( + pdb_info + .guid + .as_ref() + .iter() + .map(|ch| format!("{:02X}", ch)) + .collect::>() + .join("") + + &format!("{:X}", info.age), // XXX: BV's pdb age + ); + let has_dir = if cab_path.is_dir() { + true + } else { + match fs::create_dir_all(&cab_path) { + Ok(_) => true, + Err(e) => { + error!("Could not create PDB cache dir: {}", e); + false + } + } + }; + if has_dir { + cab_path.push(&info.file_name); + match fs::write(&cab_path, &conts) { + Ok(_) => { + info!("Downloaded to: {}", cab_path.to_string_lossy()); + } + Err(e) => error!("Could not write PDB to cache: {}", e), + } + } + } + } + Err(e) => error!("Could not get local cache for writing: {}", e), + } + } + } else { + if check_guid { + return Err(anyhow!("File not compiled with PDB information")); + } else { + let ask = settings.get_string( + "pdb.features.loadMismatchedPDB", + Some(view), + None, + ); + + match ask.as_str() { + "true" => {}, + "ask" => { + if interaction::show_message_box( + "No PDB Information", + "This file does not look like it was compiled with a PDB, so your PDB might not correctly apply to the analysis. Do you want to load it anyway?", + MessageBoxButtonSet::YesNoButtonSet, + binaryninja::interaction::MessageBoxIcon::QuestionIcon + ) == MessageBoxButtonResult::NoButton { + return Err(anyhow!("User cancelled missing info load")); + } + } + _ => { + return Err(anyhow!("File not compiled with PDB information")); + } + } + } + } + + let mut inst = match PDBParserInstance::new(debug_info, view, pdb) { + Ok(inst) => { + info!("Loaded PDB, parsing..."); + inst + } + Err(e) => { + error!("Could not open PDB: {}", e); + return Err(e); + } + }; + match inst.try_parse_info(Box::new(|cur, max| { + (*progress)(cur, max).map_err(|_| anyhow!("Cancelled")) + })) { + Ok(()) => { + info!("Parsed pdb"); + Ok(()) + } + Err(e) => { + error!("Could not parse PDB: {}", e); + if e.to_string() == "Todo" { + Ok(()) + } else { + Err(e) + } + } + } + } +} + +impl CustomDebugInfoParser for PDBParser { + fn is_valid(&self, view: &BinaryView) -> bool { + view.type_name().to_string() == "PE" || is_pdb(view) + } + + fn parse_info( + &self, + debug_info: &mut DebugInfo, + view: &BinaryView, + debug_file: &BinaryView, + progress: Box Result<(), ()>>, + ) -> bool { + if is_pdb(debug_file) { + match self.load_from_file( + &debug_file.read_vec(0, debug_file.len()), + debug_info, + view, + &progress, + false, + false, + ) { + Ok(_) => return true, + Err(e) if e.to_string() == "Cancelled" => return false, + Err(_) => { + error!("Chosen PDB file failed to load"); + return false; + } + } + } + + // See if we can get pdb info from the view + if let Some(info) = parse_pdb_info(view) { + // First, check _NT_SYMBOL_PATH + if let Ok(sym_path) = env::var("_NT_SYMBOL_PATH") { + let stores = if let Ok(default_cache) = active_local_cache(Some(view)) { + parse_sym_srv(&sym_path, &default_cache) + } else { + Err(anyhow!("No local cache found")) + }; + if let Ok(stores) = stores { + for store in stores { + match search_sym_store(&store, &info) { + Ok(Some(conts)) => { + match self + .load_from_file(&conts, debug_info, view, &progress, true, true) + { + Ok(_) => return true, + Err(e) if e.to_string() == "Cancelled" => return false, + Err(e) => debug!("Skipping, {}", e.to_string()), + } + } + Ok(None) => {} + e => error!("Error searching symbol store {}: {:?}", store, e), + } + } + } + } + + // Does the raw path just exist? + if PathBuf::from(&info.path).exists() { + match fs::read(&info.path) { + Ok(conts) => match self + .load_from_file(&conts, debug_info, view, &progress, true, false) + { + Ok(_) => return true, + Err(e) if e.to_string() == "Cancelled" => return false, + Err(e) => debug!("Skipping, {}", e.to_string()), + }, + Err(e) if e.to_string() == "Cancelled" => return false, + Err(e) => debug!("Could not read pdb: {}", e.to_string()), + } + } + + // Try in the same directory as the file + let mut potential_path = PathBuf::from(view.file().filename().to_string()); + potential_path.pop(); + potential_path.push(&info.file_name); + if potential_path.exists() { + match fs::read( + &potential_path + .to_str() + .expect("Potential path is a real string") + .to_string(), + ) { + Ok(conts) => match self + .load_from_file(&conts, debug_info, view, &progress, true, false) + { + Ok(_) => return true, + Err(e) if e.to_string() == "Cancelled" => return false, + Err(e) => debug!("Skipping, {}", e.to_string()), + }, + Err(e) if e.to_string() == "Cancelled" => return false, + Err(e) => debug!("Could not read pdb: {}", e.to_string()), + } + } + + // Check the local symbol store + if let Ok(local_store_path) = active_local_cache(Some(view)) { + match search_sym_store(&local_store_path, &info) { + Ok(Some(conts)) => { + match self.load_from_file(&conts, debug_info, view, &progress, true, false) + { + Ok(_) => return true, + Err(e) if e.to_string() == "Cancelled" => return false, + Err(e) => debug!("Skipping, {}", e.to_string()), + } + } + Ok(None) => {} + e => error!( + "Error searching local symbol store {}: {:?}", + local_store_path, e + ), + } + } + + // Next, try downloading from all symbol servers in the server list + let server_list = + Settings::new("").get_string_list("pdb.files.symbolServerList", Some(view), None); + + for server in server_list.iter() { + match search_sym_store(&server.to_string(), &info) { + Ok(Some(conts)) => { + match self.load_from_file(&conts, debug_info, view, &progress, true, true) { + Ok(_) => return true, + Err(e) if e.to_string() == "Cancelled" => return false, + Err(e) => debug!("Skipping, {}", e.to_string()), + } + } + Ok(None) => {} + e => error!("Error searching remote symbol server {}: {:?}", server, e), + } + } + } + false + } +} + +#[cfg(not(feature = "demo"))] +#[no_mangle] +pub extern "C" fn CorePluginDependencies() { + add_optional_plugin_dependency("view_pe"); +} + +#[cfg(not(feature = "demo"))] +#[no_mangle] +pub extern "C" fn CorePluginInit() -> bool { + init_plugin() +} + +#[cfg(feature = "demo")] +#[no_mangle] +pub extern "C" fn PDBPluginInit() -> bool { + init_plugin() +} + +fn init_plugin() -> bool { + let _ = logger::init(LevelFilter::Debug); + DebugInfoParser::register("PDB", PDBParser {}); + + let settings = Settings::new(""); + settings.register_group("pdb", "PDB Loader"); + settings.register_setting_json( + "pdb.files.localStoreAbsolute", + r#"{ + "title" : "Local Symbol Store Absolute Path", + "type" : "string", + "default" : "", + "aliases" : ["pdb.local-store-absolute", "pdb.localStoreAbsolute"], + "description" : "Absolute path specifying where the PDB symbol store exists on this machine, overrides relative path.", + "ignore" : [] + }"#, + ); + + settings.register_setting_json( + "pdb.files.localStoreRelative", + r#"{ + "title" : "Local Symbol Store Relative Path", + "type" : "string", + "default" : "symbols", + "aliases" : ["pdb.local-store-relative", "pdb.localStoreRelative"], + "description" : "Path *relative* to the binaryninja _user_ directory, specifying the pdb symbol store. If the Local Symbol Store Absolute Path is specified, this is ignored.", + "ignore" : [] + }"#, + ); + + settings.register_setting_json( + "pdb.files.localStoreCache", + r#"{ + "title" : "Cache Downloaded PDBs in Local Store", + "type" : "boolean", + "default" : true, + "aliases" : ["pdb.localStoreCache"], + "description" : "Store PDBs downloaded from Symbol Servers in the local Symbol Store Path.", + "ignore" : [] + }"#, + ); + + settings.register_setting_json( + "network.pdbAutoDownload", + r#"{ + "title" : "Enable Auto Downloading PDBs", + "type" : "boolean", + "default" : true, + "aliases" : ["pdb.autoDownload", "pdb.auto-download-pdb"], + "description" : "Automatically search for and download pdb files from specified symbol servers.", + "ignore" : [] + }"#, + ); + + settings.register_setting_json( + "pdb.files.symbolServerList", + r#"{ + "title" : "Symbol Server List", + "type" : "array", + "elementType" : "string", + "sorted" : false, + "default" : ["https://msdl.microsoft.com/download/symbols"], + "aliases" : ["pdb.symbol-server-list", "pdb.symbolServerList"], + "description" : "List of servers to query for pdb symbols.", + "ignore" : [] + }"#, + ); + + settings.register_setting_json( + "pdb.features.expandRTTIStructures", + r#"{ + "title" : "Expand RTTI Structures", + "type" : "boolean", + "default" : true, + "aliases" : ["pdb.expandRTTIStructures"], + "description" : "Create structures for RTTI symbols with variable-sized names and arrays.", + "ignore" : [] + }"#, + ); + + settings.register_setting_json( + "pdb.features.generateVTables", + r#"{ + "title" : "Generate Virtual Table Structures", + "type" : "boolean", + "default" : true, + "aliases" : ["pdb.generateVTables"], + "description" : "Create Virtual Table (VTable) structures for C++ classes found when parsing.", + "ignore" : [] + }"#, + ); + + settings.register_setting_json( + "pdb.features.loadGlobalSymbols", + r#"{ + "title" : "Load Global Module Symbols", + "type" : "boolean", + "default" : true, + "aliases" : ["pdb.loadGlobalSymbols"], + "description" : "Load symbols in the Global module of the PDB. These symbols have generally lower quality types due to relying on the demangler.", + "ignore" : [] + }"#, + ); + + settings.register_setting_json( + "pdb.features.allowUnnamedVoidSymbols", + r#"{ + "title" : "Allow Unnamed Untyped Symbols", + "type" : "boolean", + "default" : false, + "aliases" : ["pdb.allowUnnamedVoidSymbols"], + "description" : "Allow creation of symbols with no name and void types, often used as static local variables. Generally, these are just noisy and not relevant.", + "ignore" : [] + }"#, + ); + + settings.register_setting_json( + "pdb.features.allowVoidGlobals", + r#"{ + "title" : "Allow Untyped Symbols", + "type" : "boolean", + "default" : true, + "aliases" : ["pdb.allowVoidGlobals"], + "description" : "Allow creation of symbols that have no type, and will be created as void-typed symbols. Generally, this happens in a stripped PDB when a Global symbol's mangled name does not contain type information.", + "ignore" : [] + }"#, + ); + + settings.register_setting_json( + "pdb.features.createMissingNamedTypes", + r#"{ + "title" : "Create Missing Named Types", + "type" : "boolean", + "default" : true, + "aliases" : ["pdb.createMissingNamedTypes"], + "description" : "Allow creation of types named by function signatures which are not found in the PDB's types list or the Binary View. These types are usually found in stripped PDBs that have no type information but function signatures reference the stripped types.", + "ignore" : [] + }"#, + ); + + settings.register_setting_json( + "pdb.features.loadMismatchedPDB", + r#"{ + "title" : "Load Mismatched PDB", + "type" : "string", + "default" : "ask", + "enum" : ["true", "ask", "false"], + "enumDescriptions" : [ + "Always load the PDB", + "Use the Interaction system to ask if the PDB should be loaded", + "Never load the PDB" + ], + "aliases" : [], + "description" : "If a manually loaded PDB has a mismatched GUID, should it be loaded?", + "ignore" : [] + }"#, + ); + + settings.register_setting_json( + "pdb.features.parseSymbols", + r#"{ + "title" : "Parse PDB Symbols", + "type" : "boolean", + "default" : true, + "aliases" : [], + "description" : "Parse Symbol names and types. If you turn this off, you will only load Types.", + "ignore" : [] + }"#, + ); + + true +} + +#[test] +fn test_default_cache_path() { + println!("{:?}", default_local_cache()); +} + +#[test] +fn test_sym_srv() { + assert_eq!( + parse_sym_srv( + &r"srv*\\mybuilds\mysymbols".to_string(), + &r"DEFAULT_STORE".to_string() + ) + .expect("parse success") + .collect::>(), + vec![r"\\mybuilds\mysymbols".to_string()] + ); + assert_eq!( + parse_sym_srv( + &r"srv*c:\localsymbols*\\mybuilds\mysymbols".to_string(), + &r"DEFAULT_STORE".to_string() + ) + .expect("parse success") + .collect::>(), + vec![ + r"c:\localsymbols".to_string(), + r"\\mybuilds\mysymbols".to_string() + ] + ); + assert_eq!( + parse_sym_srv( + &r"srv**\\mybuilds\mysymbols".to_string(), + &r"DEFAULT_STORE".to_string() + ) + .expect("parse success") + .collect::>(), + vec![ + r"DEFAULT_STORE".to_string(), + r"\\mybuilds\mysymbols".to_string() + ] + ); + assert_eq!( + parse_sym_srv( + &r"srv*c:\localsymbols*\\NearbyServer\store*https://DistantServer".to_string(), + &r"DEFAULT_STORE".to_string() + ) + .expect("parse success") + .collect::>(), + vec![ + r"c:\localsymbols".to_string(), + r"\\NearbyServer\store".to_string(), + r"https://DistantServer".to_string() + ] + ); + assert_eq!( + parse_sym_srv( + &r"srv*c:\DownstreamStore*https://msdl.microsoft.com/download/symbols".to_string(), + &r"DEFAULT_STORE".to_string() + ) + .expect("parse success") + .collect::>(), + vec![ + r"c:\DownstreamStore".to_string(), + r"https://msdl.microsoft.com/download/symbols".to_string() + ] + ); +} diff --git a/examples/pdb-ng/src/parser.rs b/examples/pdb-ng/src/parser.rs new file mode 100644 index 0000000..2d56a76 --- /dev/null +++ b/examples/pdb-ng/src/parser.rs @@ -0,0 +1,508 @@ +// Copyright 2022-2024 Vector 35 Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +use std::collections::{BTreeMap, HashMap, HashSet}; +use std::env; +use std::fmt::Display; +use std::sync::OnceLock; + +use anyhow::{anyhow, Result}; +use log::{debug, info}; +use pdb::*; + +use binaryninja::architecture::{Architecture, CoreArchitecture}; +use binaryninja::binaryview::{BinaryView, BinaryViewExt}; +use binaryninja::callingconvention::CallingConvention; +use binaryninja::debuginfo::{DebugFunctionInfo, DebugInfo}; +use binaryninja::platform::Platform; +use binaryninja::rc::Ref; +use binaryninja::settings::Settings; +use binaryninja::types::{ + min_confidence, Conf, DataVariableAndName, EnumerationBuilder, NamedTypeReference, + NamedTypeReferenceClass, StructureBuilder, StructureType, Type, TypeClass, +}; + +use crate::symbol_parser::{ParsedDataSymbol, ParsedProcedure, ParsedSymbol}; +use crate::type_parser::ParsedType; + +/// Megastruct for all the parsing +/// Certain fields are only used by specific files, as marked below. +/// Why not make new structs for them? Because vvvv this garbage +pub struct PDBParserInstance<'a, S: Source<'a> + 'a> { + /// DebugInfo where types/functions will be stored eventually + pub(crate) debug_info: &'a mut DebugInfo, + /// Parent binary view (usually during BinaryView::Finalize) + pub(crate) bv: &'a BinaryView, + /// Default arch of self.bv + pub(crate) arch: CoreArchitecture, + /// Default calling convention for self.arch + pub(crate) default_cc: Ref>, + /// Thiscall calling convention for self.bv, or default_cc if we can't find one + pub(crate) thiscall_cc: Ref>, + /// Cdecl calling convention for self.bv, or default_cc if we can't find one + pub(crate) cdecl_cc: Ref>, + /// Default platform of self.bv + pub(crate) platform: Ref, + /// pdb-rs structure for making lifetime hell a real place + pub(crate) pdb: PDB<'a, S>, + /// pdb-rs Mapping of modules to addresses for resolving RVAs + pub(crate) address_map: AddressMap<'a>, + /// Binja Settings instance (for optimization) + pub(crate) settings: Ref, + + /// type_parser.rs + + /// TypeIndex -> ParsedType enum used during parsing + pub(crate) indexed_types: BTreeMap, + /// QName -> Binja Type for finished types + pub(crate) named_types: BTreeMap>, + /// Raw (mangled) name -> TypeIndex for resolving forward references + pub(crate) full_type_indices: BTreeMap, + /// Stack of types we're currently parsing + pub(crate) type_stack: Vec, + /// Stack of parent types we're parsing nested types inside of + pub(crate) namespace_stack: Vec, + /// Type Index -> Does it return on the stack + pub(crate) type_default_returnable: BTreeMap, + + /// symbol_parser.rs + + /// List of fully parsed symbols from all modules + pub(crate) parsed_symbols: Vec, + /// Raw name -> index in parsed_symbols + pub(crate) parsed_symbols_by_name: BTreeMap, + /// Raw name -> Symbol index for looking up symbols for the currently parsing module (mostly for thunks) + pub(crate) named_symbols: BTreeMap, + /// Parent -> Children symbol index tree for the currently parsing module + pub(crate) symbol_tree: BTreeMap>, + /// Child -> Parent symbol index mapping, inverse of symbol_tree + pub(crate) symbol_parents: BTreeMap, + /// Stack of (start, end) indices for the current symbols being parsed while constructing the tree + pub(crate) symbol_stack: Vec<(SymbolIndex, SymbolIndex)>, + /// Index -> parsed symbol for the currently parsing module + pub(crate) indexed_symbols: BTreeMap, + /// Symbol address -> Symbol for looking up by address + pub(crate) addressed_symbols: BTreeMap>, + /// CPU type of the currently parsing module + pub(crate) module_cpu_type: Option, +} + +impl<'a, S: Source<'a> + 'a> PDBParserInstance<'a, S> { + /// Try to create a new parser instance from a given bv/pdb + pub fn new( + debug_info: &'a mut DebugInfo, + bv: &'a BinaryView, + mut pdb: PDB<'a, S>, + ) -> Result { + let arch = if let Some(arch) = bv.default_arch() { + arch + } else { + return Err(anyhow!("Cannot parse to view with no architecture")); + }; + + let platform = bv + .default_platform() + .expect("Expected bv to have a platform"); + + let address_map = pdb.address_map()?; + + let default_cc = platform + .get_default_calling_convention() + .expect("Expected default calling convention"); + + let thiscall_cc = Self::find_calling_convention(platform.as_ref(), "thiscall") + .unwrap_or(default_cc.clone()); + + let cdecl_cc = platform + .get_cdecl_calling_convention() + .unwrap_or(default_cc.clone()); + + Ok(Self { + debug_info, + bv, + arch, + default_cc, + thiscall_cc, + cdecl_cc, + platform, + pdb, + address_map, + settings: Settings::new(""), + indexed_types: Default::default(), + named_types: Default::default(), + full_type_indices: Default::default(), + type_stack: Default::default(), + namespace_stack: Default::default(), + type_default_returnable: Default::default(), + parsed_symbols: Default::default(), + parsed_symbols_by_name: Default::default(), + named_symbols: Default::default(), + symbol_tree: Default::default(), + symbol_parents: Default::default(), + symbol_stack: Default::default(), + indexed_symbols: Default::default(), + addressed_symbols: Default::default(), + module_cpu_type: None, + }) + } + + /// Try to parse the pdb into the DebugInfo + pub fn try_parse_info( + &mut self, + progress: Box Result<()> + 'a>, + ) -> Result<()> { + self.parse_types(Self::split_progress(&progress, 0, &[1.0, 3.0, 0.5, 0.5]))?; + for (name, ty) in self.named_types.iter() { + self.debug_info.add_type(name, ty.as_ref(), &[]); // TODO : Components + } + + info!( + "PDB found {} types (before resolving NTRs)", + self.named_types.len() + ); + + if self + .settings + .get_bool("pdb.features.parseSymbols", Some(self.bv), None) + { + let (symbols, functions) = + self.parse_symbols(Self::split_progress(&progress, 1, &[1.0, 3.0, 0.5, 0.5]))?; + + if self + .settings + .get_bool("pdb.features.createMissingNamedTypes", Some(self.bv), None) + { + self.resolve_missing_ntrs( + &symbols, + Self::split_progress(&progress, 2, &[1.0, 3.0, 0.5, 0.5]), + )?; + self.resolve_missing_ntrs( + &functions, + Self::split_progress(&progress, 3, &[1.0, 3.0, 0.5, 0.5]), + )?; + } + + info!("PDB found {} types", self.named_types.len()); + info!("PDB found {} data variables", symbols.len()); + info!("PDB found {} functions", functions.len()); + + let allow_void = + self.settings + .get_bool("pdb.features.allowVoidGlobals", Some(self.bv), None); + + let min_confidence_type = Conf::new(Type::void(), min_confidence()); + for sym in symbols.iter() { + match sym { + ParsedSymbol::Data(ParsedDataSymbol { + address, + name, + type_, + .. + }) => { + let real_type = + type_.as_ref().unwrap_or(&min_confidence_type); + + if real_type.contents.type_class() == TypeClass::VoidTypeClass { + if !allow_void { + self.log(|| { + format!("Not adding void-typed symbol {:?}@{:x}", name, address) + }); + continue; + } + } + + self.log(|| { + format!( + "Adding data variable: 0x{:x}: {} {:?}", + address, &name.raw_name, real_type + ) + }); + self.debug_info + .add_data_variable_info(DataVariableAndName::new( + *address, + real_type.clone(), + true, + name.full_name.as_ref().unwrap_or(&name.raw_name), + )); + } + s => { + self.log(|| format!("Not adding non-data symbol {:?}", s)); + } + } + } + + for sym in functions { + match sym { + ParsedSymbol::Procedure(ParsedProcedure { + address, + name, + type_, + locals: _, + .. + }) => { + self.log(|| { + format!( + "Adding function: 0x{:x}: {} {:?}", + address, &name.raw_name, type_ + ) + }); + self.debug_info.add_function(DebugFunctionInfo::new( + Some(name.short_name.unwrap_or(name.raw_name.clone())), + Some(name.full_name.unwrap_or(name.raw_name.clone())), + Some(name.raw_name), + type_.clone().and_then(|conf| { + // TODO: When DebugInfo support confidence on function types, remove this + if conf.confidence == 0 { + None + } else { + Some(conf.contents) + } + }), + Some(address), + Some(self.platform.clone()), + vec![], // TODO : Components + vec![], //TODO: local variables + )); + } + _ => {} + } + } + } + + Ok(()) + } + + fn collect_name( + &self, + name: &NamedTypeReference, + unknown_names: &mut HashMap, + ) { + let used_name = name.name().to_string(); + if let Some(&found) = + unknown_names.get(&used_name) + { + if found != name.class() { + // Interesting case, not sure we care + self.log(|| { + format!( + "Mismatch unknown NTR class for {}: {} ?", + &used_name, + name.class() as u32 + ) + }); + } + } else { + self.log(|| format!("Found new unused name: {}", &used_name)); + unknown_names.insert(used_name, name.class()); + } + } + + fn collect_names( + &self, + ty: &Type, + unknown_names: &mut HashMap, + ) { + match ty.type_class() { + TypeClass::StructureTypeClass => { + if let Ok(structure) = ty.get_structure() { + if let Ok(members) = structure.members() { + for member in members { + self.collect_names(member.ty.contents.as_ref(), unknown_names); + } + } + if let Ok(bases) = structure.base_structures() { + for base in bases { + self.collect_name(base.ty.as_ref(), unknown_names); + } + } + } + } + TypeClass::PointerTypeClass => { + if let Ok(target) = ty.target() { + self.collect_names(target.contents.as_ref(), unknown_names); + } + } + TypeClass::ArrayTypeClass => { + if let Ok(element_type) = ty.element_type() { + self.collect_names(element_type.contents.as_ref(), unknown_names); + } + } + TypeClass::FunctionTypeClass => { + if let Ok(return_value) = ty.return_value() { + self.collect_names(return_value.contents.as_ref(), unknown_names); + } + if let Ok(params) = ty.parameters() { + for param in params { + self.collect_names(param.t.contents.as_ref(), unknown_names); + } + } + } + TypeClass::NamedTypeReferenceClass => { + if let Ok(ntr) = ty.get_named_type_reference() { + self.collect_name(ntr.as_ref(), unknown_names); + } + } + _ => {} + } + } + + fn resolve_missing_ntrs( + &mut self, + symbols: &Vec, + progress: Box Result<()> + '_>, + ) -> Result<()> { + let mut unknown_names = HashMap::new(); + let mut known_names = self + .bv + .types() + .iter() + .map(|qnat| qnat.name().string()) + .collect::>(); + + for ty in &self.named_types { + known_names.insert(ty.0.clone()); + } + + let count = symbols.len(); + for (i, sym) in symbols.into_iter().enumerate() { + match sym { + ParsedSymbol::Data(ParsedDataSymbol { + type_: Some(type_), .. + }) => { + self.collect_names(type_.contents.as_ref(), &mut unknown_names); + } + ParsedSymbol::Procedure(ParsedProcedure { + type_: Some(type_), + locals, + .. + }) => { + self.collect_names(type_.contents.as_ref(), &mut unknown_names); + for l in locals { + if let Some(ltype) = &l.type_ { + self.collect_names(ltype.contents.as_ref(), &mut unknown_names); + } + } + } + _ => {} + } + (progress)(i, count)?; + } + + for (name, class) in unknown_names.into_iter() { + if known_names.contains(&name) { + self.log(|| format!("Found referenced name and ignoring: {}", &name)); + continue; + } + self.log(|| format!("Adding referenced but unknown type {} (likely due to demangled name and stripped type)", &name)); + match class { + NamedTypeReferenceClass::UnknownNamedTypeClass + | NamedTypeReferenceClass::TypedefNamedTypeClass => { + self.debug_info.add_type(name, Type::void().as_ref(), &[]); // TODO : Components + } + NamedTypeReferenceClass::ClassNamedTypeClass + | NamedTypeReferenceClass::StructNamedTypeClass + | NamedTypeReferenceClass::UnionNamedTypeClass => { + let structure = StructureBuilder::new(); + match class { + NamedTypeReferenceClass::ClassNamedTypeClass => { + structure.set_structure_type(StructureType::ClassStructureType); + } + NamedTypeReferenceClass::StructNamedTypeClass => { + structure.set_structure_type(StructureType::StructStructureType); + } + NamedTypeReferenceClass::UnionNamedTypeClass => { + structure.set_structure_type(StructureType::UnionStructureType); + } + _ => {} + } + structure.set_width(1); + structure.set_alignment(1); + + self.debug_info.add_type( + name, + Type::structure(structure.finalize().as_ref()).as_ref(), + &[], // TODO : Components + ); + } + NamedTypeReferenceClass::EnumNamedTypeClass => { + let enumeration = EnumerationBuilder::new(); + self.debug_info.add_type( + name, + Type::enumeration( + enumeration.finalize().as_ref(), + self.arch.default_integer_size(), + false, + ) + .as_ref(), + &[], // TODO : Components + ); + } + } + } + + Ok(()) + } + + /// Lazy logging function that prints like 20MB of messages + pub(crate) fn log D, D: Display>(&self, msg: F) { + static MEM: OnceLock = OnceLock::new(); + let debug_pdb = MEM.get_or_init(|| { + env::var("BN_DEBUG_PDB").is_ok() + }); + if *debug_pdb { + let space = "\t".repeat(self.type_stack.len()) + &"\t".repeat(self.symbol_stack.len()); + let msg = format!("{}", msg()); + debug!( + "{}{}", + space, + msg.replace("\n", &*("\n".to_string() + &space)) + ); + } + } + + pub(crate) fn split_progress<'b, F: Fn(usize, usize) -> Result<()> + 'b>( + original_fn: F, + subpart: usize, + subpart_weights: &[f64], + ) -> Box Result<()> + 'b> { + // Normalize weights + let weight_sum: f64 = subpart_weights.iter().sum(); + if weight_sum < 0.0001 { + return Box::new(|_, _| Ok(())); + } + + // Keep a running count of weights for the start + let mut subpart_starts = vec![]; + let mut start = 0f64; + for w in subpart_weights { + subpart_starts.push(start); + start += *w; + } + + let subpart_start = subpart_starts[subpart] / weight_sum; + let weight = subpart_weights[subpart] / weight_sum; + + Box::new(move |cur: usize, max: usize| { + // Just use a large number for easy divisibility + let steps = 1000000f64; + let subpart_size = steps * weight; + let subpart_progress = ((cur as f64) / (max as f64)) * subpart_size; + + original_fn( + (subpart_start * steps + subpart_progress) as usize, + steps as usize, + ) + }) + } +} diff --git a/examples/pdb-ng/src/struct_grouper.rs b/examples/pdb-ng/src/struct_grouper.rs new file mode 100644 index 0000000..042eb97 --- /dev/null +++ b/examples/pdb-ng/src/struct_grouper.rs @@ -0,0 +1,1164 @@ +// Copyright 2022-2024 Vector 35 Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +use std::cmp::Ordering; +use std::env; +use std::fmt::{Debug, Display, Formatter}; + +use anyhow::{anyhow, Result}; +use log::{debug, warn}; + +use binaryninja::types::{ + max_confidence, Conf, MemberAccess, MemberScope, StructureBuilder, StructureType, Type, +}; + +use crate::type_parser::ParsedMember; + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +struct MemberSize { + index: usize, + offset: u64, + width: u64, +} + +#[derive(Debug, Clone, PartialEq, Eq)] +enum ResolvedGroup { + Single(usize), + Struct(u64, Vec), + Union(u64, Vec), +} + +#[derive(Clone, PartialEq, Eq)] +struct WorkingStruct { + index: Option, + offset: u64, + width: u64, + is_union: bool, + children: Vec, +} + +impl PartialOrd for WorkingStruct { + fn partial_cmp(&self, other: &Self) -> Option { + if self.end() < other.start() { + Some(Ordering::Less) + } else if other.end() < self.start() { + Some(Ordering::Greater) + } else if self.is_same(other) { + Some(Ordering::Equal) + } else { + None + } + } +} + +impl Debug for WorkingStruct { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + if self.children.len() == 0 { + write!(f, "{:X} -> {:X}", self.start(), self.end())?; + if let Some(index) = self.index { + write!(f, " (#{:X})", index)?; + } else { + write!(f, " without index???")?; + } + Ok(()) + } else if self.is_union { + write!(f, "union {:X} -> {:X} ", self.start(), self.end())?; + if let Some(index) = self.index { + write!(f, "with index {:X} ??? ", index)?; + } + f.debug_list().entries(self.children.iter()).finish() + } else { + write!(f, "struct {:X} -> {:X} ", self.start(), self.end())?; + if let Some(index) = self.index { + write!(f, "with index {:X} ??? ", index)?; + } + f.debug_list().entries(self.children.iter()).finish() + } + } +} + +impl WorkingStruct { + pub fn start(&self) -> u64 { + self.offset + } + + pub fn end(&self) -> u64 { + self.offset + self.width + } + + pub fn extend_to(&mut self, new_end: u64) { + if new_end > self.end() { + self.width = new_end - self.offset; + } + } + + // pub fn overlaps(&self, other: &WorkingStruct) -> bool { + // // If A starts after B ends + // if self.start() >= other.end() { + // return false; + // } + // // Or if B starts after A ends + // if other.start() >= self.end() { + // return false; + // } + // // Otherwise, one of the items starts before the other ends, so there is overlap + // return true; + // } + + // pub fn contains(&self, other: &WorkingStruct) -> bool { + // // If other is fully contained within self + // self.start() <= other.start() && self.end() >= other.end() + // } + + pub fn is_same(&self, other: &WorkingStruct) -> bool { + // If self and other have the same range + self.start() == other.start() && self.end() == other.end() + } + + pub fn insert(&mut self, other: WorkingStruct, recursion: usize) -> Result<()> { + log(|| { + format!("{}self: {:#?}", " ".repeat(recursion), self) + .replace("\n", &*("\n".to_owned() + &" ".repeat(recursion))) + }); + log(|| { + format!("{}other: {:#?}", " ".repeat(recursion), other) + .replace("\n", &*("\n".to_owned() + &" ".repeat(recursion))) + }); + + self.extend_to(other.end()); + + // There are 2 cases we have to deal with here: + // a. `other` starts after the end of the last group => insert `other` into the last group + // b. `other` starts before the end of the last group => collect all the children inserted after it starts and put them into a struct + // start a new struct with `other` + + if self.children.len() == 0 { + self.children.push(other); + return Ok(()); + } + + // This is really gross. + // But also I need to ship this before I leave for France + // TODO: Clean this up + + if other.start() + >= self + .children + .last() + .ok_or_else(|| anyhow!("Expected we have children #A"))? + .end() + { + self.children.push(other); + } else { + // Create a structure with fields from self.children + if self + .children + .last() + .ok_or_else(|| anyhow!("Expected we have children #B"))? + .index + .is_none() + && self + .children + .last() + .ok_or_else(|| anyhow!("Expected we have children #C"))? + .start() + < other.start() + { + self.children + .last_mut() + .ok_or_else(|| anyhow!("Expected we have children #D"))? + .insert(other, recursion + 1)?; + return Ok(()); + } + + // If we're a union, we don't have to bother pushing a struct+union combo + if self.is_union { + self.children.push(WorkingStruct { + index: None, + offset: self.offset, + width: self.width, + is_union: false, + children: vec![other], + }); + return Ok(()); + } + + let mut start_index = None; + for (i, child) in self.children.iter().enumerate() { + if child.start() >= other.start() { + start_index = Some(i); + break; + } + } + if start_index.is_none() { + return Err(anyhow!( + "Struct has overlapping member that cannot be resolved: {:#?}", + other + )); + } + + let struct_start = self.children + [start_index.ok_or_else(|| anyhow!("Expected we have start index"))?] + .offset; + let struct_end = self + .children + .last() + .ok_or_else(|| anyhow!("Expected we have start index"))? + .end() + .max(other.end()); + + let struct_children = self + .children + .drain(start_index.ok_or_else(|| anyhow!("Expected we have start index"))?..) + .collect::>(); + self.children.push(WorkingStruct { + index: None, + offset: struct_start, + width: struct_end - struct_start, + is_union: true, + children: vec![ + WorkingStruct { + index: None, + offset: struct_start, + width: struct_end - struct_start, + is_union: false, + children: struct_children, + }, + WorkingStruct { + index: None, + offset: struct_start, + width: struct_end - struct_start, + is_union: false, + children: vec![other], + }, + ], + }); + + // union { + // struct { + // int data0; + // int[2] data4; + // int dataC; + // }; + // struct { + // int newdata0; + // ... + // }; + // }; + } + + // if other.start() < self.children[-1].end() { + // take children from other.start() until -1 and put them into a struct + // } + // else { + // add to self.children[-1], extend to fill + // } + + Ok(()) + } + + pub fn to_resolved(mut self) -> ResolvedGroup { + if let Some(index) = self.index { + ResolvedGroup::Single(index) + } else if self.is_union { + if self.children.len() == 1 { + self.children.remove(0).to_resolved() + } else { + // Collapse union of unions + ResolvedGroup::Union( + self.offset, + self.children + .into_iter() + .flat_map(|child| match child.to_resolved() { + ResolvedGroup::Union(offset, children) if offset == self.offset => { + children + } + s => vec![s], + }) + .collect(), + ) + } + } else { + if self.children.len() == 1 { + self.children.remove(0).to_resolved() + } else { + ResolvedGroup::Struct( + self.offset, + self.children + .into_iter() + .map(|child| child.to_resolved()) + .collect(), + ) + } + } + } +} + +pub fn group_structure( + name: &String, + members: &Vec, + structure: &mut StructureBuilder, +) -> Result<()> { + // SO + // PDBs handle trivial unions inside structures by just slamming all the fields together into + // one big overlappy happy family. We need to reverse this and create out union structures + // to properly represent the original source. + + // IN VISUAL FORM (if you are a visual person, like me): + // struct { + // int foos[2]; + // __offset(0): + // int foo1; + // int foo2; + // int bar; + // } + // + // Into + // + // struct { + // union { + // int foos[2]; + // struct { + // int foo1; + // int foo2; + // } + // } + // int bar; + // } + + // Into internal rep + let reps = members + .iter() + .enumerate() + .map(|(i, member)| MemberSize { + index: i, + offset: member.offset, + width: member.ty.contents.width(), + }) + .collect::>(); + + log(|| format!("{} {:#x?}", name, members)); + log(|| format!("{} {:#x?}", name, reps)); + + // Group them + match resolve_struct_groups(reps) { + Ok(groups) => { + log(|| format!("{} {:#x?}", name, groups)); + + // Apply grouped members + apply_groups(members, structure, groups, 0); + } + Err(e) => { + warn!("{} Could not resolve structure groups: {}", name, e); + for member in members { + structure.insert( + &member.ty.clone(), + member.name.clone(), + member.offset, + false, + member.access, + member.scope, + ); + } + } + } + + Ok(()) +} + +fn apply_groups( + members: &Vec, + structure: &mut StructureBuilder, + groups: Vec, + offset: u64, +) { + for (i, group) in groups.into_iter().enumerate() { + match group { + ResolvedGroup::Single(index) => { + let member = &members[index]; + + // TODO : Fix inner-offset being larger than `member.offset` + + if offset > member.offset { + structure.insert( + &member.ty.clone(), + member.name.clone(), + 0, + false, + member.access, + member.scope, + ); + } else { + structure.insert( + &member.ty.clone(), + member.name.clone(), + member.offset - offset, + false, + member.access, + member.scope, + ); + } + } + ResolvedGroup::Struct(inner_offset, children) => { + let mut inner = StructureBuilder::new(); + apply_groups(members, &mut inner, children, inner_offset); + structure.insert( + &Conf::new(Type::structure(inner.finalize().as_ref()), max_confidence()), + format!("__inner{}", i), + inner_offset - offset, + false, + MemberAccess::PublicAccess, + MemberScope::NoScope, + ); + } + ResolvedGroup::Union(inner_offset, children) => { + let mut inner = StructureBuilder::new(); + inner.set_structure_type(StructureType::UnionStructureType); + apply_groups(members, &mut inner, children, inner_offset); + structure.insert( + &Conf::new(Type::structure(inner.finalize().as_ref()), max_confidence()), + format!("__inner{}", i), + inner_offset - offset, + false, + MemberAccess::PublicAccess, + MemberScope::NoScope, + ); + } + } + } +} + +fn resolve_struct_groups(members: Vec) -> Result> { + // See if we care + let mut has_overlapping = false; + let mut last_end = 0; + let mut max_width = 0; + for member in &members { + if member.offset < last_end { + has_overlapping = true; + } + last_end = member.offset + member.width; + max_width = max_width.max(member.offset + member.width); + } + + if !has_overlapping { + // Nothing overlaps, just add em directly + return Ok(members + .into_iter() + .map(|member| ResolvedGroup::Single(member.index)) + .collect()); + } + + // Yes overlapping + + let mut groups = WorkingStruct { + index: None, + offset: 0, + width: max_width, + is_union: false, + children: vec![], + }; + for &member in &members { + let member_group = WorkingStruct { + index: Some(member.index), + offset: member.offset, + width: member.width, + is_union: false, + children: vec![], + }; + groups.insert(member_group, 0)?; + + log(|| format!("GROUPS: {:#x?}", groups)); + } + + Ok(groups + .children + .into_iter() + .map(|child| child.to_resolved()) + .collect()) +} + +#[test] +fn test_trivial() { + assert_eq!( + resolve_struct_groups(vec![ + MemberSize { + index: 0, + offset: 0, + width: 1, + }, + MemberSize { + index: 1, + offset: 1, + width: 1, + }, + MemberSize { + index: 2, + offset: 2, + width: 1, + }, + MemberSize { + index: 3, + offset: 3, + width: 1, + }, + ]) + .unwrap(), + vec![ + ResolvedGroup::Single(0), + ResolvedGroup::Single(1), + ResolvedGroup::Single(2), + ResolvedGroup::Single(3), + ] + ); +} + +#[test] +fn test_everything_everywhere() { + assert_eq!( + resolve_struct_groups(vec![ + MemberSize { + index: 0, + offset: 0, + width: 1, + }, + MemberSize { + index: 1, + offset: 0, + width: 1, + }, + MemberSize { + index: 2, + offset: 0, + width: 1, + }, + MemberSize { + index: 3, + offset: 0, + width: 1, + }, + ]) + .unwrap(), + vec![ResolvedGroup::Union( + 0, + vec![ + ResolvedGroup::Single(0), + ResolvedGroup::Single(1), + ResolvedGroup::Single(2), + ResolvedGroup::Single(3), + ] + )] + ); +} + +#[test] +fn test_unalignend() { + assert_eq!( + resolve_struct_groups(vec![ + MemberSize { + index: 0, + offset: 0, + width: 4, + }, + MemberSize { + index: 1, + offset: 4, + width: 8, + }, + MemberSize { + index: 2, + offset: 12, + width: 4, + }, + MemberSize { + index: 3, + offset: 0, + width: 8, + }, + MemberSize { + index: 4, + offset: 8, + width: 8, + }, + ]) + .unwrap(), + vec![ResolvedGroup::Union( + 0, + vec![ + ResolvedGroup::Struct( + 0, + vec![ + ResolvedGroup::Single(0), + ResolvedGroup::Single(1), + ResolvedGroup::Single(2), + ] + ), + ResolvedGroup::Struct(0, vec![ResolvedGroup::Single(3), ResolvedGroup::Single(4),]), + ] + )] + ); +} + +#[test] +fn test_heap_vs_chunk_free_header() { + assert_eq!( + resolve_struct_groups(vec![ + MemberSize { + index: 0, + offset: 0, + width: 16, + }, + MemberSize { + index: 1, + offset: 0, + width: 8, + }, + MemberSize { + index: 2, + offset: 8, + width: 24, + }, + ]) + .unwrap(), + vec![ResolvedGroup::Union( + 0, + vec![ + ResolvedGroup::Single(0), + ResolvedGroup::Struct(0, vec![ResolvedGroup::Single(1), ResolvedGroup::Single(2)]) + ] + )] + ); +} + +#[test] +fn test_kprcb() { + assert_eq!( + resolve_struct_groups(vec![ + MemberSize { + index: 0, + offset: 0, + width: 8, + }, + MemberSize { + index: 1, + offset: 8, + width: 1, + }, + MemberSize { + index: 2, + offset: 8, + width: 1, + }, + MemberSize { + index: 3, + offset: 9, + width: 1, + }, + MemberSize { + index: 4, + offset: 9, + width: 1, + }, + MemberSize { + index: 5, + offset: 10, + width: 1, + }, + MemberSize { + index: 6, + offset: 11, + width: 1, + }, + MemberSize { + index: 7, + offset: 12, + width: 1, + }, + MemberSize { + index: 8, + offset: 13, + width: 1, + }, + MemberSize { + index: 9, + offset: 14, + width: 2, + }, + MemberSize { + index: 10, + offset: 0, + width: 16, + }, + MemberSize { + index: 11, + offset: 16, + width: 1, + }, + MemberSize { + index: 12, + offset: 17, + width: 1, + }, + MemberSize { + index: 13, + offset: 18, + width: 1, + }, + MemberSize { + index: 14, + offset: 18, + width: 1, + }, + MemberSize { + index: 15, + offset: 19, + width: 1, + }, + MemberSize { + index: 16, + offset: 19, + width: 1, + }, + MemberSize { + index: 17, + offset: 20, + width: 4, + }, + MemberSize { + index: 18, + offset: 16, + width: 8, + }, + ]) + .unwrap(), + vec![ + ResolvedGroup::Union( + 0, + vec![ + ResolvedGroup::Struct( + 0, + vec![ + ResolvedGroup::Single(0), + ResolvedGroup::Union( + 8, + vec![ResolvedGroup::Single(1), ResolvedGroup::Single(2),] + ), + ResolvedGroup::Union( + 9, + vec![ResolvedGroup::Single(3), ResolvedGroup::Single(4),] + ), + ResolvedGroup::Single(5), + ResolvedGroup::Single(6), + ResolvedGroup::Single(7), + ResolvedGroup::Single(8), + ResolvedGroup::Single(9) + ] + ), + ResolvedGroup::Single(10) + ] + ), + ResolvedGroup::Union( + 16, + vec![ + ResolvedGroup::Struct( + 16, + vec![ + ResolvedGroup::Single(11), + ResolvedGroup::Single(12), + ResolvedGroup::Union( + 18, + vec![ResolvedGroup::Single(13), ResolvedGroup::Single(14),] + ), + ResolvedGroup::Union( + 19, + vec![ResolvedGroup::Single(15), ResolvedGroup::Single(16),] + ), + ResolvedGroup::Single(17) + ] + ), + ResolvedGroup::Single(18) + ] + ) + ] + ); +} + +#[test] +fn test_dispatcher_header() { + /* + XXX: This returns a different grouping which is still valid + Basically it turns this: + struct { + unsigned char data0; + union { + unsigned char data1; + struct { + unsigned char data1_2; + unsigned char data2; + unsigned char data3; + }; + }; + }; + + into this: + + struct { + unsigned char data0; + union { + unsigned char data1; + unsigned char data1_2; + }; + unsigned char data2; + unsigned char data3; + }; + */ + + assert_eq!( + resolve_struct_groups(vec![ + MemberSize { + index: 0x0, + offset: 0x0, + width: 0x4, + }, + MemberSize { + index: 0x1, + offset: 0x0, + width: 0x4, + }, + MemberSize { + index: 0x2, + offset: 0x0, + width: 0x1, + }, + MemberSize { + index: 0x3, + offset: 0x1, + width: 0x1, + }, + MemberSize { + index: 0x4, + offset: 0x2, + width: 0x1, + }, + MemberSize { + index: 0x5, + offset: 0x3, + width: 0x1, + }, + MemberSize { + index: 0x6, + offset: 0x0, + width: 0x1, + }, + MemberSize { + index: 0x7, + offset: 0x1, + width: 0x1, + }, + MemberSize { + index: 0x8, + offset: 0x1, + width: 0x1, + }, + MemberSize { + index: 0x9, + offset: 0x2, + width: 0x1, + }, + MemberSize { + index: 0xa, + offset: 0x3, + width: 0x1, + }, + MemberSize { + index: 0xb, + offset: 0x3, + width: 0x1, + }, + MemberSize { + index: 0xc, + offset: 0x0, + width: 0x1, + }, + MemberSize { + index: 0xd, + offset: 0x1, + width: 0x1, + }, + MemberSize { + index: 0xe, + offset: 0x1, + width: 0x1, + }, + MemberSize { + index: 0xf, + offset: 0x2, + width: 0x1, + }, + MemberSize { + index: 0x10, + offset: 0x3, + width: 0x1, + }, + MemberSize { + index: 0x11, + offset: 0x0, + width: 0x1, + }, + MemberSize { + index: 0x12, + offset: 0x1, + width: 0x1, + }, + MemberSize { + index: 0x13, + offset: 0x1, + width: 0x1, + }, + MemberSize { + index: 0x14, + offset: 0x2, + width: 0x1, + }, + MemberSize { + index: 0x15, + offset: 0x3, + width: 0x1, + }, + MemberSize { + index: 0x16, + offset: 0x0, + width: 0x1, + }, + MemberSize { + index: 0x17, + offset: 0x1, + width: 0x1, + }, + MemberSize { + index: 0x18, + offset: 0x2, + width: 0x1, + }, + MemberSize { + index: 0x19, + offset: 0x2, + width: 0x1, + }, + MemberSize { + index: 0x1a, + offset: 0x3, + width: 0x1, + }, + MemberSize { + index: 0x1b, + offset: 0x3, + width: 0x1, + }, + MemberSize { + index: 0x1c, + offset: 0x0, + width: 0x1, + }, + MemberSize { + index: 0x1d, + offset: 0x1, + width: 0x1, + }, + MemberSize { + index: 0x1e, + offset: 0x2, + width: 0x1, + }, + MemberSize { + index: 0x1f, + offset: 0x3, + width: 0x1, + }, + MemberSize { + index: 0x20, + offset: 0x4, + width: 0x4, + }, + MemberSize { + index: 0x21, + offset: 0x8, + width: 0x10, + }, + ]) + .unwrap(), + vec![ + ResolvedGroup::Union( + 0, + vec![ + ResolvedGroup::Single(0x0), + ResolvedGroup::Single(0x1), + ResolvedGroup::Struct( + 0, + vec![ + ResolvedGroup::Single(0x2), + ResolvedGroup::Single(0x3), + ResolvedGroup::Single(0x4), + ResolvedGroup::Single(0x5), + ] + ), + ResolvedGroup::Struct( + 0, + vec![ + ResolvedGroup::Single(0x6), + ResolvedGroup::Union( + 1, + vec![ + ResolvedGroup::Single(0x7), + ResolvedGroup::Struct( + 1, + vec![ + ResolvedGroup::Single(0x8), + ResolvedGroup::Single(0x9), + ResolvedGroup::Union( + 3, + vec![ + ResolvedGroup::Single(0xa), + ResolvedGroup::Single(0xb), + ] + ), + ] + ), + ] + ), + ] + ), + ResolvedGroup::Struct( + 0, + vec![ + ResolvedGroup::Single(0xc), + ResolvedGroup::Union( + 1, + vec![ + ResolvedGroup::Single(0xd), + ResolvedGroup::Struct( + 1, + vec![ + ResolvedGroup::Single(0xe), + ResolvedGroup::Single(0xf), + ResolvedGroup::Single(0x10), + ] + ) + ] + ), + ] + ), + ResolvedGroup::Struct( + 0, + vec![ + ResolvedGroup::Single(0x11), + ResolvedGroup::Union( + 1, + vec![ + ResolvedGroup::Single(0x12), + ResolvedGroup::Struct( + 1, + vec![ + ResolvedGroup::Single(0x13), + ResolvedGroup::Single(0x14), + ResolvedGroup::Single(0x15), + ] + ) + ] + ), + ] + ), + ResolvedGroup::Struct( + 0, + vec![ + ResolvedGroup::Single(0x16), + ResolvedGroup::Single(0x17), + ResolvedGroup::Union( + 2, + vec![ + ResolvedGroup::Single(0x18), + ResolvedGroup::Struct( + 2, + vec![ + ResolvedGroup::Single(0x19), + ResolvedGroup::Union( + 2, + vec![ + ResolvedGroup::Single(0x1a), + ResolvedGroup::Single(0x1b), + ] + ) + ] + ) + ] + ), + ] + ), + ResolvedGroup::Struct( + 0, + vec![ + ResolvedGroup::Single(0x1c), + ResolvedGroup::Single(0x1d), + ResolvedGroup::Single(0x1e), + ResolvedGroup::Single(0x1f), + ] + ), + ] + ), + ResolvedGroup::Single(0x20), + ResolvedGroup::Single(0x21), + ] + ) +} + +#[test] +fn test_bool_modifier() { + assert_eq!( + resolve_struct_groups(vec![ + MemberSize { + index: 0, + offset: 8, + width: 1, + }, + MemberSize { + index: 1, + offset: 12, + width: 8, + }, + MemberSize { + index: 2, + offset: 16, + width: 1, + }, + ]) + .unwrap_err() + .to_string(), + format!( + "Struct has overlapping member that cannot be resolved: {:#?}", + MemberSize { + index: 2, + offset: 16, + width: 1, + } + ) + ); +} + +/// Whoops I'm not in PDBParserInstance +fn log D, D: Display>(msg: F) { + // println!("{}", msg()); + if env::var("BN_DEBUG_PDB").is_ok() { + debug!("{}", msg()); + } +} diff --git a/examples/pdb-ng/src/symbol_parser.rs b/examples/pdb-ng/src/symbol_parser.rs new file mode 100644 index 0000000..7a90c4a --- /dev/null +++ b/examples/pdb-ng/src/symbol_parser.rs @@ -0,0 +1,2061 @@ +// Copyright 2022-2024 Vector 35 Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +use std::collections::{BTreeMap, HashMap, HashSet}; +use std::mem; +use std::sync::OnceLock; + +use anyhow::{anyhow, Result}; +use itertools::Itertools; +use pdb::register::Register::{AMD64, X86}; +use pdb::register::{AMD64Register, X86Register}; +use pdb::Error::UnimplementedSymbolKind; +use pdb::{ + AnnotationReferenceSymbol, BasePointerRelativeSymbol, BlockSymbol, BuildInfoSymbol, + CallSiteInfoSymbol, CompileFlagsSymbol, ConstantSymbol, DataReferenceSymbol, DataSymbol, + DefRangeFramePointerRelativeFullScopeSymbol, DefRangeFramePointerRelativeSymbol, + DefRangeRegisterRelativeSymbol, DefRangeRegisterSymbol, DefRangeSubFieldRegisterSymbol, + DefRangeSubFieldSymbol, DefRangeSymbol, ExportSymbol, FallibleIterator, FrameProcedureSymbol, + InlineSiteSymbol, LabelSymbol, LocalSymbol, MultiRegisterVariableSymbol, ObjNameSymbol, + ProcedureReferenceSymbol, ProcedureSymbol, PublicSymbol, RegisterRelativeSymbol, + RegisterVariableSymbol, Rva, SeparatedCodeSymbol, Source, Symbol, SymbolData, SymbolIndex, + SymbolIter, ThreadStorageSymbol, ThunkSymbol, TrampolineSymbol, TypeIndex, + UserDefinedTypeSymbol, UsingNamespaceSymbol, +}; + +use binaryninja::architecture::{Architecture, ArchitectureExt, Register}; +use binaryninja::binaryview::BinaryViewBase; +use binaryninja::demangle::demangle_ms; +use binaryninja::rc::Ref; +use binaryninja::types::{ + max_confidence, min_confidence, Conf, ConfMergable, FunctionParameter, QualifiedName, + StructureBuilder, Type, TypeClass, Variable, VariableSourceType, +}; + +use crate::PDBParserInstance; + +const DEMANGLE_CONFIDENCE: u8 = 32; + +/// Parsed Data Symbol like globals, etc +#[derive(Debug, Clone)] +pub struct SymbolNames { + pub raw_name: String, + pub short_name: Option, + pub full_name: Option, +} + +/// Parsed Data Symbol like globals, etc +#[derive(Debug, Clone)] +pub struct ParsedDataSymbol { + /// If the symbol comes from the public symbol list (lower quality) + pub is_public: bool, + /// Absolute address in bv + pub address: u64, + /// Symbol name + pub name: SymbolNames, + /// Type if known + pub type_: Option>>, +} + +/// Parsed functions and function-y symbols +#[derive(Debug, Clone)] +pub struct ParsedProcedure { + /// If the symbol comes from the public symbol list (lower quality) + pub is_public: bool, + /// Absolute address in bv + pub address: u64, + /// Symbol name + pub name: SymbolNames, + /// Function type if known + pub type_: Option>>, + /// List of local variables (TODO: use these) + pub locals: Vec, +} + +/// Structure with some information about a procedure +#[derive(Debug, Clone)] +pub struct ParsedProcedureInfo { + /// Known parameters for the procedure + pub params: Vec, + /// Known local variables for the procedure + pub locals: Vec, +} + +/// One parsed variable / parameter +#[derive(Debug, Clone)] +pub struct ParsedVariable { + /// Variable name + pub name: String, + /// Variable type if known + pub type_: Option>>, + /// Location(s) where the variable is stored. PDB lets you store a variable in multiple locations + /// despite binja not really understanding that. Length is probably never zero + pub storage: Vec, + /// Do we think this is a parameter + pub is_param: bool, +} + +#[derive(Debug, Copy, Clone)] +pub struct ParsedLocation { + /// Location information + pub location: Variable, + /// Is the storage location relative to the base pointer? See [ParsedProcedureInfo.frame_offset] + pub base_relative: bool, + /// Is the storage location relative to the stack pointer? + pub stack_relative: bool, +} + +/// Big enum of all the types of symbols we know how to parse +#[derive(Debug, Clone)] +pub enum ParsedSymbol { + /// Parsed Data Symbol like globals, etc + Data(ParsedDataSymbol), + /// Parsed functions and function-y symbols + Procedure(ParsedProcedure), + /// Structure with some information about a procedure + ProcedureInfo(ParsedProcedureInfo), + /// One parsed variable / parameter + LocalVariable(ParsedVariable), + /// Location of a local variable + Location(ParsedLocation), +} + +/// This is all done in the parser instance namespace because the lifetimes are impossible to +/// wrangle otherwise. +impl<'a, S: Source<'a> + 'a> PDBParserInstance<'a, S> { + pub fn parse_symbols( + &mut self, + progress: Box Result<()> + '_>, + ) -> Result<(Vec, Vec)> { + let mut module_count = 0usize; + let dbg = self.pdb.debug_information()?; + let mut modules = dbg.modules()?; + while let Some(_module) = modules.next()? { + module_count += 1; + } + + let global_symbols = self.pdb.global_symbols()?; + let symbols = global_symbols.iter(); + let parsed = self.parse_mod_symbols(symbols)?; + for sym in parsed { + match &sym { + ParsedSymbol::Data(ParsedDataSymbol { + name: SymbolNames { raw_name, .. }, + .. + }) + | ParsedSymbol::Procedure(ParsedProcedure { + name: SymbolNames { raw_name, .. }, + .. + }) => { + self.parsed_symbols_by_name + .insert(raw_name.clone(), self.parsed_symbols.len()); + } + _ => {} + } + self.parsed_symbols.push(sym); + } + + (progress)(1, module_count + 1)?; + + let dbg = self.pdb.debug_information()?; + let mut modules = dbg.modules()?; + let mut i = 0; + while let Some(module) = modules.next()? { + i += 1; + (progress)(i + 1, module_count + 1)?; + + self.log(|| { + format!( + "Module {} {}", + module.module_name(), + module.object_file_name() + ) + }); + if let Some(module_info) = self.pdb.module_info(&module)? { + let symbols = module_info.symbols()?; + let parsed = self.parse_mod_symbols(symbols)?; + for sym in parsed { + match &sym { + ParsedSymbol::Data(ParsedDataSymbol { + name: SymbolNames { raw_name, .. }, + .. + }) + | ParsedSymbol::Procedure(ParsedProcedure { + name: SymbolNames { raw_name, .. }, + .. + }) => { + self.parsed_symbols_by_name + .insert(raw_name.clone(), self.parsed_symbols.len()); + } + _ => {} + } + self.parsed_symbols.push(sym); + } + } + } + + let use_public = + self.settings + .get_bool("pdb.features.loadGlobalSymbols", Some(self.bv), None); + + let mut best_symbols = HashMap::::new(); + for sym in &self.parsed_symbols { + match sym { + ParsedSymbol::Data(ParsedDataSymbol { + is_public, + address, + name: + SymbolNames { + raw_name, + full_name, + .. + }, + type_, + .. + }) => { + if *is_public && !use_public { + continue; + } + + let this_confidence = match type_ { + Some(Conf { confidence, .. }) => *confidence, + _ => min_confidence(), + }; + let (new_better, old_exists) = match best_symbols.get(raw_name) { + Some(ParsedSymbol::Data(ParsedDataSymbol { + type_: + Some(Conf { + confidence: old_conf, + .. + }), + .. + })) => (this_confidence > *old_conf, true), + Some(ParsedSymbol::Data(ParsedDataSymbol { type_: None, .. })) => { + (true, true) + } + Some(..) => (false, true), + _ => (true, false), + }; + if new_better { + self.log(|| { + format!( + "New best symbol (at 0x{:x}) for `{}` / `{}`: {:?}", + *address, + raw_name, + full_name.as_ref().unwrap_or(raw_name), + sym + ) + }); + if old_exists { + self.log(|| format!("Clobbering old definition")); + } + best_symbols.insert(raw_name.clone(), sym); + } + } + _ => {} + } + } + + let mut best_functions = HashMap::::new(); + for sym in &self.parsed_symbols { + match sym { + ParsedSymbol::Procedure(ParsedProcedure { + is_public, + address, + name: + SymbolNames { + raw_name, + full_name, + .. + }, + type_, + .. + }) => { + if *is_public && !use_public { + continue; + } + + let this_confidence = match type_ { + Some(Conf { confidence, .. }) => *confidence, + _ => min_confidence(), + }; + let (new_better, old_exists) = match best_functions.get(raw_name) { + Some(ParsedSymbol::Procedure(ParsedProcedure { + type_: + Some(Conf { + confidence: old_conf, + .. + }), + .. + })) => (this_confidence > *old_conf, true), + Some(ParsedSymbol::Procedure(ParsedProcedure { type_: None, .. })) => { + (true, true) + } + Some(..) => (false, true), + _ => (true, false), + }; + if new_better { + self.log(|| { + format!( + "New best function (at 0x{:x}) for `{}` / `{}`: {:?}", + *address, + raw_name, + full_name.as_ref().unwrap_or(raw_name), + sym + ) + }); + if old_exists { + self.log(|| format!("Clobbering old definition")); + } + best_functions.insert(raw_name.clone(), sym); + } + } + _ => {} + } + } + + Ok(( + best_symbols + .into_iter() + .map(|(_, sym)| sym.clone()) + .sorted_by_key(|sym| match sym { + ParsedSymbol::Data(ParsedDataSymbol { + type_, is_public, .. + }) => type_ + .as_ref() + .map(|ty| { + if *is_public { + ty.confidence / 2 + } else { + ty.confidence + } + }) + .unwrap_or(0), + ParsedSymbol::Procedure(ParsedProcedure { + type_, is_public, .. + }) => type_ + .as_ref() + .map(|ty| { + if *is_public { + ty.confidence / 2 + } else { + ty.confidence + } + }) + .unwrap_or(0), + _ => 0, + }) + .collect::>(), + best_functions + .into_iter() + .map(|(_, func)| func.clone()) + .sorted_by_key(|sym| match sym { + ParsedSymbol::Data(ParsedDataSymbol { + type_, is_public, .. + }) => type_ + .as_ref() + .map(|ty| { + if *is_public { + ty.confidence / 2 + } else { + ty.confidence + } + }) + .unwrap_or(0), + ParsedSymbol::Procedure(ParsedProcedure { + type_, is_public, .. + }) => type_ + .as_ref() + .map(|ty| { + if *is_public { + ty.confidence / 2 + } else { + ty.confidence + } + }) + .unwrap_or(0), + _ => 0, + }) + .collect::>(), + )) + } + + /// Parse all the symbols in a module, via the given SymbolIter + pub fn parse_mod_symbols(&mut self, mut symbols: SymbolIter) -> Result> { + // Collect tree structure first + let mut first = None; + let mut last_local = None; + let mut top_level_syms = vec![]; + let mut thunk_syms = vec![]; + let mut unparsed_syms = BTreeMap::new(); + while let Some(sym) = symbols.next()? { + if first.is_none() { + first = Some(sym.index()); + } + unparsed_syms.insert(sym.index(), sym); + + let p = sym.parse(); + self.log(|| format!("Parsed: {:x?}", p)); + + // It's some sort of weird tree structure where SOME symbols have "end" indices + // and anything between them and that index is a child symbol + // Sometimes there are "end scope" symbols at those end indices but like, sometimes + // there aren't? Which makes that entire system seem pointless (or I'm just missing + // something and it makes sense to _someone_) + if let Some(&(start, _end)) = self.symbol_stack.last() { + self.add_symbol_child(start, sym.index()); + } else { + // Place thunk symbols in their own list at the end, so they can reference + // other symbols parsed in the module + match &p { + Ok(SymbolData::Thunk(_)) => { + thunk_syms.push(sym.index()); + } + _ => { + top_level_syms.push(sym.index()); + } + } + } + let mut popped = false; + while let Some(&(_start, end)) = self.symbol_stack.last() { + if sym.index().0 >= end.0 { + let _ = self.symbol_stack.pop(); + popped = true; + } else { + break; + } + } + + // These aren't actually used for parsing (I don't trust them) but we can include a little + // debug error check here and see if it's ever actually wrong + match p { + Ok(SymbolData::ScopeEnd) | Ok(SymbolData::InlineSiteEnd) if popped => {} + Ok(SymbolData::ScopeEnd) | Ok(SymbolData::InlineSiteEnd) if !popped => { + self.log(|| format!("Did not pop at a scope end??? WTF??")); + } + _ if popped => { + self.log(|| format!("Popped but not at a scope end??? WTF??")); + } + _ => {} + } + + // Push new scopes on the stack to build the tree + match p { + Ok(SymbolData::Procedure(data)) => { + self.symbol_stack.push((sym.index(), data.end)); + } + Ok(SymbolData::InlineSite(data)) => { + self.symbol_stack.push((sym.index(), data.end)); + } + Ok(SymbolData::Block(data)) => { + self.symbol_stack.push((sym.index(), data.end)); + } + Ok(SymbolData::Thunk(data)) => { + self.symbol_stack.push((sym.index(), data.end)); + } + Ok(SymbolData::SeparatedCode(data)) => { + self.symbol_stack.push((sym.index(), data.end)); + } + Ok(SymbolData::FrameProcedure(..)) => { + if let Some(&(_, proc_end)) = self.symbol_stack.last() { + self.symbol_stack.push((sym.index(), proc_end)); + } + } + Ok(SymbolData::Local(..)) => { + last_local = Some(sym.index()); + } + Ok(SymbolData::DefRange(..)) + | Ok(SymbolData::DefRangeSubField(..)) + | Ok(SymbolData::DefRangeRegister(..)) + | Ok(SymbolData::DefRangeFramePointerRelative(..)) + | Ok(SymbolData::DefRangeFramePointerRelativeFullScope(..)) + | Ok(SymbolData::DefRangeSubFieldRegister(..)) + | Ok(SymbolData::DefRangeRegisterRelative(..)) => { + // I'd like to retract my previous statement that someone could possibly + // understand this: + // These symbol types impact the previous symbol, if it was a local + // BUT ALSO!! PART III REVENGE OF THE SYM-TH: You can have more than one of + // these and they all (?? it's undocumented) apply to the last local, PROBABLY + if let Some(last) = last_local { + self.add_symbol_child(last, sym.index()); + } else { + self.log(|| format!("Found def range with no last local: {:?}", p)); + } + } + _ => {} + } + } + assert!(self.symbol_stack.is_empty()); + // Add thunks at the end as per above + top_level_syms.extend(thunk_syms.into_iter()); + + // Restart and do the processing for real this time + if let Some(first) = first { + symbols.seek(first); + } + + let mut final_symbols = HashSet::new(); + + for root_idx in top_level_syms { + for child_idx in self.walk_children(root_idx).into_iter() { + let &sym = unparsed_syms + .get(&child_idx) + .expect("should have parsed this"); + + self.log(|| format!("Symbol {:?} ", sym.index())); + let (name, address) = + if let Some(parsed) = self.handle_symbol_index(sym.index(), &sym)? { + final_symbols.insert(sym.index()); + match parsed { + ParsedSymbol::Data(ParsedDataSymbol { name, address, .. }) => { + (Some(name.clone()), Some(*address)) + } + ParsedSymbol::Procedure(ParsedProcedure { name, address, .. }) => { + (Some(name.clone()), Some(*address)) + } + _ => (None, None), + } + } else { + (None, None) + }; + + if let Some(name) = name { + self.named_symbols.insert(name.raw_name, sym.index()); + } + if let Some(address) = address { + if !self.addressed_symbols.contains_key(&address) { + self.addressed_symbols.insert(address, vec![]); + } + self.addressed_symbols + .get_mut(&address) + .expect("just created this") + .push( + self.indexed_symbols + .get(&sym.index()) + .ok_or_else(|| anyhow!("Can't find sym {} ?", sym.index()))? + .clone(), + ); + } + } + } + + let filtered_symbols = mem::replace(&mut self.indexed_symbols, BTreeMap::new()) + .into_iter() + .filter_map(|(idx, sym)| { + if final_symbols.contains(&idx) { + Some(sym) + } else { + None + } + }) + .collect::>(); + + // The symbols overlap between modules or something, so we can't keep this info around + self.symbol_tree.clear(); + self.module_cpu_type = None; + + Ok(filtered_symbols) + } + + /// Set a symbol to be the parent of another, building the symbol tree + fn add_symbol_child(&mut self, parent: SymbolIndex, child: SymbolIndex) { + if let Some(tree) = self.symbol_tree.get_mut(&parent) { + tree.push(child); + } else { + self.symbol_tree.insert(parent, Vec::from([child])); + } + + self.symbol_parents.insert(child, parent); + } + + /// Postorder traversal of children of symbol index (only during this module parse) + fn walk_children(&self, sym: SymbolIndex) -> Vec { + let mut children = vec![]; + + if let Some(tree) = self.symbol_tree.get(&sym) { + for &child in tree { + children.extend(self.walk_children(child).into_iter()); + } + } + + children.push(sym); + return children; + } + + /// Direct children of symbol index (only during this module parse) + fn symbol_children(&self, sym: SymbolIndex) -> Vec { + if let Some(tree) = self.symbol_tree.get(&sym) { + tree.clone() + } else { + vec![] + } + } + + /// Direct parent of symbol index (only during this module parse) + #[allow(dead_code)] + fn symbol_parent(&self, sym: SymbolIndex) -> Option { + self.symbol_parents.get(&sym).map(|idx| *idx) + } + + /// Find symbol by index (only during this module parse) + fn lookup_symbol(&self, sym: &SymbolIndex) -> Option<&ParsedSymbol> { + self.indexed_symbols.get(sym) + } + + /// Parse a new symbol by its index + fn handle_symbol_index( + &mut self, + idx: SymbolIndex, + sym: &Symbol, + ) -> Result> { + if let None = self.indexed_symbols.get(&idx) { + match sym.parse() { + Ok(data) => match self.handle_symbol(idx, &data) { + Ok(Some(parsed)) => { + self.log(|| format!("Symbol {} parsed into: {:?}", idx, parsed)); + self.indexed_symbols.insert(idx, parsed); + } + Ok(None) => {} + e => { + self.log(|| format!("Error parsing symbol {}: {:?}", idx, e)); + } + }, + Err(UnimplementedSymbolKind(k)) => { + self.log(|| format!("Not parsing unimplemented symbol {}: kind {:x?}", idx, k)); + } + Err(e) => { + self.log(|| format!("Could not parse symbol: {}: {}", idx, e)); + } + }; + } + + Ok(self.indexed_symbols.get(&idx)) + } + + /// Parse a new symbol's data + fn handle_symbol( + &mut self, + index: SymbolIndex, + data: &SymbolData, + ) -> Result> { + match data { + SymbolData::ScopeEnd => self.handle_scope_end_symbol(index), + SymbolData::ObjName(data) => self.handle_obj_name_symbol(index, &data), + SymbolData::RegisterVariable(data) => { + self.handle_register_variable_symbol(index, &data) + } + SymbolData::Constant(data) => self.handle_constant_symbol(index, &data), + SymbolData::UserDefinedType(data) => self.handle_user_defined_type_symbol(index, &data), + SymbolData::MultiRegisterVariable(data) => { + self.handle_multi_register_variable_symbol(index, &data) + } + SymbolData::Data(data) => self.handle_data_symbol(index, &data), + SymbolData::Public(data) => self.handle_public_symbol(index, &data), + SymbolData::Procedure(data) => self.handle_procedure_symbol(index, &data), + SymbolData::ThreadStorage(data) => self.handle_thread_storage_symbol(index, &data), + SymbolData::CompileFlags(data) => self.handle_compile_flags_symbol(index, &data), + SymbolData::UsingNamespace(data) => self.handle_using_namespace_symbol(index, &data), + SymbolData::ProcedureReference(data) => { + self.handle_procedure_reference_symbol(index, &data) + } + SymbolData::DataReference(data) => self.handle_data_reference_symbol(index, &data), + SymbolData::AnnotationReference(data) => { + self.handle_annotation_reference_symbol(index, &data) + } + SymbolData::Trampoline(data) => self.handle_trampoline_symbol(index, &data), + SymbolData::Export(data) => self.handle_export_symbol(index, &data), + SymbolData::Local(data) => self.handle_local_symbol(index, &data), + SymbolData::BuildInfo(data) => self.handle_build_info_symbol(index, &data), + SymbolData::InlineSite(data) => self.handle_inline_site_symbol(index, &data), + SymbolData::InlineSiteEnd => self.handle_inline_site_end_symbol(index), + SymbolData::ProcedureEnd => self.handle_procedure_end_symbol(index), + SymbolData::Label(data) => self.handle_label_symbol(index, &data), + SymbolData::Block(data) => self.handle_block_symbol(index, &data), + SymbolData::RegisterRelative(data) => { + self.handle_register_relative_symbol(index, &data) + } + SymbolData::Thunk(data) => self.handle_thunk_symbol(index, &data), + SymbolData::SeparatedCode(data) => self.handle_separated_code_symbol(index, &data), + SymbolData::DefRange(data) => self.handle_def_range(index, &data), + SymbolData::DefRangeSubField(data) => self.handle_def_range_sub_field(index, &data), + SymbolData::DefRangeRegister(data) => self.handle_def_range_register(index, &data), + SymbolData::DefRangeFramePointerRelative(data) => { + self.handle_def_range_frame_pointer_relative_symbol(index, &data) + } + SymbolData::DefRangeFramePointerRelativeFullScope(data) => { + self.handle_def_range_frame_pointer_relative_full_scope_symbol(index, &data) + } + SymbolData::DefRangeSubFieldRegister(data) => { + self.handle_def_range_sub_field_register_symbol(index, &data) + } + SymbolData::DefRangeRegisterRelative(data) => { + self.handle_def_range_register_relative_symbol(index, &data) + } + SymbolData::BasePointerRelative(data) => { + self.handle_base_pointer_relative_symbol(index, &data) + } + SymbolData::FrameProcedure(data) => self.handle_frame_procedure_symbol(index, &data), + SymbolData::CallSiteInfo(data) => self.handle_call_site_info(index, &data), + e => Err(anyhow!("Unhandled symbol type {:?}", e)), + } + } + + fn handle_scope_end_symbol(&mut self, _index: SymbolIndex) -> Result> { + self.log(|| format!("Got ScopeEnd symbol")); + Ok(None) + } + + fn handle_obj_name_symbol( + &mut self, + _index: SymbolIndex, + data: &ObjNameSymbol, + ) -> Result> { + self.log(|| format!("Got ObjName symbol: {:?}", data)); + Ok(None) + } + + fn handle_register_variable_symbol( + &mut self, + _index: SymbolIndex, + data: &RegisterVariableSymbol, + ) -> Result> { + self.log(|| format!("Got RegisterVariable symbol: {:?}", data)); + + let storage = if let Some(reg) = self.convert_register(data.register) { + vec![ParsedLocation { + location: Variable { + t: VariableSourceType::RegisterVariableSourceType, + index: 0, + storage: reg, + }, + base_relative: false, + stack_relative: false, + }] + } else { + // TODO: What do we do here? + vec![] + }; + + Ok(Some(ParsedSymbol::LocalVariable(ParsedVariable { + name: data.name.to_string().to_string(), + type_: self.lookup_type_conf(&data.type_index, false)?, + storage, + is_param: data.slot.map_or(true, |slot| slot > 0), + }))) + } + + fn handle_constant_symbol( + &mut self, + _index: SymbolIndex, + data: &ConstantSymbol, + ) -> Result> { + self.log(|| format!("Got Constant symbol: {:?}", data)); + Ok(None) + } + + fn handle_user_defined_type_symbol( + &mut self, + _index: SymbolIndex, + data: &UserDefinedTypeSymbol, + ) -> Result> { + self.log(|| format!("Got UserDefinedType symbol: {:?}", data)); + Ok(None) + } + + fn handle_multi_register_variable_symbol( + &mut self, + _index: SymbolIndex, + data: &MultiRegisterVariableSymbol, + ) -> Result> { + self.log(|| format!("Got MultiRegisterVariable symbol: {:?}", data)); + Ok(None) + } + + fn handle_data_symbol( + &mut self, + _index: SymbolIndex, + data: &DataSymbol, + ) -> Result> { + self.log(|| format!("Got Data symbol: {:?}", data)); + + let rva = data.offset.to_rva(&self.address_map).unwrap_or_default(); + let raw_name = data.name.to_string().to_string(); + let (t, name) = self.demangle_to_type(&raw_name, rva)?; + let name = name.map(|n| n.string()); + + // Sometimes the demangler REALLY knows what type this is supposed to be, and the + // data symbol is actually wrong. So in those cases, let the demangler take precedence + // Otherwise-- the demangler is usually wrong and clueless + let data_type = t.merge(self.lookup_type_conf(&data.type_index, false)?); + + // Ignore symbols with no name and no type + if !self + .settings + .get_bool("pdb.features.allowUnnamedVoidSymbols", Some(self.bv), None) + && name.is_none() + { + if let Some(ty) = &data_type { + if ty.contents.type_class() == TypeClass::VoidTypeClass { + return Ok(None); + } + } else { + return Ok(None); + } + } + + let name = SymbolNames { + raw_name, + short_name: name.clone(), + full_name: name, + }; + + self.log(|| { + format!( + "DATA: 0x{:x}: {:?} {:?}", + self.bv.start() + rva.0 as u64, + &name, + &data_type + ) + }); + + Ok(Some(ParsedSymbol::Data(ParsedDataSymbol { + is_public: false, + address: self.bv.start() + rva.0 as u64, + name, + type_: data_type, + }))) + } + + fn handle_public_symbol( + &mut self, + _index: SymbolIndex, + data: &PublicSymbol, + ) -> Result> { + self.log(|| format!("Got Public symbol: {:?}", data)); + let rva = data.offset.to_rva(&self.address_map).unwrap_or_default(); + let raw_name = data.name.to_string().to_string(); + let (t, name) = self.demangle_to_type(&raw_name, rva)?; + let name = name.map(|n| n.string()); + + let name = SymbolNames { + raw_name, + short_name: name.clone(), + full_name: name, + }; + + // These are generally low confidence because we only have the demangler to inform us of type + + if data.function { + self.log(|| { + format!( + "PUBLIC FUNCTION: 0x{:x}: {:?} {:?}", + self.bv.start() + rva.0 as u64, + &name, + t + ) + }); + + Ok(Some(ParsedSymbol::Procedure(ParsedProcedure { + is_public: true, + address: self.bv.start() + rva.0 as u64, + name, + type_: t, + locals: vec![], + }))) + } else { + self.log(|| { + format!( + "PUBLIC DATA: 0x{:x}: {:?} {:?}", + self.bv.start() + rva.0 as u64, + &name, + t + ) + }); + + Ok(Some(ParsedSymbol::Data(ParsedDataSymbol { + is_public: true, + address: self.bv.start() + rva.0 as u64, + name, + type_: t, + }))) + } + } + + /// Given a proc symbol index and guessed type (from demangler or tpi), find all the local variables + /// and parameters related to that symbol. + /// Returns Ok(Some((resolved params, locals)))) + fn lookup_locals( + &self, + index: SymbolIndex, + type_index: TypeIndex, + demangled_type: Option>>, + ) -> Result<(Option>>, Vec)> { + // So generally speaking, here's the information we have: + // - The function type is usually accurate wrt the parameter locations + // - The parameter symbols have the names we want for the params + // - The parameter symbols are a big ugly mess + // We basically want to take the function type from the type, and just fill in the + // names of all the parameters. Non-param locals don't really matter since binja + // can't handle them anyway. + + // Type parameters order needs to be like this: + // 1. `this` pointer (if exists) + // 2. Various stack params + // 3. Various register params + // We assume that if a parameter is found in a register, that is where it is passed. + // Otherwise they are in the default order as per the CC + + // Get child objects and search for local variable names + let mut locals = vec![]; + let mut params = vec![]; + let mut known_frame = false; + for child in self.symbol_children(index) { + match self.lookup_symbol(&child) { + Some(ParsedSymbol::ProcedureInfo(info)) => { + params = info.params.clone(); + locals = info.locals.clone(); + known_frame = true; + } + _ => {} + } + } + + let raw_type = self.lookup_type_conf(&type_index, false)?; + let fancy_type = self.lookup_type_conf(&type_index, true)?; + + // Best guess so far in case of error handling + let fancier_type = fancy_type + .clone() + .merge(raw_type.clone()) + .merge(demangled_type.clone()); + + if !known_frame { + return Ok((fancier_type, vec![])); + } + + // We need both of these to exist (not sure why they wouldn't) + let (raw_type, fancy_type) = match (raw_type, fancy_type) { + (Some(raw), Some(fancy)) => (raw, fancy), + _ => return Ok((fancier_type, vec![])), + }; + + let raw_params = raw_type + .contents + .parameters() + .map_err(|_| anyhow!("no params"))?; + let mut fancy_params = fancy_type + .contents + .parameters() + .map_err(|_| anyhow!("no params"))?; + + // Collect all the parameters we are expecting from the symbols + let mut parsed_params = vec![]; + for p in ¶ms { + let param = FunctionParameter::new( + p.type_.clone().merge(Conf::new( + Type::int(self.arch.address_size(), false), + min_confidence(), + )), + p.name.clone(), + p.storage.get(0).map(|loc| loc.location.clone()), + ); + // Ignore thisptr because it's not technically part of the raw type signature + if p.name != "this" { + parsed_params.push(param); + } + } + let mut parsed_locals = vec![]; + for p in &locals { + let param = FunctionParameter::new( + p.type_.clone().merge(Conf::new( + Type::int(self.arch.address_size(), false), + min_confidence(), + )), + p.name.clone(), + p.storage.get(0).map(|loc| loc.location.clone()), + ); + // Ignore thisptr because it's not technically part of the raw type signature + if p.name != "this" { + parsed_locals.push(param); + } + } + + self.log(|| format!("Raw params: {:#x?}", raw_params)); + self.log(|| format!("Fancy params: {:#x?}", fancy_params)); + self.log(|| format!("Parsed params: {:#x?}", parsed_params)); + + // We expect one parameter for each unnamed parameter in the marked up type + let expected_param_count = fancy_params + .iter() + .filter(|p| p.name.as_str().is_empty()) + .count(); + // Sanity + if expected_param_count != raw_params.len() { + return Err(anyhow!( + "Mismatched number of formal parameters and interpreted parameters" + )); + } + + // If we don't have enough parameters to fill the slots, there's a problem here + // So just fallback to the unnamed params + if expected_param_count > parsed_params.len() { + // As per reversing of msdia140.dll (and nowhere else): if a function doesn't have + // enough parameter variables declared as parameters, the remaining parameters are + // the first however many locals. If you don't have enough of those, idk?? + if expected_param_count > (parsed_params.len() + parsed_locals.len()) { + return Ok((fancier_type, vec![])); + } + parsed_params.extend(parsed_locals.into_iter()); + } + let expected_parsed_params = parsed_params + .drain(0..expected_param_count) + .collect::>(); + + // For all formal parameters, apply names to them in fancy_params + // These should be all types in fancy_params that are unnamed (named ones we inserted) + + let mut i = 0; + for p in fancy_params.iter_mut() { + if p.name.as_str().is_empty() { + if p.t.contents != expected_parsed_params[i].t.contents { + self.log(|| { + format!( + "Suspicious parameter {}: {:?} vs {:?}", + i, p, expected_parsed_params[i] + ) + }); + } + if expected_parsed_params[i].name.as_str() == "__formal" { + p.name = format!("__formal{}", i); + } else { + p.name = expected_parsed_params[i].name.clone(); + } + i += 1; + } + } + + // Now apply the default location for the params from the cc + let cc = fancy_type + .contents + .calling_convention() + .map_or_else(|_| Conf::new(self.default_cc.clone(), 0), |cc| cc); + + self.log(|| { + format!( + "Type calling convention: {:?}", + fancy_type.contents.calling_convention() + ) + }); + self.log(|| format!("Default calling convention: {:?}", self.default_cc)); + self.log(|| format!("Result calling convention: {:?}", cc)); + + let locations = cc.contents.variables_for_parameters(&fancy_params, None); + for (p, new_location) in fancy_params.iter_mut().zip(locations.into_iter()) { + p.location = Some(new_location); + } + + self.log(|| format!("Final params: {:#x?}", fancy_params)); + + // Use the new locals we've parsed to make the Real Definitely True function type + let fancy_type = Conf::new( + Type::function_with_options( + &fancy_type + .contents + .return_value() + .map_err(|_| anyhow!("no ret"))?, + fancy_params.as_slice(), + fancy_type.contents.has_variable_arguments().contents, + &cc, + fancy_type.contents.stack_adjustment(), + ), + max_confidence(), + ); + + let fancier_type = fancy_type + .clone() + .merge(raw_type.clone()) + .merge(demangled_type.clone()); + + self.log(|| format!("Raw type: {:#x?}", raw_type)); + self.log(|| format!("Demangled type: {:#x?}", demangled_type)); + self.log(|| format!("Fancy type: {:#x?}", fancy_type)); + self.log(|| format!("Result type: {:#x?}", fancier_type)); + + Ok((Some(fancier_type), vec![])) + } + + fn handle_procedure_symbol( + &mut self, + index: SymbolIndex, + data: &ProcedureSymbol, + ) -> Result> { + self.log(|| format!("Got Procedure symbol: {:?}", data)); + + let rva = data.offset.to_rva(&self.address_map).unwrap_or_default(); + let address = self.bv.start() + rva.0 as u64; + + let mut raw_name = data.name.to_string().to_string(); + + // Generally proc symbols have real types, but use the demangler just in case the microsoft + // public pdbs have the function type as `void` + let (t, name) = self.demangle_to_type(&raw_name, rva)?; + let mut name = name.map(|n| n.string()); + + // Some proc symbols don't have a mangled name, so try and look up their name + if name.is_none() || name.as_ref().expect("just failed none") == &raw_name { + // Lookup public symbol with the same name + if let Some(others) = self.addressed_symbols.get(&address) { + for o in others { + match o { + ParsedSymbol::Procedure(ParsedProcedure { + name: proc_name, .. + }) => { + if proc_name.full_name.as_ref().unwrap_or(&proc_name.raw_name) + == &raw_name + { + name = Some(raw_name); + raw_name = proc_name.raw_name.clone(); + break; + } + } + _ => {} + } + } + } + } + + let (fn_type, locals) = self.lookup_locals(index, data.type_index, t)?; + + let name = SymbolNames { + raw_name, + short_name: name.clone(), + full_name: name, + }; + + self.log(|| format!("PROC: 0x{:x}: {:?} {:?}", address, &name, &fn_type)); + + Ok(Some(ParsedSymbol::Procedure(ParsedProcedure { + is_public: false, + address, + name, + type_: fn_type, + locals, + }))) + } + + fn handle_thread_storage_symbol( + &mut self, + _index: SymbolIndex, + data: &ThreadStorageSymbol, + ) -> Result> { + self.log(|| format!("Got ThreadStorage symbol: {:?}", data)); + Ok(None) + } + + fn handle_compile_flags_symbol( + &mut self, + _index: SymbolIndex, + data: &CompileFlagsSymbol, + ) -> Result> { + self.log(|| format!("Got CompileFlags symbol: {:?}", data)); + self.module_cpu_type = Some(data.cpu_type); + Ok(None) + } + + fn handle_using_namespace_symbol( + &mut self, + _index: SymbolIndex, + data: &UsingNamespaceSymbol, + ) -> Result> { + self.log(|| format!("Got UsingNamespace symbol: {:?}", data)); + Ok(None) + } + + fn handle_procedure_reference_symbol( + &mut self, + _index: SymbolIndex, + data: &ProcedureReferenceSymbol, + ) -> Result> { + self.log(|| format!("Got ProcedureReference symbol: {:?}", data)); + Ok(None) + } + + fn handle_data_reference_symbol( + &mut self, + _index: SymbolIndex, + data: &DataReferenceSymbol, + ) -> Result> { + self.log(|| format!("Got DataReference symbol: {:?}", data)); + Ok(None) + } + + fn handle_annotation_reference_symbol( + &mut self, + _index: SymbolIndex, + data: &AnnotationReferenceSymbol, + ) -> Result> { + self.log(|| format!("Got AnnotationReference symbol: {:?}", data)); + Ok(None) + } + + fn handle_trampoline_symbol( + &mut self, + _index: SymbolIndex, + data: &TrampolineSymbol, + ) -> Result> { + self.log(|| format!("Got Trampoline symbol: {:?}", data)); + let rva = data.thunk.to_rva(&self.address_map).unwrap_or_default(); + let target_rva = data.target.to_rva(&self.address_map).unwrap_or_default(); + + let address = self.bv.start() + rva.0 as u64; + let target_address = self.bv.start() + target_rva.0 as u64; + + let mut target_name = None; + let mut thunk_name = None; + + let mut fn_type: Option>> = None; + + // These have the same name as their target, so look that up + if let Some(syms) = self.addressed_symbols.get(&target_address) { + // Take name from the public symbol + for sym in syms { + match sym { + ParsedSymbol::Procedure(proc) if proc.is_public => { + fn_type = proc.type_.clone().merge(fn_type); + target_name = Some(proc.name.clone()); + } + _ => {} + } + } + // Take type from the non-public symbol if we have one + for sym in syms { + match sym { + ParsedSymbol::Procedure(proc) if !proc.is_public => { + fn_type = proc.type_.clone().merge(fn_type); + if target_name.is_none() { + target_name = Some(proc.name.clone()); + } + } + _ => {} + } + } + } + + // And handle the fact that pdb public symbols for trampolines have the name of their target + // ugh + if let Some(syms) = self.addressed_symbols.get_mut(&address) { + if let [ParsedSymbol::Procedure(proc)] = syms.as_mut_slice() { + if let Some(tn) = &target_name { + if proc.name.raw_name == tn.raw_name + || proc.name.full_name.as_ref().unwrap_or(&proc.name.raw_name) + == tn.full_name.as_ref().unwrap_or(&tn.raw_name) + { + // Yeah it's one of these symbols + let old_name = proc.name.clone(); + let new_name = SymbolNames { + raw_name: "j_".to_string() + &old_name.raw_name, + short_name: old_name.short_name.as_ref().map(|n| "j_".to_string() + n), + full_name: old_name.full_name.as_ref().map(|n| "j_".to_string() + n), + }; + + // I'm so sorry about this + // XXX: Update the parsed public symbol's name to use j_ syntax + if let Some(idx) = self.named_symbols.remove(&old_name.raw_name) { + self.named_symbols.insert(new_name.raw_name.clone(), idx); + } + if let Some(idx) = self.parsed_symbols_by_name.remove(&old_name.raw_name) { + self.parsed_symbols_by_name + .insert(new_name.raw_name.clone(), idx); + match &mut self.parsed_symbols[idx] { + ParsedSymbol::Data(ParsedDataSymbol { + name: parsed_name, .. + }) + | ParsedSymbol::Procedure(ParsedProcedure { + name: parsed_name, + .. + }) => { + parsed_name.raw_name = new_name.raw_name.clone(); + parsed_name.short_name = new_name.short_name.clone(); + parsed_name.full_name = new_name.full_name.clone(); + } + _ => {} + } + } + proc.name = new_name.clone(); + thunk_name = Some(new_name); + } + } + } + } + + if thunk_name.is_none() { + if let Some(tn) = target_name { + thunk_name = Some(SymbolNames { + raw_name: "j_".to_string() + &tn.raw_name, + short_name: tn.short_name.as_ref().map(|n| "j_".to_string() + n), + full_name: tn.full_name.as_ref().map(|n| "j_".to_string() + n), + }); + } + } + + let name = thunk_name.unwrap_or(SymbolNames { + raw_name: format!("j_sub_{:x}", target_address), + short_name: None, + full_name: None, + }); + + self.log(|| format!("TRAMPOLINE: 0x{:x}: {:?} {:?}", address, &name, &fn_type)); + + Ok(Some(ParsedSymbol::Procedure(ParsedProcedure { + is_public: false, + address, + name, + type_: fn_type, + locals: vec![], + }))) + } + + fn handle_export_symbol( + &mut self, + _index: SymbolIndex, + data: &ExportSymbol, + ) -> Result> { + self.log(|| format!("Got Export symbol: {:?}", data)); + Ok(None) + } + + fn handle_local_symbol( + &mut self, + index: SymbolIndex, + data: &LocalSymbol, + ) -> Result> { + self.log(|| format!("Got Local symbol: {:?}", data)); + // Look for definition ranges for this symbol + let mut locations = vec![]; + for child in self.symbol_children(index) { + match self.lookup_symbol(&child) { + Some(ParsedSymbol::Location(loc)) => { + locations.push(loc.clone()); + } + _ => {} + } + } + + Ok(Some(ParsedSymbol::LocalVariable(ParsedVariable { + name: data.name.to_string().to_string(), + type_: self.lookup_type_conf(&data.type_index, false)?, + storage: locations, + is_param: data.flags.isparam, + }))) + } + + fn handle_build_info_symbol( + &mut self, + _index: SymbolIndex, + data: &BuildInfoSymbol, + ) -> Result> { + self.log(|| format!("Got BuildInfo symbol: {:?}", data)); + Ok(None) + } + + fn handle_inline_site_symbol( + &mut self, + _index: SymbolIndex, + data: &InlineSiteSymbol, + ) -> Result> { + self.log(|| format!("Got InlineSite symbol: {:?}", data)); + Ok(None) + } + + fn handle_inline_site_end_symbol( + &mut self, + _index: SymbolIndex, + ) -> Result> { + self.log(|| format!("Got InlineSiteEnd symbol")); + Ok(None) + } + + fn handle_procedure_end_symbol(&mut self, _index: SymbolIndex) -> Result> { + self.log(|| format!("Got ProcedureEnd symbol")); + Ok(None) + } + + fn handle_label_symbol( + &mut self, + _index: SymbolIndex, + data: &LabelSymbol, + ) -> Result> { + self.log(|| format!("Got Label symbol: {:?}", data)); + Ok(None) + } + + fn handle_block_symbol( + &mut self, + _index: SymbolIndex, + data: &BlockSymbol, + ) -> Result> { + self.log(|| format!("Got Block symbol: {:?}", data)); + Ok(None) + } + + fn handle_register_relative_symbol( + &mut self, + _index: SymbolIndex, + data: &RegisterRelativeSymbol, + ) -> Result> { + self.log(|| format!("Got RegisterRelative symbol: {:?}", data)); + match self.lookup_register(data.register) { + Some(X86(X86Register::EBP)) | Some(AMD64(AMD64Register::RBP)) => { + // Local is relative to base pointer + // This is just another way of writing BasePointerRelativeSymbol + Ok(Some(ParsedSymbol::LocalVariable(ParsedVariable { + name: data.name.to_string().to_string(), + type_: self.lookup_type_conf(&data.type_index, false)?, + storage: vec![ParsedLocation { + location: Variable { + t: VariableSourceType::StackVariableSourceType, + index: 0, + storage: data.offset as i64, + }, + base_relative: true, // !! + stack_relative: false, // !! + }], + is_param: data.slot.map_or(false, |slot| slot > 0), + }))) + } + Some(X86(X86Register::ESP)) | Some(AMD64(AMD64Register::RSP)) => { + // Local is relative to stack pointer + // This is the same as base pointer case except not base relative (ofc) + Ok(Some(ParsedSymbol::LocalVariable(ParsedVariable { + name: data.name.to_string().to_string(), + type_: self.lookup_type_conf(&data.type_index, false)?, + storage: vec![ParsedLocation { + location: Variable { + t: VariableSourceType::StackVariableSourceType, + index: 0, + storage: data.offset as i64, + }, + base_relative: false, // !! + stack_relative: true, // !! + }], + is_param: data.slot.map_or(false, |slot| slot > 0), + }))) + } + _ => { + // Local is relative to some non-bp register. + // This is, of course, totally possible and normal + // Binja just can't handle it in the slightest. + // Soooooooo ???? + // TODO + Ok(None) + } + } + } + + fn handle_thunk_symbol( + &mut self, + _index: SymbolIndex, + data: &ThunkSymbol, + ) -> Result> { + self.log(|| format!("Got Thunk symbol: {:?}", data)); + let rva = data.offset.to_rva(&self.address_map).unwrap_or_default(); + let raw_name = data.name.to_string().to_string(); + let address = self.bv.start() + rva.0 as u64; + + let (t, name) = self.demangle_to_type(&raw_name, rva)?; + let name = name.map(|n| n.string()); + let mut fn_type = t; + + // These have the same name as their target, so look that up + if let Some(&idx) = self.named_symbols.get(&raw_name) { + if let Some(ParsedSymbol::Procedure(proc)) = self.indexed_symbols.get(&idx) { + fn_type = proc.type_.clone().merge(fn_type); + } + } + + let mut thunk_name = None; + + // And handle the fact that pdb public symbols for thunks have the name of their target + // ugh + if let Some(syms) = self.addressed_symbols.get_mut(&address) { + if let [ParsedSymbol::Procedure(proc)] = syms.as_mut_slice() { + // Yeah it's one of these symbols + // Make sure we don't do this twice (does that even happen?) + if !proc.name.raw_name.starts_with("j_") { + let old_name = proc.name.clone(); + let new_name = SymbolNames { + raw_name: "j_".to_string() + &old_name.raw_name, + short_name: Some( + "j_".to_string() + old_name.short_name.as_ref().unwrap_or(&raw_name), + ), + full_name: Some( + "j_".to_string() + old_name.full_name.as_ref().unwrap_or(&raw_name), + ), + }; + + // I'm so sorry about this + // XXX: Update the parsed public symbol's name to use j_ syntax + if let Some(idx) = self.named_symbols.remove(&old_name.raw_name) { + self.named_symbols.insert(new_name.raw_name.clone(), idx); + } + if let Some(idx) = self.parsed_symbols_by_name.remove(&old_name.raw_name) { + self.parsed_symbols_by_name + .insert(new_name.raw_name.clone(), idx); + match &mut self.parsed_symbols[idx] { + ParsedSymbol::Data(ParsedDataSymbol { + name: parsed_name, .. + }) + | ParsedSymbol::Procedure(ParsedProcedure { + name: parsed_name, .. + }) => { + parsed_name.raw_name = new_name.raw_name.clone(); + parsed_name.short_name = new_name.short_name.clone(); + parsed_name.full_name = new_name.full_name.clone(); + } + _ => {} + } + } + proc.name = new_name.clone(); + thunk_name = Some(new_name); + } + } + } + + let locals = vec![]; + let name = thunk_name.unwrap_or(SymbolNames { + raw_name, + short_name: name.clone(), + full_name: name, + }); + + self.log(|| format!("THUNK: 0x{:x}: {:?} {:?}", address, &name, &fn_type)); + + Ok(Some(ParsedSymbol::Procedure(ParsedProcedure { + is_public: false, + address: address, + name, + type_: fn_type, + locals, + }))) + } + + fn handle_separated_code_symbol( + &mut self, + _index: SymbolIndex, + data: &SeparatedCodeSymbol, + ) -> Result> { + self.log(|| format!("Got SeparatedCode symbol: {:?}", data)); + Ok(None) + } + + fn handle_def_range( + &mut self, + _index: SymbolIndex, + data: &DefRangeSymbol, + ) -> Result> { + self.log(|| format!("Got DefRange symbol: {:?}", data)); + Ok(None) + } + + fn handle_def_range_sub_field( + &mut self, + _index: SymbolIndex, + data: &DefRangeSubFieldSymbol, + ) -> Result> { + self.log(|| format!("Got DefRangeSubField symbol: {:?}", data)); + Ok(None) + } + + fn handle_def_range_register( + &mut self, + _index: SymbolIndex, + data: &DefRangeRegisterSymbol, + ) -> Result> { + self.log(|| format!("Got DefRangeRegister symbol: {:?}", data)); + if let Some(reg) = self.convert_register(data.register) { + Ok(Some(ParsedSymbol::Location(ParsedLocation { + location: Variable { + t: VariableSourceType::RegisterVariableSourceType, + index: 0, + storage: reg, + }, + base_relative: false, + stack_relative: false, + }))) + } else { + Ok(None) + } + } + + fn handle_def_range_frame_pointer_relative_symbol( + &mut self, + _index: SymbolIndex, + data: &DefRangeFramePointerRelativeSymbol, + ) -> Result> { + self.log(|| format!("Got DefRangeFramePointerRelative symbol: {:?}", data)); + Ok(None) + } + + fn handle_def_range_frame_pointer_relative_full_scope_symbol( + &mut self, + _index: SymbolIndex, + data: &DefRangeFramePointerRelativeFullScopeSymbol, + ) -> Result> { + self.log(|| { + format!( + "Got DefRangeFramePointerRelativeFullScope symbol: {:?}", + data + ) + }); + Ok(None) + } + + fn handle_def_range_sub_field_register_symbol( + &mut self, + _index: SymbolIndex, + data: &DefRangeSubFieldRegisterSymbol, + ) -> Result> { + self.log(|| format!("Got DefRangeSubFieldRegister symbol: {:?}", data)); + Ok(None) + } + + fn handle_def_range_register_relative_symbol( + &mut self, + _index: SymbolIndex, + data: &DefRangeRegisterRelativeSymbol, + ) -> Result> { + self.log(|| format!("Got DefRangeRegisterRelative symbol: {:?}", data)); + Ok(None) + } + + fn handle_base_pointer_relative_symbol( + &mut self, + _index: SymbolIndex, + data: &BasePointerRelativeSymbol, + ) -> Result> { + self.log(|| format!("Got BasePointerRelative symbol: {:?}", data)); + + // These are usually parameters if offset > 0 + + Ok(Some(ParsedSymbol::LocalVariable(ParsedVariable { + name: data.name.to_string().to_string(), + type_: self.lookup_type_conf(&data.type_index, false)?, + storage: vec![ParsedLocation { + location: Variable { + t: VariableSourceType::StackVariableSourceType, + index: 0, + storage: data.offset as i64, + }, + base_relative: true, + stack_relative: false, + }], + is_param: data.offset as i64 > 0 || data.slot.map_or(false, |slot| slot > 0), + }))) + } + + fn handle_frame_procedure_symbol( + &mut self, + index: SymbolIndex, + data: &FrameProcedureSymbol, + ) -> Result> { + self.log(|| format!("Got FrameProcedure symbol: {:?}", data)); + + // This symbol generally comes before a proc and all various parameters + // It has a lot of information we don't care about, and some information we maybe do? + // This function also tries to find all the locals and parameters of the procedure + + let mut params = vec![]; + let mut locals = vec![]; + let mut seen_offsets = HashSet::new(); + + for child in self.symbol_children(index) { + match self.lookup_symbol(&child) { + Some(ParsedSymbol::LocalVariable(ParsedVariable { + name, + type_, + storage, + is_param, + .. + })) => { + let new_storage = storage.iter().map(|&var| var.location).collect::>(); + + // See if the parameter really is a parameter. Sometimes they don't say they are + let mut really_is_param = *is_param; + for loc in &new_storage { + match loc { + Variable { + t: VariableSourceType::RegisterVariableSourceType, + .. + } => { + // Assume register vars are always parameters + really_is_param = true; + } + Variable { + t: VariableSourceType::StackVariableSourceType, + storage, + .. + } if *storage >= 0 => { + // Sometimes you can get two locals at the same offset, both rbp+(x > 0) + // I'm guessing from looking at dumps from dia2dump that only the first + // one is considered a parameter, although there are times that I see + // two params at the same offset and both are considered parameters... + // This doesn't seem possible (or correct) because they would overlap + // and only one would be useful anyway. + // Regardless of the mess, Binja can only handle one parameter per slot + // so we're just going to use the first one. + really_is_param = seen_offsets.insert(*storage); + } + _ => {} + } + } + + if really_is_param { + params.push(ParsedVariable { + name: name.clone(), + type_: type_.clone(), + storage: new_storage + .into_iter() + .map(|loc| ParsedLocation { + location: loc, + // This has been handled now + base_relative: false, + stack_relative: false, + }) + .collect(), + is_param: really_is_param, + }); + } else { + locals.push(ParsedVariable { + name: name.clone(), + type_: type_.clone(), + storage: new_storage + .into_iter() + .map(|loc| ParsedLocation { + location: loc, + // This has been handled now + base_relative: false, + stack_relative: false, + }) + .collect(), + is_param: really_is_param, + }); + } + } + Some(ParsedSymbol::Data(_)) => { + // Apparently you can have static data symbols as parameters + // Because of course you can + } + None => {} + e => self.log(|| format!("Unexpected symbol type in frame: {:?}", e)), + } + } + + Ok(Some(ParsedSymbol::ProcedureInfo(ParsedProcedureInfo { + params, + locals, + }))) + } + + fn handle_call_site_info( + &mut self, + _index: SymbolIndex, + data: &CallSiteInfoSymbol, + ) -> Result> { + self.log(|| format!("Got CallSiteInfo symbol: {:?}", data)); + Ok(None) + } + + /// Demangle a name and get a type out + /// Also fixes void(void) and __s_RTTI_Nonsense + fn demangle_to_type( + &self, + raw_name: &String, + rva: Rva, + ) -> Result<(Option>>, Option)> { + let (mut t, mut name) = match demangle_ms(&self.arch, raw_name, true) { + Ok((Some(t), name)) => (Some(Conf::new(t, DEMANGLE_CONFIDENCE)), name), + Ok((_, name)) => (None, name), + _ => (None, vec![raw_name.clone()]), + }; + + if let Some(ty) = t.as_ref() { + if ty.contents.type_class() == TypeClass::FunctionTypeClass { + // demangler makes (void) into (void arg1) which is wrong + let parameters = ty + .contents + .parameters() + .map_err(|_| anyhow!("no parameters"))?; + if let [p] = parameters.as_slice() { + if p.t.contents.type_class() == TypeClass::VoidTypeClass { + t = Some(Conf::new( + Type::function::<_>( + &ty.contents + .return_value() + .map_err(|_| anyhow!("no return value"))?, + &[], + ty.contents.has_variable_arguments().contents, + ), + ty.confidence, + )) + } + } + } + } + + // These have types but they aren't actually set anywhere. So it's the demangler's + // job to take care of them, apparently? + static MEM: OnceLock)>> = OnceLock::new(); + let name_to_type = MEM.get_or_init(|| { + vec![ + ( + "`RTTI Complete Object Locator'".to_string(), + vec![ + "_s_RTTICompleteObjectLocator".to_string(), + "_s__RTTICompleteObjectLocator".to_string(), + "_s__RTTICompleteObjectLocator2".to_string(), + ], + ), + ( + "`RTTI Class Hierarchy Descriptor'".to_string(), + vec![ + "_s_RTTIClassHierarchyDescriptor".to_string(), + "_s__RTTIClassHierarchyDescriptor".to_string(), + "_s__RTTIClassHierarchyDescriptor2".to_string(), + ], + ), + ( + // TODO: This type is dynamic + "`RTTI Base Class Array'".to_string(), + vec![ + "_s_RTTIBaseClassArray".to_string(), + "_s__RTTIBaseClassArray".to_string(), + "_s__RTTIBaseClassArray2".to_string(), + ], + ), + ( + "`RTTI Base Class Descriptor at (".to_string(), + vec![ + "_s_RTTIBaseClassDescriptor".to_string(), + "_s__RTTIBaseClassDescriptor".to_string(), + "_s__RTTICBaseClassDescriptor2".to_string(), + ], + ), + ( + "`RTTI Type Descriptor'".to_string(), + vec!["_TypeDescriptor".to_string()], + ), + ] + }); + + if let Some(last_name) = name.last() { + for (search_name, search_types) in name_to_type.iter() { + if last_name.contains(search_name) { + for search_type in search_types { + if let Some(ty) = self.named_types.get(search_type) { + // Fallback in case we don't find a specific one + t = Some(Conf::new( + Type::named_type_from_type(search_type, ty.as_ref()), + DEMANGLE_CONFIDENCE, + )); + + if self.settings.get_bool( + "pdb.features.expandRTTIStructures", + Some(self.bv), + None, + ) { + if let Some((lengthy_type, length)) = + self.make_lengthy_type(ty, self.bv.start() + rva.0 as u64)? + { + // See if we have a type with this length + let lengthy_name = + format!("${}$_extraBytes_{}", search_type, length); + + if let Some(ty) = self.named_types.get(&lengthy_name) { + // Wow! + t = Some(Conf::new( + Type::named_type_from_type(lengthy_name, ty.as_ref()), + DEMANGLE_CONFIDENCE, + )); + } else { + t = Some(Conf::new(lengthy_type, DEMANGLE_CONFIDENCE)); + } + } + } + } + } + } + } + } + + // VTables have types on their data symbols, + if let Some((class_name, last)) = name.join("::").rsplit_once("::") { + if last.contains("`vftable'") { + let mut vt_name = class_name.to_string() + "::" + "VTable"; + if last.contains("{for") { + // DerivedClass::`vftable'{for `BaseClass'} + let mut base_name = last.to_owned(); + base_name.drain(0..("`vftable'{for `".len())); + base_name.drain((base_name.len() - "'}".len())..(base_name.len())); + // Multiply inherited classes have multiple vtable types + // TODO: Do that + vt_name = base_name + "::" + "VTable"; + } + + vt_name = vt_name + .replace("class ", "") + .replace("struct ", "") + .replace("enum ", ""); + + if let Some(ty) = self.named_types.get(&vt_name) { + t = Some(Conf::new( + Type::named_type_from_type(&vt_name, ty.as_ref()), + DEMANGLE_CONFIDENCE, + )); + } else { + // Sometimes the demangler has trouble with `class Foo` in templates + vt_name = vt_name + .replace("class ", "") + .replace("struct ", "") + .replace("enum ", ""); + + if let Some(ty) = self.named_types.get(&vt_name) { + t = Some(Conf::new( + Type::named_type_from_type(&vt_name, ty.as_ref()), + DEMANGLE_CONFIDENCE, + )); + } else { + t = Some(Conf::new( + Type::named_type_from_type( + &vt_name, + Type::structure(StructureBuilder::new().finalize().as_ref()) + .as_ref(), + ), + DEMANGLE_CONFIDENCE, + )); + } + } + } + } + + if let Some(last_name) = name.last_mut() { + if last_name.starts_with("__imp_") { + last_name.drain(0..("__imp_".len())); + } + } + + let name = if name.len() == 1 && &name[0] == raw_name && raw_name.starts_with('?') { + None + } else if name.len() == 1 && name[0] == "" { + None + } else if name.len() > 0 && name[0].starts_with("\x7f") { + // Not sure why these exist but they do Weird Stuff + name[0].drain(0..1); + Some(QualifiedName::from(name)) + } else { + Some(QualifiedName::from(name)) + }; + + Ok((t, name)) + } + + fn make_lengthy_type( + &self, + base_type: &Ref, + base_address: u64, + ) -> Result, usize)>> { + if base_type.type_class() != TypeClass::StructureTypeClass { + return Ok(None); + } + let structure = base_type + .get_structure() + .map_err(|_| anyhow!("Expected structure"))?; + let mut members = structure + .members() + .map_err(|_| anyhow!("Expected structure to have members"))?; + let last_member = members + .last_mut() + .ok_or_else(|| anyhow!("Not enough members"))?; + + if last_member.ty.contents.type_class() != TypeClass::ArrayTypeClass { + return Ok(None); + } + if last_member.ty.contents.count() != 0 { + return Ok(None); + } + + let member_element = last_member + .ty + .contents + .element_type() + .map_err(|_| anyhow!("Last member has no type"))? + .contents; + let member_width = member_element.width(); + + // Read member_width bytes from bv starting at that member, until we read all zeroes + let member_address = base_address + last_member.offset; + + let mut bytes = Vec::::new(); + bytes.resize(member_width as usize, 0); + + let mut element_count = 0; + while self.bv.read( + bytes.as_mut_slice(), + member_address + member_width * element_count, + ) == member_width as usize + { + if bytes.iter().all(|&b| b == 0) { + break; + } + element_count += 1; + } + + // Make a new copy of the type with the correct element count + last_member.ty.contents = Type::array(member_element.as_ref(), element_count); + + Ok(Some(( + Type::structure(StructureBuilder::from(members).finalize().as_ref()), + element_count as usize, + ))) + } + + /// Sorry about the type names + /// Given a pdb::Register (u32), get a pdb::register::Register (big enum with names) + fn lookup_register(&self, reg: pdb::Register) -> Option { + if let Some(cpu) = self.module_cpu_type { + pdb::register::Register::new(reg, cpu).ok() + } else { + None + } + } + + /// Convert a pdb::Register (u32) to a binja register index for the current arch + fn convert_register(&self, reg: pdb::Register) -> Option { + match self.lookup_register(reg) { + Some(X86(xreg)) => { + self.log(|| format!("Register {:?} ==> {:?}", reg, xreg)); + self.arch + .register_by_name(xreg.to_string().to_lowercase()) + .map(|reg| reg.id() as i64) + } + Some(AMD64(areg)) => { + self.log(|| format!("Register {:?} ==> {:?}", reg, areg)); + self.arch + .register_by_name(areg.to_string().to_lowercase()) + .map(|reg| reg.id() as i64) + } + // TODO: Other arches + _ => None, + } + } +} diff --git a/examples/pdb-ng/src/type_parser.rs b/examples/pdb-ng/src/type_parser.rs new file mode 100644 index 0000000..caf732b --- /dev/null +++ b/examples/pdb-ng/src/type_parser.rs @@ -0,0 +1,2477 @@ +// Copyright 2022-2024 Vector 35 Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +use std::collections::HashMap; +use std::sync::OnceLock; + +use anyhow::{anyhow, Result}; +use binaryninja::architecture::{Architecture, CoreArchitecture}; +use binaryninja::binaryview::BinaryViewExt; +use binaryninja::callingconvention::CallingConvention; +use binaryninja::platform::Platform; +use binaryninja::rc::Ref; +use binaryninja::types::{ + max_confidence, BaseStructure, Conf, EnumerationBuilder, EnumerationMember, FunctionParameter, + MemberAccess, MemberScope, NamedTypeReference, NamedTypeReferenceClass, QualifiedName, + StructureBuilder, StructureMember, StructureType, Type, TypeBuilder, TypeClass, +}; +use log::warn; +use pdb::Error::UnimplementedTypeKind; +use pdb::{ + ArgumentList, ArrayType, BaseClassType, BitfieldType, ClassKind, ClassType, EnumerateType, + EnumerationType, FallibleIterator, FieldAttributes, FieldList, FunctionAttributes, Indirection, + ItemFinder, MemberFunctionType, MemberType, MethodList, MethodType, ModifierType, NestedType, + OverloadedMethodType, PointerMode, PointerType, PrimitiveKind, PrimitiveType, ProcedureType, + Source, StaticMemberType, TypeData, TypeIndex, UnionType, Variant, VirtualBaseClassType, + VirtualFunctionTablePointerType, VirtualFunctionTableType, VirtualTableShapeType, +}; +use regex::Regex; + +use crate::struct_grouper::group_structure; +use crate::PDBParserInstance; + +static BUILTIN_NAMES: &[&'static str] = &[ + "size_t", + "ssize_t", + "ptrdiff_t", + "wchar_t", + "wchar16", + "wchar32", + "bool", +]; +// const VOID_RETURN_CONFIDENCE: u8 = 16; + +/// Function types +#[derive(Debug, Clone)] +pub struct ParsedProcedureType { + /// Interpreted type of the method, with thisptr, __return, etc + pub method_type: Ref, + /// Base method type right outta the pdb with no frills + pub raw_method_type: Ref, +} + +/// Bitfield member type, if we ever get around to implementing these +#[derive(Debug, Clone)] +pub struct ParsedBitfieldType { + /// Size in bits + pub size: u64, + /// Bit offset in the current bitfield set + pub position: u64, + /// Underlying type of the whole bitfield set + pub ty: Ref, +} + +/// Parsed member of a class/structure, basically just binaryninja::StructureMember but with bitfields :( +#[derive(Debug, Clone)] +pub struct ParsedMember { + /// Member type + pub ty: Conf>, + /// Member name + pub name: String, + /// Offset in structure + pub offset: u64, + /// Access flags + pub access: MemberAccess, + /// Scope doesn't really mean anything in binja + pub scope: MemberScope, + /// Bitfield size, if this is in a bitfield. Mainly you should just be checking for Some() + pub bitfield_size: Option, + /// Bit offset, if this is in a bitfield. Mainly you should just be checking for Some() + pub bitfield_position: Option, +} + +/// Parsed named method of a class +#[derive(Debug, Clone)] +pub struct ParsedMethod { + /// Attributes from pdb-rs + pub attributes: FieldAttributes, + /// Name of method + pub name: String, + /// Type of the method + class info + pub method_type: ParsedMemberFunction, + /// Offset in class's virtual table, if virtual + pub vtable_offset: Option, +} + +/// One entry in a list of parsed methods? This is just here so overloaded methods have a struct to use +#[derive(Debug, Clone)] +pub struct ParsedMethodListEntry { + /// Attributes from pdb-rs + pub attributes: FieldAttributes, + /// Type of the method + class info + pub method_type: ParsedMemberFunction, + /// Offset in class's virtual table, if virtual + pub vtable_offset: Option, +} + +/// Parsed member function type info +#[derive(Debug, Clone)] +pub struct ParsedMemberFunction { + /// Attributes from pdb-rs + pub attributes: FunctionAttributes, + /// Parent class's name + pub class_name: String, + /// Interpreted type of the method, with thisptr, __return, etc + pub method_type: Ref, + /// Base method type right outta the pdb with no frills + pub raw_method_type: Ref, + /// Type of thisptr object, if relevant + pub this_pointer_type: Option>, + /// Adjust to thisptr at start, for virtual bases or something + pub this_adjustment: usize, +} + +/// Virtual base class, c++ nightmare fuel +#[derive(Debug, Clone)] +pub struct VirtualBaseClass { + /// Base class name + pub base_name: String, + /// Base class type + pub base_type: Ref, + /// Offset in this class where the base's fields are located + pub base_offset: u64, + /// Type of vbtable, probably + pub base_table_type: Ref, + /// Offset of this base in the vbtable + pub base_table_offset: u64, +} + +/// Mega enum of all the different types of types we can parse +#[derive(Debug, Clone)] +pub enum ParsedType { + /// No info other than type data + Bare(Ref), + /// Named fully parsed class/enum/union/etc type + Named(String, Ref), + /// Function procedure + Procedure(ParsedProcedureType), + /// Bitfield entries + BitfieldType(ParsedBitfieldType), + /// A list of members for a structure / union + FieldList(Vec), + /// One member in a structure/union + Member(ParsedMember), + /// Base class name and offset details + BaseClass(String, StructureMember), + /// One member in an enumeration + Enumerate(EnumerationMember), + /// List of arguments to a function + ArgumentList(Vec), + /// Parsed member function type info + MemberFunction(ParsedMemberFunction), + /// Parsed named method of a class + Method(ParsedMethod), + /// List of all the methods in a class + MethodList(Vec), + /// (Name, Overloads) equivalent to ParsedMethod + OverloadedMethod(String, Vec), + /// Virtual table shape straight outta pdb-rs + VTableShape(Vec), + /// Also virtual table shape, but you want a pointer this time + VTablePointer(Vec), + /// Virtual base class, c++ nightmare fuel + VBaseClass(VirtualBaseClass), +} + +#[allow(non_camel_case_types)] +#[derive(Debug)] +pub enum CV_call_t { + NEAR_C = 1, + FAR_C = 2, + NEAR_PASCAL = 3, + FAR_PASCAL = 4, + NEAR_FAST = 5, + FAR_FAST = 6, + SKIPPED = 7, + NEAR_STD = 8, + FAR_STD = 9, + NEAR_SYS = 10, + FAR_SYS = 11, + THISCALL = 12, + MIPSCALL = 13, + GENERIC = 14, + ALPHACALL = 15, + PPCCALL = 16, + SHCALL = 17, + ARMCALL = 18, + AM33CALL = 19, + TRICALL = 20, + SH5CALL = 21, + M32RCALL = 22, + ALWAYS_INLINED = 23, + NEAR_VECTOR = 24, + RESERVED = 25, +} + +impl TryFrom for CV_call_t { + type Error = anyhow::Error; + + fn try_from(value: u8) -> Result { + match value { + 0 => Err(anyhow!("Empty calling convention")), + 1 => Ok(Self::NEAR_C), + 2 => Ok(Self::FAR_C), + 3 => Ok(Self::NEAR_PASCAL), + 4 => Ok(Self::FAR_PASCAL), + 5 => Ok(Self::NEAR_FAST), + 6 => Ok(Self::FAR_FAST), + 7 => Ok(Self::SKIPPED), + 8 => Ok(Self::NEAR_STD), + 9 => Ok(Self::FAR_STD), + 10 => Ok(Self::NEAR_SYS), + 11 => Ok(Self::FAR_SYS), + 12 => Ok(Self::THISCALL), + 13 => Ok(Self::MIPSCALL), + 14 => Ok(Self::GENERIC), + 15 => Ok(Self::ALPHACALL), + 16 => Ok(Self::PPCCALL), + 17 => Ok(Self::SHCALL), + 18 => Ok(Self::ARMCALL), + 19 => Ok(Self::AM33CALL), + 20 => Ok(Self::TRICALL), + 21 => Ok(Self::SH5CALL), + 22 => Ok(Self::M32RCALL), + 23 => Ok(Self::ALWAYS_INLINED), + 24 => Ok(Self::NEAR_VECTOR), + 25 => Ok(Self::RESERVED), + e => Err(anyhow!("Unknown CV_call_t convention {}", e)), + } + } +} + +/// This is all done in the parser instance namespace because the lifetimes are impossible to +/// wrangle otherwise. +impl<'a, S: Source<'a> + 'a> PDBParserInstance<'a, S> { + /// Parse all the types in a pdb + pub fn parse_types( + &mut self, + progress: Box Result<()> + '_>, + ) -> Result<()> { + // Hack: This is needed for primitive types but it's not defined in the pdb itself + self.named_types + .insert("HRESULT".to_string(), Type::int(4, true)); + + let type_information = self.pdb.type_information()?; + let mut finder = type_information.finder(); + + let mut type_count = 0; + + // Do an initial pass on the types to find the full indexes for named types + // In case something like an array needs to reference them before they're fully defined + let mut prepass_types = type_information.iter(); + while let Some(ty) = prepass_types.next()? { + type_count += 1; + finder.update(&prepass_types); + match ty.parse() { + Ok(TypeData::Class(data)) => { + if !data.properties.forward_reference() { + self.full_type_indices.insert( + data.unique_name + .unwrap_or(data.name) + .to_string() + .to_string(), + ty.index(), + ); + } + } + Ok(TypeData::Enumeration(data)) => { + if !data.properties.forward_reference() { + self.full_type_indices.insert( + data.unique_name + .unwrap_or(data.name) + .to_string() + .to_string(), + ty.index(), + ); + } + } + Ok(TypeData::Union(data)) => { + if !data.properties.forward_reference() { + self.full_type_indices.insert( + data.unique_name + .unwrap_or(data.name) + .to_string() + .to_string(), + ty.index(), + ); + } + } + _ => {} + } + } + + self.log(|| format!("Now parsing named types")); + + // Parse the types we care about, so that recursion gives us parent relationships for free + let mut types = type_information.iter(); + let mut i = 0; + while let Some(ty) = types.next()? { + i += 1; + (progress)(i, type_count * 2)?; + + match ty.parse() { + Ok(TypeData::Class(_)) | Ok(TypeData::Enumeration(_)) | Ok(TypeData::Union(_)) => { + self.handle_type_index(ty.index(), &mut finder)?; + } + _ => {} + } + + assert!(self.namespace_stack.is_empty()); + assert!(self.type_stack.is_empty()); + } + + self.log(|| format!("Now parsing unused floating types")); + + // Parse the rest because symbols often use them + let mut postpass_types = type_information.iter(); + while let Some(ty) = postpass_types.next()? { + i += 1; + (progress)(i, type_count * 2)?; + + self.handle_type_index(ty.index(), &mut finder)?; + } + + self.log(|| format!("Now adding all unreferenced named types")); + // Any referenced named types that are only forward-declared will cause missing type references, + // so create empty types for those here. + for (_, parsed) in &self.indexed_types { + match parsed { + ParsedType::Bare(ty) if ty.type_class() == TypeClass::NamedTypeReferenceClass => { + // See if we have this type + let name = ty + .get_named_type_reference() + .map_err(|_| anyhow!("expected ntr"))? + .name() + .to_string(); + if Self::is_name_anonymous(&name) { + continue; + } + if self.named_types.contains_key(&name) { + continue; + } + // If the bv has this type, DebugInfo will just update us to reference it + if let Some(_) = self.bv.get_type_by_name(&name) { + continue; + } + + self.log(|| format!("Got undefined but referenced named type: {}", &name)); + let type_class = ty + .get_named_type_reference() + .map_err(|_| anyhow!("expected ntr"))? + .class(); + + let bare_type = match type_class { + NamedTypeReferenceClass::ClassNamedTypeClass => Type::structure( + StructureBuilder::new() + .set_structure_type(StructureType::ClassStructureType) + .finalize() + .as_ref(), + ), + // Missing typedefs are just going to become structures + NamedTypeReferenceClass::UnknownNamedTypeClass + | NamedTypeReferenceClass::TypedefNamedTypeClass + | NamedTypeReferenceClass::StructNamedTypeClass => { + Type::structure(StructureBuilder::new().finalize().as_ref()) + } + NamedTypeReferenceClass::UnionNamedTypeClass => Type::structure( + StructureBuilder::new() + .set_structure_type(StructureType::UnionStructureType) + .finalize() + .as_ref(), + ), + NamedTypeReferenceClass::EnumNamedTypeClass => Type::enumeration( + EnumerationBuilder::new().finalize().as_ref(), + self.arch.default_integer_size(), + false, + ), + }; + + self.log(|| format!("Bare type created: {} {}", &name, &bare_type)); + self.named_types.insert(name, bare_type); + } + _ => {} + } + } + + // Cleanup a couple builtin names + for &name in BUILTIN_NAMES { + if self.named_types.contains_key(name) { + self.named_types.remove(name); + self.log(|| format!("Remove builtin type {}", name)); + } + } + + static MEM: OnceLock = OnceLock::new(); + let uint_regex = MEM.get_or_init(|| { + Regex::new(r"u?int\d+_t").unwrap() + }); + + let float_regex = MEM.get_or_init(|| { + Regex::new(r"float\d+").unwrap() + }); + + let mut remove_names = vec![]; + for (name, _) in &self.named_types { + if uint_regex.is_match(name) { + remove_names.push(name.clone()); + } + if float_regex.is_match(name) { + remove_names.push(name.clone()); + } + } + for name in remove_names { + self.named_types.remove(&name); + self.log(|| format!("Remove builtin type {}", &name)); + } + + Ok(()) + } + + /// Lookup a type in the parsed types by its index (ie for a procedure) + pub(crate) fn lookup_type( + &self, + index: &TypeIndex, + fancy_procs: bool, + ) -> Result>> { + match self.indexed_types.get(index) { + Some(ParsedType::Bare(ty)) => Ok(Some(ty.clone())), + Some(ParsedType::Named(name, ty)) => Ok(Some(Type::named_type_from_type(name, &ty))), + Some(ParsedType::Procedure(ParsedProcedureType { + method_type, + raw_method_type, + })) => { + if fancy_procs { + Ok(Some(method_type.clone())) + } else { + Ok(Some(raw_method_type.clone())) + } + } + Some(ParsedType::MemberFunction(ParsedMemberFunction { + method_type, + raw_method_type, + .. + })) => { + if fancy_procs { + Ok(Some(method_type.clone())) + } else { + Ok(Some(raw_method_type.clone())) + } + } + Some(ParsedType::Member(ParsedMember { ty, .. })) => Ok(Some(ty.contents.clone())), + _ => Ok(None), + } + } + + /// Lookup a type in the parsed types and get a confidence value for it too + pub(crate) fn lookup_type_conf( + &self, + index: &TypeIndex, + fancy_procs: bool, + ) -> Result>>> { + match self.lookup_type(index, fancy_procs)? { + Some(ty) if ty.type_class() == TypeClass::VoidTypeClass => Ok(Some(Conf::new(ty, 0))), + Some(ty) => { + let mut confidence = max_confidence(); + + // Extra check here for void(void) functions, they should get minimum confidence since this + // is the signature PDB uses when it doesn't actually know the signature + if ty.type_class() == TypeClass::FunctionTypeClass { + if let Ok(ret) = ty.return_value() { + if ret.contents.type_class() == TypeClass::VoidTypeClass { + if let Ok(params) = ty.parameters() { + if params.len() == 0 { + confidence = 0; + } + } + } + } + } + + // Also array of bare function pointers (often seen in vtables) + // These should not be marked confidently, as they don't actually know + // the types of their contents + + if ty.type_class() == TypeClass::ArrayTypeClass { + if let Ok(ptr) = ty.element_type() { + if ptr.contents.type_class() == TypeClass::PointerTypeClass { + if let Ok(fun) = ptr.contents.target() { + if fun.contents.type_class() == TypeClass::FunctionTypeClass + && fun + .contents + .parameters() + .map(|pars| pars.len()) + .unwrap_or(0) + == 0 + { + if let Ok(ret) = fun.contents.return_value() { + if ret.contents.type_class() == TypeClass::VoidTypeClass { + confidence = 0; + } + } + } + } + } + } + } + + Ok(Some(Conf::new(ty, confidence))) + } + None => Ok(None), + } + } + + /// Parse and return a type by its index, used as lookup-or-parse + fn handle_type_index( + &mut self, + ty: TypeIndex, + finder: &mut ItemFinder, + ) -> Result> { + if let None = self.indexed_types.get(&ty) { + self.log(|| format!("Parsing Type {:x?} ", ty)); + + match finder.find(ty).and_then(|item| item.parse()) { + Ok(data) => { + self.type_stack.push(ty); + let handled = self.handle_type(&data, finder); + self.type_stack.pop(); + + match handled { + Ok(Some(parsed)) => { + self.log(|| format!("Type {} parsed into: {:?}", ty, parsed)); + match &*parsed { + ParsedType::Named(name, parsed) => { + // PDB does this thing where anonymous inner types are represented as + // some_type:: + if !Self::is_name_anonymous(name) { + if let Some(_old) = + self.named_types.insert(name.clone(), parsed.clone()) + { + warn!("Found two types both named `{}`, only one will be used.", name); + } + } + } + _ => {} + } + self.indexed_types.insert(ty, *parsed); + } + e => { + self.log(|| format!("Error parsing type {}: {:x?}", ty, e)); + } + } + } + Err(UnimplementedTypeKind(k)) if k != 0 => { + warn!("Not parsing unimplemented type {}: kind {:x?}", ty, k); + } + Err(e) => { + self.log(|| format!("Could not parse type: {}: {}", ty, e)); + } + }; + } + + Ok(self.indexed_types.get(&ty)) + } + + /// Parse a new type's data + fn handle_type( + &mut self, + data: &TypeData, + finder: &mut ItemFinder, + ) -> Result>> { + match data { + TypeData::Primitive(data) => Ok(self.handle_primitive_type(&data, finder)?), + TypeData::Class(data) => Ok(self.handle_class_type(&data, finder)?), + TypeData::Member(data) => Ok(self.handle_member_type(&data, finder)?), + TypeData::MemberFunction(data) => Ok(self.handle_member_function_type(&data, finder)?), + TypeData::OverloadedMethod(data) => { + Ok(self.handle_overloaded_method_type(&data, finder)?) + } + TypeData::Method(data) => Ok(self.handle_method_type(&data, finder)?), + TypeData::StaticMember(data) => Ok(self.handle_static_member_type(&data, finder)?), + TypeData::Nested(data) => Ok(self.handle_nested_type(&data, finder)?), + TypeData::BaseClass(data) => Ok(self.handle_base_class_type(&data, finder)?), + TypeData::VirtualBaseClass(data) => { + Ok(self.handle_virtual_base_class_type(&data, finder)?) + } + TypeData::VirtualFunctionTable(data) => { + Ok(self.handle_virtual_function_table_type(&data, finder)?) + } + TypeData::VirtualTableShape(data) => { + Ok(self.handle_virtual_table_shape_type(&data, finder)?) + } + TypeData::VirtualFunctionTablePointer(data) => { + Ok(self.handle_virtual_function_table_pointer_type(&data, finder)?) + } + TypeData::Procedure(data) => Ok(self.handle_procedure_type(&data, finder)?), + TypeData::Pointer(data) => Ok(self.handle_pointer_type(&data, finder)?), + TypeData::Modifier(data) => Ok(self.handle_modifier_type(&data, finder)?), + TypeData::Enumeration(data) => Ok(self.handle_enumeration_type(&data, finder)?), + TypeData::Enumerate(data) => Ok(self.handle_enumerate_type(&data, finder)?), + TypeData::Array(data) => Ok(self.handle_array_type(&data, finder)?), + TypeData::Union(data) => Ok(self.handle_union_type(&data, finder)?), + TypeData::Bitfield(data) => Ok(self.handle_bitfield_type(&data, finder)?), + TypeData::FieldList(data) => Ok(self.handle_field_list_type(&data, finder)?), + TypeData::ArgumentList(data) => Ok(self.handle_argument_list_type(&data, finder)?), + TypeData::MethodList(data) => Ok(self.handle_method_list_type(&data, finder)?), + _ => Err(anyhow!("Unknown typedata")), + } + } + + /// Get the raw (mangled) name out of a type, if possible + fn type_data_to_raw_name(data: &TypeData) -> Option { + match data { + TypeData::Class(data) => Some( + data.unique_name + .unwrap_or(data.name) + .to_string() + .to_string(), + ), + TypeData::Enumeration(data) => Some( + data.unique_name + .unwrap_or(data.name) + .to_string() + .to_string(), + ), + TypeData::Union(data) => Some( + data.unique_name + .unwrap_or(data.name) + .to_string() + .to_string(), + ), + _ => None, + } + } + + fn handle_primitive_type( + &mut self, + data: &PrimitiveType, + _finder: &mut ItemFinder, + ) -> Result>> { + self.log(|| format!("Got Primitive type: {:x?}", data)); + let base = match data.kind { + PrimitiveKind::NoType => Ok(Type::void()), + PrimitiveKind::Void => Ok(Type::void()), + PrimitiveKind::Char => Ok(Type::int(1, true)), + PrimitiveKind::UChar => Ok(Type::int(1, false)), + PrimitiveKind::RChar => Ok(Type::int(1, true)), + PrimitiveKind::WChar => Ok(Type::wide_char(2)), + PrimitiveKind::RChar16 => Ok(Type::wide_char(2)), + PrimitiveKind::RChar32 => Ok(Type::wide_char(4)), + PrimitiveKind::I8 => Ok(Type::int(1, true)), + PrimitiveKind::U8 => Ok(Type::int(1, false)), + PrimitiveKind::Short => Ok(Type::int(2, true)), + PrimitiveKind::UShort => Ok(Type::int(2, false)), + PrimitiveKind::I16 => Ok(Type::int(2, true)), + PrimitiveKind::U16 => Ok(Type::int(2, false)), + PrimitiveKind::Long => Ok(Type::int(4, true)), + PrimitiveKind::ULong => Ok(Type::int(4, false)), + PrimitiveKind::I32 => Ok(Type::int(4, true)), + PrimitiveKind::U32 => Ok(Type::int(4, false)), + PrimitiveKind::Quad => Ok(Type::int(8, true)), + PrimitiveKind::UQuad => Ok(Type::int(8, false)), + PrimitiveKind::I64 => Ok(Type::int(8, true)), + PrimitiveKind::U64 => Ok(Type::int(8, false)), + PrimitiveKind::Octa => Ok(Type::int(16, true)), + PrimitiveKind::UOcta => Ok(Type::int(16, false)), + PrimitiveKind::I128 => Ok(Type::int(16, true)), + PrimitiveKind::U128 => Ok(Type::int(16, false)), + PrimitiveKind::F16 => Ok(Type::float(2)), + PrimitiveKind::F32 => Ok(Type::float(4)), + PrimitiveKind::F32PP => Ok(Type::float(4)), + PrimitiveKind::F48 => Ok(Type::float(6)), + PrimitiveKind::F64 => Ok(Type::float(8)), + PrimitiveKind::F80 => Ok(Type::float(10)), + PrimitiveKind::F128 => Ok(Type::float(16)), + PrimitiveKind::Complex32 => Err(anyhow!("Complex32 unimplmented")), + PrimitiveKind::Complex64 => Err(anyhow!("Complex64 unimplmented")), + PrimitiveKind::Complex80 => Err(anyhow!("Complex80 unimplmented")), + PrimitiveKind::Complex128 => Err(anyhow!("Complex128 unimplmented")), + PrimitiveKind::Bool8 => Ok(Type::int(1, false)), + PrimitiveKind::Bool16 => Ok(Type::int(2, false)), + PrimitiveKind::Bool32 => Ok(Type::int(4, false)), + PrimitiveKind::Bool64 => Ok(Type::int(8, false)), + // Hack: this isn't always defined + PrimitiveKind::HRESULT => Ok(Type::named_type_from_type( + "HRESULT", + Type::int(4, true).as_ref(), + )), + _ => Err(anyhow!("Unknown type unimplmented")), + }?; + + // TODO: Pointer suffix is not exposed + match data.indirection { + Some(Indirection::Near16) => Ok(Some(Box::new(ParsedType::Bare(Type::pointer( + &self.arch, + base.as_ref(), + ))))), + Some(Indirection::Far16) => Ok(Some(Box::new(ParsedType::Bare(Type::pointer( + &self.arch, + base.as_ref(), + ))))), + Some(Indirection::Huge16) => Ok(Some(Box::new(ParsedType::Bare(Type::pointer( + &self.arch, + base.as_ref(), + ))))), + Some(Indirection::Near32) => Ok(Some(Box::new(ParsedType::Bare(Type::pointer( + &self.arch, + base.as_ref(), + ))))), + Some(Indirection::Far32) => Ok(Some(Box::new(ParsedType::Bare(Type::pointer( + &self.arch, + base.as_ref(), + ))))), + Some(Indirection::Near64) => Ok(Some(Box::new(ParsedType::Bare(Type::pointer( + &self.arch, + base.as_ref(), + ))))), + Some(Indirection::Near128) => Ok(Some(Box::new(ParsedType::Bare(Type::pointer( + &self.arch, + base.as_ref(), + ))))), + None => Ok(Some(Box::new(ParsedType::Bare(base)))), + } + } + + fn handle_class_type( + &mut self, + data: &ClassType, + finder: &mut ItemFinder, + ) -> Result>> { + self.log(|| format!("Got Class type: {:x?}", data)); + + let raw_class_name = &data.name.to_string(); + let class_name = raw_class_name.to_string(); + + self.log(|| format!("Named: {}", class_name)); + + if data.properties.forward_reference() { + // Try and find it first + if let Some(existing) = self.named_types.get(&class_name) { + return Ok(Some(Box::new(ParsedType::Bare( + Type::named_type_from_type(&class_name, existing), + )))); + } + + let ntr_class = match data.kind { + ClassKind::Class => NamedTypeReferenceClass::ClassNamedTypeClass, + ClassKind::Struct => NamedTypeReferenceClass::StructNamedTypeClass, + ClassKind::Interface => NamedTypeReferenceClass::StructNamedTypeClass, + }; + return Ok(Some(Box::new(ParsedType::Bare(Type::named_type( + &*NamedTypeReference::new(ntr_class, QualifiedName::from(class_name)), + ))))); + } + + let struct_kind = match &data.kind { + ClassKind::Class => StructureType::ClassStructureType, + ClassKind::Struct => StructureType::StructStructureType, + ClassKind::Interface => StructureType::StructStructureType, + }; + + let mut structure = StructureBuilder::new(); + structure.set_structure_type(struct_kind); + structure.set_width(data.size); + structure.set_packed(data.properties.packed()); + + if let Some(fields) = data.fields { + self.namespace_stack.push(class_name.to_string()); + let success = self.parse_structure_fields(&mut structure, fields, finder); + self.namespace_stack.pop(); + let _ = success?; + } + + let new_type = Type::structure(structure.finalize().as_ref()); + Ok(Some(Box::new(ParsedType::Named(class_name, new_type)))) + } + + /// Handle all the structure field parsing for a given field list, putting the fields into a struct + fn parse_structure_fields( + &mut self, + structure: &mut StructureBuilder, + fields: TypeIndex, + finder: &mut ItemFinder, + ) -> Result<()> { + let mut base_classes = vec![]; + let mut virt_methods = HashMap::new(); + let mut non_virt_methods = Vec::new(); + + let mut members = vec![]; + + match self.handle_type_index(fields, finder)? { + Some(ParsedType::FieldList(fields)) => { + for field in fields { + match field { + ParsedType::Member(member) => { + members.push(member.clone()); + } + b @ ParsedType::BaseClass(..) => { + base_classes.push(b.clone()); + } + b @ ParsedType::VBaseClass(..) => { + base_classes.push(b.clone()); + } + ParsedType::Named(..) => {} + ParsedType::VTablePointer(_vt) => {} + ParsedType::Method(method) => { + if let Some(offset) = method.vtable_offset { + virt_methods.insert( + offset, + (method.name.clone(), method.method_type.clone()), + ); + } else { + non_virt_methods + .push((method.name.clone(), method.method_type.clone())); + } + } + ParsedType::OverloadedMethod(name, methods) => { + for method in methods { + if let Some(offset) = method.vtable_offset { + virt_methods + .insert(offset, (name.clone(), method.method_type.clone())); + } + } + } + f => { + return Err(anyhow!("Unexpected field type {:?}", f)); + } + } + } + } + Some(_) => { + return Err(anyhow!( + "Structure fields list did not parse into member list?" + )); + } + // No fields? + None => {} + } + + // Combine bitfields into structures + let mut combined_bitfield_members = vec![]; + let mut last_bitfield_offset = u64::MAX; + let mut last_bitfield_pos = u64::MAX; + let mut last_bitfield_idx = 0; + let mut bitfield_builder: Option = None; + + fn bitfield_name(offset: u64, idx: u64) -> String { + if idx > 0 { + format!("__bitfield{:x}_{}", offset, idx) + } else { + format!("__bitfield{:x}", offset) + } + } + + for m in members { + match (m.bitfield_position, m.bitfield_size) { + (Some(pos), Some(_size)) => { + if last_bitfield_offset != m.offset || last_bitfield_pos >= pos { + if let Some(builder) = bitfield_builder.take() { + combined_bitfield_members.push(ParsedMember { + ty: Conf::new( + Type::structure(builder.finalize().as_ref()), + max_confidence(), + ), + name: bitfield_name(last_bitfield_offset, last_bitfield_idx), + offset: last_bitfield_offset, + access: MemberAccess::PublicAccess, + scope: MemberScope::NoScope, + bitfield_size: None, + bitfield_position: None, + }); + } + let new_builder = StructureBuilder::new(); + new_builder.set_structure_type(StructureType::UnionStructureType); + new_builder.set_width(m.ty.contents.width()); + bitfield_builder = Some(new_builder); + + if last_bitfield_offset != m.offset { + last_bitfield_idx = 0; + } else { + last_bitfield_idx += 1; + } + } + + last_bitfield_pos = pos; + last_bitfield_offset = m.offset; + bitfield_builder + .as_mut() + .expect("Invariant") + .insert(&m.ty, m.name, 0, false, m.access, m.scope); + } + (None, None) => { + if let Some(builder) = bitfield_builder.take() { + combined_bitfield_members.push(ParsedMember { + ty: Conf::new( + Type::structure(builder.finalize().as_ref()), + max_confidence(), + ), + name: bitfield_name(last_bitfield_offset, last_bitfield_idx), + offset: last_bitfield_offset, + access: MemberAccess::PublicAccess, + scope: MemberScope::NoScope, + bitfield_size: None, + bitfield_position: None, + }); + } + last_bitfield_offset = u64::MAX; + last_bitfield_pos = u64::MAX; + combined_bitfield_members.push(m); + } + e => return Err(anyhow!("Unexpected bitfield parameters {:?}", e)), + } + } + if let Some(builder) = bitfield_builder.take() { + combined_bitfield_members.push(ParsedMember { + ty: Conf::new( + Type::structure(builder.finalize().as_ref()), + max_confidence(), + ), + name: bitfield_name(last_bitfield_offset, last_bitfield_idx), + offset: last_bitfield_offset, + access: MemberAccess::PublicAccess, + scope: MemberScope::NoScope, + bitfield_size: None, + bitfield_position: None, + }); + } + members = combined_bitfield_members; + group_structure( + &format!( + "`{}`", + self.namespace_stack + .last() + .ok_or_else(|| anyhow!("Expected class in ns stack"))? + ), + &members, + structure, + )?; + + let mut bases = vec![]; + + for base_class in &base_classes { + match base_class { + ParsedType::BaseClass(name, base) => { + let ntr_class = match self.named_types.get(name) { + Some(ty) if ty.type_class() == TypeClass::StructureTypeClass => { + match ty.get_structure() { + Ok(str) + if str.structure_type() + == StructureType::StructStructureType => + { + NamedTypeReferenceClass::StructNamedTypeClass + } + Ok(str) + if str.structure_type() + == StructureType::ClassStructureType => + { + NamedTypeReferenceClass::ClassNamedTypeClass + } + _ => NamedTypeReferenceClass::StructNamedTypeClass, + } + } + _ => NamedTypeReferenceClass::StructNamedTypeClass, + }; + bases.push(BaseStructure::new( + NamedTypeReference::new(ntr_class, name.into()), + base.offset, + base.ty.contents.width(), + )); + } + ParsedType::VBaseClass(VirtualBaseClass { + base_name, + base_type, + base_offset, + .. + }) => { + let ntr_class = match self.named_types.get(base_name) { + Some(ty) if ty.type_class() == TypeClass::StructureTypeClass => { + match ty.get_structure() { + Ok(str) + if str.structure_type() + == StructureType::StructStructureType => + { + NamedTypeReferenceClass::StructNamedTypeClass + } + Ok(str) + if str.structure_type() + == StructureType::ClassStructureType => + { + NamedTypeReferenceClass::ClassNamedTypeClass + } + _ => NamedTypeReferenceClass::StructNamedTypeClass, + } + } + _ => NamedTypeReferenceClass::StructNamedTypeClass, + }; + bases.push(BaseStructure::new( + NamedTypeReference::new(ntr_class, base_name.into()), + *base_offset, + base_type.width(), + )); + warn!( + "Class `{}` uses virtual inheritance. Type information may be inaccurate.", + self.namespace_stack + .last() + .ok_or_else(|| anyhow!("Expected class in ns stack"))? + ); + } + e => return Err(anyhow!("Unexpected base class type: {:x?}", e)), + } + } + + if bases.len() > 1 { + warn!( + "Class `{}` has multiple base classes. Type information may be inaccurate.", + self.namespace_stack + .last() + .ok_or_else(|| anyhow!("Expected class in ns stack"))? + ); + } + structure.set_base_structures(bases); + + if self + .settings + .get_bool("pdb.features.generateVTables", Some(self.bv), None) + && !virt_methods.is_empty() + { + let vt = StructureBuilder::new(); + + let mut vt_bases = vec![]; + + for base_class in &base_classes { + match base_class { + ParsedType::BaseClass(base_name, _base_type) => { + let mut vt_base_name = base_name + .split("::") + .into_iter() + .map(|s| s.to_string()) + .collect::>(); + vt_base_name.push("VTable".to_string()); + let vt_base_name = vt_base_name.join("::"); + + match self.named_types.get(&vt_base_name) { + Some(vt_base_type) + if vt_base_type.type_class() == TypeClass::StructureTypeClass => + { + let ntr_class = + if vt_base_type.type_class() == TypeClass::StructureTypeClass { + match vt_base_type.get_structure() { + Ok(str) + if str.structure_type() + == StructureType::StructStructureType => + { + NamedTypeReferenceClass::StructNamedTypeClass + } + Ok(str) + if str.structure_type() + == StructureType::ClassStructureType => + { + NamedTypeReferenceClass::ClassNamedTypeClass + } + _ => NamedTypeReferenceClass::StructNamedTypeClass, + } + } else { + NamedTypeReferenceClass::StructNamedTypeClass + }; + vt_bases.push(BaseStructure::new( + NamedTypeReference::new(ntr_class, vt_base_name.into()), + 0, + vt_base_type.width(), + )); + } + e @ Some(_) => { + return Err(anyhow!("Unexpected vtable base class: {:?}", e)) + } + None => { + // Parent might just not have a vtable + } + } + } + ParsedType::VBaseClass(_vbase) => {} + e => return Err(anyhow!("Unexpected base class type: {:x?}", e)), + } + } + + let mut min_width = 0; + for base in &vt_bases { + min_width = min_width.max(base.width); + } + + vt.set_base_structures(vt_bases); + vt.set_propagates_data_var_refs(true); + + for (offset, (name, method)) in virt_methods { + vt.insert( + &Conf::new( + Type::pointer(&self.arch, &Conf::new(method.method_type, max_confidence())), + max_confidence(), + ), + &name, + offset as u64, + true, + MemberAccess::PublicAccess, + MemberScope::NoScope, + ); + min_width = min_width.max((offset + self.arch.address_size()) as u64); + } + + vt.set_width(min_width); + + let vt_type = Type::structure(vt.finalize().as_ref()); + // Need to insert a new named type for the vtable + let mut vt_name = self + .namespace_stack + .last() + .ok_or_else(|| anyhow!("Expected class in ns stack"))? + .clone(); + vt_name += "::VTable"; + self.named_types.insert(vt_name.clone(), vt_type.clone()); + + let vt_pointer = Type::pointer( + &self.arch, + &Conf::new( + Type::named_type_from_type(&QualifiedName::from(vt_name), vt_type.as_ref()), + max_confidence(), + ), + ); + + structure.insert( + &Conf::new(vt_pointer, max_confidence()), + "vtable", + 0, + true, + MemberAccess::PublicAccess, + MemberScope::NoScope, + ); + } + + Ok(()) + } + + fn handle_member_type( + &mut self, + data: &MemberType, + finder: &mut ItemFinder, + ) -> Result>> { + self.log(|| format!("Got Member type: {:x?}", data)); + + let member_name = data.name.to_string(); + let member_offset = data.offset; + let member_attrs = data.attributes; + + let access = match member_attrs.access() { + 1 /* CV_private */ => MemberAccess::PrivateAccess, + 2 /* CV_protected */ => MemberAccess::ProtectedAccess, + 3 /* CV_public */ => MemberAccess::PublicAccess, + _ => return Err(anyhow!("Unknown access")) + }; + + let scope = MemberScope::NoScope; + + match self.try_type_index_to_bare(data.field_type, finder, true)? { + Some(ty) => Ok(Some(Box::new(ParsedType::Member(ParsedMember { + ty: Conf::new(ty, max_confidence()), + name: member_name.into_owned(), + offset: member_offset, + access, + scope, + bitfield_position: None, + bitfield_size: None, + })))), + None => match self.handle_type_index(data.field_type, finder)? { + Some(ParsedType::BitfieldType(bitfield)) => { + Ok(Some(Box::new(ParsedType::Member(ParsedMember { + ty: Conf::new(bitfield.ty.clone(), max_confidence()), + name: member_name.into_owned(), + offset: member_offset, + access, + scope, + bitfield_position: Some(bitfield.position), + bitfield_size: Some(bitfield.size), + })))) + } + e => Err(anyhow!("Unexpected member type: {:x?}", e)), + }, + } + } + + fn handle_member_function_type( + &mut self, + data: &MemberFunctionType, + finder: &mut ItemFinder, + ) -> Result>> { + self.log(|| format!("Got MemberFunction type: {:x?}", data)); + let return_type = self.type_index_to_bare(data.return_type, finder, false)?; + + let class_name = match self.handle_type_index(data.class_type, finder)? { + Some(ParsedType::Bare(ty)) if ty.type_class() == TypeClass::NamedTypeReferenceClass => { + ty.get_named_type_reference() + .map_err(|_| anyhow!("Expected NTR to have NTR"))? + .name() + .to_string() + } + e => return Err(anyhow!("Unexpected class type: {:x?}", e)), + }; + + let this_pointer_type = if let Some(this_pointer_type) = data.this_pointer_type { + match self.handle_type_index(this_pointer_type, finder)? { + Some(ParsedType::Bare(ty)) => Some(ty.clone()), + e => return Err(anyhow!("Unexpected this pointer type: {:x?}", e)), + } + } else { + None + }; + + let mut arguments = match self.handle_type_index(data.argument_list, finder)? { + Some(ParsedType::ArgumentList(args)) => args.clone(), + e => return Err(anyhow!("Unexpected argument list type: {:x?}", e)), + }; + + // It looks like pdb stores varargs by having the final argument be void + let mut is_varargs = false; + if let Some(last) = arguments.pop() { + if last.t.contents.as_ref().type_class() == TypeClass::VoidTypeClass { + is_varargs = true; + } else { + arguments.push(last); + } + } + + let mut fancy_return_type = return_type.clone(); + let mut fancy_arguments = arguments.clone(); + + if data.attributes.cxx_return_udt() + || !self.can_fit_in_register(data.return_type, finder, true) + { + // Return UDT?? + // This probably means the return value got pushed to the stack + fancy_return_type = Type::pointer( + &self.arch, + &Conf::new(return_type.clone(), max_confidence()), + ); + fancy_arguments.insert( + 0, + FunctionParameter::new( + Conf::new(fancy_return_type.clone(), max_confidence()), + "__return".to_string(), + None, + ), + ); + } + + if let Some(this_ptr) = &this_pointer_type { + self.insert_this_pointer(&mut fancy_arguments, this_ptr.clone())?; + } + + let convention = self + .cv_call_t_to_calling_convention(data.attributes.calling_convention()) + .map(|cc| Conf::new(cc, max_confidence())) + .unwrap_or({ + if is_varargs { + Conf::new(self.cdecl_cc.clone(), max_confidence()) + } else if this_pointer_type.is_some() { + Conf::new(self.thiscall_cc.clone(), max_confidence()) + } else { + Conf::new(self.default_cc.clone(), 16) + } + }); + + let func = Type::function_with_options( + &Conf::new(return_type, max_confidence()), + arguments.as_slice(), + is_varargs, + &convention, + Conf::new(0, 0), + ); + + let fancy_func = Type::function_with_options( + &Conf::new(fancy_return_type, max_confidence()), + fancy_arguments.as_slice(), + is_varargs, + &convention, + Conf::new(0, 0), + ); + + Ok(Some(Box::new(ParsedType::MemberFunction( + ParsedMemberFunction { + attributes: data.attributes, + class_name, + method_type: fancy_func, + raw_method_type: func, + this_pointer_type, + this_adjustment: data.this_adjustment as usize, + }, + )))) + } + + fn handle_overloaded_method_type( + &mut self, + data: &OverloadedMethodType, + finder: &mut ItemFinder, + ) -> Result>> { + self.log(|| format!("Got OverloadedMethod type: {:x?}", data)); + // This is just a MethodList in disguise + let method_list = match self.handle_type_index(data.method_list, finder)? { + Some(ParsedType::MethodList(list)) => list.clone(), + e => return Err(anyhow!("Unexpected method list type: {:x?}", e)), + }; + + Ok(Some(Box::new(ParsedType::OverloadedMethod( + data.name.to_string().to_string(), + method_list, + )))) + } + + fn handle_method_type( + &mut self, + data: &MethodType, + finder: &mut ItemFinder, + ) -> Result>> { + self.log(|| format!("Got Method type: {:x?}", data)); + + let member_function = match self.handle_type_index(data.method_type, finder)? { + Some(ParsedType::MemberFunction(func)) => func.clone(), + e => return Err(anyhow!("Unexpected method type {:?}", e)), + }; + + Ok(Some(Box::new(ParsedType::Method(ParsedMethod { + attributes: data.attributes, + name: data.name.to_string().to_string(), + method_type: member_function, + vtable_offset: data.vtable_offset.map(|o| o as usize), + })))) + } + + fn handle_static_member_type( + &mut self, + data: &StaticMemberType, + _finder: &mut ItemFinder, + ) -> Result>> { + self.log(|| format!("Got StaticMember type: {:x?}", data)); + // TODO: Not handling these + Ok(None) + } + + fn handle_nested_type( + &mut self, + data: &NestedType, + finder: &mut ItemFinder, + ) -> Result>> { + self.log(|| format!("Got Nested type: {:x?}", data)); + let mut class_name_ns = self.namespace_stack.clone(); + class_name_ns.push(data.name.to_string().to_string()); + let ty = self.type_index_to_bare(data.nested_type, finder, false)?; + Ok(Some(Box::new(ParsedType::Named( + class_name_ns.join("::"), + ty, + )))) + } + + fn handle_base_class_type( + &mut self, + data: &BaseClassType, + finder: &mut ItemFinder, + ) -> Result>> { + self.log(|| format!("Got BaseClass type: {:x?}", data)); + + let base_offset = data.offset; + let base_attrs = data.attributes; + + let (member_name, t) = match self.handle_type_index(data.base_class, finder)? { + Some(ParsedType::Named(n, t)) => (n.clone(), t.clone()), + Some(ParsedType::Bare(t)) if t.type_class() == TypeClass::NamedTypeReferenceClass => { + let name = t + .get_named_type_reference() + .map_err(|_| anyhow!("Expected NTR to have NTR"))? + .name() + .to_string(); + (name, t.clone()) + } + e => return Err(anyhow!("Unexpected base class type: {:x?}", e)), + }; + + // Try to resolve the full base type + let resolved_type = match self.try_type_index_to_bare(data.base_class, finder, true)? { + Some(ty) => Type::named_type_from_type(&member_name, ty.as_ref()), + None => t.clone(), + }; + + let access = match base_attrs.access() { + 1 /* CV_private */ => MemberAccess::PrivateAccess, + 2 /* CV_protected */ => MemberAccess::ProtectedAccess, + 3 /* CV_public */ => MemberAccess::PublicAccess, + _ => return Err(anyhow!("Unknown access")) + }; + + let scope = MemberScope::NoScope; + Ok(Some(Box::new(ParsedType::BaseClass( + member_name.clone(), + StructureMember::new( + Conf::new(resolved_type, max_confidence()), + member_name, + base_offset as u64, + access, + scope, + ), + )))) + } + + fn handle_virtual_base_class_type( + &mut self, + data: &VirtualBaseClassType, + finder: &mut ItemFinder, + ) -> Result>> { + self.log(|| format!("Got VirtualBaseClass type: {:x?}", data)); + + let (n, ty) = match self.handle_type_index(data.base_class, finder)? { + Some(ParsedType::Named(n, t)) => (n.clone(), t.clone()), + Some(ParsedType::Bare(t)) if t.type_class() == TypeClass::NamedTypeReferenceClass => { + let name = t + .get_named_type_reference() + .map_err(|_| anyhow!("Expected NTR to have NTR"))? + .name() + .to_string(); + (name, t.clone()) + } + e => return Err(anyhow!("Unexpected base class type: {:x?}", e)), + }; + + // In addition to the base class, we also have a vbtable + let vbptr_type = match self.handle_type_index(data.base_pointer, finder)? { + Some(ParsedType::Bare(t)) => t.clone(), + e => return Err(anyhow!("Unexpected virtual base pointer type: {:x?}", e)), + }; + + Ok(Some(Box::new(ParsedType::VBaseClass(VirtualBaseClass { + base_name: n, + base_type: ty, + base_offset: data.base_pointer_offset as u64, + base_table_type: vbptr_type, + base_table_offset: data.virtual_base_offset as u64, + })))) + } + + fn handle_virtual_function_table_type( + &mut self, + data: &VirtualFunctionTableType, + _finder: &mut ItemFinder, + ) -> Result>> { + self.log(|| format!("Got VirtualFunctionTableType type: {:x?}", data)); + Err(anyhow!("VirtualFunctionTableType unimplemented")) + } + + fn handle_virtual_table_shape_type( + &mut self, + data: &VirtualTableShapeType, + _finder: &mut ItemFinder, + ) -> Result>> { + self.log(|| format!("Got VirtualTableShapeType type: {:x?}", data)); + Ok(Some(Box::new(ParsedType::VTableShape( + data.descriptors.clone(), + )))) + } + + fn handle_virtual_function_table_pointer_type( + &mut self, + data: &VirtualFunctionTablePointerType, + finder: &mut ItemFinder, + ) -> Result>> { + self.log(|| format!("Got VirtualFunctionTablePointer type: {:x?}", data)); + let shape = match self.handle_type_index(data.table, finder)? { + Some(ParsedType::VTablePointer(shape)) => shape.clone(), + e => { + return Err(anyhow!( + "Could not parse virtual function table pointer type: {:x?}", + e + )) + } + }; + + Ok(Some(Box::new(ParsedType::VTablePointer(shape)))) + } + + fn handle_procedure_type( + &mut self, + data: &ProcedureType, + finder: &mut ItemFinder, + ) -> Result>> { + self.log(|| format!("Got Procedure type: {:x?}", data)); + let return_type = if let Some(return_type_index) = data.return_type { + self.try_type_index_to_bare(return_type_index, finder, false)? + } else { + None + } + .map(|r| Conf::new(r, max_confidence())) + .unwrap_or(Conf::new(Type::void(), 0)); + + let mut arguments = match self.handle_type_index(data.argument_list, finder)? { + Some(ParsedType::ArgumentList(args)) => args.clone(), + e => return Err(anyhow!("Unexpected argument list type: {:x?}", e)), + }; + + // It looks like pdb stores varargs by having the final argument be void + let mut is_varargs = false; + if let Some(last) = arguments.pop() { + if last.t.contents.as_ref().type_class() == TypeClass::VoidTypeClass { + is_varargs = true; + } else { + arguments.push(last); + } + } + + let mut fancy_return_type = return_type.clone(); + let mut fancy_arguments = arguments.clone(); + + let mut return_stacky = data.attributes.cxx_return_udt(); + if let Some(return_type_index) = data.return_type { + return_stacky |= !self.can_fit_in_register(return_type_index, finder, true); + } + if return_stacky { + // Stack return via a pointer in the first parameter + fancy_return_type = + Conf::new(Type::pointer(&self.arch, &return_type), max_confidence()); + fancy_arguments.insert( + 0, + FunctionParameter::new(fancy_return_type.clone(), "__return".to_string(), None), + ); + } + + let convention = self + .cv_call_t_to_calling_convention(data.attributes.calling_convention()) + .map(|cc| Conf::new(cc, max_confidence())) + .unwrap_or(Conf::new(self.default_cc.clone(), 0)); + self.log(|| format!("Convention: {:?}", convention)); + + let func = Type::function_with_options( + &return_type, + arguments.as_slice(), + is_varargs, + &convention, + Conf::new(0, 0), + ); + + let fancy_func = Type::function_with_options( + &fancy_return_type, + fancy_arguments.as_slice(), + is_varargs, + &convention, + Conf::new(0, 0), + ); + + Ok(Some(Box::new(ParsedType::Procedure(ParsedProcedureType { + method_type: fancy_func, + raw_method_type: func, + })))) + } + + fn handle_pointer_type( + &mut self, + data: &PointerType, + finder: &mut ItemFinder, + ) -> Result>> { + self.log(|| format!("Got Pointer type: {:x?}", data)); + let base = match self.try_type_index_to_bare(data.underlying_type, finder, false)? { + Some(ty) => Some(ty.clone()), + None => match self.handle_type_index(data.underlying_type, finder)? { + Some(ParsedType::VTableShape(descriptors)) => { + return Ok(Some(Box::new(ParsedType::VTablePointer( + descriptors.clone(), + )))); + } + _ => None, + }, + }; + + if let Some(base) = base { + Ok(Some(Box::new(ParsedType::Bare(Type::pointer( + &self.arch, + base.as_ref(), + ))))) + } else { + Ok(None) + } + } + + fn handle_modifier_type( + &mut self, + data: &ModifierType, + finder: &mut ItemFinder, + ) -> Result>> { + self.log(|| format!("Got Modifier type: {:x?}", data)); + let base = self.try_type_index_to_bare(data.underlying_type, finder, false)?; + + if let Some(base) = base { + let builder = TypeBuilder::new(base.as_ref()); + builder.set_const(data.constant); + builder.set_volatile(data.volatile); + Ok(Some(Box::new(ParsedType::Bare(builder.finalize())))) + } else { + Ok(None) + } + } + + fn handle_enumeration_type( + &mut self, + data: &EnumerationType, + finder: &mut ItemFinder, + ) -> Result>> { + self.log(|| format!("Got Enumeration type: {:x?}", data)); + + let raw_enum_name = &data.name.to_string(); + let enum_name = raw_enum_name.to_string(); + self.log(|| format!("Named: {}", enum_name)); + + if data.properties.forward_reference() { + // Try and find it first + if let Some(existing) = self.named_types.get(&enum_name) { + return Ok(Some(Box::new(ParsedType::Bare( + Type::named_type_from_type(&enum_name, existing), + )))); + } + + let ntr_class = NamedTypeReferenceClass::EnumNamedTypeClass; + return Ok(Some(Box::new(ParsedType::Bare(Type::named_type( + &*NamedTypeReference::new(ntr_class, QualifiedName::from(enum_name)), + ))))); + } + + let enumeration = EnumerationBuilder::new(); + + match self.handle_type_index(data.fields, finder)? { + Some(ParsedType::FieldList(fields)) => { + for field in fields { + match field { + ParsedType::Enumerate(member) => { + enumeration.insert(member.name.clone(), member.value); + } + e => return Err(anyhow!("Unexpected enumerate member: {:?}", e)), + } + } + } + // No fields? + None => {} + e => return Err(anyhow!("Unexpected enumeration field list: {:?}", e)), + } + + let underlying = match self.handle_type_index(data.underlying_type, finder)? { + Some(ParsedType::Bare(ty)) => ty.clone(), + e => return Err(anyhow!("Making enumeration from unexpected type: {:x?}", e)), + }; + + let new_type = Type::enumeration( + enumeration.finalize().as_ref(), + underlying.width() as usize, + underlying.is_signed().contents, + ); + + Ok(Some(Box::new(ParsedType::Named(enum_name, new_type)))) + } + + fn handle_enumerate_type( + &mut self, + data: &EnumerateType, + _finder: &mut ItemFinder, + ) -> Result>> { + self.log(|| format!("Got Enumerate type: {:x?}", data)); + Ok(Some(Box::new(ParsedType::Enumerate(EnumerationMember { + name: data.name.to_string().to_string(), + value: match data.value { + Variant::U8(v) => v as u64, + Variant::U16(v) => v as u64, + Variant::U32(v) => v as u64, + Variant::U64(v) => v as u64, + Variant::I8(v) => (v as i64) as u64, + Variant::I16(v) => (v as i64) as u64, + Variant::I32(v) => (v as i64) as u64, + Variant::I64(v) => (v as i64) as u64, + }, + is_default: false, + })))) + } + + fn handle_array_type( + &mut self, + data: &ArrayType, + finder: &mut ItemFinder, + ) -> Result>> { + self.log(|| format!("Got Array type: {:x?}", data)); + // PDB stores array sizes as TOTAL bytes not element count + // So we need to look up the original type's size to know how many there are + let base = self.try_type_index_to_bare(data.element_type, finder, true)?; + + if let Some(base) = base { + let mut new_type = base; + if new_type.width() == 0 { + if new_type.width() == 0 { + return Err(anyhow!( + "Cannot calculate array of 0-size elements: {}", + new_type + )); + } + } + + let mut counts = data + .dimensions + .iter() + .map(|t| *t as u64) + .collect::>(); + for i in 0..counts.len() { + for j in i..counts.len() { + if counts[j] % new_type.width() != 0 { + return Err(anyhow!( + "Array stride {} is not a multiple of element {} size {}", + counts[j], + new_type, + new_type.width() + )); + } + counts[j] /= new_type.width(); + } + + new_type = Type::array(new_type.as_ref(), counts[i] as u64); + } + + Ok(Some(Box::new(ParsedType::Bare(new_type)))) + } else { + Ok(None) + } + } + + fn handle_union_type( + &mut self, + data: &UnionType, + finder: &mut ItemFinder, + ) -> Result>> { + self.log(|| format!("Got Union type: {:x?}", data)); + + let raw_union_name = &data.name.to_string(); + let union_name = raw_union_name.to_string(); + self.log(|| format!("Named: {}", union_name)); + + if data.properties.forward_reference() { + // Try and find it first + if let Some(existing) = self.named_types.get(&union_name) { + return Ok(Some(Box::new(ParsedType::Bare( + Type::named_type_from_type(&union_name, existing), + )))); + } + + let ntr_class = NamedTypeReferenceClass::UnionNamedTypeClass; + return Ok(Some(Box::new(ParsedType::Bare(Type::named_type( + &*NamedTypeReference::new(ntr_class, QualifiedName::from(union_name)), + ))))); + } + + let mut structure = StructureBuilder::new(); + structure.set_structure_type(StructureType::UnionStructureType); + structure.set_width(data.size); + + self.namespace_stack.push(union_name.to_string()); + let success = self.parse_union_fields(&mut structure, data.fields, finder); + self.namespace_stack.pop(); + let _ = success?; + + let new_type = Type::structure(structure.finalize().as_ref()); + Ok(Some(Box::new(ParsedType::Named(union_name, new_type)))) + } + + /// Parse the fields in a union's field list + fn parse_union_fields( + &mut self, + structure: &mut StructureBuilder, + fields: TypeIndex, + finder: &mut ItemFinder, + ) -> Result<()> { + let mut union_groups = vec![]; + let mut last_union_group = u64::MAX; + + match self.handle_type_index(fields, finder) { + Ok(Some(ParsedType::FieldList(fields))) => { + for field in fields { + match field { + ParsedType::Member(member) => { + if member.offset <= last_union_group { + union_groups.push(vec![]); + } + last_union_group = member.offset; + union_groups + .last_mut() + .expect("invariant") + .push(member.clone()); + } + ParsedType::Method(..) => {} + ParsedType::Named(..) => {} + e => return Err(anyhow!("Unexpected union member type {:?}", e)), + } + } + } + e => return Err(anyhow!("Unexpected union field list type {:?}", e)), + } + + for (i, group) in union_groups.into_iter().enumerate() { + if group.len() == 1 { + structure.insert( + &group[0].ty, + group[0].name.clone(), + group[0].offset, + false, + group[0].access, + group[0].scope, + ); + } else { + let inner_struct = StructureBuilder::new(); + for member in group { + inner_struct.insert( + &member.ty, + member.name.clone(), + member.offset, + false, + member.access, + member.scope, + ); + } + structure.insert( + &Conf::new( + Type::structure(inner_struct.finalize().as_ref()), + max_confidence(), + ), + format!("__inner{:x}", i), + 0, + false, + MemberAccess::PublicAccess, + MemberScope::NoScope, + ); + } + } + + Ok(()) + } + + fn handle_bitfield_type( + &mut self, + data: &BitfieldType, + finder: &mut ItemFinder, + ) -> Result>> { + self.log(|| format!("Got Bitfield type: {:x?}", data)); + Ok(self + .try_type_index_to_bare(data.underlying_type, finder, true)? + .map(|ty| { + Box::new(ParsedType::BitfieldType(ParsedBitfieldType { + size: data.length as u64, + position: data.position as u64, + ty, + })) + })) + } + + fn handle_field_list_type( + &mut self, + data: &FieldList, + finder: &mut ItemFinder, + ) -> Result>> { + self.log(|| format!("Got FieldList type: {:x?}", data)); + + let mut fields = vec![]; + for (i, field) in data.fields.iter().enumerate() { + match self.handle_type(field, finder)? { + Some(f) => { + self.log(|| format!("Inner field {} parsed into {:?}", i, f)); + fields.push(*f); + } + None => { + self.log(|| format!("Inner field {} parsed into None", i)); + } + } + } + + if let Some(cont) = data.continuation { + match self.handle_type_index(cont, finder)? { + Some(ParsedType::FieldList(cont_fields)) => { + fields.extend(cont_fields.clone()); + } + None => {} + f => { + return Err(anyhow!("Unexpected field list continuation {:?}", f)); + } + } + } + Ok(Some(Box::new(ParsedType::FieldList(fields)))) + } + + fn handle_argument_list_type( + &mut self, + data: &ArgumentList, + finder: &mut ItemFinder, + ) -> Result>> { + self.log(|| format!("Got ArgumentList type: {:x?}", data)); + let mut args = vec![]; + for &arg in data.arguments.iter() { + match self.try_type_index_to_bare(arg, finder, false)? { + Some(ty) => { + // On x86_32, structures are stored on the stack directly + // On x64, they are put into pointers if they are not a int size + // TODO: Ugly hack + if self.arch.address_size() == 4 || Self::size_can_fit_in_register(ty.width()) { + args.push(FunctionParameter::new( + Conf::new(ty.clone(), max_confidence()), + "".to_string(), + None, + )); + } else { + args.push(FunctionParameter::new( + Conf::new( + Type::pointer(self.arch.as_ref(), ty.as_ref()), + max_confidence(), + ), + "".to_string(), + None, + )); + } + } + e => { + return Err(anyhow!("Unexpected argument type {:?}", e)); + } + } + } + Ok(Some(Box::new(ParsedType::ArgumentList(args)))) + } + + fn handle_method_list_type( + &mut self, + data: &MethodList, + finder: &mut ItemFinder, + ) -> Result>> { + self.log(|| format!("Got MethodList type: {:x?}", data)); + + let mut list = vec![]; + for method in &data.methods { + match self.handle_type_index(method.method_type, finder)? { + Some(ParsedType::MemberFunction(func)) => { + list.push(ParsedMethodListEntry { + attributes: method.attributes, + method_type: func.clone(), + vtable_offset: method.vtable_offset.map(|o| o as usize), + }); + } + e => return Err(anyhow!("Unexpected method list entry: {:?}", e)), + } + } + + Ok(Some(Box::new(ParsedType::MethodList(list)))) + } + + /// Given a type index, get a bare binja type (or fail if not found) + /// Optionally, set fully_resolve to true to parse and get the real type back in the case of NTRs + fn type_index_to_bare( + &mut self, + index: TypeIndex, + finder: &mut ItemFinder, + fully_resolve: bool, + ) -> Result> { + match self.try_type_index_to_bare(index, finder, fully_resolve)? { + Some(ty) => Ok(ty), + None => Err(anyhow!("Unresolved expected type {:?}", index)), + } + } + + /// Given a type index, try to get a bare binja type + /// Optionally, set fully_resolve to true to parse and get the real type back in the case of NTRs + fn try_type_index_to_bare( + &mut self, + index: TypeIndex, + finder: &mut ItemFinder, + fully_resolve: bool, + ) -> Result>> { + let (mut type_, inner) = match self.handle_type_index(index, finder)? { + Some(ParsedType::Bare(ty)) => (ty.clone(), None), + Some(ParsedType::Named(name, ty)) => { + (Type::named_type_from_type(name, &ty), Some(ty.clone())) + } + Some(ParsedType::Procedure(ParsedProcedureType { method_type, .. })) => { + (method_type.clone(), Some(method_type.clone())) + } + Some(ParsedType::MemberFunction(ParsedMemberFunction { method_type, .. })) => { + (method_type.clone(), Some(method_type.clone())) + } + Some(ParsedType::Member(ParsedMember { ty, .. })) => { + (ty.contents.clone(), Some(ty.contents.clone())) + } + _ => return Ok(None), + }; + + if type_.type_class() == TypeClass::NamedTypeReferenceClass { + if type_.width() == 0 { + // Replace empty NTR with fully parsed NTR, if we can + let name = type_ + .get_named_type_reference() + .map_err(|_| anyhow!("expected ntr"))? + .name() + .to_string(); + if let Some(full_ntr) = self.named_types.get(&name) { + type_ = Type::named_type_from_type(&name, full_ntr.as_ref()); + } + } + } + + if !fully_resolve { + return Ok(Some(type_)); + } + + if type_.type_class() == TypeClass::NamedTypeReferenceClass { + if type_.width() == 0 { + // Look up raw name of this type + if let Ok(raw) = finder.find(index) { + if let Ok(parsed) = raw.parse() { + // Have to use raw name here because self.full_type_indices uses raw name + // for some reason + if let Some(raw_name) = Self::type_data_to_raw_name(&parsed) { + if let Some(&full_index) = self.full_type_indices.get(&raw_name) { + if let None = self.type_stack.iter().find(|&&idx| idx == full_index) + { + if full_index != index { + return self.try_type_index_to_bare( + full_index, + finder, + fully_resolve, + ); + } + } + } + } + } + } + } + } + + if type_.type_class() == TypeClass::NamedTypeReferenceClass { + // PDB does this thing where anonymous inner types are represented as + // some_type:: + let name = type_ + .get_named_type_reference() + .map_err(|_| anyhow!("expected ntr"))? + .name() + .to_string(); + if Self::is_name_anonymous(&name) { + if let Some(inner) = inner.as_ref() { + type_ = inner.clone(); + } else { + // Look up raw name of this type + if let Ok(raw) = finder.find(index) { + if let Ok(parsed) = raw.parse() { + // Have to use raw name here because self.full_type_indices uses raw name + // for some reason + if let Some(raw_name) = Self::type_data_to_raw_name(&parsed) { + if let Some(&full_index) = self.full_type_indices.get(&raw_name) { + if let None = + self.type_stack.iter().find(|&&idx| idx == full_index) + { + if full_index != index { + return self.try_type_index_to_bare( + full_index, + finder, + fully_resolve, + ); + } + } + } + } + } + } + } + } + } + Ok(Some(type_)) + } + + /// Is this name one of the stupid microsoft unnamed type names + fn is_name_anonymous(name: &String) -> bool { + let name_string = name.split("::").last().unwrap_or("").to_string(); + return name_string == "" || name_string.starts_with(" Option>> { + platform + .calling_conventions() + .iter() + .find(|c| c.name().as_str() == name) + .map(|g| g.clone()) + } + + /// Convert pdb calling convention enum to binja + fn cv_call_t_to_calling_convention( + &self, + cv: u8, + ) -> Option>> { + match CV_call_t::try_from(cv) { + Ok(CV_call_t::NEAR_FAST) | Ok(CV_call_t::FAR_FAST) => { + self.platform.get_fastcall_calling_convention() + } + Ok(CV_call_t::NEAR_STD) | Ok(CV_call_t::FAR_STD) => { + self.platform.get_stdcall_calling_convention() + } + Ok(CV_call_t::NEAR_C) | Ok(CV_call_t::FAR_C) => { + self.platform.get_cdecl_calling_convention() + } + Ok(CV_call_t::THISCALL) => { + Self::find_calling_convention(self.platform.as_ref(), "thiscall") + } + Ok(CV_call_t::NEAR_PASCAL) | Ok(CV_call_t::FAR_PASCAL) => { + Self::find_calling_convention(self.platform.as_ref(), "pascal") + } + Ok(CV_call_t::NEAR_SYS) | Ok(CV_call_t::FAR_SYS) => { + Self::find_calling_convention(self.platform.as_ref(), "sys") + } + Ok(CV_call_t::MIPSCALL) => { + Self::find_calling_convention(self.platform.as_ref(), "mipscall") + } + Ok(CV_call_t::ALPHACALL) => { + Self::find_calling_convention(self.platform.as_ref(), "alphacall") + } + Ok(CV_call_t::PPCCALL) => { + Self::find_calling_convention(self.platform.as_ref(), "ppccall") + } + Ok(CV_call_t::SHCALL) => { + Self::find_calling_convention(self.platform.as_ref(), "shcall") + } + Ok(CV_call_t::ARMCALL) => { + Self::find_calling_convention(self.platform.as_ref(), "armcall") + } + Ok(CV_call_t::AM33CALL) => { + Self::find_calling_convention(self.platform.as_ref(), "am33call") + } + Ok(CV_call_t::TRICALL) => { + Self::find_calling_convention(self.platform.as_ref(), "tricall") + } + Ok(CV_call_t::SH5CALL) => { + Self::find_calling_convention(self.platform.as_ref(), "sh5call") + } + Ok(CV_call_t::M32RCALL) => { + Self::find_calling_convention(self.platform.as_ref(), "m32rcall") + } + Ok(CV_call_t::NEAR_VECTOR) => { + Self::find_calling_convention(self.platform.as_ref(), "vectorcall") + } + _ => None, + } + } + + /// Insert an argument for the thisptr in a function param list + fn insert_this_pointer( + &self, + parameters: &mut Vec, + this_type: Ref, + ) -> Result<()> { + parameters.insert( + 0, + FunctionParameter::new( + Conf::new(this_type, max_confidence()), + "this".to_string(), + None, + ), + ); + + Ok(()) + } + + /// Does this type get returned in rax? Or should we put it on the stack? + pub fn can_fit_in_register( + &mut self, + index: TypeIndex, + finder: &mut ItemFinder, + treat_references_like_pointers: bool, + ) -> bool { + // TLDR "This is impossible so we're making a best-guess" + // GET READY OKAY + + // "A scalar return value that can fit into 64 bits, including the __m64 type, is returned + // through RAX. Non-scalar types including floats, doubles, and vector types such as __m128, + // __m128i, __m128d are returned in XMM0. The state of unused bits in the value returned + // in RAX or XMM0 is undefined. + + // "User-defined types can be returned by value from global functions and static member + // functions. To return a user-defined type by value in RAX, it must have a length of + // 1, 2, 4, 8, 16, 32, or 64 bits. It must also have no user-defined constructor, destructor, + // or copy assignment operator. It can have no private or protected non-static data members, + // and no non-static data members of reference type. It can't have base classes or virtual + // functions. And, it can only have data members that also meet these requirements. + // (This definition is essentially the same as a C++03 POD type. Because the definition has + // changed in the C++11 standard, we don't recommend using std::is_pod for this test.) + // Otherwise, the caller must allocate memory for the return value and pass a pointer to it + // as the first argument. The remaining arguments are then shifted one argument to the right. + // The same pointer must be returned by the callee in RAX." + + // - length of 1, 2, 4, 8, 16, 32, or 64 bits + // - no user-defined constructor + // - no user-defined destructor + // - no user-defined copy assignment operator + // - no private data members + // - no protected data members + // - no reference data members + // - no base classes + // - no virtual functions + + // This one is incorrect, so we're not including it: + // - all members meet these requirements + // https://godbolt.org/z/hsTxrxq9c extremely cool + + // Are we going to implement all of this? + // No? We're just going to do something close and leave it to the users to figure out the rest + // There's no way I'm digging through all nonsense + + // After a quick GitHub discussion (https://github.com/MicrosoftDocs/cpp-docs/issues/4152) + // I've determined this is unknowable. + // Microsoft does it again!!!! + + if let Some(&returnable) = self.type_default_returnable.get(&index) { + returnable + } else { + let returnable = + self.can_fit_in_register_impl(index, finder, treat_references_like_pointers); + self.log(|| format!("Type {} is default returnable: {}", index, returnable)); + self.type_default_returnable.insert(index, returnable); + returnable + } + } + + fn size_can_fit_in_register(size: u64) -> bool { + match size { + 0 | 1 | 2 | 4 | 8 => true, + _ => false, + } + } + + // Memoized... because this has gotta be real slow + fn can_fit_in_register_impl( + &mut self, + index: TypeIndex, + finder: &mut ItemFinder, + treat_references_like_pointers: bool, + ) -> bool { + let ty = match finder.find(index) { + Ok(item) => match item.parse() { + Ok(ty) => ty, + Err(_) => return false, + }, + Err(_) => return false, + }; + + fn get_fields<'a>( + index: TypeIndex, + finder: &mut ItemFinder<'a, TypeIndex>, + ) -> Result>> { + match finder.find(index).and_then(|fields| fields.parse()) { + Ok(TypeData::FieldList(fields)) => { + if let Some(cont) = fields.continuation { + Ok(fields + .fields + .into_iter() + .chain(get_fields(cont, finder)?.into_iter()) + .collect::>()) + } else { + Ok(fields.fields) + } + } + _ => Err(anyhow!("can't lookup fields")), + } + } + + match ty { + TypeData::Primitive(_) => true, + TypeData::Pointer(p) => match p.attributes.pointer_mode() { + PointerMode::Pointer => true, + PointerMode::Member => true, + PointerMode::MemberFunction => true, + // - no reference data members + PointerMode::LValueReference => treat_references_like_pointers, + PointerMode::RValueReference => treat_references_like_pointers, + }, + TypeData::Array(a) => { + Self::size_can_fit_in_register(*a.dimensions.last().unwrap_or(&0) as u64) + && self.can_fit_in_register(a.element_type, finder, false) + } + TypeData::Modifier(m) => { + self.can_fit_in_register(m.underlying_type, finder, treat_references_like_pointers) + } + TypeData::Enumeration(e) => self.can_fit_in_register(e.underlying_type, finder, false), + TypeData::Class(c) => { + if c.properties.forward_reference() { + if let Some(raw_name) = c.unique_name { + if let Some(&full) = self + .full_type_indices + .get(&raw_name.to_string().to_string()) + { + return self.can_fit_in_register( + full, + finder, + treat_references_like_pointers, + ); + } + } + // Can't look up, assume not + return false; + } + + // - length of 1, 2, 4, 8, 16, 32, or 64 bits + if !Self::size_can_fit_in_register(c.size) { + return false; + } + + // - no user-defined constructor + // - no user-defined destructor + // - no user-defined copy assignment operator + if c.properties.constructors() || c.properties.overloaded_assignment() { + return false; + } + + // - no base classes + if let Some(_) = c.derived_from { + return false; + } + // - no virtual functions + if let Some(_) = c.vtable_shape { + return false; + } + + let fields = if let Some(fields_idx) = c.fields { + if let Ok(fields) = get_fields(fields_idx, finder) { + fields + } else { + return false; + } + } else { + // No fields? + return true; + }; + + for field in fields { + match field { + TypeData::Member(m) => { + // - no private data members + // - no protected data members + if m.attributes.access() == 1 || m.attributes.access() == 2 { + return false; + } + } + TypeData::OverloadedMethod(m) => { + match finder.find(m.method_list).and_then(|l| l.parse()) { + Ok(TypeData::MethodList(list)) => { + for m in list.methods { + // - no virtual functions + if m.attributes.is_virtual() { + return false; + } + } + } + _ => return false, + } + } + TypeData::Method(m) => { + // - no virtual functions + if m.attributes.is_virtual() { + return false; + } + } + // - no base classes + TypeData::BaseClass(_) => return false, + TypeData::VirtualBaseClass(_) => return false, + TypeData::VirtualFunctionTable(_) => return false, + TypeData::VirtualTableShape(_) => return false, + TypeData::VirtualFunctionTablePointer(_) => return false, + _ => {} + } + } + return true; + } + TypeData::Union(u) => { + if u.properties.forward_reference() { + if let Some(raw_name) = u.unique_name { + if let Some(&full) = self + .full_type_indices + .get(&raw_name.to_string().to_string()) + { + return self.can_fit_in_register( + full, + finder, + treat_references_like_pointers, + ); + } + } + // Can't look up, assume not + return false; + } + + // - length of 1, 2, 4, 8, 16, 32, or 64 bits + if !Self::size_can_fit_in_register(u.size) { + return false; + } + + // - no user-defined constructor + // - no user-defined destructor + // - no user-defined copy assignment operator + if u.properties.constructors() || u.properties.overloaded_assignment() { + return false; + } + + let fields = if let Ok(fields) = get_fields(u.fields, finder) { + fields + } else { + return false; + }; + + for field in fields { + match field { + TypeData::Member(m) => { + // - no private data members + // - no protected data members + if m.attributes.access() == 1 || m.attributes.access() == 2 { + return false; + } + } + TypeData::OverloadedMethod(m) => { + match finder.find(m.method_list).and_then(|l| l.parse()) { + Ok(TypeData::MethodList(list)) => { + for m in list.methods { + // - no virtual functions + if m.attributes.is_virtual() { + return false; + } + } + } + _ => return false, + } + } + TypeData::Method(m) => { + // - no virtual functions + if m.attributes.is_virtual() { + return false; + } + } + // - no base classes + TypeData::BaseClass(_) => return false, + TypeData::VirtualBaseClass(_) => return false, + TypeData::VirtualFunctionTable(_) => return false, + TypeData::VirtualTableShape(_) => return false, + TypeData::VirtualFunctionTablePointer(_) => return false, + _ => {} + } + } + return true; + } + _ => false, + } + } +} diff --git a/examples/template/src/main.rs b/examples/template/src/main.rs index 5b21efb..4a1bab2 100644 --- a/examples/template/src/main.rs +++ b/examples/template/src/main.rs @@ -31,9 +31,7 @@ fn main() { .get_data(), addr, ) { - tokens - .iter() - .for_each(|token| print!("{}", token.text().as_str())); + tokens.iter().for_each(|token| print!("{}", token.text())); println!(); } } diff --git a/src/architecture.rs b/src/architecture.rs index 9a7b3e7..cf5d32d 100644 --- a/src/architecture.rs +++ b/src/architecture.rs @@ -23,7 +23,7 @@ use std::{ collections::HashMap, ffi::{c_char, c_int, CStr, CString}, hash::Hash, - mem::zeroed, + mem::{zeroed, MaybeUninit}, ops, ptr, slice, }; @@ -97,11 +97,11 @@ impl<'a> Iterator for BranchIter<'a> { #[repr(C)] pub struct InstructionInfo(BNInstructionInfo); impl InstructionInfo { - pub fn new(len: usize, branch_delay: bool) -> Self { + pub fn new(len: usize, delay_slots: u8) -> Self { InstructionInfo(BNInstructionInfo { length: len, archTransitionByTargetAddr: false, - branchDelay: branch_delay, + delaySlots: delay_slots, branchCount: 0usize, branchType: [BranchType::UnresolvedBranch; 3], branchTarget: [0u64; 3], @@ -121,8 +121,8 @@ impl InstructionInfo { self.0.branchCount } - pub fn branch_delay(&self) -> bool { - self.0.branchDelay + pub fn delay_slots(&self) -> u8 { + self.0.delaySlots } pub fn branches(&self) -> BranchIter { @@ -296,7 +296,7 @@ pub trait FlagGroup: Sized + Clone + Copy { /// Types to represent the different comparisons, so for `cr1_lt` we /// would return a mapping along the lines of: /// - /// ``` + /// ```text /// cr1_signed -> LLFC_SLT, /// cr1_unsigned -> LLFC_ULT, /// ``` @@ -313,7 +313,7 @@ pub trait Intrinsic: Sized + Clone + Copy { fn id(&self) -> u32; /// Reeturns the list of the input names and types for this intrinsic. - fn inputs(&self) -> Vec>; + fn inputs(&self) -> Vec>; /// Returns the list of the output types for this intrinsic. fn outputs(&self) -> Vec>>; @@ -650,7 +650,7 @@ impl Intrinsic for UnusedIntrinsic { fn id(&self) -> u32 { unreachable!() } - fn inputs(&self) -> Vec> { + fn inputs(&self) -> Vec> { unreachable!() } fn outputs(&self) -> Vec>> { @@ -715,6 +715,21 @@ impl Register for CoreRegister { } } +impl CoreArrayProvider for CoreRegister { + type Raw = u32; + type Context = CoreArchitecture; + type Wrapped<'a> = Self; +} + +unsafe impl CoreArrayProviderInner for CoreRegister { + unsafe fn free(raw: *mut Self::Raw, _count: usize, _context: &Self::Context) { + BNFreeRegisterList(raw) + } + unsafe fn wrap_raw<'a>(raw: &'a Self::Raw, context: &'a Self::Context) -> Self::Wrapped<'a> { + Self(context.0, *raw) + } +} + pub struct CoreRegisterStackInfo(*mut BNArchitecture, BNRegisterStackInfo); impl RegisterStackInfo for CoreRegisterStackInfo { @@ -968,8 +983,8 @@ impl FlagGroup for CoreFlagGroup { } } -#[derive(Copy, Clone, Eq, PartialEq)] -pub struct CoreIntrinsic(*mut BNArchitecture, u32); +#[derive(Debug, Copy, Clone, Eq, PartialEq)] +pub struct CoreIntrinsic(pub(crate) *mut BNArchitecture, pub(crate) u32); impl Intrinsic for crate::architecture::CoreIntrinsic { fn name(&self) -> Cow { @@ -992,7 +1007,7 @@ impl Intrinsic for crate::architecture::CoreIntrinsic { self.1 } - fn inputs(&self) -> Vec> { + fn inputs(&self) -> Vec> { let mut count: usize = 0; unsafe { @@ -1000,7 +1015,7 @@ impl Intrinsic for crate::architecture::CoreIntrinsic { let ret = slice::from_raw_parts_mut(inputs, count) .iter() - .map(NameAndType::from_raw) + .map(|x| NameAndType::from_raw(x).to_owned()) .collect(); BNFreeNameAndTypeList(inputs, count); @@ -1162,17 +1177,18 @@ impl Architecture for CoreArchitecture { &mut result as *mut _, &mut count as *mut _, ) { - let vec = Vec::::from_raw_parts(result, count, count) + let vec = slice::from_raw_parts(result, count) .iter() - .map(|x| InstructionTextToken::from_raw(x)) + .map(|x| InstructionTextToken::from_raw(x).to_owned()) .collect(); + BNFreeInstructionText(result, count); Some((consumed, vec)) } else { None } } } - + fn instruction_llil( &self, data: &[u8], @@ -1689,8 +1705,8 @@ where A: 'static + Architecture> + Send + Sync, F: FnOnce(CustomArchitectureHandle, CoreArchitecture) -> A, { - arch: A, - func: F, + arch: MaybeUninit, + func: Option, } extern "C" fn cb_init(ctxt: *mut c_void, obj: *mut BNArchitecture) @@ -1704,11 +1720,10 @@ where handle: ctxt as *mut A, }; - let create = ptr::read(&custom_arch.func); - ptr::write( - &mut custom_arch.arch, - create(custom_arch_handle, CoreArchitecture(obj)), - ); + let create = custom_arch.func.take().unwrap(); + custom_arch + .arch + .write(create(custom_arch_handle, CoreArchitecture(obj))); } } @@ -1811,27 +1826,25 @@ where let data = unsafe { slice::from_raw_parts(data, *len) }; let result = unsafe { &mut *result }; - match custom_arch.instruction_text(data, addr) { - Some((res_size, mut res_tokens)) => { - unsafe { - // TODO: Can't use into_raw_parts as it's unstable so we do this instead... - let r_ptr = res_tokens.as_mut_ptr(); - let r_count = res_tokens.len(); - mem::forget(res_tokens); + let Some((res_size, res_tokens)) = custom_arch.instruction_text(data, addr) else { + return false; + }; - *result = &mut (*r_ptr).0; - *count = r_count; - *len = res_size; - } - true - } - None => false, + let res_tokens: Box<[_]> = res_tokens.into_boxed_slice(); + unsafe { + let res_tokens = Box::leak(res_tokens); + let r_ptr = res_tokens.as_mut_ptr(); + let r_count = res_tokens.len(); + + *result = &mut (*r_ptr).0; + *count = r_count; + *len = res_size; } + true } extern "C" fn cb_free_instruction_text(tokens: *mut BNInstructionTextToken, count: usize) { - let _tokens = - unsafe { Vec::from_raw_parts(tokens as *mut InstructionTextToken, count, count) }; + let _tokens = unsafe { Box::from_raw(ptr::slice_from_raw_parts_mut(tokens, count)) }; } extern "C" fn cb_instruction_llil( @@ -1931,15 +1944,7 @@ where if len == 0 { ptr::null_mut() } else { - let mut res = Vec::with_capacity(len + 1); - - res.push(len as u32); - - for i in items { - res.push(i); - } - - assert!(res.len() == len + 1); + let mut res: Box<[_]> = [len as u32].into_iter().chain(items).collect(); let raw = res.as_mut_ptr(); mem::forget(res); @@ -2280,7 +2285,8 @@ where unsafe { let actual_start = regs.offset(-1); let len = *actual_start + 1; - let _regs = Vec::from_raw_parts(actual_start, len as usize, len as usize); + let regs_ptr = ptr::slice_from_raw_parts_mut(actual_start, len.try_into().unwrap()); + let _regs = Box::from_raw(regs_ptr); } } @@ -2420,28 +2426,28 @@ where { let custom_arch = unsafe { &*(ctxt as *mut A) }; - if let Some(intrinsic) = custom_arch.intrinsic_from_id(intrinsic) { - let inputs = intrinsic.inputs(); - let mut res = Vec::with_capacity(inputs.len()); - for input in inputs { - res.push(input.into_raw()); - } - - unsafe { - *count = res.len(); - if res.is_empty() { - ptr::null_mut() - } else { - let raw = res.as_mut_ptr(); - mem::forget(res); - raw - } - } - } else { + let Some(intrinsic) = custom_arch.intrinsic_from_id(intrinsic) else { unsafe { *count = 0; } - ptr::null_mut() + return ptr::null_mut(); + }; + + let inputs = intrinsic.inputs(); + let mut res: Box<[_]> = inputs + .into_iter() + .map(|input| unsafe { Ref::into_raw(input) }.0) + .collect(); + + unsafe { + *count = res.len(); + if res.is_empty() { + ptr::null_mut() + } else { + let raw = res.as_mut_ptr(); + mem::forget(res); + raw + } } } @@ -2453,9 +2459,9 @@ where if !nt.is_null() { unsafe { - let list = Vec::from_raw_parts(nt, count, count); - for nt in list { - BnString::from_raw(nt.name); + let name_and_types = Box::from_raw(ptr::slice_from_raw_parts_mut(nt, count)); + for nt in name_and_types.iter() { + Ref::new(NameAndType::from_raw(nt)); } } } @@ -2473,10 +2479,7 @@ where if let Some(intrinsic) = custom_arch.intrinsic_from_id(intrinsic) { let inputs = intrinsic.outputs(); - let mut res = Vec::with_capacity(inputs.len()); - for input in inputs { - res.push(input.into()); - } + let mut res: Box<[_]> = inputs.iter().map(|input| input.as_ref().into()).collect(); unsafe { *count = res.len(); @@ -2505,9 +2508,7 @@ where { let _custom_arch = unsafe { &*(ctxt as *mut A) }; if !tl.is_null() { - unsafe { - let _list = Vec::from_raw_parts(tl, count, count); - } + let _type_list = unsafe { Box::from_raw(ptr::slice_from_raw_parts_mut(tl, count)) }; } } @@ -2685,8 +2686,8 @@ where let name = name.into_bytes_with_nul(); let uninit_arch = ArchitectureBuilder { - arch: unsafe { zeroed() }, - func, + arch: MaybeUninit::zeroed(), + func: Some(func), }; let raw = Box::into_raw(Box::new(uninit_arch)); @@ -2776,7 +2777,7 @@ where assert!(!res.is_null()); - &(*raw).arch + (*raw).arch.assume_init_mut() } } diff --git a/src/backgroundtask.rs b/src/backgroundtask.rs index 1eb090d..e5fa6e7 100644 --- a/src/backgroundtask.rs +++ b/src/backgroundtask.rs @@ -104,21 +104,14 @@ unsafe impl RefCountable for BackgroundTask { impl CoreArrayProvider for BackgroundTask { type Raw = *mut BNBackgroundTask; type Context = (); + type Wrapped<'a> = Guard<'a, BackgroundTask>; } -unsafe impl CoreOwnedArrayProvider for BackgroundTask { +unsafe impl CoreArrayProviderInner for BackgroundTask { unsafe fn free(raw: *mut *mut BNBackgroundTask, count: usize, _context: &()) { BNFreeBackgroundTaskList(raw, count); } -} - -unsafe impl<'a> CoreArrayWrapper<'a> for BackgroundTask { - type Wrapped = Guard<'a, BackgroundTask>; - - unsafe fn wrap_raw( - raw: &'a *mut BNBackgroundTask, - context: &'a (), - ) -> Guard<'a, BackgroundTask> { + unsafe fn wrap_raw<'a>(raw: &'a *mut BNBackgroundTask, context: &'a ()) -> Self::Wrapped<'a> { Guard::new(BackgroundTask::from_raw(*raw), context) } } diff --git a/src/basicblock.rs b/src/basicblock.rs index 73ad936..a6ac8f7 100644 --- a/src/basicblock.rs +++ b/src/basicblock.rs @@ -68,18 +68,14 @@ pub struct EdgeContext<'a, C: 'a + BlockContext> { impl<'a, C: 'a + BlockContext> CoreArrayProvider for Edge<'a, C> { type Raw = BNBasicBlockEdge; type Context = EdgeContext<'a, C>; + type Wrapped<'b> = Edge<'b, C> where 'a: 'b; } -unsafe impl<'a, C: 'a + BlockContext> CoreOwnedArrayProvider for Edge<'a, C> { +unsafe impl<'a, C: 'a + BlockContext> CoreArrayProviderInner for Edge<'a, C> { unsafe fn free(raw: *mut Self::Raw, count: usize, _context: &Self::Context) { BNFreeBasicBlockEdgeList(raw, count); } -} - -unsafe impl<'a, C: 'a + BlockContext> CoreArrayWrapper<'a> for Edge<'a, C> { - type Wrapped = Edge<'a, C>; - - unsafe fn wrap_raw(raw: &'a Self::Raw, context: &'a Self::Context) -> Edge<'a, C> { + unsafe fn wrap_raw<'b>(raw: &'b Self::Raw, context: &'b Self::Context) -> Self::Wrapped<'b> { let edge_target = Guard::new( BasicBlock::from_raw(raw.target, context.orig_block.context.clone()), raw, @@ -301,18 +297,14 @@ unsafe impl RefCountable for BasicBlock { impl CoreArrayProvider for BasicBlock { type Raw = *mut BNBasicBlock; type Context = C; + type Wrapped<'a> = Guard<'a, BasicBlock> where C: 'a; } -unsafe impl CoreOwnedArrayProvider for BasicBlock { +unsafe impl CoreArrayProviderInner for BasicBlock { unsafe fn free(raw: *mut Self::Raw, count: usize, _context: &Self::Context) { BNFreeBasicBlockList(raw, count); } -} - -unsafe impl<'a, C: 'a + BlockContext> CoreArrayWrapper<'a> for BasicBlock { - type Wrapped = Guard<'a, BasicBlock>; - - unsafe fn wrap_raw(raw: &'a Self::Raw, context: &'a Self::Context) -> Self::Wrapped { + unsafe fn wrap_raw<'a>(raw: &'a Self::Raw, context: &'a Self::Context) -> Self::Wrapped<'a> { Guard::new(BasicBlock::from_raw(*raw, context.clone()), context) } } diff --git a/src/binaryview.rs b/src/binaryview.rs index e188b65..b20bc59 100644 --- a/src/binaryview.rs +++ b/src/binaryview.rs @@ -23,24 +23,20 @@ pub use binaryninjacore_sys::BNAnalysisState as AnalysisState; pub use binaryninjacore_sys::BNModificationStatus as ModificationStatus; use std::collections::HashMap; -use std::ffi::c_void; +use std::ffi::{c_char, c_void}; use std::ops::Range; -use std::os::raw::c_char; -use std::ptr; -use std::result; -use std::{ops, slice}; +use std::{ops, ptr, result, slice}; -use crate::architecture::Architecture; -use crate::architecture::CoreArchitecture; +use crate::architecture::{Architecture, CoreArchitecture}; use crate::basicblock::BasicBlock; +use crate::component::{Component, ComponentBuilder, IntoComponentGuid}; use crate::databuffer::DataBuffer; use crate::debuginfo::DebugInfo; use crate::fileaccessor::FileAccessor; use crate::filemetadata::FileMetadata; use crate::flowgraph::FlowGraph; use crate::function::{Function, NativeBlock}; -use crate::linearview::LinearDisassemblyLine; -use crate::linearview::LinearViewCursor; +use crate::linearview::{LinearDisassemblyLine, LinearViewCursor}; use crate::metadata::Metadata; use crate::platform::Platform; use crate::relocation::Relocation; @@ -49,7 +45,10 @@ use crate::segment::{Segment, SegmentBuilder}; use crate::settings::Settings; use crate::symbol::{Symbol, SymbolType}; use crate::tags::{Tag, TagType}; -use crate::types::{DataVariable, NamedTypeReference, QualifiedName, QualifiedNameAndType, Type}; +use crate::typelibrary::TypeLibrary; +use crate::types::{ + Conf, DataVariable, NamedTypeReference, QualifiedName, QualifiedNameAndType, Type, +}; use crate::Endianness; use crate::rc::*; @@ -225,18 +224,10 @@ pub trait BinaryViewExt: BinaryViewBase { /// Reads up to `len` bytes from address `offset` fn read_vec(&self, offset: u64, len: usize) -> Vec { - let mut ret = Vec::with_capacity(len); + let mut ret = vec![0; len]; - unsafe { - let res; - - { - let dest_slice = ret.get_unchecked_mut(0..len); - res = self.read(dest_slice, offset); - } - - ret.set_len(res); - } + let size = self.read(&mut ret, offset); + ret.truncate(size); ret } @@ -244,26 +235,10 @@ pub trait BinaryViewExt: BinaryViewBase { /// Appends up to `len` bytes from address `offset` into `dest` fn read_into_vec(&self, dest: &mut Vec, offset: u64, len: usize) -> usize { let starting_len = dest.len(); - let space = dest.capacity() - starting_len; - - if space < len { - dest.reserve(len - space); - } - - unsafe { - let res; - - { - let dest_slice = dest.get_unchecked_mut(starting_len..starting_len + len); - res = self.read(dest_slice, offset); - } - - if res > 0 { - dest.set_len(starting_len + res); - } - - res - } + dest.resize(starting_len + len, 0); + let read_size = self.read(&mut dest[starting_len..], offset); + dest.truncate(starting_len + read_size); + read_size } fn notify_data_written(&self, offset: u64, len: usize) { @@ -292,6 +267,18 @@ pub trait BinaryViewExt: BinaryViewBase { unsafe { BNIsOffsetWritableSemantics(self.as_ref().handle, offset) } } + fn original_image_base(&self) -> u64 { + unsafe { + BNGetOriginalImageBase(self.as_ref().handle) + } + } + + fn set_original_image_base(&self, image_base: u64) { + unsafe { + BNSetOriginalImageBase(self.as_ref().handle, image_base) + } + } + fn end(&self) -> u64 { unsafe { BNGetEndOffset(self.as_ref().handle) } } @@ -574,16 +561,27 @@ pub trait BinaryViewExt: BinaryViewBase { } } - fn define_auto_data_var(&self, dv: DataVariable) { + fn data_variable_at_address(&self, addr: u64) -> Option> { + let dv = BNDataVariable::default(); unsafe { - BNDefineDataVariable(self.as_ref().handle, dv.address, &mut dv.t.into()); + if BNGetDataVariableAtAddress(self.as_ref().handle, addr, std::mem::transmute(&dv)) { + Some(DataVariable(dv).to_owned()) + } else { + None + } + } + } + + fn define_auto_data_var<'a, T: Into>>(&self, addr: u64, ty: T) { + unsafe { + BNDefineDataVariable(self.as_ref().handle, addr, &mut ty.into().into()); } } /// You likely would also like to call [`Self::define_user_symbol`] to bind this data variable with a name - fn define_user_data_var(&self, dv: DataVariable) { + fn define_user_data_var<'a, T: Into>>(&self, addr: u64, ty: T) { unsafe { - BNDefineUserDataVariable(self.as_ref().handle, dv.address, &mut dv.t.into()); + BNDefineUserDataVariable(self.as_ref().handle, addr, &mut ty.into().into()); } } @@ -674,7 +672,7 @@ pub trait BinaryViewExt: BinaryViewBase { let name_array = unsafe { Array::::new(result_names, result_count, ()) }; for (id, name) in id_array.iter().zip(name_array.iter()) { - result.insert(id.as_str().to_owned(), name.clone()); + result.insert(id.to_owned(), name.clone()); } result @@ -964,6 +962,15 @@ pub trait BinaryViewExt: BinaryViewBase { } } + fn entry_point_functions(&self) -> Array { + unsafe { + let mut count = 0; + let functions = BNGetAllEntryFunctions(self.as_ref().handle, &mut count); + + Array::new(functions, count, ()) + } + } + fn functions(&self) -> Array { unsafe { let mut count = 0; @@ -1056,7 +1063,7 @@ pub trait BinaryViewExt: BinaryViewBase { unsafe { BNApplyDebugInfo(self.as_ref().handle, debug_info.handle) } } - fn show_graph_report(&self, raw_name: S, graph: FlowGraph) { + fn show_graph_report(&self, raw_name: S, graph: &FlowGraph) { let raw_name = raw_name.into_bytes_with_nul(); unsafe { BNShowGraphReport( @@ -1363,6 +1370,261 @@ pub trait BinaryViewExt: BinaryViewBase { Array::new(handle, count, ()) } } + + fn component_by_guid(&self, guid: S) -> Option { + let name = guid.into_bytes_with_nul(); + let result = unsafe { + BNGetComponentByGuid( + self.as_ref().handle, + name.as_ref().as_ptr() as *const core::ffi::c_char, + ) + }; + core::ptr::NonNull::new(result).map(|h| unsafe { Component::from_raw(h) }) + } + + fn root_component(&self) -> Option { + let result = unsafe { BNGetRootComponent(self.as_ref().handle) }; + core::ptr::NonNull::new(result).map(|h| unsafe { Component::from_raw(h) }) + } + + fn component_builder(&self) -> ComponentBuilder { + ComponentBuilder::new_from_raw(self.as_ref().handle) + } + + fn component_by_path(&self, path: P) -> Option { + let path = path.into_bytes_with_nul(); + let result = unsafe { + BNGetComponentByPath( + self.as_ref().handle, + path.as_ref().as_ptr() as *const core::ffi::c_char, + ) + }; + core::ptr::NonNull::new(result).map(|h| unsafe { Component::from_raw(h) }) + } + + fn remove_component(&self, component: &Component) -> bool { + unsafe { BNRemoveComponent(self.as_ref().handle, component.as_raw()) } + } + + fn remove_component_by_guid(&self, guid: P) -> bool { + let path = guid.component_guid(); + unsafe { BNRemoveComponentByGuid(self.as_ref().handle, path.as_ptr()) } + } + + fn data_variable_parent_components( + &self, + data_variable: &DataVariable, + ) -> Array { + let mut count = 0; + let result = unsafe { + BNGetDataVariableParentComponents( + self.as_ref().handle, + data_variable.address(), + &mut count, + ) + }; + unsafe { Array::new(result, count, ()) } + } + + /// Make the contents of a type library available for type/import resolution + fn add_type_library(&self, library: &TypeLibrary) { + unsafe { BNAddBinaryViewTypeLibrary(self.as_ref().handle, library.as_raw()) } + } + + fn type_library_by_name(&self, name: S) -> Option { + let name = name.into_bytes_with_nul(); + let result = unsafe { + BNGetBinaryViewTypeLibrary( + self.as_ref().handle, + name.as_ref().as_ptr() as *const core::ffi::c_char, + ) + }; + core::ptr::NonNull::new(result).map(|h| unsafe { TypeLibrary::from_raw(h) }) + } + + /// Should be called by custom py:py:class:`BinaryView` implementations + /// when they have successfully imported an object from a type library (eg a symbol's type). + /// Values recorded with this function will then be queryable via [BinaryViewExt::lookup_imported_object_library]. + /// + /// * `lib` - Type Library containing the imported type + /// * `name` - Name of the object in the type library + /// * `addr` - address of symbol at import site + /// * `platform` - Platform of symbol at import site + fn record_imported_object_library( + &self, + lib: &TypeLibrary, + name: &QualifiedName, + addr: u64, + platform: &Platform, + ) { + unsafe { + BNBinaryViewRecordImportedObjectLibrary( + self.as_ref().handle, + platform.handle, + addr, + lib.as_raw(), + &name.0 as *const _ as *mut _, + ) + } + } + + /// Recursively imports a type from the specified type library, or, if + /// no library was explicitly provided, the first type library associated with the current [BinaryView] + /// that provides the name requested. + /// + /// This may have the impact of loading other type libraries as dependencies on other type libraries are lazily resolved + /// when references to types provided by them are first encountered. + /// + /// Note that the name actually inserted into the view may not match the name as it exists in the type library in + /// the event of a name conflict. To aid in this, the [Type] object returned is a `NamedTypeReference` to + /// the deconflicted name used. + fn import_type_library( + &self, + name: &QualifiedName, + mut lib: Option, + ) -> Option> { + let mut lib_ref = lib + .as_mut() + .map(|l| unsafe { l.as_raw() } as *mut _) + .unwrap_or(ptr::null_mut()); + let result = unsafe { + BNBinaryViewImportTypeLibraryType( + self.as_ref().handle, + &mut lib_ref, + &name.0 as *const _ as *mut _, + ) + }; + (!result.is_null()).then(|| unsafe { Type::ref_from_raw(result) }) + } + + /// Recursively imports an object from the specified type library, or, if + /// no library was explicitly provided, the first type library associated with the current [BinaryView] + /// that provides the name requested. + /// + /// This may have the impact of loading other type libraries as dependencies on other type libraries are lazily resolved + /// when references to types provided by them are first encountered. + /// + /// .. note:: If you are implementing a custom BinaryView and use this method to import object types, + /// you should then call [BinaryViewExt::record_imported_object_library] with the details of where the object is located. + fn import_type_object( + &self, + name: &QualifiedName, + mut lib: Option, + ) -> Option> { + let mut lib_ref = lib + .as_mut() + .map(|l| unsafe { l.as_raw() } as *mut _) + .unwrap_or(ptr::null_mut()); + let result = unsafe { + BNBinaryViewImportTypeLibraryObject( + self.as_ref().handle, + &mut lib_ref, + &name.0 as *const _ as *mut _, + ) + }; + (!result.is_null()).then(|| unsafe { Type::ref_from_raw(result) }) + } + + /// Recursively imports a type interface given its GUID. + /// + /// .. note:: To support this type of lookup a type library must have + /// contain a metadata key called "type_guids" which is a map + /// Dict[string_guid, string_type_name] or + /// Dict[string_guid, Tuple[string_type_name, type_library_name]] + fn import_type_by_guid(&self, guid: S) -> Option> { + let guid = guid.into_bytes_with_nul(); + let result = unsafe { + BNBinaryViewImportTypeLibraryTypeByGuid( + self.as_ref().handle, + guid.as_ref().as_ptr() as *const c_char, + ) + }; + (!result.is_null()).then(|| unsafe { Type::ref_from_raw(result) }) + } + + /// Recursively exports `type_obj` into `lib` as a type with name `name` + /// + /// As other referenced types are encountered, they are either copied into the destination type library or + /// else the type library that provided the referenced type is added as a dependency for the destination library. + fn export_type_to_library(&self, lib: &TypeLibrary, name: &QualifiedName, type_obj: &Type) { + unsafe { + BNBinaryViewExportTypeToTypeLibrary( + self.as_ref().handle, + lib.as_raw(), + &name.0 as *const _ as *mut _, + type_obj.handle, + ) + } + } + + /// Recursively exports `type_obj` into `lib` as a type with name `name` + /// + /// As other referenced types are encountered, they are either copied into the destination type library or + /// else the type library that provided the referenced type is added as a dependency for the destination library. + fn export_object_to_library(&self, lib: &TypeLibrary, name: &QualifiedName, type_obj: &Type) { + unsafe { + BNBinaryViewExportObjectToTypeLibrary( + self.as_ref().handle, + lib.as_raw(), + &name.0 as *const _ as *mut _, + type_obj.handle, + ) + } + } + + /// Gives you details of which type library and name was used to determine + /// the type of a symbol at a given address + /// + /// * `addr` - address of symbol at import site + /// * `platform` - Platform of symbol at import site + fn lookup_imported_object_library( + &self, + addr: u64, + platform: &Platform, + ) -> Option<(TypeLibrary, QualifiedName)> { + let mut result_lib = ptr::null_mut(); + let mut result_name = Default::default(); + let success = unsafe { + BNBinaryViewLookupImportedObjectLibrary( + self.as_ref().handle, + platform.handle, + addr, + &mut result_lib, + &mut result_name, + ) + }; + if !success { + return None; + } + let lib = unsafe { TypeLibrary::from_raw(ptr::NonNull::new(result_lib)?) }; + let name = QualifiedName(result_name); + Some((lib, name)) + } + + /// Gives you details of from which type library and name a given type in the analysis was imported. + /// + /// * `name` - Name of type in analysis + fn lookup_imported_type_library( + &self, + name: &QualifiedNameAndType, + ) -> Option<(TypeLibrary, QualifiedName)> { + let mut result_lib = ptr::null_mut(); + let mut result_name = Default::default(); + let success = unsafe { + BNBinaryViewLookupImportedTypeLibrary( + self.as_ref().handle, + &name.0 as *const _ as *mut _, + &mut result_lib, + &mut result_name, + ) + }; + if !success { + return None; + } + let lib = unsafe { TypeLibrary::from_raw(ptr::NonNull::new(result_lib)?) }; + let name = QualifiedName(result_name); + Some((lib, name)) + } } impl BinaryViewExt for T {} @@ -1545,7 +1807,7 @@ pub type BinaryViewEventType = BNBinaryViewEventType; /// /// # Example /// -/// ```rust +/// ```no_run /// use binaryninja::binaryview::{BinaryView, BinaryViewEventHandler, BinaryViewEventType, register_binary_view_event}; /// /// struct EventHandlerContext { @@ -1553,7 +1815,7 @@ pub type BinaryViewEventType = BNBinaryViewEventType; /// } /// /// impl BinaryViewEventHandler for EventHandlerContext { -/// fn on_event(&mut self, binary_view: &BinaryView) { +/// fn on_event(&self, binary_view: &BinaryView) { /// // handle event /// } /// } @@ -1576,11 +1838,9 @@ where ctx: *mut ::std::os::raw::c_void, view: *mut BNBinaryView, ) { - ffi_wrap!("EventHandler::on_event", unsafe { - let mut context = &mut *(ctx as *mut Handler); - - let handle = BinaryView::from_raw(BNNewViewReference(view)); - Handler::on_event(&mut context, handle.as_ref()); + ffi_wrap!("EventHandler::on_event", { + let context = unsafe { &*(ctx as *const Handler) }; + context.on_event(&BinaryView::from_raw(BNNewViewReference(view))); }) } diff --git a/src/callingconvention.rs b/src/callingconvention.rs index 56755b3..a009b43 100644 --- a/src/callingconvention.rs +++ b/src/callingconvention.rs @@ -26,7 +26,7 @@ use binaryninjacore_sys::*; use crate::architecture::{Architecture, ArchitectureExt, CoreArchitecture, Register}; use crate::rc::{ - CoreArrayProvider, CoreArrayWrapper, CoreOwnedArrayProvider, Guard, Ref, RefCountable, + CoreArrayProvider, CoreArrayProviderInner, Guard, Ref, RefCountable, }; use crate::string::*; @@ -89,23 +89,15 @@ where *count = len; if len == 0 { - ptr::null_mut() - } else { - let mut res = Vec::with_capacity(len + 1); - - res.push(len as u32); - - for i in items { - res.push(i); - } - - assert!(res.len() == len + 1); - - let raw = res.as_mut_ptr(); - mem::forget(res); - - unsafe { raw.offset(1) } + return ptr::null_mut(); } + + let res: Box<[_]> = [len as u32].into_iter().chain(items).collect(); + debug_assert!(res.len() == len + 1); + + // it's free on the function below: `cb_free_register_list` + let raw = Box::leak(res); + &mut raw[1] } extern "C" fn cb_free_register_list(_ctxt: *mut c_void, regs: *mut u32) { @@ -115,8 +107,8 @@ where } let actual_start = regs.offset(-1); - let len = *actual_start + 1; - let _regs = Vec::from_raw_parts(actual_start, len as usize, len as usize); + let len = (*actual_start) + 1; + let _regs = Box::from_raw(ptr::slice_from_raw_parts_mut(actual_start, len as usize)); }) } @@ -136,7 +128,7 @@ where where C: CallingConventionBase, { - ffi_wrap!("CallingConvention::_callee_saved_registers", unsafe { + ffi_wrap!("CallingConvention::callee_saved_registers", unsafe { let ctxt = &*(ctxt as *mut CustomCallingConventionContext); let regs = ctxt.cc.callee_saved_registers(); @@ -448,24 +440,21 @@ impl CallingConvention { unsafe { BnString::from_raw(BNGetCallingConventionName(self.handle)) } } - pub fn variables_for_parameters( + pub fn variables_for_parameters( &self, - params: &[FunctionParameter], + params: &[FunctionParameter], permitted_registers: Option<&[A::Register]>, ) -> Vec { let mut bn_params: Vec = vec![]; - let mut name_strings = vec![]; + let name_strings = params.iter().map(|parameter| ¶meter.name); - for parameter in params.iter() { - name_strings.push(parameter.name.clone().into_bytes_with_nul()); - } - for (parameter, raw_name) in params.iter().zip(name_strings.iter_mut()) { + for (parameter, raw_name) in params.iter().zip(name_strings) { let location = match ¶meter.location { Some(location) => location.raw(), None => unsafe { mem::zeroed() }, }; bn_params.push(BNFunctionParameter { - name: raw_name.as_ref().as_ptr() as *mut _, + name: BnString::new(raw_name).into_raw(), type_: parameter.t.contents.handle, typeConfidence: parameter.t.confidence, defaultLocation: parameter.location.is_none(), @@ -501,9 +490,6 @@ impl CallingConvention { } }; - // Gotta keep this around so the pointers are valid during the call - drop(name_strings); - let vars_slice = unsafe { slice::from_raw_parts(vars, count) }; let mut result = vec![]; for var in vars_slice { @@ -575,11 +561,43 @@ impl CallingConventionBase for CallingConvention { } fn int_arg_registers(&self) -> Vec { - Vec::new() + unsafe { + let mut count = 0; + let regs = BNGetIntegerArgumentRegisters(self.handle, &mut count); + let arch = self.arch_handle.borrow(); + + let res = slice::from_raw_parts(regs, count) + .iter() + .map(|&r| { + arch.register_from_id(r) + .expect("bad reg id from CallingConvention") + }) + .collect(); + + BNFreeRegisterList(regs); + + res + } } fn float_arg_registers(&self) -> Vec { - Vec::new() + unsafe { + let mut count = 0; + let regs = BNGetFloatArgumentRegisters(self.handle, &mut count); + let arch = self.arch_handle.borrow(); + + let res = slice::from_raw_parts(regs, count) + .iter() + .map(|&r| { + arch.register_from_id(r) + .expect("bad reg id from CallingConvention") + }) + .collect(); + + BNFreeRegisterList(regs); + + res + } } fn arg_registers_shared_index(&self) -> bool { @@ -660,18 +678,14 @@ unsafe impl RefCountable for CallingConvention { impl CoreArrayProvider for CallingConvention { type Raw = *mut BNCallingConvention; type Context = A::Handle; + type Wrapped<'a> = Guard<'a, CallingConvention>; } -unsafe impl CoreOwnedArrayProvider for CallingConvention { +unsafe impl CoreArrayProviderInner for CallingConvention { unsafe fn free(raw: *mut *mut BNCallingConvention, count: usize, _content: &Self::Context) { BNFreeCallingConventionList(raw, count); } -} - -unsafe impl<'a, A: Architecture> CoreArrayWrapper<'a> for CallingConvention { - type Wrapped = Guard<'a, CallingConvention>; - - unsafe fn wrap_raw(raw: &'a Self::Raw, context: &'a Self::Context) -> Self::Wrapped { + unsafe fn wrap_raw<'a>(raw: &'a Self::Raw, context: &'a Self::Context) -> Self::Wrapped<'a> { Guard::new( CallingConvention { handle: *raw, @@ -691,7 +705,7 @@ impl Debug for CallingConvention { pub struct ConventionBuilder { caller_saved_registers: Vec, - _callee_saved_registers: Vec, + callee_saved_registers: Vec, int_arg_registers: Vec, float_arg_registers: Vec, @@ -760,7 +774,7 @@ impl ConventionBuilder { pub fn new(arch: &A) -> Self { Self { caller_saved_registers: Vec::new(), - _callee_saved_registers: Vec::new(), + callee_saved_registers: Vec::new(), int_arg_registers: Vec::new(), float_arg_registers: Vec::new(), @@ -785,7 +799,7 @@ impl ConventionBuilder { } reg_list!(caller_saved_registers); - reg_list!(_callee_saved_registers); + reg_list!(callee_saved_registers); reg_list!(int_arg_registers); reg_list!(float_arg_registers); @@ -819,7 +833,7 @@ impl CallingConventionBase for ConventionBuilder { } fn callee_saved_registers(&self) -> Vec { - self.caller_saved_registers.clone() + self.callee_saved_registers.clone() } fn int_arg_registers(&self) -> Vec { diff --git a/src/command.rs b/src/command.rs index f533273..e682bae 100644 --- a/src/command.rs +++ b/src/command.rs @@ -16,12 +16,16 @@ //! //! All plugins need to provide one of the following functions for Binary Ninja to call: //! -//! ```rust -//! pub extern "C" fn CorePluginInit() -> bool {} +//! ```no_run +//! pub extern "C" fn CorePluginInit() -> bool { +//! todo!(); +//! } //! ``` //! -//! ```rust -//! pub extern "C" fn UIPluginInit() -> bool {} +//! ```no_run +//! pub extern "C" fn UIPluginInit() -> bool { +//! todo!(); +//! } //! ``` //! //! Both of these functions can call any of the following registration functions, though `CorePluginInit` is called during Binary Ninja core initialization, and `UIPluginInit` is called during Binary Ninja UI initialization. @@ -62,7 +66,9 @@ where /// The function call required for generic commands; commands added in this way will be in the `Plugins` submenu of the menu bar. /// /// # Example -/// ```rust +/// ```no_run +/// # use binaryninja::command::Command; +/// # use binaryninja::binaryview::BinaryView; /// struct MyCommand; /// /// impl Command for MyCommand { @@ -76,6 +82,7 @@ where /// } /// } /// +/// # use binaryninja::command::register; /// #[no_mangle] /// pub extern "C" fn CorePluginInit() -> bool { /// register( @@ -160,7 +167,9 @@ where /// The function call required for generic commands; commands added in this way will be in the `Plugins` submenu of the menu bar. /// /// # Example -/// ```rust +/// ```no_run +/// # use binaryninja::command::AddressCommand; +/// # use binaryninja::binaryview::BinaryView; /// struct MyCommand; /// /// impl AddressCommand for MyCommand { @@ -174,6 +183,7 @@ where /// } /// } /// +/// # use binaryninja::command::register_for_address; /// #[no_mangle] /// pub extern "C" fn CorePluginInit() -> bool { /// register_for_address( @@ -258,10 +268,13 @@ where /// The function call required for generic commands; commands added in this way will be in the `Plugins` submenu of the menu bar. /// /// # Example -/// ```rust +/// ```no_run +/// # use std::ops::Range; +/// # use binaryninja::command::RangeCommand; +/// # use binaryninja::binaryview::BinaryView; /// struct MyCommand; /// -/// impl AddressCommand for MyCommand { +/// impl RangeCommand for MyCommand { /// fn action(&self, view: &BinaryView, range: Range) { /// // Your code here /// } @@ -272,6 +285,7 @@ where /// } /// } /// +/// # use binaryninja::command::register_for_range; /// #[no_mangle] /// pub extern "C" fn CorePluginInit() -> bool { /// register_for_range( @@ -361,10 +375,14 @@ where /// The function call required for generic commands; commands added in this way will be in the `Plugins` submenu of the menu bar. /// /// # Example -/// ```rust +/// ```no_run +/// # use binaryninja::command::FunctionCommand; +/// # use binaryninja::binaryview::BinaryView; +/// # use binaryninja::function::Function; +/// # use binaryninja::command::register_for_function; /// struct MyCommand; /// -/// impl AddressCommand for MyCommand { +/// impl FunctionCommand for MyCommand { /// fn action(&self, view: &BinaryView, func: &Function) { /// // Your code here /// } diff --git a/src/component.rs b/src/component.rs new file mode 100644 index 0000000..ec23504 --- /dev/null +++ b/src/component.rs @@ -0,0 +1,295 @@ +use core::{ffi, mem, ptr}; + +use crate::binaryview::{BinaryView, BinaryViewBase, BinaryViewExt}; +use crate::function::Function; +use crate::rc::{Array, CoreArrayProvider, CoreArrayProviderInner, Ref}; +use crate::string::{BnStrCompatible, BnString}; +use crate::types::{ComponentReferencedTypes, DataVariable}; + +use binaryninjacore_sys::*; + +pub struct ComponentBuilder { + bv: *mut BNBinaryView, + parent: Option, + name: Option, +} + +impl ComponentBuilder { + pub(crate) fn new_from_raw(bv: *mut BNBinaryView) -> Self { + Self { + bv, + parent: None, + name: None, + } + } + pub fn new(bv: &I) -> Self { + Self { + bv: bv.as_ref().handle, + parent: None, + name: None, + } + } + + pub fn parent(mut self, parent: G) -> Self { + self.parent = Some(parent.component_guid()); + self + } + + pub fn name(mut self, name: S) -> Self { + self.name = Some(BnString::new(name)); + self + } + + pub fn finalize(self) -> Component { + let result = match (&self.parent, &self.name) { + (None, None) => unsafe { BNCreateComponent(self.bv) }, + (None, Some(name)) => unsafe { BNCreateComponentWithName(self.bv, name.as_ptr()) }, + (Some(guid), None) => unsafe { BNCreateComponentWithParent(self.bv, guid.as_ptr()) }, + (Some(guid), Some(name)) => unsafe { + BNCreateComponentWithParentAndName(self.bv, guid.as_ptr(), name.as_ptr()) + }, + }; + unsafe { Component::from_raw(ptr::NonNull::new(result).unwrap()) } + } +} + +/// Components are objects that can contain Functions, Data Variables, and other Components. +/// +/// They can be queried for information about the items contained within them. +/// +/// Components have a Guid, which persistent across saves and loads of the database, and should be +/// used for retrieving components when such is required and a reference to the Component cannot be held. +#[repr(transparent)] +pub struct Component { + handle: ptr::NonNull, +} + +impl Component { + #[allow(clippy::mut_from_ref)] + pub(crate) unsafe fn as_raw(&self) -> &mut BNComponent { + &mut *self.handle.as_ptr() + } + + pub(crate) unsafe fn from_raw(handle: ptr::NonNull) -> Self { + Self { handle } + } + + pub(crate) unsafe fn ref_from_raw(handle: &*mut BNComponent) -> &Self { + assert!(!handle.is_null()); + mem::transmute(handle) + } + + pub fn guid(&self) -> BnString { + let result = unsafe { BNComponentGetGuid(self.as_raw()) }; + assert!(!result.is_null()); + unsafe { BnString::from_raw(result) } + } + + /// Add function to this component. + pub fn add_function(&self, func: &Function) -> bool { + unsafe { BNComponentAddFunctionReference(self.as_raw(), func.handle) } + } + + /// Check whether this component contains a function. + pub fn contains_function(&self, func: &Function) -> bool { + unsafe { BNComponentContainsFunction(self.as_raw(), func.handle) } + } + + /// Remove function from this component. + pub fn remove_function(&self, func: &Function) -> bool { + unsafe { BNComponentRemoveFunctionReference(self.as_raw(), func.handle) } + } + + /// Move component to this component. This will remove it from the old parent. + pub fn add_component(&self, component: &Component) -> bool { + unsafe { BNComponentAddComponent(self.as_raw(), component.as_raw()) } + } + + /// Check whether this component contains a component. + pub fn contains_component(&self, component: &Component) -> bool { + unsafe { BNComponentContainsComponent(self.as_raw(), component.as_raw()) } + } + + /// Remove a component from the current component, moving it to the root. + /// + /// This function has no effect when used from the root component. + /// Use `BinaryView.remove_component` to Remove a component from the tree entirely. + pub fn remove_component(&self, component: &Component) -> bool { + self.view() + .unwrap() + .root_component() + .unwrap() + .add_component(component) + } + + /// Add data variable to this component. + pub fn add_data_variable(&self, data_variable: &DataVariable) -> bool { + unsafe { BNComponentAddDataVariable(self.as_raw(), data_variable.address()) } + } + + /// Check whether this component contains a data variable. + pub fn contains_data_variable(&self, data_variable: &DataVariable) -> bool { + unsafe { BNComponentContainsDataVariable(self.as_raw(), data_variable.address()) } + } + + /// Remove data variable from this component. + pub fn remove_data_variable(&self, data_variable: &DataVariable) -> bool { + unsafe { BNComponentRemoveDataVariable(self.as_raw(), data_variable.address()) } + } + + /// Original name of the component + pub fn display_name(&self) -> BnString { + let result = unsafe { BNComponentGetDisplayName(self.as_raw()) }; + assert!(!result.is_null()); + unsafe { BnString::from_raw(result) } + } + + /// Original name set for this component + + /// :note: The `.display_name` property should be used for `bv.get_component_by_path()` lookups. + + /// This can differ from the .display_name property if one of its sibling components has the same .original_name; In that + /// case, .name will be an automatically generated unique name (e.g. "MyComponentName (1)") while .original_name will + /// remain what was originally set (e.g. "MyComponentName") + + /// If this component has a duplicate name and is moved to a component where none of its siblings share its name, + /// .name will return the original "MyComponentName" + pub fn name(&self) -> BnString { + let result = unsafe { BNComponentGetOriginalName(self.as_raw()) }; + assert!(!result.is_null()); + unsafe { BnString::from_raw(result) } + } + + pub fn set_name(&self, name: S) { + let name = name.into_bytes_with_nul(); + unsafe { BNComponentSetName(self.as_raw(), name.as_ref().as_ptr() as *const ffi::c_char) } + } + + /// The component that contains this component, if it exists. + pub fn parent(&self) -> Option { + let result = unsafe { BNComponentGetParent(self.as_raw()) }; + ptr::NonNull::new(result).map(|h| unsafe { Self::from_raw(h) }) + } + + pub fn view(&self) -> Option> { + let result = unsafe { BNComponentGetView(self.as_raw()) }; + (!result.is_null()).then(|| unsafe { BinaryView::from_raw(result) }) + } + + /// Is an iterator for all Components contained within this Component + pub fn components(&self) -> Array { + let mut count = 0; + let result = unsafe { BNComponentGetContainedComponents(self.as_raw(), &mut count) }; + assert!(!result.is_null()); + unsafe { Array::new(result, count, ()) } + } + + /// List of all Functions contained within this Component + pub fn functions(&self) -> Array { + let mut count = 0; + let result = unsafe { BNComponentGetContainedFunctions(self.as_raw(), &mut count) }; + assert!(!result.is_null()); + unsafe { Array::new(result, count, ()) } + } + + /// List of all Data Variables contained within this Component + pub fn data_variables(&self) -> Array { + let mut count = 0; + let result = unsafe { BNComponentGetContainedDataVariables(self.as_raw(), &mut count) }; + assert!(!result.is_null()); + unsafe { Array::new(result, count, ()) } + } + + /// Get data variables referenced by this component + /// + /// * `recursive` - Get all DataVariables referenced by this component and subcomponents. + pub fn get_referenced_data_variables(&self, recursive: bool) -> Array { + let mut count = 0; + let result = if recursive { + unsafe { BNComponentGetReferencedDataVariablesRecursive(self.as_raw(), &mut count) } + } else { + unsafe { BNComponentGetReferencedDataVariables(self.as_raw(), &mut count) } + }; + unsafe { Array::new(result, count, ()) } + } + + /// Get Types referenced by this component + /// + /// * `recursive` - Get all Types referenced by this component and subcomponents. + pub fn get_referenced_types(&self, recursive: bool) -> Array { + let mut count = 0; + let result = if recursive { + unsafe { BNComponentGetReferencedTypesRecursive(self.as_raw(), &mut count) } + } else { + unsafe { BNComponentGetReferencedTypes(self.as_raw(), &mut count) } + }; + unsafe { Array::new(result, count, ()) } + } + + pub fn remove_all_functions(&self) { + unsafe { BNComponentRemoveAllFunctions(self.as_raw()) } + } + + pub fn add_all_members_from(&self, component: &Component) { + unsafe { BNComponentAddAllMembersFromComponent(self.as_raw(), component.as_raw()) } + } +} + +impl PartialEq for Component { + fn eq(&self, other: &Self) -> bool { + unsafe { BNComponentsEqual(self.as_raw(), other.as_raw()) } + } + + #[allow(clippy::partialeq_ne_impl)] + fn ne(&self, other: &Self) -> bool { + unsafe { BNComponentsNotEqual(self.as_raw(), other.as_raw()) } + } +} + +impl Eq for Component {} + +impl Drop for Component { + fn drop(&mut self) { + unsafe { BNFreeComponent(self.as_raw()) } + } +} + +impl Clone for Component { + fn clone(&self) -> Self { + unsafe { + Self::from_raw(ptr::NonNull::new(BNNewComponentReference(self.as_raw())).unwrap()) + } + } +} + +impl CoreArrayProvider for Component { + type Raw = *mut BNComponent; + type Context = (); + type Wrapped<'a> = &'a Self; +} + +unsafe impl CoreArrayProviderInner for Component { + unsafe fn free(raw: *mut Self::Raw, count: usize, _context: &Self::Context) { + BNFreeComponents(raw, count) + } + + unsafe fn wrap_raw<'a>(raw: &'a Self::Raw, _context: &'a Self::Context) -> Self::Wrapped<'a> { + Self::ref_from_raw(raw) + } +} + +pub trait IntoComponentGuid { + fn component_guid(self) -> BnString; +} + +impl IntoComponentGuid for &Component { + fn component_guid(self) -> BnString { + self.guid() + } +} + +impl IntoComponentGuid for S { + fn component_guid(self) -> BnString { + BnString::new(self) + } +} diff --git a/src/custombinaryview.rs b/src/custombinaryview.rs index 21b63c9..05f1acb 100644 --- a/src/custombinaryview.rs +++ b/src/custombinaryview.rs @@ -20,6 +20,7 @@ pub use binaryninjacore_sys::BNModificationStatus as ModificationStatus; use std::marker::PhantomData; use std::mem; +use std::mem::MaybeUninit; use std::os::raw::c_void; use std::ptr; use std::slice; @@ -122,11 +123,10 @@ where let long_name = long_name.into_bytes_with_nul(); let long_name_ptr = long_name.as_ref().as_ptr() as *mut _; - let ctxt = Box::new(unsafe { mem::zeroed() }); - let ctxt = Box::into_raw(ctxt); + let ctxt = Box::leak(Box::new(MaybeUninit::zeroed())); let mut bn_obj = BNCustomBinaryViewType { - context: ctxt as *mut _, + context: ctxt.as_mut_ptr() as *mut _, create: Some(cb_create::), parse: Some(cb_parse::), isValidForData: Some(cb_valid::), @@ -140,15 +140,16 @@ where if res.is_null() { // avoid leaking the space allocated for the type, but also // avoid running its Drop impl (if any -- not that there should - // be one since view types live for the life of the process) - mem::forget(*Box::from_raw(ctxt)); + // be one since view types live for the life of the process) as + // MaybeUninit suppress the Drop implementation of it's inner type + drop(Box::from_raw(ctxt)); panic!("bvt registration failed"); } - ptr::write(ctxt, constructor(BinaryViewType(res))); + ctxt.write(constructor(BinaryViewType(res))); - &*ctxt + ctxt.assume_init_mut() } } @@ -289,19 +290,15 @@ impl BinaryViewTypeBase for BinaryViewType { impl CoreArrayProvider for BinaryViewType { type Raw = *mut BNBinaryViewType; type Context = (); + type Wrapped<'a> = Guard<'a, BinaryViewType>; } -unsafe impl CoreOwnedArrayProvider for BinaryViewType { +unsafe impl CoreArrayProviderInner for BinaryViewType { unsafe fn free(raw: *mut Self::Raw, _count: usize, _context: &Self::Context) { BNFreeBinaryViewTypeList(raw); } -} - -unsafe impl<'a> CoreArrayWrapper<'a> for BinaryViewType { - type Wrapped = BinaryViewType; - - unsafe fn wrap_raw(raw: &'a Self::Raw, _context: &'a Self::Context) -> Self::Wrapped { - BinaryViewType(*raw) + unsafe fn wrap_raw<'a>(raw: &'a Self::Raw, _context: &'a Self::Context) -> Self::Wrapped<'a> { + Guard::new(BinaryViewType(*raw), &()) } } @@ -388,7 +385,7 @@ impl<'a, T: CustomBinaryViewType> CustomViewBuilder<'a, T> { let view_name = view_type.name(); - if let Ok(bv) = file.get_view_of_type(view_name.as_cstr()) { + if let Ok(bv) = file.get_view_of_type(view_name.as_str()) { // while it seems to work most of the time, you can get really unlucky // if the a free of the existing view of the same type kicks off while // BNCreateBinaryViewOfType is still running. the freeObject callback @@ -772,7 +769,7 @@ impl<'a, T: CustomBinaryViewType> CustomViewBuilder<'a, T> { unsafe { let res = BNCreateCustomBinaryView( - view_name.as_cstr().as_ptr(), + view_name.as_ptr(), file.handle, parent.handle, &mut bn_obj, diff --git a/src/database.rs b/src/database.rs new file mode 100644 index 0000000..3274c9c --- /dev/null +++ b/src/database.rs @@ -0,0 +1,654 @@ +use std::collections::HashMap; +use std::time::{Duration, SystemTime, UNIX_EPOCH}; +use std::{ffi, mem, ptr}; + +use binaryninjacore_sys::*; + +use crate::binaryview::BinaryView; +use crate::databuffer::DataBuffer; +use crate::disassembly::InstructionTextToken; +use crate::filemetadata::FileMetadata; +use crate::rc::{Array, CoreArrayProvider, CoreArrayProviderInner, Ref}; +use crate::string::{BnStrCompatible, BnString}; + +#[repr(transparent)] +pub struct Database { + handle: ptr::NonNull, +} + +impl Database { + pub(crate) unsafe fn from_raw(handle: ptr::NonNull) -> Self { + Self { handle } + } + + #[allow(clippy::mut_from_ref)] + pub(crate) unsafe fn as_raw(&self) -> &mut BNDatabase { + &mut *self.handle.as_ptr() + } + + /// Get a snapshot by its id, or None if no snapshot with that id exists + pub fn snapshot(&self, id: i64) -> Option { + let result = unsafe { BNGetDatabaseSnapshot(self.as_raw(), id) }; + ptr::NonNull::new(result).map(|handle| unsafe { Snapshot::from_raw(handle) }) + } + + /// Get a list of all snapshots in the database + pub fn snapshots(&self) -> Array { + let mut count = 0; + let result = unsafe { BNGetDatabaseSnapshots(self.as_raw(), &mut count) }; + assert!(!result.is_null()); + unsafe { Array::new(result, count, ()) } + } + + /// Get the current snapshot + pub fn current_snapshot(&self) -> Option { + let result = unsafe { BNGetDatabaseCurrentSnapshot(self.as_raw()) }; + ptr::NonNull::new(result).map(|handle| unsafe { Snapshot::from_raw(handle) }) + } + + pub fn set_current_snapshot(&self, value: &Snapshot) { + unsafe { BNSetDatabaseCurrentSnapshot(self.as_raw(), value.id()) } + } + + pub fn write_snapshot_data( + &self, + parents: &[i64], + file: &BinaryView, + name: N, + data: &KeyValueStore, + auto_save: bool, + ) -> i64 { + let name_raw = name.into_bytes_with_nul(); + let name_ptr = name_raw.as_ref().as_ptr() as *const ffi::c_char; + unsafe { + BNWriteDatabaseSnapshotData( + self.as_raw(), + parents.as_ptr() as *mut _, + parents.len(), + file.handle, + name_ptr, + data.as_raw(), + auto_save, + ptr::null_mut(), + Some(cb_progress_nop), + ) + } + } + + pub fn write_snapshot_data_with_progress( + &self, + parents: &[i64], + file: &BinaryView, + name: N, + data: &KeyValueStore, + auto_save: bool, + mut progress: F, + ) -> i64 + where + N: BnStrCompatible, + F: FnMut(usize, usize) -> bool, + { + let name_raw = name.into_bytes_with_nul(); + let name_ptr = name_raw.as_ref().as_ptr() as *const ffi::c_char; + let ctxt = &mut progress as *mut _ as *mut ffi::c_void; + unsafe { + BNWriteDatabaseSnapshotData( + self.as_raw(), + parents.as_ptr() as *mut _, + parents.len(), + file.handle, + name_ptr, + data.as_raw(), + auto_save, + ctxt, + Some(cb_progress::), + ) + } + } + + /// Trim a snapshot's contents in the database by id, but leave the parent/child + /// hierarchy intact. Future references to this snapshot will return False for has_contents + pub fn trim_snapshot(&self, id: i64) -> Result<(), ()> { + if unsafe { BNTrimDatabaseSnapshot(self.as_raw(), id) } { + Ok(()) + } else { + Err(()) + } + } + + /// Remove a snapshot in the database by id, deleting its contents and references. + /// Attempting to remove a snapshot with children will raise an exception. + pub fn remove_snapshot(&self, id: i64) -> Result<(), ()> { + if unsafe { BNRemoveDatabaseSnapshot(self.as_raw(), id) } { + Ok(()) + } else { + Err(()) + } + } + pub fn has_global(&self, key: S) -> bool { + let key_raw = key.into_bytes_with_nul(); + let key_ptr = key_raw.as_ref().as_ptr() as *const ffi::c_char; + unsafe { BNDatabaseHasGlobal(self.as_raw(), key_ptr) != 0 } + } + + /// Get a list of keys for all globals in the database + pub fn global_keys(&self) -> Array { + let mut count = 0; + let result = unsafe { BNGetDatabaseGlobalKeys(self.as_raw(), &mut count) }; + assert!(!result.is_null()); + unsafe { Array::new(result, count, ()) } + } + + /// Get a dictionary of all globals + pub fn globals(&self) -> HashMap { + self.global_keys() + .iter() + .filter_map(|key| Some((key.to_string(), self.read_global(key)?.to_string()))) + .collect() + } + + /// Get a specific global by key + pub fn read_global(&self, key: S) -> Option { + let key_raw = key.into_bytes_with_nul(); + let key_ptr = key_raw.as_ref().as_ptr() as *const ffi::c_char; + let result = unsafe { BNReadDatabaseGlobal(self.as_raw(), key_ptr) }; + unsafe { ptr::NonNull::new(result).map(|_| BnString::from_raw(result)) } + } + + /// Write a global into the database + pub fn write_global(&self, key: K, value: V) -> bool { + let key_raw = key.into_bytes_with_nul(); + let key_ptr = key_raw.as_ref().as_ptr() as *const ffi::c_char; + let value_raw = value.into_bytes_with_nul(); + let value_ptr = value_raw.as_ref().as_ptr() as *const ffi::c_char; + unsafe { BNWriteDatabaseGlobal(self.as_raw(), key_ptr, value_ptr) } + } + + /// Get a specific global by key, as a binary buffer + pub fn read_global_data(&self, key: S) -> Option { + let key_raw = key.into_bytes_with_nul(); + let key_ptr = key_raw.as_ref().as_ptr() as *const ffi::c_char; + let result = unsafe { BNReadDatabaseGlobalData(self.as_raw(), key_ptr) }; + ptr::NonNull::new(result).map(|_| DataBuffer::from_raw(result)) + } + + /// Write a binary buffer into a global in the database + pub fn write_global_data(&self, key: K, value: &DataBuffer) -> bool { + let key_raw = key.into_bytes_with_nul(); + let key_ptr = key_raw.as_ref().as_ptr() as *const ffi::c_char; + unsafe { BNWriteDatabaseGlobalData(self.as_raw(), key_ptr, value.as_raw()) } + } + + /// Get the owning FileMetadata + pub fn file(&self) -> Ref { + let result = unsafe { BNGetDatabaseFile(self.as_raw()) }; + assert!(!result.is_null()); + unsafe { Ref::new(FileMetadata::from_raw(result)) } + } + + /// Get the backing analysis cache kvs + pub fn analysis_cache(&self) -> KeyValueStore { + let result = unsafe { BNReadDatabaseAnalysisCache(self.as_raw()) }; + unsafe { KeyValueStore::from_raw(ptr::NonNull::new(result).unwrap()) } + } + + pub fn reload_connection(&self) { + unsafe { BNDatabaseReloadConnection(self.as_raw()) } + } + + pub fn write_analysis_cache(&self, val: &KeyValueStore) -> Result<(), ()> { + if unsafe { BNWriteDatabaseAnalysisCache(self.as_raw(), val.as_raw()) } { + Ok(()) + } else { + Err(()) + } + } + + pub fn snapshot_has_data(&self, id: i64) -> bool { + unsafe { BNSnapshotHasData(self.as_raw(), id) } + } +} + +impl Clone for Database { + fn clone(&self) -> Self { + unsafe { Self::from_raw(ptr::NonNull::new(BNNewDatabaseReference(self.as_raw())).unwrap()) } + } +} + +impl Drop for Database { + fn drop(&mut self) { + unsafe { BNFreeDatabase(self.as_raw()) } + } +} + +#[repr(transparent)] +pub struct Snapshot { + handle: ptr::NonNull, +} + +impl Snapshot { + pub(crate) unsafe fn from_raw(handle: ptr::NonNull) -> Self { + Self { handle } + } + + pub(crate) unsafe fn ref_from_raw(handle: &*mut BNSnapshot) -> &Self { + mem::transmute(handle) + } + + #[allow(clippy::mut_from_ref)] + pub(crate) unsafe fn as_raw(&self) -> &mut BNSnapshot { + &mut *self.handle.as_ptr() + } + + /// Get the owning database + pub fn database(&self) -> Database { + unsafe { + Database::from_raw(ptr::NonNull::new(BNGetSnapshotDatabase(self.as_raw())).unwrap()) + } + } + + /// Get the numerical id (read-only) + pub fn id(&self) -> i64 { + unsafe { BNGetSnapshotId(self.as_raw()) } + } + + /// Get the displayed snapshot name + pub fn name(&self) -> BnString { + unsafe { BnString::from_raw(BNGetSnapshotName(self.as_raw())) } + } + + /// Set the displayed snapshot name + pub fn set_name(&self, value: S) { + let value_raw = value.into_bytes_with_nul(); + let value_ptr = value_raw.as_ref().as_ptr() as *const ffi::c_char; + unsafe { BNSetSnapshotName(self.as_raw(), value_ptr) } + } + + /// If the snapshot was the result of an auto-save + pub fn is_auto_save(&self) -> bool { + unsafe { BNIsSnapshotAutoSave(self.as_raw()) } + } + + /// If the snapshot has contents, and has not been trimmed + pub fn has_contents(&self) -> bool { + unsafe { BNSnapshotHasContents(self.as_raw()) } + } + + /// If the snapshot has undo data + pub fn has_undo(&self) -> bool { + unsafe { BNSnapshotHasUndo(self.as_raw()) } + } + + /// Get the first parent of the snapshot, or None if it has no parents + pub fn first_parent(&self) -> Option { + let result = unsafe { BNGetSnapshotFirstParent(self.as_raw()) }; + ptr::NonNull::new(result).map(|s| unsafe { Snapshot::from_raw(s) }) + } + + /// Get a list of all parent snapshots of the snapshot + pub fn parents(&self) -> Array { + let mut count = 0; + let result = unsafe { BNGetSnapshotParents(self.as_raw(), &mut count) }; + assert!(!result.is_null()); + unsafe { Array::new(result, count, ()) } + } + + /// Get a list of all child snapshots of the snapshot + pub fn children(&self) -> Array { + let mut count = 0; + let result = unsafe { BNGetSnapshotChildren(self.as_raw(), &mut count) }; + assert!(!result.is_null()); + unsafe { Array::new(result, count, ()) } + } + + /// Get a buffer of the raw data at the time of the snapshot + pub fn file_contents(&self) -> Option { + self.has_contents().then(|| unsafe { + let result = BNGetSnapshotFileContents(self.as_raw()); + assert!(!result.is_null()); + DataBuffer::from_raw(result) + }) + } + + /// Get a hash of the data at the time of the snapshot + pub fn file_contents_hash(&self) -> Option { + self.has_contents().then(|| unsafe { + let result = BNGetSnapshotFileContentsHash(self.as_raw()); + assert!(!result.is_null()); + DataBuffer::from_raw(result) + }) + } + + /// Get a list of undo entries at the time of the snapshot + pub fn undo_entries(&self) -> Array { + assert!(self.has_undo()); + let mut count = 0; + let result = unsafe { BNGetSnapshotUndoEntries(self.as_raw(), &mut count) }; + assert!(!result.is_null()); + unsafe { Array::new(result, count, ()) } + } + + pub fn undo_entries_with_progress bool>( + &self, + mut progress: F, + ) -> Array { + assert!(self.has_undo()); + let ctxt = &mut progress as *mut _ as *mut ffi::c_void; + let mut count = 0; + let result = unsafe { + BNGetSnapshotUndoEntriesWithProgress( + self.as_raw(), + ctxt, + Some(cb_progress::), + &mut count, + ) + }; + assert!(!result.is_null()); + unsafe { Array::new(result, count, ()) } + } + + /// Get the backing kvs data with snapshot fields + pub fn read_data(&self) -> KeyValueStore { + let result = unsafe { BNReadSnapshotData(self.as_raw()) }; + unsafe { KeyValueStore::from_raw(ptr::NonNull::new(result).unwrap()) } + } + + pub fn read_data_with_progress bool>( + &self, + mut progress: F, + ) -> KeyValueStore { + let ctxt = &mut progress as *mut _ as *mut ffi::c_void; + let result = + unsafe { BNReadSnapshotDataWithProgress(self.as_raw(), ctxt, Some(cb_progress::)) }; + unsafe { KeyValueStore::from_raw(ptr::NonNull::new(result).unwrap()) } + } + + pub fn undo_data(&self) -> DataBuffer { + let result = unsafe { BNGetSnapshotUndoData(self.as_raw()) }; + assert!(!result.is_null()); + DataBuffer::from_raw(result) + } + + pub fn store_data bool>( + &self, + data: KeyValueStore, + mut progress: F, + ) -> bool { + let ctxt = &mut progress as *mut _ as *mut ffi::c_void; + unsafe { BNSnapshotStoreData(self.as_raw(), data.as_raw(), ctxt, Some(cb_progress::)) } + } + + /// Determine if this snapshot has another as an ancestor + pub fn has_ancestor(self, other: &Snapshot) -> bool { + unsafe { BNSnapshotHasAncestor(self.as_raw(), other.as_raw()) } + } +} + +impl Clone for Snapshot { + fn clone(&self) -> Self { + unsafe { Self::from_raw(ptr::NonNull::new(BNNewSnapshotReference(self.as_raw())).unwrap()) } + } +} + +impl Drop for Snapshot { + fn drop(&mut self) { + unsafe { BNFreeSnapshot(self.as_raw()) } + } +} + +impl CoreArrayProvider for Snapshot { + type Raw = *mut BNSnapshot; + type Context = (); + type Wrapped<'a> = &'a Self; +} + +unsafe impl CoreArrayProviderInner for Snapshot { + unsafe fn free(raw: *mut Self::Raw, count: usize, _context: &Self::Context) { + BNFreeSnapshotList(raw, count); + } + + unsafe fn wrap_raw<'a>(raw: &'a Self::Raw, _context: &'a Self::Context) -> Self::Wrapped<'a> { + Self::ref_from_raw(raw) + } +} + +#[repr(transparent)] +pub struct KeyValueStore { + handle: ptr::NonNull, +} + +impl KeyValueStore { + pub(crate) unsafe fn from_raw(handle: ptr::NonNull) -> Self { + Self { handle } + } + + #[allow(clippy::mut_from_ref)] + pub(crate) unsafe fn as_raw(&self) -> &mut BNKeyValueStore { + &mut *self.handle.as_ptr() + } + + /// Get a list of all keys stored in the kvs + pub fn keys(&self) -> Array { + let mut count = 0; + let result = unsafe { BNGetKeyValueStoreKeys(self.as_raw(), &mut count) }; + assert!(!result.is_null()); + unsafe { Array::new(result, count, ()) } + } + + /// Get the value for a single key + pub fn value(&self, key: S) -> Option { + let key_raw = key.into_bytes_with_nul(); + let key_ptr = key_raw.as_ref().as_ptr() as *const ffi::c_char; + let result = unsafe { BNGetKeyValueStoreBuffer(self.as_raw(), key_ptr) }; + ptr::NonNull::new(result).map(|_| DataBuffer::from_raw(result)) + } + + /// Set the value for a single key + pub fn set_value(&self, key: S, value: &DataBuffer) -> bool { + let key_raw = key.into_bytes_with_nul(); + let key_ptr = key_raw.as_ref().as_ptr() as *const ffi::c_char; + unsafe { BNSetKeyValueStoreBuffer(self.as_raw(), key_ptr, value.as_raw()) } + } + + /// Get the stored representation of the kvs + pub fn serialized_data(&self) -> DataBuffer { + let result = unsafe { BNGetKeyValueStoreSerializedData(self.as_raw()) }; + assert!(!result.is_null()); + DataBuffer::from_raw(result) + } + + /// Begin storing new keys into a namespace + pub fn begin_namespace(&self, name: S) { + let name_raw = name.into_bytes_with_nul(); + let name_ptr = name_raw.as_ref().as_ptr() as *const ffi::c_char; + unsafe { BNBeginKeyValueStoreNamespace(self.as_raw(), name_ptr) } + } + + /// End storing new keys into a namespace + pub fn end_namespace(&self) { + unsafe { BNEndKeyValueStoreNamespace(self.as_raw()) } + } + + /// If the kvs is empty + pub fn empty(&self) -> bool { + unsafe { BNIsKeyValueStoreEmpty(self.as_raw()) } + } + + /// Number of values in the kvs + pub fn value_size(&self) -> usize { + unsafe { BNGetKeyValueStoreValueSize(self.as_raw()) } + } + + /// Length of serialized data + pub fn data_size(&self) -> usize { + unsafe { BNGetKeyValueStoreDataSize(self.as_raw()) } + } + + /// Size of all data in storage + pub fn value_storage_size(self) -> usize { + unsafe { BNGetKeyValueStoreValueStorageSize(self.as_raw()) } + } + + /// Number of namespaces pushed with begin_namespace + pub fn namespace_size(self) -> usize { + unsafe { BNGetKeyValueStoreNamespaceSize(self.as_raw()) } + } +} + +impl Clone for KeyValueStore { + fn clone(&self) -> Self { + unsafe { + Self::from_raw(ptr::NonNull::new(BNNewKeyValueStoreReference(self.as_raw())).unwrap()) + } + } +} + +impl Drop for KeyValueStore { + fn drop(&mut self) { + unsafe { BNFreeKeyValueStore(self.as_raw()) } + } +} + +#[repr(transparent)] +pub struct UndoEntry { + handle: ptr::NonNull, +} + +impl UndoEntry { + pub(crate) unsafe fn from_raw(handle: ptr::NonNull) -> Self { + Self { handle } + } + + pub(crate) unsafe fn ref_from_raw(handle: &*mut BNUndoEntry) -> &Self { + mem::transmute(handle) + } + + #[allow(clippy::mut_from_ref)] + pub(crate) unsafe fn as_raw(&self) -> &mut BNUndoEntry { + &mut *self.handle.as_ptr() + } + + pub fn id(&self) -> BnString { + let result = unsafe { BNUndoEntryGetId(self.as_raw()) }; + assert!(!result.is_null()); + unsafe { BnString::from_raw(result) } + } + + pub fn actions(&self) -> Array { + let mut count = 0; + let result = unsafe { BNUndoEntryGetActions(self.as_raw(), &mut count) }; + assert!(!result.is_null()); + unsafe { Array::new(result, count, ()) } + } + + pub fn time(&self) -> SystemTime { + let m = Duration::from_secs(unsafe { BNUndoEntryGetTimestamp(self.as_raw()) }); + UNIX_EPOCH + m + } +} + +impl Clone for UndoEntry { + fn clone(&self) -> Self { + unsafe { + Self::from_raw(ptr::NonNull::new(BNNewUndoEntryReference(self.as_raw())).unwrap()) + } + } +} + +impl Drop for UndoEntry { + fn drop(&mut self) { + unsafe { BNFreeUndoEntry(self.as_raw()) } + } +} + +impl CoreArrayProvider for UndoEntry { + type Raw = *mut BNUndoEntry; + type Context = (); + type Wrapped<'a> = &'a Self; +} + +unsafe impl CoreArrayProviderInner for UndoEntry { + unsafe fn free(raw: *mut Self::Raw, count: usize, _context: &Self::Context) { + BNFreeUndoEntryList(raw, count); + } + + unsafe fn wrap_raw<'a>(raw: &'a Self::Raw, _context: &'a Self::Context) -> Self::Wrapped<'a> { + Self::ref_from_raw(raw) + } +} + +#[repr(transparent)] +pub struct UndoAction { + handle: ptr::NonNull, +} + +impl UndoAction { + pub(crate) unsafe fn from_raw(handle: ptr::NonNull) -> Self { + Self { handle } + } + + pub(crate) unsafe fn ref_from_raw(handle: &*mut BNUndoAction) -> &Self { + mem::transmute(handle) + } + + #[allow(clippy::mut_from_ref)] + pub(crate) unsafe fn as_raw(&self) -> &mut BNUndoAction { + &mut *self.handle.as_ptr() + } + + pub fn summary_text(&self) -> BnString { + let result = unsafe { BNUndoActionGetSummaryText(self.as_raw()) }; + assert!(!result.is_null()); + unsafe { BnString::from_raw(result) } + } + + pub fn summary(&self) -> Array { + let mut count = 0; + let result = unsafe { BNUndoActionGetSummary(self.as_raw(), &mut count) }; + assert!(!result.is_null()); + unsafe { Array::new(result, count, ()) } + } +} + +impl Clone for UndoAction { + fn clone(&self) -> Self { + unsafe { + Self::from_raw(ptr::NonNull::new(BNNewUndoActionReference(self.as_raw())).unwrap()) + } + } +} + +impl Drop for UndoAction { + fn drop(&mut self) { + unsafe { BNFreeUndoAction(self.as_raw()) } + } +} + +impl CoreArrayProvider for UndoAction { + type Raw = *mut BNUndoAction; + type Context = (); + type Wrapped<'a> = &'a Self; +} + +unsafe impl CoreArrayProviderInner for UndoAction { + unsafe fn free(raw: *mut Self::Raw, count: usize, _context: &Self::Context) { + BNFreeUndoActionList(raw, count); + } + + unsafe fn wrap_raw<'a>(raw: &'a Self::Raw, _context: &'a Self::Context) -> Self::Wrapped<'a> { + Self::ref_from_raw(raw) + } +} + +unsafe extern "C" fn cb_progress bool>( + ctxt: *mut ffi::c_void, + arg1: usize, + arg2: usize, +) -> bool { + let ctxt: &mut F = &mut *(ctxt as *mut F); + ctxt(arg1, arg2) +} + +unsafe extern "C" fn cb_progress_nop(_ctxt: *mut ffi::c_void, _arg1: usize, _arg2: usize) -> bool { + true +} diff --git a/src/databuffer.rs b/src/databuffer.rs index ebe1984..c3d268c 100644 --- a/src/databuffer.rs +++ b/src/databuffer.rs @@ -17,9 +17,10 @@ use binaryninjacore_sys::*; use std::ffi::c_void; -use std::ptr; use std::slice; +use crate::string::BnString; + pub struct DataBuffer(*mut BNDataBuffer); impl DataBuffer { @@ -31,10 +32,6 @@ impl DataBuffer { } pub fn get_data(&self) -> &[u8] { - if self.0.is_null() { - // TODO : Change the default value and remove this - return &[]; - } let buffer = unsafe { BNGetDataBufferContents(self.0) }; if buffer.is_null() { &[] @@ -43,6 +40,65 @@ impl DataBuffer { } } + pub fn get_data_at(&self, offset: usize) -> &[u8] { + let len = self.len(); + if offset > len { + panic!(); + } + let slice_len = len - offset; + let buffer = unsafe { BNGetDataBufferContentsAt(self.0, offset) }; + if buffer.is_null() { + &[] + } else { + unsafe { slice::from_raw_parts(buffer as *const _, slice_len) } + } + } + + /// Create a copy of a especified part of the data + pub fn get_slice(&self, start: usize, len: usize) -> Option { + if start + len > self.len() { + return None; + } + let ptr = unsafe { BNGetDataBufferSlice(self.0, start, len) }; + (!ptr.is_null()).then(|| Self(ptr)) + } + + /// change the size of the allocated data, if new size is bigger data is + /// need to be initialized + pub unsafe fn set_len(&mut self, len: usize) { + unsafe { BNSetDataBufferLength(self.0, len) } + } + + /// set the size to 0 + pub fn clear(&self) { + unsafe { BNClearDataBuffer(self.0) } + } + + /// Copy the contents of `src` into `dst` + pub fn assign(dst: &mut Self, src: &Self) { + unsafe { BNAssignDataBuffer(dst.0, src.0) } + } + + /// Concat the contents of `src` into `dst` + pub fn append(dst: &mut Self, src: &Self) { + unsafe { BNAppendDataBuffer(dst.0, src.0) } + } + + /// concat the contents of `data` into self + pub fn append_data(&self, data: &[u8]) { + unsafe { BNAppendDataBufferContents(self.0, data.as_ptr() as *const c_void, data.len()) } + } + + /// Return the byte at `offset` + pub unsafe fn byte_at(&self, offset: usize) -> u8 { + unsafe { BNGetDataBufferByte(self.0, offset) } + } + + /// Set the value of the byte at `offset` + pub unsafe fn set_byte_at(&mut self, offset: usize, byte: u8) { + unsafe { BNSetDataBufferByte(self.0, offset, byte) } + } + pub fn set_data(&mut self, data: &[u8]) { unsafe { BNSetDataBufferContents( @@ -53,12 +109,48 @@ impl DataBuffer { } } + pub fn to_escaped_string(&self, null_terminates: bool) -> BnString { + unsafe { BnString::from_raw(BNDataBufferToEscapedString(self.0, null_terminates)) } + } + + pub fn from_escaped_string(value: &BnString) -> Self { + Self(unsafe { BNDecodeEscapedString(value.as_raw()) }) + } + + pub fn to_base64(&self) -> BnString { + unsafe { BnString::from_raw(BNDataBufferToBase64(self.0)) } + } + + pub fn from_base64(value: &BnString) -> Self { + Self(unsafe { BNDecodeBase64(value.as_raw()) }) + } + + pub fn zlib_compress(&self) -> Self { + Self(unsafe { BNZlibCompress(self.0) }) + } + + pub fn zlib_decompress(&self) -> Self { + Self(unsafe { BNZlibDecompress(self.0) }) + } + + pub fn lzma_decompress(&self) -> Self { + Self(unsafe { BNLzmaDecompress(self.0) }) + } + + pub fn lzma2_decompress(&self) -> Self { + Self(unsafe { BNLzma2Decompress(self.0) }) + } + + pub fn xz_decompress(&self) -> Self { + Self(unsafe { BNXzDecompress(self.0) }) + } + pub fn len(&self) -> usize { unsafe { BNGetDataBufferLength(self.0) } } pub fn is_empty(&self) -> bool { - unsafe { BNGetDataBufferLength(self.0) == 0 } + self.len() == 0 } pub fn new(data: &[u8]) -> Result { @@ -71,19 +163,16 @@ impl DataBuffer { } } -// TODO : delete this impl Default for DataBuffer { fn default() -> Self { - DataBuffer::from_raw(ptr::null_mut()) + Self(unsafe { BNCreateDataBuffer([].as_ptr() as *const c_void, 0) }) } } impl Drop for DataBuffer { fn drop(&mut self) { - if !self.0.is_null() { - unsafe { - BNFreeDataBuffer(self.0); - } + unsafe { + BNFreeDataBuffer(self.0); } } } @@ -93,3 +182,152 @@ impl Clone for DataBuffer { Self::from_raw(unsafe { BNDuplicateDataBuffer(self.0) }) } } + +impl TryFrom<&[u8]> for DataBuffer { + type Error = (); + + fn try_from(value: &[u8]) -> Result { + DataBuffer::new(value) + } +} + +impl AsRef<[u8]> for DataBuffer { + fn as_ref(&self) -> &[u8] { + self.get_data() + } +} + +impl std::borrow::Borrow<[u8]> for DataBuffer { + fn borrow(&self) -> &[u8] { + self.as_ref() + } +} + +macro_rules! data_buffer_index { + ($range:ty, $output:ty) => { + impl std::ops::Index<$range> for DataBuffer { + type Output = $output; + + fn index(&self, index: $range) -> &Self::Output { + &self.get_data()[index] + } + } + }; +} + +data_buffer_index!(usize, u8); +data_buffer_index!(std::ops::Range, [u8]); +data_buffer_index!(std::ops::RangeInclusive, [u8]); +data_buffer_index!(std::ops::RangeTo, [u8]); +data_buffer_index!(std::ops::RangeFull, [u8]); + +impl PartialEq for DataBuffer { + fn eq(&self, other: &Self) -> bool { + self.as_ref() == other.as_ref() + } +} +impl Eq for DataBuffer {} + +impl PartialOrd for DataBuffer { + fn partial_cmp(&self, other: &Self) -> Option { + Some(self.as_ref().cmp(other.as_ref())) + } +} + +impl Ord for DataBuffer { + fn cmp(&self, other: &Self) -> std::cmp::Ordering { + self.as_ref().cmp(other.as_ref()) + } +} + +#[cfg(test)] +mod test { + use super::DataBuffer; + + const DUMMY_DATA_0: &[u8] = b"0123456789\x00\x01\x02\x03\x04\x05\x06\x07\x08\x09\x09\xFF"; + const DUMMY_DATA_1: &[u8] = b"qwertyuiopasdfghjkl\xE7zxcvbnm\x00\x01\x00"; + + #[test] + fn get_slice() { + let data = DataBuffer::new(DUMMY_DATA_0).unwrap(); + let slice = data.get_slice(9, 10).unwrap(); + assert_eq!(slice.get_data(), &DUMMY_DATA_0[9..19]); + } + + #[test] + fn set_len_write() { + let mut data = DataBuffer::default(); + assert_eq!(data.get_data(), &[]); + unsafe { data.set_len(DUMMY_DATA_0.len()) }; + assert_eq!(data.len(), DUMMY_DATA_0.len()); + let mut contents = DUMMY_DATA_0.to_vec(); + data.set_data(&contents); + // modify the orinal contents, to make sure DataBuffer copied the data + // and is not using the original pointer + contents.as_mut_slice().fill(0x55); + drop(contents); + assert_eq!(data.get_data(), &DUMMY_DATA_0[..]); + + // make sure the new len truncate the original data + unsafe { data.set_len(13) }; + assert_eq!(data.get_data(), &DUMMY_DATA_0[..13]); + + data.clear(); + assert_eq!(data.get_data(), &[]); + } + + #[test] + fn assign_append() { + let mut dst = DataBuffer::new(DUMMY_DATA_0).unwrap(); + let mut src = DataBuffer::new(DUMMY_DATA_1).unwrap(); + DataBuffer::assign(&mut dst, &src); + + assert_eq!(dst.get_data(), DUMMY_DATA_1); + assert_eq!(src.get_data(), DUMMY_DATA_1); + // overwrite the src, to make sure that src is copied to dst, and not + // moved into it + src.set_data(DUMMY_DATA_0); + assert_eq!(dst.get_data(), DUMMY_DATA_1); + assert_eq!(src.get_data(), DUMMY_DATA_0); + + DataBuffer::append(&mut dst, &src); + let result: Vec<_> = DUMMY_DATA_1.iter().chain(DUMMY_DATA_0).copied().collect(); + assert_eq!(dst.get_data(), &result); + + assert_eq!(src.get_data(), DUMMY_DATA_0); + src.set_data(DUMMY_DATA_1); + assert_eq!(src.get_data(), DUMMY_DATA_1); + assert_eq!(dst.get_data(), &result); + } + + #[test] + fn to_from_formats() { + let data = DataBuffer::new(DUMMY_DATA_0).unwrap(); + let escaped = data.to_escaped_string(false); + let unescaped = DataBuffer::from_escaped_string(&escaped); + drop(escaped); + let escaped_part = data.to_escaped_string(true); + let unescaped_part = DataBuffer::from_escaped_string(&escaped_part); + drop(escaped_part); + + let part = &DUMMY_DATA_0[0..DUMMY_DATA_0 + .iter() + .position(|x| *x == 0) + .unwrap_or(DUMMY_DATA_0.len())]; + assert_eq!(data.get_data(), DUMMY_DATA_0); + assert_eq!(unescaped.get_data(), DUMMY_DATA_0); + assert_eq!(unescaped_part.get_data(), part); + + let escaped = data.to_base64(); + let unescaped = DataBuffer::from_base64(&escaped); + drop(escaped); + assert_eq!(data.get_data(), DUMMY_DATA_0); + assert_eq!(unescaped.get_data(), DUMMY_DATA_0); + + let compressed = data.zlib_compress(); + let decompressed = compressed.zlib_decompress(); + drop(compressed); + assert_eq!(data.get_data(), DUMMY_DATA_0); + assert_eq!(decompressed.get_data(), DUMMY_DATA_0); + } +} diff --git a/src/debuginfo.rs b/src/debuginfo.rs index ab4f8f6..2b43c9c 100644 --- a/src/debuginfo.rs +++ b/src/debuginfo.rs @@ -27,7 +27,7 @@ //! And finally calling `binaryninja::debuginfo::DebugInfoParser::register` to register it with the core. //! //! Here's a minimal, complete example boilerplate-plugin: -//! ``` +//! ```no_run //! use binaryninja::{ //! binaryview::BinaryView, //! debuginfo::{CustomDebugInfoParser, DebugInfo, DebugInfoParser}, @@ -40,8 +40,9 @@ //! true //! } //! -//! fn parse_info(&self, _debug_info: &mut DebugInfo, _view: &BinaryView, _debug_file: &BinaryView, _progress: Box bool>) { +//! fn parse_info(&self, _debug_info: &mut DebugInfo, _view: &BinaryView, _debug_file: &BinaryView, _progress: Box Result<(), ()>>) -> bool { //! println!("Parsing info"); +//! true //! } //! } //! @@ -53,11 +54,14 @@ //! ``` //! //! `DebugInfo` will then be automatically applied to binary views that contain debug information (via the setting `analysis.debugInfo.internal`), binary views that provide valid external debug info files (`analysis.debugInfo.external`), or manually fetched/applied as below: -//! ``` -//! let valid_parsers = DebugInfoParser::parsers_for_view(bv); -//! let parser = valid_parsers[0]; -//! let debug_info = parser.parse_debug_info(bv); -//! bv.apply_debug_info(debug_info); +//! ```no_run +//! # use binaryninja::debuginfo::DebugInfoParser; +//! # use binaryninja::binaryview::BinaryViewExt; +//! let bv = binaryninja::load("example").unwrap(); +//! let valid_parsers = DebugInfoParser::parsers_for_view(&bv); +//! let parser = valid_parsers.get(0); +//! let debug_info = parser.parse_debug_info(&bv, &bv, None, None).unwrap(); +//! bv.apply_debug_info(&debug_info); //! ``` //! //! Multiple debug-info parsers can manually contribute debug info for a binary view by simply calling `parse_debug_info` with the @@ -71,10 +75,10 @@ use crate::{ platform::Platform, rc::*, string::{raw_to_string, BnStrCompatible, BnString}, - types::{DataVariableAndName, NameAndType, Type}, + types::{DataVariableAndName, NameAndType, NamedTypedVariable, Type}, }; -use std::{hash::Hash, mem, os::raw::c_void, ptr, slice}; +use std::{hash::Hash, os::raw::c_void, ptr, slice}; struct ProgressContext(Option Result<(), ()>>>); @@ -109,14 +113,14 @@ impl DebugInfoParser { /// List all debug-info parsers pub fn list() -> Array { - let mut count: usize = unsafe { mem::zeroed() }; + let mut count = 0; let raw_parsers = unsafe { BNGetDebugInfoParsers(&mut count as *mut _) }; unsafe { Array::new(raw_parsers, count, ()) } } /// Returns a list of debug-info parsers that are valid for the provided binary view pub fn parsers_for_view(bv: &BinaryView) -> Array { - let mut count: usize = unsafe { mem::zeroed() }; + let mut count = 0; let raw_parsers = unsafe { BNGetDebugInfoParsersForView(bv.handle, &mut count as *mut _) }; unsafe { Array::new(raw_parsers, count, ()) } } @@ -269,12 +273,16 @@ impl ToOwned for DebugInfoParser { impl CoreArrayProvider for DebugInfoParser { type Raw = *mut BNDebugInfoParser; type Context = (); + type Wrapped<'a> = Guard<'a, DebugInfoParser>; } -unsafe impl CoreOwnedArrayProvider for DebugInfoParser { +unsafe impl CoreArrayProviderInner for DebugInfoParser { unsafe fn free(raw: *mut Self::Raw, count: usize, _: &Self::Context) { BNFreeDebugInfoParserList(raw, count); } + unsafe fn wrap_raw<'a>(raw: &'a Self::Raw, context: &'a Self::Context) -> Self::Wrapped<'a> { + Guard::new(Self { handle: *raw }, context) + } } /////////////////////// @@ -293,6 +301,7 @@ pub struct DebugFunctionInfo { address: u64, platform: Option>, components: Vec, + local_variables: Vec, } impl From<&BNDebugFunctionInfo> for DebugFunctionInfo { @@ -302,6 +311,15 @@ impl From<&BNDebugFunctionInfo> for DebugFunctionInfo { .map(|component| raw_to_string(*component as *const _).unwrap()) .collect(); + let local_variables: Vec = unsafe { slice::from_raw_parts(raw.localVariables, raw.localVariableN) } + .iter() + .map(|local_variable| { + unsafe { + NamedTypedVariable::from_raw(local_variable) + } + }) + .collect(); + Self { short_name: raw_to_string(raw.shortName), full_name: raw_to_string(raw.fullName), @@ -318,11 +336,13 @@ impl From<&BNDebugFunctionInfo> for DebugFunctionInfo { Some(unsafe { Platform::ref_from_raw(raw.platform) }) }, components, + local_variables, } } } impl DebugFunctionInfo { + #[allow(clippy::too_many_arguments)] pub fn new( short_name: Option, full_name: Option, @@ -331,18 +351,17 @@ impl DebugFunctionInfo { address: Option, platform: Option>, components: Vec, + local_variables: Vec, ) -> Self { Self { short_name, full_name, raw_name, type_, - address: match address { - Some(address) => address, - _ => 0, - }, + address: address.unwrap_or(0), platform, components, + local_variables, } } } @@ -376,7 +395,7 @@ impl DebugInfo { } /// Returns a generator of all types provided by a named DebugInfoParser - pub fn types_by_name(&self, parser_name: S) -> Vec> { + pub fn types_by_name(&self, parser_name: S) -> Vec> { let parser_name = parser_name.into_bytes_with_nul(); let mut count: usize = 0; @@ -387,10 +406,10 @@ impl DebugInfo { &mut count, ) }; - let result: Vec> = unsafe { + let result: Vec> = unsafe { slice::from_raw_parts_mut(debug_types_ptr, count) .iter() - .map(NameAndType::::from_raw) + .map(|x| NameAndType::from_raw(x).to_owned()) .collect() }; @@ -399,13 +418,13 @@ impl DebugInfo { } /// A generator of all types provided by DebugInfoParsers - pub fn types(&self) -> Vec> { + pub fn types(&self) -> Vec> { let mut count: usize = 0; let debug_types_ptr = unsafe { BNGetDebugTypes(self.handle, ptr::null_mut(), &mut count) }; - let result: Vec> = unsafe { + let result: Vec> = unsafe { slice::from_raw_parts_mut(debug_types_ptr, count) .iter() - .map(NameAndType::::from_raw) + .map(|x| NameAndType::from_raw(x).to_owned()) .collect() }; @@ -416,7 +435,7 @@ impl DebugInfo { /// Returns a generator of all functions provided by a named DebugInfoParser pub fn functions_by_name( &self, - parser_name: S, + parser_name: S ) -> Vec { let parser_name = parser_name.into_bytes_with_nul(); @@ -758,30 +777,41 @@ impl DebugInfo { let short_name_bytes = new_func.short_name.map(|name| name.into_bytes_with_nul()); let short_name = short_name_bytes .as_ref() - .map_or(ptr::null_mut() as *mut _, |name| { - name.as_ptr() as _ - }); + .map_or(ptr::null_mut() as *mut _, |name| name.as_ptr() as _); let full_name_bytes = new_func.full_name.map(|name| name.into_bytes_with_nul()); let full_name = full_name_bytes .as_ref() - .map_or(ptr::null_mut() as *mut _, |name| { - name.as_ptr() as _ - }); + .map_or(ptr::null_mut() as *mut _, |name| name.as_ptr() as _); let raw_name_bytes = new_func.raw_name.map(|name| name.into_bytes_with_nul()); let raw_name = raw_name_bytes .as_ref() - .map_or(ptr::null_mut() as *mut _, |name| { - name.as_ptr() as _ - }); + .map_or(ptr::null_mut() as *mut _, |name| name.as_ptr() as _); - let mut components_array: Vec<*const ::std::os::raw::c_char> = + let mut components_array: Vec<*mut ::std::os::raw::c_char> = Vec::with_capacity(new_func.components.len()); - for component in &new_func.components { - components_array.push(component.as_ptr() as _); - } + + + let mut local_variables_array: Vec = + Vec::with_capacity(new_func.local_variables.len()); unsafe { - BNAddDebugFunction( + for component in &new_func.components { + components_array.push(BNAllocString(component.clone().into_bytes_with_nul().as_ptr() as _)); + } + + for local_variable in &new_func.local_variables { + local_variables_array.push( + BNVariableNameAndType { + var: local_variable.var.raw(), + autoDefined: local_variable.auto_defined, + typeConfidence: local_variable.ty.confidence, + name: BNAllocString(local_variable.name.clone().into_bytes_with_nul().as_ptr() as _), + type_: local_variable.ty.contents.handle, + } + ); + } + + let result = BNAddDebugFunction( self.handle, &mut BNDebugFunctionInfo { shortName: short_name, @@ -798,8 +828,19 @@ impl DebugInfo { }, components: components_array.as_ptr() as _, componentN: new_func.components.len(), + localVariables: local_variables_array.as_ptr() as _, + localVariableN: local_variables_array.len(), }, - ) + ); + + for i in components_array { + BNFreeString(i); + } + + for i in &local_variables_array { + BNFreeString(i.name); + } + result } } diff --git a/src/demangle.rs b/src/demangle.rs index 2efa0ca..1b940ff 100644 --- a/src/demangle.rs +++ b/src/demangle.rs @@ -26,6 +26,49 @@ use crate::rc::*; pub type Result = result::Result; +pub fn demangle_llvm( + mangled_name: S, + simplify: bool, +) -> Result> { + let mangled_name_bwn = mangled_name.into_bytes_with_nul(); + let mangled_name_ptr = mangled_name_bwn.as_ref(); + let mut out_name: *mut *mut std::os::raw::c_char = unsafe { std::mem::zeroed() }; + let mut out_size: usize = 0; + let res = unsafe { + BNDemangleLLVM( + mangled_name_ptr.as_ptr() as *const c_char, + &mut out_name, + &mut out_size, + simplify, + ) + }; + + if !res || out_size == 0 { + let cstr = match CStr::from_bytes_with_nul(mangled_name_ptr) { + Ok(cstr) => cstr, + Err(_) => { + log::error!("demangle_llvm: failed to parse mangled name"); + return Err(()); + } + }; + return Ok(vec![cstr.to_string_lossy().into_owned()]); + } + + if out_name.is_null() { + log::error!("demangle_llvm: out_name is NULL"); + return Err(()); + } + + let names = unsafe { ArrayGuard::::new(out_name, out_size, ()) } + .iter() + .map(str::to_string) + .collect(); + + unsafe { BNFreeDemangledName(&mut out_name, out_size) }; + + Ok(names) +} + pub fn demangle_gnu3( arch: &CoreArchitecture, mangled_name: S, @@ -33,8 +76,8 @@ pub fn demangle_gnu3( ) -> Result<(Option>, Vec)> { let mangled_name_bwn = mangled_name.into_bytes_with_nul(); let mangled_name_ptr = mangled_name_bwn.as_ref(); - let mut out_type: *mut BNType = unsafe { std::mem::zeroed() }; - let mut out_name: *mut *mut std::os::raw::c_char = unsafe { std::mem::zeroed() }; + let mut out_type: *mut BNType = std::ptr::null_mut(); + let mut out_name: *mut *mut std::os::raw::c_char = std::ptr::null_mut(); let mut out_size: usize = 0; let res = unsafe { BNDemangleGNU3( @@ -73,7 +116,7 @@ pub fn demangle_gnu3( let names = unsafe { ArrayGuard::::new(out_name, out_size, ()) } .iter() - .map(|name| name.to_string()) + .map(str::to_string) .collect(); unsafe { BNFreeDemangledName(&mut out_name, out_size) }; @@ -89,8 +132,8 @@ pub fn demangle_ms( let mangled_name_bwn = mangled_name.into_bytes_with_nul(); let mangled_name_ptr = mangled_name_bwn.as_ref(); - let mut out_type: *mut BNType = unsafe { std::mem::zeroed() }; - let mut out_name: *mut *mut std::os::raw::c_char = unsafe { std::mem::zeroed() }; + let mut out_type: *mut BNType = std::ptr::null_mut(); + let mut out_name: *mut *mut std::os::raw::c_char = std::ptr::null_mut(); let mut out_size: usize = 0; let res = unsafe { BNDemangleMS( diff --git a/src/disassembly.rs b/src/disassembly.rs index 1d0dfb8..5d3da31 100644 --- a/src/disassembly.rs +++ b/src/disassembly.rs @@ -16,12 +16,13 @@ use binaryninjacore_sys::*; -use crate::string::{BnStr, BnString}; +use crate::string::BnString; use crate::{BN_FULL_CONFIDENCE, BN_INVALID_EXPR}; use crate::rc::*; use std::convert::From; +use std::ffi::CStr; use std::mem; use std::ptr; @@ -72,7 +73,7 @@ pub type InstructionTextTokenContext = BNInstructionTextTokenContext; // IndirectImportToken = 69, // ExternalSymbolToken = 70, -#[repr(C)] +#[repr(transparent)] pub struct InstructionTextToken(pub(crate) BNInstructionTextToken); #[derive(Copy, Clone, PartialEq, Eq, Hash, Debug)] @@ -98,11 +99,15 @@ pub enum InstructionTextTokenContents { } impl InstructionTextToken { - pub(crate) unsafe fn from_raw(raw: &BNInstructionTextToken) -> Self { - Self(*raw) + pub(crate) unsafe fn from_raw(raw: &BNInstructionTextToken) -> &Self { + mem::transmute(raw) } - pub fn new(text: BnString, contents: InstructionTextTokenContents) -> Self { + pub(crate) fn into_raw(self) -> BNInstructionTextToken { + mem::ManuallyDrop::new(self).0 + } + + pub fn new(text: &str, contents: InstructionTextTokenContents) -> Self { let (value, address) = match contents { InstructionTextTokenContents::Integer(v) => (v, 0), InstructionTextTokenContents::PossibleAddress(v) @@ -149,7 +154,7 @@ impl InstructionTextToken { InstructionTextToken(BNInstructionTextToken { type_, - text: text.into_raw(), + text: BnString::new(text).into_raw(), value, width, size: 0, @@ -159,7 +164,7 @@ impl InstructionTextToken { address, typeNames: ptr::null_mut(), namesCount: 0, - exprIndex: BN_INVALID_EXPR + exprIndex: BN_INVALID_EXPR, }) } @@ -171,8 +176,8 @@ impl InstructionTextToken { self.0.context = context; } - pub fn text(&self) -> &BnStr { - unsafe { BnStr::from_raw(self.0.text) } + pub fn text(&self) -> &str { + unsafe { CStr::from_ptr(self.0.text) }.to_str().unwrap() } pub fn contents(&self) -> InstructionTextTokenContents { @@ -229,7 +234,7 @@ impl Default for InstructionTextToken { address: 0, typeNames: ptr::null_mut(), namesCount: 0, - exprIndex: BN_INVALID_EXPR + exprIndex: BN_INVALID_EXPR, }) } } @@ -248,19 +253,51 @@ impl Clone for InstructionTextToken { confidence: 0xff, typeNames: ptr::null_mut(), namesCount: 0, - exprIndex: self.0.exprIndex + exprIndex: self.0.exprIndex, }) } } -// TODO : There is almost certainly a memory leak here - in the case where -// `impl CoreOwnedArrayProvider for InstructionTextToken` doesn't get triggered -// impl Drop for InstructionTextToken { -// fn drop(&mut self) { -// let _owned = unsafe { BnString::from_raw(self.0.text) }; -// } -// } +impl Drop for InstructionTextToken { + fn drop(&mut self) { + if !self.0.text.is_null() { + let _owned = unsafe { BnString::from_raw(self.0.text) }; + } + if !self.0.typeNames.is_null() && self.0.namesCount != 0 { + unsafe { BNFreeStringList(self.0.typeNames, self.0.namesCount) } + } + } +} +impl CoreArrayProvider for InstructionTextToken { + type Raw = BNInstructionTextToken; + type Context = (); + type Wrapped<'a> = &'a Self; +} +unsafe impl CoreArrayProviderInner for InstructionTextToken { + unsafe fn free(raw: *mut Self::Raw, count: usize, _context: &Self::Context) { + BNFreeInstructionText(raw, count) + } + unsafe fn wrap_raw<'a>(raw: &'a Self::Raw, _context: &'a Self::Context) -> Self::Wrapped<'a> { + core::mem::transmute(raw) + } +} + +impl CoreArrayProvider for Array { + type Raw = BNInstructionTextLine; + type Context = (); + type Wrapped<'a> = mem::ManuallyDrop; +} +unsafe impl CoreArrayProviderInner for Array { + unsafe fn free(raw: *mut Self::Raw, count: usize, _context: &Self::Context) { + BNFreeInstructionTextLines(raw, count) + } + unsafe fn wrap_raw<'a>(raw: &'a Self::Raw, _context: &'a Self::Context) -> Self::Wrapped<'a> { + mem::ManuallyDrop::new(Self::new(raw.tokens, raw.count, ())) + } +} + +#[repr(transparent)] pub struct DisassemblyTextLine(pub(crate) BNDisassemblyTextLine); impl DisassemblyTextLine { @@ -289,7 +326,7 @@ impl DisassemblyTextLine { unsafe { std::slice::from_raw_parts::(self.0.tokens, self.0.count) .iter() - .map(|&x| InstructionTextToken::from_raw(&x)) + .map(|x| InstructionTextToken::from_raw(x).clone()) .collect() } } @@ -306,10 +343,9 @@ impl std::fmt::Display for DisassemblyTextLine { } impl From> for DisassemblyTextLine { - fn from(mut tokens: Vec) -> Self { - tokens.shrink_to_fit(); + fn from(tokens: Vec) -> Self { + let mut tokens: Box<[_]> = tokens.into(); - assert!(tokens.len() == tokens.capacity()); // TODO: let (tokens_pointer, tokens_len, _) = unsafe { tokens.into_raw_parts() }; // Can't use for now...still a rust nightly feature let tokens_pointer = tokens.as_mut_ptr(); let tokens_len = tokens.len(); @@ -344,12 +380,11 @@ impl From> for DisassemblyTextLine { impl From<&Vec<&str>> for DisassemblyTextLine { fn from(string_tokens: &Vec<&str>) -> Self { - let mut tokens: Vec = Vec::with_capacity(string_tokens.len()); - tokens.extend(string_tokens.iter().map(|&token| { - InstructionTextToken::new(BnString::new(token), InstructionTextTokenContents::Text).0 - })); + let mut tokens: Box<[BNInstructionTextToken]> = string_tokens + .iter() + .map(|&token| InstructionTextToken::new(token, InstructionTextTokenContents::Text).into_raw()) + .collect(); - assert!(tokens.len() == tokens.capacity()); // let (tokens_pointer, tokens_len, _) = unsafe { tokens.into_raw_parts() }; // Can't use for now...still a rust nighly feature let tokens_pointer = tokens.as_mut_ptr(); let tokens_len = tokens.len(); @@ -413,12 +448,28 @@ impl Default for DisassemblyTextLine { impl Drop for DisassemblyTextLine { fn drop(&mut self) { - unsafe { - Vec::from_raw_parts(self.0.tokens, self.0.count, self.0.count); + if !self.0.tokens.is_null() { + let ptr = core::ptr::slice_from_raw_parts_mut(self.0.tokens, self.0.count); + let _ = unsafe { Box::from_raw(ptr) }; } } } +impl CoreArrayProvider for DisassemblyTextLine { + type Raw = BNDisassemblyTextLine; + type Context = (); + type Wrapped<'a> = &'a Self; +} + +unsafe impl CoreArrayProviderInner for DisassemblyTextLine { + unsafe fn free(raw: *mut Self::Raw, count: usize, _context: &Self::Context) { + BNFreeDisassemblyTextLines(raw, count) + } + unsafe fn wrap_raw<'a>(raw: &'a Self::Raw, _context: &'a Self::Context) -> Self::Wrapped<'a> { + core::mem::transmute(raw) + } +} + pub type DisassemblyOption = BNDisassemblyOption; #[derive(PartialEq, Eq, Hash)] diff --git a/src/downloadprovider.rs b/src/downloadprovider.rs index 4ba07db..0d38848 100644 --- a/src/downloadprovider.rs +++ b/src/downloadprovider.rs @@ -1,11 +1,9 @@ -use crate::rc::{ - Array, CoreArrayProvider, CoreArrayWrapper, CoreOwnedArrayProvider, Ref, RefCountable, -}; +use crate::rc::{Array, CoreArrayProvider, Guard, CoreArrayProviderInner, Ref, RefCountable}; use crate::settings::Settings; -use crate::string::{BnStr, BnStrCompatible, BnString}; +use crate::string::{BnStrCompatible, BnString}; use binaryninjacore_sys::*; use std::collections::HashMap; -use std::ffi::c_void; +use std::ffi::{c_void, CStr}; use std::os::raw::c_char; use std::ptr::null_mut; use std::slice; @@ -63,19 +61,15 @@ impl DownloadProvider { impl CoreArrayProvider for DownloadProvider { type Raw = *mut BNDownloadProvider; type Context = (); + type Wrapped<'a> = Guard<'a, DownloadProvider>; } -unsafe impl CoreOwnedArrayProvider for DownloadProvider { +unsafe impl CoreArrayProviderInner for DownloadProvider { unsafe fn free(raw: *mut Self::Raw, _count: usize, _context: &Self::Context) { BNFreeDownloadProviderList(raw); } -} - -unsafe impl<'a> CoreArrayWrapper<'a> for DownloadProvider { - type Wrapped = DownloadProvider; - - unsafe fn wrap_raw(raw: &'a Self::Raw, _context: &'a Self::Context) -> Self::Wrapped { - DownloadProvider::from_raw(*raw) + unsafe fn wrap_raw<'a>(raw: &'a Self::Raw, _context: &'a Self::Context) -> Self::Wrapped<'a> { + Guard::new(DownloadProvider::from_raw(*raw), &()) } } @@ -270,8 +264,8 @@ impl DownloadInstance { .zip(response_header_values.iter()) { response_headers.insert( - BnStr::from_raw(*key).to_string(), - BnStr::from_raw(*value).to_string(), + CStr::from_ptr(*key).to_str().unwrap().to_owned(), + CStr::from_ptr(*value).to_str().unwrap().to_owned(), ); } } diff --git a/src/enterprise.rs b/src/enterprise.rs new file mode 100644 index 0000000..232526d --- /dev/null +++ b/src/enterprise.rs @@ -0,0 +1,196 @@ +use std::marker::PhantomData; +use std::time::{Duration, SystemTime, UNIX_EPOCH}; + +use crate::rc::Array; +use crate::string::{BnStrCompatible, BnString}; + +pub fn server_username() -> BnString { + unsafe { BnString::from_raw(binaryninjacore_sys::BNGetEnterpriseServerUsername()) } +} + +pub fn server_url() -> BnString { + unsafe { BnString::from_raw(binaryninjacore_sys::BNGetEnterpriseServerUrl()) } +} + +pub fn set_server_url(url: S) -> Result<(), ()> { + let url = url.into_bytes_with_nul(); + let result = unsafe { + binaryninjacore_sys::BNSetEnterpriseServerUrl(url.as_ref().as_ptr() as *const std::os::raw::c_char) + }; + if result { + Ok(()) + } else { + Err(()) + } +} + +pub fn server_name() -> BnString { + unsafe { BnString::from_raw(binaryninjacore_sys::BNGetEnterpriseServerName()) } +} + +pub fn server_id() -> BnString { + unsafe { BnString::from_raw(binaryninjacore_sys::BNGetEnterpriseServerId()) } +} + +pub fn server_version() -> u64 { + unsafe { binaryninjacore_sys::BNGetEnterpriseServerVersion() } +} + +pub fn server_build_id() -> BnString { + unsafe { BnString::from_raw(binaryninjacore_sys::BNGetEnterpriseServerBuildId()) } +} + +pub fn server_token() -> BnString { + unsafe { BnString::from_raw(binaryninjacore_sys::BNGetEnterpriseServerToken()) } +} + +pub fn license_duration() -> Duration { + Duration::from_secs(unsafe { binaryninjacore_sys::BNGetEnterpriseServerLicenseDuration() }) +} + +pub fn license_expiration_time() -> SystemTime { + let m = Duration::from_secs(unsafe { + binaryninjacore_sys::BNGetEnterpriseServerLicenseExpirationTime() + }); + UNIX_EPOCH + m +} + +pub fn server_reservation_time_limit() -> Duration { + Duration::from_secs(unsafe { binaryninjacore_sys::BNGetEnterpriseServerReservationTimeLimit() }) +} + +pub fn is_server_floating_license() -> bool { + unsafe { binaryninjacore_sys::BNIsEnterpriseServerFloatingLicense() } +} + +pub fn is_server_license_still_activated() -> bool { + unsafe { binaryninjacore_sys::BNIsEnterpriseServerLicenseStillActivated() } +} + +pub fn authenticate_server_with_credentials(username: U, password: P, remember: bool) -> bool +where + U: BnStrCompatible, + P: BnStrCompatible, +{ + let username = username.into_bytes_with_nul(); + let password = password.into_bytes_with_nul(); + unsafe { + binaryninjacore_sys::BNAuthenticateEnterpriseServerWithCredentials( + username.as_ref().as_ptr() as *const std::os::raw::c_char, + password.as_ref().as_ptr() as *const std::os::raw::c_char, + remember, + ) + } +} + +pub fn authenticate_server_with_method(method: S, remember: bool) -> bool { + let method = method.into_bytes_with_nul(); + unsafe { + binaryninjacore_sys::BNAuthenticateEnterpriseServerWithMethod( + method.as_ref().as_ptr() as *const std::os::raw::c_char, + remember, + ) + } +} + +pub fn connect_server() -> bool { + unsafe { binaryninjacore_sys::BNConnectEnterpriseServer() } +} + +pub fn deauthenticate_server() -> bool { + unsafe { binaryninjacore_sys::BNDeauthenticateEnterpriseServer() } +} + +pub fn cancel_server_authentication() { + unsafe { binaryninjacore_sys::BNCancelEnterpriseServerAuthentication() } +} + +pub fn update_server_license(timeout: Duration) -> bool { + unsafe { binaryninjacore_sys::BNUpdateEnterpriseServerLicense(timeout.as_secs()) } +} + +pub fn release_server_license() -> bool { + unsafe { binaryninjacore_sys::BNReleaseEnterpriseServerLicense() } +} + +pub fn is_server_connected() -> bool { + unsafe { binaryninjacore_sys::BNIsEnterpriseServerConnected() } +} + +pub fn is_server_authenticated() -> bool { + unsafe { binaryninjacore_sys::BNIsEnterpriseServerAuthenticated() } +} + +pub fn is_server_initialized() -> bool { + unsafe { binaryninjacore_sys::BNIsEnterpriseServerInitialized() } +} + +pub fn server_last_error() -> BnString { + unsafe { BnString::from_raw(binaryninjacore_sys::BNGetEnterpriseServerLastError()) } +} + +pub fn server_authentication_methods() -> (Array, Array) { + let mut methods = core::ptr::null_mut(); + let mut names = core::ptr::null_mut(); + let count = unsafe { + binaryninjacore_sys::BNGetEnterpriseServerAuthenticationMethods(&mut methods, &mut names) + }; + unsafe { (Array::new(methods, count, ()), Array::new(names, count, ())) } +} + +// NOTE don't implement Clone, Copy, so each callback can only be +// register/unregistered only once +#[repr(transparent)] +#[derive(Debug)] +pub struct EnterpriseServerCallback<'a> { + handle: binaryninjacore_sys::BNEnterpriseServerCallbacks, + lifetime: PhantomData<&'a ()>, +} + +pub fn register_license_changed_callback<'a, F: FnMut(bool) + 'a>( + callback: F, +) -> EnterpriseServerCallback<'a> { + unsafe extern "C" fn cb_license_status_changed( + ctxt: *mut ::std::os::raw::c_void, + still_valid: bool, + ) { + let ctxt: &mut F = &mut *(ctxt as *mut F); + ctxt(still_valid) + } + let mut handle = binaryninjacore_sys::BNEnterpriseServerCallbacks { + context: Box::leak(Box::new(callback)) as *mut F as *mut core::ffi::c_void, + licenseStatusChanged: Some(cb_license_status_changed::), + }; + unsafe { binaryninjacore_sys::BNRegisterEnterpriseServerNotification(&mut handle) } + EnterpriseServerCallback { + handle, + lifetime: PhantomData, + } +} + +pub fn unregister_license_changed_callback(mut callback_handle: EnterpriseServerCallback) { + unsafe { + binaryninjacore_sys::BNUnregisterEnterpriseServerNotification(&mut callback_handle.handle) + } +} + +impl<'a> EnterpriseServerCallback<'a> { + /// register the license changed callback + pub fn register(callback: F) -> Self { + register_license_changed_callback(callback) + } + + /// deregister the license changed callback, equivalent to drop the struct + pub fn deregister(self) { + // Nothing, just drop self + } +} + +impl Drop for EnterpriseServerCallback<'_> { + fn drop(&mut self) { + unregister_license_changed_callback(EnterpriseServerCallback { + handle: self.handle, + lifetime: PhantomData, + }) + } +} diff --git a/src/externallibrary.rs b/src/externallibrary.rs new file mode 100644 index 0000000..a5dcefc --- /dev/null +++ b/src/externallibrary.rs @@ -0,0 +1,209 @@ +use core::{ffi, mem, ptr}; + +use binaryninjacore_sys::*; + +use crate::project::ProjectFile; +use crate::rc::{CoreArrayProvider, CoreArrayProviderInner}; +use crate::string::{BnStrCompatible, BnString}; +use crate::symbol::Symbol; + +/// An ExternalLibrary is an abstraction for a library that is optionally backed +/// by a [ProjectFile]. +#[repr(transparent)] +pub struct ExternalLibrary { + handle: ptr::NonNull, +} + +impl Drop for ExternalLibrary { + fn drop(&mut self) { + unsafe { BNFreeExternalLibrary(self.as_raw()) } + } +} + +impl Clone for ExternalLibrary { + fn clone(&self) -> Self { + unsafe { + Self::from_raw(ptr::NonNull::new(BNNewExternalLibraryReference(self.as_raw())).unwrap()) + } + } +} + +impl ExternalLibrary { + pub(crate) unsafe fn from_raw(handle: ptr::NonNull) -> Self { + Self { handle } + } + + pub(crate) unsafe fn ref_from_raw(handle: &*mut BNExternalLibrary) -> &Self { + assert!(!handle.is_null()); + mem::transmute(handle) + } + + #[allow(clippy::mut_from_ref)] + pub(crate) unsafe fn as_raw(&self) -> &mut BNExternalLibrary { + &mut *self.handle.as_ptr() + } + + /// Get the name of this external library + pub fn name(&self) -> BnString { + let result = unsafe { BNExternalLibraryGetName(self.as_raw()) }; + assert!(!result.is_null()); + unsafe { BnString::from_raw(result) } + } + + /// Get the file backing this external library + pub fn backing_file(&self) -> Option { + let result = unsafe { BNExternalLibraryGetBackingFile(self.as_raw()) }; + let handle = ptr::NonNull::new(result)?; + Some(unsafe { ProjectFile::from_raw(handle) }) + } + + /// Set the file backing this external library + pub fn set_backing_file(&self, file: Option<&ProjectFile>) { + let file_handle = file + .map(|x| unsafe {x.as_raw() as *mut _}) + .unwrap_or(ptr::null_mut()); + unsafe { BNExternalLibrarySetBackingFile(self.as_raw(), file_handle) } + } +} + +impl CoreArrayProvider for ExternalLibrary { + type Raw = *mut BNExternalLibrary; + type Context = (); + type Wrapped<'a> = &'a Self; +} + +unsafe impl CoreArrayProviderInner for ExternalLibrary { + unsafe fn free(raw: *mut Self::Raw, count: usize, _context: &Self::Context) { + BNFreeExternalLibraryList(raw, count) + } + + unsafe fn wrap_raw<'a>(raw: &'a Self::Raw, _context: &'a Self::Context) -> Self::Wrapped<'a> { + Self::ref_from_raw(raw) + } +} + +/// An ExternalLocation is an association from a source symbol in a binary view +/// to a target symbol and/or address in an [ExternalLibrary]. +#[repr(transparent)] +pub struct ExternalLocation { + handle: ptr::NonNull, +} + +impl Drop for ExternalLocation { + fn drop(&mut self) { + unsafe { BNFreeExternalLocation(self.as_raw()) } + } +} + +impl Clone for ExternalLocation { + fn clone(&self) -> Self { + unsafe { + Self::from_raw( + ptr::NonNull::new(BNNewExternalLocationReference(self.as_raw())).unwrap(), + ) + } + } +} + +impl ExternalLocation { + pub(crate) unsafe fn from_raw(handle: ptr::NonNull) -> Self { + Self { handle } + } + + pub(crate) unsafe fn ref_from_raw(handle: &*mut BNExternalLocation) -> &Self { + assert!(!handle.is_null()); + mem::transmute(handle) + } + + #[allow(clippy::mut_from_ref)] + pub(crate) unsafe fn as_raw(&self) -> &mut BNExternalLocation { + &mut *self.handle.as_ptr() + } + + /// Get the source symbol for this ExternalLocation + pub fn source_symbol(&self) -> Symbol { + let result = unsafe { BNExternalLocationGetSourceSymbol(self.as_raw()) }; + assert!(!result.is_null()); + unsafe { Symbol::from_raw(result) } + } + + /// Get the ExternalLibrary that this ExternalLocation targets + pub fn library(&self) -> Option { + let result = unsafe { BNExternalLocationGetExternalLibrary(self.as_raw()) }; + let handle = ptr::NonNull::new(result)?; + Some(unsafe { ExternalLibrary::from_raw(handle) }) + } + + /// Set the ExternalLibrary that this ExternalLocation targets + pub fn set_external_library(&self, lib: Option<&ExternalLibrary>) { + let lib_handle = lib + .map(|x| unsafe {x.as_raw() as *mut _}) + .unwrap_or(ptr::null_mut()); + unsafe { BNExternalLocationSetExternalLibrary(self.as_raw(), lib_handle) } + } + + /// Check if this ExternalLocation has a target address + pub fn has_target_address(&self) -> bool { + unsafe { BNExternalLocationHasTargetAddress(self.as_raw()) } + } + + /// Check if this ExternalLocation has a target symbol + pub fn has_target_symbol(&self) -> bool { + unsafe { BNExternalLocationHasTargetSymbol(self.as_raw()) } + } + + /// Get the address pointed to by this ExternalLocation, if any + pub fn target_address(&self) -> Option { + self.has_target_address() + .then(|| unsafe { BNExternalLocationGetTargetAddress(self.as_raw()) }) + } + + /// Set the address pointed to by this ExternalLocation. + /// ExternalLocations must have a valid target address and/or symbol set. + pub fn set_target_address(&self, mut address: Option) -> bool { + let address_ptr = address + .as_mut() + .map(|x| x as *mut u64) + .unwrap_or(ptr::null_mut()); + unsafe { BNExternalLocationSetTargetAddress(self.as_raw(), address_ptr) } + } + + /// Get the symbol pointed to by this ExternalLocation, if any + pub fn target_symbol(&self) -> Option { + self.has_target_symbol().then(|| unsafe { + let result = BNExternalLocationGetTargetSymbol(self.as_raw()); + assert!(!result.is_null()); + BnString::from_raw(result) + }) + } + + /// Set the symbol pointed to by this ExternalLocation. + /// ExternalLocations must have a valid target address and/or symbol set. + pub fn set_target_symbol(&self, symbol: Option) -> bool { + let symbol = symbol + .map(|x| x.into_bytes_with_nul().as_ref().as_ptr() as *const ffi::c_char) + .unwrap_or(ptr::null_mut()); + unsafe { + BNExternalLocationSetTargetSymbol( + self.as_raw(), + symbol, + ) + } + } +} + +impl CoreArrayProvider for ExternalLocation { + type Raw = *mut BNExternalLocation; + type Context = (); + type Wrapped<'a> = &'a Self; +} + +unsafe impl CoreArrayProviderInner for ExternalLocation { + unsafe fn free(raw: *mut Self::Raw, count: usize, _context: &Self::Context) { + BNFreeExternalLocationList(raw, count) + } + + unsafe fn wrap_raw<'a>(raw: &'a Self::Raw, _context: &'a Self::Context) -> Self::Wrapped<'a> { + Self::ref_from_raw(raw) + } +} diff --git a/src/filemetadata.rs b/src/filemetadata.rs index 6f335c2..7d385a4 100644 --- a/src/filemetadata.rs +++ b/src/filemetadata.rs @@ -27,6 +27,7 @@ use binaryninjacore_sys::{ BNIsAnalysisChanged, BNIsBackedByDatabase, //BNSetFileMetadataNavigationHandler, + BNGetFileMetadataDatabase, BNIsFileModified, BNMarkFileModified, BNMarkFileSaved, @@ -40,8 +41,10 @@ use binaryninjacore_sys::{ BNSetFilename, BNUndo, }; +use binaryninjacore_sys::{BNCreateDatabaseWithProgress, BNOpenExistingDatabaseWithProgress}; use crate::binaryview::BinaryView; +use crate::database::Database; use crate::rc::*; use crate::string::*; @@ -204,16 +207,28 @@ impl FileMetadata { } } - pub fn create_database(&self, filename: S) -> bool { + pub fn create_database( + &self, + filename: S, + progress_func: Option bool>, + ) -> bool { let filename = filename.into_bytes_with_nul(); + let filename_ptr = filename.as_ref().as_ptr() as *mut _; let raw = "Raw".into_bytes_with_nul(); + let raw_ptr = raw.as_ptr() as *mut _; - unsafe { - BNCreateDatabase( - BNGetFileViewOfType(self.handle, raw.as_ptr() as *mut _), - filename.as_ref().as_ptr() as *mut _, - ptr::null_mut() as *mut _, - ) + let handle = unsafe { BNGetFileViewOfType(self.handle, raw_ptr) }; + match progress_func { + None => unsafe { BNCreateDatabase(handle, filename_ptr, ptr::null_mut()) }, + Some(func) => unsafe { + BNCreateDatabaseWithProgress( + handle, + filename_ptr, + func as *mut libc::c_void, + Some(cb_progress_func), + ptr::null_mut(), + ) + }, } } @@ -244,17 +259,25 @@ impl FileMetadata { } } - pub fn open_database(&self, filename: S) -> Result, ()> { + pub fn open_database( + &self, + filename: S, + progress_func: Option bool>, + ) -> Result, ()> { let filename = filename.into_bytes_with_nul(); let filename_ptr = filename.as_ref().as_ptr() as *mut _; - let view = unsafe { BNOpenExistingDatabase(self.handle, filename_ptr) }; - - // TODO : add optional progress function - // let view = match progress_func { - // None => BNOpenExistingDatabase(self.handle, filename_ptr), - // _ => BNOpenExistingDatabaseWithProgress(self.handle, str(filename), None, ctypes.CFUNCTYPE(ctypes.c_bool, ctypes.c_void_p, ctypes.c_ulonglong, ctypes.c_ulonglong)(lambda ctxt, cur, total: progress_func(cur, total))) - // }; + let view = match progress_func { + None => unsafe { BNOpenExistingDatabase(self.handle, filename_ptr) }, + Some(func) => unsafe { + BNOpenExistingDatabaseWithProgress( + self.handle, + filename_ptr, + func as *mut libc::c_void, + Some(cb_progress_func), + ) + }, + }; if view.is_null() { Err(()) @@ -262,6 +285,12 @@ impl FileMetadata { Ok(unsafe { BinaryView::from_raw(view) }) } } + + /// Get the current database + pub fn database(&self) -> Option { + let result = unsafe { BNGetFileMetadataDatabase(self.handle) }; + ptr::NonNull::new(result).map(|handle| unsafe { Database::from_raw(handle) }) + } } impl ToOwned for FileMetadata { @@ -284,7 +313,11 @@ unsafe impl RefCountable for FileMetadata { } } -/* -BNCreateDatabase, -BNCreateDatabaseWithProgress, -*/ +unsafe extern "C" fn cb_progress_func( + ctxt: *mut ::std::os::raw::c_void, + progress: usize, + total: usize, +) -> bool { + let func: fn(usize, usize) -> bool = core::mem::transmute(ctxt); + func(progress, total) +} diff --git a/src/flowgraph.rs b/src/flowgraph.rs index 9d95cc4..c850518 100644 --- a/src/flowgraph.rs +++ b/src/flowgraph.rs @@ -68,7 +68,7 @@ impl<'a> FlowGraphNode<'a> { unsafe { FlowGraphNode::from_raw(BNCreateFlowGraphNode(graph.handle)) } } - pub fn set_disassembly_lines(&self, lines: &'a Vec) { + pub fn set_disassembly_lines(&self, lines: &'a [DisassemblyTextLine]) { unsafe { BNSetFlowGraphNodeLines(self.handle, lines.as_ptr() as *mut _, lines.len()); // BNFreeDisassemblyTextLines(lines.as_ptr() as *mut _, lines.len()); // Shouldn't need...would be a double free? @@ -79,7 +79,7 @@ impl<'a> FlowGraphNode<'a> { let lines = lines .iter() .map(|&line| DisassemblyTextLine::from(&vec![line])) - .collect(); + .collect::>(); self.set_disassembly_lines(&lines); } @@ -114,8 +114,6 @@ impl<'a> ToOwned for FlowGraphNode<'a> { } } -// TODO : FlowGraph are RefCounted objects, this needs to be changed to only return Refs to FlowGraph - #[derive(PartialEq, Eq, Hash)] pub struct FlowGraph { pub(crate) handle: *mut BNFlowGraph, @@ -126,8 +124,8 @@ impl FlowGraph { Self { handle: raw } } - pub fn new() -> Self { - unsafe { FlowGraph::from_raw(BNCreateFlowGraph()) } + pub fn new() -> Ref { + unsafe { Ref::new(FlowGraph::from_raw(BNCreateFlowGraph())) } } pub fn append(&self, node: &FlowGraphNode) -> usize { diff --git a/src/function.rs b/src/function.rs index 273a086..8b38ec5 100644 --- a/src/function.rs +++ b/src/function.rs @@ -14,25 +14,34 @@ use binaryninjacore_sys::*; -use crate::rc::*; -use crate::string::*; -use crate::types::Variable; use crate::{ - architecture::CoreArchitecture, + architecture::{Architecture, CoreArchitecture, CoreRegister, Register}, basicblock::{BasicBlock, BlockContext}, binaryview::{BinaryView, BinaryViewExt}, - hlil, llil, mlil, + callingconvention::CallingConvention, + component::Component, + disassembly::{DisassemblySettings, DisassemblyTextLine}, + flowgraph::FlowGraph, + hlil, llil, + mlil::{self, FunctionGraphType}, platform::Platform, + references::CodeReference, + string::*, symbol::Symbol, - types::{Conf, NamedTypedVariable, Type}, + tags::{Tag, TagReference, TagType}, + types::{ + Conf, ConstantReference, HighlightColor, IndirectBranchInfo, IntegerDisplayType, + MergedVariable, NamedTypedVariable, QualifiedName, RegisterStackAdjustment, RegisterValue, + RegisterValueType, StackVariableReference, Type, UnresolvedIndirectBranches, Variable, + }, }; +use crate::{databuffer::DataBuffer, disassembly::InstructionTextToken, rc::*}; pub use binaryninjacore_sys::BNAnalysisSkipReason as AnalysisSkipReason; pub use binaryninjacore_sys::BNFunctionAnalysisSkipOverride as FunctionAnalysisSkipOverride; pub use binaryninjacore_sys::BNFunctionUpdateType as FunctionUpdateType; - -use std::hash::Hash; use std::{fmt, mem}; +use std::{ffi::c_char, hash::Hash, ops::Range}; pub struct Location { pub arch: Option, @@ -158,6 +167,10 @@ impl Function { unsafe { BNGetFunctionStart(self.handle) } } + pub fn lowest_address(&self) -> u64 { + unsafe { BNGetFunctionLowestAddress(self.handle) } + } + pub fn highest_address(&self) -> u64 { unsafe { BNGetFunctionHighestAddress(self.handle) } } @@ -190,7 +203,7 @@ impl Function { pub fn set_can_return_user>>(&self, can_return: T) { let mut bool_with_confidence = can_return.into().into(); - unsafe { BNSetAutoFunctionCanReturn(self.handle, &mut bool_with_confidence) } + unsafe { BNSetUserFunctionCanReturn(self.handle, &mut bool_with_confidence) } } pub fn comment_at(&self, addr: u64) -> BnString { @@ -205,6 +218,13 @@ impl Function { } } + /// All comments in the function + pub fn comments(&self) -> Array { + let mut count = 0; + let lines = unsafe { BNGetCommentedAddresses(self.handle, &mut count) }; + unsafe { Array::new(lines, count, self.to_owned()) } + } + pub fn basic_blocks(&self) -> Array> { unsafe { let mut count = 0; @@ -215,11 +235,23 @@ impl Function { } } + /// Returns the BasicBlock that contains the given address `addr`. + /// + /// * `addr` - Address of the BasicBlock to retrieve. + /// * `arch` - Architecture of the basic block if different from the Function's self.arch + /// + /// # Example + /// ```no_run + /// # use binaryninja::function::Function; + /// # let fun: Function = todo!(); + /// let blocks = fun.basic_block_containing(0x1000, None); + /// ``` pub fn basic_block_containing( &self, - arch: &CoreArchitecture, addr: u64, + arch: Option, ) -> Option>> { + let arch = arch.unwrap_or_else(|| self.arch()); unsafe { let block = BNGetFunctionBasicBlockAtAddress(self.handle, arch.0, addr); let context = NativeBlock { _priv: () }; @@ -232,6 +264,18 @@ impl Function { } } + pub fn block_annotations( + &self, + addr: u64, + arch: Option, + ) -> Array> { + let arch = arch.unwrap_or_else(|| self.arch()); + let mut count = 0; + let lines = unsafe { BNGetFunctionBlockAnnotations(self.handle, arch.0, addr, &mut count) }; + assert!(!lines.is_null()); + unsafe { Array::new(lines, count, ()) } + } + pub fn get_variable_name(&self, var: &Variable) -> BnString { unsafe { let raw_var = var.raw(); @@ -252,6 +296,30 @@ impl Function { } } + pub fn high_level_il_if_available(&self) -> Option> { + let hlil = unsafe { BNGetFunctionHighLevelILIfAvailable(self.handle) }; + (!hlil.is_null()).then(|| unsafe { hlil::HighLevelILFunction::ref_from_raw(hlil, true) }) + } + + /// MediumLevelILFunction used to represent Function mapped medium level IL + pub fn mapped_medium_level_il(&self) -> Result, ()> { + let mlil = unsafe { BNGetFunctionMappedMediumLevelIL(self.handle) }; + if mlil.is_null() { + return Err(()); + } + Ok(unsafe { mlil::MediumLevelILFunction::ref_from_raw(mlil) }) + } + + pub fn mapped_medium_level_il_if_available( + &self, + ) -> Result, ()> { + let mlil = unsafe { BNGetFunctionMappedMediumLevelILIfAvailable(self.handle) }; + if mlil.is_null() { + return Err(()); + } + Ok(unsafe { mlil::MediumLevelILFunction::ref_from_raw(mlil) }) + } + pub fn medium_level_il(&self) -> Result, ()> { unsafe { let mlil = BNGetFunctionMediumLevelIL(self.handle); @@ -264,6 +332,11 @@ impl Function { } } + pub fn medium_level_il_if_available(&self) -> Option> { + let mlil = unsafe { BNGetFunctionMediumLevelILIfAvailable(self.handle) }; + (!mlil.is_null()).then(|| unsafe { mlil::MediumLevelILFunction::ref_from_raw(mlil) }) + } + pub fn low_level_il(&self) -> Result>, ()> { unsafe { let llil = BNGetFunctionLowLevelIL(self.handle); @@ -276,6 +349,13 @@ impl Function { } } + pub fn low_level_il_if_available( + &self, + ) -> Option>> { + let llil = unsafe { BNGetFunctionLowLevelILIfAvailable(self.handle) }; + (!llil.is_null()).then(|| unsafe { llil::RegularFunction::from_raw(self.arch(), llil) }) + } + pub fn lifted_il(&self) -> Result>, ()> { unsafe { let llil = BNGetFunctionLiftedIL(self.handle); @@ -288,6 +368,11 @@ impl Function { } } + pub fn lifted_il_if_available(&self) -> Option>> { + let llil = unsafe { BNGetFunctionLiftedILIfAvailable(self.handle) }; + (!llil.is_null()).then(|| unsafe { llil::LiftedFunction::from_raw(self.arch(), llil) }) + } + pub fn return_type(&self) -> Conf> { let result = unsafe { BNGetFunctionReturnType(self.handle) }; @@ -297,14 +382,52 @@ impl Function { ) } + pub fn set_auto_return_type<'a, C>(&self, return_type: C) + where + C: Into>, + { + let return_type: Conf<&Type> = return_type.into(); + unsafe { + BNSetAutoFunctionReturnType( + self.handle, + &mut BNTypeWithConfidence { + type_: return_type.contents.handle, + confidence: return_type.confidence, + }, + ) + } + } + + pub fn set_user_return_type<'a, C>(&self, return_type: C) + where + C: Into>, + { + let return_type: Conf<&Type> = return_type.into(); + unsafe { + BNSetUserFunctionReturnType( + self.handle, + &mut BNTypeWithConfidence { + type_: return_type.contents.handle, + confidence: return_type.confidence, + }, + ) + } + } + pub fn function_type(&self) -> Ref { unsafe { Type::ref_from_raw(BNGetFunctionType(self.handle)) } } - pub fn set_user_type(&self, t: Type) { - unsafe { - BNSetFunctionUserType(self.handle, t.handle); - } + pub fn has_user_type(&self) -> bool { + unsafe { BNFunctionHasUserType(self.handle) } + } + + pub fn set_user_type(&self, t: &Type) { + unsafe { BNSetFunctionUserType(self.handle, t.handle) } + } + + pub fn set_auto_type(&self, t: &Type) { + unsafe { BNSetFunctionAutoType(self.handle, t.handle) } } pub fn stack_layout(&self) -> Array { @@ -315,6 +438,393 @@ impl Function { } } + /// Gets number of bytes removed from the stack after return + pub fn stack_adjustment(&self) -> Conf { + unsafe { BNGetFunctionStackAdjustment(self.handle) }.into() + } + + /// Sets number of bytes removed from the stack after return + pub fn set_user_stack_adjustment(&self, value: C) + where + C: Into>, + { + let value: Conf = value.into(); + let mut value_raw = value.into(); + unsafe { BNSetUserFunctionStackAdjustment(self.handle, &mut value_raw) } + } + + /// Sets number of bytes removed from the stack after return + pub fn set_auto_stack_adjustment(&self, value: C) + where + C: Into>, + { + let value: Conf = value.into(); + let mut value_raw = value.into(); + unsafe { BNSetAutoFunctionStackAdjustment(self.handle, &mut value_raw) } + } + + pub fn call_stack_adjustment(&self, addr: u64, arch: Option) -> Conf { + let arch = arch.unwrap_or_else(|| self.arch()); + let result = unsafe { BNGetCallStackAdjustment(self.handle, arch.0, addr) }; + result.into() + } + + pub fn set_user_call_stack_adjustment( + &self, + addr: u64, + adjust: I, + arch: Option, + ) where + I: Into>, + { + let arch = arch.unwrap_or_else(|| self.arch()); + let adjust: Conf = adjust.into(); + unsafe { + BNSetUserCallStackAdjustment( + self.handle, + arch.0, + addr, + adjust.contents, + adjust.confidence, + ) + } + } + + pub fn set_auto_call_stack_adjustment( + &self, + addr: u64, + adjust: I, + arch: Option, + ) where + I: Into>, + { + let arch = arch.unwrap_or_else(|| self.arch()); + let adjust: Conf = adjust.into(); + unsafe { + BNSetAutoCallStackAdjustment( + self.handle, + arch.0, + addr, + adjust.contents, + adjust.confidence, + ) + } + } + + pub fn call_type_adjustment( + &self, + addr: u64, + arch: Option, + ) -> Option>> { + let arch = arch.unwrap_or_else(|| self.arch()); + let result = unsafe { BNGetCallTypeAdjustment(self.handle, arch.0, addr) }; + (!result.type_.is_null()) + .then(|| unsafe { Conf::new(Type::ref_from_raw(result.type_), result.confidence) }) + } + + /// Sets or removes the call type override at a call site to the given type. + /// + /// * `addr` - virtual address of the call instruction to adjust + /// * `adjust_type` - (optional) overridden call type, or `None` to remove an existing adjustment + /// * `arch` - (optional) Architecture of the instruction if different from self.arch + pub fn set_user_call_type_adjustment<'a, I>( + &self, + addr: u64, + adjust_type: Option, + arch: Option, + ) where + I: Into>, + { + let arch = arch.unwrap_or_else(|| self.arch()); + let mut adjust_type = adjust_type.map(|adjust_type| { + let adjust_type = adjust_type.into(); + BNTypeWithConfidence { + type_: adjust_type.contents.handle, + confidence: adjust_type.confidence, + } + }); + let adjust_ptr = adjust_type + .as_mut() + .map(|x| x as *mut _) + .unwrap_or(core::ptr::null_mut()); + unsafe { BNSetUserCallTypeAdjustment(self.handle, arch.0, addr, adjust_ptr) } + } + + pub fn set_auto_call_type_adjustment<'a, I>( + &self, + addr: u64, + adjust_type: I, + arch: Option, + ) where + I: Into>, + { + let arch = arch.unwrap_or_else(|| self.arch()); + let adjust_type: Conf<&Type> = adjust_type.into(); + unsafe { + BNSetAutoCallTypeAdjustment( + self.handle, + arch.0, + addr, + &mut BNTypeWithConfidence { + type_: adjust_type.contents.handle, + confidence: adjust_type.confidence, + }, + ) + } + } + + pub fn call_reg_stack_adjustment( + &self, + addr: u64, + arch: Option, + ) -> Array> { + let arch = arch.unwrap_or_else(|| self.arch()); + let mut count = 0; + let adjust = + unsafe { BNGetCallRegisterStackAdjustment(self.handle, arch.0, addr, &mut count) }; + assert!(!adjust.is_null()); + unsafe { Array::new(adjust, count, arch.handle()) } + } + + pub fn set_user_call_reg_stack_adjustment( + self, + addr: u64, + adjust: I, + arch: Option, + ) where + I: IntoIterator>, + { + let arch = arch.unwrap_or_else(|| self.arch()); + let mut adjust_buf: Box<[BNRegisterStackAdjustment]> = + adjust.into_iter().map(|adjust| adjust.into_raw()).collect(); + unsafe { + BNSetUserCallRegisterStackAdjustment( + self.handle, + arch.0, + addr, + adjust_buf.as_mut_ptr(), + adjust_buf.len(), + ) + } + } + + pub fn set_auto_call_reg_stack_adjustment( + &self, + addr: u64, + adjust: I, + arch: Option, + ) where + I: IntoIterator>, + { + let arch = arch.unwrap_or_else(|| self.arch()); + let mut adjust_buf: Box<[BNRegisterStackAdjustment]> = + adjust.into_iter().map(|reg| reg.into_raw()).collect(); + + unsafe { + BNSetAutoCallRegisterStackAdjustment( + self.handle, + arch.0, + addr, + adjust_buf.as_mut_ptr(), + adjust_buf.len(), + ) + } + } + + pub fn call_reg_stack_adjustment_for_reg_stack( + &self, + addr: u64, + reg_stack_id: u32, + arch: Option, + ) -> RegisterStackAdjustment { + let arch = arch.unwrap_or_else(|| self.arch()); + let adjust = unsafe { + BNGetCallRegisterStackAdjustmentForRegisterStack( + self.handle, + arch.0, + addr, + reg_stack_id, + ) + }; + unsafe { RegisterStackAdjustment::from_raw(adjust, arch) } + } + + pub fn set_user_call_reg_stack_adjustment_for_reg_stack( + &self, + addr: u64, + reg_stack_id: u32, + adjust: I, + arch: Option, + ) where + I: Into>, + { + let arch = arch.unwrap_or_else(|| self.arch()); + let adjust: Conf = adjust.into(); + unsafe { + BNSetUserCallRegisterStackAdjustmentForRegisterStack( + self.handle, + arch.0, + addr, + reg_stack_id, + adjust.contents, + adjust.confidence, + ) + } + } + + pub fn set_auto_call_reg_stack_adjustment_for_reg_stack( + &self, + addr: u64, + reg_stack_id: u32, + adjust: I, + arch: Option, + ) where + I: Into>, + { + let arch = arch.unwrap_or_else(|| self.arch()); + let adjust: Conf = adjust.into(); + unsafe { + BNSetAutoCallRegisterStackAdjustmentForRegisterStack( + self.handle, + arch.0, + addr, + reg_stack_id, + adjust.contents, + adjust.confidence, + ) + } + } + + pub fn reg_stack_adjustments(&self) -> Array> { + let mut count = 0; + let adjust = unsafe { BNGetFunctionRegisterStackAdjustments(self.handle, &mut count) }; + assert!(!adjust.is_null()); + unsafe { Array::new(adjust, count, self.arch().handle()) } + } + + pub fn set_user_reg_stack_adjustments(&self, values: I) + where + I: IntoIterator>, + A: Architecture, + { + let mut values: Box<[BNRegisterStackAdjustment]> = + values.into_iter().map(|r| r.into_raw()).collect(); + unsafe { + BNSetUserFunctionRegisterStackAdjustments( + self.handle, + values.as_mut_ptr(), + values.len(), + ) + } + } + + pub fn set_auto_reg_stack_adjustments(&self, values: I) + where + I: IntoIterator>, + A: Architecture, + { + let mut values: Box<[BNRegisterStackAdjustment]> = + values.into_iter().map(|r| r.into_raw()).collect(); + unsafe { + BNSetAutoFunctionRegisterStackAdjustments( + self.handle, + values.as_mut_ptr(), + values.len(), + ) + } + } + + /// List of function variables: including name, variable and type + pub fn variables(&self) -> Array<(&str, Variable, &Type)> { + let mut count = 0; + let vars = unsafe { BNGetFunctionVariables(self.handle, &mut count) }; + assert!(!vars.is_null()); + unsafe { Array::new(vars, count, ()) } + } + + pub fn split_variables(&self) -> Array { + let mut count = 0; + let vars = unsafe { BNGetSplitVariables(self.handle, &mut count) }; + assert!(!vars.is_null()); + unsafe { Array::new(vars, count, ()) } + } + + pub fn parameter_variables(&self) -> Conf> { + unsafe { + let mut variables = BNGetFunctionParameterVariables(self.handle); + let mut result = Vec::with_capacity(variables.count); + let confidence = variables.confidence; + let vars = std::slice::from_raw_parts(variables.vars, variables.count); + + for var in vars.iter().take(variables.count) { + result.push(Variable::from_raw(*var)); + } + + BNFreeParameterVariables(&mut variables); + Conf::new(result, confidence) + } + } + + pub fn set_user_parameter_variables(&self, values: I, confidence: u8) + where + I: IntoIterator, + { + let mut vars: Box<[BNVariable]> = values.into_iter().map(|var| var.raw()).collect(); + unsafe { + BNSetUserFunctionParameterVariables( + self.handle, + &mut BNParameterVariablesWithConfidence { + vars: vars.as_mut_ptr(), + count: vars.len(), + confidence, + }, + ) + } + } + + pub fn set_auto_parameter_variables(&self, values: I, confidence: u8) + where + I: IntoIterator, + { + let mut vars: Box<[BNVariable]> = values.into_iter().map(|var| var.raw()).collect(); + unsafe { + BNSetAutoFunctionParameterVariables( + self.handle, + &mut BNParameterVariablesWithConfidence { + vars: vars.as_mut_ptr(), + count: vars.len(), + confidence, + }, + ) + } + } + + pub fn parameter_at( + &self, + addr: u64, + func_type: Option<&Type>, + i: usize, + arch: Option, + ) -> RegisterValue { + let arch = arch.unwrap_or_else(|| self.arch()); + let func_type = func_type.map(|f| f.handle).unwrap_or(core::ptr::null_mut()); + let value = + unsafe { BNGetParameterValueAtInstruction(self.handle, arch.0, addr, func_type, i) }; + value.into() + } + + pub fn parameter_at_low_level_il_instruction( + &self, + instr: usize, + func_type: &Type, + i: usize, + ) -> RegisterValue { + let value = unsafe { + BNGetParameterValueAtLowLevelILInstruction(self.handle, instr, func_type.handle, i) + }; + value.into() + } + pub fn apply_imported_types(&self, sym: &Symbol, t: Option<&Type>) { unsafe { BNApplyImportedTypes( @@ -329,6 +839,12 @@ impl Function { } } + pub fn apply_auto_discovered_type(&self, func_type: &Type) { + unsafe { BNApplyAutoDiscoveredFunctionType(self.handle, func_type.handle) } + } + + /// Whether automatic analysis was skipped for this function. + /// Can be set to false to re-enable analysis. pub fn analysis_skipped(&self) -> bool { unsafe { BNIsFunctionAnalysisSkipped(self.handle) } } @@ -362,6 +878,1274 @@ impl Function { pub fn set_analysis_skip_override(&self, override_: FunctionAnalysisSkipOverride) { unsafe { BNSetFunctionAnalysisSkipOverride(self.handle, override_) } } + + ///Whether the function's IL should be inlined into all callers' IL + pub fn inline_during_analysis(&self) -> Conf { + let result = unsafe { BNIsFunctionInlinedDuringAnalysis(self.handle) }; + result.into() + } + + pub fn set_auto_inline_during_analysis(&self, value: C) + where + C: Into>, + { + let value: Conf = value.into(); + unsafe { + BNSetAutoFunctionInlinedDuringAnalysis( + self.handle, + BNBoolWithConfidence { + value: value.contents, + confidence: value.confidence, + }, + ) + } + } + + pub fn set_user_inline_during_analysis(&self, value: C) + where + C: Into>, + { + let value: Conf = value.into(); + unsafe { + BNSetUserFunctionInlinedDuringAnalysis( + self.handle, + BNBoolWithConfidence { + value: value.contents, + confidence: value.confidence, + }, + ) + } + } + + pub fn analysis_performance_info(&self) -> Array { + let mut count = 0; + let info = unsafe { BNGetFunctionAnalysisPerformanceInfo(self.handle, &mut count) }; + assert!(!info.is_null()); + unsafe { Array::new(info, count, ()) } + } + + /// Creates and adds a [Tag] object on either a function, or on + /// an address inside of a function. + /// + /// "Function tags" appear at the top of a function and are a good way to label an + /// entire function with some information. If you include an address when you call + /// Function.add_tag, you'll create an "address tag". These are good for labeling + /// specific instructions. + /// + /// For tagging arbitrary data, consider [BinaryViewExt::add_tag]. + /// + /// * `tag_type_name` - The name of the tag type for this Tag. + /// * `data` - Additional data for the Tag. + /// * `addr` - Address at which to add the tag. + /// * `user` - Whether or not a user tag. + /// + /// # Example + /// + /// ```no_run + /// # use binaryninja::binaryview::{BinaryView, BinaryViewExt}; + /// # use binaryninja::function::Function; + /// # let fun: Function = todo!(); + /// # let bv: BinaryView = todo!(); + /// let important = bv.create_tag_type("Important", "⚠️"); + /// fun.add_tag(&important, "I think this is the main function", None, false, None); + /// let crash = bv.create_tag_type("Crashes", "🎯"); + /// fun.add_tag(&crash, "Nullpointer dereference", Some(0x1337), false, None); + /// ``` + pub fn add_tag( + &self, + tag_type: &TagType, + data: S, + addr: Option, + user: bool, + arch: Option, + ) { + let arch = arch.unwrap_or_else(|| self.arch()); + + // Create tag + let tag = Tag::new(tag_type, data); + let binaryview = unsafe { BinaryView::from_raw(BNGetFunctionData(self.handle)) }; + unsafe { BNAddTag(binaryview.handle, tag.handle, user) }; + + unsafe { + match (user, addr) { + (false, None) => BNAddAutoFunctionTag(self.handle, tag.handle), + (false, Some(addr)) => BNAddAutoAddressTag(self.handle, arch.0, addr, tag.handle), + (true, None) => BNAddUserFunctionTag(self.handle, tag.handle), + (true, Some(addr)) => BNAddUserAddressTag(self.handle, arch.0, addr, tag.handle), + } + } + } + + /// Remove [Tag] object on either a function, or on an address inside of a function. + /// + /// * `tag` - The tag to remove. + /// * `addr` - (optional) Address at which to remove the tag. + /// * `user` - Whether or not a user tag. + pub fn remove_tag( + &self, + tag: &Tag, + addr: Option, + user: bool, + arch: Option, + ) { + let arch = arch.unwrap_or_else(|| self.arch()); + unsafe { + match (user, addr) { + (false, None) => BNRemoveAutoFunctionTag(self.handle, tag.handle), + (false, Some(addr)) => { + BNRemoveAutoAddressTag(self.handle, arch.0, addr, tag.handle) + } + (true, None) => BNRemoveUserFunctionTag(self.handle, tag.handle), + (true, Some(addr)) => BNRemoveUserAddressTag(self.handle, arch.0, addr, tag.handle), + } + } + } + + /// Remove [Tag] object of type on either a function, or on an address + /// inside of a function. + /// + /// * `tag_type` - The type of the to remove. + /// * `addr` - Address at which to add the tag. + /// * `user` - Whether or not a user tag. + pub fn remove_tags_of_type( + &self, + tag_type: &TagType, + addr: Option, + user: bool, + arch: Option, + ) { + let arch = arch.unwrap_or_else(|| self.arch()); + unsafe { + match (user, addr) { + (false, None) => BNRemoveAutoFunctionTagsOfType(self.handle, tag_type.handle), + (false, Some(addr)) => { + BNRemoveAutoAddressTagsOfType(self.handle, arch.0, addr, tag_type.handle) + } + (true, None) => BNRemoveUserFunctionTagsOfType(self.handle, tag_type.handle), + (true, Some(addr)) => { + BNRemoveUserAddressTagsOfType(self.handle, arch.0, addr, tag_type.handle) + } + } + } + } + + /// Places a user-defined cross-reference from the instruction at + /// the given address and architecture to the specified target address. If the specified + /// source instruction is not contained within this function, no action is performed. + /// To remove the reference, use [Function::remove_user_code_ref]. + /// + /// * `from_addr` - Virtual address of the source instruction. + /// * `to_addr` - Virtual address of the xref's destination. + /// * `arch` - Architecture of the source instruction. + /// + /// # Example + /// + /// ```no_run + /// # use binaryninja::function::Function; + /// # let fun: Function = todo!(); + /// fun.add_user_code_ref(0x1337, 0x400000, None); + /// ``` + pub fn add_user_code_ref(&self, from_addr: u64, to_addr: u64, arch: Option) { + let arch = arch.unwrap_or_else(|| self.arch()); + unsafe { BNAddUserCodeReference(self.handle, arch.0, from_addr, to_addr) } + } + + /// Removes a user-defined cross-reference. + /// If the given address is not contained within this function, or if there is no + /// such user-defined cross-reference, no action is performed. + /// + /// * `from_addr` - virtual address of the source instruction + /// * `to_addr` - virtual address of the xref's destination. + /// * `arch` - architecture of the source instruction + /// + /// #Example + /// + /// ```no_run + /// # use binaryninja::function::Function; + /// # let fun: Function = todo!(); + /// fun.remove_user_code_ref(0x1337, 0x400000, None); + /// ``` + pub fn remove_user_code_ref( + self, + from_addr: u64, + to_addr: u64, + arch: Option, + ) { + let arch = arch.unwrap_or_else(|| self.arch()); + unsafe { BNRemoveUserCodeReference(self.handle, arch.0, from_addr, to_addr) } + } + + /// Places a user-defined type cross-reference from the instruction at + /// the given address and architecture to the specified type. If the specified + /// source instruction is not contained within this function, no action is performed. + /// To remove the reference, use [Function::remove_user_type_ref]. + /// + /// * `from_addr` - Virtual address of the source instruction. + /// * `name` - Name of the referenced type. + /// * `arch` - Architecture of the source instruction. + /// + /// # Example + /// ```no_run + /// # use binaryninja::function::Function; + /// # let fun: Function = todo!(); + /// fun.add_user_type_ref(0x1337, &"A".into(), None); + /// ``` + pub fn add_user_type_ref( + &self, + from_addr: u64, + name: &QualifiedName, + arch: Option, + ) { + let arch = arch.unwrap_or_else(|| self.arch()); + let name_ptr = &name.0 as *const BNQualifiedName as *mut _; + unsafe { BNAddUserTypeReference(self.handle, arch.0, from_addr, name_ptr) } + } + + /// Removes a user-defined type cross-reference. + /// If the given address is not contained within this function, or if there is no + /// such user-defined cross-reference, no action is performed. + /// + /// * `from_addr` - Virtual address of the source instruction. + /// * `name` - Name of the referenced type. + /// * `from_arch` - Architecture of the source instruction. + /// + /// # Example + /// ```no_run + /// # use binaryninja::function::Function; + /// # let fun: Function = todo!(); + /// fun.remove_user_type_ref(0x1337, &"A".into(), None); + /// ``` + pub fn remove_user_type_ref( + &self, + from_addr: u64, + name: &QualifiedName, + arch: Option, + ) { + let arch = arch.unwrap_or_else(|| self.arch()); + let name_ptr = &name.0 as *const BNQualifiedName as *mut _; + unsafe { BNRemoveUserTypeReference(self.handle, arch.0, from_addr, name_ptr) } + } + + /// Places a user-defined type field cross-reference from the + /// instruction at the given address and architecture to the specified type. If the specified + /// source instruction is not contained within this function, no action is performed. + /// To remove the reference, use [Function::remove_user_type_field_ref]. + /// + /// * `from_addr` - Virtual address of the source instruction. + /// * `name` - Name of the referenced type. + /// * `offset` - Offset of the field, relative to the type. + /// * `arch` - Architecture of the source instruction. + /// * `size` - The size of the access. + /// + /// # Example + /// ```no_run + /// # use binaryninja::function::Function; + /// # let fun: Function = todo!(); + /// fun.add_user_type_field_ref(0x1337, &"A".into(), 0x8, None, None); + /// ``` + pub fn add_user_type_field_ref( + &self, + from_addr: u64, + name: &QualifiedName, + offset: u64, + arch: Option, + size: Option, + ) { + let size = size.unwrap_or(0); + let arch = arch.unwrap_or_else(|| self.arch()); + let name_ptr = &name.0 as *const _ as *mut _; + unsafe { + BNAddUserTypeFieldReference(self.handle, arch.0, from_addr, name_ptr, offset, size) + } + } + + /// Removes a user-defined type field cross-reference. + /// If the given address is not contained within this function, or if there is no + /// such user-defined cross-reference, no action is performed. + /// + /// * `from_addr` - Virtual address of the source instruction + /// * `name` - Name of the referenced type + /// * `offset` - Offset of the field, relative to the type + /// * `arch` - Architecture of the source instruction + /// * `size` - The size of the access + /// + /// # Example + /// ```no_run + /// # use binaryninja::function::Function; + /// # let fun: Function = todo!(); + /// fun.remove_user_type_field_ref(0x1337, &"A".into(), 0x8, None, None); + /// ``` + pub fn remove_user_type_field_ref( + &self, + from_addr: u64, + name: &QualifiedName, + offset: u64, + arch: Option, + size: Option, + ) { + let size = size.unwrap_or(0); + let arch = arch.unwrap_or_else(|| self.arch()); + let name_ptr = &name.0 as *const _ as *mut _; + unsafe { + BNRemoveUserTypeFieldReference(self.handle, arch.0, from_addr, name_ptr, offset, size) + } + } + + pub fn constant_data( + &self, + state: RegisterValueType, + value: u64, + size: Option, + ) -> DataBuffer { + let size = size.unwrap_or(0); + let state_raw = state.into_raw_value(); + DataBuffer::from_raw(unsafe { BNGetConstantData(self.handle, state_raw, value, size) }) + } + + pub fn constants_referenced_by( + &self, + addr: u64, + arch: Option, + ) -> Array { + let arch = arch.unwrap_or_else(|| self.arch()); + let mut count = 0; + let refs = + unsafe { BNGetConstantsReferencedByInstruction(self.handle, arch.0, addr, &mut count) }; + assert!(!refs.is_null()); + unsafe { Array::new(refs, count, ()) } + } + + pub fn constants_referenced_by_address_if_available( + &self, + addr: u64, + arch: Option, + ) -> Array { + let arch = arch.unwrap_or_else(|| self.arch()); + let mut count = 0; + let refs = unsafe { + BNGetConstantsReferencedByInstructionIfAvailable(self.handle, arch.0, addr, &mut count) + }; + assert!(!refs.is_null()); + unsafe { Array::new(refs, count, ()) } + } + + /// Returns a list of function Tags for the function. + /// + /// `auto` - If `None`, gets all tags, if `true`, gets auto tags, if `false`, gets user tags + /// `tag_type` - If `None`, gets all tags, otherwise only gets tags of the given type + pub fn function_tags(&self, auto: Option, tag_type: Option<&str>) -> Array { + let mut count = 0; + + let tag_type = tag_type.map(|tag_type| self.view().get_tag_type(tag_type)); + + let tags = unsafe { + match (tag_type, auto) { + // received a tag_type, BinaryView found none + (Some(None), _) => return Array::new(core::ptr::null_mut(), 0, ()), + + // with tag_type + (Some(Some(tag_type)), None) => { + BNGetFunctionTagsOfType(self.handle, tag_type.handle, &mut count) + } + (Some(Some(tag_type)), Some(true)) => { + BNGetAutoFunctionTagsOfType(self.handle, tag_type.handle, &mut count) + } + (Some(Some(tag_type)), Some(false)) => { + BNGetUserFunctionTagsOfType(self.handle, tag_type.handle, &mut count) + } + // without tag_type + (None, None) => BNGetFunctionTags(self.handle, &mut count), + (None, Some(true)) => BNGetAutoFunctionTags(self.handle, &mut count), + (None, Some(false)) => BNGetUserFunctionTags(self.handle, &mut count), + } + }; + assert!(!tags.is_null()); + + unsafe { Array::new(tags, count, ()) } + } + + pub fn tags(&self) -> Array { + let mut count = 0; + let tags = unsafe { BNGetAddressTagReferences(self.handle, &mut count) }; + unsafe { Array::new(tags, count, ()) } + } + + /// Gets a list of Tags at the address. + /// + /// * `addr` - Address to get tags from. + /// * `auto` - If `None`, gets all tags, if `true`, gets auto tags, if `false`, gets user tags + pub fn tags_at( + &self, + addr: u64, + auto: Option, + arch: Option, + ) -> Array { + let arch = arch.unwrap_or_else(|| self.arch()); + let mut count = 0; + + let tags = match auto { + None => unsafe { BNGetAddressTags(self.handle, arch.0, addr, &mut count) }, + Some(true) => unsafe { BNGetAutoAddressTags(self.handle, arch.0, addr, &mut count) }, + Some(false) => unsafe { BNGetUserAddressTags(self.handle, arch.0, addr, &mut count) }, + }; + assert!(!tags.is_null()); + unsafe { Array::new(tags, count, ()) } + } + + /// Gets a list of Tags in the address range. + /// + /// * `addr` - Address to get tags from. + /// * `auto` - If `None`, gets all tags, if `true`, gets auto tags, if `false`, gets user tags + pub fn tags_in_range( + &self, + range: Range, + auto: Option, + arch: Option, + ) -> Array { + let arch = arch.unwrap_or_else(|| self.arch()); + let mut count = 0; + + let tags = match auto { + None => unsafe { + BNGetAddressTagsInRange(self.handle, arch.0, range.start, range.end, &mut count) + }, + Some(true) => unsafe { + BNGetAutoAddressTagsInRange(self.handle, arch.0, range.start, range.end, &mut count) + }, + Some(false) => unsafe { + BNGetUserAddressTagsInRange(self.handle, arch.0, range.start, range.end, &mut count) + }, + }; + assert!(!tags.is_null()); + unsafe { Array::new(tags, count, ()) } + } + + /// List of indirect branches + pub fn indirect_branches(&self) -> Array { + let mut count = 0; + let branches = unsafe { BNGetIndirectBranches(self.handle, &mut count) }; + assert!(!branches.is_null()); + unsafe { Array::new(branches, count, ()) } + } + + pub fn set_user_indirect_branches( + &self, + source: u64, + branches: I, + arch: Option, + ) where + I: IntoIterator, + { + let arch = arch.unwrap_or_else(|| self.arch()); + let mut branches: Box<[BNArchitectureAndAddress]> = branches + .into_iter() + .map(|address| BNArchitectureAndAddress { + address, + arch: arch.0, + }) + .collect(); + unsafe { + BNSetUserIndirectBranches( + self.handle, + arch.0, + source, + branches.as_mut_ptr(), + branches.len(), + ) + } + } + + pub fn set_auto_indirect_branches( + &self, + source: u64, + branches: I, + arch: Option, + ) where + I: IntoIterator, + { + let arch = arch.unwrap_or_else(|| self.arch()); + let mut branches: Box<[BNArchitectureAndAddress]> = branches + .into_iter() + .map(|address| BNArchitectureAndAddress { + address, + arch: arch.0, + }) + .collect(); + unsafe { + BNSetAutoIndirectBranches( + self.handle, + arch.0, + source, + branches.as_mut_ptr(), + branches.len(), + ) + } + } + + /// List of indirect branches at this address + pub fn indirect_branches_at( + &self, + addr: u64, + arch: Option, + ) -> Array { + let arch = arch.unwrap_or_else(|| self.arch()); + let mut count = 0; + let branches = unsafe { BNGetIndirectBranchesAt(self.handle, arch.0, addr, &mut count) }; + assert!(!branches.is_null()); + unsafe { Array::new(branches, count, ()) } + } + + /// # Example + /// ```no_run + /// # let fun: binaryninja::function::Function = todo!(); + /// let color = fun.instr_highlight(0x1337, None); + /// ``` + pub fn instr_highlight(&self, addr: u64, arch: Option) -> HighlightColor { + let arch = arch.unwrap_or_else(|| self.arch()); + let color = unsafe { BNGetInstructionHighlight(self.handle, arch.0, addr) }; + HighlightColor::from_raw(color) + } + + /// Sets the highlights the instruction at the specified address with the supplied color + /// + ///
Use only in analysis plugins. Do not use in regular plugins, as colors won't be saved to the database.
+ /// + /// * `addr` - virtual address of the instruction to be highlighted + /// * `color` - Color value to use for highlighting + /// * `arch` - (optional) Architecture of the instruction if different from self.arch + pub fn set_auto_instr_highlight( + &self, + addr: u64, + color: HighlightColor, + arch: Option, + ) { + let arch = arch.unwrap_or_else(|| self.arch()); + let color_raw = color.into_raw(); + unsafe { BNSetAutoInstructionHighlight(self.handle, arch.0, addr, color_raw) } + } + + /// Sets the highlights the instruction at the specified address with the supplied color + /// + /// * `addr` - virtual address of the instruction to be highlighted + /// * `color` - Color value to use for highlighting + /// * `arch` - (optional) Architecture of the instruction if different from self.arch + /// + /// # Example + /// ```no_run + /// # use binaryninja::types::HighlightColor; + /// # let fun: binaryninja::function::Function = todo!(); + /// let color = HighlightColor::NoHighlightColor { alpha: u8::MAX }; + /// fun.set_user_instr_highlight(0x1337, color, None); + /// ``` + pub fn set_user_instr_highlight( + &self, + addr: u64, + color: HighlightColor, + arch: Option, + ) { + let arch = arch.unwrap_or_else(|| self.arch()); + let color_raw = color.into_raw(); + unsafe { BNSetUserInstructionHighlight(self.handle, arch.0, addr, color_raw) } + } + + /// return the address, if any, of the instruction that contains the + /// provided address + pub fn instruction_containing_address( + &self, + addr: u64, + arch: Option, + ) -> Option { + let arch = arch.unwrap_or_else(|| self.arch()); + let mut start = 0; + unsafe { BNGetInstructionContainingAddress(self.handle, arch.0, addr, &mut start) } + .then_some(start) + } + + /// Get the current text display type for an integer token in the disassembly or IL views + /// + /// See also see [Function::int_display_type_and_typeid] + /// + /// * `instr_addr` - Address of the instruction or IL line containing the token + /// * `value` - field of the InstructionTextToken object for the token, usually the constant displayed + /// * `operand` - Operand index of the token, defined as the number of OperandSeparatorTokens in the disassembly line before the token + /// * `arch` - (optional) Architecture of the instruction or IL line containing the token + pub fn int_display_type( + &self, + instr_addr: u64, + value: u64, + operand: usize, + arch: Option, + ) -> IntegerDisplayType { + let arch = arch.unwrap_or_else(|| self.arch()); + unsafe { BNGetIntegerConstantDisplayType(self.handle, arch.0, instr_addr, value, operand) } + } + + /// Change the text display type for an integer token in the disassembly or IL views + /// + /// * `instr_addr` - Address of the instruction or IL line containing the token + /// * `value` - Field of the InstructionTextToken object for the token, usually the constant displayed + /// * `operand` - Operand index of the token, defined as the number of OperandSeparatorTokens in the disassembly line before the token + /// * `display_type` - Desired display type + /// * `arch` - (optional) Architecture of the instruction or IL line containing the token + /// * `enum_display_typeid` - (optional) Whenever passing EnumDisplayType to `display_type`, passing a type ID here will specify the Enumeration display type. Must be a valid type ID and resolve to an enumeration type. + pub fn set_int_display_type( + &self, + instr_addr: u64, + value: u64, + operand: usize, + display_type: IntegerDisplayType, + arch: Option, + enum_display_typeid: Option, + ) { + let arch = arch.unwrap_or_else(|| self.arch()); + let enum_display_typeid = enum_display_typeid.map(BnStrCompatible::into_bytes_with_nul); + let enum_display_typeid_ptr = enum_display_typeid + .map(|x| x.as_ref().as_ptr() as *const c_char) + .unwrap_or(core::ptr::null()); + unsafe { + BNSetIntegerConstantDisplayType( + self.handle, + arch.0, + instr_addr, + value, + operand, + display_type, + enum_display_typeid_ptr, + ) + } + } + + /// Get the current text display enum type for an integer token in the disassembly or IL views. + /// + /// See also see [Function::int_display_type_and_typeid] + /// + /// * `instr_addr` - Address of the instruction or IL line containing the token + /// * `value` - field of the InstructionTextToken object for the token, usually the constant displayed + /// * `operand` - Operand index of the token, defined as the number of OperandSeparatorTokens in the disassembly line before the token + /// * `arch` - (optional) Architecture of the instruction or IL line containing the token + pub fn int_enum_display_typeid( + &self, + instr_addr: u64, + value: u64, + operand: usize, + arch: Option, + ) -> BnString { + let arch = arch.unwrap_or_else(|| self.arch()); + unsafe { + BnString::from_raw(BNGetIntegerConstantDisplayTypeEnumerationType( + self.handle, + arch.0, + instr_addr, + value, + operand, + )) + } + } + + /// Get the current text display type for an integer token in the disassembly or IL views + /// + /// * `instr_addr` - Address of the instruction or IL line containing the token + /// * `value` - field of the InstructionTextToken object for the token, usually the constant displayed + /// * `operand` - Operand index of the token, defined as the number of OperandSeparatorTokens in the disassembly line before the token + /// * `arch` - (optional) Architecture of the instruction or IL line containing the token + pub fn int_display_type_and_typeid( + &self, + instr_addr: u64, + value: u64, + operand: usize, + arch: Option, + ) -> (IntegerDisplayType, BnString) { + let arch = arch.unwrap_or_else(|| self.arch()); + let name = self.int_enum_display_typeid(instr_addr, value, operand, Some(arch)); + let display = self.int_display_type(instr_addr, value, operand, Some(arch)); + (display, name) + } + + /// Get the value the provided string register address corresponding to the given virtual address + /// + /// * `addr` - virtual address of the instruction to query + /// * `reg` - string value of native register to query + /// * `arch` - (optional) Architecture for the given function + /// + /// # Example + /// ```no_run + /// # use binaryninja::architecture::{ArchitectureExt, Register}; + /// # let fun: binaryninja::function::Function = todo!(); + /// let reg = fun.arch().register_by_name("rdi").unwrap(); + /// let value = fun.register_value_at(0x400dbe, reg.id(), None); + /// ``` + pub fn register_value_at( + &self, + addr: u64, + reg: u32, + arch: Option, + ) -> RegisterValue { + let arch = arch.unwrap_or_else(|| self.arch()); + let register = unsafe { BNGetRegisterValueAtInstruction(self.handle, arch.0, addr, reg) }; + register.into() + } + + /// Gets the value instruction address corresponding to the given virtual address + /// + /// * `addr` - virtual address of the instruction to query + /// * `reg` - string value of native register to query + /// * `arch` - (optional) Architecture for the given function + /// + /// # Example + /// ```no_run + /// # use binaryninja::architecture::{ArchitectureExt, Register}; + /// # let fun: binaryninja::function::Function = todo!(); + /// let reg = fun.arch().register_by_name("rdi").unwrap(); + /// let value = fun.register_value_after(0x400dbe, reg.id(), None); + /// ``` + pub fn register_value_after( + &self, + addr: u64, + reg: u32, + arch: Option, + ) -> RegisterValue { + let arch = arch.unwrap_or_else(|| self.arch()); + let register = + unsafe { BNGetRegisterValueAfterInstruction(self.handle, arch.0, addr, reg) }; + register.into() + } + + pub fn register_value_at_exit(&self, reg: u32) -> Conf { + let register = unsafe { BNGetFunctionRegisterValueAtExit(self.handle, reg) }; + Conf::new(register.value.into(), register.confidence) + } + + pub fn registers_read_by( + &self, + addr: u64, + arch: Option, + ) -> Array { + let arch = arch.unwrap_or_else(|| self.arch()); + let mut count = 0; + let regs = + unsafe { BNGetRegistersReadByInstruction(self.handle, arch.0, addr, &mut count) }; + assert!(!regs.is_null()); + unsafe { Array::new(regs, count, arch) } + } + + pub fn registers_written_by( + &self, + addr: u64, + arch: Option, + ) -> Array { + let arch = arch.unwrap_or_else(|| self.arch()); + let mut count = 0; + let regs = + unsafe { BNGetRegistersWrittenByInstruction(self.handle, arch.0, addr, &mut count) }; + assert!(!regs.is_null()); + unsafe { Array::new(regs, count, arch) } + } + + /// Registers that are modified by this function + pub fn clobbered_registers(&self) -> Conf> { + let result = unsafe { BNGetFunctionClobberedRegisters(self.handle) }; + + let reg_set = unsafe { Array::new(result.regs, result.count, self.arch().handle()) }; + Conf::new(reg_set, result.confidence) + } + + pub fn set_user_clobbered_registers(&self, registers: I, confidence: u8) + where + I: IntoIterator, + { + let mut regs: Box<[u32]> = registers.into_iter().map(|reg| reg.id()).collect(); + let mut regs = BNRegisterSetWithConfidence { + regs: regs.as_mut_ptr(), + count: regs.len(), + confidence, + }; + unsafe { BNSetUserFunctionClobberedRegisters(self.handle, &mut regs) } + } + + pub fn set_auto_clobbered_registers(&self, registers: I, confidence: u8) + where + I: IntoIterator, + { + let mut regs: Box<[u32]> = registers.into_iter().map(|reg| reg.id()).collect(); + let mut regs = BNRegisterSetWithConfidence { + regs: regs.as_mut_ptr(), + count: regs.len(), + confidence, + }; + unsafe { BNSetAutoFunctionClobberedRegisters(self.handle, &mut regs) } + } + + pub fn stack_contents_at( + &self, + addr: u64, + offset: i64, + size: usize, + arch: Option, + ) -> RegisterValue { + let arch = arch.unwrap_or_else(|| self.arch()); + let value = + unsafe { BNGetStackContentsAtInstruction(self.handle, arch.0, addr, offset, size) }; + value.into() + } + + pub fn stack_contents_after( + &self, + addr: u64, + offset: i64, + size: usize, + arch: Option, + ) -> RegisterValue { + let arch = arch.unwrap_or_else(|| self.arch()); + let value = + unsafe { BNGetStackContentsAfterInstruction(self.handle, arch.0, addr, offset, size) }; + value.into() + } + + pub fn stack_var_at_frame_offset( + &self, + addr: u64, + offset: i64, + arch: Option, + ) -> Option<(Variable, BnString, Conf>)> { + let arch = arch.unwrap_or_else(|| self.arch()); + let mut found_value: BNVariableNameAndType = unsafe { mem::zeroed() }; + let found = unsafe { + BNGetStackVariableAtFrameOffset(self.handle, arch.0, addr, offset, &mut found_value) + }; + if !found { + return None; + } + let var = unsafe { Variable::from_raw(found_value.var) }; + let name = unsafe { BnString::from_raw(found_value.name) }; + let var_type = Conf::new( + unsafe { Type::ref_from_raw(found_value.type_) }, + found_value.typeConfidence, + ); + Some((var, name, var_type)) + } + + pub fn stack_variables_referenced_by( + &self, + addr: u64, + arch: Option, + ) -> Array { + let arch = arch.unwrap_or_else(|| self.arch()); + let mut count = 0; + let refs = unsafe { + BNGetStackVariablesReferencedByInstruction(self.handle, arch.0, addr, &mut count) + }; + assert!(!refs.is_null()); + unsafe { Array::new(refs, count, ()) } + } + + pub fn stack_variables_referenced_by_address_if_available( + &self, + addr: u64, + arch: Option, + ) -> Array { + let arch = arch.unwrap_or_else(|| self.arch()); + let mut count = 0; + let refs = unsafe { + BNGetStackVariablesReferencedByInstructionIfAvailable( + self.handle, + arch.0, + addr, + &mut count, + ) + }; + assert!(!refs.is_null()); + unsafe { Array::new(refs, count, ()) } + } + + /// Discovered value of the global pointer register, if the function uses one + pub fn global_pointer_value(&self) -> Conf { + let result = unsafe { BNGetFunctionGlobalPointerValue(self.handle) }; + Conf::new(result.value.into(), result.confidence) + } + + pub fn type_tokens( + &self, + settings: Option<&DisassemblySettings>, + ) -> Array { + let settings = settings.map(|s| s.handle).unwrap_or(core::ptr::null_mut()); + let mut count = 0; + let lines = unsafe { BNGetFunctionTypeTokens(self.handle, settings, &mut count) }; + assert!(!lines.is_null()); + unsafe { Array::new(lines, count, ()) } + } + + pub fn is_call_instruction(&self, addr: u64, arch: Option) -> bool { + let arch = arch.unwrap_or_else(|| self.arch()); + unsafe { BNIsCallInstruction(self.handle, arch.0, addr) } + } + + pub fn is_variable_user_defined(&self, var: &Variable) -> bool { + unsafe { BNIsVariableUserDefined(self.handle, &var.raw()) } + } + + pub fn is_pure(&self) -> Conf { + unsafe { BNIsFunctionPure(self.handle) }.into() + } + + pub fn set_user_pure(&self, value: C) + where + C: Into>, + { + let value: Conf = value.into(); + let mut value_raw = value.into(); + unsafe { BNSetUserFunctionPure(self.handle, &mut value_raw) }; + } + + pub fn set_auto_pure(&self, value: C) + where + C: Into>, + { + let value: Conf = value.into(); + let mut value_raw = value.into(); + unsafe { BNSetAutoFunctionPure(self.handle, &mut value_raw) }; + } + + pub fn is_too_large(&self) -> bool { + unsafe { BNIsFunctionTooLarge(self.handle) } + } + + pub fn is_update_needed(&self) -> bool { + unsafe { BNIsFunctionUpdateNeeded(self.handle) } + } + + /// Indicates that this function needs to be reanalyzed during the next update cycle + /// + /// * `update_type` - Desired update type + pub fn mark_updates_required(&self, update_type: FunctionUpdateType) { + unsafe { BNMarkUpdatesRequired(self.handle, update_type) } + } + + /// Indicates that callers of this function need to be reanalyzed during the next update cycle + /// + /// * `uppdate_type` - Desired update type + pub fn mark_caller_updates_required(&self, update_type: FunctionUpdateType) { + unsafe { BNMarkCallerUpdatesRequired(self.handle, update_type) } + } + + pub fn mark_recent_use(&self) { + unsafe { BNMarkFunctionAsRecentlyUsed(self.handle) } + } + + // Gets the list of merged variables + pub fn merged_variables(&self) -> Array { + let mut count = 0; + let vars = unsafe { BNGetMergedVariables(self.handle, &mut count) }; + assert!(!vars.is_null()); + unsafe { Array::new(vars, count, ()) } + } + + /// Merge one or more varibles in `sources` into the `target` variable. All + /// variable accesses to the variables in `sources` will be rewritten to use `target`. + /// + /// * `target` - target variable + /// * `sources` - list of source variables + pub fn merge_variables<'a>( + &self, + target: &Variable, + sources: impl IntoIterator, + ) { + let sources_raw: Box<[BNVariable]> = sources.into_iter().map(|s| s.raw()).collect(); + unsafe { + BNMergeVariables( + self.handle, + &target.raw(), + sources_raw.as_ptr(), + sources_raw.len(), + ) + } + } + + /// Undoes variable merging performed with [Function::merge_variables]. The variables in + /// `sources` will no longer be merged into the `target` variable. + /// + /// * `target` - target variable + /// * `sources` - list of source variables + pub fn unmerge_variables<'a>( + &self, + target: &Variable, + sources: impl IntoIterator, + ) { + let sources_raw: Box<[BNVariable]> = sources.into_iter().map(|s| s.raw()).collect(); + unsafe { + BNUnmergeVariables( + self.handle, + &target.raw(), + sources_raw.as_ptr(), + sources_raw.len(), + ) + } + } + + /// Splits a varible at the definition site. The given `var` must be the + /// variable unique to the definition and should be obtained by using + /// [mlil::MediumLevelILInstruction::get_split_var_for_definition] at the definition site. + /// + /// This function is not meant to split variables that have been previously merged. Use + /// [Function::unmerge_variables] to split previously merged variables. + /// + ///
+ /// + /// Binary Ninja automatically splits all variables that the analysis determines + /// to be safely splittable. Splitting a variable manually with [Function::split_variable] can cause + /// IL and decompilation to be incorrect. There are some patterns where variables can be safely + /// split semantically but analysis cannot determine that it is safe. This function is provided + /// to allow variable splitting to be performed in these cases by plugins or by the user. + /// + ///
+ /// + /// * `var` - variable to split + pub fn split_variable(&self, var: &Variable) { + unsafe { BNSplitVariable(self.handle, &var.raw()) } + } + + /// Undoes varible splitting performed with [Function::split_variable]. The given `var` + /// must be the variable unique to the definition and should be obtained by using + /// [mlil::MediumLevelILInstruction::get_split_var_for_definition] at the definition site. + /// + /// * `var` - variable to unsplit + pub fn unsplit_variable(&self, var: &Variable) { + unsafe { BNUnsplitVariable(self.handle, &var.raw()) } + } + + /// Causes this function to be reanalyzed. This function does not wait for the analysis to finish. + /// + /// * `update_type` - Desired update type + /// + ///
+ /// + /// If analysis_skipped is `true`, using this API will not trigger + /// re-analysis. Instead, use [Function::set_analysis_skipped] with `false`. + /// + ///
+ pub fn reanalyze(&self, update_type: FunctionUpdateType) { + unsafe { BNReanalyzeFunction(self.handle, update_type) } + } + + /// Generate internal debug reports for a variety of analysis. + /// Current list of possible values include: + /// + /// - mlil_translator + /// - stack_adjust_graph + /// - high_level_il + /// + /// * `name` - Name of the debug report + pub fn request_debug_report(&self, name: &str) { + const DEBUG_REPORT_ALIAS: &[(&str, &str)] = &[ + ("stack", "stack_adjust_graph\x00"), + ("mlil", "mlil_translator\x00"), + ("hlil", "high_level_il\x00"), + ]; + + if let Some(alias_idx) = DEBUG_REPORT_ALIAS + .iter() + .position(|(alias, _value)| *alias == name) + { + let name = DEBUG_REPORT_ALIAS[alias_idx].1.as_ptr() as *const c_char; + unsafe { BNRequestFunctionDebugReport(self.handle, name) } + } else { + let name = std::ffi::CString::new(name.to_string()).unwrap(); + unsafe { BNRequestFunctionDebugReport(self.handle, name.as_ptr()) } + } + + self.view().update_analysis() + } + + /// Whether function was automatically discovered s a result of some creation of a 'user' function. + /// 'user' functions may or may not have been created by a user through the or API. For instance the entry point + /// into a function is always created a 'user' function. 'user' functions should be considered the root of auto + /// analysis. + pub fn auto(&self) -> bool { + unsafe { BNWasFunctionAutomaticallyDiscovered(self.handle) } + } + + /// Returns a list of possible call sites contained in this function. + /// This includes ordinary calls, tail calls, and indirect jumps. Not all of + /// the returned call sites are necessarily true call sites; some may simply + /// be unresolved indirect jumps, for example. + pub fn call_sites(&self) -> Array { + let mut count = 0; + let refs = unsafe { BNGetFunctionCallSites(self.handle, &mut count) }; + assert!(!refs.is_null()); + unsafe { Array::new(refs, count, ()) } + } + + /// Returns a list of ReferenceSource objects corresponding to the addresses + /// in functions which reference this function + pub fn caller_sites(&self) -> Array { + self.view().get_code_refs(self.start()) + } + + /// Calling convention used by the function + pub fn calling_convention(&self) -> Option>>> { + let result = unsafe { BNGetFunctionCallingConvention(self.handle) }; + (!result.convention.is_null()).then(|| { + Conf::new( + unsafe { CallingConvention::ref_from_raw(result.convention, self.arch()) }, + result.confidence, + ) + }) + } + + /// Set the User calling convention used by the function + pub fn set_user_calling_convention<'a, I>(&self, value: Option) + where + I: Into>>, + { + let mut conv_conf: BNCallingConventionWithConfidence = unsafe { mem::zeroed() }; + if let Some(value) = value { + let value = value.into(); + conv_conf.convention = value.contents.handle; + conv_conf.confidence = value.confidence; + } + unsafe { BNSetUserFunctionCallingConvention(self.handle, &mut conv_conf) } + } + + /// Set the calling convention used by the function + pub fn set_auto_calling_convention<'a, I>(&self, value: Option) + where + I: Into>>, + { + let mut conv_conf: BNCallingConventionWithConfidence = unsafe { mem::zeroed() }; + if let Some(value) = value { + let value = value.into(); + conv_conf.convention = value.contents.handle; + conv_conf.confidence = value.confidence; + } + unsafe { BNSetAutoFunctionCallingConvention(self.handle, &mut conv_conf) } + } + + pub fn can_return(&self) -> Conf { + unsafe { BNCanFunctionReturn(self.handle) }.into() + } + + pub fn set_user_can_return(&self, value: I) + where + I: Into>, + { + let value: Conf = value.into(); + let mut value_raw: BNBoolWithConfidence = value.into(); + unsafe { BNSetUserFunctionCanReturn(self.handle, &mut value_raw) } + } + + pub fn set_auto_can_return(&self, value: I) + where + I: Into>, + { + let value: Conf = value.into(); + let mut value_raw: BNBoolWithConfidence = value.into(); + unsafe { BNSetAutoFunctionCanReturn(self.handle, &mut value_raw) } + } + + /// Whether function has explicitly defined types + pub fn has_explicitly_defined_type(&self) -> bool { + unsafe { BNFunctionHasExplicitlyDefinedType(self.handle) } + } + + pub fn has_user_annotations(&self) -> bool { + unsafe { BNFunctionHasUserAnnotations(self.handle) } + } + + pub fn has_variable_arguments(&self) -> Conf { + unsafe { BNFunctionHasVariableArguments(self.handle) }.into() + } + + pub fn set_user_has_variable_arguments(&self, value: I) + where + I: Into>, + { + let bc: Conf = value.into(); + let mut bc = bc.into(); + unsafe { BNSetUserFunctionHasVariableArguments(self.handle, &mut bc) } + } + + pub fn set_auto_has_variable_arguments(&self, value: I) + where + I: Into>, + { + let bc: Conf = value.into(); + let mut bc = bc.into(); + unsafe { BNSetAutoFunctionHasVariableArguments(self.handle, &mut bc) } + } + + /// Has unresolved indirect branches + pub fn has_unresolved_indirect_branches(&self) -> bool { + unsafe { BNHasUnresolvedIndirectBranches(self.handle) } + } + + /// List of address of unresolved indirect branches + pub fn unresolved_indirect_branches(&self) -> Array { + let mut count = 0; + let result = unsafe { BNGetUnresolvedIndirectBranches(self.handle, &mut count) }; + unsafe { Array::new(result, count, ()) } + } + + /// Returns a string representing the provenance. This portion of the API + /// is under development. Currently the provenance information is + /// undocumented, not persistent, and not saved to a database. + pub fn provenance(&self) -> BnString { + unsafe { BnString::from_raw(BNGetProvenanceString(self.handle)) } + } + + /// Get registers that are used for the return value + pub fn return_registers(&self) -> Conf> { + let result = unsafe { BNGetFunctionReturnRegisters(self.handle) }; + let regs = unsafe { Array::new(result.regs, result.count, self.arch().handle()) }; + Conf::new(regs, result.confidence) + } + + pub fn set_user_return_registers(&self, values: I, confidence: u8) + where + I: IntoIterator, + { + let mut regs: Box<[u32]> = values.into_iter().map(|reg| reg.id()).collect(); + let mut regs = BNRegisterSetWithConfidence { + regs: regs.as_mut_ptr(), + count: regs.len(), + confidence, + }; + unsafe { BNSetUserFunctionReturnRegisters(self.handle, &mut regs) } + } + + pub fn set_auto_return_registers(&self, values: I, confidence: u8) + where + I: IntoIterator, + { + let mut regs: Box<[u32]> = values.into_iter().map(|reg| reg.id()).collect(); + let mut regs = BNRegisterSetWithConfidence { + regs: regs.as_mut_ptr(), + count: regs.len(), + confidence, + }; + unsafe { BNSetAutoFunctionReturnRegisters(self.handle, &mut regs) } + } + + /// Flow graph of unresolved stack adjustments + pub fn unresolved_stack_adjustment_graph(&self) -> Option> { + let graph = unsafe { BNGetUnresolvedStackAdjustmentGraph(self.handle) }; + (!graph.is_null()).then(|| unsafe { Ref::new(FlowGraph::from_raw(graph)) }) + } + + pub fn create_graph( + &self, + graph_type: FunctionGraphType, + settings: Option, + ) -> Ref { + let settings_raw = settings.map(|s| s.handle).unwrap_or(core::ptr::null_mut()); + let result = unsafe { BNCreateFunctionGraph(self.handle, graph_type, settings_raw) }; + unsafe { Ref::new(FlowGraph::from_raw(result)) } + } + + pub fn parent_components(&self) -> Array { + let mut count = 0; + let result = unsafe{ BNGetFunctionParentComponents(self.view().handle, self.handle, &mut count) }; + assert!(!result.is_null()); + unsafe{ Array::new(result, count, ()) } + } } impl fmt::Debug for Function { @@ -399,18 +2183,14 @@ unsafe impl RefCountable for Function { impl CoreArrayProvider for Function { type Raw = *mut BNFunction; type Context = (); + type Wrapped<'a> = Guard<'a, Function>; } -unsafe impl CoreOwnedArrayProvider for Function { +unsafe impl CoreArrayProviderInner for Function { unsafe fn free(raw: *mut *mut BNFunction, count: usize, _context: &()) { BNFreeFunctionList(raw, count); } -} - -unsafe impl<'a> CoreArrayWrapper<'a> for Function { - type Wrapped = Guard<'a, Function>; - - unsafe fn wrap_raw(raw: &'a *mut BNFunction, context: &'a ()) -> Guard<'a, Function> { + unsafe fn wrap_raw<'a>(raw: &'a *mut BNFunction, context: &'a ()) -> Self::Wrapped<'a> { Guard::new(Function { handle: *raw }, context) } } @@ -454,17 +2234,80 @@ impl AddressRange { impl CoreArrayProvider for AddressRange { type Raw = BNAddressRange; type Context = (); + type Wrapped<'a> = &'a AddressRange; } -unsafe impl CoreOwnedArrayProvider for AddressRange { +unsafe impl CoreArrayProviderInner for AddressRange { unsafe fn free(raw: *mut Self::Raw, _count: usize, _context: &Self::Context) { BNFreeAddressRanges(raw); } -} - -unsafe impl<'a> CoreArrayWrapper<'a> for AddressRange { - type Wrapped = &'a AddressRange; - - unsafe fn wrap_raw(raw: &'a Self::Raw, _context: &'a Self::Context) -> Self::Wrapped { + unsafe fn wrap_raw<'a>(raw: &'a Self::Raw, _context: &'a Self::Context) -> Self::Wrapped<'a> { mem::transmute(raw) } } + +///////////////// +// PerformanceInfo + +// NOTE only exists as Array, cant be owned +#[repr(transparent)] +pub struct PerformanceInfo(BNPerformanceInfo); + +impl PerformanceInfo { + pub fn name(&self) -> &str { + unsafe { std::ffi::CStr::from_ptr(self.0.name) } + .to_str() + .unwrap() + } + pub fn seconds(&self) -> f64 { + self.0.seconds + } +} + +impl CoreArrayProvider for PerformanceInfo { + type Raw = BNPerformanceInfo; + type Context = (); + type Wrapped<'a> = Guard<'a, PerformanceInfo>; +} +unsafe impl CoreArrayProviderInner for PerformanceInfo { + unsafe fn free(raw: *mut Self::Raw, count: usize, _context: &Self::Context) { + BNFreeAnalysisPerformanceInfo(raw, count); + } + unsafe fn wrap_raw<'a>(raw: &'a Self::Raw, context: &'a Self::Context) -> Self::Wrapped<'a> { + Guard::new(Self(*raw), context) + } +} + +///////////////// +// Comments + +// NOTE only exists as Array, cant be owned +pub struct Comments { + addr: u64, + comment: BnString, +} + +impl Comments { + pub fn address(&self) -> u64 { + self.addr + } + pub fn comment(&self) -> &str { + self.comment.as_str() + } +} + +impl CoreArrayProvider for Comments { + type Raw = u64; + type Context = Ref; + type Wrapped<'a> = Comments; +} +unsafe impl CoreArrayProviderInner for Comments { + unsafe fn free(raw: *mut Self::Raw, _count: usize, _context: &Self::Context) { + BNFreeAddressList(raw); + } + unsafe fn wrap_raw<'a>(raw: &'a Self::Raw, function: &'a Self::Context) -> Self::Wrapped<'a> { + Comments { + addr: *raw, + comment: function.comment_at(*raw), + } + } +} diff --git a/src/headless.rs b/src/headless.rs index 1520fc5..1de41ae 100644 --- a/src/headless.rs +++ b/src/headless.rs @@ -14,9 +14,8 @@ use crate::{ binaryview, - metadata::Metadata, - rc::{self, Ref}, - string::BnStrCompatible, + rc, + string::{BnStrCompatible, IntoJson}, }; use std::env; @@ -59,6 +58,11 @@ fn binja_path() -> PathBuf { let path = CStr::from_ptr(info.dli_fname); let path = OsStr::from_bytes(path.to_bytes()); let mut path = PathBuf::from(path); + while path.is_symlink() { + path = path + .read_link() + .expect("Failed to find libbinaryninjacore path!"); + } path.pop(); path @@ -95,13 +99,18 @@ pub fn shutdown() { unsafe { binaryninjacore_sys::BNShutdown() }; } +pub fn is_shutdown_requested() -> bool { + unsafe { binaryninjacore_sys::BNIsShutdownRequested() } +} + /// Prelued-postlued helper function (calls [`init`] and [`shutdown`] for you) -/// ```rust +/// ```no_run +/// # use binaryninja::binaryview::BinaryViewExt; /// binaryninja::headless::script_helper(|| { -/// binaryninja::load("/bin/cat") -/// .expect("Couldn't open `/bin/cat`") -/// .iter() -/// .for_each(|func| println!(" `{}`", func.symbol().full_name())); +/// let cat = binaryninja::load("/bin/cat").expect("Couldn't open `/bin/cat`"); +/// for function in cat.functions().iter() { +/// println!(" `{}`", function.symbol().full_name()); +/// } /// }); /// ``` pub fn script_helper(func: fn()) { @@ -119,7 +128,7 @@ impl Session { Self {} } - /// ```rust + /// ```no_run /// let headless_session = binaryninja::headless::Session::new(); /// /// let bv = headless_session.load("/bin/cat").expect("Couldn't open `/bin/cat`"); @@ -128,18 +137,23 @@ impl Session { crate::load(filename) } - /// ```rust - /// let settings = [("analysis.linearSweep.autorun", false)].into(); + /// ```no_run + /// use binaryninja::{metadata::Metadata, rc::Ref}; + /// use std::collections::HashMap; + /// + /// let settings: Ref = HashMap::from([ + /// ("analysis.linearSweep.autorun", false.into()), + /// ]).into(); /// let headless_session = binaryninja::headless::Session::new(); /// /// let bv = headless_session.load_with_options("/bin/cat", true, Some(settings)) /// .expect("Couldn't open `/bin/cat`"); /// ``` - pub fn load_with_options( + pub fn load_with_options( &self, filename: &str, update_analysis_and_wait: bool, - options: Option>, + options: Option, ) -> Option> { crate::load_with_options(filename, update_analysis_and_wait, options) } diff --git a/src/hlil/function.rs b/src/hlil/function.rs index 4bad7f0..25608d7 100644 --- a/src/hlil/function.rs +++ b/src/hlil/function.rs @@ -2,8 +2,10 @@ use std::hash::{Hash, Hasher}; use binaryninjacore_sys::BNFreeHighLevelILFunction; use binaryninjacore_sys::BNGetHighLevelILBasicBlockList; +use binaryninjacore_sys::BNGetHighLevelILIndexForInstruction; use binaryninjacore_sys::BNGetHighLevelILInstructionCount; use binaryninjacore_sys::BNGetHighLevelILOwnerFunction; +use binaryninjacore_sys::BNGetHighLevelILRootExpr; use binaryninjacore_sys::BNGetHighLevelILSSAForm; use binaryninjacore_sys::BNHighLevelILFunction; use binaryninjacore_sys::BNNewHighLevelILFunctionReference; @@ -52,6 +54,29 @@ impl HighLevelILFunction { self.instruction_from_idx(expr_idx).lift() } + pub fn instruction_from_instruction_idx(&self, instr_idx: usize) -> HighLevelILInstruction { + HighLevelILInstruction::new(self.as_non_ast(), unsafe { + BNGetHighLevelILIndexForInstruction(self.handle, instr_idx) + }) + } + + pub fn lifted_instruction_from_instruction_idx( + &self, + instr_idx: usize, + ) -> HighLevelILLiftedInstruction { + self.instruction_from_instruction_idx(instr_idx).lift() + } + + pub fn root(&self) -> HighLevelILInstruction { + HighLevelILInstruction::new(self.as_ast(), unsafe { + BNGetHighLevelILRootExpr(self.handle) + }) + } + + pub fn lifted_root(&self) -> HighLevelILLiftedInstruction { + self.root().lift() + } + pub fn instruction_count(&self) -> usize { unsafe { BNGetHighLevelILInstructionCount(self.handle) } } @@ -81,6 +106,22 @@ impl HighLevelILFunction { unsafe { Array::new(blocks, count, context) } } + + pub fn as_ast(&self) -> Ref { + Self { + handle: self.handle, + full_ast: true, + } + .to_owned() + } + + pub fn as_non_ast(&self) -> Ref { + Self { + handle: self.handle, + full_ast: false, + } + .to_owned() + } } impl ToOwned for HighLevelILFunction { diff --git a/src/hlil/instruction.rs b/src/hlil/instruction.rs index 159652f..6b27284 100644 --- a/src/hlil/instruction.rs +++ b/src/hlil/instruction.rs @@ -1,12 +1,10 @@ -use binaryninjacore_sys::BNFromVariableIdentifier; use binaryninjacore_sys::BNGetHighLevelILByIndex; use binaryninjacore_sys::BNHighLevelILOperation; +use crate::architecture::CoreIntrinsic; use crate::operand_iter::OperandIter; use crate::rc::Ref; -use crate::types::{ - ConstantData, ILIntrinsic, RegisterValue, RegisterValueType, SSAVariable, Variable, -}; +use crate::types::{ConstantData, RegisterValue, RegisterValueType, SSAVariable, Variable}; use super::operation::*; use super::{HighLevelILFunction, HighLevelILLiftedInstruction, HighLevelILLiftedInstructionKind}; @@ -15,6 +13,8 @@ use super::{HighLevelILFunction, HighLevelILLiftedInstruction, HighLevelILLifted pub struct HighLevelILInstruction { pub function: Ref, pub address: u64, + pub index: usize, + pub size: usize, pub kind: HighLevelILInstructionKind, } @@ -144,8 +144,8 @@ pub enum HighLevelILInstructionKind { DoWhileSsa(WhileSsa), } impl HighLevelILInstruction { - pub(crate) fn new(function: Ref, idx: usize) -> Self { - let op = unsafe { BNGetHighLevelILByIndex(function.handle, idx, function.full_ast) }; + pub(crate) fn new(function: Ref, index: usize) -> Self { + let op = unsafe { BNGetHighLevelILByIndex(function.handle, index, function.full_ast) }; use BNHighLevelILOperation::*; use HighLevelILInstructionKind as Op; let kind = match op.operation { @@ -610,8 +610,8 @@ impl HighLevelILInstruction { body: op.operands[1] as usize, }), HLIL_DO_WHILE => Op::DoWhile(While { - condition: op.operands[0] as usize, - body: op.operands[1] as usize, + body: op.operands[0] as usize, + condition: op.operands[1] as usize, }), HLIL_WHILE_SSA => Op::WhileSsa(WhileSsa { condition_phi: op.operands[0] as usize, @@ -627,6 +627,8 @@ impl HighLevelILInstruction { Self { function, address: op.address, + index, + size: op.size, kind, } } @@ -809,11 +811,11 @@ impl HighLevelILInstruction { cond_false: self.lift_operand(op.cond_false), }), Intrinsic(op) => Lifted::Intrinsic(LiftedIntrinsic { - intrinsic: ILIntrinsic::new(self.function.get_function().arch(), op.intrinsic), + intrinsic: CoreIntrinsic(self.function.get_function().arch().0, op.intrinsic), params: self.lift_instruction_list(op.first_param, op.num_params), }), IntrinsicSsa(op) => Lifted::IntrinsicSsa(LiftedIntrinsicSsa { - intrinsic: ILIntrinsic::new(self.function.get_function().arch(), op.intrinsic), + intrinsic: CoreIntrinsic(self.function.get_function().arch().0, op.intrinsic), params: self.lift_instruction_list(op.first_param, op.num_params), dest_memory: op.dest_memory, src_memory: op.src_memory, @@ -875,6 +877,8 @@ impl HighLevelILInstruction { HighLevelILLiftedInstruction { function: self.function.clone(), address: self.address, + index: self.index, + size: self.size, kind, } } @@ -979,7 +983,7 @@ fn get_float(value: u64, size: usize) -> f64 { } fn get_var(id: u64) -> Variable { - unsafe { Variable::from_raw(BNFromVariableIdentifier(id)) } + unsafe { Variable::from_identifier(id) } } fn get_member_index(idx: u64) -> Option { diff --git a/src/hlil/lift.rs b/src/hlil/lift.rs index f8db34c..300a7f1 100644 --- a/src/hlil/lift.rs +++ b/src/hlil/lift.rs @@ -1,7 +1,9 @@ -use super::{operation::*, HighLevelILFunction}; +use super::operation::*; +use super::HighLevelILFunction; +use crate::architecture::CoreIntrinsic; use crate::rc::Ref; -use crate::types::{ConstantData, ILIntrinsic, SSAVariable, Variable}; +use crate::types::{ConstantData, SSAVariable, Variable}; #[derive(Clone)] pub enum HighLevelILLiftedOperand { @@ -11,7 +13,7 @@ pub enum HighLevelILLiftedOperand { Float(f64), Int(u64), IntList(Vec), - Intrinsic(ILIntrinsic), + Intrinsic(CoreIntrinsic), Label(GotoLabel), MemberIndex(Option), Var(Variable), @@ -23,6 +25,8 @@ pub enum HighLevelILLiftedOperand { pub struct HighLevelILLiftedInstruction { pub function: Ref, pub address: u64, + pub index: usize, + pub size: usize, pub kind: HighLevelILLiftedInstructionKind, } @@ -153,6 +157,134 @@ pub enum HighLevelILLiftedInstructionKind { } impl HighLevelILLiftedInstruction { + pub fn name(&self) -> &'static str { + use HighLevelILLiftedInstructionKind::*; + match self.kind { + Nop => "Nop", + Break => "Break", + Continue => "Continue", + Noret => "Noret", + Unreachable => "Unreachable", + Bp => "Bp", + Undef => "Undef", + Unimpl => "Unimpl", + Adc(_) => "Adc", + Sbb(_) => "Sbb", + Rlc(_) => "Rlc", + Rrc(_) => "Rrc", + Add(_) => "Add", + Sub(_) => "Sub", + And(_) => "And", + Or(_) => "Or", + Xor(_) => "Xor", + Lsl(_) => "Lsl", + Lsr(_) => "Lsr", + Asr(_) => "Asr", + Rol(_) => "Rol", + Ror(_) => "Ror", + Mul(_) => "Mul", + MuluDp(_) => "MuluDp", + MulsDp(_) => "MulsDp", + Divu(_) => "Divu", + DivuDp(_) => "DivuDp", + Divs(_) => "Divs", + DivsDp(_) => "DivsDp", + Modu(_) => "Modu", + ModuDp(_) => "ModuDp", + Mods(_) => "Mods", + ModsDp(_) => "ModsDp", + CmpE(_) => "CmpE", + CmpNe(_) => "CmpNe", + CmpSlt(_) => "CmpSlt", + CmpUlt(_) => "CmpUlt", + CmpSle(_) => "CmpSle", + CmpUle(_) => "CmpUle", + CmpSge(_) => "CmpSge", + CmpUge(_) => "CmpUge", + CmpSgt(_) => "CmpSgt", + CmpUgt(_) => "CmpUgt", + TestBit(_) => "TestBit", + AddOverflow(_) => "AddOverflow", + Fadd(_) => "Fadd", + Fsub(_) => "Fsub", + Fmul(_) => "Fmul", + Fdiv(_) => "Fdiv", + FcmpE(_) => "FcmpE", + FcmpNe(_) => "FcmpNe", + FcmpLt(_) => "FcmpLt", + FcmpLe(_) => "FcmpLe", + FcmpGe(_) => "FcmpGe", + FcmpGt(_) => "FcmpGt", + FcmpO(_) => "FcmpO", + FcmpUo(_) => "FcmpUo", + ArrayIndex(_) => "ArrayIndex", + ArrayIndexSsa(_) => "ArrayIndexSsa", + Assign(_) => "Assign", + AssignMemSsa(_) => "AssignMemSsa", + AssignUnpack(_) => "AssignUnpack", + AssignUnpackMemSsa(_) => "AssignUnpackMemSsa", + Block(_) => "Block", + Call(_) => "Call", + Tailcall(_) => "Tailcall", + CallSsa(_) => "CallSsa", + Case(_) => "Case", + Const(_) => "Const", + ConstPtr(_) => "ConstPtr", + Import(_) => "Import", + ConstData(_) => "ConstData", + Deref(_) => "Deref", + AddressOf(_) => "AddressOf", + Neg(_) => "Neg", + Not(_) => "Not", + Sx(_) => "Sx", + Zx(_) => "Zx", + LowPart(_) => "LowPart", + BoolToInt(_) => "BoolToInt", + UnimplMem(_) => "UnimplMem", + Fsqrt(_) => "Fsqrt", + Fneg(_) => "Fneg", + Fabs(_) => "Fabs", + FloatToInt(_) => "FloatToInt", + IntToFloat(_) => "IntToFloat", + FloatConv(_) => "FloatConv", + RoundToInt(_) => "RoundToInt", + Floor(_) => "Floor", + Ceil(_) => "Ceil", + Ftrunc(_) => "Ftrunc", + DerefFieldSsa(_) => "DerefFieldSsa", + DerefSsa(_) => "DerefSsa", + ExternPtr(_) => "ExternPtr", + FloatConst(_) => "FloatConst", + For(_) => "For", + ForSsa(_) => "ForSsa", + Goto(_) => "Goto", + Label(_) => "Label", + If(_) => "If", + Intrinsic(_) => "Intrinsic", + IntrinsicSsa(_) => "IntrinsicSsa", + Jump(_) => "Jump", + MemPhi(_) => "MemPhi", + Ret(_) => "Ret", + Split(_) => "Split", + StructField(_) => "StructField", + DerefField(_) => "DerefField", + Switch(_) => "Switch", + Syscall(_) => "Syscall", + SyscallSsa(_) => "SyscallSsa", + Trap(_) => "Trap", + VarDeclare(_) => "VarDeclare", + Var(_) => "Var", + VarInit(_) => "VarInit", + VarInitSsa(_) => "VarInitSsa", + VarPhi(_) => "VarPhi", + VarSsa(_) => "VarSsa", + While(_) => "While", + DoWhile(_) => "DoWhile", + WhileSsa(_) => "WhileSsa", + DoWhileSsa(_) => "DoWhileSsa", + } + } + pub fn operands(&self) -> Vec<(&'static str, HighLevelILLiftedOperand)> { use HighLevelILLiftedInstructionKind::*; use HighLevelILLiftedOperand as Operand; diff --git a/src/hlil/operation.rs b/src/hlil/operation.rs index 965d951..e762a74 100644 --- a/src/hlil/operation.rs +++ b/src/hlil/operation.rs @@ -1,22 +1,22 @@ use binaryninjacore_sys::BNGetGotoLabelName; +use crate::architecture::CoreIntrinsic; use crate::function::Function; use crate::rc::Ref; -use crate::types::{ConstantData, ILIntrinsic, SSAVariable, Variable}; +use crate::string::BnString; +use crate::types::{ConstantData, SSAVariable, Variable}; use super::HighLevelILLiftedInstruction; #[derive(Clone, Debug, PartialEq, Eq)] pub struct GotoLabel { pub(crate) function: Ref, - pub(crate) target: u64, + pub target: u64, } impl GotoLabel { - pub fn name(&self) -> &str { - let raw_str = unsafe { BNGetGotoLabelName(self.function.handle, self.target) }; - let c_str = unsafe { core::ffi::CStr::from_ptr(raw_str) }; - c_str.to_str().unwrap() + pub fn name(&self) -> BnString { + unsafe { BnString::from_raw(BNGetGotoLabelName(self.function.handle, self.target)) } } } @@ -320,7 +320,7 @@ pub struct Intrinsic { } #[derive(Clone, Debug, PartialEq)] pub struct LiftedIntrinsic { - pub intrinsic: ILIntrinsic, + pub intrinsic: CoreIntrinsic, pub params: Vec, } @@ -335,7 +335,7 @@ pub struct IntrinsicSsa { } #[derive(Clone, Debug, PartialEq)] pub struct LiftedIntrinsicSsa { - pub intrinsic: ILIntrinsic, + pub intrinsic: CoreIntrinsic, pub params: Vec, pub dest_memory: u64, pub src_memory: u64, diff --git a/src/interaction.rs b/src/interaction.rs index 7e54925..7d10bbe 100644 --- a/src/interaction.rs +++ b/src/interaction.rs @@ -16,12 +16,13 @@ use binaryninjacore_sys::*; +use std::ffi::CStr; use std::os::raw::{c_char, c_void}; use std::path::PathBuf; use crate::binaryview::BinaryView; use crate::rc::Ref; -use crate::string::{BnStr, BnStrCompatible, BnString}; +use crate::string::{BnStrCompatible, BnString}; pub fn get_text_line_input(prompt: &str, title: &str) -> Option { let mut value: *mut libc::c_char = std::ptr::null_mut(); @@ -295,7 +296,9 @@ impl FormInputBuilder { result.type_ = BNFormInputFieldType::AddressFormField; result.prompt = prompt.as_ref().as_ptr() as *const c_char; if let Some(view) = view { - result.view = view.handle; + // the view is being moved into result, there is no need to clone + // and drop is intentionally being avoided with `Ref::into_raw` + result.view = unsafe { Ref::into_raw(view) }.handle; } result.currentAddress = current_address.unwrap_or(0); result.hasDefault = default.is_some(); @@ -448,8 +451,10 @@ impl FormInputBuilder { /// /// This API is flexible and works both in the UI via a pop-up dialog and on the command-line. /// - /// ``` - /// let responses = interaction::FormInputBuilder::new() + /// ```no_run + /// # use binaryninja::interaction::FormInputBuilder; + /// # use binaryninja::interaction::FormResponses; + /// let responses = FormInputBuilder::new() /// .text_field("First Name", None) /// .text_field("Last Name", None) /// .choice_field( @@ -466,15 +471,19 @@ impl FormInputBuilder { /// .get_form_input("Form Title"); /// /// let food = match responses[2] { - /// Index(0) => "Pizza", - /// Index(1) => "Also Pizza", - /// Index(2) => "Also Pizza", - /// Index(3) => "Wrong Answer", + /// FormResponses::Index(0) => "Pizza", + /// FormResponses::Index(1) => "Also Pizza", + /// FormResponses::Index(2) => "Also Pizza", + /// FormResponses::Index(3) => "Wrong Answer", /// _ => panic!("This person doesn't like pizza?!?"), /// }; /// - /// let interaction::FormResponses::String(last_name) = responses[0]; - /// let interaction::FormResponses::String(first_name) = responses[1]; + /// let FormResponses::String(last_name) = &responses[0] else { + /// unreachable!() + /// }; + /// let FormResponses::String(first_name) = &responses[1] else { + /// unreachable!() + /// }; /// /// println!("{} {} likes {}", &first_name, &last_name, food); /// ``` @@ -499,7 +508,10 @@ impl FormInputBuilder { | BNFormInputFieldType::SaveFileNameFormField | BNFormInputFieldType::DirectoryNameFormField => { FormResponses::String(unsafe { - BnStr::from_raw(form_field.stringResult).to_string() + CStr::from_ptr(form_field.stringResult) + .to_str() + .unwrap() + .to_owned() }) } diff --git a/src/lib.rs b/src/lib.rs index 0d38574..bd46611 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -49,7 +49,7 @@ //! //! Create a new library (`cargo new --lib `) and include the following in your `Cargo.toml`: //! -//! ``` +//! ```toml //! [lib] //! crate-type = ["cdylib"] //! @@ -73,21 +73,19 @@ //! //! ### `main.rs` //! Standalone binaries need to initialize Binary Ninja before they can work. You can do this through [`headless::Session`], [`headless::script_helper`], or [`headless::init()`] at start and [`headless::shutdown()`] at shutdown. -//! ```rust -//! fn main() { -//! // This loads all the core architecture, platform, etc plugins -//! // Standalone executables need to call this, but plugins do not -//! let headless_session = binaryninja::headless::Session::new(); +//! ```no_run +//! // This loads all the core architecture, platform, etc plugins +//! // Standalone executables need to call this, but plugins do not +//! let headless_session = binaryninja::headless::Session::new(); //! -//! println!("Loading binary..."); -//! let bv = headless_session.load("/bin/cat").expect("Couldn't open `/bin/cat`"); +//! println!("Loading binary..."); +//! let bv = headless_session.load("/bin/cat").expect("Couldn't open `/bin/cat`"); //! -//! // Your code here... -//! } +//! // Your code here... //! ``` //! //! ### `Cargo.toml` -//! ``` +//! ```toml //! [dependencies] //! binaryninja = { git = "https://github.com/Vector35/binaryninja-api.git", branch = "dev"} //! ``` @@ -135,11 +133,15 @@ pub mod binarywriter; pub mod callingconvention; pub mod command; pub mod custombinaryview; +pub mod database; pub mod databuffer; pub mod debuginfo; pub mod demangle; pub mod disassembly; +pub mod enterprise; +pub mod component; pub mod downloadprovider; +pub mod externallibrary; pub mod fileaccessor; pub mod filemetadata; pub mod flowgraph; @@ -154,6 +156,7 @@ pub mod logger; pub mod metadata; pub mod mlil; pub mod platform; +pub mod project; pub mod rc; pub mod references; pub mod relocation; @@ -164,7 +167,10 @@ pub mod string; pub mod symbol; pub mod tags; pub mod templatesimplifier; +pub mod typelibrary; +pub mod typearchive; pub mod types; +pub mod update; use std::path::PathBuf; @@ -173,8 +179,8 @@ pub use binaryninjacore_sys::BNEndianness as Endianness; use binaryview::BinaryView; use metadata::Metadata; use metadata::MetadataType; -use rc::Ref; use string::BnStrCompatible; +use string::IntoJson; // Commented out to suppress unused warnings // const BN_MAX_INSTRUCTION_LENGTH: u64 = 256; @@ -199,14 +205,14 @@ const BN_INVALID_EXPR: usize = usize::MAX; /// The main way to open and load files into Binary Ninja. Make sure you've properly initialized the core before calling this function. See [`crate::headless::init()`] pub fn load(filename: S) -> Option> { let filename = filename.into_bytes_with_nul(); - let metadata = Metadata::new_of_type(MetadataType::KeyValueDataType); + let options = "\x00"; let handle = unsafe { binaryninjacore_sys::BNLoadFilename( filename.as_ref().as_ptr() as *mut _, true, + options.as_ptr() as *mut core::ffi::c_char, None, - metadata.handle, ) }; @@ -219,31 +225,83 @@ pub fn load(filename: S) -> OptionStrict JSON doesn't support single quotes for strings, so you'll need to either use a raw strings (f#"{"setting": "value"}"#) or escape double quotes ("{\"setting\": \"value\"}"). Or use serde_json::json. /// -/// let bv = binaryninja::load_with_options("/bin/cat", true, Some(settings)) +/// ```no_run +/// # // Mock implementation of json! macro for documentation purposes +/// # macro_rules! json { +/// # ($($arg:tt)*) => { +/// # stringify!($($arg)*) +/// # }; +/// # } +/// use binaryninja::{metadata::Metadata, rc::Ref}; +/// use std::collections::HashMap; +/// +/// let bv = binaryninja::load_with_options("/bin/cat", true, Some(json!("analysis.linearSweep.autorun": false).to_string())) /// .expect("Couldn't open `/bin/cat`"); /// ``` -pub fn load_with_options( +pub fn load_with_options( filename: S, update_analysis_and_wait: bool, - options: Option>, + options: Option, ) -> Option> { let filename = filename.into_bytes_with_nul(); let options_or_default = if let Some(opt) = options { - opt + opt.get_json_string() + .ok()? + .into_bytes_with_nul() + .as_ref() + .to_vec() } else { Metadata::new_of_type(MetadataType::KeyValueDataType) + .get_json_string() + .ok()? + .as_ref() + .to_vec() }; let handle = unsafe { binaryninjacore_sys::BNLoadFilename( filename.as_ref().as_ptr() as *mut _, update_analysis_and_wait, + options_or_default.as_ptr() as *mut core::ffi::c_char, + None, + ) + }; + + if handle.is_null() { + None + } else { + Some(unsafe { BinaryView::from_raw(handle) }) + } +} + +pub fn load_view( + bv: &BinaryView, + update_analysis_and_wait: bool, + options: Option, +) -> Option> { + let options_or_default = if let Some(opt) = options { + opt.get_json_string() + .ok()? + .into_bytes_with_nul() + .as_ref() + .to_vec() + } else { + Metadata::new_of_type(MetadataType::KeyValueDataType) + .get_json_string() + .ok()? + .as_ref() + .to_vec() + }; + + let handle = unsafe { + binaryninjacore_sys::BNLoadBinaryView( + bv.handle as *mut _, + update_analysis_and_wait, + options_or_default.as_ptr() as *mut core::ffi::c_char, None, - options_or_default.as_ref().handle, ) }; @@ -377,6 +435,75 @@ pub fn version() -> string::BnString { unsafe { string::BnString::from_raw(binaryninjacore_sys::BNGetVersionString()) } } +pub fn build_id() -> u32 { + unsafe { binaryninjacore_sys::BNGetBuildId() } +} + +#[derive(Clone, PartialEq, Eq, Hash)] +pub struct VersionInfo { + pub major: u32, + pub minor: u32, + pub build: u32, + pub channel: string::BnString, +} + +pub fn version_info() -> VersionInfo { + let info_raw = unsafe { binaryninjacore_sys::BNGetVersionInfo() }; + VersionInfo { + major: info_raw.major, + minor: info_raw.minor, + build: info_raw.build, + channel: unsafe { string::BnString::from_raw(info_raw.channel) }, + } +} + +pub fn serial_number() -> string::BnString { + unsafe { string::BnString::from_raw(binaryninjacore_sys::BNGetSerialNumber()) } +} + +pub fn is_license_validated() -> bool { + unsafe { binaryninjacore_sys::BNIsLicenseValidated() } +} + +pub fn licensed_user_email() -> string::BnString { + unsafe { string::BnString::from_raw(binaryninjacore_sys::BNGetLicensedUserEmail()) } +} + +pub fn license_count() -> i32 { + unsafe { binaryninjacore_sys::BNGetLicenseCount() } +} + +pub fn set_license(license: S) { + let license = license.into_bytes_with_nul(); + let license_slice = license.as_ref(); + unsafe { binaryninjacore_sys::BNSetLicense(license_slice.as_ptr() as *const std::os::raw::c_char) } +} + +pub fn product() -> string::BnString { + unsafe { string::BnString::from_raw(binaryninjacore_sys::BNGetProduct()) } +} + +pub fn product_type() -> string::BnString { + unsafe { string::BnString::from_raw(binaryninjacore_sys::BNGetProductType()) } +} + +pub fn license_expiration_time() -> std::time::SystemTime { + let m = std::time::Duration::from_secs(unsafe { + binaryninjacore_sys::BNGetLicenseExpirationTime() + }); + std::time::UNIX_EPOCH + m +} + +pub fn is_ui_enabled() -> bool { + unsafe { binaryninjacore_sys::BNIsUIEnabled() } +} + +pub fn is_database(filename: S) -> bool { + let filename = filename.into_bytes_with_nul(); + let filename_slice = filename.as_ref(); + unsafe { binaryninjacore_sys::BNIsDatabase(filename_slice.as_ptr() as *const std::os::raw::c_char) } +} + pub fn plugin_abi_version() -> u32 { binaryninjacore_sys::BN_CURRENT_CORE_ABI_VERSION } diff --git a/src/linearview.rs b/src/linearview.rs index 09968fc..84b4323 100644 --- a/src/linearview.rs +++ b/src/linearview.rs @@ -415,18 +415,14 @@ impl std::fmt::Display for LinearDisassemblyLine { impl CoreArrayProvider for LinearDisassemblyLine { type Raw = BNLinearDisassemblyLine; type Context = (); + type Wrapped<'a> = Guard<'a, LinearDisassemblyLine>; } -unsafe impl CoreOwnedArrayProvider for LinearDisassemblyLine { +unsafe impl CoreArrayProviderInner for LinearDisassemblyLine { unsafe fn free(raw: *mut BNLinearDisassemblyLine, count: usize, _context: &()) { BNFreeLinearDisassemblyLines(raw, count); } -} - -unsafe impl<'a> CoreArrayWrapper<'a> for LinearDisassemblyLine { - type Wrapped = Guard<'a, LinearDisassemblyLine>; - - unsafe fn wrap_raw(raw: &'a Self::Raw, _context: &'a Self::Context) -> Self::Wrapped { + unsafe fn wrap_raw<'a>(raw: &'a Self::Raw, _context: &'a Self::Context) -> Self::Wrapped<'a> { Guard::new(LinearDisassemblyLine::from_raw(raw), _context) } } diff --git a/src/llil/expression.rs b/src/llil/expression.rs index d8a3a64..4339787 100644 --- a/src/llil/expression.rs +++ b/src/llil/expression.rs @@ -136,6 +136,8 @@ where LLIL_ZX => ExprInfo::Zx(Operation::new(function, op)), LLIL_LOW_PART => ExprInfo::LowPart(Operation::new(function, op)), + LLIL_REG_SPLIT => ExprInfo::RegSplit(Operation::new(function, op)), + LLIL_CMP_E => ExprInfo::CmpE(Operation::new(function, op)), LLIL_CMP_NE => ExprInfo::CmpNe(Operation::new(function, op)), LLIL_CMP_SLT => ExprInfo::CmpSlt(Operation::new(function, op)), @@ -273,6 +275,7 @@ where LLIL_LOAD => ExprInfo::Load(Operation::new(self.function, op)), LLIL_POP => ExprInfo::Pop(Operation::new(self.function, op)), LLIL_REG => ExprInfo::Reg(Operation::new(self.function, op)), + LLIL_REG_SPLIT => ExprInfo::RegSplit(Operation::new(self.function, op)), LLIL_FLAG => ExprInfo::Flag(Operation::new(self.function, op)), LLIL_FLAG_BIT => ExprInfo::FlagBit(Operation::new(self.function, op)), LLIL_FLAG_COND => ExprInfo::FlagCond(Operation::new(self.function, op)), // TODO lifted only @@ -327,6 +330,7 @@ where match op.operation { LLIL_LOAD_SSA => ExprInfo::Load(Operation::new(self.function, op)), LLIL_REG_SSA | LLIL_REG_SSA_PARTIAL => ExprInfo::Reg(Operation::new(self.function, op)), + LLIL_REG_SPLIT_SSA => ExprInfo::RegSplit(Operation::new(self.function, op)), LLIL_FLAG_SSA => ExprInfo::Flag(Operation::new(self.function, op)), LLIL_FLAG_BIT_SSA => ExprInfo::FlagBit(Operation::new(self.function, op)), _ => common_info(self.function, op), @@ -383,6 +387,7 @@ where Load(Operation<'func, A, M, F, operation::Load>), Pop(Operation<'func, A, M, F, operation::Pop>), Reg(Operation<'func, A, M, F, operation::Reg>), + RegSplit(Operation<'func, A, M, F, operation::RegSplit>), Const(Operation<'func, A, M, F, operation::Const>), ConstPtr(Operation<'func, A, M, F, operation::Const>), Flag(Operation<'func, A, M, F, operation::Flag>), @@ -595,6 +600,8 @@ where Reg(ref op) => &op.op, + RegSplit(ref op) => &op.op, + Flag(ref op) => &op.op, FlagBit(ref op) => &op.op, @@ -648,6 +655,8 @@ where Reg(ref op) => op.flag_write(), + RegSplit(ref op) => op.flag_write(), + Flag(ref op) => op.flag_write(), FlagBit(ref op) => op.flag_write(), @@ -735,6 +744,31 @@ where } } + RegSplit(ref op) => { + let low_reg = op.low_reg(); + let high_reg = op.high_reg(); + let size = op.size(); + + let low_size = match low_reg { + Register::Temp(_) => Some(size), + Register::ArchReg(ref r) if r.info().size() != size => Some(size), + _ => None, + }; + + let high_size = match high_reg { + Register::Temp(_) => Some(size), + Register::ArchReg(ref r) if r.info().size() != size => Some(size), + _ => None, + }; + + match (low_size, high_size) { + (Some(ls), Some(hs)) => write!(f, "{:?}.{}:{:?}.{}", high_reg, hs, low_reg, ls), + (Some(ls), None) => write!(f, "{:?}:{:?}.{}", high_reg, low_reg, ls), + (None, Some(hs)) => write!(f, "{:?}.{}:{:?}", high_reg, hs, low_reg), + _ => write!(f, "{:?}:{:?}", high_reg, low_reg), + } + } + Flag(ref _op) => write!(f, "flag"), // TODO FlagBit(ref _op) => write!(f, "flag_bit"), // TODO diff --git a/src/llil/instruction.rs b/src/llil/instruction.rs index 469c10f..62e5045 100644 --- a/src/llil/instruction.rs +++ b/src/llil/instruction.rs @@ -99,7 +99,7 @@ where let expr_idx = unsafe { BNGetLowLevelILIndexForInstruction(self.function.handle, self.instr_idx) }; let op = unsafe { BNGetLowLevelILByIndex(self.function.handle, expr_idx) }; - return op.address; + op.address } pub fn info(&self) -> InstrInfo<'func, A, M, NonSSA> { diff --git a/src/llil/lifting.rs b/src/llil/lifting.rs index daf95ac..f8b8257 100644 --- a/src/llil/lifting.rs +++ b/src/llil/lifting.rs @@ -607,6 +607,26 @@ where A: 'a + Architecture, R: ExpressionResultType, { + pub fn from_expr(expr: Expression<'a, A, Mutable, NonSSA, R>) -> Self { + use binaryninjacore_sys::BNGetLowLevelILByIndex; + + let instr = unsafe { + BNGetLowLevelILByIndex(expr.function.handle, expr.expr_idx) + }; + + ExpressionBuilder { + function: expr.function, + op: instr.operation, + size: instr.size, + flags: instr.flags, + op1: instr.operands[0], + op2: instr.operands[1], + op3: instr.operands[2], + op4: instr.operands[3], + _ty: PhantomData + } + } + pub fn with_flag_write(mut self, flag_write: A::FlagWrite) -> Self { // TODO verify valid id self.flags = flag_write.id(); @@ -1016,6 +1036,43 @@ where Expression::new(self, expr_idx) } + pub fn reg_split>, L: Into>>( + &self, + size: usize, + hi_reg: H, + lo_reg: L, + ) -> Expression, ValueExpr> { + use binaryninjacore_sys::BNLowLevelILAddExpr; + use binaryninjacore_sys::BNLowLevelILOperation::LLIL_REG_SPLIT; + + // TODO verify valid id + let hi_reg = match hi_reg.into() { + Register::ArchReg(r) => r.id(), + Register::Temp(r) => 0x8000_0000 | r, + }; + + // TODO verify valid id + let lo_reg = match lo_reg.into() { + Register::ArchReg(r) => r.id(), + Register::Temp(r) => 0x8000_0000 | r, + }; + + let expr_idx = unsafe { + BNLowLevelILAddExpr( + self.handle, + LLIL_REG_SPLIT, + size, + 0, + hi_reg as u64, + lo_reg as u64, + 0, + 0, + ) + }; + + Expression::new(self, expr_idx) + } + pub fn set_reg<'a, R, E>( &'a self, size: usize, diff --git a/src/llil/operation.rs b/src/llil/operation.rs index 3c40f20..f42b369 100644 --- a/src/llil/operation.rs +++ b/src/llil/operation.rs @@ -12,8 +12,9 @@ // See the License for the specific language governing permissions and // limitations under the License. -use binaryninjacore_sys::BNLowLevelILInstruction; +use binaryninjacore_sys::{BNGetLowLevelILByIndex, BNLowLevelILInstruction}; +use std::collections::BTreeMap; use std::marker::PhantomData; use std::mem; @@ -89,10 +90,10 @@ pub struct Syscall; pub struct Intrinsic; impl<'func, A, M, V> Operation<'func, A, M, NonSSA, Intrinsic> - where - A: 'func + Architecture, - M: FunctionMutability, - V: NonSSAVariant, +where + A: 'func + Architecture, + M: FunctionMutability, + V: NonSSAVariant, { // TODO: Support register and expression lists pub fn intrinsic(&self) -> Option { @@ -289,6 +290,62 @@ where } } +// LLIL_REG_SPLIT +pub struct RegSplit; + +impl<'func, A, M, V> Operation<'func, A, M, NonSSA, RegSplit> + where + A: 'func + Architecture, + M: FunctionMutability, + V: NonSSAVariant, +{ + pub fn size(&self) -> usize { + self.op.size + } + + pub fn low_reg(&self) -> Register { + let raw_id = self.op.operands[0] as u32; + + if raw_id >= 0x8000_0000 { + Register::Temp(raw_id & 0x7fff_ffff) + } else { + self.function + .arch() + .register_from_id(raw_id) + .map(Register::ArchReg) + .unwrap_or_else(|| { + error!( + "got garbage register from LLIL_REG @ 0x{:x}", + self.op.address + ); + + Register::Temp(0) + }) + } + } + + pub fn high_reg(&self) -> Register { + let raw_id = self.op.operands[1] as u32; + + if raw_id >= 0x8000_0000 { + Register::Temp(raw_id & 0x7fff_ffff) + } else { + self.function + .arch() + .register_from_id(raw_id) + .map(Register::ArchReg) + .unwrap_or_else(|| { + error!( + "got garbage register from LLIL_REG @ 0x{:x}", + self.op.address + ); + + Register::Temp(0) + }) + } + } +} + // LLIL_FLAG, LLIL_FLAG_SSA pub struct Flag; @@ -312,6 +369,36 @@ where // LLIL_JUMP_TO pub struct JumpTo; +struct TargetListIter<'func, A, M, F> +where + A: 'func + Architecture, + M: FunctionMutability, + F: FunctionForm, +{ + function: &'func Function, + cursor: BNLowLevelILInstruction, + cursor_operand: usize, +} + +impl<'func, A, M, F> TargetListIter<'func, A, M, F> +where + A: 'func + Architecture, + M: FunctionMutability, + F: FunctionForm, +{ + fn next(&mut self) -> u64 { + if self.cursor_operand >= 3 { + self.cursor = unsafe { + BNGetLowLevelILByIndex(self.function.handle, self.cursor.operands[3] as usize) + }; + self.cursor_operand = 0; + } + let result = self.cursor.operands[self.cursor_operand]; + self.cursor_operand += 1; + result + } +} + impl<'func, A, M, F> Operation<'func, A, M, F, JumpTo> where A: 'func + Architecture, @@ -321,7 +408,26 @@ where pub fn target(&self) -> Expression<'func, A, M, F, ValueExpr> { Expression::new(self.function, self.op.operands[0] as usize) } - // TODO target list + + pub fn target_list(&self) -> BTreeMap { + let mut result = BTreeMap::new(); + let count = self.op.operands[1] as usize / 2; + let mut list = TargetListIter { + function: self.function, + cursor: unsafe { + BNGetLowLevelILByIndex(self.function.handle, self.op.operands[2] as usize) + }, + cursor_operand: 0, + }; + + for _ in 0..count { + let value = list.next(); + let target = list.next() as usize; + result.insert(value, target); + } + + result + } } // LLIL_CALL, LLIL_CALL_SSA @@ -382,12 +488,20 @@ where } } + pub fn true_target_idx(&self) -> usize { + self.op.operands[1] as usize + } + pub fn false_target(&self) -> Instruction<'func, A, M, F> { Instruction { function: self.function, instr_idx: self.op.operands[2] as usize, } } + + pub fn false_target_idx(&self) -> usize { + self.op.operands[2] as usize + } } // LLIL_GOTO @@ -405,6 +519,10 @@ where instr_idx: self.op.operands[0] as usize, } } + + pub fn target_idx(&self) -> usize { + self.op.operands[0] as usize + } } // LLIL_FLAG_COND @@ -640,6 +758,7 @@ impl OperationArguments for SetFlag {} impl OperationArguments for Load {} impl OperationArguments for Store {} impl OperationArguments for Reg {} +impl OperationArguments for RegSplit {} impl OperationArguments for Flag {} impl OperationArguments for FlagBit {} impl OperationArguments for Jump {} diff --git a/src/logger.rs b/src/logger.rs index 8651eec..6e0b188 100644 --- a/src/logger.rs +++ b/src/logger.rs @@ -2,7 +2,7 @@ //! To use logging in your script, do something like: //! -//! ``` +//! ```no-test //! use binaryninja::logger; //! use log::{info, LevelFilter}; //! @@ -15,7 +15,7 @@ //! //! or //! -//!``` +//!```no-test //! use binaryninja::logger; //! use log::{info, LevelFilter}; //! @@ -29,12 +29,11 @@ //! ``` //! -use crate::string::BnStr; - pub use binaryninjacore_sys::BNLogLevel as Level; use binaryninjacore_sys::{BNLogListener, BNUpdateLogListeners}; use log; +use std::ffi::CStr; use std::os::raw::{c_char, c_void}; struct Logger; @@ -84,7 +83,7 @@ pub fn init(filter: log::LevelFilter) -> Result<(), log::SetLoggerError> { } pub trait LogListener: 'static + Sync { - fn log(&self, session: usize, level: Level, msg: &BnStr, logger_name: &BnStr, tid: usize); + fn log(&self, session: usize, level: Level, msg: &CStr, logger_name: &CStr, tid: usize); fn level(&self) -> Level; fn close(&self) {} } @@ -147,8 +146,8 @@ extern "C" fn cb_log( listener.log( session, level, - BnStr::from_raw(msg), - BnStr::from_raw(logger_name), + CStr::from_ptr(msg), + CStr::from_ptr(logger_name), tid, ); }) diff --git a/src/metadata.rs b/src/metadata.rs index e29789e..9eeff8f 100644 --- a/src/metadata.rs +++ b/src/metadata.rs @@ -1,7 +1,5 @@ -use crate::rc::{ - Array, CoreArrayProvider, CoreArrayWrapper, CoreOwnedArrayProvider, Guard, Ref, RefCountable, -}; -use crate::string::{BnStrCompatible, BnString}; +use crate::rc::{Array, CoreArrayProvider, CoreArrayProviderInner, Guard, Ref, RefCountable}; +use crate::string::{BnStrCompatible, BnString, IntoJson}; use binaryninjacore_sys::*; use std::collections::HashMap; use std::os::raw::c_char; @@ -168,6 +166,19 @@ impl Metadata { } } + pub fn get_json_string(&self) -> Result { + match self.get_type() { + MetadataType::StringDataType => { + let ptr: *mut c_char = unsafe { BNMetadataGetJsonString(self.handle) }; + if ptr.is_null() { + return Err(()); + } + Ok(unsafe { BnString::from_raw(ptr) }) + } + _ => Err(()), + } + } + pub fn get_raw(&self) -> Result, ()> { match self.get_type() { MetadataType::RawDataType => { @@ -335,18 +346,14 @@ unsafe impl RefCountable for Metadata { impl CoreArrayProvider for Metadata { type Raw = *mut BNMetadata; type Context = (); + type Wrapped<'a> = Guard<'a, Metadata>; } -unsafe impl CoreOwnedArrayProvider for Metadata { +unsafe impl CoreArrayProviderInner for Metadata { unsafe fn free(raw: *mut *mut BNMetadata, _count: usize, _context: &()) { BNFreeMetadataArray(raw); } -} - -unsafe impl<'a> CoreArrayWrapper<'a> for Metadata { - type Wrapped = Guard<'a, Metadata>; - - unsafe fn wrap_raw(raw: &'a *mut BNMetadata, context: &'a ()) -> Guard<'a, Metadata> { + unsafe fn wrap_raw<'a>(raw: &'a *mut BNMetadata, context: &'a ()) -> Self::Wrapped<'a> { Guard::new(Metadata::from_raw(*raw), context) } } @@ -403,12 +410,6 @@ impl From<&str> for Ref { } } -impl>> From<&T> for Ref { - fn from(value: &T) -> Self { - value.into() - } -} - impl From<&Vec> for Ref { fn from(value: &Vec) -> Self { unsafe { Metadata::ref_from_raw(BNCreateMetadataRawData(value.as_ptr(), value.len())) } @@ -441,16 +442,15 @@ impl From<&Array> for Ref { impl From>> for Ref { fn from(value: HashMap>) -> Self { - let mut key_refs: Vec = vec![]; - let mut keys: Vec<*const c_char> = vec![]; - let mut values: Vec<*mut BNMetadata> = vec![]; - for (k, v) in value.into_iter() { - key_refs.push(k.into_bytes_with_nul()); - values.push(v.as_ref().handle); - } - for k in &key_refs { - keys.push(k.as_ref().as_ptr() as *const c_char); - } + let data: Vec<(S::Result, Ref)> = value + .into_iter() + .map(|(k, v)| (k.into_bytes_with_nul(), v)) + .collect(); + let mut keys: Vec<*const c_char> = data + .iter() + .map(|(k, _)| k.as_ref().as_ptr() as *const c_char) + .collect(); + let mut values: Vec<*mut BNMetadata> = data.iter().map(|(_, v)| v.handle).collect(); unsafe { Metadata::ref_from_raw(BNCreateMetadataValueStore( @@ -462,19 +462,21 @@ impl From>> for Ref { } } -impl>> From<&[(S, T)]> for Ref { +impl From<&[(S, T)]> for Ref +where + S: BnStrCompatible + Copy, + for<'a> &'a T: Into>, +{ fn from(value: &[(S, T)]) -> Self { - let mut key_refs: Vec = vec![]; - let mut keys: Vec<*const c_char> = vec![]; - let mut values: Vec<*mut BNMetadata> = vec![]; - for (k, v) in value.iter() { - key_refs.push(k.into_bytes_with_nul()); - let value_metadata: Ref = v.into(); - values.push(value_metadata.handle); - } - for k in &key_refs { - keys.push(k.as_ref().as_ptr() as *const c_char); - } + let data: Vec<(S::Result, Ref)> = value + .iter() + .map(|(k, v)| (k.into_bytes_with_nul(), v.into())) + .collect(); + let mut keys: Vec<*const c_char> = data + .iter() + .map(|(k, _)| k.as_ref().as_ptr() as *const c_char) + .collect(); + let mut values: Vec<*mut BNMetadata> = data.iter().map(|(_, v)| v.handle).collect(); unsafe { Metadata::ref_from_raw(BNCreateMetadataValueStore( @@ -486,29 +488,15 @@ impl>> From<&[(S, T)]> for Ref< } } -impl>, const N: usize> From<[(S, T); N]> - for Ref +impl From<[(S, T); N]> for Ref +where + S: BnStrCompatible + Copy, + for<'a> &'a T: Into>, { fn from(value: [(S, T); N]) -> Self { - let mut key_refs: Vec = vec![]; - let mut keys: Vec<*const c_char> = vec![]; - let mut values: Vec<*mut BNMetadata> = vec![]; - for (k, v) in value.into_iter() { - key_refs.push(k.into_bytes_with_nul()); - let value_metadata: Ref = v.into(); - values.push(value_metadata.handle); - } - for k in &key_refs { - keys.push(k.as_ref().as_ptr() as *const c_char); - } - - unsafe { - Metadata::ref_from_raw(BNCreateMetadataValueStore( - keys.as_mut_ptr(), - values.as_mut_ptr(), - keys.len(), - )) - } + let slice = &value[..]; + // use the `impl From<&[(S, T)]>` + slice.into() } } @@ -714,3 +702,17 @@ impl TryFrom<&Metadata> for HashMap> { .map(|m| m.into_iter().map(|(k, v)| (k.to_string(), v)).collect()) } } + +impl IntoJson for &Metadata { + type Output = BnString; + fn get_json_string(self) -> Result { + Metadata::get_json_string(self) + } +} + +impl IntoJson for Ref { + type Output = BnString; + fn get_json_string(self) -> Result { + Metadata::get_json_string(&self) + } +} diff --git a/src/mlil/function.rs b/src/mlil/function.rs index 16cc510..928f1b6 100644 --- a/src/mlil/function.rs +++ b/src/mlil/function.rs @@ -1,18 +1,18 @@ use core::hash::{Hash, Hasher}; +use std::ffi::c_char; -use binaryninjacore_sys::BNFreeMediumLevelILFunction; -use binaryninjacore_sys::BNGetMediumLevelILBasicBlockList; -use binaryninjacore_sys::BNGetMediumLevelILInstructionCount; -use binaryninjacore_sys::BNGetMediumLevelILOwnerFunction; -use binaryninjacore_sys::BNGetMediumLevelILSSAForm; -use binaryninjacore_sys::BNMediumLevelILFunction; -use binaryninjacore_sys::BNMediumLevelILGetInstructionStart; -use binaryninjacore_sys::BNNewMediumLevelILFunctionReference; +use binaryninjacore_sys::*; +use crate::architecture::CoreArchitecture; use crate::basicblock::BasicBlock; -use crate::function::Function; -use crate::function::Location; -use crate::rc::{Array, Ref, RefCountable}; +use crate::disassembly::DisassemblySettings; +use crate::flowgraph::FlowGraph; +use crate::function::{Function, Location}; +use crate::rc::{Array, CoreArrayProvider, CoreArrayProviderInner, Ref, RefCountable}; +use crate::string::BnStrCompatible; +use crate::types::{ + Conf, PossibleValueSet, RegisterValue, SSAVariable, Type, UserVariableValues, Variable, +}; use super::{MediumLevelILBlock, MediumLevelILInstruction, MediumLevelILLiftedInstruction}; @@ -65,6 +65,19 @@ impl MediumLevelILFunction { self.instruction_from_idx(expr_idx).lift() } + pub fn instruction_from_instruction_idx(&self, instr_idx: usize) -> MediumLevelILInstruction { + MediumLevelILInstruction::new(self.to_owned(), unsafe { + BNGetMediumLevelILIndexForInstruction(self.handle, instr_idx) + }) + } + + pub fn lifted_instruction_from_instruction_idx( + &self, + instr_idx: usize, + ) -> MediumLevelILLiftedInstruction { + self.instruction_from_instruction_idx(instr_idx).lift() + } + pub fn instruction_count(&self) -> usize { unsafe { BNGetMediumLevelILInstructionCount(self.handle) } } @@ -91,6 +104,478 @@ impl MediumLevelILFunction { unsafe { Array::new(blocks, count, context) } } + + pub fn get_var_definitions<'a>(&'a self, var: &Variable) -> MediumLevelILInstructionList<'a> { + let mut count = 0; + let raw_instrs = + unsafe { BNGetMediumLevelILVariableDefinitions(self.handle, &var.raw(), &mut count) }; + assert!(!raw_instrs.is_null()); + let instrs = unsafe { core::slice::from_raw_parts(raw_instrs, count) }; + MediumLevelILInstructionList { + mlil: self, + ptr: raw_instrs, + instr_idxs: instrs.iter(), + } + } + + pub fn create_user_stack_var<'a, S: BnStrCompatible, C: Into>>( + self, + offset: i64, + var_type: C, + name: S, + ) { + let var_type = var_type.into(); + let mut raw_var_type: BNTypeWithConfidence = var_type.into(); + let name = name.into_bytes_with_nul(); + unsafe { + BNCreateUserStackVariable( + self.get_function().handle, + offset, + &mut raw_var_type, + name.as_ref().as_ptr() as *const c_char, + ) + } + } + + pub fn delete_user_stack_var(self, offset: i64) { + unsafe { BNDeleteUserStackVariable(self.get_function().handle, offset) } + } + + pub fn create_user_var<'a, S: BnStrCompatible, C: Into>>( + &self, + var: &Variable, + var_type: C, + name: S, + ignore_disjoint_uses: bool, + ) { + let var_type = var_type.into(); + let raw_var_type: BNTypeWithConfidence = var_type.into(); + let name = name.into_bytes_with_nul(); + unsafe { + BNCreateUserVariable( + self.get_function().handle, + &var.raw(), + &raw_var_type as *const _ as *mut _, + name.as_ref().as_ptr() as *const _, + ignore_disjoint_uses, + ) + } + } + + pub fn delete_user_var(&self, var: &Variable) { + unsafe { BNDeleteUserVariable(self.get_function().handle, &var.raw()) } + } + + pub fn is_var_user_defined(&self, var: &Variable) -> bool { + unsafe { BNIsVariableUserDefined(self.get_function().handle, &var.raw()) } + } + + /// Allows the user to specify a PossibleValueSet value for an MLIL + /// variable at its definition site. + /// + /// .. warning:: Setting the variable value, triggers a reanalysis of the + /// function and allows the dataflow to compute and propagate values which + /// depend on the current variable. This implies that branch conditions + /// whose values can be determined statically will be computed, leading to + /// potential branch elimination at the HLIL layer. + /// + /// * `var` - Variable for which the value is to be set + /// * `addr` - Address of the definition site of the variable + /// * `value` - Informed value of the variable + /// + /// # Example + /// ```no_run + /// # use binaryninja::mlil::MediumLevelILFunction; + /// # use binaryninja::types::PossibleValueSet; + /// # let mlil_fun: MediumLevelILFunction = todo!(); + /// let (mlil_var, arch_addr, _val) = mlil_fun.user_var_values().all().next().unwrap(); + /// let def_address = arch_addr.address; + /// let var_value = PossibleValueSet::ConstantValue{value: 5}; + /// mlil_fun.set_user_var_value(&mlil_var, def_address, var_value).unwrap(); + /// ``` + pub fn set_user_var_value( + &self, + var: &Variable, + addr: u64, + value: PossibleValueSet, + ) -> Result<(), ()> { + let Some(_def_site) = self + .get_var_definitions(var) + .find(|def| def.address == addr) + else { + // Error "No definition for Variable found at given address" + return Err(()); + }; + let function = self.get_function(); + let def_site = BNArchitectureAndAddress { + arch: function.arch().0, + address: addr, + }; + let value = value.into_raw(); + + unsafe { BNSetUserVariableValue(function.handle, &var.raw(), &def_site, value.as_ffi()) } + Ok(()) + } + + /// Clears a previously defined user variable value. + /// + /// * `var` - Variable for which the value was informed + /// * `def_addr` - Address of the definition site of the variable + pub fn clear_user_var_value(&self, var: &Variable, addr: u64) -> Result<(), ()> { + let Some(_var_def) = self + .get_var_definitions(var) + .find(|site| site.address == addr) + else { + //error "Could not get definition for Variable" + return Err(()); + }; + + let function = self.get_function(); + let def_site = BNArchitectureAndAddress { + arch: function.arch().0, + address: addr, + }; + + unsafe { BNClearUserVariableValue(function.handle, &var.raw(), &def_site) }; + Ok(()) + } + + /// Returns a map of current defined user variable values. + /// Returns a Map of user current defined user variable values and their definition sites. + pub fn user_var_values(&self) -> UserVariableValues { + let mut count = 0; + let function = self.get_function(); + let var_values = unsafe { BNGetAllUserVariableValues(function.handle, &mut count) }; + assert!(!var_values.is_null()); + UserVariableValues { + vars: core::ptr::slice_from_raw_parts(var_values, count), + } + } + + /// Clear all user defined variable values. + pub fn clear_user_var_values(&self) -> Result<(), ()> { + for (var, arch_and_addr, _value) in self.user_var_values().all() { + self.clear_user_var_value(&var, arch_and_addr.address)?; + } + Ok(()) + } + + pub fn create_auto_stack_var<'a, T: Into>, S: BnStrCompatible>( + &self, + offset: i64, + var_type: T, + name: S, + ) { + let var_type: Conf<&Type> = var_type.into(); + let mut var_type = var_type.into(); + let name = name.into_bytes_with_nul(); + let name_c_str = name.as_ref(); + unsafe { + BNCreateAutoStackVariable( + self.get_function().handle, + offset, + &mut var_type, + name_c_str.as_ptr() as *const c_char, + ) + } + } + + pub fn delete_auto_stack_var(&self, offset: i64) { + unsafe { BNDeleteAutoStackVariable(self.get_function().handle, offset) } + } + + pub fn create_auto_var<'a, S: BnStrCompatible, C: Into>>( + &self, + var: &Variable, + var_type: C, + name: S, + ignore_disjoint_uses: bool, + ) { + let var_type: Conf<&Type> = var_type.into(); + let mut var_type = var_type.into(); + let name = name.into_bytes_with_nul(); + let name_c_str = name.as_ref(); + unsafe { + BNCreateAutoVariable( + self.get_function().handle, + &var.raw(), + &mut var_type, + name_c_str.as_ptr() as *const c_char, + ignore_disjoint_uses, + ) + } + } + + /// Returns a list of ILReferenceSource objects (IL xrefs or cross-references) + /// that reference the given variable. The variable is a local variable that can be either on the stack, + /// in a register, or in a flag. + /// This function is related to get_hlil_var_refs(), which returns variable references collected + /// from HLIL. The two can be different in several cases, e.g., multiple variables in MLIL can be merged + /// into a single variable in HLIL. + /// + /// * `var` - Variable for which to query the xref + /// + /// # Example + /// ```no_run + /// # use binaryninja::mlil::MediumLevelILFunction; + /// # use binaryninja::types::Variable; + /// # let mlil_fun: MediumLevelILFunction = todo!(); + /// # let mlil_var: Variable = todo!(); + /// let instr = mlil_fun.var_refs(&mlil_var).get(0).expr(); + /// ``` + pub fn var_refs(&self, var: &Variable) -> Array { + let mut count = 0; + let refs = unsafe { + BNGetMediumLevelILVariableReferences( + self.get_function().handle, + &mut var.raw(), + &mut count, + ) + }; + assert!(!refs.is_null()); + unsafe { Array::new(refs, count, self.to_owned()) } + } + + /// Returns a list of variables referenced by code in the function ``func``, + /// of the architecture ``arch``, and at the address ``addr``. If no function is specified, references from + /// all functions and containing the address will be returned. If no architecture is specified, the + /// architecture of the function will be used. + /// This function is related to get_hlil_var_refs_from(), which returns variable references collected + /// from HLIL. The two can be different in several cases, e.g., multiple variables in MLIL can be merged + /// into a single variable in HLIL. + /// + /// * `addr` - virtual address to query for variable references + /// * `length` - optional length of query + /// * `arch` - optional architecture of query + pub fn var_refs_from( + &self, + addr: u64, + length: Option, + arch: Option, + ) -> Array { + let function = self.get_function(); + let arch = arch.unwrap_or_else(|| function.arch()); + let mut count = 0; + + let refs = if let Some(length) = length { + unsafe { + BNGetMediumLevelILVariableReferencesInRange( + function.handle, + arch.0, + addr, + length, + &mut count, + ) + } + } else { + unsafe { + BNGetMediumLevelILVariableReferencesFrom(function.handle, arch.0, addr, &mut count) + } + }; + assert!(!refs.is_null()); + unsafe { Array::new(refs, count, self.to_owned()) } + } + + /// Current IL Address + pub fn current_address(&self) -> u64 { + unsafe { BNMediumLevelILGetCurrentAddress(self.handle) } + } + + /// Set the current IL Address + pub fn set_current_address(&self, value: u64, arch: Option) { + let arch = arch + .map(|x| x.0) + .unwrap_or_else(|| self.get_function().arch().0); + unsafe { BNMediumLevelILSetCurrentAddress(self.handle, arch, value) } + } + + /// Returns the BasicBlock at the given MLIL `instruction`. + pub fn basic_block_containing( + &self, + instruction: &MediumLevelILInstruction, + ) -> Option> { + let index = instruction.index; + let block = unsafe { BNGetMediumLevelILBasicBlockForInstruction(self.handle, index) }; + (!block.is_null()).then(|| unsafe { + BasicBlock::from_raw( + block, + MediumLevelILBlock { + function: self.to_owned(), + }, + ) + }) + } + /// ends the function and computes the list of basic blocks. + pub fn finalize(&self) { + unsafe { BNFinalizeMediumLevelILFunction(self.handle) } + } + + /// Generate SSA form given the current MLIL + /// + /// * `analyze_conditionals` - whether or not to analyze conditionals + /// * `handle_aliases` - whether or not to handle aliases + /// * `known_not_aliases` - optional list of variables known to be not aliased + /// * `known_aliases` - optional list of variables known to be aliased + pub fn generate_ssa_form( + &self, + analyze_conditionals: bool, + handle_aliases: bool, + known_not_aliases: impl IntoIterator, + known_aliases: impl IntoIterator, + ) { + let mut known_not_aliases: Box<[_]> = + known_not_aliases.into_iter().map(|x| x.raw()).collect(); + let mut known_aliases: Box<[_]> = known_aliases.into_iter().map(|x| x.raw()).collect(); + let (known_not_aliases_ptr, known_not_aliases_len) = if known_not_aliases.is_empty() { + (core::ptr::null_mut(), 0) + } else { + (known_not_aliases.as_mut_ptr(), known_not_aliases.len()) + }; + let (known_aliases_ptr, known_aliases_len) = if known_not_aliases.is_empty() { + (core::ptr::null_mut(), 0) + } else { + (known_aliases.as_mut_ptr(), known_aliases.len()) + }; + unsafe { + BNGenerateMediumLevelILSSAForm( + self.handle, + analyze_conditionals, + handle_aliases, + known_not_aliases_ptr, + known_not_aliases_len, + known_aliases_ptr, + known_aliases_len, + ) + } + } + + /// Gets the instruction that contains the given SSA variable's definition. + /// + /// Since SSA variables can only be defined once, this will return the single instruction where that occurs. + /// For SSA variable version 0s, which don't have definitions, this will return None instead. + pub fn ssa_variable_definition(&self, var: SSAVariable) -> Option { + let result = unsafe { + BNGetMediumLevelILSSAVarDefinition(self.handle, &var.variable.raw(), var.version) + }; + (result < self.instruction_count()) + .then(|| MediumLevelILInstruction::new(self.to_owned(), result)) + } + + pub fn ssa_memory_definition(&self, version: usize) -> Option { + let result = unsafe { BNGetMediumLevelILSSAMemoryDefinition(self.handle, version) }; + (result < self.instruction_count()) + .then(|| MediumLevelILInstruction::new(self.to_owned(), result)) + } + + ///Gets all the instructions that use the given SSA variable. + pub fn ssa_variable_uses(&self, ssa_var: SSAVariable) -> Array { + let mut count = 0; + let uses = unsafe { + BNGetMediumLevelILSSAVarUses( + self.handle, + &ssa_var.variable.raw(), + ssa_var.version, + &mut count, + ) + }; + assert!(!uses.is_null()); + unsafe { Array::new(uses, count, self.to_owned()) } + } + + pub fn ssa_memory_uses(&self, version: usize) -> Array { + let mut count = 0; + let uses = unsafe { BNGetMediumLevelILSSAMemoryUses(self.handle, version, &mut count) }; + assert!(!uses.is_null()); + unsafe { Array::new(uses, count, self.to_owned()) } + } + + /// determines if `ssa_var` is live at any point in the function + pub fn is_ssa_variable_live(&self, ssa_var: SSAVariable) -> bool { + unsafe { + BNIsMediumLevelILSSAVarLive(self.handle, &ssa_var.variable.raw(), ssa_var.version) + } + } + + pub fn variable_definitions(&self, variable: Variable) -> Array { + let mut count = 0; + let defs = unsafe { + BNGetMediumLevelILVariableDefinitions(self.handle, &variable.raw(), &mut count) + }; + unsafe { Array::new(defs, count, self.to_owned()) } + } + + pub fn variable_uses(&self, variable: Variable) -> Array { + let mut count = 0; + let uses = + unsafe { BNGetMediumLevelILVariableUses(self.handle, &variable.raw(), &mut count) }; + unsafe { Array::new(uses, count, self.to_owned()) } + } + + /// Computes the list of instructions for which `var` is live. + /// If `include_last_use` is false, the last use of the variable will not be included in the + /// list (this allows for easier computation of overlaps in liveness between two variables). + /// If the variable is never used, this function will return an empty list. + /// + /// `var` - the variable to query + /// `include_last_use` - whether to include the last use of the variable in the list of instructions + pub fn live_instruction_for_variable( + &self, + variable: Variable, + include_last_user: bool, + ) -> Array { + let mut count = 0; + let uses = unsafe { + BNGetMediumLevelILLiveInstructionsForVariable( + self.handle, + &variable.raw(), + include_last_user, + &mut count, + ) + }; + unsafe { Array::new(uses, count, self.to_owned()) } + } + + pub fn ssa_variable_value(&self, ssa_var: SSAVariable) -> RegisterValue { + unsafe { + BNGetMediumLevelILSSAVarValue(self.handle, &ssa_var.variable.raw(), ssa_var.version) + } + .into() + } + + pub fn create_graph(&self, settings: Option) -> FlowGraph { + let settings = settings.map(|x| x.handle).unwrap_or(core::ptr::null_mut()); + let graph = unsafe { BNCreateMediumLevelILFunctionGraph(self.handle, settings) }; + unsafe { FlowGraph::from_raw(graph) } + } + + /// This gets just the MLIL variables - you may be interested in the union + /// of [MediumLevelILFunction::aliased_variables] and + /// [crate::function::Function::parameter_variables] for all the + /// variables used in the function + pub fn variables(&self) -> Array { + let mut count = 0; + let uses = unsafe { BNGetMediumLevelILVariables(self.handle, &mut count) }; + unsafe { Array::new(uses, count, ()) } + } + + /// This returns a list of Variables that are taken reference to and used + /// elsewhere. You may also wish to consider [MediumLevelILFunction::variables] + /// and [crate::function::Function::parameter_variables] + pub fn aliased_variables(&self) -> Array { + let mut count = 0; + let uses = unsafe { BNGetMediumLevelILAliasedVariables(self.handle, &mut count) }; + unsafe { Array::new(uses, count, ()) } + } + + /// This gets just the MLIL SSA variables - you may be interested in the + /// union of [MediumLevelILFunction::aliased_variables] and + /// [crate::function::Function::parameter_variables] for all the + /// variables used in the function. + pub fn ssa_variables(&self) -> Array> { + let mut count = 0; + let vars = unsafe { BNGetMediumLevelILVariables(self.handle, &mut count) }; + unsafe { Array::new(vars, count, self.to_owned()) } + } } impl ToOwned for MediumLevelILFunction { @@ -118,3 +603,125 @@ impl core::fmt::Debug for MediumLevelILFunction { write!(f, "", self.handle) } } + +#[derive(Clone, Debug)] +pub struct MediumLevelILInstructionList<'a> { + mlil: &'a MediumLevelILFunction, + ptr: *mut usize, + instr_idxs: core::slice::Iter<'a, usize>, +} + +impl Drop for MediumLevelILInstructionList<'_> { + fn drop(&mut self) { + unsafe { BNFreeILInstructionList(self.ptr) }; + } +} + +impl Iterator for MediumLevelILInstructionList<'_> { + type Item = MediumLevelILInstruction; + + fn next(&mut self) -> Option { + self.instr_idxs + .next() + .map(|i| self.mlil.instruction_from_instruction_idx(*i)) + } +} + +impl DoubleEndedIterator for MediumLevelILInstructionList<'_> { + fn next_back(&mut self) -> Option { + self.instr_idxs + .next_back() + .map(|i| self.mlil.instruction_from_instruction_idx(*i)) + } +} + +impl ExactSizeIterator for MediumLevelILInstructionList<'_> {} +impl core::iter::FusedIterator for MediumLevelILInstructionList<'_> {} + +///////////////////////// +// FunctionGraphType + +pub type FunctionGraphType = binaryninjacore_sys::BNFunctionGraphType; + +///////////////////////// +// ILReferenceSource + +pub struct ILReferenceSource { + mlil: Ref, + _func: Ref, + _arch: CoreArchitecture, + addr: u64, + type_: FunctionGraphType, + expr_id: usize, +} + +impl ILReferenceSource { + unsafe fn from_raw(value: BNILReferenceSource, mlil: Ref) -> Self { + Self { + mlil, + _func: Function::from_raw(value.func), + _arch: CoreArchitecture::from_raw(value.arch), + addr: value.addr, + type_: value.type_, + expr_id: value.exprId, + } + } + pub fn addr(&self) -> u64 { + self.addr + } + pub fn graph_type(&self) -> FunctionGraphType { + self.type_ + } + pub fn expr(&self) -> MediumLevelILInstruction { + self.mlil.instruction_from_idx(self.expr_id) + } +} + +impl CoreArrayProvider for ILReferenceSource { + type Raw = BNILReferenceSource; + type Context = Ref; + type Wrapped<'a> = Self; +} +unsafe impl CoreArrayProviderInner for ILReferenceSource { + unsafe fn free(raw: *mut Self::Raw, count: usize, _context: &Self::Context) { + BNFreeILReferences(raw, count) + } + unsafe fn wrap_raw<'a>(raw: &'a Self::Raw, context: &'a Self::Context) -> Self::Wrapped<'a> { + Self::from_raw(*raw, context.to_owned()) + } +} + +///////////////////////// +// VariableReferenceSource + +pub struct VariableReferenceSource { + var: Variable, + source: ILReferenceSource, +} + +impl VariableReferenceSource { + pub fn variable(&self) -> &Variable { + &self.var + } + pub fn source(&self) -> &ILReferenceSource { + &self.source + } +} + +impl CoreArrayProvider for VariableReferenceSource { + type Raw = BNVariableReferenceSource; + type Context = Ref; + type Wrapped<'a> = Self; +} + +unsafe impl CoreArrayProviderInner for VariableReferenceSource { + unsafe fn free(raw: *mut Self::Raw, count: usize, _context: &Self::Context) { + BNFreeVariableReferenceSourceList(raw, count) + } + unsafe fn wrap_raw<'a>(raw: &'a Self::Raw, context: &'a Self::Context) -> Self::Wrapped<'a> { + Self { + var: Variable::from_raw(raw.var), + source: ILReferenceSource::from_raw(raw.source, context.to_owned()), + } + } +} diff --git a/src/mlil/instruction.rs b/src/mlil/instruction.rs index e457c93..88564cc 100644 --- a/src/mlil/instruction.rs +++ b/src/mlil/instruction.rs @@ -1,12 +1,12 @@ -use binaryninjacore_sys::BNFromVariableIdentifier; -use binaryninjacore_sys::BNGetMediumLevelILByIndex; -use binaryninjacore_sys::BNMediumLevelILInstruction; -use binaryninjacore_sys::BNMediumLevelILOperation; +use binaryninjacore_sys::*; +use crate::architecture::CoreIntrinsic; +use crate::disassembly::InstructionTextToken; use crate::operand_iter::OperandIter; -use crate::rc::Ref; +use crate::rc::{Array, CoreArrayProvider, CoreArrayProviderInner, Ref}; use crate::types::{ - ConstantData, ILIntrinsic, RegisterValue, RegisterValueType, SSAVariable, Variable, + Conf, ConstantData, DataFlowQueryOption, ILBranchDependence, PossibleValueSet, + RegisterValue, RegisterValueType, SSAVariable, Type, Variable, }; use super::lift::*; @@ -17,6 +17,8 @@ use super::MediumLevelILFunction; pub struct MediumLevelILInstruction { pub function: Ref, pub address: u64, + pub index: usize, + pub size: usize, pub kind: MediumLevelILInstructionKind, } @@ -166,8 +168,8 @@ impl core::fmt::Debug for MediumLevelILInstruction { } impl MediumLevelILInstruction { - pub(crate) fn new(function: Ref, idx: usize) -> Self { - let op = unsafe { BNGetMediumLevelILByIndex(function.handle, idx) }; + pub(crate) fn new(function: Ref, index: usize) -> Self { + let op = unsafe { BNGetMediumLevelILByIndex(function.handle, index) }; use BNMediumLevelILOperation::*; use MediumLevelILInstructionKind as Op; let kind = match op.operation { @@ -703,7 +705,12 @@ impl MediumLevelILInstruction { }), // translated directly into a list for Expression or Variables // TODO MLIL_MEMORY_INTRINSIC_SSA needs to be handled properly - MLIL_CALL_OUTPUT | MLIL_CALL_PARAM | MLIL_CALL_PARAM_SSA | MLIL_CALL_OUTPUT_SSA | MLIL_MEMORY_INTRINSIC_OUTPUT_SSA | MLIL_MEMORY_INTRINSIC_SSA => { + MLIL_CALL_OUTPUT + | MLIL_CALL_PARAM + | MLIL_CALL_PARAM_SSA + | MLIL_CALL_OUTPUT_SSA + | MLIL_MEMORY_INTRINSIC_OUTPUT_SSA + | MLIL_MEMORY_INTRINSIC_SSA => { unreachable!() } }; @@ -711,6 +718,8 @@ impl MediumLevelILInstruction { Self { function, address: op.address, + index, + size: op.size, kind, } } @@ -897,7 +906,7 @@ impl MediumLevelILInstruction { output: OperandIter::new(&*self.function, op.first_output, op.num_outputs) .vars() .collect(), - intrinsic: ILIntrinsic::new(self.function.get_function().arch(), op.intrinsic), + intrinsic: CoreIntrinsic(self.function.get_function().arch().0, op.intrinsic), params: OperandIter::new(&*self.function, op.first_param, op.num_params) .exprs() .map(|expr| expr.lift()) @@ -916,7 +925,7 @@ impl MediumLevelILInstruction { output: OperandIter::new(&*self.function, op.first_output, op.num_outputs) .ssa_vars() .collect(), - intrinsic: ILIntrinsic::new(self.function.get_function().arch(), op.intrinsic), + intrinsic: CoreIntrinsic(self.function.get_function().arch().0, op.intrinsic), params: OperandIter::new(&*self.function, op.first_param, op.num_params) .exprs() .map(|expr| expr.lift()) @@ -1019,10 +1028,394 @@ impl MediumLevelILInstruction { MediumLevelILLiftedInstruction { function: self.function.clone(), address: self.address, + index: self.index, + size: self.size, kind, } } + pub fn tokens(&self) -> Array { + let mut count = 0; + let mut tokens = core::ptr::null_mut(); + assert!(unsafe { + BNGetMediumLevelILExprText( + self.function.handle, + self.function.get_function().arch().0, + self.index, + &mut tokens, + &mut count, + core::ptr::null_mut(), + ) + }); + unsafe { Array::new(tokens, count, ()) } + } + + /// Value of expression if constant or a known value + pub fn value(&self) -> RegisterValue { + unsafe { BNGetMediumLevelILExprValue(self.function.handle, self.index) }.into() + } + + /// Possible values of expression using path-sensitive static data flow analysis + pub fn possible_values(&self, options: Option<&[DataFlowQueryOption]>) -> PossibleValueSet { + let options_ptr = options + .map(|op| op.as_ptr() as *mut DataFlowQueryOption) + .unwrap_or(core::ptr::null_mut()); + let options_len = options.map(|op| op.len()).unwrap_or(0); + let mut value = unsafe { + BNGetMediumLevelILPossibleExprValues( + self.function.handle, + self.index, + options_ptr, + options_len, + ) + }; + let result = unsafe { PossibleValueSet::from_raw(value) }; + unsafe { BNFreePossibleValueSet(&mut value) } + result + } + + pub fn possible_ssa_variable_values( + &self, + ssa_var: SSAVariable, + options: Option<&[DataFlowQueryOption]>, + ) -> PossibleValueSet { + let options_ptr = options + .map(|op| op.as_ptr() as *mut DataFlowQueryOption) + .unwrap_or(core::ptr::null_mut()); + let options_len = options.map(|op| op.len()).unwrap_or(0); + let mut value = unsafe { + BNGetMediumLevelILPossibleSSAVarValues( + self.function.handle, + &ssa_var.variable.raw(), + ssa_var.version, + self.index, + options_ptr, + options_len, + ) + }; + let result = unsafe { PossibleValueSet::from_raw(value) }; + unsafe { BNFreePossibleValueSet(&mut value) } + result + } + + /// return the variable version used at this instruction + pub fn ssa_variable_version(&self, var: Variable) -> SSAVariable { + let version = unsafe { + BNGetMediumLevelILSSAVarVersionAtILInstruction( + self.function.handle, + &var.raw(), + self.index, + ) + }; + SSAVariable::new(var, version) + } + + /// Set of branching instructions that must take the true or false path to reach this instruction + pub fn branch_dependence(&self) -> Array { + let mut count = 0; + let deps = unsafe { + BNGetAllMediumLevelILBranchDependence(self.function.handle, self.index, &mut count) + }; + assert!(!deps.is_null()); + unsafe { Array::new(deps, count, self.function.clone()) } + } + + pub fn branch_dependence_at(&self, instruction: MediumLevelILInstruction) -> BranchDependence { + let deps = unsafe { + BNGetMediumLevelILBranchDependence(self.function.handle, self.index, instruction.index) + }; + BranchDependence { + instruction, + dependence: deps, + } + } + + /// Version of active memory contents in SSA form for this instruction + pub fn ssa_memory_version(&self) -> usize { + unsafe { + BNGetMediumLevelILSSAMemoryVersionAtILInstruction(self.function.handle, self.index) + } + } + + /// Type of expression + pub fn expr_type(&self) -> Option>> { + let result = unsafe { BNGetMediumLevelILExprType(self.function.handle, self.index) }; + (!result.type_.is_null()).then(|| { + Conf::new( + unsafe { Type::ref_from_raw(result.type_) }, + result.confidence, + ) + }) + } + + /// Set type of expression + /// + /// This API is only meant for workflows or for debugging purposes, since the changes they make are not persistent + /// and get lost after a database save and reload. To make persistent changes to the analysis, one should use other + /// APIs to, for example, change the type of variables. The analysis will then propagate the type of the variable + /// and update the type of related expressions. + pub fn set_expr_type<'a, T: Into>>(&self, value: T) { + let type_: Conf<&'a Type> = value.into(); + let mut type_raw: BNTypeWithConfidence = BNTypeWithConfidence { + type_: type_.contents.handle, + confidence: type_.confidence, + }; + unsafe { BNSetMediumLevelILExprType(self.function.handle, self.index, &mut type_raw) } + } + + pub fn variable_for_register(&self, reg_id: u32) -> Variable { + let result = unsafe { + BNGetMediumLevelILVariableForRegisterAtInstruction( + self.function.handle, + reg_id, + self.index, + ) + }; + unsafe { Variable::from_raw(result) } + } + + pub fn variable_for_flag(&self, flag_id: u32) -> Variable { + let result = unsafe { + BNGetMediumLevelILVariableForFlagAtInstruction( + self.function.handle, + flag_id, + self.index, + ) + }; + unsafe { Variable::from_raw(result) } + } + + pub fn variable_for_stack_location(&self, offset: i64) -> Variable { + let result = unsafe { + BNGetMediumLevelILVariableForStackLocationAtInstruction( + self.function.handle, + offset, + self.index, + ) + }; + unsafe { Variable::from_raw(result) } + } + + pub fn register_value(&self, reg_id: u32) -> RegisterValue { + unsafe { + BNGetMediumLevelILRegisterValueAtInstruction(self.function.handle, reg_id, self.index) + } + .into() + } + + pub fn register_value_after(&self, reg_id: u32) -> RegisterValue { + unsafe { + BNGetMediumLevelILRegisterValueAfterInstruction( + self.function.handle, + reg_id, + self.index, + ) + } + .into() + } + + pub fn possible_register_values( + &self, + reg_id: u32, + options: Option<&[DataFlowQueryOption]>, + ) -> PossibleValueSet { + let options_ptr = options + .map(|op| op.as_ptr() as *mut DataFlowQueryOption) + .unwrap_or(core::ptr::null_mut()); + let options_len = options.map(|op| op.len()).unwrap_or(0); + let mut value = unsafe { + BNGetMediumLevelILPossibleRegisterValuesAtInstruction( + self.function.handle, + reg_id, + self.index, + options_ptr, + options_len, + ) + }; + let result = unsafe { PossibleValueSet::from_raw(value) }; + unsafe { BNFreePossibleValueSet(&mut value) } + result + } + + pub fn possible_register_values_after( + &self, + reg_id: u32, + options: Option<&[DataFlowQueryOption]>, + ) -> PossibleValueSet { + let options_ptr = options + .map(|op| op.as_ptr() as *mut DataFlowQueryOption) + .unwrap_or(core::ptr::null_mut()); + let options_len = options.map(|op| op.len()).unwrap_or(0); + let mut value = unsafe { + BNGetMediumLevelILPossibleRegisterValuesAfterInstruction( + self.function.handle, + reg_id, + self.index, + options_ptr, + options_len, + ) + }; + let result = unsafe { PossibleValueSet::from_raw(value) }; + unsafe { BNFreePossibleValueSet(&mut value) } + result + } + + pub fn flag_value(&self, flag_id: u32) -> RegisterValue { + unsafe { + BNGetMediumLevelILFlagValueAtInstruction(self.function.handle, flag_id, self.index) + } + .into() + } + + pub fn flag_value_after(&self, flag_id: u32) -> RegisterValue { + unsafe { + BNGetMediumLevelILFlagValueAfterInstruction(self.function.handle, flag_id, self.index) + } + .into() + } + + pub fn possible_flag_values( + &self, + flag_id: u32, + options: Option<&[DataFlowQueryOption]>, + ) -> PossibleValueSet { + let options_ptr = options + .map(|op| op.as_ptr() as *mut DataFlowQueryOption) + .unwrap_or(core::ptr::null_mut()); + let options_len = options.map(|op| op.len()).unwrap_or(0); + let mut value = unsafe { + BNGetMediumLevelILPossibleFlagValuesAtInstruction( + self.function.handle, + flag_id, + self.index, + options_ptr, + options_len, + ) + }; + let result = unsafe { PossibleValueSet::from_raw(value) }; + unsafe { BNFreePossibleValueSet(&mut value) } + result + } + + pub fn possible_flag_values_after( + &self, + flag_id: u32, + options: Option<&[DataFlowQueryOption]>, + ) -> PossibleValueSet { + let options_ptr = options + .map(|op| op.as_ptr() as *mut DataFlowQueryOption) + .unwrap_or(core::ptr::null_mut()); + let options_len = options.map(|op| op.len()).unwrap_or(0); + let mut value = unsafe { + BNGetMediumLevelILPossibleFlagValuesAfterInstruction( + self.function.handle, + flag_id, + self.index, + options_ptr, + options_len, + ) + }; + let result = unsafe { PossibleValueSet::from_raw(value) }; + unsafe { BNFreePossibleValueSet(&mut value) } + result + } + + pub fn stack_contents(&self, offset: i64, size: usize) -> RegisterValue { + unsafe { + BNGetMediumLevelILStackContentsAtInstruction( + self.function.handle, + offset, + size, + self.index, + ) + } + .into() + } + + pub fn stack_contents_after(&self, offset: i64, size: usize) -> RegisterValue { + unsafe { + BNGetMediumLevelILStackContentsAfterInstruction( + self.function.handle, + offset, + size, + self.index, + ) + } + .into() + } + + pub fn possible_stack_contents( + &self, + offset: i64, + size: usize, + options: Option<&[DataFlowQueryOption]>, + ) -> PossibleValueSet { + let options_ptr = options + .map(|op| op.as_ptr() as *mut DataFlowQueryOption) + .unwrap_or(core::ptr::null_mut()); + let options_len = options.map(|op| op.len()).unwrap_or(0); + let mut value = unsafe { + BNGetMediumLevelILPossibleStackContentsAtInstruction( + self.function.handle, + offset, + size, + self.index, + options_ptr, + options_len, + ) + }; + let result = unsafe { PossibleValueSet::from_raw(value) }; + unsafe { BNFreePossibleValueSet(&mut value) } + result + } + + pub fn possible_stack_contents_after( + &self, + offset: i64, + size: usize, + options: Option<&[DataFlowQueryOption]>, + ) -> PossibleValueSet { + let options_ptr = options + .map(|op| op.as_ptr() as *mut DataFlowQueryOption) + .unwrap_or(core::ptr::null_mut()); + let options_len = options.map(|op| op.len()).unwrap_or(0); + let mut value = unsafe { + BNGetMediumLevelILPossibleStackContentsAfterInstruction( + self.function.handle, + offset, + size, + self.index, + options_ptr, + options_len, + ) + }; + let result = unsafe { PossibleValueSet::from_raw(value) }; + unsafe { BNFreePossibleValueSet(&mut value) } + result + } + + /// Gets the unique variable for a definition instruction. This unique variable can be passed + /// to [crate::function::Function::split_variable] to split a variable at a definition. The given `var` is the + /// assigned variable to query. + /// + /// * `var` - variable to query + pub fn split_var_for_definition(&self, var: Variable) -> Variable { + let index = unsafe { + BNGetDefaultIndexForMediumLevelILVariableDefinition( + self.function.handle, + &var.raw(), + self.index, + ) + }; + Variable::new(var.t, index, var.storage) + } + + /// alias for [MediumLevelILInstruction::split_var_for_definition] + #[inline] + pub fn get_split_var_for_definition(&self, var: &Variable) -> Variable { + self.split_var_for_definition(*var) + } + fn lift_operand(&self, expr_idx: usize) -> Box { Box::new(self.function.lifted_instruction_from_idx(expr_idx)) } @@ -1096,6 +1489,22 @@ impl MediumLevelILInstruction { } } +impl CoreArrayProvider for MediumLevelILInstruction { + type Raw = usize; + type Context = Ref; + type Wrapped<'a> = Self; +} + +unsafe impl CoreArrayProviderInner for MediumLevelILInstruction { + unsafe fn free(raw: *mut Self::Raw, _count: usize, _context: &Self::Context) { + BNFreeILInstructionList(raw) + } + + unsafe fn wrap_raw<'a>(raw: &'a Self::Raw, context: &'a Self::Context) -> Self::Wrapped<'a> { + context.instruction_from_idx(*raw) + } +} + fn get_float(value: u64, size: usize) -> f64 { match size { 4 => f32::from_bits(value as u32) as f64, @@ -1110,7 +1519,7 @@ fn get_raw_operation(function: &MediumLevelILFunction, idx: usize) -> BNMediumLe } fn get_var(id: u64) -> Variable { - unsafe { Variable::from_raw(BNFromVariableIdentifier(id)) } + unsafe { Variable::from_identifier(id) } } fn get_var_ssa(id: u64, version: usize) -> SSAVariable { @@ -1149,3 +1558,28 @@ fn get_call_params_ssa( assert_eq!(op.operation, BNMediumLevelILOperation::MLIL_CALL_PARAM_SSA); OperandIter::new(function, op.operands[2] as usize, op.operands[1] as usize).exprs() } + +/// Conditional branching instruction and an expected conditional result +pub struct BranchDependence { + pub instruction: MediumLevelILInstruction, + pub dependence: ILBranchDependence, +} + +impl CoreArrayProvider for BranchDependence { + type Raw = BNILBranchInstructionAndDependence; + type Context = Ref; + type Wrapped<'a> = Self; +} + +unsafe impl CoreArrayProviderInner for BranchDependence { + unsafe fn free(raw: *mut Self::Raw, _count: usize, _context: &Self::Context) { + unsafe { BNFreeILBranchDependenceList(raw) }; + } + + unsafe fn wrap_raw<'a>(raw: &'a Self::Raw, context: &'a Self::Context) -> Self::Wrapped<'a> { + Self { + instruction: MediumLevelILInstruction::new(context.clone(), raw.branch), + dependence: raw.dependence, + } + } +} diff --git a/src/mlil/lift.rs b/src/mlil/lift.rs index ba434d1..7a5e159 100644 --- a/src/mlil/lift.rs +++ b/src/mlil/lift.rs @@ -1,7 +1,8 @@ use std::collections::BTreeMap; +use crate::architecture::CoreIntrinsic; use crate::rc::Ref; -use crate::types::{ConstantData, ILIntrinsic, SSAVariable, Variable}; +use crate::types::{ConstantData, SSAVariable, Variable}; use super::operation::*; use super::MediumLevelILFunction; @@ -9,7 +10,7 @@ use super::MediumLevelILFunction; #[derive(Clone)] pub enum MediumLevelILLiftedOperand { ConstantData(ConstantData), - Intrinsic(ILIntrinsic), + Intrinsic(CoreIntrinsic), Expr(MediumLevelILLiftedInstruction), ExprList(Vec), Float(f64), @@ -26,6 +27,8 @@ pub enum MediumLevelILLiftedOperand { pub struct MediumLevelILLiftedInstruction { pub function: Ref, pub address: u64, + pub index: usize, + pub size: usize, pub kind: MediumLevelILLiftedInstructionKind, } @@ -164,6 +167,142 @@ pub enum MediumLevelILLiftedInstructionKind { } impl MediumLevelILLiftedInstruction { + pub fn name(&self) -> &'static str { + use MediumLevelILLiftedInstructionKind::*; + match self.kind { + Nop => "Nop", + Noret => "Noret", + Bp => "Bp", + Undef => "Undef", + Unimpl => "Unimpl", + If(_) => "If", + FloatConst(_) => "FloatConst", + Const(_) => "Const", + ConstPtr(_) => "ConstPtr", + Import(_) => "Import", + ExternPtr(_) => "ExternPtr", + ConstData(_) => "ConstData", + Jump(_) => "Jump", + RetHint(_) => "RetHint", + StoreSsa(_) => "StoreSsa", + StoreStructSsa(_) => "StoreStructSsa", + StoreStruct(_) => "StoreStruct", + Store(_) => "Store", + JumpTo(_) => "JumpTo", + Goto(_) => "Goto", + FreeVarSlot(_) => "FreeVarSlot", + SetVarField(_) => "SetVarField", + SetVar(_) => "SetVar", + FreeVarSlotSsa(_) => "FreeVarSlotSsa", + SetVarSsaField(_) => "SetVarSsaField", + SetVarAliasedField(_) => "SetVarAliasedField", + SetVarAliased(_) => "SetVarAliased", + SetVarSsa(_) => "SetVarSsa", + VarPhi(_) => "VarPhi", + MemPhi(_) => "MemPhi", + VarSplit(_) => "VarSplit", + SetVarSplit(_) => "SetVarSplit", + VarSplitSsa(_) => "VarSplitSsa", + SetVarSplitSsa(_) => "SetVarSplitSsa", + Add(_) => "Add", + Sub(_) => "Sub", + And(_) => "And", + Or(_) => "Or", + Xor(_) => "Xor", + Lsl(_) => "Lsl", + Lsr(_) => "Lsr", + Asr(_) => "Asr", + Rol(_) => "Rol", + Ror(_) => "Ror", + Mul(_) => "Mul", + MuluDp(_) => "MuluDp", + MulsDp(_) => "MulsDp", + Divu(_) => "Divu", + DivuDp(_) => "DivuDp", + Divs(_) => "Divs", + DivsDp(_) => "DivsDp", + Modu(_) => "Modu", + ModuDp(_) => "ModuDp", + Mods(_) => "Mods", + ModsDp(_) => "ModsDp", + CmpE(_) => "CmpE", + CmpNe(_) => "CmpNe", + CmpSlt(_) => "CmpSlt", + CmpUlt(_) => "CmpUlt", + CmpSle(_) => "CmpSle", + CmpUle(_) => "CmpUle", + CmpSge(_) => "CmpSge", + CmpUge(_) => "CmpUge", + CmpSgt(_) => "CmpSgt", + CmpUgt(_) => "CmpUgt", + TestBit(_) => "TestBit", + AddOverflow(_) => "AddOverflow", + FcmpE(_) => "FcmpE", + FcmpNe(_) => "FcmpNe", + FcmpLt(_) => "FcmpLt", + FcmpLe(_) => "FcmpLe", + FcmpGe(_) => "FcmpGe", + FcmpGt(_) => "FcmpGt", + FcmpO(_) => "FcmpO", + FcmpUo(_) => "FcmpUo", + Fadd(_) => "Fadd", + Fsub(_) => "Fsub", + Fmul(_) => "Fmul", + Fdiv(_) => "Fdiv", + Adc(_) => "Adc", + Sbb(_) => "Sbb", + Rlc(_) => "Rlc", + Rrc(_) => "Rrc", + Call(_) => "Call", + Tailcall(_) => "Tailcall", + Syscall(_) => "Syscall", + Intrinsic(_) => "Intrinsic", + IntrinsicSsa(_) => "IntrinsicSsa", + CallSsa(_) => "CallSsa", + TailcallSsa(_) => "TailcallSsa", + CallUntypedSsa(_) => "CallUntypedSsa", + TailcallUntypedSsa(_) => "TailcallUntypedSsa", + SyscallSsa(_) => "SyscallSsa", + SyscallUntypedSsa(_) => "SyscallUntypedSsa", + CallUntyped(_) => "CallUntyped", + TailcallUntyped(_) => "TailcallUntyped", + SyscallUntyped(_) => "SyscallUntyped", + SeparateParamList(_) => "SeparateParamList", + SharedParamSlot(_) => "SharedParamSlot", + Neg(_) => "Neg", + Not(_) => "Not", + Sx(_) => "Sx", + Zx(_) => "Zx", + LowPart(_) => "LowPart", + BoolToInt(_) => "BoolToInt", + UnimplMem(_) => "UnimplMem", + Fsqrt(_) => "Fsqrt", + Fneg(_) => "Fneg", + Fabs(_) => "Fabs", + FloatToInt(_) => "FloatToInt", + IntToFloat(_) => "IntToFloat", + FloatConv(_) => "FloatConv", + RoundToInt(_) => "RoundToInt", + Floor(_) => "Floor", + Ceil(_) => "Ceil", + Ftrunc(_) => "Ftrunc", + Load(_) => "Load", + LoadStruct(_) => "LoadStruct", + LoadStructSsa(_) => "LoadStructSsa", + LoadSsa(_) => "LoadSsa", + Ret(_) => "Ret", + Var(_) => "Var", + AddressOf(_) => "AddressOf", + VarField(_) => "VarField", + AddressOfField(_) => "AddressOfField", + VarSsa(_) => "VarSsa", + VarAliased(_) => "VarAliased", + VarSsaField(_) => "VarSsaField", + VarAliasedField(_) => "VarAliasedField", + Trap(_) => "Trap", + } + } + pub fn operands(&self) -> Vec<(&'static str, MediumLevelILLiftedOperand)> { use MediumLevelILLiftedInstructionKind::*; use MediumLevelILLiftedOperand as Operand; diff --git a/src/mlil/operation.rs b/src/mlil/operation.rs index 822688a..278d1ed 100644 --- a/src/mlil/operation.rs +++ b/src/mlil/operation.rs @@ -1,6 +1,6 @@ use std::collections::BTreeMap; -use crate::types::{ConstantData, ILIntrinsic, SSAVariable, Variable}; +use crate::{architecture::CoreIntrinsic, types::{ConstantData, SSAVariable, Variable}}; use super::MediumLevelILLiftedInstruction; @@ -355,7 +355,7 @@ pub struct Intrinsic { #[derive(Clone, Debug, PartialEq)] pub struct LiftedIntrinsic { pub output: Vec, - pub intrinsic: ILIntrinsic, + pub intrinsic: CoreIntrinsic, pub params: Vec, } @@ -371,7 +371,7 @@ pub struct IntrinsicSsa { #[derive(Clone, Debug, PartialEq)] pub struct LiftedIntrinsicSsa { pub output: Vec, - pub intrinsic: ILIntrinsic, + pub intrinsic: CoreIntrinsic, pub params: Vec, } diff --git a/src/operand_iter.rs b/src/operand_iter.rs index 5f0fbd8..0d24fd4 100644 --- a/src/operand_iter.rs +++ b/src/operand_iter.rs @@ -1,4 +1,3 @@ -use binaryninjacore_sys::BNFromVariableIdentifier; use binaryninjacore_sys::BNGetHighLevelILByIndex; use binaryninjacore_sys::BNGetMediumLevelILByIndex; use binaryninjacore_sys::BNHighLevelILOperation; @@ -215,7 +214,7 @@ impl ExactSizeIterator for OperandSSAVarIter { } pub fn get_var(id: u64) -> Variable { - unsafe { Variable::from_raw(BNFromVariableIdentifier(id)) } + unsafe { Variable::from_identifier(id) } } pub fn get_var_ssa(id: u64, version: usize) -> SSAVariable { diff --git a/src/platform.rs b/src/platform.rs index 3df5e7c..4e31a0a 100644 --- a/src/platform.rs +++ b/src/platform.rs @@ -23,6 +23,7 @@ use crate::{ callingconvention::CallingConvention, rc::*, string::*, + typelibrary::TypeLibrary, types::{QualifiedName, QualifiedNameAndType, Type}, }; @@ -163,6 +164,15 @@ impl Platform { unsafe { CoreArchitecture::from_raw(BNGetPlatformArchitecture(self.handle)) } } + pub fn get_type_libraries_by_name(&self, name: &QualifiedName) -> Array { + let mut count = 0; + let result = unsafe { + BNGetPlatformTypeLibrariesByName(self.handle, &name.0 as *const _ as *mut _, &mut count) + }; + assert!(!result.is_null()); + unsafe { Array::new(result, count, ()) } + } + pub fn register_os(&self, os: S) { let os = os.into_bytes_with_nul(); @@ -365,18 +375,14 @@ unsafe impl RefCountable for Platform { impl CoreArrayProvider for Platform { type Raw = *mut BNPlatform; type Context = (); + type Wrapped<'a> = Guard<'a, Platform>; } -unsafe impl CoreOwnedArrayProvider for Platform { +unsafe impl CoreArrayProviderInner for Platform { unsafe fn free(raw: *mut *mut BNPlatform, count: usize, _context: &()) { BNFreePlatformList(raw, count); } -} - -unsafe impl<'a> CoreArrayWrapper<'a> for Platform { - type Wrapped = Guard<'a, Platform>; - - unsafe fn wrap_raw(raw: &'a *mut BNPlatform, context: &'a ()) -> Guard<'a, Platform> { + unsafe fn wrap_raw<'a>(raw: &'a *mut BNPlatform, context: &'a ()) -> Self::Wrapped<'a> { debug_assert!(!raw.is_null()); Guard::new(Platform { handle: *raw }, context) } diff --git a/src/project.rs b/src/project.rs new file mode 100644 index 0000000..f08ad63 --- /dev/null +++ b/src/project.rs @@ -0,0 +1,1464 @@ +use std::ptr::{null_mut, NonNull}; +use std::time::{Duration, SystemTime, UNIX_EPOCH}; +use std::{ffi, mem}; + +use binaryninjacore_sys::*; + +use crate::metadata::Metadata; +use crate::rc::{Array, CoreArrayProvider, CoreArrayProviderInner, Ref}; +use crate::string::{BnStrCompatible, BnString}; + +#[repr(C)] +pub struct Project { + handle: NonNull, +} + +impl Project { + pub(crate) unsafe fn from_raw(handle: NonNull) -> Self { + Project { handle } + } + + pub(crate) unsafe fn ref_from_raw(handle: &*mut BNProject) -> &Self { + debug_assert!(!handle.is_null()); + mem::transmute(handle) + } + + #[allow(clippy::mut_from_ref)] + pub(crate) unsafe fn as_raw(&self) -> &mut BNProject { + &mut *self.handle.as_ptr() + } + + pub fn all_open() -> Array { + let mut count = 0; + let result = unsafe { BNGetOpenProjects(&mut count) }; + assert!(!result.is_null()); + unsafe { Array::new(result, count, ()) } + } + + /// Create a new project + /// + /// * `path` - Path to the project directory (.bnpr) + /// * `name` - Name of the new project + pub fn create(path: P, name: S) -> Self { + let path_raw = path.into_bytes_with_nul(); + let name_raw = name.into_bytes_with_nul(); + let handle = unsafe { + BNCreateProject( + path_raw.as_ref().as_ptr() as *const ffi::c_char, + name_raw.as_ref().as_ptr() as *const ffi::c_char, + ) + }; + unsafe { Self::from_raw(NonNull::new(handle).unwrap()) } + } + + /// Open an existing project + /// + /// * `path` - Path to the project directory (.bnpr) or project metadata file (.bnpm) + pub fn open_project(path: P) -> Self { + let path_raw = path.into_bytes_with_nul(); + let handle = unsafe { BNOpenProject(path_raw.as_ref().as_ptr() as *const ffi::c_char) }; + unsafe { Self::from_raw(NonNull::new(handle).unwrap()) } + } + + /// Check if the project is currently open + pub fn is_open(&self) -> bool { + unsafe { BNProjectIsOpen(self.as_raw()) } + } + + /// Open a closed project + pub fn open(&self) -> Result<(), ()> { + if unsafe { BNProjectOpen(self.as_raw()) } { + Ok(()) + } else { + Err(()) + } + } + + /// Close a open project + pub fn close(&self) -> Result<(), ()> { + if unsafe { BNProjectClose(self.as_raw()) } { + Ok(()) + } else { + Err(()) + } + } + + /// Get the unique id of this project + pub fn id(&self) -> BnString { + unsafe { BnString::from_raw(BNProjectGetId(self.as_raw())) } + } + + /// Get the path of the project + pub fn path(&self) -> BnString { + unsafe { BnString::from_raw(BNProjectGetPath(self.as_raw())) } + } + + /// Get the name of the project + pub fn name(&self) -> BnString { + unsafe { BnString::from_raw(BNProjectGetName(self.as_raw())) } + } + + /// Set the name of the project + pub fn set_name(&self, value: S) { + let value = value.into_bytes_with_nul(); + unsafe { BNProjectSetName(self.as_raw(), value.as_ref().as_ptr() as *const ffi::c_char) } + } + + /// Get the description of the project + pub fn description(&self) -> BnString { + unsafe { BnString::from_raw(BNProjectGetDescription(self.as_raw())) } + } + + /// Set the description of the project + pub fn set_description(&self, value: S) { + let value = value.into_bytes_with_nul(); + unsafe { + BNProjectSetDescription(self.as_raw(), value.as_ref().as_ptr() as *const ffi::c_char) + } + } + + /// Retrieves metadata stored under a key from the project + pub fn query_metadata(&self, key: S) -> Ref { + let key = key.into_bytes_with_nul(); + let result = unsafe { + BNProjectQueryMetadata(self.as_raw(), key.as_ref().as_ptr() as *const ffi::c_char) + }; + unsafe { Metadata::ref_from_raw(result) } + } + + /// Stores metadata within the project, + /// + /// * `key` - Key under which to store the Metadata object + /// * `value` - Object to store + pub fn store_metadata(&self, key: S, value: &Metadata) -> bool { + let key_raw = key.into_bytes_with_nul(); + unsafe { + BNProjectStoreMetadata( + self.as_raw(), + key_raw.as_ref().as_ptr() as *const ffi::c_char, + value.handle, + ) + } + } + + /// Removes the metadata associated with this `key` from the project + pub fn remove_metadata(&self, key: S) { + let key_raw = key.into_bytes_with_nul(); + unsafe { + BNProjectRemoveMetadata( + self.as_raw(), + key_raw.as_ref().as_ptr() as *const ffi::c_char, + ) + } + } + + pub fn push_folder(&self, file: &ProjectFolder) { + unsafe { BNProjectPushFolder(self.as_raw(), file.as_raw()) } + } + + /// Recursively create files and folders in the project from a path on disk + /// + /// * `path` - Path to folder on disk + /// * `parent` - Parent folder in the project that will contain the new contents + /// * `description` - Description for created root folder + pub fn create_folder_from_path( + &self, + path: P, + parent: Option<&ProjectFolder>, + description: D, + ) -> Result + where + P: BnStrCompatible, + D: BnStrCompatible, + { + let path_raw = path.into_bytes_with_nul(); + let description_raw = description.into_bytes_with_nul(); + let parent_ptr = parent + .map(|p| unsafe { p.as_raw() as *mut _ }) + .unwrap_or(null_mut()); + + unsafe { + let result = BNProjectCreateFolderFromPath( + self.as_raw(), + path_raw.as_ref().as_ptr() as *const ffi::c_char, + parent_ptr, + description_raw.as_ref().as_ptr() as *const ffi::c_char, + null_mut(), + Some(cb_progress_func_nop), + ); + Ok(ProjectFolder::from_raw(NonNull::new(result).ok_or(())?)) + } + } + + /// Recursively create files and folders in the project from a path on disk + /// + /// * `path` - Path to folder on disk + /// * `parent` - Parent folder in the project that will contain the new contents + /// * `description` - Description for created root folder + /// * `progress_func` - Progress function that will be called + pub fn create_folder_from_path_with_progress( + &self, + path: P, + parent: Option<&ProjectFolder>, + description: D, + mut progress_func: F, + ) -> Result + where + P: BnStrCompatible, + D: BnStrCompatible, + F: FnMut(usize, usize) -> bool, + { + let path_raw = path.into_bytes_with_nul(); + let description_raw = description.into_bytes_with_nul(); + let parent_ptr = parent + .map(|p| unsafe { p.as_raw() as *mut _ }) + .unwrap_or(null_mut()); + + let progress_ctx = &mut progress_func as *mut F as *mut ffi::c_void; + unsafe { + let result = BNProjectCreateFolderFromPath( + self.as_raw(), + path_raw.as_ref().as_ptr() as *const ffi::c_char, + parent_ptr, + description_raw.as_ref().as_ptr() as *const ffi::c_char, + progress_ctx, + Some(cb_progress_func::), + ); + Ok(ProjectFolder::from_raw(NonNull::new(result).ok_or(())?)) + } + } + + /// Recursively create files and folders in the project from a path on disk + /// + /// * `parent` - Parent folder in the project that will contain the new folder + /// * `name` - Name for the created folder + /// * `description` - Description for created folder + pub fn create_folder( + &self, + parent: Option<&ProjectFolder>, + name: N, + description: D, + ) -> Result + where + N: BnStrCompatible, + D: BnStrCompatible, + { + let name_raw = name.into_bytes_with_nul(); + let description_raw = description.into_bytes_with_nul(); + let parent_ptr = parent + .map(|p| unsafe { p.as_raw() as *mut _ }) + .unwrap_or(null_mut()); + unsafe { + let result = BNProjectCreateFolder( + self.as_raw(), + parent_ptr, + name_raw.as_ref().as_ptr() as *const ffi::c_char, + description_raw.as_ref().as_ptr() as *const ffi::c_char, + ); + Ok(ProjectFolder::from_raw(NonNull::new(result).ok_or(())?)) + } + } + + /// Recursively create files and folders in the project from a path on disk + /// + /// * `parent` - Parent folder in the project that will contain the new folder + /// * `name` - Name for the created folder + /// * `description` - Description for created folder + /// * `id` - id unique ID + pub unsafe fn create_folder_unsafe( + &self, + parent: Option<&ProjectFolder>, + name: N, + description: D, + id: I, + ) -> Result + where + N: BnStrCompatible, + D: BnStrCompatible, + I: BnStrCompatible, + { + let name_raw = name.into_bytes_with_nul(); + let description_raw = description.into_bytes_with_nul(); + let parent_ptr = parent + .map(|p| unsafe { p.as_raw() as *mut _ }) + .unwrap_or(null_mut()); + let id_raw = id.into_bytes_with_nul(); + unsafe { + let result = BNProjectCreateFolderUnsafe( + self.as_raw(), + parent_ptr, + name_raw.as_ref().as_ptr() as *const ffi::c_char, + description_raw.as_ref().as_ptr() as *const ffi::c_char, + id_raw.as_ref().as_ptr() as *const ffi::c_char, + ); + Ok(ProjectFolder::from_raw(NonNull::new(result).ok_or(())?)) + } + } + + /// Get a list of folders in the project + pub fn folders(&self) -> Result, ()> { + let mut count = 0; + let result = unsafe { BNProjectGetFolders(self.as_raw(), &mut count) }; + if result.is_null() { + return Err(()); + } + + Ok(unsafe { Array::new(result, count, ()) }) + } + + /// Retrieve a folder in the project by unique folder `id` + pub fn folder_by_id(&self, id: S) -> Option { + let id_raw = id.into_bytes_with_nul(); + let id_ptr = id_raw.as_ref().as_ptr() as *const ffi::c_char; + + let result = unsafe { BNProjectGetFolderById(self.as_raw(), id_ptr) }; + let handle = NonNull::new(result)?; + Some(unsafe { ProjectFolder::from_raw(handle) }) + } + + /// Recursively delete a folder from the project + /// + /// * `folder` - Folder to delete recursively + pub fn delete_folder(&self, folder: &ProjectFolder) -> Result<(), ()> { + let result = unsafe { + BNProjectDeleteFolder( + self.as_raw(), + folder.as_raw(), + null_mut(), + Some(cb_progress_func_nop), + ) + }; + if result { + Ok(()) + } else { + Err(()) + } + } + + /// Recursively delete a folder from the project + /// + /// * `folder` - Folder to delete recursively + /// * `progress_func` - Progress function that will be called as objects get deleted + pub fn delete_folder_with_progress( + &self, + folder: &ProjectFolder, + mut progress_func: F, + ) -> Result<(), ()> + where + F: FnMut(usize, usize) -> bool, + { + let progress_ctx = &mut progress_func as *mut F as *mut ffi::c_void; + let result = unsafe { + BNProjectDeleteFolder( + self.as_raw(), + folder.as_raw(), + progress_ctx, + Some(cb_progress_func::), + ) + }; + if result { + Ok(()) + } else { + Err(()) + } + } + + pub fn push_file(&self, file: &ProjectFile) { + unsafe { BNProjectPushFile(self.as_raw(), file.as_raw()) } + } + + /// Create a file in the project from a path on disk + /// + /// * `path` - Path on disk + /// * `folder` - Folder to place the created file in + /// * `name` - Name to assign to the created file + /// * `description` - Description to assign to the created file + pub fn create_file_from_path( + &self, + path: P, + folder: Option<&ProjectFolder>, + name: N, + description: D, + ) -> Result + where + P: BnStrCompatible, + N: BnStrCompatible, + D: BnStrCompatible, + { + let path_raw = path.into_bytes_with_nul(); + let name_raw = name.into_bytes_with_nul(); + let description_raw = description.into_bytes_with_nul(); + unsafe { + let result = BNProjectCreateFileFromPath( + self.as_raw(), + path_raw.as_ref().as_ptr() as *const ffi::c_char, + folder.map(|x| x.as_raw() as *mut _).unwrap_or(null_mut()), + name_raw.as_ref().as_ptr() as *const ffi::c_char, + description_raw.as_ref().as_ptr() as *const ffi::c_char, + null_mut(), + Some(cb_progress_func_nop), + ); + Ok(ProjectFile::from_raw(NonNull::new(result).ok_or(())?)) + } + } + + /// Create a file in the project from a path on disk + /// + /// * `path` - Path on disk + /// * `folder` - Folder to place the created file in + /// * `name` - Name to assign to the created file + /// * `description` - Description to assign to the created file + /// * `progress_func` - Progress function that will be called as the file is being added + pub fn create_file_from_path_with_progress( + &self, + path: P, + folder: Option<&ProjectFolder>, + name: N, + description: D, + mut progress_func: F, + ) -> Result + where + P: BnStrCompatible, + N: BnStrCompatible, + D: BnStrCompatible, + F: FnMut(usize, usize) -> bool, + { + let path_raw = path.into_bytes_with_nul(); + let name_raw = name.into_bytes_with_nul(); + let description_raw = description.into_bytes_with_nul(); + let progress_ctx = &mut progress_func as *mut F as *mut ffi::c_void; + unsafe { + let result = BNProjectCreateFileFromPath( + self.as_raw(), + path_raw.as_ref().as_ptr() as *const ffi::c_char, + folder.map(|x| x.as_raw() as *mut _).unwrap_or(null_mut()), + name_raw.as_ref().as_ptr() as *const ffi::c_char, + description_raw.as_ref().as_ptr() as *const ffi::c_char, + progress_ctx, + Some(cb_progress_func::), + ); + Ok(ProjectFile::from_raw(NonNull::new(result).ok_or(())?)) + } + } + + /// Create a file in the project from a path on disk + /// + /// * `path` - Path on disk + /// * `folder` - Folder to place the created file in + /// * `name` - Name to assign to the created file + /// * `description` - Description to assign to the created file + /// * `id` - id unique ID + /// * `creation_time` - Creation time of the file + pub unsafe fn create_file_from_path_unsafe( + &self, + path: P, + folder: Option<&ProjectFolder>, + name: N, + description: D, + id: I, + creation_time: SystemTime, + ) -> Result + where + P: BnStrCompatible, + N: BnStrCompatible, + D: BnStrCompatible, + I: BnStrCompatible, + { + let path_raw = path.into_bytes_with_nul(); + let name_raw = name.into_bytes_with_nul(); + let description_raw = description.into_bytes_with_nul(); + let id_raw = id.into_bytes_with_nul(); + unsafe { + let result = BNProjectCreateFileFromPathUnsafe( + self.as_raw(), + path_raw.as_ref().as_ptr() as *const ffi::c_char, + folder.map(|x| x.as_raw() as *mut _).unwrap_or(null_mut()), + name_raw.as_ref().as_ptr() as *const ffi::c_char, + description_raw.as_ref().as_ptr() as *const ffi::c_char, + id_raw.as_ref().as_ptr() as *const ffi::c_char, + systime_to_bntime(creation_time).unwrap(), + null_mut(), + Some(cb_progress_func_nop), + ); + Ok(ProjectFile::from_raw(NonNull::new(result).ok_or(())?)) + } + } + + /// Create a file in the project from a path on disk + /// + /// * `path` - Path on disk + /// * `folder` - Folder to place the created file in + /// * `name` - Name to assign to the created file + /// * `description` - Description to assign to the created file + /// * `id` - id unique ID + /// * `creation_time` - Creation time of the file + /// * `progress_func` - Progress function that will be called as the file is being added + #[allow(clippy::too_many_arguments)] + pub unsafe fn create_file_from_path_with_progress_unsafe( + &self, + path: P, + folder: Option<&ProjectFolder>, + name: N, + description: D, + id: I, + creation_time: SystemTime, + mut progress_func: F, + ) -> Result + where + P: BnStrCompatible, + N: BnStrCompatible, + D: BnStrCompatible, + I: BnStrCompatible, + F: FnMut(usize, usize) -> bool, + { + let path_raw = path.into_bytes_with_nul(); + let name_raw = name.into_bytes_with_nul(); + let description_raw = description.into_bytes_with_nul(); + let id_raw = id.into_bytes_with_nul(); + let progress_ctx = &mut progress_func as *mut F as *mut ffi::c_void; + unsafe { + let result = BNProjectCreateFileFromPathUnsafe( + self.as_raw(), + path_raw.as_ref().as_ptr() as *const ffi::c_char, + folder.map(|x| x.as_raw() as *mut _).unwrap_or(null_mut()), + name_raw.as_ref().as_ptr() as *const ffi::c_char, + description_raw.as_ref().as_ptr() as *const ffi::c_char, + id_raw.as_ref().as_ptr() as *const ffi::c_char, + systime_to_bntime(creation_time).unwrap(), + progress_ctx, + Some(cb_progress_func::), + ); + Ok(ProjectFile::from_raw(NonNull::new(result).ok_or(())?)) + } + } + + /// Create a file in the project + /// + /// * `contents` - Bytes of the file that will be created + /// * `folder` - Folder to place the created file in + /// * `name` - Name to assign to the created file + /// * `description` - Description to assign to the created file + pub fn create_file( + &self, + contents: &[u8], + folder: Option<&ProjectFolder>, + name: N, + description: D, + ) -> Result + where + N: BnStrCompatible, + D: BnStrCompatible, + { + let name_raw = name.into_bytes_with_nul(); + let description_raw = description.into_bytes_with_nul(); + unsafe { + let result = BNProjectCreateFile( + self.as_raw(), + contents.as_ptr(), + contents.len(), + folder.map(|x| x.as_raw() as *mut _).unwrap_or(null_mut()), + name_raw.as_ref().as_ptr() as *const ffi::c_char, + description_raw.as_ref().as_ptr() as *const ffi::c_char, + null_mut(), + Some(cb_progress_func_nop), + ); + Ok(ProjectFile::from_raw(NonNull::new(result).ok_or(())?)) + } + } + + /// Create a file in the project + /// + /// * `contents` - Bytes of the file that will be created + /// * `folder` - Folder to place the created file in + /// * `name` - Name to assign to the created file + /// * `description` - Description to assign to the created file + /// * `progress_func` - Progress function that will be called as the file is being added + pub fn create_file_with_progress( + &self, + contents: &[u8], + folder: Option<&ProjectFolder>, + name: N, + description: D, + mut progress_func: F, + ) -> Result + where + N: BnStrCompatible, + D: BnStrCompatible, + F: FnMut(usize, usize) -> bool, + { + let name_raw = name.into_bytes_with_nul(); + let description_raw = description.into_bytes_with_nul(); + let progress_ctx = &mut progress_func as *mut F as *mut ffi::c_void; + unsafe { + let result = BNProjectCreateFile( + self.as_raw(), + contents.as_ptr(), + contents.len(), + folder.map(|x| x.as_raw() as *mut _).unwrap_or(null_mut()), + name_raw.as_ref().as_ptr() as *const ffi::c_char, + description_raw.as_ref().as_ptr() as *const ffi::c_char, + progress_ctx, + Some(cb_progress_func::), + ); + Ok(ProjectFile::from_raw(NonNull::new(result).ok_or(())?)) + } + } + + /// Create a file in the project + /// + /// * `contents` - Bytes of the file that will be created + /// * `folder` - Folder to place the created file in + /// * `name` - Name to assign to the created file + /// * `description` - Description to assign to the created file + /// * `id` - id unique ID + /// * `creation_time` - Creation time of the file + pub unsafe fn create_file_unsafe( + &self, + contents: &[u8], + folder: Option<&ProjectFolder>, + name: N, + description: D, + id: I, + creation_time: SystemTime, + ) -> Result + where + N: BnStrCompatible, + D: BnStrCompatible, + I: BnStrCompatible, + { + let name_raw = name.into_bytes_with_nul(); + let description_raw = description.into_bytes_with_nul(); + let id_raw = id.into_bytes_with_nul(); + unsafe { + let result = BNProjectCreateFileUnsafe( + self.as_raw(), + contents.as_ptr(), + contents.len(), + folder.map(|x| x.as_raw() as *mut _).unwrap_or(null_mut()), + name_raw.as_ref().as_ptr() as *const ffi::c_char, + description_raw.as_ref().as_ptr() as *const ffi::c_char, + id_raw.as_ref().as_ptr() as *const ffi::c_char, + systime_to_bntime(creation_time).unwrap(), + null_mut(), + Some(cb_progress_func_nop), + ); + Ok(ProjectFile::from_raw(NonNull::new(result).ok_or(())?)) + } + } + + /// Create a file in the project + /// + /// * `contents` - Bytes of the file that will be created + /// * `folder` - Folder to place the created file in + /// * `name` - Name to assign to the created file + /// * `description` - Description to assign to the created file + /// * `id` - id unique ID + /// * `creation_time` - Creation time of the file + /// * `progress_func` - Progress function that will be called as the file is being added + #[allow(clippy::too_many_arguments)] + pub unsafe fn create_file_with_progress_unsafe( + &self, + contents: &[u8], + folder: Option<&ProjectFolder>, + name: N, + description: D, + id: I, + creation_time: SystemTime, + mut progress_func: F, + ) -> Result + where + N: BnStrCompatible, + D: BnStrCompatible, + I: BnStrCompatible, + F: FnMut(usize, usize) -> bool, + { + let name_raw = name.into_bytes_with_nul(); + let description_raw = description.into_bytes_with_nul(); + let id_raw = id.into_bytes_with_nul(); + let progress_ctx = &mut progress_func as *mut F as *mut ffi::c_void; + unsafe { + let result = BNProjectCreateFileUnsafe( + self.as_raw(), + contents.as_ptr(), + contents.len(), + folder.map(|x| x.as_raw() as *mut _).unwrap_or(null_mut()), + name_raw.as_ref().as_ptr() as *const ffi::c_char, + description_raw.as_ref().as_ptr() as *const ffi::c_char, + id_raw.as_ref().as_ptr() as *const ffi::c_char, + systime_to_bntime(creation_time).unwrap(), + progress_ctx, + Some(cb_progress_func::), + ); + Ok(ProjectFile::from_raw(NonNull::new(result).ok_or(())?)) + } + } + + /// Get a list of files in the project + pub fn files(&self) -> Result, ()> { + let mut count = 0; + let result = unsafe { BNProjectGetFiles(self.as_raw(), &mut count) }; + assert!(!result.is_null()); + Ok(unsafe { Array::new(result, count, ()) }) + } + + /// Retrieve a file in the project by unique `id` + pub fn file_by_id(&self, id: S) -> Option { + let id_raw = id.into_bytes_with_nul(); + let id_ptr = id_raw.as_ref().as_ptr() as *const ffi::c_char; + + let result = unsafe { BNProjectGetFileById(self.as_raw(), id_ptr) }; + let handle = NonNull::new(result)?; + Some(unsafe { ProjectFile::from_raw(handle) }) + } + + /// Retrieve a file in the project by the `path` on disk + pub fn file_by_path(&self, path: S) -> Option { + let path_raw = path.into_bytes_with_nul(); + let path_ptr = path_raw.as_ref().as_ptr() as *const ffi::c_char; + + let result = unsafe { BNProjectGetFileByPathOnDisk(self.as_raw(), path_ptr) }; + let handle = NonNull::new(result)?; + Some(unsafe { ProjectFile::from_raw(handle) }) + } + + /// Delete a file from the project + pub fn delete_file(&self, file: &ProjectFile) -> bool { + unsafe { BNProjectDeleteFile(self.as_raw(), file.as_raw()) } + } + + /// A context manager to speed up bulk project operations. + /// Project modifications are synced to disk in chunks, + /// and the project on disk vs in memory may not agree on state + /// if an exception occurs while a bulk operation is happening. + /// + /// ```no_run + /// # use binaryninja::project::Project; + /// # let project: Project = todo!(); + /// if let Ok(bulk) = project.bulk_operation() { + /// for file in std::fs::read_dir("/bin/").unwrap().into_iter() { + /// let file = file.unwrap(); + /// let file_type = file.file_type().unwrap(); + /// if file_type.is_file() && !file_type.is_symlink() { + /// bulk.create_file_from_path( + /// "/bin/", + /// None, + /// &file.file_name().to_string_lossy(), + /// "", + /// ).unwrap(); + /// } + /// } + /// } + /// ``` + // NOTE mut is used here, so only one lock can be aquired at once + pub fn bulk_operation(&mut self) -> Result { + Ok(ProjectBulkOperationLock::lock(self)) + } +} + +impl Drop for Project { + fn drop(&mut self) { + unsafe { BNFreeProject(self.as_raw()) } + } +} + +impl Clone for Project { + fn clone(&self) -> Self { + unsafe { Self::from_raw(NonNull::new(BNNewProjectReference(self.as_raw())).unwrap()) } + } +} + +impl CoreArrayProvider for Project { + type Raw = *mut BNProject; + type Context = (); + type Wrapped<'a> = &'a Project; +} + +unsafe impl CoreArrayProviderInner for Project { + unsafe fn free(raw: *mut Self::Raw, count: usize, _context: &Self::Context) { + BNFreeProjectList(raw, count) + } + + unsafe fn wrap_raw<'a>(raw: &'a Self::Raw, _context: &'a Self::Context) -> Self::Wrapped<'a> { + Self::ref_from_raw(raw) + } +} + +pub struct ProjectBulkOperationLock<'a> { + lock: &'a mut Project, +} + +impl<'a> ProjectBulkOperationLock<'a> { + pub fn lock(project: &'a mut Project) -> Self { + unsafe { BNProjectBeginBulkOperation(project.as_raw()) }; + Self { lock: project } + } + + pub fn unlock(self) { + // NOTE does nothing, just drop self + } +} + +impl std::ops::Deref for ProjectBulkOperationLock<'_> { + type Target = Project; + fn deref(&self) -> &Self::Target { + self.lock + } +} + +impl Drop for ProjectBulkOperationLock<'_> { + fn drop(&mut self) { + unsafe { BNProjectEndBulkOperation(self.lock.as_raw()) }; + } +} + +#[repr(transparent)] +pub struct ProjectFolder { + handle: NonNull, +} + +impl ProjectFolder { + pub(crate) unsafe fn from_raw(handle: NonNull) -> Self { + Self { handle } + } + + pub(crate) unsafe fn ref_from_raw(handle: &*mut BNProjectFolder) -> &Self { + debug_assert!(!handle.is_null()); + mem::transmute(handle) + } + + #[allow(clippy::mut_from_ref)] + pub(crate) unsafe fn as_raw(&self) -> &mut BNProjectFolder { + &mut *self.handle.as_ptr() + } + + /// Get the project that owns this folder + pub fn project(&self) -> Project { + unsafe { + Project::from_raw(NonNull::new(BNProjectFolderGetProject(self.as_raw())).unwrap()) + } + } + + /// Get the unique id of this folder + pub fn id(&self) -> BnString { + unsafe { BnString::from_raw(BNProjectFolderGetId(self.as_raw())) } + } + + /// Get the name of this folder + pub fn name(&self) -> BnString { + unsafe { BnString::from_raw(BNProjectFolderGetName(self.as_raw())) } + } + + /// Set the name of this folder + pub fn set_name(&self, value: S) -> bool { + let value_raw = value.into_bytes_with_nul(); + unsafe { + BNProjectFolderSetName( + self.as_raw(), + value_raw.as_ref().as_ptr() as *const ffi::c_char, + ) + } + } + + /// Get the description of this folder + pub fn description(&self) -> BnString { + unsafe { BnString::from_raw(BNProjectFolderGetDescription(self.as_raw())) } + } + + /// Set the description of this folder + pub fn set_description(&self, value: S) -> bool { + let value_raw = value.into_bytes_with_nul(); + unsafe { + BNProjectFolderSetDescription( + self.as_raw(), + value_raw.as_ref().as_ptr() as *const ffi::c_char, + ) + } + } + + /// Get the folder that contains this folder + pub fn parent(&self) -> Option { + let result = unsafe { BNProjectFolderGetParent(self.as_raw()) }; + NonNull::new(result).map(|handle| unsafe { ProjectFolder::from_raw(handle) }) + } + + /// Set the folder that contains this folder + pub fn set_folder(&self, folder: Option<&ProjectFolder>) -> bool { + let folder_handle = folder + .map(|x| unsafe { x.as_raw() as *mut _ }) + .unwrap_or(null_mut()); + unsafe { BNProjectFolderSetParent(self.as_raw(), folder_handle) } + } + + /// Recursively export this folder to disk, returns `true' if the export succeeded + /// + /// * `dest` - Destination path for the exported contents + pub fn export(&self, dest: S) -> bool { + let dest_raw = dest.into_bytes_with_nul(); + unsafe { + BNProjectFolderExport( + self.as_raw(), + dest_raw.as_ref().as_ptr() as *const ffi::c_char, + null_mut(), + Some(cb_progress_func_nop), + ) + } + } + + /// Recursively export this folder to disk, returns `true' if the export succeeded + /// + /// * `dest` - Destination path for the exported contents + /// * `progress_func` - Progress function that will be called as contents are exporting + pub fn export_with_progress(&self, dest: S, mut progress: F) -> bool + where + S: BnStrCompatible, + F: FnMut(usize, usize) -> bool, + { + let dest_raw = dest.into_bytes_with_nul(); + unsafe { + BNProjectFolderExport( + self.as_raw(), + dest_raw.as_ref().as_ptr() as *const ffi::c_char, + &mut progress as *mut _ as *mut ffi::c_void, + Some(cb_progress_func::), + ) + } + } +} + +impl Drop for ProjectFolder { + fn drop(&mut self) { + unsafe { BNFreeProjectFolder(self.as_raw()) } + } +} + +impl Clone for ProjectFolder { + fn clone(&self) -> Self { + unsafe { Self::from_raw(NonNull::new(BNNewProjectFolderReference(self.as_raw())).unwrap()) } + } +} + +impl CoreArrayProvider for ProjectFolder { + type Raw = *mut BNProjectFolder; + type Context = (); + type Wrapped<'a> = &'a Self; +} + +unsafe impl CoreArrayProviderInner for ProjectFolder { + unsafe fn free(raw: *mut Self::Raw, count: usize, _context: &Self::Context) { + BNFreeProjectFolderList(raw, count) + } + + unsafe fn wrap_raw<'a>(raw: &'a Self::Raw, _context: &'a Self::Context) -> Self::Wrapped<'a> { + Self::ref_from_raw(raw) + } +} + +#[repr(transparent)] +pub struct ProjectFile { + handle: NonNull, +} + +impl ProjectFile { + pub(crate) unsafe fn from_raw(handle: NonNull) -> Self { + Self { handle } + } + + pub(crate) unsafe fn ref_from_raw(handle: &*mut BNProjectFile) -> &Self { + debug_assert!(!handle.is_null()); + mem::transmute(handle) + } + + #[allow(clippy::mut_from_ref)] + pub(crate) unsafe fn as_raw(&self) -> &mut BNProjectFile { + &mut *self.handle.as_ptr() + } + + /// Get the project that owns this file + pub fn project(&self) -> Project { + unsafe { Project::from_raw(NonNull::new(BNProjectFileGetProject(self.as_raw())).unwrap()) } + } + + /// Get the path on disk to this file's contents + pub fn path_on_disk(&self) -> BnString { + unsafe { BnString::from_raw(BNProjectFileGetPathOnDisk(self.as_raw())) } + } + + /// Check if this file's contents exist on disk + pub fn exists_on_disk(&self) -> bool { + unsafe { BNProjectFileExistsOnDisk(self.as_raw()) } + } + + /// Get the unique id of this file + pub fn id(&self) -> BnString { + unsafe { BnString::from_raw(BNProjectFileGetId(self.as_raw())) } + } + + /// Get the name of this file + pub fn name(&self) -> BnString { + unsafe { BnString::from_raw(BNProjectFileGetName(self.as_raw())) } + } + + /// Set the name of this file + pub fn set_name(&self, value: S) -> bool { + let value_raw = value.into_bytes_with_nul(); + unsafe { + BNProjectFileSetName( + self.as_raw(), + value_raw.as_ref().as_ptr() as *const ffi::c_char, + ) + } + } + + /// Get the description of this file + pub fn description(&self) -> BnString { + unsafe { BnString::from_raw(BNProjectFileGetDescription(self.as_raw())) } + } + + /// Set the description of this file + pub fn set_description(&self, value: S) -> bool { + let value_raw = value.into_bytes_with_nul(); + unsafe { + BNProjectFileSetDescription( + self.as_raw(), + value_raw.as_ref().as_ptr() as *const ffi::c_char, + ) + } + } + + /// Get the file creation time + pub fn creation_time(&self) -> SystemTime { + systime_from_bntime(unsafe { BNProjectFileGetCreationTimestamp(self.as_raw()) }).unwrap() + } + + /// Get the folder that contains this file + pub fn folder(&self) -> Option { + let result = unsafe { BNProjectFileGetFolder(self.as_raw()) }; + NonNull::new(result).map(|handle| unsafe { ProjectFolder::from_raw(handle) }) + } + + /// Set the folder that contains this file + pub fn set_folder(&self, folder: Option<&ProjectFolder>) -> bool { + let folder_handle = folder + .map(|x| unsafe { x.as_raw() as *mut _ }) + .unwrap_or(null_mut()); + unsafe { BNProjectFileSetFolder(self.as_raw(), folder_handle) } + } + + /// Export this file to disk, `true' if the export succeeded + /// + /// * `dest` - Destination path for the exported contents + pub fn export(&self, dest: S) -> bool { + let dest_raw = dest.into_bytes_with_nul(); + unsafe { + BNProjectFileExport( + self.as_raw(), + dest_raw.as_ref().as_ptr() as *const ffi::c_char, + ) + } + } +} + +impl Drop for ProjectFile { + fn drop(&mut self) { + unsafe { BNFreeProjectFile(self.as_raw()) } + } +} + +impl Clone for ProjectFile { + fn clone(&self) -> Self { + unsafe { Self::from_raw(NonNull::new(BNNewProjectFileReference(self.as_raw())).unwrap()) } + } +} + +impl CoreArrayProvider for ProjectFile { + type Raw = *mut BNProjectFile; + type Context = (); + type Wrapped<'a> = &'a Self; +} + +unsafe impl CoreArrayProviderInner for ProjectFile { + unsafe fn free(raw: *mut Self::Raw, count: usize, _context: &Self::Context) { + BNFreeProjectFileList(raw, count) + } + + unsafe fn wrap_raw<'a>(raw: &'a Self::Raw, _context: &'a Self::Context) -> Self::Wrapped<'a> { + Self::ref_from_raw(raw) + } +} + +fn systime_from_bntime(time: i64) -> Option { + let m = Duration::from_secs(time.try_into().ok()?); + Some(UNIX_EPOCH + m) +} + +fn systime_to_bntime(time: SystemTime) -> Option { + time.duration_since(UNIX_EPOCH) + .ok()? + .as_secs() + .try_into() + .ok() +} + +unsafe extern "C" fn cb_progress_func bool>( + ctxt: *mut ffi::c_void, + progress: usize, + total: usize, +) -> bool { + if ctxt.is_null() { + return true; + } + let closure: &mut F = mem::transmute(ctxt); + closure(progress, total) +} + +unsafe extern "C" fn cb_progress_func_nop( + _ctxt: *mut ffi::c_void, + _progress: usize, + _total: usize, +) -> bool { + true +} + +#[cfg(test)] +mod test { + use std::time::SystemTime; + + use crate::metadata::Metadata; + use crate::rc::Ref; + + use super::Project; + + fn unique_project(name: &str) -> String { + format!("{}/{}", std::env::temp_dir().to_str().unwrap(), name) + } + + #[test] + fn create_delete_empty() { + crate::headless::init(); + + let project_name = "create_delete_empty_project"; + let project_path = unique_project(project_name); + // create the project + let project = Project::create(&project_path, project_name); + project.open().unwrap(); + assert!(project.is_open()); + + // check project data + let project_path_received = project.path(); + assert_eq!(&project_path, project_path_received.as_str()); + let project_name_received = project.name(); + assert_eq!(project_name, project_name_received.as_str()); + + // close the project + project.close().unwrap(); + assert!(!project.is_open()); + drop(project); + + // delete the project + std::fs::remove_dir_all(project_path).unwrap(); + + crate::headless::shutdown(); + } + + #[test] + fn create_close_open_close() { + crate::headless::init(); + + let project_name = "create_close_open_close"; + let project_path = unique_project(project_name); + // create the project + let project = Project::create(&project_path, project_name); + project.open().unwrap(); + + // get the project id + let id = project.id(); + + // close the project + project.close().unwrap(); + drop(project); + + let project = Project::open_project(&project_path); + // assert same id + let new_id = project.id(); + assert_eq!(id, new_id); + + // close the project + project.close().unwrap(); + drop(project); + + // delete the project + std::fs::remove_dir_all(project_path).unwrap(); + + crate::headless::shutdown(); + } + + #[test] + fn modify_project() { + crate::headless::init(); + + let project_name = "modify_project"; + let project_path = unique_project(project_name); + // create the project + let project = Project::create(&project_path, project_name); + project.open().unwrap(); + + // get project id + let id = project.id(); + + // create data and verify that data was created + let data_1: Ref = "data1".into(); + let data_2: Ref = "data2".into(); + assert!(project.store_metadata("key", data_1.as_ref())); + assert_eq!( + data_1.get_string().unwrap(), + project.query_metadata("key").get_string().unwrap() + ); + project.remove_metadata("key"); + assert!(project.store_metadata("key", data_2.as_ref())); + assert_eq!( + data_2.get_string().unwrap(), + project.query_metadata("key").get_string().unwrap() + ); + + // create file that will be imported to the project + let tmp_folder_1_name = format!( + "tmp_folder_{}", + std::time::SystemTime::now() + .duration_since(std::time::UNIX_EPOCH) + .unwrap() + .as_millis() + ); + let tmp_folder_2_name = format!("{tmp_folder_1_name }_2"); + let tmp_folder_1 = format!( + "{}/{tmp_folder_1_name}", + std::env::temp_dir().to_str().unwrap() + ); + let tmp_folder_2 = format!( + "{}/{tmp_folder_2_name}", + std::env::temp_dir().to_str().unwrap() + ); + std::fs::create_dir(&tmp_folder_1).unwrap(); + std::fs::create_dir(&tmp_folder_2).unwrap(); + let input_file_1 = format!("{tmp_folder_2}/input_1"); + let input_file_2 = format!("{tmp_folder_2}/input_2"); + let input_file_1_data = b"input_1_data"; + let input_file_2_data = b"input_1_data"; + std::fs::write(&input_file_1, input_file_1_data).unwrap(); + std::fs::write(&input_file_2, input_file_2_data).unwrap(); + + // create and delete folders + let folder_1_desc = "desc_folder_1"; + let folder_1 = project + .create_folder(None, "folder_1", folder_1_desc) + .unwrap(); + let folder_2_desc = "AAAAA"; + let folder_2_id = "1717416787371"; + let folder_2 = unsafe { + project + .create_folder_unsafe(Some(&folder_1), "folder_2", folder_2_desc, folder_2_id) + .unwrap() + }; + let folder_3_desc = ""; // TODO "çàáÁÀ"; + let folder_3 = project + .create_folder_from_path(&tmp_folder_1, None, folder_3_desc) + .unwrap(); + let folder_4_desc = ""; + let _folder_4 = project + .create_folder_from_path_with_progress( + &tmp_folder_2, + Some(&folder_3), + folder_4_desc, + |_, _| true, + ) + .unwrap(); + let folder_5 = project + .create_folder(None, "deleted_folder", folder_4_desc) + .unwrap(); + + assert_eq!(project.folders().unwrap().len(), 5); + let last_folder = project.folder_by_id(folder_5.id()).unwrap(); + project.delete_folder(&last_folder).unwrap(); + assert_eq!(project.folders().unwrap().len(), 4); + drop(folder_5); + + // create, import and delete file + let file_1_data = b"data_1"; + let file_1_desc = "desc_file_1"; + let _file_1 = project + .create_file(file_1_data, None, "file_1", file_1_desc) + .unwrap(); + let file_2_data = b"data_2"; + let file_2_desc = "my desc"; + let file_2_id = "12334545"; + let _file_2 = unsafe { + project.create_file_unsafe( + file_2_data, + Some(&folder_2), + "file_2", + file_2_desc, + file_2_id, + SystemTime::UNIX_EPOCH, + ) + } + .unwrap(); + let file_3_data = b"data\x023"; + let file_3_desc = "!"; + let _file_3 = project + .create_file_with_progress( + file_3_data, + Some(&folder_1), + "file_3", + file_3_desc, + |_, _| true, + ) + .unwrap(); + let file_4_time = SystemTime::now(); + let file_4_data = b"data_4\x00_4"; + let file_4_desc = ""; + let file_4_id = "123123123"; + let _file_4 = unsafe { + project.create_file_with_progress_unsafe( + file_4_data, + Some(&folder_3), + "file_4", + file_4_desc, + file_4_id, + file_4_time, + |_, _| true, + ) + } + .unwrap(); + let file_5_desc = "desc"; + let _file_5 = project + .create_file_from_path(&input_file_1, None, "file_5", file_5_desc) + .unwrap(); + let file_6_time = SystemTime::now(); + let file_6_desc = "de"; + let file_6_id = "90218347"; + let _file_6 = unsafe { + project.create_file_from_path_unsafe( + &input_file_2, + Some(&folder_3), + "file_6", + file_6_desc, + file_6_id, + file_6_time, + ) + } + .unwrap(); + let file_7 = project + .create_file_from_path_with_progress( + &input_file_2, + Some(&folder_2), + "file_7", + "no", + |_, _| true, + ) + .unwrap(); + let file_8 = unsafe { + project.create_file_from_path_with_progress_unsafe( + &input_file_1, + None, + "file_7", + "no", + "92736528", + SystemTime::now(), + |_, _| true, + ) + } + .unwrap(); + + assert_eq!(project.files().unwrap().len(), 10); + let file_a = project.file_by_id(file_8.id()).unwrap(); + let file_b = project.file_by_path(file_7.path_on_disk()).unwrap(); + project.delete_file(&file_a); + project.delete_file(&file_b); + assert_eq!(project.files().unwrap().len(), 8); + drop(file_8); + drop(file_7); + + project.set_name("project_name"); + project.set_description("project_description"); + + // close the project + project.close().unwrap(); + drop(project); + drop(folder_1); + drop(folder_2); + drop(folder_3); + + // reopen the project and verify the information store on it + let project = Project::open_project(&project_path); + + // assert same id + assert_eq!(id, project.id()); + + // verify metadata + assert_eq!( + data_2.get_string().unwrap(), + project.query_metadata("key").get_string().unwrap() + ); + + // check folders + let folders = [ + ("folder_1", None), + ("folder_2", Some(folder_2_id)), + (&tmp_folder_1_name, None), + (&tmp_folder_2_name, None), + ]; + for folder in project.folders().unwrap().iter() { + let found = folders + .iter() + .find(|f| folder.name().as_str() == f.0) + .unwrap(); + if let Some(id) = found.1 { + assert_eq!(folder.id().as_str(), id); + } + } + + // check files + #[rustfmt::skip] + let files = [ + ("file_1", &file_1_data[..], None, None), + ("file_2", &file_2_data[..], Some(file_2_id), None), + ("file_3", &file_3_data[..], None, None), + ("file_4", &file_4_data[..], Some(file_4_id), Some(file_4_time)), + ("file_5", &input_file_1_data[..], None, None), + ("file_6", &input_file_2_data[..], Some(file_6_id), Some(file_6_time)), + ("input_1", &input_file_1_data[..], None, None), + ("input_2", &input_file_2_data[..], None, None), + ]; + for file in project.files().unwrap().iter() { + let found = files.iter().find(|f| file.name().as_str() == f.0).unwrap(); + if let Some(id) = found.2 { + assert_eq!(file.id().as_str(), id); + } + if let Some(time) = found.3 { + assert_eq!( + file.creation_time() + .duration_since(SystemTime::UNIX_EPOCH) + .unwrap() + .as_secs(), + time.duration_since(SystemTime::UNIX_EPOCH) + .unwrap() + .as_secs() + ); + } + let content = std::fs::read(file.path_on_disk().as_str()).unwrap(); + assert_eq!(content, found.1); + } + + assert_eq!(project.name().as_str(), "project_name"); + assert_eq!(project.description().as_str(), "project_description"); + + // close the project + project.close().unwrap(); + + // delete the project + std::fs::remove_dir_all(project_path).unwrap(); + std::fs::remove_dir_all(tmp_folder_1).unwrap(); + std::fs::remove_dir_all(tmp_folder_2).unwrap(); + + crate::headless::shutdown(); + } +} diff --git a/src/rc.rs b/src/rc.rs index cdcae17..dc1a502 100644 --- a/src/rc.rs +++ b/src/rc.rs @@ -35,7 +35,7 @@ use std::slice; // `T` does not have the `Drop` impl in order to allow more // efficient handling of core owned objects we receive pointers // to in callbacks -pub unsafe trait RefCountable: ToOwned> + Sized { +pub(crate) unsafe trait RefCountable: ToOwned> + Sized { unsafe fn inc_ref(handle: &Self) -> Ref; unsafe fn dec_ref(handle: &Self); } @@ -43,10 +43,12 @@ pub unsafe trait RefCountable: ToOwned> + Sized { // Represents an 'owned' reference tracked by the core // that we are responsible for cleaning up once we're // done with the encapsulated value. +#[allow(private_bounds)] pub struct Ref { contents: T, } +#[allow(private_bounds)] impl Ref { /// Safety: You need to make sure wherever you got the contents from incremented the ref count already. Anywhere the core passes out an object to the API does this. pub(crate) unsafe fn new(contents: T) -> Self { @@ -151,6 +153,7 @@ impl<'a, T> Guard<'a, T> { } } +#[allow(private_bounds)] impl<'a, T> Guard<'a, T> where T: RefCountable, @@ -190,23 +193,18 @@ impl<'a, T> Borrow for Guard<'a, T> { pub trait CoreArrayProvider { type Raw; type Context; + type Wrapped<'a> + where + Self: 'a; } -pub unsafe trait CoreOwnedArrayProvider: CoreArrayProvider { +pub(crate) unsafe trait CoreArrayProviderInner: CoreArrayProvider { unsafe fn free(raw: *mut Self::Raw, count: usize, context: &Self::Context); + unsafe fn wrap_raw<'a>(raw: &'a Self::Raw, context: &'a Self::Context) -> Self::Wrapped<'a>; } -pub unsafe trait CoreArrayWrapper<'a>: CoreArrayProvider -where - Self::Raw: 'a, - Self::Context: 'a, -{ - type Wrapped: 'a; - - unsafe fn wrap_raw(raw: &'a Self::Raw, context: &'a Self::Context) -> Self::Wrapped; -} - -pub struct Array { +#[allow(private_bounds)] +pub struct Array { contents: *mut P::Raw, count: usize, context: P::Context, @@ -214,18 +212,19 @@ pub struct Array { unsafe impl

Sync for Array

where - P: CoreOwnedArrayProvider, + P: CoreArrayProviderInner, P::Context: Sync, { } unsafe impl

Send for Array

where - P: CoreOwnedArrayProvider, + P: CoreArrayProviderInner, P::Context: Send, { } -impl Array

{ +#[allow(private_bounds)] +impl Array

{ pub(crate) unsafe fn new(raw: *mut P::Raw, count: usize, context: P::Context) -> Self { Self { contents: raw, @@ -243,23 +242,19 @@ impl Array

{ pub fn is_empty(&self) -> bool { self.count == 0 } - - pub fn into_raw_parts(self) -> (*mut P::Raw, usize) { - let me = mem::ManuallyDrop::new(self); - (me.contents, me.count) - } } -impl<'a, P: 'a + CoreArrayWrapper<'a> + CoreOwnedArrayProvider> Array

{ +#[allow(private_bounds)] +impl Array

{ #[inline] - pub fn get(&'a self, index: usize) -> P::Wrapped { + pub fn get(&self, index: usize) -> P::Wrapped<'_> { unsafe { let backing = slice::from_raw_parts(self.contents, self.count); P::wrap_raw(&backing[index], &self.context) } } - pub fn iter(&'a self) -> ArrayIter<'a, P> { + pub fn iter(&self) -> ArrayIter

{ ArrayIter { it: unsafe { slice::from_raw_parts(self.contents, self.count).iter() }, context: &self.context, @@ -267,8 +262,8 @@ impl<'a, P: 'a + CoreArrayWrapper<'a> + CoreOwnedArrayProvider> Array

{ } } -impl<'a, P: 'a + CoreArrayWrapper<'a> + CoreOwnedArrayProvider> IntoIterator for &'a Array

{ - type Item = P::Wrapped; +impl<'a, P: CoreArrayProviderInner> IntoIterator for &'a Array

{ + type Item = P::Wrapped<'a>; type IntoIter = ArrayIter<'a, P>; fn into_iter(self) -> Self::IntoIter { @@ -276,7 +271,7 @@ impl<'a, P: 'a + CoreArrayWrapper<'a> + CoreOwnedArrayProvider> IntoIterator for } } -impl Drop for Array

{ +impl Drop for Array

{ fn drop(&mut self) { unsafe { P::free(self.contents, self.count, &self.context); @@ -284,7 +279,8 @@ impl Drop for Array

{ } } -pub struct ArrayGuard { +#[allow(private_bounds)] +pub struct ArrayGuard { contents: *mut P::Raw, count: usize, context: P::Context, @@ -292,18 +288,19 @@ pub struct ArrayGuard { unsafe impl

Sync for ArrayGuard

where - P: CoreArrayProvider, + P: CoreArrayProviderInner, P::Context: Sync, { } unsafe impl

Send for ArrayGuard

where - P: CoreArrayProvider, + P: CoreArrayProviderInner, P::Context: Send, { } -impl ArrayGuard

{ +#[allow(private_bounds)] +impl ArrayGuard

{ pub(crate) unsafe fn new(raw: *mut P::Raw, count: usize, context: P::Context) -> Self { Self { contents: raw, @@ -323,16 +320,17 @@ impl ArrayGuard

{ } } -impl<'a, P: 'a + CoreArrayWrapper<'a> + CoreArrayProvider> ArrayGuard

{ +#[allow(private_bounds)] +impl ArrayGuard

{ #[inline] - pub fn get(&'a self, index: usize) -> P::Wrapped { + pub fn get(&self, index: usize) -> P::Wrapped<'_> { unsafe { let backing = slice::from_raw_parts(self.contents, self.count); P::wrap_raw(&backing[index], &self.context) } } - pub fn iter(&'a self) -> ArrayIter<'a, P> { + pub fn iter(&self) -> ArrayIter

{ ArrayIter { it: unsafe { slice::from_raw_parts(self.contents, self.count).iter() }, context: &self.context, @@ -340,8 +338,8 @@ impl<'a, P: 'a + CoreArrayWrapper<'a> + CoreArrayProvider> ArrayGuard

{ } } -impl<'a, P: 'a + CoreArrayWrapper<'a> + CoreArrayProvider> IntoIterator for &'a ArrayGuard

{ - type Item = P::Wrapped; +impl<'a, P: CoreArrayProviderInner> IntoIterator for &'a ArrayGuard

{ + type Item = P::Wrapped<'a>; type IntoIter = ArrayIter<'a, P>; fn into_iter(self) -> Self::IntoIter { @@ -349,29 +347,30 @@ impl<'a, P: 'a + CoreArrayWrapper<'a> + CoreArrayProvider> IntoIterator for &'a } } +#[allow(private_bounds)] pub struct ArrayIter<'a, P> where - P: 'a + CoreArrayWrapper<'a>, + P: CoreArrayProviderInner, { it: slice::Iter<'a, P::Raw>, context: &'a P::Context, } -unsafe impl<'a, P> Send for ArrayIter<'a, P> +unsafe impl

Send for ArrayIter<'_, P> where - P: CoreArrayWrapper<'a>, + P: CoreArrayProviderInner, P::Context: Sync, { } impl<'a, P> Iterator for ArrayIter<'a, P> where - P: 'a + CoreArrayWrapper<'a>, + P: 'a + CoreArrayProviderInner, { - type Item = P::Wrapped; + type Item = P::Wrapped<'a>; #[inline] - fn next(&mut self) -> Option { + fn next(&mut self) -> Option { self.it .next() .map(|r| unsafe { P::wrap_raw(r, self.context) }) @@ -385,7 +384,7 @@ where impl<'a, P> ExactSizeIterator for ArrayIter<'a, P> where - P: 'a + CoreArrayWrapper<'a>, + P: 'a + CoreArrayProviderInner, { #[inline] fn len(&self) -> usize { @@ -395,10 +394,10 @@ where impl<'a, P> DoubleEndedIterator for ArrayIter<'a, P> where - P: 'a + CoreArrayWrapper<'a>, + P: 'a + CoreArrayProviderInner, { #[inline] - fn next_back(&mut self) -> Option { + fn next_back(&mut self) -> Option> { self.it .next_back() .map(|r| unsafe { P::wrap_raw(r, self.context) }) @@ -411,21 +410,23 @@ use rayon::prelude::*; #[cfg(feature = "rayon")] use rayon::iter::plumbing::*; +#[allow(private_bounds)] #[cfg(feature = "rayon")] -impl<'a, P> Array

+impl

Array

where - P: 'a + CoreArrayWrapper<'a> + CoreOwnedArrayProvider, + P: CoreArrayProviderInner, P::Context: Sync, - P::Wrapped: Send, + for<'a> P::Wrapped<'a>: Send, { - pub fn par_iter(&'a self) -> ParArrayIter<'a, P> { + pub fn par_iter(&self) -> ParArrayIter<'_, P> { ParArrayIter { it: self.iter() } } } +#[allow(private_bounds)] #[cfg(feature = "rayon")] pub struct ParArrayIter<'a, P> where - P: 'a + CoreArrayWrapper<'a>, + P: CoreArrayProviderInner, ArrayIter<'a, P>: Send, { it: ArrayIter<'a, P>, @@ -434,11 +435,11 @@ where #[cfg(feature = "rayon")] impl<'a, P> ParallelIterator for ParArrayIter<'a, P> where - P: 'a + CoreArrayWrapper<'a>, - P::Wrapped: Send, + P: 'a + CoreArrayProviderInner, + P::Wrapped<'a>: Send, ArrayIter<'a, P>: Send, { - type Item = P::Wrapped; + type Item = P::Wrapped<'a>; fn drive_unindexed(self, consumer: C) -> C::Result where @@ -455,8 +456,8 @@ where #[cfg(feature = "rayon")] impl<'a, P> IndexedParallelIterator for ParArrayIter<'a, P> where - P: 'a + CoreArrayWrapper<'a>, - P::Wrapped: Send, + P: 'a + CoreArrayProviderInner, + P::Wrapped<'a>: Send, ArrayIter<'a, P>: Send, { fn drive(self, consumer: C) -> C::Result @@ -481,7 +482,7 @@ where #[cfg(feature = "rayon")] struct ArrayIterProducer<'a, P> where - P: 'a + CoreArrayWrapper<'a>, + P: 'a + CoreArrayProviderInner, ArrayIter<'a, P>: Send, { it: ArrayIter<'a, P>, @@ -490,10 +491,10 @@ where #[cfg(feature = "rayon")] impl<'a, P> Producer for ArrayIterProducer<'a, P> where - P: 'a + CoreArrayWrapper<'a>, + P: 'a + CoreArrayProviderInner, ArrayIter<'a, P>: Send, { - type Item = P::Wrapped; + type Item = P::Wrapped<'a>; type IntoIter = ArrayIter<'a, P>; fn into_iter(self) -> ArrayIter<'a, P> { diff --git a/src/references.rs b/src/references.rs index 76ac449..d12c33c 100644 --- a/src/references.rs +++ b/src/references.rs @@ -1,6 +1,6 @@ use crate::architecture::CoreArchitecture; use crate::function::Function; -use crate::rc::{CoreArrayProvider, CoreArrayWrapper, CoreOwnedArrayProvider, Ref}; +use crate::rc::{CoreArrayProvider, CoreArrayProviderInner, Guard, Ref}; use binaryninjacore_sys::{BNFreeCodeReferences, BNFreeDataReferences, BNReferenceSource}; use std::mem::ManuallyDrop; @@ -56,19 +56,15 @@ impl<'a> CodeReference { impl CoreArrayProvider for CodeReference { type Raw = BNReferenceSource; type Context = (); + type Wrapped<'a> = Guard<'a, CodeReference>; } -unsafe impl CoreOwnedArrayProvider for CodeReference { +unsafe impl CoreArrayProviderInner for CodeReference { unsafe fn free(raw: *mut Self::Raw, count: usize, _context: &Self::Context) { BNFreeCodeReferences(raw, count) } -} - -unsafe impl<'a> CoreArrayWrapper<'a> for CodeReference { - type Wrapped = CodeReference; - - unsafe fn wrap_raw(raw: &'a Self::Raw, _context: &'a Self::Context) -> Self::Wrapped { - CodeReference::new(raw) + unsafe fn wrap_raw<'a>(raw: &'a Self::Raw, _context: &'a Self::Context) -> Self::Wrapped<'a> { + Guard::new(CodeReference::new(raw), &()) } } @@ -77,18 +73,14 @@ unsafe impl<'a> CoreArrayWrapper<'a> for CodeReference { impl CoreArrayProvider for DataReference { type Raw = u64; type Context = (); + type Wrapped<'a> = DataReference; } -unsafe impl CoreOwnedArrayProvider for DataReference { +unsafe impl CoreArrayProviderInner for DataReference { unsafe fn free(raw: *mut Self::Raw, _count: usize, _context: &Self::Context) { BNFreeDataReferences(raw) } -} - -unsafe impl<'a> CoreArrayWrapper<'a> for DataReference { - type Wrapped = DataReference; - - unsafe fn wrap_raw(raw: &'a Self::Raw, _context: &'a Self::Context) -> Self::Wrapped { + unsafe fn wrap_raw<'a>(raw: &'a Self::Raw, _context: &'a Self::Context) -> Self::Wrapped<'a> { DataReference { address: *raw } } } diff --git a/src/relocation.rs b/src/relocation.rs index f9cbb3c..f9ece04 100644 --- a/src/relocation.rs +++ b/src/relocation.rs @@ -1,13 +1,15 @@ +use crate::rc::Guard; use crate::string::BnStrCompatible; use crate::{ architecture::{Architecture, CoreArchitecture}, binaryview::BinaryView, llil, - rc::{CoreArrayProvider, CoreArrayWrapper, CoreOwnedArrayProvider, Ref, RefCountable}, + rc::{CoreArrayProvider, CoreArrayProviderInner, Ref, RefCountable}, symbol::Symbol, }; use binaryninjacore_sys::*; use std::borrow::Borrow; +use std::mem::MaybeUninit; use std::os::raw::c_void; #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] @@ -219,18 +221,15 @@ impl Relocation { impl CoreArrayProvider for Relocation { type Raw = *mut BNRelocation; type Context = (); + type Wrapped<'a> = Guard<'a, Relocation>; } -unsafe impl CoreOwnedArrayProvider for Relocation { +unsafe impl CoreArrayProviderInner for Relocation { unsafe fn free(raw: *mut Self::Raw, count: usize, _context: &Self::Context) { BNFreeRelocationList(raw, count); } -} - -unsafe impl<'a> CoreArrayWrapper<'a> for Relocation { - type Wrapped = Relocation; - unsafe fn wrap_raw(raw: &'a Self::Raw, _context: &'a Self::Context) -> Self::Wrapped { - Relocation(*raw) + unsafe fn wrap_raw<'a>(raw: &'a Self::Raw, _context: &'a Self::Context) -> Self::Wrapped<'a> { + Guard::new(Relocation(*raw), &()) } } @@ -501,12 +500,9 @@ where let name = name.into_bytes_with_nul(); - let uninit_handler = RelocationHandlerBuilder { - handler: unsafe { std::mem::zeroed() }, - }; - let raw = Box::into_raw(Box::new(uninit_handler)); + let raw = Box::leak(Box::new(MaybeUninit::>::zeroed())); let mut custom_handler = BNCustomRelocationHandler { - context: raw as *mut _, + context: raw.as_mut_ptr() as *mut _, freeObject: Some(cb_free::), getRelocationInfo: Some(cb_get_relocation_info::), applyRelocation: Some(cb_apply_relocation::), @@ -517,13 +513,12 @@ where assert!(!handle_raw.is_null()); let handle = CoreRelocationHandler(handle_raw); let custom_handle = CustomRelocationHandlerHandle { - handle: raw as *mut R, + handle: raw.as_mut_ptr() as *mut R, }; unsafe { - core::ptr::write( - &mut raw.as_mut().unwrap().handler, - func(custom_handle, CoreRelocationHandler(handle.0)), - ); + raw.write(RelocationHandlerBuilder { + handler: func(custom_handle, CoreRelocationHandler(handle.0)), + }); BNArchitectureRegisterRelocationHandler( arch.handle().as_ref().0, diff --git a/src/section.rs b/src/section.rs index 25e8ea5..1e45db8 100644 --- a/src/section.rs +++ b/src/section.rs @@ -72,8 +72,11 @@ impl Section { /// You need to create a section builder, customize that section, then add it to a binary view: /// - /// ``` - /// bv.add_section(Section::new().align(4).entry_size(4)) + /// ```no_run + /// # use binaryninja::section::Section; + /// # use binaryninja::binaryview::BinaryViewExt; + /// let bv = binaryninja::load("example").unwrap(); + /// bv.add_section(Section::builder("example", 0..1024).align(4).entry_size(4)) /// ``` pub fn builder(name: S, range: Range) -> SectionBuilder { SectionBuilder::new(name, range) @@ -171,18 +174,14 @@ unsafe impl RefCountable for Section { impl CoreArrayProvider for Section { type Raw = *mut BNSection; type Context = (); + type Wrapped<'a> = Guard<'a, Section>; } -unsafe impl CoreOwnedArrayProvider for Section { +unsafe impl CoreArrayProviderInner for Section { unsafe fn free(raw: *mut Self::Raw, count: usize, _context: &Self::Context) { BNFreeSectionList(raw, count); } -} - -unsafe impl<'a> CoreArrayWrapper<'a> for Section { - type Wrapped = Guard<'a, Section>; - - unsafe fn wrap_raw(raw: &'a Self::Raw, context: &'a Self::Context) -> Self::Wrapped { + unsafe fn wrap_raw<'a>(raw: &'a Self::Raw, context: &'a Self::Context) -> Self::Wrapped<'a> { Guard::new(Section::from_raw(*raw), context) } } diff --git a/src/segment.rs b/src/segment.rs index 2de785c..4c06a7b 100644 --- a/src/segment.rs +++ b/src/segment.rs @@ -117,8 +117,11 @@ impl Segment { /// You need to create a segment builder, customize that segment, then add it to a binary view: /// - /// ``` - /// bv.add_segment(Segment::new().align(4).entry_size(4)) + /// ```no_run + /// # use binaryninja::segment::Segment; + /// # use binaryninja::binaryview::BinaryViewExt; + /// let bv = binaryninja::load("example").unwrap(); + /// bv.add_segment(Segment::builder(0..0x1000).writable(true).readable(true)) /// ``` pub fn builder(ea_range: Range) -> SegmentBuilder { SegmentBuilder::new(ea_range) @@ -201,18 +204,14 @@ unsafe impl RefCountable for Segment { impl CoreArrayProvider for Segment { type Raw = *mut BNSegment; type Context = (); + type Wrapped<'a> = Guard<'a, Segment>; } -unsafe impl CoreOwnedArrayProvider for Segment { +unsafe impl CoreArrayProviderInner for Segment { unsafe fn free(raw: *mut Self::Raw, count: usize, _context: &Self::Context) { BNFreeSegmentList(raw, count); } -} - -unsafe impl<'a> CoreArrayWrapper<'a> for Segment { - type Wrapped = Guard<'a, Segment>; - - unsafe fn wrap_raw(raw: &'a Self::Raw, context: &'a Self::Context) -> Self::Wrapped { + unsafe fn wrap_raw<'a>(raw: &'a Self::Raw, context: &'a Self::Context) -> Self::Wrapped<'a> { Guard::new(Segment::from_raw(*raw), context) } } diff --git a/src/string.rs b/src/string.rs index 55521f0..bfb998d 100644 --- a/src/string.rs +++ b/src/string.rs @@ -14,7 +14,7 @@ //! String wrappers for core-owned strings and strings being passed to the core -use std::borrow::{Borrow, Cow}; +use std::borrow::Cow; use std::ffi::{CStr, CString}; use std::fmt; use std::hash::{Hash, Hasher}; @@ -33,61 +33,9 @@ pub(crate) fn raw_to_string(ptr: *const raw::c_char) -> Option { } } -/// These are strings that the core will both allocate and free. -/// We just have a reference to these strings and want to be able use them, but aren't responsible for cleanup -#[derive(PartialEq, Eq, PartialOrd, Ord, Hash, Debug)] -#[repr(C)] -pub struct BnStr { - raw: [u8], -} - -impl BnStr { - pub(crate) unsafe fn from_raw<'a>(ptr: *const raw::c_char) -> &'a Self { - mem::transmute(CStr::from_ptr(ptr).to_bytes_with_nul()) - } - - pub fn as_str(&self) -> &str { - self.as_cstr().to_str().unwrap() - } - - pub fn as_cstr(&self) -> &CStr { - unsafe { CStr::from_bytes_with_nul_unchecked(&self.raw) } - } -} - -impl Deref for BnStr { - type Target = str; - - fn deref(&self) -> &str { - self.as_str() - } -} - -impl AsRef<[u8]> for BnStr { - fn as_ref(&self) -> &[u8] { - &self.raw - } -} - -impl AsRef for BnStr { - fn as_ref(&self) -> &str { - self.as_str() - } -} - -impl Borrow for BnStr { - fn borrow(&self) -> &str { - self.as_str() - } -} - -impl fmt::Display for BnStr { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - write!(f, "{}", self.as_cstr().to_string_lossy()) - } -} - -#[repr(C)] +/// Is the quivalent of `core::ffi::CString` but using the allocation and free +/// functions provided by binaryninja_sys. +#[repr(transparent)] pub struct BnString { raw: *mut raw::c_char, } @@ -131,8 +79,28 @@ impl BnString { res } + pub(crate) fn as_raw(&self) -> &raw::c_char { + unsafe { &*self.raw } + } + pub fn as_str(&self) -> &str { - unsafe { BnStr::from_raw(self.raw).as_str() } + unsafe { CStr::from_ptr(self.raw).to_str().unwrap() } + } + + pub fn as_bytes(&self) -> &[u8] { + self.as_str().as_bytes() + } + + pub fn as_bytes_with_null(&self) -> &[u8] { + self.deref().to_bytes() + } + + pub fn len(&self) -> usize { + self.as_ref().len() + } + + pub fn is_empty(&self) -> bool { + self.as_ref().is_empty() } } @@ -158,16 +126,16 @@ impl Clone for BnString { } impl Deref for BnString { - type Target = BnStr; + type Target = CStr; - fn deref(&self) -> &BnStr { - unsafe { BnStr::from_raw(self.raw) } + fn deref(&self) -> &CStr { + unsafe { CStr::from_ptr(self.raw) } } } impl AsRef<[u8]> for BnString { fn as_ref(&self) -> &[u8] { - self.as_cstr().to_bytes_with_nul() + self.to_bytes_with_nul() } } @@ -187,33 +155,29 @@ impl Eq for BnString {} impl fmt::Display for BnString { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - write!(f, "{}", self.as_cstr().to_string_lossy()) + write!(f, "{}", self.to_string_lossy()) } } impl fmt::Debug for BnString { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - write!(f, "{}", self.as_cstr().to_string_lossy()) + write!(f, "{}", self.to_string_lossy()) } } impl CoreArrayProvider for BnString { type Raw = *mut raw::c_char; type Context = (); + type Wrapped<'a> = &'a str; } -unsafe impl CoreOwnedArrayProvider for BnString { +unsafe impl CoreArrayProviderInner for BnString { unsafe fn free(raw: *mut Self::Raw, count: usize, _context: &Self::Context) { use binaryninjacore_sys::BNFreeStringList; BNFreeStringList(raw, count); } -} - -unsafe impl<'a> CoreArrayWrapper<'a> for BnString { - type Wrapped = &'a BnStr; - - unsafe fn wrap_raw(raw: &'a Self::Raw, _context: &'a Self::Context) -> Self::Wrapped { - BnStr::from_raw(*raw) + unsafe fn wrap_raw<'a>(raw: &'a Self::Raw, _context: &'a Self::Context) -> Self::Wrapped<'a> { + CStr::from_ptr(*raw).to_str().unwrap() } } @@ -222,11 +186,11 @@ pub unsafe trait BnStrCompatible { fn into_bytes_with_nul(self) -> Self::Result; } -unsafe impl<'a> BnStrCompatible for &'a BnStr { +unsafe impl<'a> BnStrCompatible for &'a CStr { type Result = &'a [u8]; fn into_bytes_with_nul(self) -> Self::Result { - self.as_cstr().to_bytes_with_nul() + self.to_bytes_with_nul() } } @@ -238,14 +202,6 @@ unsafe impl BnStrCompatible for BnString { } } -unsafe impl<'a> BnStrCompatible for &'a CStr { - type Result = &'a [u8]; - - fn into_bytes_with_nul(self) -> Self::Result { - self.to_bytes_with_nul() - } -} - unsafe impl BnStrCompatible for CString { type Result = Vec; @@ -294,3 +250,15 @@ unsafe impl BnStrCompatible for &QualifiedName { self.string().into_bytes_with_nul() } } + +pub trait IntoJson { + type Output: BnStrCompatible; + fn get_json_string(self) -> Result; +} + +impl IntoJson for S { + type Output = S; + fn get_json_string(self) -> Result { + Ok(self) + } +} diff --git a/src/symbol.rs b/src/symbol.rs index 25801ee..d7a73a0 100644 --- a/src/symbol.rs +++ b/src/symbol.rs @@ -230,8 +230,10 @@ impl Symbol { /// To create a new symbol, you need to create a symbol builder, customize that symbol, then add `SymbolBuilder::create` it into a `Ref`: /// - /// ``` - /// Symbol::new().short_name("hello").full_name("hello").create(); + /// ```no_run + /// # use binaryninja::symbol::Symbol; + /// # use binaryninja::symbol::SymbolType; + /// Symbol::builder(SymbolType::Data, "hello", 0x1337).short_name("hello").full_name("hello").create(); /// ``` pub fn builder(ty: SymbolType, raw_name: &str, addr: u64) -> SymbolBuilder { SymbolBuilder::new(ty, raw_name, addr) @@ -246,24 +248,15 @@ impl Symbol { } pub fn full_name(&self) -> BnString { - unsafe { - let name = BNGetSymbolFullName(self.handle); - BnString::from_raw(name) - } + unsafe { BnString::from_raw(BNGetSymbolFullName(self.handle)) } } pub fn short_name(&self) -> BnString { - unsafe { - let name = BNGetSymbolShortName(self.handle); - BnString::from_raw(name) - } + unsafe { BnString::from_raw(BNGetSymbolShortName(self.handle)) } } pub fn raw_name(&self) -> BnString { - unsafe { - let name = BNGetSymbolRawName(self.handle); - BnString::from_raw(name) - } + unsafe { BnString::from_raw(BNGetSymbolRawName(self.handle)) } } pub fn address(&self) -> u64 { @@ -326,18 +319,14 @@ unsafe impl RefCountable for Symbol { impl CoreArrayProvider for Symbol { type Raw = *mut BNSymbol; type Context = (); + type Wrapped<'a> = Guard<'a, Symbol>; } -unsafe impl CoreOwnedArrayProvider for Symbol { +unsafe impl CoreArrayProviderInner for Symbol { unsafe fn free(raw: *mut Self::Raw, count: usize, _context: &Self::Context) { BNFreeSymbolList(raw, count); } -} - -unsafe impl<'a> CoreArrayWrapper<'a> for Symbol { - type Wrapped = Guard<'a, Symbol>; - - unsafe fn wrap_raw(raw: &'a Self::Raw, context: &'a Self::Context) -> Self::Wrapped { + unsafe fn wrap_raw<'a>(raw: &'a Self::Raw, context: &'a Self::Context) -> Self::Wrapped<'a> { Guard::new(Symbol::from_raw(*raw), context) } } diff --git a/src/tags.rs b/src/tags.rs index 65680b8..29266ed 100644 --- a/src/tags.rs +++ b/src/tags.rs @@ -16,8 +16,10 @@ use binaryninjacore_sys::*; +use crate::architecture::CoreArchitecture; use crate::binaryview::BinaryView; +use crate::function::Function; use crate::rc::*; use crate::string::*; @@ -77,6 +79,21 @@ impl ToOwned for Tag { } } +impl CoreArrayProvider for Tag { + type Raw = *mut BNTag; + type Context = (); + type Wrapped<'a> = Guard<'a, Self>; +} + +unsafe impl CoreArrayProviderInner for Tag { + unsafe fn free(raw: *mut Self::Raw, count: usize, _context: &Self::Context) { + BNFreeTagList(raw, count) + } + unsafe fn wrap_raw<'a>(raw: &'a Self::Raw, context: &'a Self::Context) -> Self::Wrapped<'a> { + Guard::new(Self { handle: *raw }, &context) + } +} + unsafe impl Send for Tag {} unsafe impl Sync for Tag {} @@ -115,7 +132,7 @@ impl TagType { pub fn set_icon(&self, icon: S) { let icon = icon.into_bytes_with_nul(); unsafe { - BNTagTypeSetName(self.handle, icon.as_ref().as_ptr() as *mut _); + BNTagTypeSetIcon(self.handle, icon.as_ref().as_ptr() as *mut _); } } @@ -176,3 +193,60 @@ impl ToOwned for TagType { unsafe impl Send for TagType {} unsafe impl Sync for TagType {} + +pub type TagReferenceType = BNTagReferenceType; + +pub struct TagReference { + ref_type: TagReferenceType, + auto_defined: bool, + tag: Ref, + arch: CoreArchitecture, + func: Ref, + addr: u64, +} + +impl TagReference { + unsafe fn from_borrowed_raw(value: &BNTagReference) -> Self { + Self { + ref_type: value.refType, + auto_defined: value.autoDefined, + tag: Tag { handle: value.tag }.to_owned(), + arch: CoreArchitecture::from_raw(value.arch), + func: Function { handle: value.func }.to_owned(), + addr: value.addr, + } + } + pub fn ref_type(&self) -> TagReferenceType { + self.ref_type + } + pub fn auto(&self) -> bool { + self.auto_defined + } + pub fn tag(&self) -> &Tag { + &self.tag + } + pub fn arch(&self) -> CoreArchitecture { + self.arch + } + pub fn functions(&self) -> &Function { + &self.func + } + pub fn address(&self) -> u64 { + self.addr + } +} + +impl CoreArrayProvider for TagReference { + type Raw = BNTagReference; + type Context = (); + type Wrapped<'a> = Self; +} + +unsafe impl CoreArrayProviderInner for TagReference { + unsafe fn free(raw: *mut Self::Raw, count: usize, _context: &Self::Context) { + BNFreeTagReferences(raw, count) + } + unsafe fn wrap_raw<'a>(raw: &'a Self::Raw, _context: &'a Self::Context) -> Self::Wrapped<'a> { + Self::from_borrowed_raw(raw) + } +} diff --git a/src/typearchive.rs b/src/typearchive.rs new file mode 100644 index 0000000..71a7058 --- /dev/null +++ b/src/typearchive.rs @@ -0,0 +1,947 @@ +use core::{ffi, mem, ptr}; + +use binaryninjacore_sys::*; + +use crate::databuffer::DataBuffer; +use crate::metadata::Metadata; +use crate::platform::Platform; +use crate::rc::{Array, CoreArrayProvider, CoreArrayProviderInner, Ref}; +use crate::string::{BnStrCompatible, BnString}; +use crate::types::{QualifiedName, QualifiedNameAndType, QualifiedNameTypeAndId, Type}; + +/// Type Archives are a collection of types which can be shared between different analysis +/// sessions and are backed by a database file on disk. Their types can be modified, and +/// a history of previous versions of types is stored in snapshots in the archive. +#[repr(transparent)] +pub struct TypeArchive { + handle: ptr::NonNull, +} + +impl Drop for TypeArchive { + fn drop(&mut self) { + unsafe { BNFreeTypeArchiveReference(self.as_raw()) } + } +} + +impl Clone for TypeArchive { + fn clone(&self) -> Self { + unsafe { + Self::from_raw(ptr::NonNull::new(BNNewTypeArchiveReference(self.as_raw())).unwrap()) + } + } +} + +impl PartialEq for TypeArchive { + fn eq(&self, other: &Self) -> bool { + self.id() == other.id() + } +} +impl Eq for TypeArchive {} + +impl core::hash::Hash for TypeArchive { + fn hash(&self, state: &mut H) { + (self.handle.as_ptr() as usize).hash(state); + } +} + +impl core::fmt::Debug for TypeArchive { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + let path = self.path().map(|x| x.to_string()); + f.debug_struct("TypeArchive").field("path", &path).finish() + } +} + +impl TypeArchive { + pub(crate) unsafe fn from_raw(handle: ptr::NonNull) -> Self { + Self { handle } + } + + pub(crate) unsafe fn ref_from_raw(handle: &*mut BNTypeArchive) -> &Self { + assert!(!handle.is_null()); + mem::transmute(handle) + } + + #[allow(clippy::mut_from_ref)] + pub(crate) unsafe fn as_raw(&self) -> &mut BNTypeArchive { + &mut *self.handle.as_ptr() + } + + /// Open the Type Archive at the given path, if it exists. + pub fn open(path: S) -> Option { + let path = path.into_bytes_with_nul(); + let handle = unsafe { BNOpenTypeArchive(path.as_ref().as_ptr() as *const ffi::c_char) }; + ptr::NonNull::new(handle).map(|handle| unsafe { TypeArchive::from_raw(handle) }) + } + + /// Create a Type Archive at the given path, returning None if it could not be created. + pub fn create(path: S, platform: &Platform) -> Option { + let path = path.into_bytes_with_nul(); + let handle = unsafe { + BNCreateTypeArchive( + path.as_ref().as_ptr() as *const ffi::c_char, + platform.handle, + ) + }; + ptr::NonNull::new(handle).map(|handle| unsafe { TypeArchive::from_raw(handle) }) + } + + /// Create a Type Archive at the given path and id, returning None if it could not be created. + pub fn create_with_id( + path: P, + id: I, + platform: &Platform, + ) -> Option { + let path = path.into_bytes_with_nul(); + let id = id.into_bytes_with_nul(); + let handle = unsafe { + BNCreateTypeArchiveWithId( + path.as_ref().as_ptr() as *const ffi::c_char, + platform.handle, + id.as_ref().as_ptr() as *const ffi::c_char, + ) + }; + ptr::NonNull::new(handle).map(|handle| unsafe { TypeArchive::from_raw(handle) }) + } + + /// Get a reference to the Type Archive with the known id, if one exists. + pub fn lookup_by_id(id: S) -> Option { + let id = id.into_bytes_with_nul(); + let handle = unsafe { BNLookupTypeArchiveById(id.as_ref().as_ptr() as *const ffi::c_char) }; + ptr::NonNull::new(handle).map(|handle| unsafe { TypeArchive::from_raw(handle) }) + } + + /// Get the path to the Type Archive's file + pub fn path(&self) -> Option { + let result = unsafe { BNGetTypeArchivePath(self.as_raw()) }; + (!result.is_null()).then(|| unsafe { BnString::from_raw(result) }) + } + + /// Get the guid for a Type Archive + pub fn id(&self) -> Option { + let result = unsafe { BNGetTypeArchiveId(self.as_raw()) }; + (!result.is_null()).then(|| unsafe { BnString::from_raw(result) }) + } + + /// Get the associated Platform for a Type Archive + pub fn platform(&self) -> Ref { + let result = unsafe { BNGetTypeArchivePlatform(self.as_raw()) }; + assert!(!result.is_null()); + unsafe { Platform::ref_from_raw(result) } + } + + /// Get the id of the current snapshot in the type archive + pub fn current_snapshot_id(&self) -> BnString { + let result = unsafe { BNGetTypeArchiveCurrentSnapshotId(self.as_raw()) }; + assert!(!result.is_null()); + unsafe { BnString::from_raw(result) } + } + + /// Revert the type archive's current snapshot to the given snapshot + pub fn set_current_snapshot_id(&self, id: S) { + let id = id.into_bytes_with_nul(); + unsafe { + BNSetTypeArchiveCurrentSnapshot( + self.as_raw(), + id.as_ref().as_ptr() as *const ffi::c_char, + ) + } + } + + /// Get a list of every snapshot's id + pub fn all_snapshot_ids(&self) -> Array { + let mut count = 0; + let result = unsafe { BNGetTypeArchiveAllSnapshotIds(self.as_raw(), &mut count) }; + assert!(!result.is_null()); + unsafe { Array::new(result, count, ()) } + } + + /// Get the ids of the parents to the given snapshot + pub fn get_snapshot_parent_ids( + &self, + snapshot: S, + ) -> Option> { + let snapshot = snapshot.into_bytes_with_nul(); + let mut count = 0; + let result = unsafe { + BNGetTypeArchiveSnapshotParentIds( + self.as_raw(), + snapshot.as_ref().as_ptr() as *const ffi::c_char, + &mut count, + ) + }; + (!result.is_null()).then(|| unsafe { Array::new(result, count, ()) }) + } + + /// Get the ids of the children to the given snapshot + pub fn get_snapshot_child_ids( + &self, + snapshot: S, + ) -> Option> { + let snapshot = snapshot.into_bytes_with_nul(); + let mut count = 0; + let result = unsafe { + BNGetTypeArchiveSnapshotChildIds( + self.as_raw(), + snapshot.as_ref().as_ptr() as *const ffi::c_char, + &mut count, + ) + }; + (!result.is_null()).then(|| unsafe { Array::new(result, count, ()) }) + } + + /// Add named types to the type archive. Type must have all dependant named types added + /// prior to being added, or this function will fail. + /// If the type already exists, it will be overwritten. + /// + /// * `name` - Name of new type + /// * `type` - Definition of new type + pub fn add_type(&self, name: &QualifiedNameAndType) { + self.add_types(core::slice::from_ref(name)) + } + + /// Add named types to the type archive. Types must have all dependant named + /// types prior to being added, or included in the list, or this function will fail. + /// Types already existing with any added names will be overwritten. + /// + /// * `new_types` - Names and definitions of new types + pub fn add_types(&self, new_types: &[QualifiedNameAndType]) { + // SAFETY BNQualifiedNameAndType and QualifiedNameAndType are transparent + let new_types_raw: &[BNQualifiedNameAndType] = unsafe { mem::transmute(new_types) }; + let result = unsafe { + BNAddTypeArchiveTypes(self.as_raw(), new_types_raw.as_ptr(), new_types.len()) + }; + assert!(result); + } + + /// Change the name of an existing type in the type archive. + /// + /// * `old_name` - Old type name in archive + /// * `new_name` - New type name + pub fn rename_type(&self, old_name: &QualifiedName, new_name: &QualifiedNameAndType) { + let id = self + .get_type_id(old_name, self.current_snapshot_id()) + .unwrap(); + return self.rename_type_by_id(id, new_name.name()); + } + + /// Change the name of an existing type in the type archive. + /// + /// * `id` - Old id of type in archive + /// * `new_name` - New type name + pub fn rename_type_by_id(&self, id: S, new_name: &QualifiedName) { + let id = id.into_bytes_with_nul(); + let result = unsafe { + BNRenameTypeArchiveType( + self.as_raw(), + id.as_ref().as_ptr() as *const ffi::c_char, + &new_name.0, + ) + }; + assert!(result); + } + + /// Delete an existing type in the type archive. + pub fn delete_type(&self, name: &QualifiedName) { + let id = self.get_type_id(name, self.current_snapshot_id()); + let Some(id) = id else { + panic!("Unknown type {}", name.string()) + }; + self.delete_type_by_id(id); + } + + /// Delete an existing type in the type archive. + pub fn delete_type_by_id(&self, id: S) { + let id = id.into_bytes_with_nul(); + let result = unsafe { + BNDeleteTypeArchiveType(self.as_raw(), id.as_ref().as_ptr() as *const ffi::c_char) + }; + assert!(result); + } + + /// Retrieve a stored type in the archive + /// + /// * `name` - Type name + /// * `snapshot` - Snapshot id to search for types + pub fn get_type_by_name( + &self, + name: &QualifiedName, + snapshot: S, + ) -> Option> { + let snapshot = snapshot.into_bytes_with_nul(); + let result = unsafe { + BNGetTypeArchiveTypeByName( + self.as_raw(), + &name.0, + snapshot.as_ref().as_ptr() as *const ffi::c_char, + ) + }; + (!result.is_null()).then(|| unsafe { Type::ref_from_raw(result) }) + } + + /// Retrieve a stored type in the archive by id + /// + /// * `id` - Type id + /// * `snapshot` - Snapshot id to search for types + pub fn get_type_by_id( + &self, + id: I, + snapshot: S, + ) -> Option> { + let snapshot = snapshot.into_bytes_with_nul(); + let id = id.into_bytes_with_nul(); + let result = unsafe { + BNGetTypeArchiveTypeById( + self.as_raw(), + id.as_ref().as_ptr() as *const ffi::c_char, + snapshot.as_ref().as_ptr() as *const ffi::c_char, + ) + }; + (!result.is_null()).then(|| unsafe { Type::ref_from_raw(result) }) + } + + /// Retrieve a type's name by its id + /// + /// * `id` - Type id + /// * `snapshot` - Snapshot id to search for types + pub fn get_type_name_by_id( + &self, + id: I, + snapshot: S, + ) -> QualifiedName { + let snapshot = snapshot.into_bytes_with_nul(); + let id = id.into_bytes_with_nul(); + let result = unsafe { + BNGetTypeArchiveTypeName( + self.as_raw(), + id.as_ref().as_ptr() as *const ffi::c_char, + snapshot.as_ref().as_ptr() as *const ffi::c_char, + ) + }; + QualifiedName(result) + } + + /// Retrieve a type's id by its name + /// + /// * `name` - Type name + /// * `snapshot` - Snapshot id to search for types + pub fn get_type_id( + &self, + name: &QualifiedName, + snapshot: S, + ) -> Option { + let snapshot = snapshot.into_bytes_with_nul(); + let result = unsafe { + BNGetTypeArchiveTypeId( + self.as_raw(), + &name.0, + snapshot.as_ref().as_ptr() as *const ffi::c_char, + ) + }; + (!result.is_null()).then(|| unsafe { BnString::from_raw(result) }) + } + + /// Retrieve all stored types in the archive at a snapshot + /// + /// * `snapshot` - Snapshot id to search for types + pub fn get_types_and_ids( + &self, + snapshot: S, + ) -> Array { + let snapshot = snapshot.into_bytes_with_nul(); + let mut count = 0; + let result = unsafe { + BNGetTypeArchiveTypes( + self.as_raw(), + snapshot.as_ref().as_ptr() as *const ffi::c_char, + &mut count, + ) + }; + assert!(!result.is_null()); + unsafe { Array::new(result, count, ()) } + } + + /// Get a list of all types' ids in the archive at a snapshot + /// + /// * `snapshot` - Snapshot id to search for types + pub fn get_type_ids(&self, snapshot: S) -> Array { + let snapshot = snapshot.into_bytes_with_nul(); + let mut count = 0; + let result = unsafe { + BNGetTypeArchiveTypeIds( + self.as_raw(), + snapshot.as_ref().as_ptr() as *const ffi::c_char, + &mut count, + ) + }; + assert!(!result.is_null()); + unsafe { Array::new(result, count, ()) } + } + + /// Get a list of all types' names in the archive at a snapshot + /// + /// * `snapshot` - Snapshot id to search for types + pub fn get_type_names(&self, snapshot: S) -> Array { + let snapshot = snapshot.into_bytes_with_nul(); + let mut count = 0; + let result = unsafe { + BNGetTypeArchiveTypeNames( + self.as_raw(), + snapshot.as_ref().as_ptr() as *const ffi::c_char, + &mut count, + ) + }; + assert!(!result.is_null()); + unsafe { Array::new(result, count, ()) } + } + + /// Get a list of all types' names and ids in the archive at a current snapshot + + /// * `snapshot` - Snapshot id to search for types + pub fn get_type_names_and_ids( + &self, + snapshot: S, + ) -> (Array, Array) { + let snapshot = snapshot.into_bytes_with_nul(); + let mut count = 0; + let mut names = ptr::null_mut(); + let mut ids = ptr::null_mut(); + let result = unsafe { + BNGetTypeArchiveTypeNamesAndIds( + self.as_raw(), + snapshot.as_ref().as_ptr() as *const ffi::c_char, + &mut names, + &mut ids, + &mut count, + ) + }; + assert!(result); + (unsafe { Array::new(names, count, ()) }, unsafe { + Array::new(ids, count, ()) + }) + } + + /// Get all types a given type references directly + /// + /// * `id` - Source type id + /// * `snapshot` - Snapshot id to search for types + pub fn get_outgoing_direct_references( + &self, + id: I, + snapshot: S, + ) -> Array { + let snapshot = snapshot.into_bytes_with_nul(); + let id = id.into_bytes_with_nul(); + let mut count = 0; + let result = unsafe { + BNGetTypeArchiveOutgoingDirectTypeReferences( + self.as_raw(), + id.as_ref().as_ptr() as *const ffi::c_char, + snapshot.as_ref().as_ptr() as *const ffi::c_char, + &mut count, + ) + }; + assert!(!result.is_null()); + unsafe { Array::new(result, count, ()) } + } + + /// Get all types a given type references, and any types that the referenced types reference + /// + /// :param id: Source type id + /// :param snapshot: Snapshot id to search for types + pub fn get_outgoing_recursive_references( + &self, + id: I, + snapshot: S, + ) -> Array { + let snapshot = snapshot.into_bytes_with_nul(); + let id = id.into_bytes_with_nul(); + let mut count = 0; + let result = unsafe { + BNGetTypeArchiveOutgoingRecursiveTypeReferences( + self.as_raw(), + id.as_ref().as_ptr() as *const ffi::c_char, + snapshot.as_ref().as_ptr() as *const ffi::c_char, + &mut count, + ) + }; + assert!(!result.is_null()); + unsafe { Array::new(result, count, ()) } + } + + /// Get all types that reference a given type + /// + /// * `id` - Target type id + /// * `snapshot` - Snapshot id to search for types + pub fn get_incoming_direct_references( + &self, + id: I, + snapshot: S, + ) -> Array { + let snapshot = snapshot.into_bytes_with_nul(); + let id = id.into_bytes_with_nul(); + let mut count = 0; + let result = unsafe { + BNGetTypeArchiveIncomingDirectTypeReferences( + self.as_raw(), + id.as_ref().as_ptr() as *const ffi::c_char, + snapshot.as_ref().as_ptr() as *const ffi::c_char, + &mut count, + ) + }; + assert!(!result.is_null()); + unsafe { Array::new(result, count, ()) } + } + + /// Get all types that reference a given type, and all types that reference them, recursively + /// + /// * `id` - Target type id + /// * `snapshot` - Snapshot id to search for types, or empty string to search the latest snapshot + pub fn get_incoming_recursive_references( + &self, + id: I, + snapshot: S, + ) -> Array { + let snapshot = snapshot.into_bytes_with_nul(); + let id = id.into_bytes_with_nul(); + let mut count = 0; + let result = unsafe { + BNGetTypeArchiveIncomingRecursiveTypeReferences( + self.as_raw(), + id.as_ref().as_ptr() as *const ffi::c_char, + snapshot.as_ref().as_ptr() as *const ffi::c_char, + &mut count, + ) + }; + assert!(!result.is_null()); + unsafe { Array::new(result, count, ()) } + } + + /// Look up a metadata entry in the archive + pub fn query_metadata(&self, key: S) -> Option> { + let key = key.into_bytes_with_nul(); + let result = unsafe { + BNTypeArchiveQueryMetadata(self.as_raw(), key.as_ref().as_ptr() as *const ffi::c_char) + }; + (!result.is_null()).then(|| unsafe { Metadata::ref_from_raw(result) }) + } + + /// Store a key/value pair in the archive's metadata storage + /// + /// * `key` - key value to associate the Metadata object with + /// * `md` - object to store. + pub fn store_metadata(&self, key: S, md: &Metadata) { + let key = key.into_bytes_with_nul(); + let result = unsafe { + BNTypeArchiveStoreMetadata( + self.as_raw(), + key.as_ref().as_ptr() as *const ffi::c_char, + md.handle, + ) + }; + assert!(result); + } + + /// Delete a given metadata entry in the archive from the `key` + pub fn remove_metadata(&self, key: S) -> bool { + let key = key.into_bytes_with_nul(); + unsafe { + BNTypeArchiveRemoveMetadata(self.as_raw(), key.as_ref().as_ptr() as *const ffi::c_char) + } + } + + /// Turn a given `snapshot` id into a data stream + pub fn serialize_snapshot(&self, snapshot: S) -> DataBuffer { + let snapshot = snapshot.into_bytes_with_nul(); + let result = unsafe { + BNTypeArchiveSerializeSnapshot( + self.as_raw(), + snapshot.as_ref().as_ptr() as *const ffi::c_char, + ) + }; + assert!(!result.is_null()); + DataBuffer::from_raw(result) + } + + /// Take a serialized snapshot `data` stream and create a new snapshot from it + pub fn deserialize_snapshot(&self, data: &DataBuffer) -> BnString { + let result = unsafe { BNTypeArchiveDeserializeSnapshot(self.as_raw(), data.as_raw()) }; + assert!(!result.is_null()); + unsafe { BnString::from_raw(result) } + } + + /// Register a notification listener + pub fn register_notification_callback( + &self, + callback: T, + ) -> TypeArchiveCallbackHandle { + // SAFETY free on [TypeArchiveCallbackHandle::Drop] + let callback = Box::leak(Box::new(callback)); + let mut notification = BNTypeArchiveNotification { + context: callback as *mut T as *mut ffi::c_void, + typeAdded: Some(cb_type_added::), + typeUpdated: Some(cb_type_updated::), + typeRenamed: Some(cb_type_renamed::), + typeDeleted: Some(cb_type_deleted::), + }; + unsafe { BNRegisterTypeArchiveNotification(self.as_raw(), &mut notification) } + TypeArchiveCallbackHandle { + callback, + type_archive: self.clone(), + } + } + + // NOTE NotificationClosure is left private, there is no need for the user + // to know or use it. + #[allow(private_interfaces)] + pub fn register_notification_closure( + &self, + type_added: A, + type_updated: U, + type_renamed: R, + type_deleted: D, + ) -> TypeArchiveCallbackHandle> + where + A: FnMut(&TypeArchive, &str, &Type), + U: FnMut(&TypeArchive, &str, &Type, &Type), + R: FnMut(&TypeArchive, &str, &QualifiedName, &QualifiedName), + D: FnMut(&TypeArchive, &str, &Type), + { + self.register_notification_callback(NotificationClosure { + fun_type_added: type_added, + fun_type_updated: type_updated, + fun_type_renamed: type_renamed, + fun_type_deleted: type_deleted, + }) + } + + /// Close a type archive, disconnecting it from any active views and closing + /// any open file handles + pub fn close(self) { + unsafe { BNCloseTypeArchive(self.as_raw()) } + // NOTE self must be dropped after, don't make it `&self` + } + + /// Determine if `file` is a Type Archive + pub fn is_type_archive(file: P) -> bool { + let file = file.into_bytes_with_nul(); + unsafe { BNIsTypeArchive(file.as_ref().as_ptr() as *const ffi::c_char) } + } + + // TODO implement TypeContainer + ///// Get the TypeContainer interface for this Type Archive, presenting types + ///// at the current snapshot in the archive. + //pub fn type_container(&self) -> TypeContainer { + // let result = unsafe { BNGetTypeArchiveTypeContainer(self.as_raw()) }; + // unsafe { TypeContainer::from_raw(ptr::NonNull::new(result).unwrap()) } + //} + + /// Do some function in a transaction making a new snapshot whose id is passed to func. If func throws, + /// the transaction will be rolled back and the snapshot will not be created. + /// + /// * `func` - Function to call + /// * `parents` - Parent snapshot ids + /// + /// Returns Created snapshot id + pub fn new_snapshot_transaction(&self, mut function: F, parents: &[BnString]) -> BnString + where + P: BnStrCompatible, + F: FnMut(&str) -> bool, + { + unsafe extern "C" fn cb_callback bool>( + ctxt: *mut ffi::c_void, + id: *const ffi::c_char, + ) -> bool { + let fun: &mut F = &mut *(ctxt as *mut F); + fun(&ffi::CStr::from_ptr(id).to_string_lossy()) + } + + // SAFETY BnString and `*const ffi::c_char` are transparent + let parents_raw = parents.as_ptr() as *const *const ffi::c_char; + + let result = unsafe { + BNTypeArchiveNewSnapshotTransaction( + self.as_raw(), + Some(cb_callback::), + &mut function as *mut F as *mut ffi::c_void, + parents_raw, + parents.len(), + ) + }; + assert!(!result.is_null()); + unsafe { BnString::from_raw(result) } + } + + /// Merge two snapshots in the archive to produce a new snapshot + /// + /// * `base_snapshot` - Common ancestor of snapshots + /// * `first_snapshot` - First snapshot to merge + /// * `second_snapshot` - Second snapshot to merge + /// * `merge_conflicts` - List of all conflicting types, id <-> target snapshot + /// * `progress` - Function to call for progress updates + /// + /// Returns Snapshot id, if merge was successful, otherwise the List of + /// conflicting type ids + pub fn merge_snapshots( + &self, + base_snapshot: B, + first_snapshot: F, + second_snapshot: S, + merge_conflicts: M, + mut progress: P, + ) -> Result> + where + B: BnStrCompatible, + F: BnStrCompatible, + S: BnStrCompatible, + P: FnMut(usize, usize) -> bool, + M: IntoIterator, + MI: BnStrCompatible, + MK: BnStrCompatible, + { + unsafe extern "C" fn cb_callback bool>( + ctxt: *mut ffi::c_void, + progress: usize, + total: usize, + ) -> bool { + let ctxt: &mut F = &mut *(ctxt as *mut F); + ctxt(progress, total) + } + + let base_snapshot = base_snapshot.into_bytes_with_nul(); + let first_snapshot = first_snapshot.into_bytes_with_nul(); + let second_snapshot = second_snapshot.into_bytes_with_nul(); + let (merge_keys, merge_values): (Vec, Vec) = merge_conflicts + .into_iter() + .map(|(k, v)| (BnString::new(k), BnString::new(v))) + .unzip(); + // SAFETY BnString and `*const ffi::c_char` are transparent + let merge_keys_raw = merge_keys.as_ptr() as *const *const ffi::c_char; + let merge_values_raw = merge_values.as_ptr() as *const *const ffi::c_char; + + let mut conflicts_errors = ptr::null_mut(); + let mut conflicts_errors_count = 0; + + let mut result = ptr::null_mut(); + + let success = unsafe { + BNTypeArchiveMergeSnapshots( + self.as_raw(), + base_snapshot.as_ref().as_ptr() as *const ffi::c_char, + first_snapshot.as_ref().as_ptr() as *const ffi::c_char, + second_snapshot.as_ref().as_ptr() as *const ffi::c_char, + merge_keys_raw, + merge_values_raw, + merge_keys.len(), + &mut conflicts_errors, + &mut conflicts_errors_count, + &mut result, + Some(cb_callback::

), + (&mut progress) as *mut P as *mut ffi::c_void, + ) + }; + if success { + assert!(!result.is_null()); + Ok(unsafe { BnString::from_raw(result) }) + } else { + assert!(!conflicts_errors.is_null()); + Err(unsafe { Array::new(conflicts_errors, conflicts_errors_count, ()) }) + } + } +} + +impl CoreArrayProvider for TypeArchive { + type Raw = *mut BNTypeArchive; + type Context = (); + type Wrapped<'a> = &'a TypeArchive; +} + +unsafe impl CoreArrayProviderInner for TypeArchive { + unsafe fn free(raw: *mut Self::Raw, count: usize, _context: &Self::Context) { + BNFreeTypeArchiveList(raw, count) + } + + unsafe fn wrap_raw<'a>(raw: &'a Self::Raw, _context: &'a Self::Context) -> Self::Wrapped<'a> { + Self::ref_from_raw(raw) + } +} + +pub struct TypeArchiveCallbackHandle { + callback: *mut T, + type_archive: TypeArchive, +} + +impl Drop for TypeArchiveCallbackHandle { + fn drop(&mut self) { + let mut notification = BNTypeArchiveNotification { + context: self.callback as *mut ffi::c_void, + typeAdded: Some(cb_type_added::), + typeUpdated: Some(cb_type_updated::), + typeRenamed: Some(cb_type_renamed::), + typeDeleted: Some(cb_type_deleted::), + }; + // unregister the notification callback + unsafe { + BNUnregisterTypeArchiveNotification(self.type_archive.as_raw(), &mut notification) + } + // free the context created at [TypeArchive::register_notification_callback] + drop(unsafe { Box::from_raw(self.callback) }); + } +} + +pub trait TypeArchiveNotificationCallback { + /// Called when a type is added to the archive + /// + /// * `archive` - Source Type archive + /// * `id` - Id of type added + /// * `definition` - Definition of type + fn type_added(&mut self, _archive: &TypeArchive, _id: &str, _definition: &Type) {} + + /// Called when a type in the archive is updated to a new definition + /// + /// * `archive` - Source Type archive + /// * `id` - Id of type + /// * `old_definition` - Previous definition + /// * `new_definition` - Current definition + fn type_updated( + &mut self, + _archive: &TypeArchive, + _id: &str, + _old_definition: &Type, + _new_definition: &Type, + ) { + } + + /// Called when a type in the archive is renamed + /// + /// * `archive` - Source Type archive + /// * `id` - Type id + /// * `old_name` - Previous name + /// * `new_name` - Current name + fn type_renamed( + &mut self, + _archive: &TypeArchive, + _id: &str, + _old_name: &QualifiedName, + _new_name: &QualifiedName, + ) { + } + + /// Called when a type in the archive is deleted from the archive + /// + /// * `archive` - Source Type archive + /// * `id` - Id of type deleted + /// * `definition` - Definition of type deleted + fn type_deleted(&mut self, _archive: &TypeArchive, _id: &str, _definition: &Type) {} +} + +struct NotificationClosure +where + A: FnMut(&TypeArchive, &str, &Type), + U: FnMut(&TypeArchive, &str, &Type, &Type), + R: FnMut(&TypeArchive, &str, &QualifiedName, &QualifiedName), + D: FnMut(&TypeArchive, &str, &Type), +{ + fun_type_added: A, + fun_type_updated: U, + fun_type_renamed: R, + fun_type_deleted: D, +} + +impl TypeArchiveNotificationCallback for NotificationClosure +where + A: FnMut(&TypeArchive, &str, &Type), + U: FnMut(&TypeArchive, &str, &Type, &Type), + R: FnMut(&TypeArchive, &str, &QualifiedName, &QualifiedName), + D: FnMut(&TypeArchive, &str, &Type), +{ + fn type_added(&mut self, archive: &TypeArchive, id: &str, definition: &Type) { + (self.fun_type_added)(archive, id, definition) + } + + fn type_updated( + &mut self, + archive: &TypeArchive, + id: &str, + old_definition: &Type, + new_definition: &Type, + ) { + (self.fun_type_updated)(archive, id, old_definition, new_definition) + } + + fn type_renamed( + &mut self, + archive: &TypeArchive, + id: &str, + old_name: &QualifiedName, + new_name: &QualifiedName, + ) { + (self.fun_type_renamed)(archive, id, old_name, new_name) + } + + fn type_deleted(&mut self, archive: &TypeArchive, id: &str, definition: &Type) { + (self.fun_type_deleted)(archive, id, definition) + } +} + +unsafe extern "C" fn cb_type_added( + ctxt: *mut ::std::os::raw::c_void, + archive: *mut BNTypeArchive, + id: *const ::std::os::raw::c_char, + definition: *mut BNType, +) { + let ctxt: &mut T = &mut *(ctxt as *mut T); + ctxt.type_added( + unsafe { TypeArchive::ref_from_raw(&archive) }, + unsafe { ffi::CStr::from_ptr(id).to_string_lossy().as_ref() }, + &Type { handle: definition }, + ) +} +unsafe extern "C" fn cb_type_updated( + ctxt: *mut ::std::os::raw::c_void, + archive: *mut BNTypeArchive, + id: *const ::std::os::raw::c_char, + old_definition: *mut BNType, + new_definition: *mut BNType, +) { + let ctxt: &mut T = &mut *(ctxt as *mut T); + ctxt.type_updated( + unsafe { TypeArchive::ref_from_raw(&archive) }, + unsafe { ffi::CStr::from_ptr(id).to_string_lossy().as_ref() }, + &Type { + handle: old_definition, + }, + &Type { + handle: new_definition, + }, + ) +} +unsafe extern "C" fn cb_type_renamed( + ctxt: *mut ::std::os::raw::c_void, + archive: *mut BNTypeArchive, + id: *const ::std::os::raw::c_char, + old_name: *const BNQualifiedName, + new_name: *const BNQualifiedName, +) { + let ctxt: &mut T = &mut *(ctxt as *mut T); + let old_name = mem::ManuallyDrop::new(QualifiedName(*old_name)); + let new_name = mem::ManuallyDrop::new(QualifiedName(*new_name)); + ctxt.type_renamed( + unsafe { TypeArchive::ref_from_raw(&archive) }, + unsafe { ffi::CStr::from_ptr(id).to_string_lossy().as_ref() }, + &old_name, + &new_name, + ) +} +unsafe extern "C" fn cb_type_deleted( + ctxt: *mut ::std::os::raw::c_void, + archive: *mut BNTypeArchive, + id: *const ::std::os::raw::c_char, + definition: *mut BNType, +) { + let ctxt: &mut T = &mut *(ctxt as *mut T); + ctxt.type_deleted( + unsafe { TypeArchive::ref_from_raw(&archive) }, + unsafe { ffi::CStr::from_ptr(id).to_string_lossy().as_ref() }, + &Type { handle: definition }, + ) +} diff --git a/src/typelibrary.rs b/src/typelibrary.rs new file mode 100644 index 0000000..a0992e1 --- /dev/null +++ b/src/typelibrary.rs @@ -0,0 +1,367 @@ +use binaryninjacore_sys::*; + +use core::{ffi, mem, ptr}; + +use crate::{ + architecture::CoreArchitecture, + metadata::Metadata, + platform::Platform, + rc::{Array, CoreArrayProvider, CoreArrayProviderInner, Ref}, + string::{BnStrCompatible, BnString}, + types::{QualifiedName, QualifiedNameAndType, Type}, +}; + +#[repr(transparent)] +pub struct TypeLibrary { + handle: ptr::NonNull, +} + +impl TypeLibrary { + pub(crate) unsafe fn from_raw(handle: ptr::NonNull) -> Self { + Self { handle } + } + + pub(crate) unsafe fn ref_from_raw(handle: &*mut BNTypeLibrary) -> &Self { + assert!(!handle.is_null()); + mem::transmute(handle) + } + + #[allow(clippy::mut_from_ref)] + pub(crate) unsafe fn as_raw(&self) -> &mut BNTypeLibrary { + &mut *self.handle.as_ptr() + } + + pub fn new_reference(&self) -> Self { + unsafe { + Self::from_raw(ptr::NonNull::new(BNNewTypeLibraryReference(self.as_raw())).unwrap()) + } + } + + pub fn new_duplicated(&self) -> Self { + unsafe { Self::from_raw(ptr::NonNull::new(BNDuplicateTypeLibrary(self.as_raw())).unwrap()) } + } + + /// Creates an empty type library object with a random GUID and the provided name. + pub fn new(arch: CoreArchitecture, name: S) -> TypeLibrary { + let name = name.into_bytes_with_nul(); + let new_lib = + unsafe { BNNewTypeLibrary(arch.0, name.as_ref().as_ptr() as *const ffi::c_char) }; + unsafe { TypeLibrary::from_raw(ptr::NonNull::new(new_lib).unwrap()) } + } + + pub fn all(arch: CoreArchitecture) -> Array { + let mut count = 0; + let result = unsafe { BNGetArchitectureTypeLibraries(arch.0, &mut count) }; + assert!(!result.is_null()); + unsafe { Array::new(result, count, ()) } + } + + /// Decompresses a type library file to a file on disk. + pub fn decompress_to_file(path: P, output: O) -> bool { + let path = path.into_bytes_with_nul(); + let output = output.into_bytes_with_nul(); + unsafe { + BNTypeLibraryDecompressToFile( + path.as_ref().as_ptr() as *const ffi::c_char, + output.as_ref().as_ptr() as *const ffi::c_char, + ) + } + } + + /// Loads a finalized type library instance from file + pub fn load_from_file(path: S) -> Option { + let path = path.into_bytes_with_nul(); + let handle = + unsafe { BNLoadTypeLibraryFromFile(path.as_ref().as_ptr() as *const ffi::c_char) }; + ptr::NonNull::new(handle).map(|h| unsafe { TypeLibrary::from_raw(h) }) + } + + /// Saves a finalized type library instance to file + pub fn write_to_file(&self, path: S) -> bool { + let path = path.into_bytes_with_nul(); + unsafe { + BNWriteTypeLibraryToFile(self.as_raw(), path.as_ref().as_ptr() as *const ffi::c_char) + } + } + + /// Looks up the first type library found with a matching name. Keep in mind that names are not + /// necessarily unique. + pub fn from_name(arch: CoreArchitecture, name: S) -> Option { + let name = name.into_bytes_with_nul(); + let handle = unsafe { + BNLookupTypeLibraryByName(arch.0, name.as_ref().as_ptr() as *const ffi::c_char) + }; + ptr::NonNull::new(handle).map(|h| unsafe { TypeLibrary::from_raw(h) }) + } + + /// Attempts to grab a type library associated with the provided Architecture and GUID pair + pub fn from_guid(arch: CoreArchitecture, guid: S) -> Option { + let guid = guid.into_bytes_with_nul(); + let handle = unsafe { + BNLookupTypeLibraryByGuid(arch.0, guid.as_ref().as_ptr() as *const ffi::c_char) + }; + ptr::NonNull::new(handle).map(|h| unsafe { TypeLibrary::from_raw(h) }) + } + + /// The Architecture this type library is associated with + pub fn arch(&self) -> CoreArchitecture { + let arch = unsafe { BNGetTypeLibraryArchitecture(self.as_raw()) }; + assert!(!arch.is_null()); + CoreArchitecture(arch) + } + + /// The primary name associated with this type library + pub fn name(&self) -> Option { + let result = unsafe { BNGetTypeLibraryName(self.as_raw()) }; + (!result.is_null()).then(|| unsafe { BnString::from_raw(result) }) + } + + /// Sets the name of a type library instance that has not been finalized + pub fn set_name(&self, value: S) { + let value = value.into_bytes_with_nul(); + unsafe { + BNSetTypeLibraryName(self.as_raw(), value.as_ref().as_ptr() as *const ffi::c_char) + } + } + + /// The `dependency_name` of a library is the name used to record dependencies across + /// type libraries. This allows, for example, a library with the name "musl_libc" to have + /// dependencies on it recorded as "libc_generic", allowing a type library to be used across + /// multiple platforms where each has a specific libc that also provides the name "libc_generic" + /// as an `alternate_name`. + pub fn dependency_name(&self) -> Option { + let result = unsafe { BNGetTypeLibraryDependencyName(self.as_raw()) }; + (!result.is_null()).then(|| unsafe { BnString::from_raw(result) }) + } + + /// Sets the dependency name of a type library instance that has not been finalized + pub fn set_dependency_name(&self, value: S) { + let value = value.into_bytes_with_nul(); + unsafe { + BNSetTypeLibraryDependencyName( + self.as_raw(), + value.as_ref().as_ptr() as *const ffi::c_char, + ) + } + } + + /// Returns the GUID associated with the type library + pub fn guid(&self) -> Option { + let result = unsafe { BNGetTypeLibraryGuid(self.as_raw()) }; + (!result.is_null()).then(|| unsafe { BnString::from_raw(result) }) + } + + /// Sets the GUID of a type library instance that has not been finalized + pub fn set_guid(&self, value: S) { + let value = value.into_bytes_with_nul(); + unsafe { + BNSetTypeLibraryGuid(self.as_raw(), value.as_ref().as_ptr() as *const ffi::c_char) + } + } + + /// A list of extra names that will be considered a match by [Platform::get_type_libraries_by_name] + pub fn alternate_names(&self) -> Array { + let mut count = 0; + let result = unsafe { BNGetTypeLibraryAlternateNames(self.as_raw(), &mut count) }; + assert!(!result.is_null()); + unsafe { Array::new(result, count, ()) } + } + + /// Adds an extra name to this type library used during library lookups and dependency resolution + pub fn add_alternate_name(&self, value: S) { + let value = value.into_bytes_with_nul(); + unsafe { + BNAddTypeLibraryAlternateName( + self.as_raw(), + value.as_ref().as_ptr() as *const ffi::c_char, + ) + } + } + + /// Returns a list of all platform names that this type library will register with during platform + /// type registration. + /// + /// This returns strings, not Platform objects, as type libraries can be distributed with support for + /// Platforms that may not be present. + pub fn platform_names(&self) -> Array { + let mut count = 0; + let result = unsafe { BNGetTypeLibraryPlatforms(self.as_raw(), &mut count) }; + assert!(!result.is_null()); + unsafe { Array::new(result, count, ()) } + } + + /// Associate a platform with a type library instance that has not been finalized. + /// + /// This will cause the library to be searchable by [Platform::get_type_libraries_by_name] + /// when loaded. + /// + /// This does not have side affects until finalization of the type library. + pub fn add_platform(&self, plat: &Platform) { + unsafe { BNAddTypeLibraryPlatform(self.as_raw(), plat.handle) } + } + + /// Clears the list of platforms associated with a type library instance that has not been finalized + pub fn clear_platforms(&self) { + unsafe { BNClearTypeLibraryPlatforms(self.as_raw()) } + } + + /// Flags a newly created type library instance as finalized and makes it available for Platform and Architecture + /// type library searches + pub fn finalize(&self) -> bool { + unsafe { BNFinalizeTypeLibrary(self.as_raw()) } + } + + /// Retrieves a metadata associated with the given key stored in the type library + pub fn query_metadata(&self, key: S) -> Option { + let key = key.into_bytes_with_nul(); + let result = unsafe { + BNTypeLibraryQueryMetadata(self.as_raw(), key.as_ref().as_ptr() as *const ffi::c_char) + }; + (!result.is_null()).then(|| unsafe { Metadata::from_raw(result) }) + } + + /// Stores an object for the given key in the current type library. Objects stored using + /// `store_metadata` can be retrieved from any reference to the library. Objects stored are not arbitrary python + /// objects! The values stored must be able to be held in a Metadata object. See [Metadata] + /// for more information. Python objects could obviously be serialized using pickle but this intentionally + /// a task left to the user since there is the potential security issues. + /// + /// This is primarily intended as a way to store Platform specific information relevant to BinaryView implementations; + /// for example the PE BinaryViewType uses type library metadata to retrieve ordinal information, when available. + /// + /// * `key` - key value to associate the Metadata object with + /// * `md` - object to store. + pub fn store_metadata(&self, key: S, md: &Metadata) { + let key = key.into_bytes_with_nul(); + unsafe { + BNTypeLibraryStoreMetadata( + self.as_raw(), + key.as_ref().as_ptr() as *const ffi::c_char, + md.handle, + ) + } + } + + /// Removes the metadata associated with key from the current type library. + pub fn remove_metadata(&self, key: S) { + let key = key.into_bytes_with_nul(); + unsafe { + BNTypeLibraryRemoveMetadata(self.as_raw(), key.as_ref().as_ptr() as *const ffi::c_char) + } + } + + /// Retrieves the metadata associated with the current type library. + pub fn metadata(&self) -> Metadata { + let md_handle = unsafe { BNTypeLibraryGetMetadata(self.as_raw()) }; + assert!(!md_handle.is_null()); + unsafe { Metadata::from_raw(md_handle) } + } + + // TODO: implement TypeContainer + // /// Type Container for all TYPES within the Type Library. Objects are not included. + // /// The Type Container's Platform will be the first platform associated with the Type Library. + // pub fn type_container(&self) -> TypeContainer { + // let result = unsafe{ BNGetTypeLibraryTypeContainer(self.as_raw())}; + // unsafe{TypeContainer::from_raw(ptr::NonNull::new(result).unwrap())} + // } + + /// Directly inserts a named object into the type library's object store. + /// This is not done recursively, so care should be taken that types referring to other types + /// through NamedTypeReferences are already appropriately prepared. + /// + /// To add types and objects from an existing BinaryView, it is recommended to use + /// `export_object_to_library `, which will automatically pull in + /// all referenced types and record additional dependencies as needed. + pub fn add_named_object(&self, name: &QualifiedName, type_: &Type) { + unsafe { + BNAddTypeLibraryNamedObject(self.as_raw(), &name.0 as *const _ as *mut _, type_.handle) + } + } + + /// Directly inserts a named object into the type library's object store. + /// This is not done recursively, so care should be taken that types referring to other types + /// through NamedTypeReferences are already appropriately prepared. + /// + /// To add types and objects from an existing BinaryView, it is recommended to use + /// `export_type_to_library `, which will automatically pull in + /// all referenced types and record additional dependencies as needed. + pub fn add_named_type(&self, name: &QualifiedNameAndType, type_: &Type) { + unsafe { + BNAddTypeLibraryNamedType(self.as_raw(), &name.0 as *const _ as *mut _, type_.handle) + } + } + + /// Manually flag NamedTypeReferences to the given QualifiedName as originating from another source + /// TypeLibrary with the given dependency name. + /// + ///

+ /// + /// Use this api with extreme caution. + /// + ///
(&self, name: &QualifiedName, source: S) { + let source = source.into_bytes_with_nul(); + unsafe { + BNAddTypeLibraryNamedTypeSource( + self.as_raw(), + &name.0 as *const _ as *mut _, + source.as_ref().as_ptr() as *const ffi::c_char, + ) + } + } + + /// Direct extracts a reference to a contained object -- when + /// attempting to extract types from a library into a BinaryView, consider using + /// `import_library_object ` instead. + pub fn get_named_object(&self, name: &QualifiedName) -> Option> { + let t = + unsafe { BNGetTypeLibraryNamedObject(self.as_raw(), &name.0 as *const _ as *mut _) }; + (!t.is_null()).then(|| unsafe { Type::ref_from_raw(t) }) + } + + /// Direct extracts a reference to a contained type -- when + /// attempting to extract types from a library into a BinaryView, consider using + /// `import_library_type ` instead. + pub fn get_named_type(&self, name: &QualifiedName) -> Option> { + let t = unsafe { BNGetTypeLibraryNamedType(self.as_raw(), &name.0 as *const _ as *mut _) }; + (!t.is_null()).then(|| unsafe { Type::ref_from_raw(t) }) + } + + /// A dict containing all named objects (functions, exported variables) provided by a type library + pub fn named_objects(&self) -> Array { + let mut count = 0; + let result = unsafe { BNGetTypeLibraryNamedObjects(self.as_raw(), &mut count) }; + assert!(!result.is_null()); + unsafe { Array::new(result, count, ()) } + } + + /// A dict containing all named types provided by a type library + pub fn named_types(&self) -> Array { + let mut count = 0; + let result = unsafe { BNGetTypeLibraryNamedTypes(self.as_raw(), &mut count) }; + assert!(!result.is_null()); + unsafe { Array::new(result, count, ()) } + } +} + +impl Drop for TypeLibrary { + fn drop(&mut self) { + unsafe { BNFreeTypeLibrary(self.as_raw()) } + } +} + +impl CoreArrayProvider for TypeLibrary { + type Raw = *mut BNTypeLibrary; + type Context = (); + type Wrapped<'a> = &'a Self; +} + +unsafe impl CoreArrayProviderInner for TypeLibrary { + unsafe fn free(raw: *mut Self::Raw, count: usize, _context: &Self::Context) { + BNFreeTypeLibraryList(raw, count) + } + + unsafe fn wrap_raw<'a>(raw: &'a Self::Raw, _context: &'a Self::Context) -> Self::Wrapped<'a> { + Self::ref_from_raw(raw) + } +} diff --git a/src/types.rs b/src/types.rs index 5463ce3..1c8071e 100644 --- a/src/types.rs +++ b/src/types.rs @@ -24,22 +24,23 @@ use crate::{ callingconvention::CallingConvention, filemetadata::FileMetadata, function::Function, + mlil::MediumLevelILFunction, rc::*, - string::{raw_to_string, BnStr, BnStrCompatible, BnString}, + string::{raw_to_string, BnStrCompatible, BnString}, symbol::Symbol, }; use lazy_static::lazy_static; +use std::ptr::null_mut; use std::{ - borrow::Cow, - collections::HashSet, + borrow::{Borrow, Cow}, + collections::{HashMap, HashSet}, ffi::CStr, - fmt, - fmt::{Debug, Display, Formatter}, + fmt::{self, Debug, Display, Formatter}, hash::{Hash, Hasher}, iter::{zip, IntoIterator}, - mem, - mem::ManuallyDrop, + mem::{self, ManuallyDrop}, + ops::Range, os::raw::c_char, ptr, result, slice, sync::Mutex, @@ -52,10 +53,14 @@ pub type TypeClass = BNTypeClass; pub type NamedTypeReferenceClass = BNNamedTypeReferenceClass; pub type MemberAccess = BNMemberAccess; pub type MemberScope = BNMemberScope; +pub type ILBranchDependence = BNILBranchDependence; +pub type DataFlowQueryOption = BNDataFlowQueryOption; +pub type VariableSourceType = BNVariableSourceType; //////////////// // Confidence +/// Compatible with the `BNType*WithConfidence` types pub struct Conf { pub contents: T, pub confidence: u8, @@ -177,6 +182,20 @@ impl Display for Conf { } } +impl PartialEq for Conf { + fn eq(&self, other: &Self) -> bool { + self.contents.eq(&other.contents) + } +} + +impl Eq for Conf {} + +impl Hash for Conf { + fn hash(&self, state: &mut H) { + self.contents.hash(state); + } +} + impl<'a, T> From<&'a Conf> for Conf<&'a T> { fn from(c: &'a Conf) -> Self { Conf::new(&c.contents, c.confidence) @@ -214,6 +233,8 @@ impl Clone for Conf { } } +impl Copy for Conf {} + impl From for Conf { fn from(contents: T) -> Self { Self::new(contents, max_confidence()) @@ -260,15 +281,6 @@ impl From for Conf { } } -impl From>> for BNTypeWithConfidence { - fn from(conf: Conf>) -> Self { - Self { - type_: conf.contents.handle, - confidence: conf.confidence, - } - } -} - impl From> for BNTypeWithConfidence { fn from(conf: Conf<&Type>) -> Self { Self { @@ -420,9 +432,9 @@ impl TypeBuilder { } } - pub fn parameters(&self) -> Result>> { + pub fn parameters(&self) -> Result> { unsafe { - let mut count: usize = mem::zeroed(); + let mut count = 0; let parameters_raw = BNGetTypeBuilderParameters(self.handle, &mut count); if parameters_raw.is_null() { Err(()) @@ -698,15 +710,17 @@ impl Drop for TypeBuilder { ////////// // Type +#[repr(transparent)] pub struct Type { pub(crate) handle: *mut BNType, } -/// ``` -/// use binaryninja::types::Type; -/// let bv = unsafe { BinaryView::from_raw(view) }; -/// let my_custom_type_1 = Self::named_int(5, false, "my_w"); -/// let my_custom_type_2 = Self::int(5, false); +/// ```no_run +/// # use crate::binaryninja::binaryview::BinaryViewExt; +/// # use binaryninja::types::Type; +/// let bv = binaryninja::load("example.bin").unwrap(); +/// let my_custom_type_1 = Type::named_int(5, false, "my_w"); +/// let my_custom_type_2 = Type::int(5, false); /// bv.define_user_type("int_1", &my_custom_type_1); /// bv.define_user_type("int_2", &my_custom_type_2); /// ``` @@ -791,9 +805,9 @@ impl Type { } } - pub fn parameters(&self) -> Result>> { + pub fn parameters(&self) -> Result> { unsafe { - let mut count: usize = mem::zeroed(); + let mut count = 0; let parameters_raw: *mut BNFunctionParameter = BNGetTypeParameters(self.handle, &mut count); if parameters_raw.is_null() { @@ -992,9 +1006,9 @@ impl Type { } } - pub fn function<'a, S: BnStrCompatible + Clone, T: Into>>( + pub fn function<'a, T: Into>>( return_type: T, - parameters: &[FunctionParameter], + parameters: &[FunctionParameter], variable_arguments: bool, ) -> Ref { let mut return_type = return_type.into().into(); @@ -1012,14 +1026,14 @@ impl Type { let mut raw_parameters = Vec::::with_capacity(parameters.len()); let mut parameter_name_references = Vec::with_capacity(parameters.len()); for parameter in parameters { - let raw_name = parameter.name.clone().into_bytes_with_nul(); + let raw_name = parameter.name.as_str().into_bytes_with_nul(); let location = match ¶meter.location { Some(location) => location.raw(), None => unsafe { mem::zeroed() }, }; raw_parameters.push(BNFunctionParameter { - name: raw_name.as_ref().as_ptr() as *mut _, + name: raw_name.as_slice().as_ptr() as *mut _, type_: parameter.t.contents.handle, typeConfidence: parameter.t.confidence, defaultLocation: parameter.location.is_none(), @@ -1058,12 +1072,11 @@ impl Type { pub fn function_with_options< 'a, A: Architecture, - S: BnStrCompatible + Clone, T: Into>, C: Into>>, >( return_type: T, - parameters: &[FunctionParameter], + parameters: &[FunctionParameter], variable_arguments: bool, calling_convention: C, stack_adjust: Conf, @@ -1084,14 +1097,14 @@ impl Type { } for (name, parameter) in zip(name_ptrs, parameters) { - let raw_name = name.into_bytes_with_nul(); + let raw_name = name.as_str().into_bytes_with_nul(); let location = match ¶meter.location { Some(location) => location.raw(), None => unsafe { mem::zeroed() }, }; raw_parameters.push(BNFunctionParameter { - name: raw_name.as_ref().as_ptr() as *mut _, + name: raw_name.as_slice().as_ptr() as *mut _, type_: parameter.t.contents.handle, typeConfidence: parameter.t.confidence, defaultLocation: parameter.location.is_none(), @@ -1200,9 +1213,9 @@ impl Type { } } - pub fn generate_auto_demangled_type_id<'a, S: BnStrCompatible>(name: S) -> &'a BnStr { + pub fn generate_auto_demangled_type_id(name: S) -> BnString { let mut name = QualifiedName::from(name); - unsafe { BnStr::from_raw(BNGenerateAutoDemangledTypeId(&mut name.0)) } + unsafe { BnString::from_raw(BNGenerateAutoDemangledTypeId(&mut name.0)) } } } @@ -1227,17 +1240,33 @@ impl fmt::Debug for Type { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { if let Ok(lock) = TYPE_DEBUG_BV.lock() { if let Some(bv) = &*lock { - let mut count: usize = 0; let container = unsafe { BNGetAnalysisTypeContainer(bv.handle) }; - let lines: *mut BNTypeDefinitionLine = unsafe { - BNGetTypeLines( + + let printer = if f.alternate() { + unsafe { BNGetTypePrinterByName(c"_DebugTypePrinter".as_ptr()) } + } else { + unsafe { BNGetTypePrinterByName(c"CoreTypePrinter".as_ptr()) } + }; + if printer.is_null() { + return Err(fmt::Error); + } + + let mut name = QualifiedName::from(""); + + let mut lines: *mut BNTypeDefinitionLine = null_mut(); + let mut count: usize = 0; + + unsafe { + BNGetTypePrinterTypeLines( + printer, self.handle, container, - "".as_ptr() as *const c_char, - 80, + &mut name.0, + 64, false, BNTokenEscapingType::NoTokenEscapingType, - &mut count as *mut usize, + &mut lines, + &mut count, ) }; unsafe { @@ -1311,38 +1340,57 @@ impl ToOwned for Type { } } +pub struct ComponentReferencedTypes; +impl CoreArrayProvider for ComponentReferencedTypes { + type Raw = *mut BNType; + type Context = (); + type Wrapped<'a> = &'a Type; +} + +unsafe impl CoreArrayProviderInner for ComponentReferencedTypes { + unsafe fn free(raw: *mut Self::Raw, count: usize, _context: &Self::Context) { + BNComponentFreeReferencedTypes(raw, count) + } + + unsafe fn wrap_raw<'a>(raw: &'a Self::Raw, _context: &'a Self::Context) -> Self::Wrapped<'a> { + // SAFETY: BNType and Type are trasparent + core::mem::transmute(raw) + } +} + /////////////////////// // FunctionParameter #[derive(Clone, Debug)] -pub struct FunctionParameter { +pub struct FunctionParameter { pub t: Conf>, - pub name: S, + pub name: String, pub location: Option, } -impl FunctionParameter { - pub fn new>>>(t: T, name: S, location: Option) -> Self { +impl FunctionParameter { + pub fn new>>>(t: T, name: String, location: Option) -> Self { Self { t: t.into(), name, location, } } -} -impl FunctionParameter { pub(crate) fn from_raw(member: BNFunctionParameter) -> Self { - let name: BnString = if member.name.is_null() { - if member.location.type_ == BNVariableSourceType::RegisterVariableSourceType { - BnString::new(format!("reg_{}", member.location.storage)) - } else if member.location.type_ == BNVariableSourceType::StackVariableSourceType { - BnString::new(format!("arg_{}", member.location.storage)) + let name = if member.name.is_null() { + if member.location.type_ == VariableSourceType::RegisterVariableSourceType { + format!("reg_{}", member.location.storage) + } else if member.location.type_ == VariableSourceType::StackVariableSourceType { + format!("arg_{}", member.location.storage) } else { - BnString::new("") + String::new() } } else { - BnString::new(unsafe { BnStr::from_raw(member.name) }) + unsafe { CStr::from_ptr(member.name) } + .to_str() + .unwrap() + .to_owned() }; Self { @@ -1365,13 +1413,13 @@ impl FunctionParameter { #[derive(Clone, Copy, Debug, Hash, Eq, PartialEq)] pub struct Variable { - pub t: BNVariableSourceType, + pub t: VariableSourceType, pub index: u32, pub storage: i64, } impl Variable { - pub fn new(t: BNVariableSourceType, index: u32, storage: i64) -> Self { + pub fn new(t: VariableSourceType, index: u32, storage: i64) -> Self { Self { t, index, storage } } @@ -1382,6 +1430,9 @@ impl Variable { storage: var.storage, } } + pub(crate) unsafe fn from_identifier(var: u64) -> Self { + Self::from_raw(unsafe { BNFromVariableIdentifier(var) }) + } pub(crate) fn raw(&self) -> BNVariable { BNVariable { @@ -1392,6 +1443,43 @@ impl Variable { } } +impl CoreArrayProvider for Variable { + type Raw = BNVariable; + type Context = (); + type Wrapped<'a> = Self; +} + +unsafe impl CoreArrayProviderInner for Variable { + unsafe fn free(raw: *mut Self::Raw, _count: usize, _context: &Self::Context) { + BNFreeVariableList(raw) + } + unsafe fn wrap_raw<'a>(raw: &'a Self::Raw, _context: &'a Self::Context) -> Self::Wrapped<'a> { + Variable::from_raw(*raw) + } +} + +// Name, Variable and Type +impl CoreArrayProvider for (&str, Variable, &Type) { + type Raw = BNVariableNameAndType; + type Context = (); + type Wrapped<'a> = (&'a str, Variable, &'a Type) where Self: 'a; +} + +unsafe impl CoreArrayProviderInner for (&str, Variable, &Type) { + unsafe fn free(raw: *mut Self::Raw, count: usize, _context: &Self::Context) { + BNFreeVariableNameAndTypeList(raw, count) + } + unsafe fn wrap_raw<'a>( + raw: &'a Self::Raw, + _context: &'a Self::Context, + ) -> (&'a str, Variable, &'a Type) { + let name = CStr::from_ptr(raw.name).to_str().unwrap(); + let var = Variable::from_raw(raw.var); + let var_type = core::mem::transmute(&raw.type_); + (name, var, var_type) + } +} + ////////////// // SSAVariable @@ -1407,24 +1495,77 @@ impl SSAVariable { } } +impl CoreArrayProvider for SSAVariable { + type Raw = usize; + type Context = Variable; + type Wrapped<'a> = Self; +} + +unsafe impl CoreArrayProviderInner for SSAVariable { + unsafe fn free(raw: *mut Self::Raw, _count: usize, _context: &Self::Context) { + BNFreeILInstructionList(raw) + } + + unsafe fn wrap_raw<'a>(raw: &'a Self::Raw, context: &'a Self::Context) -> Self::Wrapped<'a> { + SSAVariable::new(*context, *raw) + } +} + +impl CoreArrayProvider for Array { + type Raw = BNVariable; + type Context = Ref; + type Wrapped<'a> = Self; +} + +unsafe impl CoreArrayProviderInner for Array { + unsafe fn free(raw: *mut Self::Raw, _count: usize, _context: &Self::Context) { + BNFreeVariableList(raw) + } + + unsafe fn wrap_raw<'a>(raw: &'a Self::Raw, context: &'a Self::Context) -> Self::Wrapped<'a> { + let mut count = 0; + let versions = + unsafe { BNGetMediumLevelILVariableSSAVersions(context.handle, raw, &mut count) }; + Array::new(versions, count, Variable::from_raw(*raw)) + } +} + /////////////// // NamedVariable +#[derive(Clone, Debug, Hash, Eq, PartialEq)] pub struct NamedTypedVariable { - var: BNVariable, - auto_defined: bool, - type_confidence: u8, - name: *mut c_char, - ty: *mut BNType, + pub name: String, + pub ty: Conf>, + pub var: Variable, + pub auto_defined: bool, } impl NamedTypedVariable { + pub fn new(var: Variable, name: String, ty: Conf>, auto_defined: bool) -> Self { + Self { + name, + ty, + var, + auto_defined, + } + } + + pub(crate) unsafe fn from_raw(var: &BNVariableNameAndType) -> Self { + Self { + var: Variable::from_raw(var.var), + auto_defined: var.autoDefined, + name: CStr::from_ptr(var.name).to_str().unwrap().to_string(), + ty: Conf::new(Type::ref_from_raw(var.type_), var.typeConfidence), + } + } + pub fn name(&self) -> &str { - unsafe { BnStr::from_raw(self.name).as_str() } + &self.name } pub fn var(&self) -> Variable { - unsafe { Variable::from_raw(self.var) } + self.var } pub fn auto_defined(&self) -> bool { @@ -1432,36 +1573,26 @@ impl NamedTypedVariable { } pub fn type_confidence(&self) -> u8 { - self.type_confidence + self.ty.confidence } pub fn var_type(&self) -> Ref { - unsafe { Ref::new(Type::from_raw(self.ty)) } + self.ty.contents.clone() } } impl CoreArrayProvider for NamedTypedVariable { type Raw = BNVariableNameAndType; type Context = (); + type Wrapped<'a> = Guard<'a, NamedTypedVariable>; } -unsafe impl CoreOwnedArrayProvider for NamedTypedVariable { +unsafe impl CoreArrayProviderInner for NamedTypedVariable { unsafe fn free(raw: *mut Self::Raw, count: usize, _context: &Self::Context) { BNFreeVariableNameAndTypeList(raw, count) } -} - -unsafe impl<'a> CoreArrayWrapper<'a> for NamedTypedVariable { - type Wrapped = ManuallyDrop; - - unsafe fn wrap_raw(raw: &'a Self::Raw, _context: &'a Self::Context) -> Self::Wrapped { - ManuallyDrop::new(NamedTypedVariable { - var: raw.var, - ty: raw.type_, - name: raw.name, - auto_defined: raw.autoDefined, - type_confidence: raw.typeConfidence, - }) + unsafe fn wrap_raw<'a>(raw: &'a Self::Raw, _context: &'a Self::Context) -> Self::Wrapped<'a> { + unsafe { Guard::new(NamedTypedVariable::from_raw(raw), raw) } } } @@ -1470,15 +1601,15 @@ unsafe impl<'a> CoreArrayWrapper<'a> for NamedTypedVariable { #[derive(Debug, Clone)] pub struct EnumerationMember { - pub name: BnString, + pub name: String, pub value: u64, pub is_default: bool, } impl EnumerationMember { - pub fn new(name: S, value: u64, is_default: bool) -> Self { + pub fn new(name: String, value: u64, is_default: bool) -> Self { Self { - name: BnString::new(name), + name, value, is_default, } @@ -1486,7 +1617,7 @@ impl EnumerationMember { pub(crate) unsafe fn from_raw(member: BNEnumerationMember) -> Self { Self { - name: BnString::new(raw_to_string(member.name).unwrap()), + name: raw_to_string(member.name).unwrap(), value: member.value, is_default: member.isDefault, } @@ -1547,7 +1678,7 @@ impl EnumerationBuilder { pub fn members(&self) -> Vec { unsafe { - let mut count: usize = mem::zeroed(); + let mut count = 0; let members_raw = BNGetEnumerationBuilderMembers(self.handle, &mut count); let members: &[BNEnumerationMember] = slice::from_raw_parts(members_raw, count); @@ -1604,7 +1735,7 @@ impl Enumeration { pub fn members(&self) -> Vec { unsafe { - let mut count: usize = mem::zeroed(); + let mut count = 0; let members_raw = BNGetEnumerationMembers(self.handle, &mut count); let members: &[BNEnumerationMember] = slice::from_raw_parts(members_raw, count); @@ -1647,9 +1778,10 @@ pub struct StructureBuilder { pub(crate) handle: *mut BNStructureBuilder, } -/// ```rust +/// ```no_run /// // Includes -/// use binaryninja::types::{Structure, Type}; +/// # use binaryninja::binaryview::BinaryViewExt; +/// use binaryninja::types::{Structure, StructureBuilder, Type, MemberAccess, MemberScope}; /// /// // Define struct, set size (in bytes) /// let mut my_custom_struct = StructureBuilder::new(); @@ -1658,16 +1790,16 @@ pub struct StructureBuilder { /// let field_3 = Type::int(8, false); /// /// // Assign those fields -/// my_custom_struct.append(&field_1, "field_4"); -/// my_custom_struct.insert(&field_1, "field_1", 0); -/// my_custom_struct.insert(&field_2, "field_2", 5); -/// my_custom_struct.insert(&field_3, "field_3", 9); +/// my_custom_struct.insert(&field_1, "field_1", 0, false, MemberAccess::PublicAccess, MemberScope::NoScope); +/// my_custom_struct.insert(&field_2, "field_2", 5, false, MemberAccess::PublicAccess, MemberScope::NoScope); +/// my_custom_struct.insert(&field_3, "field_3", 9, false, MemberAccess::PublicAccess, MemberScope::NoScope); +/// my_custom_struct.append(&field_1, "field_4", MemberAccess::PublicAccess, MemberScope::NoScope); /// /// // Convert structure to type -/// let my_custom_structure_type = Self::structure_type(&mut my_custom_struct); +/// let my_custom_structure_type = Type::structure(&my_custom_struct.finalize()); /// /// // Add the struct to the binary view to use in analysis -/// let bv = unsafe { BinaryView::from_raw(view) }; +/// let bv = binaryninja::load("example").unwrap(); /// bv.define_user_type("my_custom_struct", &my_custom_structure_type); /// ``` impl StructureBuilder { @@ -1812,7 +1944,7 @@ impl StructureBuilder { &self, members: impl IntoIterator, ) -> &Self { - for (t, name) in members.into_iter() { + for (t, name) in members { self.append(t, name, MemberAccess::NoAccess, MemberScope::NoScope); } self @@ -1865,7 +1997,58 @@ impl StructureBuilder { } } - // TODO : The other methods in the python version (type, members, remove, replace, etc) + pub fn members(&self) -> Array { + let mut count = 0; + let members_raw = unsafe { BNGetStructureBuilderMembers(self.handle, &mut count) }; + unsafe { Array::new(members_raw, count, ()) } + } + + pub fn index_by_name(&self, name: &str) -> Option { + self.members().iter().position(|member| member.name == name) + } + + pub fn index_by_offset(&self, offset: u64) -> Option { + self.members() + .iter() + .position(|member| member.offset == offset) + } + + // Setters + + pub fn clear_members(&self) { + let len = self.members().len(); + for idx in (0..len).rev() { + self.remove(idx) + } + } + + pub fn add_members<'a>(&self, members: impl IntoIterator) { + for member in members { + self.append(&member.ty, &member.name, member.access, member.scope); + } + } + + pub fn set_members<'a>(&self, members: impl IntoIterator) { + self.clear_members(); + self.add_members(members); + } + + pub fn remove(&self, index: usize) { + unsafe { BNRemoveStructureBuilderMember(self.handle, index) } + } + + pub fn replace(&self, index: usize, type_: Conf<&Type>, name: &str, overwrite: bool) { + let name = name.into_bytes_with_nul(); + let name_ptr = name.as_ptr() as *const _; + + let raw_type_ = BNTypeWithConfidence { + type_: type_.contents as *const Type as *mut _, + confidence: type_.confidence, + }; + unsafe { + BNReplaceStructureBuilderMember(self.handle, index, &raw_type_, name_ptr, overwrite) + } + } } impl From<&Structure> for StructureBuilder { @@ -1935,7 +2118,7 @@ impl Structure { pub fn members(&self) -> Result> { unsafe { - let mut count: usize = mem::zeroed(); + let mut count = 0; let members_raw: *mut BNStructureMember = BNGetStructureMembers(self.handle, &mut count); if members_raw.is_null() { @@ -2010,23 +2193,23 @@ impl ToOwned for Structure { #[derive(Debug, Clone)] pub struct StructureMember { pub ty: Conf>, - pub name: BnString, + pub name: String, pub offset: u64, pub access: MemberAccess, pub scope: MemberScope, } impl StructureMember { - pub fn new( + pub fn new( ty: Conf>, - name: T, + name: String, offset: u64, access: MemberAccess, scope: MemberScope, ) -> Self { Self { ty, - name: BnString::new(name), + name, offset, access, scope, @@ -2039,7 +2222,7 @@ impl StructureMember { RefCountable::inc_ref(&Type::from_raw(handle.type_)), handle.typeConfidence, ), - name: BnString::new(BnStr::from_raw(handle.name)), + name: CStr::from_ptr(handle.name).to_string_lossy().to_string(), offset: handle.offset, access: handle.access, scope: handle.scope, @@ -2047,6 +2230,21 @@ impl StructureMember { } } +impl CoreArrayProvider for StructureMember { + type Raw = BNStructureMember; + type Context = (); + type Wrapped<'a> = Guard<'a, StructureMember>; +} + +unsafe impl CoreArrayProviderInner for StructureMember { + unsafe fn free(raw: *mut Self::Raw, count: usize, _context: &Self::Context) { + BNFreeStructureMemberList(raw, count) + } + unsafe fn wrap_raw<'a>(raw: &'a Self::Raw, _context: &'a Self::Context) -> Self::Wrapped<'a> { + Guard::new(StructureMember::from_raw(*raw), &()) + } +} + #[derive(Debug, Clone)] pub struct InheritedStructureMember { pub base: Ref, @@ -2339,17 +2537,13 @@ impl Drop for QualifiedName { impl CoreArrayProvider for QualifiedName { type Raw = BNQualifiedName; type Context = (); + type Wrapped<'a> = &'a QualifiedName; } -unsafe impl CoreOwnedArrayProvider for QualifiedName { +unsafe impl CoreArrayProviderInner for QualifiedName { unsafe fn free(raw: *mut Self::Raw, count: usize, _context: &Self::Context) { BNFreeTypeNameList(raw, count); } -} - -unsafe impl<'a> CoreArrayWrapper<'a> for QualifiedName { - type Wrapped = &'a QualifiedName; - - unsafe fn wrap_raw(raw: &'a Self::Raw, _context: &'a Self::Context) -> Self::Wrapped { + unsafe fn wrap_raw<'a>(raw: &'a Self::Raw, _context: &'a Self::Context) -> Self::Wrapped<'a> { mem::transmute(raw) } } @@ -2381,17 +2575,13 @@ impl Drop for QualifiedNameAndType { impl CoreArrayProvider for QualifiedNameAndType { type Raw = BNQualifiedNameAndType; type Context = (); + type Wrapped<'a> = &'a QualifiedNameAndType; } -unsafe impl CoreOwnedArrayProvider for QualifiedNameAndType { +unsafe impl CoreArrayProviderInner for QualifiedNameAndType { unsafe fn free(raw: *mut Self::Raw, count: usize, _context: &Self::Context) { BNFreeTypeAndNameList(raw, count); } -} - -unsafe impl<'a> CoreArrayWrapper<'a> for QualifiedNameAndType { - type Wrapped = &'a QualifiedNameAndType; - - unsafe fn wrap_raw(raw: &'a Self::Raw, _context: &'a Self::Context) -> Self::Wrapped { + unsafe fn wrap_raw<'a>(raw: &'a Self::Raw, _context: &'a Self::Context) -> Self::Wrapped<'a> { mem::transmute(raw) } } @@ -2407,8 +2597,8 @@ impl QualifiedNameTypeAndId { unsafe { mem::transmute(&self.0.name) } } - pub fn id(&self) -> &BnStr { - unsafe { BnStr::from_raw(self.0.id) } + pub fn id(&self) -> &str { + unsafe { CStr::from_ptr(self.0.id).to_str().unwrap() } } pub fn type_object(&self) -> Guard { @@ -2427,17 +2617,13 @@ impl Drop for QualifiedNameTypeAndId { impl CoreArrayProvider for QualifiedNameTypeAndId { type Raw = BNQualifiedNameTypeAndId; type Context = (); + type Wrapped<'a> = &'a QualifiedNameTypeAndId; } -unsafe impl CoreOwnedArrayProvider for QualifiedNameTypeAndId { +unsafe impl CoreArrayProviderInner for QualifiedNameTypeAndId { unsafe fn free(raw: *mut Self::Raw, count: usize, _context: &Self::Context) { BNFreeTypeIdList(raw, count); } -} - -unsafe impl<'a> CoreArrayWrapper<'a> for QualifiedNameTypeAndId { - type Wrapped = &'a QualifiedNameTypeAndId; - - unsafe fn wrap_raw(raw: &'a Self::Raw, _context: &'a Self::Context) -> Self::Wrapped { + unsafe fn wrap_raw<'a>(raw: &'a Self::Raw, _context: &'a Self::Context) -> Self::Wrapped<'a> { mem::transmute(raw) } } @@ -2445,107 +2631,150 @@ unsafe impl<'a> CoreArrayWrapper<'a> for QualifiedNameTypeAndId { ////////////////////////// // NameAndType -pub struct NameAndType { - pub name: S, - pub t: Conf>, -} +pub struct NameAndType(pub(crate) BNNameAndType); -impl NameAndType { - pub(crate) fn from_raw(raw: &BNNameAndType) -> Self { - Self::new( - raw_to_string(raw.name).unwrap(), - unsafe { &Type::ref_from_raw(raw.type_) }, - raw.typeConfidence, - ) +impl NameAndType { + pub(crate) unsafe fn from_raw(raw: &BNNameAndType) -> Self { + Self(*raw) } } -impl NameAndType { - pub fn new(name: S, t: &Ref, confidence: u8) -> Self { - Self { - name, - t: Conf::new(t.clone(), confidence), +impl NameAndType { + pub fn new(name: S, t: &Type, confidence: u8) -> Ref { + unsafe { + Ref::new(Self(BNNameAndType { + name: BNAllocString(name.into_bytes_with_nul().as_ref().as_ptr() as *mut _), + type_: Ref::into_raw(t.to_owned()).handle, + typeConfidence: confidence, + })) } } - pub(crate) fn into_raw(self) -> BNNameAndType { - let t = self.t.clone(); - let res = BNNameAndType { - name: BnString::new(self.name).into_raw(), - type_: t.contents.handle, - typeConfidence: self.t.confidence, - }; - mem::forget(t); - res + pub fn name(&self) -> &str { + let c_str = unsafe { CStr::from_ptr(self.0.name) }; + c_str.to_str().unwrap() } - pub fn type_with_confidence(&self) -> Conf> { - self.t.clone() + pub fn t(&self) -> &Type { + unsafe { mem::transmute::<_, &Type>(&self.0.type_) } + } + + pub fn type_with_confidence(&self) -> Conf<&Type> { + Conf::new(self.t(), self.0.typeConfidence) } } -impl CoreArrayProvider for NameAndType { +impl ToOwned for NameAndType { + type Owned = Ref; + + fn to_owned(&self) -> Self::Owned { + unsafe { RefCountable::inc_ref(self) } + } +} + +unsafe impl RefCountable for NameAndType { + unsafe fn inc_ref(handle: &Self) -> Ref { + Self::new( + CStr::from_ptr(handle.0.name), + handle.t(), + handle.0.typeConfidence, + ) + } + + unsafe fn dec_ref(handle: &Self) { + unsafe { + BNFreeString(handle.0.name); + RefCountable::dec_ref(handle.t()); + } + } +} + +impl CoreArrayProvider for NameAndType { type Raw = BNNameAndType; type Context = (); + type Wrapped<'a> = Guard<'a, NameAndType>; } -unsafe impl CoreOwnedArrayProvider for NameAndType { +unsafe impl CoreArrayProviderInner for NameAndType { unsafe fn free(raw: *mut Self::Raw, count: usize, _context: &Self::Context) { BNFreeNameAndTypeList(raw, count); } -} - -unsafe impl<'a, S: 'a + BnStrCompatible> CoreArrayWrapper<'a> for NameAndType { - type Wrapped = &'a NameAndType; - - unsafe fn wrap_raw(raw: &'a Self::Raw, _context: &'a Self::Context) -> Self::Wrapped { - mem::transmute(raw) + unsafe fn wrap_raw<'a>(raw: &'a Self::Raw, _context: &'a Self::Context) -> Self::Wrapped<'a> { + unsafe { Guard::new(NameAndType::from_raw(raw), raw) } } } ////////////////// // DataVariable -pub struct DataVariable { - pub address: u64, - pub t: Conf>, - pub auto_discovered: bool, -} +#[repr(transparent)] +pub struct DataVariable(pub(crate) BNDataVariable); // impl DataVariable { // pub(crate) fn from_raw(var: &BNDataVariable) -> Self { -// Self { -// address: var.address, -// t: Conf::new(unsafe { Type::ref_from_raw(var.type_) }, var.typeConfidence), -// auto_discovered: var.autoDiscovered, -// } +// let var = DataVariable(*var); +// Self(BNDataVariable { +// type_: unsafe { Ref::into_raw(var.t().to_owned()).handle }, +// ..var.0 +// }) // } // } impl DataVariable { - pub fn type_with_confidence(&self) -> Conf> { - Conf::new(self.t.contents.clone(), self.t.confidence) + pub fn address(&self) -> u64 { + self.0.address + } + + pub fn auto_discovered(&self) -> bool { + self.0.autoDiscovered + } + + pub fn t(&self) -> &Type { + unsafe { mem::transmute(&self.0.type_) } + } + + pub fn type_with_confidence(&self) -> Conf<&Type> { + Conf::new(self.t(), self.0.typeConfidence) } pub fn symbol(&self, bv: &BinaryView) -> Option> { - bv.symbol_by_address(self.address).ok() + bv.symbol_by_address(self.0.address).ok() + } +} + +impl ToOwned for DataVariable { + type Owned = Ref; + + fn to_owned(&self) -> Self::Owned { + unsafe { RefCountable::inc_ref(self) } + } +} + +unsafe impl RefCountable for DataVariable { + unsafe fn inc_ref(handle: &Self) -> Ref { + unsafe { + Ref::new(Self(BNDataVariable { + type_: Ref::into_raw(handle.t().to_owned()).handle, + ..handle.0 + })) + } + } + + unsafe fn dec_ref(handle: &Self) { + unsafe { BNFreeType(handle.0.type_) } } } impl CoreArrayProvider for DataVariable { type Raw = BNDataVariable; type Context = (); + type Wrapped<'a> = &'a DataVariable; } -unsafe impl CoreOwnedArrayProvider for DataVariable { +unsafe impl CoreArrayProviderInner for DataVariable { unsafe fn free(raw: *mut Self::Raw, count: usize, _context: &Self::Context) { BNFreeDataVariables(raw, count); } -} - -unsafe impl<'a> CoreArrayWrapper<'a> for DataVariable { - type Wrapped = &'a DataVariable; - - unsafe fn wrap_raw(raw: &'a Self::Raw, _context: &'a Self::Context) -> Self::Wrapped { + unsafe fn wrap_raw<'a>(raw: &'a Self::Raw, _context: &'a Self::Context) -> Self::Wrapped<'a> { mem::transmute(raw) } } @@ -2586,29 +2815,6 @@ impl DataVariableAndName { } } -///////////////////////// -// ILIntrinsic - -#[derive(Clone, Copy, Debug, Eq, PartialEq)] -pub struct ILIntrinsic { - arch: CoreArchitecture, - index: u32, -} - -impl ILIntrinsic { - pub(crate) fn new(arch: CoreArchitecture, index: u32) -> Self { - Self { arch, index } - } - - pub fn name(&self) -> &str { - let name_ptr = unsafe { BNGetArchitectureIntrinsicName(self.arch.0, self.index) }; - let name_raw = unsafe { core::ffi::CStr::from_ptr(name_ptr) }; - name_raw.to_str().unwrap() - } - - // TODO impl inputs and outputs function -} - ///////////////////////// // RegisterValueType @@ -2761,3 +2967,818 @@ impl ConstantData { // mem::transmute(raw) // } // } + +///////////////////////// +// ValueRange + +#[repr(transparent)] +#[derive(Copy, Clone, Debug)] +pub struct ValueRange { + raw: BNValueRange, + _t: core::marker::PhantomData, +} + +impl ValueRange { + fn from_raw(value: BNValueRange) -> Self { + Self { + raw: value, + _t: core::marker::PhantomData, + } + } + fn into_raw(self) -> BNValueRange { + self.raw + } +} + +impl IntoIterator for ValueRange { + type Item = u64; + type IntoIter = core::iter::StepBy>; + + fn into_iter(self) -> Self::IntoIter { + (self.raw.start..self.raw.end).step_by(self.raw.step.try_into().unwrap()) + } +} +impl IntoIterator for ValueRange { + type Item = i64; + type IntoIter = core::iter::StepBy>; + + fn into_iter(self) -> Self::IntoIter { + (self.raw.start as i64..self.raw.end as i64).step_by(self.raw.step.try_into().unwrap()) + } +} + +///////////////////////// +// PossibleValueSet + +#[derive(Clone, Debug)] +pub enum PossibleValueSet { + UndeterminedValue, + EntryValue { + reg: i64, + }, + ConstantValue { + value: i64, + }, + ConstantPointerValue { + value: i64, + }, + ExternalPointerValue, + StackFrameOffset { + offset: i64, + }, + ReturnAddressValue, + ImportedAddressValue, + SignedRangeValue { + offset: i64, + ranges: Vec>, + }, + UnsignedRangeValue { + offset: i64, + ranges: Vec>, + }, + LookupTableValue { + tables: Vec, + }, + InSetOfValues { + values: HashSet, + }, + NotInSetOfValues { + values: HashSet, + }, + ConstantDataValue { + value_type: ConstantDataType, + value: i64, + }, +} + +#[derive(Copy, Clone, Debug)] +pub enum ConstantDataType { + Value, + ZeroExtend, + SignExtend, + Aggregate, +} + +impl PossibleValueSet { + pub(crate) unsafe fn from_raw(value: BNPossibleValueSet) -> Self { + unsafe fn from_range(value: BNPossibleValueSet) -> Vec> { + core::slice::from_raw_parts(value.ranges, value.count) + .iter() + .copied() + .map(|range| ValueRange::from_raw(range)) + .collect() + } + let from_sets = |value: BNPossibleValueSet| { + unsafe { core::slice::from_raw_parts(value.valueSet, value.count) } + .iter() + .copied() + .collect() + }; + use BNRegisterValueType::*; + match value.state { + UndeterminedValue => Self::UndeterminedValue, + EntryValue => Self::EntryValue { reg: value.value }, + ConstantValue => Self::ConstantValue { value: value.value }, + ConstantPointerValue => Self::ConstantPointerValue { value: value.value }, + StackFrameOffset => Self::StackFrameOffset { + offset: value.value, + }, + ConstantDataValue => Self::ConstantDataValue { + value_type: ConstantDataType::Value, + value: value.value, + }, + ConstantDataZeroExtendValue => Self::ConstantDataValue { + value_type: ConstantDataType::ZeroExtend, + value: value.value, + }, + ConstantDataSignExtendValue => Self::ConstantDataValue { + value_type: ConstantDataType::SignExtend, + value: value.value, + }, + ConstantDataAggregateValue => Self::ConstantDataValue { + value_type: ConstantDataType::Aggregate, + value: value.value, + }, + SignedRangeValue => Self::SignedRangeValue { + offset: value.value, + ranges: from_range(value), + }, + UnsignedRangeValue => Self::UnsignedRangeValue { + offset: value.value, + ranges: from_range(value), + }, + LookupTableValue => { + let raw_tables = unsafe { core::slice::from_raw_parts(value.table, value.count) }; + let raw_from_tables = |i: &BNLookupTableEntry| unsafe { + core::slice::from_raw_parts(i.fromValues, i.fromCount) + }; + let tables = raw_tables + .iter() + .map(|table| LookupTableEntry { + from_values: raw_from_tables(table).to_vec(), + to_value: table.toValue, + }) + .collect(); + Self::LookupTableValue { tables } + } + NotInSetOfValues => Self::NotInSetOfValues { + values: from_sets(value), + }, + InSetOfValues => Self::InSetOfValues { + values: from_sets(value), + }, + ImportedAddressValue => Self::ImportedAddressValue, + ReturnAddressValue => Self::ReturnAddressValue, + ExternalPointerValue => Self::ExternalPointerValue, + } + } + pub(crate) fn into_raw(self) -> PossibleValueSetRaw { + let mut raw: BNPossibleValueSet = unsafe { core::mem::zeroed() }; + // set the state field + raw.state = self.value_type().into_raw_value(); + // set all other fields + match self { + PossibleValueSet::UndeterminedValue + | PossibleValueSet::ExternalPointerValue + | PossibleValueSet::ReturnAddressValue + | PossibleValueSet::ImportedAddressValue => {} + PossibleValueSet::EntryValue { reg: value } + | PossibleValueSet::ConstantValue { value } + | PossibleValueSet::ConstantPointerValue { value } + | PossibleValueSet::ConstantDataValue { value, .. } + | PossibleValueSet::StackFrameOffset { offset: value } => raw.value = value, + PossibleValueSet::NotInSetOfValues { values } + | PossibleValueSet::InSetOfValues { values } => { + let values = Box::leak(values.into_iter().collect()); + raw.valueSet = values.as_mut_ptr(); + raw.count = values.len(); + } + PossibleValueSet::SignedRangeValue { offset, ranges } => { + let ranges = Box::leak(ranges.into_iter().map(|x| x.into_raw()).collect()); + raw.value = offset; + raw.ranges = ranges.as_mut_ptr(); + raw.count = ranges.len(); + } + PossibleValueSet::UnsignedRangeValue { offset, ranges } => { + let ranges = Box::leak(ranges.into_iter().map(|x| x.into_raw()).collect()); + raw.value = offset; + raw.ranges = ranges.as_mut_ptr(); + raw.count = ranges.len(); + } + PossibleValueSet::LookupTableValue { tables } => { + let tables = Box::leak(tables.into_iter().map(|table| table.into_raw()).collect()); + // SAFETY: BNLookupTableEntry and LookupTableEntryRaw are transparent + raw.table = tables.as_mut_ptr() as *mut BNLookupTableEntry; + raw.count = tables.len(); + } + } + PossibleValueSetRaw(raw) + } + + pub fn value_type(&self) -> RegisterValueType { + use RegisterValueType::*; + match self { + PossibleValueSet::UndeterminedValue => UndeterminedValue, + PossibleValueSet::EntryValue { .. } => EntryValue, + PossibleValueSet::ConstantValue { .. } => ConstantValue, + PossibleValueSet::ConstantPointerValue { .. } => ConstantPointerValue, + PossibleValueSet::ExternalPointerValue => ExternalPointerValue, + PossibleValueSet::StackFrameOffset { .. } => StackFrameOffset, + PossibleValueSet::ReturnAddressValue => ReturnAddressValue, + PossibleValueSet::ImportedAddressValue => ImportedAddressValue, + PossibleValueSet::SignedRangeValue { .. } => SignedRangeValue, + PossibleValueSet::UnsignedRangeValue { .. } => UnsignedRangeValue, + PossibleValueSet::LookupTableValue { .. } => LookupTableValue, + PossibleValueSet::InSetOfValues { .. } => InSetOfValues, + PossibleValueSet::NotInSetOfValues { .. } => NotInSetOfValues, + PossibleValueSet::ConstantDataValue { + value_type: ConstantDataType::Value, + .. + } => ConstantDataValue, + PossibleValueSet::ConstantDataValue { + value_type: ConstantDataType::ZeroExtend, + .. + } => ConstantDataZeroExtendValue, + PossibleValueSet::ConstantDataValue { + value_type: ConstantDataType::SignExtend, + .. + } => ConstantDataSignExtendValue, + PossibleValueSet::ConstantDataValue { + value_type: ConstantDataType::Aggregate, + .. + } => ConstantDataAggregateValue, + } + } +} + +/// The owned version of the BNPossibleValueSet +#[repr(transparent)] +pub(crate) struct PossibleValueSetRaw(BNPossibleValueSet); + +impl PossibleValueSetRaw { + pub fn as_ffi(&self) -> &BNPossibleValueSet { + &self.0 + } +} + +impl Drop for PossibleValueSetRaw { + fn drop(&mut self) { + use BNRegisterValueType::*; + match self.0.state { + UndeterminedValue + | ExternalPointerValue + | ReturnAddressValue + | ImportedAddressValue + | EntryValue + | ConstantValue + | ConstantPointerValue + | StackFrameOffset + | ConstantDataValue + | ConstantDataZeroExtendValue + | ConstantDataSignExtendValue + | ConstantDataAggregateValue => {} + InSetOfValues | NotInSetOfValues => { + let _values: Box<[i64]> = unsafe { + Box::from_raw(ptr::slice_from_raw_parts_mut(self.0.valueSet, self.0.count)) + }; + } + SignedRangeValue | UnsignedRangeValue => { + let _ranges: Box<[BNValueRange]> = unsafe { + Box::from_raw(ptr::slice_from_raw_parts_mut(self.0.ranges, self.0.count)) + }; + } + LookupTableValue => { + // SAFETY: LookupTableEntryRaw and BNLookupTableEntry can be safely transmuted + let table_ptr = self.0.table as *mut LookupTableEntryRaw; + let _table: Box<[LookupTableEntryRaw]> = unsafe { + Box::from_raw(ptr::slice_from_raw_parts_mut(table_ptr, self.0.count)) + }; + } + } + } +} + +///////////////////////// +// LookupTableEntry + +#[derive(Clone, Debug)] +pub struct LookupTableEntry { + pub from_values: Vec, + pub to_value: i64, +} + +impl LookupTableEntry { + fn into_raw(self) -> LookupTableEntryRaw { + let from_value = Box::leak(self.from_values.into_boxed_slice()); + LookupTableEntryRaw(BNLookupTableEntry { + toValue: self.to_value, + fromValues: from_value.as_mut_ptr(), + fromCount: from_value.len(), + }) + } +} + +/// The owned version of the BNLookupTableEntry +#[repr(transparent)] +struct LookupTableEntryRaw(BNLookupTableEntry); +impl Drop for LookupTableEntryRaw { + fn drop(&mut self) { + let _from_value: Box<[i64]> = unsafe { + Box::from_raw(ptr::slice_from_raw_parts_mut( + self.0.fromValues, + self.0.fromCount, + )) + }; + } +} + +///////////////////////// +// ArchAndAddr + +#[derive(Copy, Clone, Eq, Hash, PartialEq)] +pub struct ArchAndAddr { + pub arch: CoreArchitecture, + pub address: u64, +} + +///////////////////////// +// UserVariableValues + +pub struct UserVariableValues { + pub(crate) vars: *const [BNUserVariableValue], +} + +impl UserVariableValues { + pub fn into_hashmap(self) -> HashMap> { + let mut result: HashMap> = HashMap::new(); + for (var, def_site, possible_val) in self.all() { + result + .entry(var) + .or_default() + .entry(def_site) + .or_insert(possible_val); + } + result + } + pub fn all(&self) -> impl Iterator { + unsafe { &*self.vars }.iter().map(|var_val| { + let var = unsafe { Variable::from_raw(var_val.var) }; + let def_site = ArchAndAddr { + arch: unsafe { CoreArchitecture::from_raw(var_val.defSite.arch) }, + address: var_val.defSite.address, + }; + let possible_val = unsafe { PossibleValueSet::from_raw(var_val.value) }; + (var, def_site, possible_val) + }) + } + pub fn values_from_variable( + &self, + var: Variable, + ) -> impl Iterator { + self.all() + .filter(move |(t_var, _, _)| t_var == &var) + .map(|(_var, def_site, possible_val)| (def_site, possible_val)) + } +} + +impl Drop for UserVariableValues { + fn drop(&mut self) { + unsafe { BNFreeUserVariableValues(self.vars as *mut BNUserVariableValue) }; + } +} + +///////////////////////// +// ConstantReference + +#[derive(Copy, Clone, Eq, Hash, PartialEq)] +pub struct ConstantReference { + pub value: i64, + pub size: usize, + pub pointer: bool, + pub intermediate: bool, +} + +impl ConstantReference { + pub fn from_raw(value: BNConstantReference) -> Self { + Self { + value: value.value, + size: value.size, + pointer: value.pointer, + intermediate: value.intermediate, + } + } + pub fn into_raw(self) -> BNConstantReference { + BNConstantReference { + value: self.value, + size: self.size, + pointer: self.pointer, + intermediate: self.intermediate, + } + } +} + +impl CoreArrayProvider for ConstantReference { + type Raw = BNConstantReference; + type Context = (); + type Wrapped<'a> = Self; +} + +unsafe impl CoreArrayProviderInner for ConstantReference { + unsafe fn free(raw: *mut Self::Raw, _count: usize, _context: &Self::Context) { + BNFreeConstantReferenceList(raw) + } + unsafe fn wrap_raw<'a>(raw: &'a Self::Raw, _context: &'a Self::Context) -> Self::Wrapped<'a> { + Self::from_raw(*raw) + } +} + +///////////////////////// +// IndirectBranchInfo + +pub struct IndirectBranchInfo { + pub source_arch: CoreArchitecture, + pub source_addr: u64, + pub dest_arch: CoreArchitecture, + pub dest_addr: u64, + pub auto_defined: bool, +} + +impl IndirectBranchInfo { + pub fn from_raw(value: BNIndirectBranchInfo) -> Self { + Self { + source_arch: unsafe { CoreArchitecture::from_raw(value.sourceArch) }, + source_addr: value.sourceAddr, + dest_arch: unsafe { CoreArchitecture::from_raw(value.destArch) }, + dest_addr: value.destAddr, + auto_defined: value.autoDefined, + } + } + pub fn into_raw(self) -> BNIndirectBranchInfo { + BNIndirectBranchInfo { + sourceArch: self.source_arch.0, + sourceAddr: self.source_addr, + destArch: self.dest_arch.0, + destAddr: self.dest_addr, + autoDefined: self.auto_defined, + } + } +} + +impl CoreArrayProvider for IndirectBranchInfo { + type Raw = BNIndirectBranchInfo; + type Context = (); + type Wrapped<'a> = Self; +} + +unsafe impl CoreArrayProviderInner for IndirectBranchInfo { + unsafe fn free(raw: *mut Self::Raw, _count: usize, _context: &Self::Context) { + BNFreeIndirectBranchList(raw) + } + unsafe fn wrap_raw<'a>(raw: &'a Self::Raw, _context: &'a Self::Context) -> Self::Wrapped<'a> { + Self::from_raw(*raw) + } +} + +///////////////////////// +// HighlightStandardColor + +#[derive(Debug, Copy, Clone, Hash, PartialEq, Eq)] +pub enum HighlightStandardColor { + //NoHighlightColor, + BlueHighlightColor, + GreenHighlightColor, + CyanHighlightColor, + RedHighlightColor, + MagentaHighlightColor, + YellowHighlightColor, + OrangeHighlightColor, + WhiteHighlightColor, + BlackHighlightColor, +} + +impl HighlightStandardColor { + pub fn from_raw(value: BNHighlightStandardColor) -> Option { + Some(match value { + BNHighlightStandardColor::NoHighlightColor => return None, + BNHighlightStandardColor::BlueHighlightColor => Self::BlueHighlightColor, + BNHighlightStandardColor::GreenHighlightColor => Self::GreenHighlightColor, + BNHighlightStandardColor::CyanHighlightColor => Self::CyanHighlightColor, + BNHighlightStandardColor::RedHighlightColor => Self::RedHighlightColor, + BNHighlightStandardColor::MagentaHighlightColor => Self::MagentaHighlightColor, + BNHighlightStandardColor::YellowHighlightColor => Self::YellowHighlightColor, + BNHighlightStandardColor::OrangeHighlightColor => Self::OrangeHighlightColor, + BNHighlightStandardColor::WhiteHighlightColor => Self::WhiteHighlightColor, + BNHighlightStandardColor::BlackHighlightColor => Self::BlackHighlightColor, + }) + } + pub fn into_raw(self) -> BNHighlightStandardColor { + match self { + //Self::NoHighlightColor => BNHighlightStandardColor::NoHighlightColor, + Self::BlueHighlightColor => BNHighlightStandardColor::BlueHighlightColor, + Self::GreenHighlightColor => BNHighlightStandardColor::GreenHighlightColor, + Self::CyanHighlightColor => BNHighlightStandardColor::CyanHighlightColor, + Self::RedHighlightColor => BNHighlightStandardColor::RedHighlightColor, + Self::MagentaHighlightColor => BNHighlightStandardColor::MagentaHighlightColor, + Self::YellowHighlightColor => BNHighlightStandardColor::YellowHighlightColor, + Self::OrangeHighlightColor => BNHighlightStandardColor::OrangeHighlightColor, + Self::WhiteHighlightColor => BNHighlightStandardColor::WhiteHighlightColor, + Self::BlackHighlightColor => BNHighlightStandardColor::BlackHighlightColor, + } + } +} + +///////////////////////// +// HighlightColor + +#[derive(Debug, Copy, Clone)] +pub enum HighlightColor { + NoHighlightColor { + alpha: u8, + }, + StandardHighlightColor { + color: HighlightStandardColor, + alpha: u8, + }, + MixedHighlightColor { + color: HighlightStandardColor, + mix_color: HighlightStandardColor, + mix: u8, + alpha: u8, + }, + CustomHighlightColor { + r: u8, + g: u8, + b: u8, + alpha: u8, + }, +} + +impl HighlightColor { + pub fn from_raw(raw: BNHighlightColor) -> Self { + const HIGHLIGHT_COLOR: u32 = BNHighlightColorStyle::StandardHighlightColor as u32; + const MIXED_HIGHLIGHT_COLOR: u32 = BNHighlightColorStyle::MixedHighlightColor as u32; + const CUSTOM_HIGHLIHGT_COLOR: u32 = BNHighlightColorStyle::CustomHighlightColor as u32; + match raw.style as u32 { + HIGHLIGHT_COLOR => { + let Some(color) = HighlightStandardColor::from_raw(raw.color) else { + // StandardHighlightColor with NoHighlightColor, is no color + return Self::NoHighlightColor { alpha: raw.alpha }; + }; + Self::StandardHighlightColor { + color, + alpha: raw.alpha, + } + } + MIXED_HIGHLIGHT_COLOR => { + let Some(color) = HighlightStandardColor::from_raw(raw.color) else { + panic!("Highlight mixed color with no color"); + }; + let Some(mix_color) = HighlightStandardColor::from_raw(raw.mixColor) else { + panic!("Highlight mixed color with no mix_color"); + }; + Self::MixedHighlightColor { + color, + mix_color, + mix: raw.mix, + alpha: raw.alpha, + } + } + CUSTOM_HIGHLIHGT_COLOR => Self::CustomHighlightColor { + r: raw.r, + g: raw.g, + b: raw.b, + alpha: raw.alpha, + }, + // other color style is just no color + _ => Self::NoHighlightColor { alpha: u8::MAX }, + } + } + + pub fn into_raw(self) -> BNHighlightColor { + let zeroed: BNHighlightColor = unsafe { core::mem::zeroed() }; + match self { + Self::NoHighlightColor { alpha } => BNHighlightColor { + style: BNHighlightColorStyle::StandardHighlightColor, + color: BNHighlightStandardColor::NoHighlightColor, + alpha, + ..zeroed + }, + Self::StandardHighlightColor { color, alpha } => BNHighlightColor { + style: BNHighlightColorStyle::StandardHighlightColor, + color: color.into_raw(), + alpha, + ..zeroed + }, + Self::MixedHighlightColor { + color, + mix_color, + mix, + alpha, + } => BNHighlightColor { + color: color.into_raw(), + mixColor: mix_color.into_raw(), + mix, + alpha, + ..zeroed + }, + Self::CustomHighlightColor { r, g, b, alpha } => BNHighlightColor { + r, + g, + b, + alpha, + ..zeroed + }, + } + } +} + +///////////////////////// +// IntegerDisplayType + +pub type IntegerDisplayType = binaryninjacore_sys::BNIntegerDisplayType; + +///////////////////////// +// StackVariableReference + +#[derive(Debug, Clone)] +pub struct StackVariableReference { + _source_operand: u32, + var_type: Conf>, + name: BnString, + var: Variable, + offset: i64, + size: usize, +} + +impl StackVariableReference { + pub fn from_raw(value: BNStackVariableReference) -> Self { + let var_type = Conf::new( + unsafe { Type::ref_from_raw(value.type_) }, + value.typeConfidence, + ); + let name = unsafe { BnString::from_raw(value.name) }; + let var = unsafe { Variable::from_identifier(value.varIdentifier) }; + let offset = value.referencedOffset; + let size = value.size; + Self { + _source_operand: value.sourceOperand, + var_type, + name, + var, + offset, + size, + } + } + pub fn variable(&self) -> &Variable { + &self.var + } + pub fn variable_type(&self) -> Conf<&Type> { + self.var_type.as_ref() + } + pub fn name(&self) -> &str { + self.name.as_str() + } + pub fn offset(&self) -> i64 { + self.offset + } + pub fn size(&self) -> usize { + self.size + } +} + +impl CoreArrayProvider for StackVariableReference { + type Raw = BNStackVariableReference; + type Context = (); + type Wrapped<'a> = Guard<'a, Self>; +} + +unsafe impl CoreArrayProviderInner for StackVariableReference { + unsafe fn free(raw: *mut Self::Raw, count: usize, _context: &Self::Context) { + BNFreeStackVariableReferenceList(raw, count) + } + unsafe fn wrap_raw<'a>(raw: &'a Self::Raw, context: &'a Self::Context) -> Self::Wrapped<'a> { + Guard::new(Self::from_raw(*raw), context) + } +} + +///////////////////////// +// RegisterStackAdjustment + +#[derive(Debug, Copy, Clone)] +pub struct RegisterStackAdjustment { + reg_id: u32, + adjustment: Conf, + arch: A::Handle, +} + +impl RegisterStackAdjustment
{ + pub(crate) unsafe fn from_raw(value: BNRegisterStackAdjustment, arch: A::Handle) -> Self { + RegisterStackAdjustment { + reg_id: value.regStack, + adjustment: Conf::new(value.adjustment, value.confidence), + arch, + } + } + pub(crate) fn into_raw(self) -> BNRegisterStackAdjustment { + BNRegisterStackAdjustment { + regStack: self.reg_id, + adjustment: self.adjustment.contents, + confidence: self.adjustment.confidence, + } + } + pub fn new(reg_id: u32, adjustment: I, arch_handle: A::Handle) -> Self + where + I: Into>, + { + Self { + reg_id, + adjustment: adjustment.into(), + arch: arch_handle, + } + } + pub const fn register_id(&self) -> u32 { + self.reg_id + } + pub fn register(&self) -> A::Register { + self.arch.borrow().register_from_id(self.reg_id).unwrap() + } +} + +impl CoreArrayProvider for RegisterStackAdjustment { + type Raw = BNRegisterStackAdjustment; + type Context = A::Handle; + type Wrapped<'a> = Self; +} + +unsafe impl CoreArrayProviderInner for RegisterStackAdjustment { + unsafe fn free(raw: *mut Self::Raw, _count: usize, _context: &Self::Context) { + BNFreeRegisterStackAdjustments(raw) + } + unsafe fn wrap_raw<'a>(raw: &'a Self::Raw, context: &'a Self::Context) -> Self::Wrapped<'a> { + Self::from_raw(*raw, context.clone()) + } +} + +///////////////////////// +// RegisterStackAdjustment + +// NOTE only exists as part of an Array, never owned +pub struct MergedVariable { + target: Variable, + // droped by the CoreArrayProviderInner::free + sources: ManuallyDrop>, +} + +impl MergedVariable { + pub fn target(&self) -> Variable { + self.target + } + pub fn sources(&self) -> &Array { + &self.sources + } +} + +impl CoreArrayProvider for MergedVariable { + type Raw = BNMergedVariable; + type Context = (); + type Wrapped<'a> = Self; +} + +unsafe impl CoreArrayProviderInner for MergedVariable { + unsafe fn free(raw: *mut Self::Raw, count: usize, _context: &Self::Context) { + BNFreeMergedVariableList(raw, count) + } + unsafe fn wrap_raw<'a>(raw: &'a Self::Raw, _context: &'a Self::Context) -> Self::Wrapped<'a> { + Self { + target: Variable::from_raw(raw.target), + sources: ManuallyDrop::new(Array::new(raw.sources, raw.sourceCount, ())), + } + } +} + +///////////////////////// +// UnresolvedIndirectBranches + +// NOTE only exists as part of an Array, never owned +pub struct UnresolvedIndirectBranches(u64); + +impl UnresolvedIndirectBranches { + pub fn address(&self) -> u64 { + self.0 + } +} + +impl CoreArrayProvider for UnresolvedIndirectBranches { + type Raw = u64; + type Context = (); + type Wrapped<'a> = Self; +} + +unsafe impl CoreArrayProviderInner for UnresolvedIndirectBranches { + unsafe fn free(raw: *mut Self::Raw, _count: usize, _context: &Self::Context) { + BNFreeAddressList(raw) + } + unsafe fn wrap_raw<'a>(raw: &'a Self::Raw, _context: &'a Self::Context) -> Self::Wrapped<'a> { + Self(*raw) + } +} diff --git a/src/update.rs b/src/update.rs new file mode 100644 index 0000000..026316f --- /dev/null +++ b/src/update.rs @@ -0,0 +1,272 @@ +use core::{ffi, mem, ptr}; +use std::time::{Duration, SystemTime, UNIX_EPOCH}; + +use binaryninjacore_sys::*; + +use crate::rc::{Array, CoreArrayProvider, CoreArrayProviderInner}; +use crate::string::BnString; + +pub type UpdateResult = BNUpdateResult; + +#[repr(C)] +pub struct UpdateChannel { + pub name: BnString, + pub description: BnString, + pub latest_version: BnString, + // NOTE don't allow the user to create his own UpdateChannel + _lock: core::marker::PhantomData<()>, +} + +impl UpdateChannel { + pub(crate) unsafe fn ref_from_raw(handle: &BNUpdateChannel) -> &Self { + mem::transmute(handle) + } + + pub fn all() -> Result, BnString> { + let mut count = 0; + let mut errors = ptr::null_mut(); + let result = unsafe { BNGetUpdateChannels(&mut count, &mut errors) }; + if !errors.is_null() { + Err(unsafe { BnString::from_raw(errors) }) + } else { + assert!(!result.is_null()); + Ok(unsafe { Array::new(result, count, ()) }) + } + } + + /// List of versions + pub fn versions(&self) -> Result, BnString> { + let mut count = 0; + let mut errors = ptr::null_mut(); + let result = + unsafe { BNGetUpdateChannelVersions(self.name.as_ptr(), &mut count, &mut errors) }; + if !errors.is_null() { + Err(unsafe { BnString::from_raw(errors) }) + } else { + assert!(!result.is_null()); + Ok(unsafe { Array::new(result, count, ()) }) + } + } + + /// Latest version + pub fn latest_version(&self) -> Result { + let last_version = &self.latest_version; + let versions = self.versions()?; + for version in &versions { + if &version.version == last_version { + return Ok(version.clone()); + } + } + panic!(); + } + + /// Whether updates are available + pub fn updates_available(&self) -> Result { + let mut errors = ptr::null_mut(); + let result = unsafe { + BNAreUpdatesAvailable( + self.name.as_ptr(), + ptr::null_mut(), + ptr::null_mut(), + &mut errors, + ) + }; + if !errors.is_null() { + Err(unsafe { BnString::from_raw(errors) }) + } else { + Ok(result) + } + } + + pub fn update_to_latest(&self) -> Result { + let mut errors = ptr::null_mut(); + let result = unsafe { + BNUpdateToLatestVersion( + self.name.as_ptr(), + &mut errors, + Some(cb_progress_nop), + ptr::null_mut(), + ) + }; + if !errors.is_null() { + Err(unsafe { BnString::from_raw(errors) }) + } else { + Ok(result) + } + } + + pub fn update_to_latest_with_progress( + &self, + mut progress: F, + ) -> Result + where + F: FnMut(u64, u64) -> bool, + { + let mut errors = ptr::null_mut(); + let result = unsafe { + BNUpdateToLatestVersion( + self.name.as_ptr(), + &mut errors, + Some(cb_progress::), + &mut progress as *mut _ as *mut ffi::c_void, + ) + }; + if !errors.is_null() { + Err(unsafe { BnString::from_raw(errors) }) + } else { + Ok(result) + } + } + + pub fn update(&self, version: &UpdateVersion) -> Result { + let mut errors = ptr::null_mut(); + let result = unsafe { + BNUpdateToVersion( + self.name.as_ptr(), + version.version.as_ptr(), + &mut errors, + Some(cb_progress_nop), + ptr::null_mut(), + ) + }; + if !errors.is_null() { + Err(unsafe { BnString::from_raw(errors) }) + } else { + Ok(result) + } + } + + pub fn update_with_progress( + &self, + version: &UpdateVersion, + mut progress: F, + ) -> Result + where + F: FnMut(u64, u64) -> bool, + { + let mut errors = ptr::null_mut(); + let result = unsafe { + BNUpdateToVersion( + self.name.as_ptr(), + version.version.as_ptr(), + &mut errors, + Some(cb_progress::), + &mut progress as *mut _ as *mut ffi::c_void, + ) + }; + if !errors.is_null() { + Err(unsafe { BnString::from_raw(errors) }) + } else { + Ok(result) + } + } +} + +impl CoreArrayProvider for UpdateChannel { + type Raw = BNUpdateChannel; + type Context = (); + type Wrapped<'a> = &'a Self; +} + +unsafe impl CoreArrayProviderInner for UpdateChannel { + unsafe fn free(raw: *mut Self::Raw, count: usize, _context: &Self::Context) { + BNFreeUpdateChannelList(raw, count) + } + + unsafe fn wrap_raw<'a>(raw: &'a Self::Raw, _context: &'a Self::Context) -> Self::Wrapped<'a> { + UpdateChannel::ref_from_raw(raw) + } +} + +#[repr(C)] +#[derive(Clone)] +pub struct UpdateVersion { + pub version: BnString, + pub notes: BnString, + time: u64, + // NOTE don't allow the user to create his own UpdateVersion + _lock: core::marker::PhantomData<()>, +} + +impl UpdateVersion { + pub(crate) unsafe fn ref_from_raw(handle: &BNUpdateVersion) -> &Self { + mem::transmute(handle) + } + + pub fn time(&self) -> SystemTime { + UNIX_EPOCH + Duration::from_secs(self.time) + } + + pub fn set_time(&mut self, time: SystemTime) { + let epoch = time.duration_since(UNIX_EPOCH).unwrap(); + self.time = epoch.as_secs(); + } +} + +impl CoreArrayProvider for UpdateVersion { + type Raw = BNUpdateVersion; + type Context = (); + type Wrapped<'a> = &'a Self; +} + +unsafe impl CoreArrayProviderInner for UpdateVersion { + unsafe fn free(raw: *mut Self::Raw, count: usize, _context: &Self::Context) { + BNFreeUpdateChannelVersionList(raw, count) + } + + unsafe fn wrap_raw<'a>(raw: &'a Self::Raw, _context: &'a Self::Context) -> Self::Wrapped<'a> { + UpdateVersion::ref_from_raw(raw) + } +} + +/// queries if auto updates are enabled. +pub fn are_auto_updates_enabled() -> bool { + unsafe { BNAreAutoUpdatesEnabled() } +} + +/// sets auto update enabled status. +pub fn set_auto_updates_enabled(enabled: bool) { + unsafe { BNSetAutoUpdatesEnabled(enabled) } +} + +/// returns the time stamp for the last time updates were checked. +pub fn get_time_since_last_update_check() -> u64 { + unsafe { BNGetTimeSinceLastUpdateCheck() } +} + +/// whether an update has been downloaded and is waiting installation +pub fn is_update_installation_pending() -> bool { + unsafe { BNIsUpdateInstallationPending() } +} + +/// installs any pending updates +pub fn install_pending_update() -> Result<(), BnString> { + let mut errors = ptr::null_mut(); + unsafe { BNInstallPendingUpdate(&mut errors) }; + if !errors.is_null() { + Err(unsafe { BnString::from_raw(errors) }) + } else { + Ok(()) + } +} + +pub fn updates_checked() { + unsafe { BNUpdatesChecked() } +} + +unsafe extern "C" fn cb_progress_nop( + _ctxt: *mut ::std::os::raw::c_void, + _progress: u64, + _total: u64, +) -> bool { + true +} + +unsafe extern "C" fn cb_progress bool>( + ctxt: *mut ::std::os::raw::c_void, + progress: u64, + total: u64, +) -> bool { + let ctxt: &mut F = &mut *(ctxt as *mut F); + ctxt(progress, total) +}