commit 06c785c352e18f30376336af31b3988af5f4e780 Author: Rairosu Date: Wed Jan 24 13:07:27 2024 +0100 initial commit diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000..6489456 --- /dev/null +++ b/.gitmodules @@ -0,0 +1,3 @@ +[submodule "binaryninja-api"] + path = binaryninja-api + url = https://github.com/Vector35/binaryninja-api diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..c46d990 --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,63 @@ +cmake_minimum_required(VERSION 3.13 FATAL_ERROR) + +project(rust_api) + +set(CARGO_API_VERSION stable-2022-12-15) + +file(GLOB RUST_API_SOURCES CONFIGURE_DEPENDS + ../binaryninjacore.h + binaryninjacore-sys/build.rs + binaryninjacore-sys/Cargo.toml + binaryninjacore-sys/src/* + Cargo.toml + src/*.rs) + +if(CMAKE_BUILD_TYPE MATCHES Debug) + set(CARGO_CHECK_OPTS --workspace) + set(CARGO_DOC_OPTS --no-deps) +else() + set(CARGO_CHECK_OPTS --workspace --release) + set(CARGO_DOC_OPTS --no-deps --release) +endif() + +if (BN_BUILD_NUMBER) + set(RUST_DOC_FLAGS "--crate-version ${BN_MAJOR_VERSION}.${BN_MINOR_VERSION}.${BN_BUILD_NUMBER}") +else() + set(RUST_DOC_FLAGS "") +endif() + +# TODO : RUSTDOCFLAGS="--theme binja-dark.css --default-theme=binja-dark" + +add_custom_target(rust_api ALL DEPENDS) +add_dependencies(rust_api binaryninjaapi) + +add_custom_target(rust_docs ALL DEPENDS ${PROJECT_BINARY_DIR}/target/doc) +add_dependencies(rust_docs binaryninjaapi rust_api) + +# Get API source directory so we can find BinaryNinjaCore +get_target_property(BN_API_SOURCE_DIR binaryninjaapi SOURCE_DIR) +message(STATUS "${BN_API_SOURCE_DIR}") +list(APPEND CMAKE_MODULE_PATH "${BN_API_SOURCE_DIR}/cmake") + +find_program(RUSTUP_PATH rustup REQUIRED HINTS ~/.cargo/bin) +set(RUSTUP_CHECK_COMMAND ${RUSTUP_PATH} run ${CARGO_API_VERSION} cargo check) +set(RUSTUP_DOC_COMMAND ${RUSTUP_PATH} run ${CARGO_API_VERSION} cargo doc) + +add_custom_command( + TARGET rust_api + PRE_BUILD + COMMAND ${CMAKE_COMMAND} -E env BINARYNINJADIR=${BN_INSTALL_BIN_DIR} ${RUSTUP_CHECK_COMMAND} ${CARGO_CHECK_OPTS} + WORKING_DIRECTORY ${PROJECT_SOURCE_DIR} + DEPENDS ${RUST_API_SOURCES} +) + +add_custom_command( + OUTPUT ${PROJECT_BINARY_DIR}/target/doc + COMMAND ${CMAKE_COMMAND} -E env BINARYNINJADIR=${BN_INSTALL_BIN_DIR} RUSTDOCFLAGS=${RUST_DOC_FLAGS} ${RUSTUP_DOC_COMMAND} ${CARGO_DOC_OPTS} + WORKING_DIRECTORY ${PROJECT_SOURCE_DIR} + DEPENDS ${RUST_API_SOURCES} +) + +if(BN_API_BUILD_EXAMPLES) + add_subdirectory(examples/basic_script) +endif() diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 0000000..42f4ed8 --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,1126 @@ +# 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 = "ahash" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2c99f64d1e06488f620f932677e24bc6e2897582980441ae90a671415bd7ec2f" +dependencies = [ + "cfg-if", + "once_cell", + "version_check", +] + +[[package]] +name = "anstream" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ca84f3628370c59db74ee214b3263d58f9aadd9b4fe7e711fd87dc452b7f163" +dependencies = [ + "anstyle", + "anstyle-parse", + "anstyle-query", + "anstyle-wincon", + "colorchoice", + "is-terminal", + "utf8parse", +] + +[[package]] +name = "anstyle" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3a30da5c5f2d5e72842e00bcb57657162cdabef0931f40e2deb9b4140440cecd" + +[[package]] +name = "anstyle-parse" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "938874ff5980b03a87c5524b3ae5b59cf99b1d6bc836848df7bc5ada9643c333" +dependencies = [ + "utf8parse", +] + +[[package]] +name = "anstyle-query" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5ca11d4be1bab0c8bc8734a9aa7bf4ee8316d462a08c6ac5052f888fef5b494b" +dependencies = [ + "windows-sys", +] + +[[package]] +name = "anstyle-wincon" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "180abfa45703aebe0093f79badacc01b8fd4ea2e35118747e5811127f926e188" +dependencies = [ + "anstyle", + "windows-sys", +] + +[[package]] +name = "autocfg" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cdb031dd78e28731d87d56cc8ffef4a8f36ca26c38fe2de700543e627f8a464a" + +[[package]] +name = "basic_script" +version = "0.1.0" +dependencies = [ + "binaryninja", +] + +[[package]] +name = "binaryninja" +version = "0.1.0" +dependencies = [ + "binaryninjacore-sys", + "lazy_static", + "libc", + "log", + "rayon", +] + +[[package]] +name = "binaryninjacore-sys" +version = "0.1.0" +dependencies = [ + "bindgen", +] + +[[package]] +name = "bindgen" +version = "0.66.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2b84e06fc203107bfbad243f4aba2af864eb7db3b1cf46ea0a023b0b433d2a7" +dependencies = [ + "bitflags 2.3.3", + "cexpr", + "clang-sys", + "lazy_static", + "lazycell", + "log", + "peeking_take_while", + "prettyplease", + "proc-macro2", + "quote", + "regex", + "rustc-hash", + "shlex", + "syn 2.0.25", + "which", +] + +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + +[[package]] +name = "bitflags" +version = "2.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "630be753d4e58660abd17930c71b647fe46c27ea6b63cc59e1e3851406972e42" + +[[package]] +name = "cc" +version = "1.0.79" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "50d30906286121d95be3d479533b458f87493b30a4b5f79a607db8f5d11aa91f" + +[[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.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "853eda514c284c2287f4bf20ae614f8781f40a81d32ecda6e91449304dfe077c" +dependencies = [ + "glob", + "libc", + "libloading", +] + +[[package]] +name = "clap" +version = "4.3.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1640e5cc7fb47dbb8338fd471b105e7ed6c3cb2aeb00c2e067127ffd3764a05d" +dependencies = [ + "clap_builder", + "clap_derive", + "once_cell", +] + +[[package]] +name = "clap_builder" +version = "4.3.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "98c59138d527eeaf9b53f35a77fcc1fad9d883116070c63d5de1c7dc7b00c72b" +dependencies = [ + "anstream", + "anstyle", + "clap_lex", + "strsim", +] + +[[package]] +name = "clap_derive" +version = "4.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8cd2b2a819ad6eec39e8f1d6b53001af1e5469f8c177579cdaeb313115b825f" +dependencies = [ + "heck", + "proc-macro2", + "quote", + "syn 2.0.25", +] + +[[package]] +name = "clap_lex" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2da6da31387c7e4ef160ffab6d5e7f00c42626fe39aea70a7b0f1773f7dd6c1b" + +[[package]] +name = "colorchoice" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "acbf1af155f9b9ef647e42cdc158db4b64a1b61f743629225fde6f3e0be2a7c7" + +[[package]] +name = "crc32fast" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b540bd8bc810d3885c6ea91e2018302f68baba2129ab3e88f32389ee9370880d" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "crossbeam-channel" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06ed27e177f16d65f0f0c22a213e17c696ace5dd64b14258b52f9417ccb52db4" +dependencies = [ + "cfg-if", + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-deque" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94af6efb46fef72616855b036a624cf27ba656ffc9be1b9a3c931cfc7749a9a9" +dependencies = [ + "cfg-if", + "crossbeam-epoch", + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-epoch" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ec02e091aa634e2c3ada4a392989e7c3116673ef0ac5b72232439094d73b7fd" +dependencies = [ + "cfg-if", + "crossbeam-utils", + "lazy_static", + "memoffset", + "scopeguard", +] + +[[package]] +name = "crossbeam-utils" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d82cfc11ce7f2c3faef78d8a684447b40d503d9681acebed6cb728d45940c4db" +dependencies = [ + "cfg-if", + "lazy_static", +] + +[[package]] +name = "debugid" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef552e6f588e446098f6ba40d89ac146c8c7b64aade83c051ee00bb5d2bc18d" +dependencies = [ + "uuid", +] + +[[package]] +name = "decompile" +version = "0.1.0" +dependencies = [ + "binaryninja", + "clap", +] + +[[package]] +name = "dwarf_export" +version = "0.1.0" +dependencies = [ + "binaryninja", + "gimli", + "log", + "object", +] + +[[package]] +name = "dwarf_import" +version = "0.1.0" +dependencies = [ + "binaryninja", + "dwarfreader", + "gimli", + "log", +] + +[[package]] +name = "dwarfdump" +version = "0.1.0" +dependencies = [ + "binaryninja", + "dwarfreader", + "gimli", +] + +[[package]] +name = "dwarfreader" +version = "0.1.0" +dependencies = [ + "binaryninja", + "gimli", +] + +[[package]] +name = "either" +version = "1.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e78d4f1cc4ae33bbfc157ed5d5a5ef3bc29227303d595861deb238fcec4e9457" + +[[package]] +name = "encoding" +version = "0.2.33" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6b0d943856b990d12d3b55b359144ff341533e516d94098b1d3fc1ac666d36ec" +dependencies = [ + "encoding-index-japanese", + "encoding-index-korean", + "encoding-index-simpchinese", + "encoding-index-singlebyte", + "encoding-index-tradchinese", +] + +[[package]] +name = "encoding-index-japanese" +version = "1.20141219.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "04e8b2ff42e9a05335dbf8b5c6f7567e5591d0d916ccef4e0b1710d32a0d0c91" +dependencies = [ + "encoding_index_tests", +] + +[[package]] +name = "encoding-index-korean" +version = "1.20141219.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4dc33fb8e6bcba213fe2f14275f0963fd16f0a02c878e3095ecfdf5bee529d81" +dependencies = [ + "encoding_index_tests", +] + +[[package]] +name = "encoding-index-simpchinese" +version = "1.20141219.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d87a7194909b9118fc707194baa434a4e3b0fb6a5a757c73c3adb07aa25031f7" +dependencies = [ + "encoding_index_tests", +] + +[[package]] +name = "encoding-index-singlebyte" +version = "1.20141219.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3351d5acffb224af9ca265f435b859c7c01537c0849754d3db3fdf2bfe2ae84a" +dependencies = [ + "encoding_index_tests", +] + +[[package]] +name = "encoding-index-tradchinese" +version = "1.20141219.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fd0e20d5688ce3cab59eb3ef3a2083a5c77bf496cb798dc6fcdb75f323890c18" +dependencies = [ + "encoding_index_tests", +] + +[[package]] +name = "encoding_index_tests" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a246d82be1c9d791c5dfde9a2bd045fc3cbba3fa2b11ad558f27d01712f00569" + +[[package]] +name = "enum-primitive-derive" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c375b9c5eadb68d0a6efee2999fef292f45854c3444c86f09d8ab086ba942b0e" +dependencies = [ + "num-traits", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "errno" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4bcfec3a70f97c962c307b2d2c56e358cf1d00b558d74262b5f929ee8cc7e73a" +dependencies = [ + "errno-dragonfly", + "libc", + "windows-sys", +] + +[[package]] +name = "errno-dragonfly" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aa68f1b12764fab894d2755d2518754e71b4fd80ecfb822714a1206c2aab39bf" +dependencies = [ + "cc", + "libc", +] + +[[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.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a8a2db397cb1c8772f31494cb8917e48cd1e64f0fa7efac59fbd741a0a8ce841" +dependencies = [ + "crc32fast", + "miniz_oxide", +] + +[[package]] +name = "flowgraph" +version = "0.1.0" +dependencies = [ + "binaryninja", +] + +[[package]] +name = "gimli" +version = "0.27.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ad0a93d233ebf96623465aad4046a8d3aa4da22d4f4beba5388838c8a434bbb4" +dependencies = [ + "fallible-iterator", + "indexmap", + "stable_deref_trait", +] + +[[package]] +name = "glob" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b919933a397b79c37e33b77bb2aa3dc8eb6e165ad809e58ff75bc7db2e34574" + +[[package]] +name = "hashbrown" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" + +[[package]] +name = "hashbrown" +version = "0.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "43a3c133739dddd0d2990f9a4bdf8eb4b21ef50e4851ca85ab661199821d510e" +dependencies = [ + "ahash", +] + +[[package]] +name = "heck" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" + +[[package]] +name = "hermit-abi" +version = "0.1.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33" +dependencies = [ + "libc", +] + +[[package]] +name = "hermit-abi" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "443144c8cdadd93ebf52ddb4056d257f5b52c04d3c804e657d19eb73fc33668b" + +[[package]] +name = "indexmap" +version = "1.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99" +dependencies = [ + "autocfg", + "hashbrown 0.12.3", +] + +[[package]] +name = "is-terminal" +version = "0.4.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb0889898416213fab133e1d33a0e5858a48177452750691bde3666d0fdbaf8b" +dependencies = [ + "hermit-abi 0.3.2", + "rustix", + "windows-sys", +] + +[[package]] +name = "itoa" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "453ad9f582a441959e5f0d088b02ce04cfe8d51a8eaf077f12ac6d3e94164ca6" + +[[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.147" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b4668fb0ea861c1df094127ac5f1da3409a82116a4ba74fca2e58ef927159bb3" + +[[package]] +name = "libloading" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6f84d96438c15fcd6c3f244c8fce01d1e2b9c6b5623e9c711dc9286d8fc92d6a" +dependencies = [ + "cfg-if", + "winapi", +] + +[[package]] +name = "linux-raw-sys" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09fc20d2ca12cb9f044c93e3bd6d32d523e6e2ec3db4f7b2939cd99026ecd3f0" + +[[package]] +name = "log" +version = "0.4.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "abb12e687cfb44aa40f41fc3978ef76448f9b6038cad6aef4259d3c095a2382e" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "memchr" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d" + +[[package]] +name = "memmap2" +version = "0.5.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "83faa42c0a078c393f6b29d5db232d8be22776a891f8f56e5284faee4a20b327" +dependencies = [ + "libc", +] + +[[package]] +name = "memoffset" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "59accc507f1338036a0477ef61afdae33cde60840f4dfe481319ce3ad116ddf9" +dependencies = [ + "autocfg", +] + +[[package]] +name = "minidump" +version = "0.15.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94e5cd2ca4f6b85c6c7fb41ae0aebe0b443a6c0558876f1d779c7236d42417cf" +dependencies = [ + "debugid", + "encoding", + "memmap2", + "minidump-common", + "num-traits", + "range-map", + "scroll", + "thiserror", + "time", + "tracing", + "uuid", +] + +[[package]] +name = "minidump-common" +version = "0.15.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "694717103b2c15f8c16ddfaec1333fe15673bc22b10ffa6164427415701974ba" +dependencies = [ + "bitflags 1.3.2", + "debugid", + "enum-primitive-derive", + "num-traits", + "range-map", + "scroll", + "smart-default", +] + +[[package]] +name = "minidump_bn" +version = "0.1.0" +dependencies = [ + "binaryninja", + "log", + "minidump", + "time", +] + +[[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.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b275950c28b37e794e8c55d88aeb5e139d0ce23fdbbeda68f8d7174abdf9e8fa" +dependencies = [ + "adler", +] + +[[package]] +name = "mlil_lifter" +version = "0.1.0" +dependencies = [ + "binaryninja", +] + +[[package]] +name = "mlil_visitor" +version = "0.1.0" +dependencies = [ + "binaryninja", +] + +[[package]] +name = "nom" +version = "7.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a8903e5a29a317527874d0402f867152a3d21c908bb0b933e416c65e301d4c36" +dependencies = [ + "memchr", + "minimal-lexical", +] + +[[package]] +name = "num-traits" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "578ede34cf02f8924ab9447f50c28075b4d3e5b269972345e7e0372b38c6cdcd" +dependencies = [ + "autocfg", +] + +[[package]] +name = "num_cpus" +version = "1.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05499f3756671c15885fee9034446956fff3f243d6077b91e5767df161f766b3" +dependencies = [ + "hermit-abi 0.1.19", + "libc", +] + +[[package]] +name = "num_threads" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2819ce041d2ee131036f4fc9d6ae7ae125a3a40e97ba64d04fe799ad9dabbb44" +dependencies = [ + "libc", +] + +[[package]] +name = "object" +version = "0.30.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ea86265d3d3dcb6a27fc51bd29a4bf387fae9d2986b823079d4986af253eb439" +dependencies = [ + "crc32fast", + "flate2", + "hashbrown 0.13.2", + "indexmap", + "memchr", +] + +[[package]] +name = "once_cell" +version = "1.17.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b7e5500299e16ebb147ae15a00a942af264cf3688f47923b8fc2cd5858f23ad3" + +[[package]] +name = "peeking_take_while" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "19b17cddbe7ec3f8bc800887bab5e717348c95ea2ca0b1bf0837fb964dc67099" + +[[package]] +name = "pin-project-lite" +version = "0.2.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e0a7ae3ac2f1173085d398531c705756c94a4c56843785df85a60c1a0afac116" + +[[package]] +name = "prettyplease" +version = "0.2.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92139198957b410250d43fad93e630d956499a625c527eda65175c8680f83387" +dependencies = [ + "proc-macro2", + "syn 2.0.25", +] + +[[package]] +name = "proc-macro2" +version = "1.0.64" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78803b62cbf1f46fde80d7c0e803111524b9877184cfe7c3033659490ac7a7da" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "573015e8ab27661678357f27dc26460738fd2b6c86e46f386fde94cb5d913105" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "range-map" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "12a5a2d6c7039059af621472a4389be1215a816df61aa4d531cfe85264aee95f" +dependencies = [ + "num-traits", +] + +[[package]] +name = "rayon" +version = "1.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c06aca804d41dbc8ba42dfd964f0d01334eceb64314b9ecf7c5fad5188a06d90" +dependencies = [ + "autocfg", + "crossbeam-deque", + "either", + "rayon-core", +] + +[[package]] +name = "rayon-core" +version = "1.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d78120e2c850279833f1dd3582f730c4ab53ed95aeaaaa862a2a5c71b1656d8e" +dependencies = [ + "crossbeam-channel", + "crossbeam-deque", + "crossbeam-utils", + "lazy_static", + "num_cpus", +] + +[[package]] +name = "regex" +version = "1.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d07a8629359eb56f1e2fb1652bb04212c072a87ba68546a04065d525673ac461" +dependencies = [ + "regex-syntax", +] + +[[package]] +name = "regex-syntax" +version = "0.6.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f497285884f3fcff424ffc933e56d7cbca511def0c9831a7f9b5f6153e3cc89b" + +[[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.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0a962918ea88d644592894bc6dc55acc6c0956488adcebbfb6e273506b7fd6e5" +dependencies = [ + "bitflags 2.3.3", + "errno", + "libc", + "linux-raw-sys", + "windows-sys", +] + +[[package]] +name = "scopeguard" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" + +[[package]] +name = "scroll" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "04c565b551bafbef4157586fa379538366e4385d42082f255bfd96e4fe8519da" +dependencies = [ + "scroll_derive", +] + +[[package]] +name = "scroll_derive" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bdbda6ac5cd1321e724fa9cee216f3a61885889b896f073b8f82322789c5250e" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "serde" +version = "1.0.159" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c04e8343c3daeec41f58990b9d77068df31209f2af111e059e9fe9646693065" + +[[package]] +name = "shlex" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42a568c8f2cd051a4d283bd6eb0343ac214c1b0f1ac19f93e1175b2dee38c73d" + +[[package]] +name = "smart-default" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "133659a15339456eeeb07572eb02a91c91e9815e9cbc89566944d2c8d3efdbf6" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "stable_deref_trait" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3" + +[[package]] +name = "strsim" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" + +[[package]] +name = "syn" +version = "1.0.109" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "syn" +version = "2.0.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "15e3fc8c0c74267e2df136e5e5fb656a464158aa57624053375eb9c8c6e25ae2" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "template" +version = "0.1.0" +dependencies = [ + "binaryninja", +] + +[[package]] +name = "thiserror" +version = "1.0.40" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "978c9a314bd8dc99be594bc3c175faaa9794be04a5a5e153caba6915336cebac" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.40" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f9456a42c5b0d803c8cd86e73dd7cc9edd429499f37a3550d286d5e86720569f" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.25", +] + +[[package]] +name = "time" +version = "0.3.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fab5c8b9980850e06d92ddbe3ab839c062c801f3927c0fb8abd6fc8e918fbca" +dependencies = [ + "itoa", + "libc", + "num_threads", + "serde", + "time-core", + "time-macros", +] + +[[package]] +name = "time-core" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2e153e1f1acaef8acc537e68b44906d2db6436e2b35ac2c6b42640fff91f00fd" + +[[package]] +name = "time-macros" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "65bb801831d812c562ae7d2bfb531f26e66e4e1f6b17307ba4149c5064710e5b" +dependencies = [ + "time-core", +] + +[[package]] +name = "tracing" +version = "0.1.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ce8c33a8d48bd45d624a6e523445fd21ec13d3653cd51f681abf67418f54eb8" +dependencies = [ + "cfg-if", + "log", + "pin-project-lite", + "tracing-attributes", + "tracing-core", +] + +[[package]] +name = "tracing-attributes" +version = "0.1.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4017f8f45139870ca7e672686113917c71c7a6e02d4924eda67186083c03081a" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "tracing-core" +version = "0.1.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24eb03ba0eab1fd845050058ce5e616558e8f8d8fca633e6b163fe25c797213a" +dependencies = [ + "once_cell", +] + +[[package]] +name = "unicode-ident" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5bd2fe26506023ed7b5e1e315add59d6f584c621d037f9368fea9cfb988f368c" + +[[package]] +name = "utf8parse" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "711b9620af191e0cdc7468a8d14e709c3dcdb115b36f838e601583af800a370a" + +[[package]] +name = "uuid" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1674845326ee10d37ca60470760d4288a6f80f304007d92e5c53bab78c9cfd79" + +[[package]] +name = "version_check" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" + +[[package]] +name = "which" +version = "4.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c4fb54e6113b6a8772ee41c3404fb0301ac79604489467e0a9ce1f3e97c24ae" +dependencies = [ + "either", + "lazy_static", + "libc", +] + +[[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.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05d4b17490f70499f20b9e791dcf6a299785ce8af4d709018206dc5b4953e95f" +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.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91ae572e1b79dba883e0d315474df7305d12f569b400fcf90581b06062f7e1bc" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2ef27e0d7bdfcfc7b868b317c1d32c641a6fe4629c171b8928c7b08d98d7cf3" + +[[package]] +name = "windows_i686_gnu" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "622a1962a7db830d6fd0a69683c80a18fda201879f0f447f065a3b7467daa241" + +[[package]] +name = "windows_i686_msvc" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4542c6e364ce21bf45d69fdd2a8e455fa38d316158cfd43b3ac1c5b1b19f8e00" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca2b8a661f7628cbd23440e50b05d705db3686f894fc9580820623656af974b1" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7896dbc1f41e08872e9d5e8f8baa8fdd2677f29468c4e156210174edc7f7b953" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a515f5799fe4961cb532f983ce2b23082366b898e52ffbce459c86f67c8378a" diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..9ab2413 --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,30 @@ +[package] +name = "binaryninja" +version = "0.1.0" +authors = ["Ryan Snyder ", "Kyle Martin "] +edition = "2021" + +[features] +noexports = [] + +[dependencies] +lazy_static = "1.4.0" +log = "0.4" +libc = "0.2" +rayon = { version = "1.0", optional = true } +binaryninjacore-sys = { path = "binaryninjacore-sys" } + +[workspace] +members = [ + "examples/basic_script", + "examples/decompile", + "examples/dwarf/dwarf_export", + "examples/dwarf/dwarf_import", + "examples/dwarf/dwarfdump", + "examples/dwarf/shared", + "examples/flowgraph", + "examples/minidump", + "examples/mlil_visitor", + "examples/mlil_lifter", + "examples/template" +] diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..265883c --- /dev/null +++ b/LICENSE @@ -0,0 +1,13 @@ +Copyright 2021 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. diff --git a/README.md b/README.md new file mode 100644 index 0000000..d2f7f1c --- /dev/null +++ b/README.md @@ -0,0 +1,67 @@ +# BinaryNinja-rs + + + +> :warning: **These bindings are in a very early beta, only have partial support for the core APIs and are still actively under development. Compatibility _will_ break and conventions _will_ change! They are being used for core Binary Ninja features however, so we expect much of what is already there to be reliable enough to build on, just don't be surprised if your plugins/scripts need to hit a moving target.** + +> :warning: This project runs on Rust version `stable-2022-12-15` + + +## Contributing + +:warning: If you're thinking of contributing to the Rust API, we encourage you to join the #rust-api channel in our Slack: https://slack.binary.ninja, especially for large-effort PRs. +Add a "Contributing" section to the Rust API readme + + +## Dependencies + +Having BinaryNinja installed (and your license registered) +Clang +Rust + + +## How to use + +See [`examples/template`](examples/template) for more details. + +### To write a plugin: + +`Cargo.toml`: +``` +[lib] +crate-type = ["cdylib"] + +[dependencies] +binaryninja = {git = "https://github.com/Vector35/binaryninja-api.git", branch = "dev"} +``` + +See the `./examples/`. Plugin registration commands are in `binaryninja::command::*` + + +### To write a standalone executable: + +`Cargo.toml`: +``` +[dependencies] +binaryninja = { git = "https://github.com/Vector35/binaryninja-api.git", branch = "dev"} +``` + +All standalone binaries need to provide a `build.rs`. +See [`examples/template`](examples/template) for details. + +## Docs + +Docs can be found at https://dev-rust.binary.ninja/ + +--- + +#### Attribution + +This project makes use of: + - [log] ([log license] - MIT) + - [rayon] ([rayon license] - MIT) + +[log]: https://github.com/rust-lang/log +[log license]: https://github.com/rust-lang/log/blob/master/LICENSE-MIT +[rayon]: https://github.com/rayon-rs/rayon +[rayon license]: https://github.com/rayon-rs/rayon/blob/master/LICENSE-MIT diff --git a/binaryninja-api b/binaryninja-api new file mode 160000 index 0000000..2d45ae4 --- /dev/null +++ b/binaryninja-api @@ -0,0 +1 @@ +Subproject commit 2d45ae4ea90d0792627927def462ed7c2350bb89 diff --git a/binaryninjacore-sys/Cargo.lock b/binaryninjacore-sys/Cargo.lock new file mode 100644 index 0000000..3a70154 --- /dev/null +++ b/binaryninjacore-sys/Cargo.lock @@ -0,0 +1,337 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "aho-corasick" +version = "0.7.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e37cfd5e7657ada45f742d6e99ca5788580b5c529dc78faf11ece6dc702656f" +dependencies = [ + "memchr", +] + +[[package]] +name = "ansi_term" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d52a9bb7ec0cf484c551830a7ce27bd20d67eac647e1befb56b0be4ee39a55d2" +dependencies = [ + "winapi", +] + +[[package]] +name = "atty" +version = "0.2.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8" +dependencies = [ + "hermit-abi", + "libc", + "winapi", +] + +[[package]] +name = "binaryninjacore-sys" +version = "0.1.0" +dependencies = [ + "bindgen", +] + +[[package]] +name = "bindgen" +version = "0.58.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0f8523b410d7187a43085e7e064416ea32ded16bd0a4e6fc025e21616d01258f" +dependencies = [ + "bitflags", + "cexpr", + "clang-sys", + "clap", + "env_logger", + "lazy_static", + "lazycell", + "log", + "peeking_take_while", + "proc-macro2", + "quote", + "regex", + "rustc-hash", + "shlex", + "which", +] + +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + +[[package]] +name = "cexpr" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f4aedb84272dbe89af497cf81375129abda4fc0a9e7c5d317498c15cc30c0d27" +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.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "853eda514c284c2287f4bf20ae614f8781f40a81d32ecda6e91449304dfe077c" +dependencies = [ + "glob", + "libc", + "libloading", +] + +[[package]] +name = "clap" +version = "2.34.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a0610544180c38b88101fecf2dd634b174a62eef6946f84dfc6a7127512b381c" +dependencies = [ + "ansi_term", + "atty", + "bitflags", + "strsim", + "textwrap", + "unicode-width", + "vec_map", +] + +[[package]] +name = "env_logger" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a19187fea3ac7e84da7dacf48de0c45d63c6a76f9490dae389aead16c243fce3" +dependencies = [ + "atty", + "humantime", + "log", + "regex", + "termcolor", +] + +[[package]] +name = "glob" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b919933a397b79c37e33b77bb2aa3dc8eb6e165ad809e58ff75bc7db2e34574" + +[[package]] +name = "hermit-abi" +version = "0.1.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33" +dependencies = [ + "libc", +] + +[[package]] +name = "humantime" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4" + +[[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.112" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b03d17f364a3a042d5e5d46b053bbbf82c92c9430c592dd4c064dc6ee997125" + +[[package]] +name = "libloading" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6f84d96438c15fcd6c3f244c8fce01d1e2b9c6b5623e9c711dc9286d8fc92d6a" +dependencies = [ + "cfg-if", + "winapi", +] + +[[package]] +name = "log" +version = "0.4.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "51b9bbe6c47d51fc3e1a9b945965946b4c44142ab8792c50835a980d362c2710" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "memchr" +version = "2.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "308cc39be01b73d0d18f82a0e7b2a3df85245f84af96fdddc5d202d27e47b86a" + +[[package]] +name = "nom" +version = "5.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ffb4262d26ed83a1c0a33a38fe2bb15797329c85770da05e6b828ddb782627af" +dependencies = [ + "memchr", + "version_check", +] + +[[package]] +name = "peeking_take_while" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "19b17cddbe7ec3f8bc800887bab5e717348c95ea2ca0b1bf0837fb964dc67099" + +[[package]] +name = "proc-macro2" +version = "1.0.36" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c7342d5883fbccae1cc37a2353b09c87c9b0f3afd73f5fb9bba687a1f733b029" +dependencies = [ + "unicode-xid", +] + +[[package]] +name = "quote" +version = "1.0.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "47aa80447ce4daf1717500037052af176af5d38cc3e571d9ec1c7353fc10c87d" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "regex" +version = "1.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d07a8629359eb56f1e2fb1652bb04212c072a87ba68546a04065d525673ac461" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] + +[[package]] +name = "regex-syntax" +version = "0.6.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f497285884f3fcff424ffc933e56d7cbca511def0c9831a7f9b5f6153e3cc89b" + +[[package]] +name = "rustc-hash" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" + +[[package]] +name = "shlex" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42a568c8f2cd051a4d283bd6eb0343ac214c1b0f1ac19f93e1175b2dee38c73d" + +[[package]] +name = "strsim" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ea5119cdb4c55b55d432abb513a0429384878c15dde60cc77b1c99de1a95a6a" + +[[package]] +name = "termcolor" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2dfed899f0eb03f32ee8c6a0aabdb8a7949659e3466561fc0adf54e26d88c5f4" +dependencies = [ + "winapi-util", +] + +[[package]] +name = "textwrap" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d326610f408c7a4eb6f51c37c330e496b08506c9457c9d34287ecc38809fb060" +dependencies = [ + "unicode-width", +] + +[[package]] +name = "unicode-width" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3ed742d4ea2bd1176e236172c8429aaf54486e7ac098db29ffe6529e0ce50973" + +[[package]] +name = "unicode-xid" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ccb82d61f80a663efe1f787a51b16b5a51e3314d6ac365b08639f52387b33f3" + +[[package]] +name = "vec_map" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1bddf1187be692e79c5ffeab891132dfb0f236ed36a43c7ed39f1165ee20191" + +[[package]] +name = "version_check" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" + +[[package]] +name = "which" +version = "3.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d011071ae14a2f6671d0b74080ae0cd8ebf3a6f8c9589a2cd45f23126fe29724" +dependencies = [ + "libc", +] + +[[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-util" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178" +dependencies = [ + "winapi", +] + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" diff --git a/binaryninjacore-sys/Cargo.toml b/binaryninjacore-sys/Cargo.toml new file mode 100644 index 0000000..83e9b39 --- /dev/null +++ b/binaryninjacore-sys/Cargo.toml @@ -0,0 +1,8 @@ +[package] +name = "binaryninjacore-sys" +version = "0.1.0" +authors = ["Ryan Snyder ", "Kyle Martin "] +build = "build.rs" + +[build-dependencies] +bindgen = "^0.66" diff --git a/binaryninjacore-sys/build.rs b/binaryninjacore-sys/build.rs new file mode 100644 index 0000000..2980ca3 --- /dev/null +++ b/binaryninjacore-sys/build.rs @@ -0,0 +1,116 @@ +extern crate bindgen; + +use std::env; +use std::fs::File; +use std::io::BufRead; +use std::io::BufReader; +use std::path::PathBuf; + +#[cfg(target_os = "macos")] +static LASTRUN_PATH: (&str, &str) = ("HOME", "Library/Application Support/Binary Ninja/lastrun"); + +#[cfg(target_os = "linux")] +static LASTRUN_PATH: (&str, &str) = ("HOME", ".binaryninja/lastrun"); + +#[cfg(windows)] +static LASTRUN_PATH: (&str, &str) = ("APPDATA", "Binary Ninja\\lastrun"); + +// Check last run location for path to BinaryNinja; Otherwise check the default install locations +fn link_path() -> PathBuf { + use std::io::prelude::*; + + let home = PathBuf::from(env::var(LASTRUN_PATH.0).unwrap()); + let lastrun = PathBuf::from(&home).join(LASTRUN_PATH.1); + + File::open(lastrun) + .and_then(|f| { + let mut binja_path = String::new(); + let mut reader = BufReader::new(f); + + reader.read_line(&mut binja_path)?; + Ok(PathBuf::from(binja_path.trim())) + }) + .unwrap_or_else(|_| { + #[cfg(target_os = "macos")] + return PathBuf::from("/Applications/Binary Ninja.app/Contents/MacOS"); + + #[cfg(target_os = "linux")] + return home.join("binaryninja"); + + #[cfg(windows)] + return PathBuf::from(env::var("PROGRAMFILES").unwrap()) + .join("Vector35\\BinaryNinja\\"); + }) +} + +fn main() { + println!("cargo:rerun-if-changed=source/binaryninjacore.h"); + + //Cargo's output directory + let out_dir = env::var("OUT_DIR").unwrap(); + + let link_path = env::var("BINARYNINJADIR") + .map(PathBuf::from) + .unwrap_or_else(|_| link_path()); + + // Linux builds of binaryninja ship with libbinaryninjacore.so.1 in the + // application folder and no symlink. The linker will attempt to link with + // libbinaryninjacore.so. Since this is likely going to fail, we detect this + // ahead of time and create an appropriately named symlink inside of OUT_DIR + // and add it to the library search path. + #[cfg(target_os = "linux")] + { + let symlink_target = PathBuf::from(&out_dir).join("libbinaryninjacore.so"); + if link_path.join("libbinaryninjacore.so.1").exists() && !symlink_target.exists() { + use std::os::unix::fs; + fs::symlink( + link_path.join("libbinaryninjacore.so.1"), + PathBuf::from(&out_dir).join("libbinaryninjacore.so"), + ) + .expect("failed to create required symlink"); + } + println!("cargo:rustc-link-search={}", out_dir); + } + + println!("cargo:rustc-link-lib=binaryninjacore"); + println!("cargo:rustc-link-search={}", link_path.to_str().unwrap()); + + let current_line = "#define BN_CURRENT_UI_ABI_VERSION "; + let minimum_line = "#define BN_MINIMUM_UI_ABI_VERSION "; + let mut current_version = "0".to_string(); + let mut minimum_version = "0".to_string(); + let file = File::open("source/ui/uitypes.h").expect("Couldn't open uitypes.h"); + 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(); + } else if let Some(version) = line.strip_prefix(minimum_line) { + minimum_version = version.to_owned(); + } + } + + bindgen::builder() + .header("source/binaryninjacore.h") + .clang_arg("-std=c++17") + .clang_arg("-x") + .clang_arg("c++") + .size_t_is_usize(true) + .generate_comments(false) + .allowlist_function("BN.*") + .allowlist_var("BN_CURRENT_CORE_ABI_VERSION") + .allowlist_var("BN_MINIMUM_CORE_ABI_VERSION") + .allowlist_var("MAX_RELOCATION_SIZE") + .raw_line(format!( + "pub const BN_CURRENT_UI_ABI_VERSION: u32 = {};", + current_version + )) + .raw_line(format!( + "pub const BN_MINIMUM_UI_ABI_VERSION: u32 = {};", + minimum_version + )) + .rustified_enum("BN.*") + .generate() + .expect("Unable to generate bindings") + .write_to_file(PathBuf::from(out_dir).join("bindings.rs")) + .expect("Couldn't write bindings!"); +} diff --git a/binaryninjacore-sys/src/lib.rs b/binaryninjacore-sys/src/lib.rs new file mode 100644 index 0000000..d39a7c0 --- /dev/null +++ b/binaryninjacore-sys/src/lib.rs @@ -0,0 +1,7 @@ +#![allow(non_upper_case_globals)] +#![allow(non_camel_case_types)] +#![allow(non_snake_case)] +#![allow(unused)] +#![allow(clippy::type_complexity)] + +include!(concat!(env!("OUT_DIR"), "/bindings.rs")); diff --git a/build.rs b/build.rs new file mode 100644 index 0000000..91f1021 --- /dev/null +++ b/build.rs @@ -0,0 +1,12 @@ +fn main() { + // TODO : Enable the following when https://github.com/rust-lang/rust/issues/43781 stabilizes + // #[cfg(doc)] + let _ = std::fs::create_dir("target"); + let _ = std::fs::create_dir("target/doc"); + let _ = std::fs::copy("../docs/img/favicon.ico", "target/doc/favicon.ico"); + let _ = std::fs::copy( + "under_construction.png", + "target/doc/under_construction.png", + ); + let _ = std::fs::copy("../docs/img/logo.png", "target/doc/logo.png"); +} diff --git a/examples/basic_script/CMakeLists.txt b/examples/basic_script/CMakeLists.txt new file mode 100644 index 0000000..706b08c --- /dev/null +++ b/examples/basic_script/CMakeLists.txt @@ -0,0 +1,105 @@ +cmake_minimum_required(VERSION 3.9 FATAL_ERROR) + +project(test_headless) + +file(GLOB PLUGIN_SOURCES + ${PROJECT_SOURCE_DIR}/Cargo.toml + ${PROJECT_SOURCE_DIR}/src/*.rs) + +file(GLOB API_SOURCES + ${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() + +set(OUTPUT_FILE basic_script${CMAKE_EXECUTABLE_SUFFIX}) +set(OUTPUT_PATH ${CMAKE_BINARY_DIR}/out/bin/${OUTPUT_FILE}) + + +if(NOT BN_API_BUILD_EXAMPLES) + # Out-of-tree build + find_path( + BN_API_PATH + NAMES binaryninjaapi.h + HINTS ../../.. binaryninjaapi + REQUIRED + ) + add_subdirectory(${BN_API_PATH} api) +endif() + +add_custom_target(test_headless ALL DEPENDS ${OUTPUT_PATH}) +add_dependencies(test_headless binaryninjaapi) + +# Get API source directory so we can find BinaryNinjaCore +get_target_property(BN_API_SOURCE_DIR binaryninjaapi SOURCE_DIR) +message(STATUS "${BN_API_SOURCE_DIR}") +list(APPEND CMAKE_MODULE_PATH "${BN_API_SOURCE_DIR}/cmake") + +# BinaryNinjaCore has the user plugins dir define that we want +find_package(BinaryNinjaCore REQUIRED) + +find_program(RUSTUP_PATH rustup REQUIRED HINTS ~/.cargo/bin) +set(RUSTUP_COMMAND ${RUSTUP_PATH} run ${CARGO_API_VERSION} cargo build) + +if(APPLE) + if(UNIVERSAL) + if(CMAKE_BUILD_TYPE MATCHES Debug) + set(AARCH64_LIB_PATH ${PROJECT_BINARY_DIR}/target/aarch64-apple-darwin/debug/${OUTPUT_FILE}) + set(X86_64_LIB_PATH ${PROJECT_BINARY_DIR}/target/x86_64-apple-darwin/debug/${OUTPUT_FILE}) + else() + set(AARCH64_LIB_PATH ${PROJECT_BINARY_DIR}/target/aarch64-apple-darwin/release/${OUTPUT_FILE}) + set(X86_64_LIB_PATH ${PROJECT_BINARY_DIR}/target/x86_64-apple-darwin/release/${OUTPUT_FILE}) + endif() + + add_custom_command( + OUTPUT ${OUTPUT_PATH} + COMMAND ${CMAKE_COMMAND} -E env + MACOSX_DEPLOYMENT_TARGET=10.14 BINARYNINJADIR=${BN_INSTALL_BIN_DIR} + ${RUSTUP_COMMAND} --target=aarch64-apple-darwin ${CARGO_OPTS} + COMMAND ${CMAKE_COMMAND} -E env + MACOSX_DEPLOYMENT_TARGET=10.14 BINARYNINJADIR=${BN_INSTALL_BIN_DIR} + ${RUSTUP_COMMAND} --target=x86_64-apple-darwin ${CARGO_OPTS} + COMMAND lipo -create ${AARCH64_LIB_PATH} ${X86_64_LIB_PATH} -output ${OUTPUT_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}) + else() + set(LIB_PATH ${PROJECT_BINARY_DIR}/target/release/${OUTPUT_FILE}) + endif() + + add_custom_command( + OUTPUT ${OUTPUT_PATH} + COMMAND ${CMAKE_COMMAND} -E env + MACOSX_DEPLOYMENT_TARGET=10.14 BINARYNINJADIR=${BN_INSTALL_BIN_DIR} + ${RUSTUP_COMMAND} ${CARGO_OPTS} + COMMAND ${CMAKE_COMMAND} -E copy ${LIB_PATH} ${OUTPUT_PATH} + WORKING_DIRECTORY ${PROJECT_SOURCE_DIR} + DEPENDS ${PLUGIN_SOURCES} ${API_SOURCES}) + endif() +elseif(WIN32) + add_custom_command( + OUTPUT ${OUTPUT_PATH} + COMMAND ${CMAKE_COMMAND} -E env BINARYNINJADIR=${BN_INSTALL_BIN_DIR} ${RUSTUP_COMMAND} ${CARGO_OPTS} + COMMAND ${CMAKE_COMMAND} -E copy ${TARGET_DIR}/${OUTPUT_FILE} ${OUTPUT_PATH} + WORKING_DIRECTORY ${PROJECT_SOURCE_DIR} + DEPENDS ${PLUGIN_SOURCES} ${API_SOURCES}) +else() + add_custom_command( + OUTPUT ${OUTPUT_PATH} + COMMAND ${CMAKE_COMMAND} -E env BINARYNINJADIR=${BN_INSTALL_BIN_DIR} ${RUSTUP_COMMAND} ${CARGO_OPTS} + COMMAND ${CMAKE_COMMAND} -E copy ${TARGET_DIR}/${OUTPUT_FILE} ${OUTPUT_PATH} + WORKING_DIRECTORY ${PROJECT_SOURCE_DIR} + DEPENDS ${PLUGIN_SOURCES} ${API_SOURCES}) +endif() diff --git a/examples/basic_script/Cargo.toml b/examples/basic_script/Cargo.toml new file mode 100644 index 0000000..c037389 --- /dev/null +++ b/examples/basic_script/Cargo.toml @@ -0,0 +1,8 @@ +[package] +name = "basic_script" +version = "0.1.0" +authors = ["KyleMiles "] +edition = "2021" + +[dependencies] +binaryninja = {path="../../"} diff --git a/examples/basic_script/build.rs b/examples/basic_script/build.rs new file mode 100644 index 0000000..5ba9bcd --- /dev/null +++ b/examples/basic_script/build.rs @@ -0,0 +1,68 @@ +use std::env; +use std::fs::File; +use std::io::BufReader; +use std::path::PathBuf; + +#[cfg(target_os = "macos")] +static LASTRUN_PATH: (&str, &str) = ("HOME", "Library/Application Support/Binary Ninja/lastrun"); + +#[cfg(target_os = "linux")] +static LASTRUN_PATH: (&str, &str) = ("HOME", ".binaryninja/lastrun"); + +#[cfg(windows)] +static LASTRUN_PATH: (&str, &str) = ("APPDATA", "Binary Ninja\\lastrun"); + +// Check last run location for path to BinaryNinja; Otherwise check the default install locations +fn link_path() -> PathBuf { + use std::io::prelude::*; + + let home = PathBuf::from(env::var(LASTRUN_PATH.0).unwrap()); + let lastrun = PathBuf::from(&home).join(LASTRUN_PATH.1); + + File::open(lastrun) + .and_then(|f| { + let mut binja_path = String::new(); + let mut reader = BufReader::new(f); + + reader.read_line(&mut binja_path)?; + Ok(PathBuf::from(binja_path.trim())) + }) + .unwrap_or_else(|_| { + #[cfg(target_os = "macos")] + return PathBuf::from("/Applications/Binary Ninja.app/Contents/MacOS"); + + #[cfg(target_os = "linux")] + return home.join("binaryninja"); + + #[cfg(windows)] + return PathBuf::from(env::var("PROGRAMFILES").unwrap()) + .join("Vector35\\BinaryNinja\\"); + }) +} + +fn main() { + // Use BINARYNINJADIR first for custom BN builds/configurations (BN devs/build server), fallback on defaults + let install_path = env::var("BINARYNINJADIR") + .map(PathBuf::from) + .unwrap_or_else(|_| link_path()); + + #[cfg(target_os = "linux")] + println!( + "cargo:rustc-link-arg=-Wl,-rpath,{},-L{},-l:libbinaryninjacore.so.1", + install_path.to_str().unwrap(), + install_path.to_str().unwrap(), + ); + + #[cfg(target_os = "macos")] + println!( + "cargo:rustc-link-arg=-Wl,-rpath,{},-L{},-lbinaryninjacore", + install_path.to_str().unwrap(), + install_path.to_str().unwrap(), + ); + + #[cfg(target_os = "windows")] + { + println!("cargo:rustc-link-lib=binaryninjacore"); + println!("cargo:rustc-link-search={}", install_path.to_str().unwrap()); + } +} diff --git a/examples/basic_script/src/main.rs b/examples/basic_script/src/main.rs new file mode 100644 index 0000000..b979c15 --- /dev/null +++ b/examples/basic_script/src/main.rs @@ -0,0 +1,37 @@ +use binaryninja::architecture::Architecture; +use binaryninja::binaryview::{BinaryViewBase, BinaryViewExt}; + +fn main() { + println!("Loading plugins..."); // This loads all the core architecture, platform, etc plugins + let headless_session = binaryninja::headless::Session::new(); + + println!("Loading binary..."); + let bv = headless_session + .load("/bin/cat") + .expect("Couldn't open `/bin/cat`"); + + println!("Filename: `{}`", bv.file().filename()); + println!("File size: `{:#x}`", bv.len()); + println!("Function count: {}", bv.functions().len()); + + for func in &bv.functions() { + println!(" `{}`:", func.symbol().full_name()); + for basic_block in &func.basic_blocks() { + // TODO : This is intended to be refactored to be more nice to work with soon(TM) + for addr in basic_block.as_ref() { + print!(" {} ", addr); + if let Some((_, tokens)) = func.arch().instruction_text( + bv.read_buffer(addr, func.arch().max_instr_len()) + .unwrap() + .get_data(), + addr, + ) { + tokens + .iter() + .for_each(|token| print!("{}", token.text().as_str())); + println!(); + } + } + } + } +} diff --git a/examples/decompile/Cargo.toml b/examples/decompile/Cargo.toml new file mode 100644 index 0000000..3c3cbb3 --- /dev/null +++ b/examples/decompile/Cargo.toml @@ -0,0 +1,10 @@ +[package] +name = "decompile" +version = "0.1.0" +authors = ["Fabian Freyer "] +edition = "2021" + +[dependencies] +binaryninja = {path="../../"} +clap = { version = "4.3", features = ["derive"] } + diff --git a/examples/decompile/build.rs b/examples/decompile/build.rs new file mode 100644 index 0000000..5ba9bcd --- /dev/null +++ b/examples/decompile/build.rs @@ -0,0 +1,68 @@ +use std::env; +use std::fs::File; +use std::io::BufReader; +use std::path::PathBuf; + +#[cfg(target_os = "macos")] +static LASTRUN_PATH: (&str, &str) = ("HOME", "Library/Application Support/Binary Ninja/lastrun"); + +#[cfg(target_os = "linux")] +static LASTRUN_PATH: (&str, &str) = ("HOME", ".binaryninja/lastrun"); + +#[cfg(windows)] +static LASTRUN_PATH: (&str, &str) = ("APPDATA", "Binary Ninja\\lastrun"); + +// Check last run location for path to BinaryNinja; Otherwise check the default install locations +fn link_path() -> PathBuf { + use std::io::prelude::*; + + let home = PathBuf::from(env::var(LASTRUN_PATH.0).unwrap()); + let lastrun = PathBuf::from(&home).join(LASTRUN_PATH.1); + + File::open(lastrun) + .and_then(|f| { + let mut binja_path = String::new(); + let mut reader = BufReader::new(f); + + reader.read_line(&mut binja_path)?; + Ok(PathBuf::from(binja_path.trim())) + }) + .unwrap_or_else(|_| { + #[cfg(target_os = "macos")] + return PathBuf::from("/Applications/Binary Ninja.app/Contents/MacOS"); + + #[cfg(target_os = "linux")] + return home.join("binaryninja"); + + #[cfg(windows)] + return PathBuf::from(env::var("PROGRAMFILES").unwrap()) + .join("Vector35\\BinaryNinja\\"); + }) +} + +fn main() { + // Use BINARYNINJADIR first for custom BN builds/configurations (BN devs/build server), fallback on defaults + let install_path = env::var("BINARYNINJADIR") + .map(PathBuf::from) + .unwrap_or_else(|_| link_path()); + + #[cfg(target_os = "linux")] + println!( + "cargo:rustc-link-arg=-Wl,-rpath,{},-L{},-l:libbinaryninjacore.so.1", + install_path.to_str().unwrap(), + install_path.to_str().unwrap(), + ); + + #[cfg(target_os = "macos")] + println!( + "cargo:rustc-link-arg=-Wl,-rpath,{},-L{},-lbinaryninjacore", + install_path.to_str().unwrap(), + install_path.to_str().unwrap(), + ); + + #[cfg(target_os = "windows")] + { + println!("cargo:rustc-link-lib=binaryninjacore"); + println!("cargo:rustc-link-search={}", install_path.to_str().unwrap()); + } +} diff --git a/examples/decompile/src/main.rs b/examples/decompile/src/main.rs new file mode 100644 index 0000000..0865538 --- /dev/null +++ b/examples/decompile/src/main.rs @@ -0,0 +1,54 @@ +use binaryninja::binaryview::{BinaryView, BinaryViewBase, BinaryViewExt}; +use binaryninja::disassembly::{DisassemblyOption, DisassemblySettings}; +use binaryninja::function::Function; +use binaryninja::linearview::{LinearViewCursor, LinearViewObject}; + +use clap::Parser; + +/// Use binaryninja to decompile to C. +#[derive(Parser, Debug)] +#[clap(version, long_about = None)] +struct Args { + /// Path to the file to decompile + filename: String, +} + +fn decompile_to_c(view: &BinaryView, func: &Function) { + let settings = DisassemblySettings::new(); + settings.set_option(DisassemblyOption::ShowAddress, false); + settings.set_option(DisassemblyOption::WaitForIL, true); + + let linearview = LinearViewObject::language_representation(view, &settings); + + let mut cursor = LinearViewCursor::new(&linearview); + cursor.seek_to_address(func.highest_address()); + + 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()); + + for line in lines { + println!("{}", line.as_ref()); + } +} + +fn main() { + let args = Args::parse(); + + eprintln!("Loading plugins..."); + binaryninja::headless::init(); + + eprintln!("Loading binary..."); + let bv = binaryninja::load(args.filename).expect("Couldn't open file"); + + eprintln!("Filename: `{}`", bv.file().filename()); + eprintln!("File size: `{:#x}`", bv.len()); + eprintln!("Function count: {}", bv.functions().len()); + + for func in &bv.functions() { + decompile_to_c(bv.as_ref(), func.as_ref()); + } + + binaryninja::headless::shutdown(); +} diff --git a/examples/dwarf/dwarf_export/CMakeLists.txt b/examples/dwarf/dwarf_export/CMakeLists.txt new file mode 100644 index 0000000..5827c1d --- /dev/null +++ b/examples/dwarf/dwarf_export/CMakeLists.txt @@ -0,0 +1,93 @@ +cmake_minimum_required(VERSION 3.9 FATAL_ERROR) + +project(dwarf_export) + +file(GLOB PLUGIN_SOURCES CONFIGURE_DEPENDS + ${PROJECT_SOURCE_DIR}/Cargo.toml + ${PROJECT_SOURCE_DIR}/src/*.rs + ${PROJECT_SOURCE_DIR}/../shared/Cargo.toml + ${PROJECT_SOURCE_DIR}/../shared/src/*.rs) + +file(GLOB_RECURSE API_SOURCES CONFIGURE_DEPENDS + ${PROJECT_SOURCE_DIR}/../../../../binaryninjacore.h + ${PROJECT_SOURCE_DIR}/../../../binaryninjacore-sys/build.rs + ${PROJECT_SOURCE_DIR}/../../../binaryninjacore-sys/Cargo.toml + ${PROJECT_SOURCE_DIR}/../../../binaryninjacore-sys/src/* + ${PROJECT_SOURCE_DIR}/../../../Cargo.toml + ${PROJECT_SOURCE_DIR}/../../../src/*.rs) + +if(CMAKE_BUILD_TYPE MATCHES Debug) + set(TARGET_DIR ${PROJECT_BINARY_DIR}/target/debug) + set(CARGO_OPTS --target-dir=${PROJECT_BINARY_DIR}/target) +else() + set(TARGET_DIR ${PROJECT_BINARY_DIR}/target/release) + set(CARGO_OPTS --target-dir=${PROJECT_BINARY_DIR}/target --release) + set(OUTPUT_PDB_NAME ${CMAKE_SHARED_LIBRARY_PREFIX}dwarf_export.pdb) +endif() + +set(OUTPUT_FILE ${CMAKE_STATIC_LIBRARY_PREFIX}dwarf_export${CMAKE_SHARED_LIBRARY_SUFFIX}) +set(PLUGIN_PATH ${TARGET_DIR}/${OUTPUT_FILE}) + +add_custom_target(dwarf_export ALL DEPENDS ${PLUGIN_PATH}) +add_dependencies(dwarf_export binaryninjaapi) + +find_program(RUSTUP_PATH rustup REQUIRED HINTS ~/.cargo/bin) +if(CARGO_API_VERSION) + set(RUSTUP_COMMAND ${RUSTUP_PATH} run ${CARGO_API_VERSION} cargo build) +else() + set(RUSTUP_COMMAND ${RUSTUP_PATH} run ${CARGO_STABLE_VERSION} cargo build) +endif() + +if(APPLE) + if(UNIVERSAL) + if(CMAKE_BUILD_TYPE MATCHES Debug) + set(AARCH64_LIB_PATH ${PROJECT_BINARY_DIR}/target/aarch64-apple-darwin/debug/${OUTPUT_FILE}) + set(X86_64_LIB_PATH ${PROJECT_BINARY_DIR}/target/x86_64-apple-darwin/debug/${OUTPUT_FILE}) + else() + set(AARCH64_LIB_PATH ${PROJECT_BINARY_DIR}/target/aarch64-apple-darwin/release/${OUTPUT_FILE}) + set(X86_64_LIB_PATH ${PROJECT_BINARY_DIR}/target/x86_64-apple-darwin/release/${OUTPUT_FILE}) + endif() + + add_custom_command( + OUTPUT ${PLUGIN_PATH} + COMMAND ${CMAKE_COMMAND} -E env + MACOSX_DEPLOYMENT_TARGET=10.14 BINARYNINJADIR=${BN_CORE_OUTPUT_DIR} + ${RUSTUP_COMMAND} --target=aarch64-apple-darwin ${CARGO_OPTS} + COMMAND ${CMAKE_COMMAND} -E env + MACOSX_DEPLOYMENT_TARGET=10.14 BINARYNINJADIR=${BN_CORE_OUTPUT_DIR} + ${RUSTUP_COMMAND} --target=x86_64-apple-darwin ${CARGO_OPTS} + COMMAND mkdir -p ${TARGET_DIR} + COMMAND lipo -create ${AARCH64_LIB_PATH} ${X86_64_LIB_PATH} -output ${PLUGIN_PATH} + COMMAND ${CMAKE_COMMAND} -E copy ${PLUGIN_PATH} ${BN_CORE_PLUGIN_DIR} + WORKING_DIRECTORY ${PROJECT_SOURCE_DIR} + DEPENDS ${PLUGIN_SOURCES} ${API_SOURCES}) + else() + if(CMAKE_BUILD_TYPE MATCHES Debug) + set(LIB_PATH ${PROJECT_BINARY_DIR}/target/debug/${OUTPUT_FILE}) + else() + set(LIB_PATH ${PROJECT_BINARY_DIR}/target/release/${OUTPUT_FILE}) + endif() + + add_custom_command( + OUTPUT ${PLUGIN_PATH} + COMMAND ${CMAKE_COMMAND} -E env MACOSX_DEPLOYMENT_TARGET=10.14 BINARYNINJADIR=${BN_CORE_OUTPUT_DIR} ${RUSTUP_COMMAND} ${CARGO_OPTS} + COMMAND ${CMAKE_COMMAND} -E copy ${PLUGIN_PATH} ${BN_CORE_PLUGIN_DIR} + WORKING_DIRECTORY ${PROJECT_SOURCE_DIR} + DEPENDS ${PLUGIN_SOURCES} ${API_SOURCES}) + endif() +elseif(WIN32) + add_custom_command( + OUTPUT ${PLUGIN_PATH} + COMMAND ${CMAKE_COMMAND} -E env BINARYNINJADIR=${BN_CORE_OUTPUT_DIR} ${RUSTUP_COMMAND} ${CARGO_OPTS} + COMMAND ${CMAKE_COMMAND} -E copy ${PLUGIN_PATH} ${BN_CORE_PLUGIN_DIR} + COMMAND ${CMAKE_COMMAND} -E copy ${TARGET_DIR}/${OUTPUT_PDB_NAME} ${BN_CORE_PLUGIN_DIR} + WORKING_DIRECTORY ${PROJECT_SOURCE_DIR} + DEPENDS ${PLUGIN_SOURCES} ${API_SOURCES}) +else() + add_custom_command( + OUTPUT ${PLUGIN_PATH} + COMMAND ${CMAKE_COMMAND} -E env BINARYNINJADIR=${BN_CORE_OUTPUT_DIR} ${RUSTUP_COMMAND} ${CARGO_OPTS} + COMMAND ${CMAKE_COMMAND} -E copy ${PLUGIN_PATH} ${BN_CORE_PLUGIN_DIR} + WORKING_DIRECTORY ${PROJECT_SOURCE_DIR} + DEPENDS ${PLUGIN_SOURCES} ${API_SOURCES}) +endif() diff --git a/examples/dwarf/dwarf_export/Cargo.toml b/examples/dwarf/dwarf_export/Cargo.toml new file mode 100644 index 0000000..022d3e0 --- /dev/null +++ b/examples/dwarf/dwarf_export/Cargo.toml @@ -0,0 +1,13 @@ +[package] +name = "dwarf_export" +version = "0.1.0" +edition = "2021" + +[lib] +crate-type = ["cdylib"] + +[dependencies] +binaryninja = {path="../../../"} +gimli = "^0.27" +log = "^0.4" +object = { version = "0.30.3", features = ["write"] } diff --git a/examples/dwarf/dwarf_export/README.md b/examples/dwarf/dwarf_export/README.md new file mode 100644 index 0000000..cea8e3b --- /dev/null +++ b/examples/dwarf/dwarf_export/README.md @@ -0,0 +1 @@ +# DWARF Export diff --git a/examples/dwarf/dwarf_export/build.rs b/examples/dwarf/dwarf_export/build.rs new file mode 100644 index 0000000..5ba9bcd --- /dev/null +++ b/examples/dwarf/dwarf_export/build.rs @@ -0,0 +1,68 @@ +use std::env; +use std::fs::File; +use std::io::BufReader; +use std::path::PathBuf; + +#[cfg(target_os = "macos")] +static LASTRUN_PATH: (&str, &str) = ("HOME", "Library/Application Support/Binary Ninja/lastrun"); + +#[cfg(target_os = "linux")] +static LASTRUN_PATH: (&str, &str) = ("HOME", ".binaryninja/lastrun"); + +#[cfg(windows)] +static LASTRUN_PATH: (&str, &str) = ("APPDATA", "Binary Ninja\\lastrun"); + +// Check last run location for path to BinaryNinja; Otherwise check the default install locations +fn link_path() -> PathBuf { + use std::io::prelude::*; + + let home = PathBuf::from(env::var(LASTRUN_PATH.0).unwrap()); + let lastrun = PathBuf::from(&home).join(LASTRUN_PATH.1); + + File::open(lastrun) + .and_then(|f| { + let mut binja_path = String::new(); + let mut reader = BufReader::new(f); + + reader.read_line(&mut binja_path)?; + Ok(PathBuf::from(binja_path.trim())) + }) + .unwrap_or_else(|_| { + #[cfg(target_os = "macos")] + return PathBuf::from("/Applications/Binary Ninja.app/Contents/MacOS"); + + #[cfg(target_os = "linux")] + return home.join("binaryninja"); + + #[cfg(windows)] + return PathBuf::from(env::var("PROGRAMFILES").unwrap()) + .join("Vector35\\BinaryNinja\\"); + }) +} + +fn main() { + // Use BINARYNINJADIR first for custom BN builds/configurations (BN devs/build server), fallback on defaults + let install_path = env::var("BINARYNINJADIR") + .map(PathBuf::from) + .unwrap_or_else(|_| link_path()); + + #[cfg(target_os = "linux")] + println!( + "cargo:rustc-link-arg=-Wl,-rpath,{},-L{},-l:libbinaryninjacore.so.1", + install_path.to_str().unwrap(), + install_path.to_str().unwrap(), + ); + + #[cfg(target_os = "macos")] + println!( + "cargo:rustc-link-arg=-Wl,-rpath,{},-L{},-lbinaryninjacore", + install_path.to_str().unwrap(), + install_path.to_str().unwrap(), + ); + + #[cfg(target_os = "windows")] + { + println!("cargo:rustc-link-lib=binaryninjacore"); + println!("cargo:rustc-link-search={}", install_path.to_str().unwrap()); + } +} diff --git a/examples/dwarf/dwarf_export/src/edit_distance.rs b/examples/dwarf/dwarf_export/src/edit_distance.rs new file mode 100644 index 0000000..9f13545 --- /dev/null +++ b/examples/dwarf/dwarf_export/src/edit_distance.rs @@ -0,0 +1,44 @@ +pub(crate) fn distance(a: &str, b: &str) -> usize { + if a == b { + return 0; + } + match (a.chars().count(), b.chars().count()) { + (0, b) => return b, + (a, 0) => return a, + // (a_len, b_len) if a_len < b_len => return distance(b, a), + _ => (), + } + + let mut result = 0; + let mut cache: Vec = (1..a.chars().count() + 1).collect(); + + for (index_b, char_b) in b.chars().enumerate() { + result = index_b; + let mut distance_a = index_b; + + for (index_a, char_a) in a.chars().enumerate() { + let distance_b = if char_a == char_b { + distance_a + } else { + distance_a + 1 + }; + + distance_a = cache[index_a]; + + result = if distance_a > result { + if distance_b > result { + result + 1 + } else { + distance_b + } + } else if distance_b > distance_a { + distance_a + 1 + } else { + distance_b + }; + + cache[index_a] = result; + } + } + result +} diff --git a/examples/dwarf/dwarf_export/src/lib.rs b/examples/dwarf/dwarf_export/src/lib.rs new file mode 100644 index 0000000..6af9afe --- /dev/null +++ b/examples/dwarf/dwarf_export/src/lib.rs @@ -0,0 +1,795 @@ +mod edit_distance; + +use gimli::{ + constants, + write::{ + Address, AttributeValue, DwarfUnit, EndianVec, Expression, Range, RangeList, Sections, + UnitEntryId, + }, +}; +use object::{write, Architecture, BinaryFormat, SectionKind}; +use std::fs; + +use binaryninja::{ + binaryview::{BinaryView, BinaryViewBase, BinaryViewExt}, + command::{register, Command}, + interaction, + interaction::{FormResponses, FormResponses::Index}, + logger::init, + rc::Ref, + string::BnString, + symbol::SymbolType, + types::{Conf, MemberAccess, StructureType, Type, TypeClass}, +}; +use log::{error, info, LevelFilter}; + +fn export_type( + name: String, + t: &Type, + bv: &BinaryView, + defined_types: &mut Vec<(Ref, UnitEntryId)>, + dwarf: &mut DwarfUnit, +) -> Option { + if let Some((_, die)) = defined_types + .iter() + .find(|(defined_type, _)| defined_type.as_ref() == t) + { + return Some(*die); + } + + let root = dwarf.unit.root(); + match t.type_class() { + TypeClass::VoidTypeClass => { + let void_die_uid = dwarf.unit.add(root, constants::DW_TAG_unspecified_type); + defined_types.push((t.to_owned(), void_die_uid)); + + dwarf.unit.get_mut(void_die_uid).set( + gimli::DW_AT_name, + AttributeValue::String("void".as_bytes().to_vec()), + ); + Some(void_die_uid) + } + TypeClass::BoolTypeClass => { + let bool_die_uid = dwarf.unit.add(root, constants::DW_TAG_base_type); + defined_types.push((t.to_owned(), bool_die_uid)); + + dwarf.unit.get_mut(bool_die_uid).set( + gimli::DW_AT_name, + AttributeValue::String(name.as_bytes().to_vec()), + ); + dwarf.unit.get_mut(bool_die_uid).set( + gimli::DW_AT_byte_size, + AttributeValue::Data1(t.width() as u8), + ); + dwarf.unit.get_mut(bool_die_uid).set( + gimli::DW_AT_encoding, + AttributeValue::Encoding(constants::DW_ATE_float), + ); + Some(bool_die_uid) + } + TypeClass::IntegerTypeClass => { + let int_die_uid = dwarf.unit.add(root, constants::DW_TAG_base_type); + defined_types.push((t.to_owned(), int_die_uid)); + + dwarf.unit.get_mut(int_die_uid).set( + gimli::DW_AT_name, + AttributeValue::String(name.as_bytes().to_vec()), + ); + dwarf.unit.get_mut(int_die_uid).set( + gimli::DW_AT_byte_size, + AttributeValue::Data1(t.width() as u8), + ); + dwarf.unit.get_mut(int_die_uid).set( + gimli::DW_AT_encoding, + if t.is_signed().contents { + AttributeValue::Encoding(constants::DW_ATE_signed) + } else { + AttributeValue::Encoding(constants::DW_ATE_unsigned) + }, + ); + Some(int_die_uid) + } + TypeClass::FloatTypeClass => { + let float_die_uid = dwarf.unit.add(root, constants::DW_TAG_base_type); + defined_types.push((t.to_owned(), float_die_uid)); + + dwarf.unit.get_mut(float_die_uid).set( + gimli::DW_AT_name, + AttributeValue::String(name.as_bytes().to_vec()), + ); + dwarf.unit.get_mut(float_die_uid).set( + gimli::DW_AT_byte_size, + AttributeValue::Data1(t.width() as u8), + ); + dwarf.unit.get_mut(float_die_uid).set( + gimli::DW_AT_encoding, + AttributeValue::Encoding(constants::DW_ATE_float), + ); + Some(float_die_uid) + } + TypeClass::StructureTypeClass => { + let structure_die_uid = match t.get_structure().unwrap().structure_type() { + StructureType::ClassStructureType => { + dwarf.unit.add(root, constants::DW_TAG_class_type) + } + StructureType::StructStructureType => { + dwarf.unit.add(root, constants::DW_TAG_structure_type) + } + StructureType::UnionStructureType => { + dwarf.unit.add(root, constants::DW_TAG_union_type) + } + }; + defined_types.push((t.to_owned(), structure_die_uid)); + + dwarf.unit.get_mut(structure_die_uid).set( + gimli::DW_AT_name, + AttributeValue::String(name.as_bytes().to_vec()), + ); + dwarf.unit.get_mut(structure_die_uid).set( + gimli::DW_AT_byte_size, + AttributeValue::Data2(t.width() as u16), + ); + + for struct_member in t.get_structure().unwrap().members().unwrap() { + let struct_member_die_uid = + dwarf.unit.add(structure_die_uid, constants::DW_TAG_member); + dwarf.unit.get_mut(struct_member_die_uid).set( + gimli::DW_AT_name, + AttributeValue::String(struct_member.name.as_bytes().to_vec()), + ); + match struct_member.access { + MemberAccess::PrivateAccess => { + dwarf.unit.get_mut(struct_member_die_uid).set( + gimli::DW_AT_accessibility, + AttributeValue::Accessibility(gimli::DW_ACCESS_private), + ); + } + MemberAccess::ProtectedAccess => { + dwarf.unit.get_mut(struct_member_die_uid).set( + gimli::DW_AT_accessibility, + AttributeValue::Accessibility(gimli::DW_ACCESS_protected), + ); + } + MemberAccess::PublicAccess => { + dwarf.unit.get_mut(struct_member_die_uid).set( + gimli::DW_AT_accessibility, + AttributeValue::Accessibility(gimli::DW_ACCESS_public), + ); + } + _ => (), + }; + dwarf.unit.get_mut(struct_member_die_uid).set( + gimli::DW_AT_data_member_location, + AttributeValue::Data8(struct_member.offset), + ); + + if let Some(target_die_uid) = export_type( + format!("{}", struct_member.ty.contents), + struct_member.ty.contents.as_ref(), + bv, + defined_types, + dwarf, + ) { + dwarf + .unit + .get_mut(struct_member_die_uid) + .set(gimli::DW_AT_type, AttributeValue::UnitRef(target_die_uid)); + } + } + + Some(structure_die_uid) + } + TypeClass::EnumerationTypeClass => { + let enum_die_uid = dwarf.unit.add(root, constants::DW_TAG_enumeration_type); + defined_types.push((t.to_owned(), enum_die_uid)); + + dwarf.unit.get_mut(enum_die_uid).set( + gimli::DW_AT_name, + AttributeValue::String(name.as_bytes().to_vec()), + ); + dwarf.unit.get_mut(enum_die_uid).set( + gimli::DW_AT_byte_size, + AttributeValue::Data1(t.width() as u8), + ); + + for enum_field in t.get_enumeration().unwrap().members() { + let enum_field_die_uid = dwarf.unit.add(enum_die_uid, constants::DW_TAG_enumerator); + dwarf.unit.get_mut(enum_field_die_uid).set( + gimli::DW_AT_name, + AttributeValue::String(enum_field.name.as_bytes().to_vec()), + ); + dwarf.unit.get_mut(enum_field_die_uid).set( + gimli::DW_AT_const_value, + AttributeValue::Data4(enum_field.value as u32), + ); + } + + Some(enum_die_uid) + } + TypeClass::PointerTypeClass => { + let pointer_die_uid = dwarf.unit.add(root, constants::DW_TAG_pointer_type); + defined_types.push((t.to_owned(), pointer_die_uid)); + + dwarf.unit.get_mut(pointer_die_uid).set( + gimli::DW_AT_byte_size, + AttributeValue::Data1(t.width() as u8), + ); + if let Ok(Conf { + contents: target_type, + .. + }) = t.target() + { + // TODO : Passing through the name here might be wrong + if let Some(target_die_uid) = + export_type(name, &target_type, bv, defined_types, dwarf) + { + dwarf + .unit + .get_mut(pointer_die_uid) + .set(gimli::DW_AT_type, AttributeValue::UnitRef(target_die_uid)); + } + } + Some(pointer_die_uid) + } + TypeClass::ArrayTypeClass => { + let array_die_uid = dwarf.unit.add(root, constants::DW_TAG_array_type); + defined_types.push((t.to_owned(), array_die_uid)); + + // Name + dwarf.unit.get_mut(array_die_uid).set( + gimli::DW_AT_name, + AttributeValue::String(name.as_bytes().to_vec()), + ); + + // Element type + if let Ok(Conf { + contents: element_type, + .. + }) = t.element_type() + { + // TODO : Passing through the name here might be wrong + if let Some(target_die_uid) = + export_type(name, &element_type, bv, defined_types, dwarf) + { + dwarf + .unit + .get_mut(array_die_uid) + .set(gimli::DW_AT_type, AttributeValue::UnitRef(target_die_uid)); + } + } + + // For some reason subrange types have a 'type' field that is just "some type" that'll work to index this array + // We're hardcoding this to a uint64_t. This could be unsound. + let array_accessor_type = export_type( + "uint64_t".to_string(), + &Type::named_int(8, false, "uint64_t"), + bv, + defined_types, + dwarf, + ) + .unwrap(); + + // Array length and multidimensional arrays + let mut current_t = t.to_owned(); + while let Ok(Conf { + contents: element_type, + .. + }) = current_t.element_type() + { + let array_dimension_die_uid = dwarf + .unit + .add(array_die_uid, constants::DW_TAG_subrange_type); + + dwarf.unit.get_mut(array_dimension_die_uid).set( + gimli::DW_AT_type, + AttributeValue::UnitRef(array_accessor_type), + ); + + dwarf.unit.get_mut(array_dimension_die_uid).set( + gimli::DW_AT_upper_bound, + AttributeValue::Data8((current_t.width() / element_type.width()) - 1), + ); + + if element_type.type_class() != TypeClass::ArrayTypeClass { + break; + } else { + current_t = element_type; + } + } + + Some(array_die_uid) + } + TypeClass::FunctionTypeClass => { + Some(dwarf.unit.add(root, constants::DW_TAG_unspecified_type)) + } + TypeClass::VarArgsTypeClass => { + Some(dwarf.unit.add(root, constants::DW_TAG_unspecified_type)) + } + TypeClass::ValueTypeClass => Some(dwarf.unit.add(root, constants::DW_TAG_unspecified_type)), + TypeClass::NamedTypeReferenceClass => { + let ntr = t.get_named_type_reference().unwrap(); + if let Some(target_type) = ntr.target(bv) { + if target_type.type_class() == TypeClass::StructureTypeClass { + export_type( + ntr.name().to_string(), + &target_type, + bv, + defined_types, + dwarf, + ) + } else { + let typedef_die_uid = dwarf.unit.add(root, constants::DW_TAG_typedef); + defined_types.push((t.to_owned(), typedef_die_uid)); + + dwarf.unit.get_mut(typedef_die_uid).set( + gimli::DW_AT_name, + AttributeValue::String(ntr.name().to_string().as_bytes().to_vec()), + ); + + if let Some(target_die_uid) = export_type( + ntr.name().to_string(), + &target_type, + bv, + defined_types, + dwarf, + ) { + dwarf + .unit + .get_mut(typedef_die_uid) + .set(gimli::DW_AT_type, AttributeValue::UnitRef(target_die_uid)); + } + Some(typedef_die_uid) + } + } else { + error!("Could not get target of typedef `{}`", ntr.name()); + None + } + } + TypeClass::WideCharTypeClass => { + let wide_char_die_uid = dwarf.unit.add(root, constants::DW_TAG_base_type); + defined_types.push((t.to_owned(), wide_char_die_uid)); + + dwarf.unit.get_mut(wide_char_die_uid).set( + gimli::DW_AT_name, + AttributeValue::String(name.as_bytes().to_vec()), + ); + dwarf.unit.get_mut(wide_char_die_uid).set( + gimli::DW_AT_byte_size, + AttributeValue::Data1(t.width() as u8), + ); + dwarf.unit.get_mut(wide_char_die_uid).set( + gimli::DW_AT_encoding, + if t.is_signed().contents { + AttributeValue::Encoding(constants::DW_ATE_signed_char) + } else { + AttributeValue::Encoding(constants::DW_ATE_unsigned_char) + }, + ); + Some(wide_char_die_uid) + } + } +} + +fn export_types( + bv: &BinaryView, + dwarf: &mut DwarfUnit, + defined_types: &mut Vec<(Ref, UnitEntryId)>, +) { + for t in &bv.types() { + export_type( + t.name().to_string(), + &t.type_object(), + bv, + defined_types, + dwarf, + ); + } +} + +fn export_functions( + bv: &BinaryView, + dwarf: &mut DwarfUnit, + defined_types: &mut Vec<(Ref, UnitEntryId)>, +) { + let entry_point = bv.entry_point_function(); + + for function in &bv.functions() { + // Create function DIE as child of the compilation unit DIE + let root = dwarf.unit.root(); + let function_die_uid = dwarf.unit.add(root, constants::DW_TAG_subprogram); + // let function_die = dwarf.unit.get_mut(function_die_uid); + + // Set subprogram DIE attributes + dwarf.unit.get_mut(function_die_uid).set( + gimli::DW_AT_name, + AttributeValue::String(function.symbol().short_name().as_bytes().to_vec()), + ); + + // TODO : (DW_AT_main_subprogram VS DW_TAG_entry_point) + // TODO : This attribute seems maybe usually unused? + if let Ok(entry_point_function) = &entry_point { + if entry_point_function.as_ref() == function.as_ref() { + dwarf + .unit + .get_mut(function_die_uid) + .set(gimli::DW_AT_main_subprogram, AttributeValue::Flag(true)); + dwarf.unit.get_mut(function_die_uid).set( + gimli::DW_AT_low_pc, + AttributeValue::Address(Address::Constant(function.start())), // TODO: Relocations + ); + } + } + + let address_ranges = function.address_ranges(); + if address_ranges.len() == 1 { + let address_range = address_ranges.get(0); + dwarf.unit.get_mut(function_die_uid).set( + gimli::DW_AT_low_pc, + AttributeValue::Address(Address::Constant(address_range.start())), // TODO: Relocations + ); + dwarf.unit.get_mut(function_die_uid).set( + gimli::DW_AT_high_pc, + AttributeValue::Address(Address::Constant(address_range.end())), + ); + } else { + let range_list = RangeList( + address_ranges + .into_iter() + .map(|range| Range::StartLength { + begin: Address::Constant(range.start()), // TODO: Relocations? + length: range.end() - range.start(), + }) + .collect(), + ); + let range_list_id = dwarf.unit.ranges.add(range_list); + dwarf.unit.get_mut(function_die_uid).set( + gimli::DW_AT_ranges, + AttributeValue::RangeListRef(range_list_id), + ); + } + + // DWARFv4 2.18: " If no DW_AT_entry_pc attribute is present, then the entry address is assumed to be the same as the value of the DW_AT_low_pc attribute" + if address_ranges.get(0).start() != function.start() { + dwarf.unit.get_mut(function_die_uid).set( + gimli::DW_AT_entry_pc, + AttributeValue::Address(Address::Constant(function.start())), + ); + } + + if function.return_type().contents.type_class() != TypeClass::VoidTypeClass { + if let Some(return_type_die_uid) = export_type( + format!("{}", function.return_type().contents), + function.return_type().contents.as_ref(), + bv, + defined_types, + dwarf, + ) { + dwarf.unit.get_mut(function_die_uid).set( + gimli::DW_AT_type, + AttributeValue::UnitRef(return_type_die_uid), + ); + } + } + + for parameter in function.function_type().parameters().unwrap() { + let param_die_uid = dwarf + .unit + .add(function_die_uid, constants::DW_TAG_formal_parameter); + + dwarf.unit.get_mut(param_die_uid).set( + gimli::DW_AT_name, + AttributeValue::String(parameter.name.as_bytes().to_vec()), + ); + + if let Some(target_die_uid) = export_type( + format!("{}", parameter.t.contents), + ¶meter.t.contents, + bv, + defined_types, + dwarf, + ) { + dwarf + .unit + .get_mut(param_die_uid) + .set(gimli::DW_AT_type, AttributeValue::UnitRef(target_die_uid)); + } + } + + if function.function_type().has_variable_arguments().contents { + dwarf + .unit + .add(function_die_uid, constants::DW_TAG_unspecified_parameters); + } + + if function.symbol().external() { + dwarf + .unit + .get_mut(function_die_uid) + .set(gimli::DW_AT_external, AttributeValue::Flag(true)); + } + + // TODO : calling convention attr + // TODO : local vars + } +} + +fn export_data_vars( + bv: &BinaryView, + dwarf: &mut DwarfUnit, + defined_types: &mut Vec<(Ref, UnitEntryId)>, +) { + let root = dwarf.unit.root(); + + 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 { + continue; + } + } + + let var_die_uid = dwarf.unit.add(root, constants::DW_TAG_variable); + + if let Some(symbol) = data_variable.symbol(bv) { + dwarf.unit.get_mut(var_die_uid).set( + gimli::DW_AT_name, + AttributeValue::String(symbol.full_name().as_bytes().to_vec()), + ); + + if symbol.external() { + dwarf + .unit + .get_mut(var_die_uid) + .set(gimli::DW_AT_external, AttributeValue::Flag(true)); + } + } else { + dwarf.unit.get_mut(var_die_uid).set( + gimli::DW_AT_name, + AttributeValue::String( + format!("data_{:x}", data_variable.address) + .as_bytes() + .to_vec(), + ), + ); + } + + let mut variable_location = Expression::new(); + 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(), + bv, + defined_types, + dwarf, + ) { + dwarf + .unit + .get_mut(var_die_uid) + .set(gimli::DW_AT_type, AttributeValue::UnitRef(target_die_uid)); + } + } +} + +fn present_form(bv_arch: &str) -> Vec { + // TODO : Verify inputs (like save location) so that we can fail early + // TODO : Add Language field + // TODO : Choose to export types/functions/etc + let archs = [ + "Unknown", + "Aarch64", + "Aarch64_Ilp32", + "Arm", + "Avr", + "Bpf", + "I386", + "X86_64", + "X86_64_X32", + "Hexagon", + "LoongArch64", + "Mips", + "Mips64", + "Msp430", + "PowerPc", + "PowerPc64", + "Riscv32", + "Riscv64", + "S390x", + "Sbf", + "Sparc64", + "Wasm32", + "Xtensa", + ]; + interaction::FormInputBuilder::new() + .save_file_field( + "Save Location", + Some("Debug Files (*.dwo *.debug);;All Files (*)"), + None, + None, + ) + .choice_field( + "Architecture", + &archs, + archs + .iter() + .enumerate() + .min_by(|&(_, arch_name_1), &(_, arch_name_2)| { + edit_distance::distance(bv_arch, arch_name_1) + .cmp(&edit_distance::distance(bv_arch, arch_name_2)) + }) + .map(|(index, _)| index), + ) + // Add actual / better support for formats other than elf? + // .choice_field( + // "Container Format", + // &["Coff", "Elf", "MachO", "Pe", "Wasm", "Xcoff"], + // None, + // ) + .get_form_input("Export as DWARF") +} + +fn write_dwarf( + responses: Vec, + endian: T, + dwarf: &mut DwarfUnit, +) { + if responses.len() < 2 { + return; + } + + let arch = match responses[1] { + Index(0) => Architecture::Unknown, + Index(1) => Architecture::Aarch64, + Index(2) => Architecture::Aarch64_Ilp32, + Index(3) => Architecture::Arm, + Index(4) => Architecture::Avr, + Index(5) => Architecture::Bpf, + Index(6) => Architecture::I386, + Index(7) => Architecture::X86_64, + Index(8) => Architecture::X86_64_X32, + Index(9) => Architecture::Hexagon, + Index(10) => Architecture::LoongArch64, + Index(11) => Architecture::Mips, + Index(12) => Architecture::Mips64, + Index(13) => Architecture::Msp430, + Index(14) => Architecture::PowerPc, + Index(15) => Architecture::PowerPc64, + Index(16) => Architecture::Riscv32, + Index(17) => Architecture::Riscv64, + Index(18) => Architecture::S390x, + Index(19) => Architecture::Sbf, + Index(20) => Architecture::Sparc64, + Index(21) => Architecture::Wasm32, + Index(22) => Architecture::Xtensa, + _ => Architecture::Unknown, + }; + + // let format = match responses[2] { + // Index(0) => BinaryFormat::Coff, + // Index(1) => BinaryFormat::Elf, + // Index(2) => BinaryFormat::MachO, + // Index(3) => BinaryFormat::Pe, + // Index(4) => BinaryFormat::Wasm, + // Index(5) => BinaryFormat::Xcoff, + // _ => BinaryFormat::Elf, + // }; + + // TODO : Look in to other options (mangling, flags, etc (see Object::new)) + let mut out_object = write::Object::new( + BinaryFormat::Elf, + arch, + if endian.is_little_endian() { + object::Endianness::Little + } else { + object::Endianness::Big + }, + ); + + // Finally, write the DWARF data to the sections. + let mut sections = Sections::new(EndianVec::new(endian)); + dwarf.write(&mut sections).unwrap(); + + sections + .for_each(|input_id, input_data| { + // Create section in output object + let output_id = out_object.add_section( + vec![], // Only machos have segment names? see object::write::Object::segment_name + input_id.name().as_bytes().to_vec(), + SectionKind::Debug, // TODO: Might be wrong + ); + + // Write data to section in output object + let out_section = out_object.section_mut(output_id); + if out_section.is_bss() { + panic!("Please report this as a bug: output section is bss"); + } else { + out_section.set_data(input_data.clone().into_vec(), 1); + } + // out_section.flags = in_section.flags(); // TODO + + Ok::<(), ()>(()) + }) + .unwrap(); + + if let interaction::FormResponses::String(filename) = &responses[0] { + if let Ok(out_data) = out_object.write() { + if let Err(err) = fs::write(filename, out_data) { + error!("Failed to write DWARF file: {}", err); + } else { + info!("Successfully saved as DWARF to `{}`", filename); + } + } else { + error!("Failed to write DWARF with requested settings"); + } + } +} + +fn export_dwarf(bv: &BinaryView) { + let arch_name = if let Some(arch) = bv.default_arch() { + arch.name() + } else { + BnString::new("Unknown") + }; + let responses = present_form(&arch_name); + + let encoding = gimli::Encoding { + format: gimli::Format::Dwarf32, + version: 4, + address_size: bv.address_size() as u8, + }; + + // Create a container for a single compilation unit. + // TODO : Add attributes to the compilation unit DIE? + let mut dwarf = DwarfUnit::new(encoding); + dwarf.unit.get_mut(dwarf.unit.root()).set( + gimli::DW_AT_producer, + AttributeValue::String("Binary Ninja DWARF Export Plugin".as_bytes().to_vec()), + ); + + // Everything has types, so we need to track what is already defined globally as to not duplicate type entries + let mut defined_types: Vec<(Ref, UnitEntryId)> = vec![]; + export_types(bv, &mut dwarf, &mut defined_types); + export_functions(bv, &mut dwarf, &mut defined_types); + export_data_vars(bv, &mut dwarf, &mut defined_types); + // TODO: Export all symbols instead of just data vars? + // TODO: Sections? Segments? + + if bv.default_endianness() == binaryninja::Endianness::LittleEndian { + write_dwarf(responses, gimli::LittleEndian, &mut dwarf); + } else { + write_dwarf(responses, gimli::BigEndian, &mut dwarf); + }; +} + +struct MyCommand; +impl Command for MyCommand { + fn action(&self, view: &BinaryView) { + export_dwarf(view) + } + + fn valid(&self, _view: &BinaryView) -> bool { + true + } +} + +#[no_mangle] +pub extern "C" fn CorePluginInit() -> bool { + init(LevelFilter::Debug).expect("Unable to initialize logger"); + + register( + "Export as DWARF", + "Export current analysis state and annotations as DWARF for import into other tools", + MyCommand {}, + ); + + true +} diff --git a/examples/dwarf/dwarf_import/CMakeLists.txt b/examples/dwarf/dwarf_import/CMakeLists.txt new file mode 100644 index 0000000..9115b7a --- /dev/null +++ b/examples/dwarf/dwarf_import/CMakeLists.txt @@ -0,0 +1,93 @@ +cmake_minimum_required(VERSION 3.9 FATAL_ERROR) + +project(dwarf_import) + +file(GLOB PLUGIN_SOURCES CONFIGURE_DEPENDS + ${PROJECT_SOURCE_DIR}/Cargo.toml + ${PROJECT_SOURCE_DIR}/src/*.rs + ${PROJECT_SOURCE_DIR}/../shared/Cargo.toml + ${PROJECT_SOURCE_DIR}/../shared/src/*.rs) + +file(GLOB_RECURSE API_SOURCES CONFIGURE_DEPENDS + ${PROJECT_SOURCE_DIR}/../../../../binaryninjacore.h + ${PROJECT_SOURCE_DIR}/../../../binaryninjacore-sys/build.rs + ${PROJECT_SOURCE_DIR}/../../../binaryninjacore-sys/Cargo.toml + ${PROJECT_SOURCE_DIR}/../../../binaryninjacore-sys/src/* + ${PROJECT_SOURCE_DIR}/../../../Cargo.toml + ${PROJECT_SOURCE_DIR}/../../../src/*.rs) + +if(CMAKE_BUILD_TYPE MATCHES Debug) + set(TARGET_DIR ${PROJECT_BINARY_DIR}/target/debug) + set(CARGO_OPTS --target-dir=${PROJECT_BINARY_DIR}/target) +else() + set(TARGET_DIR ${PROJECT_BINARY_DIR}/target/release) + set(CARGO_OPTS --target-dir=${PROJECT_BINARY_DIR}/target --release) + set(OUTPUT_PDB_NAME ${CMAKE_SHARED_LIBRARY_PREFIX}dwarf_import.pdb) +endif() + +set(OUTPUT_FILE ${CMAKE_STATIC_LIBRARY_PREFIX}dwarf_import${CMAKE_SHARED_LIBRARY_SUFFIX}) +set(PLUGIN_PATH ${TARGET_DIR}/${OUTPUT_FILE}) + +add_custom_target(dwarf_import ALL DEPENDS ${PLUGIN_PATH}) +add_dependencies(dwarf_import binaryninjaapi) + +find_program(RUSTUP_PATH rustup REQUIRED HINTS ~/.cargo/bin) +if(CARGO_API_VERSION) + set(RUSTUP_COMMAND ${RUSTUP_PATH} run ${CARGO_API_VERSION} cargo build) +else() + set(RUSTUP_COMMAND ${RUSTUP_PATH} run ${CARGO_STABLE_VERSION} cargo build) +endif() + +if(APPLE) + if(UNIVERSAL) + if(CMAKE_BUILD_TYPE MATCHES Debug) + set(AARCH64_LIB_PATH ${PROJECT_BINARY_DIR}/target/aarch64-apple-darwin/debug/${OUTPUT_FILE}) + set(X86_64_LIB_PATH ${PROJECT_BINARY_DIR}/target/x86_64-apple-darwin/debug/${OUTPUT_FILE}) + else() + set(AARCH64_LIB_PATH ${PROJECT_BINARY_DIR}/target/aarch64-apple-darwin/release/${OUTPUT_FILE}) + set(X86_64_LIB_PATH ${PROJECT_BINARY_DIR}/target/x86_64-apple-darwin/release/${OUTPUT_FILE}) + endif() + + add_custom_command( + OUTPUT ${PLUGIN_PATH} + COMMAND ${CMAKE_COMMAND} -E env + MACOSX_DEPLOYMENT_TARGET=10.14 BINARYNINJADIR=${BN_CORE_OUTPUT_DIR} + ${RUSTUP_COMMAND} --target=aarch64-apple-darwin ${CARGO_OPTS} + COMMAND ${CMAKE_COMMAND} -E env + MACOSX_DEPLOYMENT_TARGET=10.14 BINARYNINJADIR=${BN_CORE_OUTPUT_DIR} + ${RUSTUP_COMMAND} --target=x86_64-apple-darwin ${CARGO_OPTS} + COMMAND mkdir -p ${TARGET_DIR} + COMMAND lipo -create ${AARCH64_LIB_PATH} ${X86_64_LIB_PATH} -output ${PLUGIN_PATH} + COMMAND ${CMAKE_COMMAND} -E copy ${PLUGIN_PATH} ${BN_CORE_PLUGIN_DIR} + WORKING_DIRECTORY ${PROJECT_SOURCE_DIR} + DEPENDS ${PLUGIN_SOURCES} ${API_SOURCES}) + else() + if(CMAKE_BUILD_TYPE MATCHES Debug) + set(LIB_PATH ${PROJECT_BINARY_DIR}/target/debug/${OUTPUT_FILE}) + else() + set(LIB_PATH ${PROJECT_BINARY_DIR}/target/release/${OUTPUT_FILE}) + endif() + + add_custom_command( + OUTPUT ${PLUGIN_PATH} + COMMAND ${CMAKE_COMMAND} -E env MACOSX_DEPLOYMENT_TARGET=10.14 BINARYNINJADIR=${BN_CORE_OUTPUT_DIR} ${RUSTUP_COMMAND} ${CARGO_OPTS} + COMMAND ${CMAKE_COMMAND} -E copy ${PLUGIN_PATH} ${BN_CORE_PLUGIN_DIR} + WORKING_DIRECTORY ${PROJECT_SOURCE_DIR} + DEPENDS ${PLUGIN_SOURCES} ${API_SOURCES}) + endif() +elseif(WIN32) + add_custom_command( + OUTPUT ${PLUGIN_PATH} + COMMAND ${CMAKE_COMMAND} -E env BINARYNINJADIR=${BN_CORE_OUTPUT_DIR} ${RUSTUP_COMMAND} ${CARGO_OPTS} + COMMAND ${CMAKE_COMMAND} -E copy ${PLUGIN_PATH} ${BN_CORE_PLUGIN_DIR} + COMMAND ${CMAKE_COMMAND} -E copy ${TARGET_DIR}/${OUTPUT_PDB_NAME} ${BN_CORE_PLUGIN_DIR} + WORKING_DIRECTORY ${PROJECT_SOURCE_DIR} + DEPENDS ${PLUGIN_SOURCES} ${API_SOURCES}) +else() + add_custom_command( + OUTPUT ${PLUGIN_PATH} + COMMAND ${CMAKE_COMMAND} -E env BINARYNINJADIR=${BN_CORE_OUTPUT_DIR} ${RUSTUP_COMMAND} ${CARGO_OPTS} + COMMAND ${CMAKE_COMMAND} -E copy ${PLUGIN_PATH} ${BN_CORE_PLUGIN_DIR} + WORKING_DIRECTORY ${PROJECT_SOURCE_DIR} + DEPENDS ${PLUGIN_SOURCES} ${API_SOURCES}) +endif() diff --git a/examples/dwarf/dwarf_import/Cargo.toml b/examples/dwarf/dwarf_import/Cargo.toml new file mode 100644 index 0000000..0760599 --- /dev/null +++ b/examples/dwarf/dwarf_import/Cargo.toml @@ -0,0 +1,14 @@ +[package] +name = "dwarf_import" +version = "0.1.0" +authors = ["KyleMiles "] +edition = "2021" + +[lib] +crate-type = ["cdylib"] + +[dependencies] +dwarfreader = { path = "../shared/" } +binaryninja = { path = "../../../" } +gimli = "0.27" +log = "0.4.17" diff --git a/examples/dwarf/dwarf_import/src/die_handlers.rs b/examples/dwarf/dwarf_import/src/die_handlers.rs new file mode 100644 index 0000000..125038d --- /dev/null +++ b/examples/dwarf/dwarf_import/src/die_handlers.rs @@ -0,0 +1,390 @@ +// Copyright 2021-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 crate::dwarfdebuginfo::{DebugInfoBuilder, DebugInfoBuilderContext, TypeUID}; +use crate::helpers::*; +use crate::types::get_type; + +use binaryninja::{ + rc::*, + types::{EnumerationBuilder, FunctionParameter, ReferenceType, Type, TypeBuilder}, +}; + +use gimli::{constants, AttributeValue::Encoding, DebuggingInformationEntry, Reader, Unit}; + +pub(crate) fn handle_base_type>( + unit: &Unit, + entry: &DebuggingInformationEntry, + debug_info_builder_context: &DebugInfoBuilderContext, +) -> Option> { + // All base types have: + // DW_AT_encoding (our concept of type_class) + // DW_AT_byte_size and/or DW_AT_bit_size + // *DW_AT_name + // *DW_AT_endianity (assumed default for arch) + // *DW_AT_data_bit_offset (assumed 0) + // *Some indication of signedness? + // * = Optional + + let name = debug_info_builder_context.get_name(unit, entry)?; + let size = get_size_as_usize(entry)?; + match entry.attr_value(constants::DW_AT_encoding) { + Ok(Some(Encoding(encoding))) => { + match encoding { + constants::DW_ATE_address => None, + constants::DW_ATE_boolean => Some(Type::bool()), + constants::DW_ATE_complex_float => None, + constants::DW_ATE_float => Some(Type::named_float(size, name)), + constants::DW_ATE_signed => Some(Type::named_int(size, true, name)), + constants::DW_ATE_signed_char => Some(Type::named_int(size, true, name)), + constants::DW_ATE_unsigned => Some(Type::named_int(size, false, name)), + constants::DW_ATE_unsigned_char => Some(Type::named_int(size, false, name)), + constants::DW_ATE_imaginary_float => None, + constants::DW_ATE_packed_decimal => None, + constants::DW_ATE_numeric_string => None, + constants::DW_ATE_edited => None, + constants::DW_ATE_signed_fixed => None, + constants::DW_ATE_unsigned_fixed => None, + constants::DW_ATE_decimal_float => Some(Type::named_float(size, name)), + constants::DW_ATE_UTF => Some(Type::named_int(size, false, name)), // TODO : Verify + constants::DW_ATE_UCS => None, + constants::DW_ATE_ASCII => None, // Some sort of array? + constants::DW_ATE_lo_user => None, + constants::DW_ATE_hi_user => None, + _ => None, // Anything else is invalid at time of writing (gimli v0.23.0) + } + } + _ => None, + } +} + +pub(crate) fn handle_enum>( + unit: &Unit, + entry: &DebuggingInformationEntry, + debug_info_builder_context: &DebugInfoBuilderContext, +) -> Option> { + // All base types have: + // DW_AT_byte_size + // *DW_AT_name + // *DW_AT_enum_class + // *DW_AT_type + // ?DW_AT_abstract_origin + // ?DW_AT_accessibility + // ?DW_AT_allocated + // ?DW_AT_associated + // ?DW_AT_bit_size + // ?DW_AT_bit_stride + // ?DW_AT_byte_stride + // ?DW_AT_data_location + // ?DW_AT_declaration + // ?DW_AT_description + // ?DW_AT_sibling + // ?DW_AT_signature + // ?DW_AT_specification + // ?DW_AT_start_scope + // ?DW_AT_visibility + // * = Optional + + // Children of enumeration_types are enumerators which contain: + // DW_AT_name + // DW_AT_const_value + // *DW_AT_description + + let enumeration_builder = EnumerationBuilder::new(); + + let mut tree = unit.entries_tree(Some(entry.offset())).unwrap(); + 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); + } + } + + Some(Type::enumeration( + &enumeration_builder.finalize(), + get_size_as_usize(entry).unwrap_or(8), + false, + )) +} + +pub(crate) fn handle_typedef( + debug_info_builder: &mut DebugInfoBuilder, + entry_type: Option, + typedef_name: String, +) -> (Option>, bool) { + // All base types have: + // DW_AT_name + // *DW_AT_type + // * = Optional + + // 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); + } + } + } + + // 5.3: "typedef represents a declaration of the type that is not also a definition" + (None, false) +} + +pub(crate) fn handle_pointer>( + entry: &DebuggingInformationEntry, + debug_info_builder_context: &DebugInfoBuilderContext, + debug_info_builder: &mut DebugInfoBuilder, + entry_type: Option, + reference_type: ReferenceType, +) -> Option> { + // All pointer types have: + // DW_AT_type + // *DW_AT_byte_size + // ?DW_AT_name + // ?DW_AT_address + // ?DW_AT_allocated + // ?DW_AT_associated + // ?DW_AT_data_location + // * = Optional + + 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; + Some(Type::pointer_of_width( + parent_type.as_ref(), + pointer_size, + false, + false, + Some(reference_type), + )) + } else { + Some(Type::pointer_of_width( + Type::void().as_ref(), + pointer_size, + false, + false, + Some(reference_type), + )) + } + } else if let Some(entry_type_offset) = entry_type { + let parent_type = debug_info_builder.get_type(entry_type_offset).unwrap().1; + Some(Type::pointer_of_width( + parent_type.as_ref(), + debug_info_builder_context.default_address_size(), + false, + false, + Some(reference_type), + )) + } else { + Some(Type::pointer_of_width( + Type::void().as_ref(), + debug_info_builder_context.default_address_size(), + false, + false, + Some(reference_type), + )) + } +} + +pub(crate) fn handle_array>( + unit: &Unit, + entry: &DebuggingInformationEntry, + debug_info_builder: &mut DebugInfoBuilder, + entry_type: Option, +) -> Option> { + // All array types have: + // DW_AT_type + // *DW_AT_name + // *DW_AT_ordering + // *DW_AT_byte_stride or DW_AT_bit_stride + // *DW_AT_byte_size or DW_AT_bit_size + // *DW_AT_allocated + // *DW_AT_associated and + // *DW_AT_data_location + // * = Optional + // 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 mut tree = unit.entries_tree(Some(entry.offset())).unwrap(); + let mut children = tree.root().unwrap().children(); + + // TODO : This is currently applying the size in reverse order + let mut result_type: Option> = None; + while let Ok(Some(child)) = children.next() { + if let Some(inner_type) = result_type { + result_type = Some(Type::array( + inner_type.as_ref(), + get_subrange_size(child.entry()), + )); + } else { + result_type = Some(Type::array( + parent_type.as_ref(), + get_subrange_size(child.entry()), + )); + } + } + + result_type.map_or(Some(Type::array(parent_type.as_ref(), 0)), Some) + } else { + None + } +} + +pub(crate) fn handle_function>( + unit: &Unit, + entry: &DebuggingInformationEntry, + debug_info_builder_context: &DebugInfoBuilderContext, + debug_info_builder: &mut DebugInfoBuilder, + entry_type: Option, +) -> Option> { + // All subroutine types have: + // *DW_AT_name + // *DW_AT_type (if not provided, void) + // *DW_AT_prototyped + // ?DW_AT_abstract_origin + // ?DW_AT_accessibility + // ?DW_AT_address_class + // ?DW_AT_allocated + // ?DW_AT_associated + // ?DW_AT_data_location + // ?DW_AT_declaration + // ?DW_AT_description + // ?DW_AT_sibling + // ?DW_AT_start_scope + // ?DW_AT_visibility + // * = Optional + + // May have children, including DW_TAG_formal_parameters, which all have: + // *DW_AT_type + // * = Optional + // or is otherwise DW_TAG_unspecified_parameters + + let return_type = match entry_type { + Some(entry_type_offset) => { + debug_info_builder + .get_type(entry_type_offset) + .expect("Subroutine return type was not processed") + .1 + } + 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) { + debug_info_builder.add_type( + get_uid(unit, entry), + name.clone(), + Type::named_type_from_type( + name, + &Type::function::( + return_type.as_ref(), + &[], + false, + ), + ), + false, + ); + } + + let mut parameters: Vec> = vec![]; + let mut variable_arguments = false; + + // Get all the children and populate + let mut tree = unit.entries_tree(Some(entry.offset())).unwrap(); + let mut children = tree.root().unwrap().children(); + while let Ok(Some(child)) = children.next() { + if child.entry().tag() == constants::DW_TAG_formal_parameter { + if let (Some(child_uid), Some(name)) = { + ( + get_type( + unit, + child.entry(), + debug_info_builder_context, + debug_info_builder, + ), + debug_info_builder_context.get_name(unit, child.entry()), + ) + } { + let child_type = debug_info_builder.get_type(child_uid).unwrap().1; + parameters.push(FunctionParameter::new(child_type, name, None)); + } + } else if child.entry().tag() == constants::DW_TAG_unspecified_parameters { + variable_arguments = true; + } + } + + if debug_info_builder_context.get_name(unit, entry).is_some() { + debug_info_builder.remove_type(get_uid(unit, entry)); + } + + Some(Type::function( + return_type.as_ref(), + ¶meters, + variable_arguments, + )) +} + +pub(crate) fn handle_const( + debug_info_builder: &mut DebugInfoBuilder, + entry_type: Option, +) -> Option> { + // All const types have: + // ?DW_AT_allocated + // ?DW_AT_associated + // ?DW_AT_data_location + // ?DW_AT_name + // ?DW_AT_sibling + // ?DW_AT_type + + if let Some(entry_type_offset) = entry_type { + let parent_type = debug_info_builder.get_type(entry_type_offset).unwrap().1; + Some((*parent_type).to_builder().set_const(true).finalize()) + } else { + Some(TypeBuilder::void().set_const(true).finalize()) + } +} + +pub(crate) fn handle_volatile( + debug_info_builder: &mut DebugInfoBuilder, + entry_type: Option, +) -> Option> { + // All const types have: + // ?DW_AT_allocated + // ?DW_AT_associated + // ?DW_AT_data_location + // ?DW_AT_name + // ?DW_AT_sibling + // ?DW_AT_type + + if let Some(entry_type_offset) = entry_type { + let parent_type = debug_info_builder.get_type(entry_type_offset).unwrap().1; + 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 new file mode 100644 index 0000000..4b0f935 --- /dev/null +++ b/examples/dwarf/dwarf_import/src/dwarfdebuginfo.rs @@ -0,0 +1,407 @@ +// Copyright 2021-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 crate::helpers::{get_uid, resolve_specification, DieReference}; + +use binaryninja::{ + binaryview::{BinaryView, BinaryViewBase, BinaryViewExt}, + debuginfo::{DebugFunctionInfo, DebugInfo}, + platform::Platform, + rc::*, + symbol::SymbolType, + templatesimplifier::simplify_str_to_fqn, + types::{Conf, FunctionParameter, Type}, +}; + +use gimli::{DebuggingInformationEntry, Dwarf, Reader, Unit}; + +use log::{error, warn}; +use std::{ + collections::{hash_map::Values, HashMap}, + hash::Hash, +}; + +pub(crate) type TypeUID = usize; + +///////////////////////// +// FunctionInfoBuilder + +// TODO : Function local variables +#[derive(PartialEq, Eq, Hash)] +pub(crate) struct FunctionInfoBuilder { + pub(crate) full_name: Option, + pub(crate) raw_name: Option, + pub(crate) return_type: Option, + pub(crate) address: Option, + pub(crate) parameters: Vec>, + pub(crate) platform: Option>, +} + +impl FunctionInfoBuilder { + pub(crate) fn update( + &mut self, + full_name: Option, + raw_name: Option, + return_type: Option, + address: Option, + parameters: Vec>, + ) { + if full_name.is_some() { + self.full_name = full_name; + } + + if raw_name.is_some() { + self.raw_name = raw_name; + } + + if return_type.is_some() { + self.return_type = return_type; + } + + if address.is_some() { + self.address = address; + } + + for (i, new_parameter) in parameters.into_iter().enumerate() { + match self.parameters.get(i) { + Some(None) => self.parameters[i] = new_parameter, + 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), + } + } + } +} + +////////////////////// +// DebugInfoBuilder + +// TODO : Don't make this pub...fix the value thing +pub(crate) struct DebugType { + name: String, + t: Ref, + commit: bool, +} + +pub(crate) struct DebugInfoBuilderContext> { + dwarf: Dwarf, + 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 { + let mut units = vec![]; + let mut iter = dwarf.units(); + while let Ok(Some(header)) = iter.next() { + if let Ok(unit) = dwarf.unit(header) { + units.push(unit); + } else { + error!("Unable to read DWARF information. File may be malformed or corrupted. Not applying debug info."); + return None; + } + } + + Some(Self { + dwarf, + 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 default_address_size(&self) -> usize { + self.default_address_size + } + + pub(crate) fn set_name(&mut self, die_uid: TypeUID, name: String) { + assert!(self.names.insert(die_uid, name).is_none()); + } + + pub(crate) fn get_name( + &self, + unit: &Unit, + entry: &DebuggingInformationEntry, + ) -> Option { + match resolve_specification(unit, entry, self) { + DieReference::UnitAndOffset((entry_unit, entry_offset)) => self + .names + .get(&get_uid( + entry_unit, + &entry_unit.entry(entry_offset).unwrap(), + )) + .cloned(), + DieReference::Err => None, + } + } +} + +// DWARF info is stored and displayed in a tree, but is really a graph +// The purpose of this builder is to help resolve those graph edges by mapping partial function +// 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, + types: HashMap, + data_variables: HashMap, TypeUID)>, +} + +impl DebugInfoBuilder { + pub(crate) fn new() -> Self { + Self { + functions: vec![], + types: HashMap::new(), + data_variables: HashMap::new(), + } + } + + #[allow(clippy::too_many_arguments)] + pub(crate) fn insert_function( + &mut self, + full_name: Option, + raw_name: Option, + return_type: Option, + address: Option, + parameters: Vec>, + ) { + // 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, + }); + } + } + + pub(crate) fn functions(&self) -> &[FunctionInfoBuilder] { + &self.functions + } + + pub(crate) fn types(&self) -> Values<'_, TypeUID, DebugType> { + self.types.values() + } + + 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, + commit: _, + }) = self.types.insert( + type_uid, + DebugType { + name: name.clone(), + t: t.clone(), + commit, + }, + ) { + if existing_type != t && commit { + error!("DWARF info contains duplicate type definition. Overwriting type `{}` (named `{:?}`) with `{}` (named `{:?}`)", + existing_type, + existing_name, + t, + name + ); + } + } + } + + pub(crate) fn remove_type(&mut self, type_uid: TypeUID) { + 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 contains_type(&self, type_uid: TypeUID) -> bool { + self.types.get(&type_uid).is_some() + } + + pub(crate) fn add_data_variable( + &mut self, + address: u64, + name: Option, + type_uid: TypeUID, + ) { + 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; + + 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 `{}`", + address, + self.get_type(existing_type_uid).unwrap().1, + self.get_type(type_uid).unwrap().1 + ); + } + } + } + + fn commit_types(&self, debug_info: &mut DebugInfo) { + for debug_type in self.types() { + if debug_type.commit { + debug_info.add_type(debug_type.name.clone(), debug_type.t.as_ref(), &[]); + // TODO : Components + } + } + } + + // TODO : Consume data? + fn commit_data_variables(&self, debug_info: &mut DebugInfo) { + for (&address, (name, type_uid)) in &self.data_variables { + assert!(debug_info.add_data_variable( + address, + &self.get_type(*type_uid).unwrap().1, + name.clone(), + &[] // TODO : Components + )); + } + } + + 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), + _ => Conf::new(binaryninja::types::Type::void(), 0), + }; + + 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, + name.clone(), + None, + )), + _ => None, + }) + .collect(); + + // TODO : Handle + let variable_parameters = false; + + binaryninja::types::Type::function(&return_type, ¶meters, variable_parameters) + } + + fn commit_functions(&self, debug_info: &mut DebugInfo) { + for function in self.functions() { + // let calling_convention: Option>> = None; + + debug_info.add_function(DebugFunctionInfo::new( + function.full_name.clone(), + function.full_name.clone(), // TODO : This should eventually be changed, but the "full_name" should probably be the unsimplified version, and the "short_name" should be the simplified version...currently the symbols view shows the full version, so changing it here too makes it look bad in the UI + function.raw_name.clone(), + Some(self.get_function_type(function)), + function.address, + function.platform.clone(), + vec![], // TODO : Components + )); + } + } + + 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 + // This is no longer true, because DWARF doesn't provide platform information for functions, so we at least need to post-process thumb functions + + for func in &mut self.functions { + // If the function's raw name already exists in the binary... + if let Some(raw_name) = &func.raw_name { + if let Ok(symbol) = bv.symbol_by_raw_name(raw_name) { + // Link mangled names without addresses to existing symbols in the binary + if func.address.is_none() && func.raw_name.is_some() { + // DWARF doesn't contain GOT info, so remove any entries there...they will be wrong (relying on Binja's mechanisms for the GOT is good ) + if symbol.sym_type() != SymbolType::ImportAddress { + func.address = Some(symbol.address()); + } + } + + if let Some(full_name) = &func.full_name { + let func_full_name = full_name; + let symbol_full_name = symbol.full_name(); + + // If our name has fewer namespaces than the existing name, assume we lost the namespace info + 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()); + } + } + } + } + + 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()); + } + } + } + + self + } + + pub(crate) fn commit_info(&self, debug_info: &mut DebugInfo) { + self.commit_types(debug_info); + self.commit_data_variables(debug_info); + self.commit_functions(debug_info); + } +} diff --git a/examples/dwarf/dwarf_import/src/functions.rs b/examples/dwarf/dwarf_import/src/functions.rs new file mode 100644 index 0000000..1d8879a --- /dev/null +++ b/examples/dwarf/dwarf_import/src/functions.rs @@ -0,0 +1,78 @@ +// Copyright 2021-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 crate::dwarfdebuginfo::{DebugInfoBuilder, DebugInfoBuilderContext, TypeUID}; +use crate::helpers::*; +use crate::types::get_type; + +use gimli::{constants, DebuggingInformationEntry, Reader, Unit}; + +fn get_parameters>( + unit: &Unit, + entry: &DebuggingInformationEntry, + debug_info_builder_context: &DebugInfoBuilderContext, + debug_info_builder: &mut DebugInfoBuilder, +) -> Vec> { + 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(); + + 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))) + } + } else { + result.push(None) + } + } + 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); + + debug_info_builder.insert_function(full_name, raw_name, return_type, address, parameters); +} diff --git a/examples/dwarf/dwarf_import/src/helpers.rs b/examples/dwarf/dwarf_import/src/helpers.rs new file mode 100644 index 0000000..ed40d44 --- /dev/null +++ b/examples/dwarf/dwarf_import/src/helpers.rs @@ -0,0 +1,293 @@ +// Copyright 2021-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 crate::DebugInfoBuilderContext; + +use gimli::{ + constants, Attribute, AttributeValue, + AttributeValue::{DebugInfoRef, UnitRef}, + DebuggingInformationEntry, Operation, Reader, Unit, UnitOffset, UnitSectionOffset, +}; + +use log::warn; + +pub(crate) fn get_uid>( + unit: &Unit, + entry: &DebuggingInformationEntry, +) -> usize { + match entry.offset().to_unit_section_offset(unit) { + UnitSectionOffset::DebugInfoOffset(o) => o.0, + UnitSectionOffset::DebugTypesOffset(o) => o.0, + } +} + +//////////////////////////////////// +// DIE attr convenience functions + +pub(crate) enum DieReference<'a, R: Reader> { + UnitAndOffset((&'a Unit, UnitOffset)), + Err, +} + +pub(crate) fn get_attr_die<'a, R: Reader>( + 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(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))); + } + } + warn!("Failed to fetch DIE. Debug information may be incomplete."); + None + } + // Ok(Some(DebugInfoRefSup(offset))) TODO - dwarf 5 stuff + _ => None, + } +} + +pub(crate) fn resolve_specification<'a, R: Reader>( + unit: &'a Unit, + entry: &DebuggingInformationEntry, + debug_info_builder_context: &'a DebugInfoBuilderContext, +) -> DieReference<'a, R> { + if let Some(die_reference) = get_attr_die( + unit, + entry, + debug_info_builder_context, + constants::DW_AT_specification, + ) { + match die_reference { + DieReference::UnitAndOffset((entry_unit, entry_offset)) => { + if let Ok(entry) = entry_unit.entry(entry_offset) { + resolve_specification(entry_unit, &entry, debug_info_builder_context) + } else { + warn!("Failed to fetch DIE. Debug information may be incomplete."); + DieReference::Err + } + } + DieReference::Err => DieReference::Err, + } + } else if let Some(die_reference) = get_attr_die( + 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() { + 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) + } else { + warn!("Failed to fetch DIE. Debug information may be incomplete."); + DieReference::Err + } + } + DieReference::Err => DieReference::Err, + } + } else { + DieReference::UnitAndOffset((unit, entry.offset())) + } +} + +// Get name from DIE, or referenced dependencies +pub(crate) fn get_name>( + 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)) => { + 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) = attr_string.to_string() { + return Some(attr_string.to_string()); + } + } + } + + // if let Some(raw_name) = get_raw_name(unit, entry, debug_info_builder_context) { + // if let Some(arch) = debug_info_builder_context.default_architecture() { + // if let Ok((_, names)) = demangle_gnu3(&arch, raw_name, true) { + // return Some(names.join("::")); + // } + // } + // } + None + } + DieReference::Err => None, + } +} + +// Get raw name from DIE, or referenced dependencies +pub(crate) fn get_raw_name>( + 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) = 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>( + entry: &DebuggingInformationEntry, +) -> Option { + if let Ok(Some(attr)) = entry.attr(constants::DW_AT_byte_size) { + get_attr_as_usize(attr) + } else if let Ok(Some(attr)) = entry.attr(constants::DW_AT_bit_size) { + get_attr_as_usize(attr).map(|attr_value| attr_value / 8) + } else { + None + } +} + +// Get the size of an object as a u64 +pub(crate) fn get_size_as_u64>( + entry: &DebuggingInformationEntry, +) -> Option { + if let Ok(Some(attr)) = entry.attr(constants::DW_AT_byte_size) { + get_attr_as_u64(&attr) + } else if let Ok(Some(attr)) = entry.attr(constants::DW_AT_bit_size) { + get_attr_as_u64(&attr).map(|attr_value| attr_value / 8) + } else { + None + } +} + +// Get the size of a subrange as a u64 +pub(crate) fn get_subrange_size>( + entry: &DebuggingInformationEntry, +) -> u64 { + if let Ok(Some(attr)) = entry.attr(constants::DW_AT_upper_bound) { + get_attr_as_u64(&attr).map_or(0, |v| v + 1) + } else if let Ok(Some(attr)) = entry.attr(constants::DW_AT_count) { + get_attr_as_u64(&attr).unwrap_or(0) + } else if let Ok(Some(attr)) = entry.attr(constants::DW_AT_lower_bound) { + get_attr_as_u64(&attr).map_or(0, |v| v + 1) + } else { + 0 + } +} + +// Get the start address of a function +pub(crate) fn get_start_address>( + 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) + { + 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) + { + 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(mut ranges) = debug_info_builder_context + .dwarf() + .ranges(unit, ranges_offset) + { + if let Ok(Some(range)) = ranges.next() { + return Some(range.begin); + } + } + } + return None; + } else { + None + } +} + +// 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() { + Some(value) + } else { + attr.sdata_value().map(|value| value as u64) + } +} + +// Get an attribute value as a usize if it can be coerced +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() { + Some(value.into()) + } else if let Some(value) = attr.udata_value() { + Some(value as usize) + } else { + attr.sdata_value().map(|value| value as usize) + } +} + +// 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>( + unit: &Unit, + attr: Attribute, +) -> Option { + if let AttributeValue::Exprloc(mut expression) = attr.value() { + match Operation::parse(&mut expression.0, unit.encoding()) { + Ok(Operation::PlusConstant { value }) => Some(value), + Ok(Operation::UnsignedConstant { value }) => Some(value), + Ok(Operation::Address { address: 0 }) => None, + Ok(Operation::Address { address }) => Some(address), + _ => None, + } + } else { + None + } +} diff --git a/examples/dwarf/dwarf_import/src/lib.rs b/examples/dwarf/dwarf_import/src/lib.rs new file mode 100644 index 0000000..0680942 --- /dev/null +++ b/examples/dwarf/dwarf_import/src/lib.rs @@ -0,0 +1,295 @@ +// Copyright 2021-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. + +mod die_handlers; +mod dwarfdebuginfo; +mod functions; +mod helpers; +mod types; + +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 binaryninja::{ + binaryview::{BinaryView, BinaryViewExt}, + debuginfo::{CustomDebugInfoParser, DebugInfo, DebugInfoParser}, + logger, + 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 log::{error, warn, LevelFilter}; + +fn recover_names>( + debug_info_builder_context: &mut DebugInfoBuilderContext, + progress: &dyn Fn(usize, usize) -> Result<(), ()>, +) -> bool { + let mut iter = debug_info_builder_context.dwarf().units(); + while let Ok(Some(header)) = iter.next() { + let unit = debug_info_builder_context.dwarf().unit(header).unwrap(); + let mut namespace_qualifiers: Vec<(isize, String)> = vec![]; + let mut entries = unit.entries(); + let mut depth = 0; + + // The first entry in the unit is the header for the unit + if let Ok(Some((delta_depth, _))) = entries.next_dfs() { + depth += delta_depth; + debug_info_builder_context.total_die_count += 1; + } + + while let Ok(Some((delta_depth, entry))) = entries.next_dfs() { + debug_info_builder_context.total_die_count += 1; + + if (*progress)(0, debug_info_builder_context.total_die_count).is_err() { + return false; // Parsing canceled + }; + + depth += delta_depth; + if depth < 0 { + error!("DWARF information is seriously malformed. Aborting parsing."); + return false; + } + + // TODO : Better module/component support + namespace_qualifiers.retain(|&(entry_depth, _)| entry_depth < depth); + + match entry.tag() { + constants::DW_TAG_namespace => { + fn resolve_namespace_name>( + unit: &Unit, + entry: &DebuggingInformationEntry, + debug_info_builder_context: &DebugInfoBuilderContext, + namespace_qualifiers: &mut Vec<(isize, String)>, + depth: isize, + ) { + if let Some(namespace_qualifier) = + get_name(unit, entry, debug_info_builder_context) + { + namespace_qualifiers.push((depth, namespace_qualifier)); + } else if let Some(die_reference) = get_attr_die( + unit, + entry, + debug_info_builder_context, + constants::DW_AT_extension, + ) { + match die_reference { + DieReference::UnitAndOffset((entry_unit, entry_offset)) => { + resolve_namespace_name( + entry_unit, + &entry_unit.entry(entry_offset).unwrap(), + debug_info_builder_context, + namespace_qualifiers, + depth, + ) + } + DieReference::Err => { + warn!( + "Failed to fetch DIE. Debug information may be incomplete." + ); + } + } + } else { + namespace_qualifiers + .push((depth, "anonymous_namespace".to_string())); + } + } + + resolve_namespace_name( + &unit, + entry, + debug_info_builder_context, + &mut namespace_qualifiers, + depth, + ); + } + 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) { + 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_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(), + ); + } + 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) { + 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(), + ); + } + } + _ => { + if let Some(name) = get_name(&unit, entry, debug_info_builder_context) { + debug_info_builder_context.set_name(get_uid(&unit, entry), name); + } + } + } + } + } + + true +} + +fn parse_unit>( + unit: &Unit, + debug_info_builder_context: &DebugInfoBuilderContext, + debug_info_builder: &mut DebugInfoBuilder, + progress: &dyn Fn(usize, usize) -> Result<(), ()>, + current_die_number: &mut usize, +) { + let mut entries = unit.entries(); + + // 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() { + *current_die_number += 1; + if (*progress)( + *current_die_number, + debug_info_builder_context.total_die_count, + ) + .is_err() + { + return; // Parsing canceled + } + + match entry.tag() { + constants::DW_TAG_subprogram => { + parse_function_entry(unit, entry, debug_info_builder_context, debug_info_builder) + } + constants::DW_TAG_variable => { + parse_data_variable(unit, entry, debug_info_builder_context, debug_info_builder) + } + _ => (), + } + } +} + +fn parse_dwarf( + view: &BinaryView, + progress: Box Result<(), ()>>, +) -> DebugInfoBuilder { + // 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 + } else { + raw_view + }; + + // gimli setup + let endian = get_endian(view); + let mut section_reader = + |section_id: SectionId| -> _ { create_section_reader(section_id, view, endian, dwo_file) }; + let mut dwarf = Dwarf::load(&mut section_reader).unwrap(); + if dwo_file { + dwarf.file_type = DwarfFileType::Dwo; + } + + // 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_context.total_die_count == 0 + { + return debug_info_builder; + } + + // Parse all the compilation units + let mut current_die_number = 0; + for unit in debug_info_builder_context.units() { + parse_unit( + unit, + &debug_info_builder_context, + &mut debug_info_builder, + &progress, + &mut current_die_number, + ); + } + } + debug_info_builder +} + +struct DWARFParser; + +impl CustomDebugInfoParser for DWARFParser { + fn is_valid(&self, view: &BinaryView) -> bool { + dwarfreader::is_valid(view) + } + + fn parse_info( + &self, + debug_info: &mut DebugInfo, + bv: &BinaryView, + debug_file: &BinaryView, + progress: Box Result<(), ()>>, + ) -> bool { + parse_dwarf(debug_file, progress) + .post_process(bv, debug_info) + .commit_info(debug_info); + true + } +} + +#[no_mangle] +pub extern "C" fn CorePluginInit() -> bool { + logger::init(LevelFilter::Debug).unwrap(); + + DebugInfoParser::register("DWARF", DWARFParser {}); + true +} diff --git a/examples/dwarf/dwarf_import/src/types.rs b/examples/dwarf/dwarf_import/src/types.rs new file mode 100644 index 0000000..38bd36e --- /dev/null +++ b/examples/dwarf/dwarf_import/src/types.rs @@ -0,0 +1,393 @@ +// Copyright 2021-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 crate::die_handlers::*; +use crate::dwarfdebuginfo::{DebugInfoBuilder, DebugInfoBuilderContext, TypeUID}; +use crate::helpers::*; + +use binaryninja::{ + rc::*, + types::{ + MemberAccess, MemberScope, ReferenceType, StructureBuilder, StructureType, Type, TypeClass, + }, +}; + +use gimli::{constants, DebuggingInformationEntry, Reader, Unit}; + +use log::warn; + +pub(crate) fn parse_data_variable>( + unit: &Unit, + entry: &DebuggingInformationEntry, + debug_info_builder_context: &DebugInfoBuilderContext, + debug_info_builder: &mut DebugInfoBuilder, +) { + 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 address = if let Ok(Some(attr)) = entry.attr(constants::DW_AT_location) { + get_expr_value(unit, attr) + } else { + None + }; + + if let (Some(address), Some(type_uid)) = (address, type_uid) { + debug_info_builder.add_data_variable(address, full_name, type_uid); + } +} + +fn do_structure_parse>( + structure_type: StructureType, + unit: &Unit, + entry: &DebuggingInformationEntry, + debug_info_builder_context: &DebugInfoBuilderContext, + debug_info_builder: &mut DebugInfoBuilder, +) -> Option { + // All struct, union, and class types will have: + // *DW_AT_name + // *DW_AT_byte_size or *DW_AT_bit_size + // *DW_AT_declaration + // *DW_AT_signature + // *DW_AT_specification + // ?DW_AT_abstract_origin + // ?DW_AT_accessibility + // ?DW_AT_allocated + // ?DW_AT_associated + // ?DW_AT_data_location + // ?DW_AT_description + // ?DW_AT_start_scope + // ?DW_AT_visibility + // * = Optional + + // Structure/Class/Union _Children_ consist of: + // Data members: + // DW_AT_type + // *DW_AT_name + // *DW_AT_accessibility (default private for classes, public for everything else) + // *DW_AT_mutable + // *DW_AT_data_member_location xor *DW_AT_data_bit_offset (otherwise assume zero) <- there are some deprecations for DWARF 4 + // *DW_AT_byte_size xor DW_AT_bit_size, iff the storage size is different than it usually would be for the given member type + // Function members: + // *DW_AT_accessibility (default private for classes, public for everything else) + // *DW_AT_virtuality (assume false) + // If true: DW_AT_vtable_elem_location + // *DW_AT_explicit (assume false) + // *DW_AT_object_pointer (assume false; for non-static member function; references the formal parameter that has "DW_AT_artificial = true" and represents "self" or "this" (language specified)) + // *DW_AT_specification + // * = Optional + + if let Ok(Some(_)) = entry.attr(constants::DW_AT_declaration) { + return None; + } + + let full_name = if get_name(unit, entry, debug_info_builder_context).is_some() { + debug_info_builder_context.get_name(unit, entry) + } else { + None + }; + + // Create structure with proper size + let size = get_size_as_u64(entry).unwrap_or(0); + let structure_builder: StructureBuilder = StructureBuilder::new(); + structure_builder + .set_packed(true) + .set_width(size) + .set_structure_type(structure_type); + + // This reference type will be used by any children to grab while we're still building this type + // 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(), + Type::named_type_from_type( + full_name.clone(), + &Type::structure(&structure_builder.finalize()), + ), + false, + ); + } else { + // 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)); + debug_info_builder.add_type( + get_uid(unit, entry), + full_name.clone(), + Type::named_type_from_type(full_name, &Type::structure(&structure_builder.finalize())), + false, + ); + } + + // Get all the children and populate + let mut tree = unit.entries_tree(Some(entry.offset())).unwrap(); + let mut children = tree.root().unwrap().children(); + while let Ok(Some(child)) = children.next() { + if child.entry().tag() == constants::DW_TAG_member { + if let Some(child_type_id) = get_type( + 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(child_name) = debug_info_builder_context + .get_name(unit, child.entry()) + .map_or( + if child_type.type_class() == TypeClass::StructureTypeClass { + Some("".to_string()) + } else { + None + }, + Some, + ) + { + // TODO : support DW_AT_data_bit_offset for offset as well + if let Ok(Some(raw_struct_offset)) = + child.entry().attr(constants::DW_AT_data_member_location) + { + // TODO : Let this fail; don't unwrap_or_default get_expr_value + let struct_offset = + get_attr_as_u64(&raw_struct_offset).unwrap_or_else(|| { + get_expr_value(unit, raw_struct_offset).unwrap_or_default() + }); + + structure_builder.insert( + child_type.as_ref(), + child_name, + struct_offset, + false, + MemberAccess::NoAccess, // TODO : Resolve actual scopes, if possible + MemberScope::NoScope, + ); + } else { + structure_builder.append( + child_type.as_ref(), + child_name, + MemberAccess::NoAccess, + MemberScope::NoScope, + ); + } + } + } + } + } + } + + 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, + finalized_structure, + true, + ); + } else { + debug_info_builder.add_type( + get_uid(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)) +} + +// 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>( + 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)); + } + + // Don't parse types that are just declarations and not definitions + if let Ok(Some(_)) = entry.attr(constants::DW_AT_declaration) { + return None; + } + + let entry_type = if let Some(die_reference) = get_attr_die( + unit, + entry, + debug_info_builder_context, + constants::DW_AT_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::Err => { + warn!("Failed to fetch DIE. 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)) + if entry_unit.header.offset() != unit.header.offset() + && entry_offset != entry.offset() => + { + get_type( + entry_unit, + &entry_unit.entry(entry_offset).unwrap(), + debug_info_builder_context, + debug_info_builder, + ) + } + DieReference::UnitAndOffset(_) => None, + DieReference::Err => { + warn!("Failed to fetch DIE. Debug information may be incomplete."); + None + } + } + }; + + // 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)); + } + + // 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), + false, + ), + + constants::DW_TAG_structure_type => { + return do_structure_parse( + StructureType::StructStructureType, + unit, + entry, + debug_info_builder_context, + debug_info_builder, + ) + } + constants::DW_TAG_class_type => { + return do_structure_parse( + StructureType::ClassStructureType, + unit, + entry, + debug_info_builder_context, + debug_info_builder, + ) + } + constants::DW_TAG_union_type => { + return do_structure_parse( + StructureType::UnionStructureType, + unit, + entry, + debug_info_builder_context, + debug_info_builder, + ) + } + + // Enum + constants::DW_TAG_enumeration_type => { + (handle_enum(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) + } else { + (None, false) + } + } + constants::DW_TAG_pointer_type => ( + handle_pointer( + entry, + debug_info_builder_context, + debug_info_builder, + entry_type, + ReferenceType::PointerReferenceType, + ), + false, + ), + constants::DW_TAG_reference_type => ( + handle_pointer( + entry, + debug_info_builder_context, + debug_info_builder, + entry_type, + ReferenceType::ReferenceReferenceType, + ), + false, + ), + constants::DW_TAG_rvalue_reference_type => ( + handle_pointer( + entry, + debug_info_builder_context, + debug_info_builder, + entry_type, + ReferenceType::RValueReferenceType, + ), + false, + ), + constants::DW_TAG_array_type => ( + handle_array(unit, entry, debug_info_builder, entry_type), + false, + ), + + // Strange Types + constants::DW_TAG_unspecified_type => (Some(Type::void()), false), + constants::DW_TAG_subroutine_type => ( + handle_function( + unit, + entry, + debug_info_builder_context, + debug_info_builder, + entry_type, + ), + false, + ), + + // Weird types + constants::DW_TAG_const_type => (handle_const(debug_info_builder, entry_type), false), + constants::DW_TAG_volatile_type => (handle_volatile(debug_info_builder, entry_type), true), // TODO : Maybe false here + + // Pass-through everything else! + _ => return entry_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) + } else { + None + } + .unwrap_or_else(|| { + commit = false; + format!("{}", type_def) + }); + + debug_info_builder.add_type(get_uid(unit, entry), name, type_def, commit); + Some(get_uid(unit, entry)) + } else { + None + } +} diff --git a/examples/dwarf/dwarfdump/Cargo.toml b/examples/dwarf/dwarfdump/Cargo.toml new file mode 100644 index 0000000..1d3411a --- /dev/null +++ b/examples/dwarf/dwarfdump/Cargo.toml @@ -0,0 +1,13 @@ +[package] +name = "dwarfdump" +version = "0.1.0" +authors = ["Kyle Martin "] +edition = "2021" + +[lib] +crate-type = ["cdylib"] + +[dependencies] +dwarfreader = { path = "../shared/" } +binaryninja = {path="../../../"} +gimli = "0.27" diff --git a/examples/dwarf/dwarfdump/readme.md b/examples/dwarf/dwarfdump/readme.md new file mode 100644 index 0000000..ae3a193 --- /dev/null +++ b/examples/dwarf/dwarfdump/readme.md @@ -0,0 +1,17 @@ +# DWARF Dump Example + +This is actually a fully-developed plugin, rather than a measly example. + +Two features this does not support are: files in big endian, and .dwo files + +## How to use + +Simply `cargo build --release` in this directory, and copy the `.so` from the target directory to your plugin directory + +### Attribution + +This example makes use of: + - [gimli] ([gimli license] - MIT) + +[gimli license]: https://github.com/gimli-rs/gimli/blob/master/LICENSE-MIT +[gimli]: https://github.com/gimli-rs/gimli diff --git a/examples/dwarf/dwarfdump/src/lib.rs b/examples/dwarf/dwarfdump/src/lib.rs new file mode 100644 index 0000000..d1a415d --- /dev/null +++ b/examples/dwarf/dwarfdump/src/lib.rs @@ -0,0 +1,308 @@ +// Copyright 2021-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 binaryninja::{ + binaryview::{BinaryView, BinaryViewExt}, + command::{register, Command}, + disassembly::{DisassemblyTextLine, InstructionTextToken, InstructionTextTokenContents}, + flowgraph::{BranchType, EdgeStyle, FlowGraph, FlowGraphNode, FlowGraphOption}, + string::BnString, +}; +use dwarfreader::is_valid; + +use gimli::{ + AttributeValue::{Encoding, Flag, UnitRef}, + // BigEndian, + DebuggingInformationEntry, + Dwarf, + EntriesTreeNode, + Reader, + ReaderOffset, + SectionId, + Unit, + UnitSectionOffset, +}; + +static PADDING: [&'static str; 23] = [ + "", + " ", + " ", + " ", + " ", + " ", + " ", + " ", + " ", + " ", + " ", + " ", + " ", + " ", + " ", + " ", + " ", + " ", + " ", + " ", + " ", + " ", + " ", +]; + +// TODO : This is very much not comprehensive: see https://github.com/gimli-rs/gimli/blob/master/examples/dwarfdump.rs +fn get_info_string( + view: &BinaryView, + dwarf: &Dwarf, + unit: &Unit, + die_node: &DebuggingInformationEntry, +) -> Vec { + let mut disassembly_lines: Vec = Vec::with_capacity(10); // This is an estimate so "most" things won't need to resize + + let label_value = match die_node.offset().to_unit_section_offset(unit) { + UnitSectionOffset::DebugInfoOffset(o) => o.0, + UnitSectionOffset::DebugTypesOffset(o) => o.0, + } + .into_u64(); + let label_string = format!("#0x{:08x}", label_value); + disassembly_lines.push(DisassemblyTextLine::from(vec![ + InstructionTextToken::new( + BnString::new(label_string), + InstructionTextTokenContents::GotoLabel(label_value), + ), + InstructionTextToken::new(BnString::new(":"), InstructionTextTokenContents::Text), + ])); + + disassembly_lines.push(DisassemblyTextLine::from(vec![InstructionTextToken::new( + BnString::new(die_node.tag().static_string().unwrap()), + InstructionTextTokenContents::TypeName, // TODO : KeywordToken? + )])); + + let mut attrs = die_node.attrs(); + 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, + )); + + let len; + if let Some(n) = attr.name().static_string() { + len = n.len(); + attr_line.push(InstructionTextToken::new( + BnString::new(n), + InstructionTextTokenContents::FieldName, + )); + } else { + // This is rather unlikely, I think + len = 1; + attr_line.push(InstructionTextToken::new( + BnString::new("?"), + InstructionTextTokenContents::FieldName, + )); + } + + // 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]), + 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), + 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()), + InstructionTextTokenContents::String({ + let (_, id, offset) = + dwarf.lookup_offset_id(attr_reader.offset_id()).unwrap(); + offset.into_u64() + view.section_by_name(id.name()).unwrap().start() + }), + )); + } 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()), + InstructionTextTokenContents::TypeName, + )); + } else if let UnitRef(offset) = attr.value() { + let addr = match offset.to_unit_section_offset(unit) { + UnitSectionOffset::DebugInfoOffset(o) => o.0, + UnitSectionOffset::DebugTypesOffset(o) => o.0, + } + .into_u64(); + let addr_string = format!("#0x{:08x}", addr); + attr_line.push(InstructionTextToken::new( + BnString::new(addr_string), + InstructionTextTokenContents::GotoLabel(addr), + )); + } else if let Flag(true) = attr.value() { + attr_line.push(InstructionTextToken::new( + BnString::new("true"), + InstructionTextTokenContents::Integer(1), + )); + } else if let Flag(false) = attr.value() { + attr_line.push(InstructionTextToken::new( + BnString::new("false"), + InstructionTextTokenContents::Integer(1), + )); + + // Fall-back cases + } else if let Some(value) = attr.u8_value() { + let value_string = format!("{}", value); + attr_line.push(InstructionTextToken::new( + BnString::new(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), + 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()), + )); + } else if let Some(value) = attr.sdata_value() { + let value_string = format!("{}", value); + attr_line.push(InstructionTextToken::new( + BnString::new(value_string), + InstructionTextTokenContents::Integer(value as u64), + )); + } else { + let attr_string = format!("{:?}", attr.value()); + attr_line.push(InstructionTextToken::new( + BnString::new(attr_string), + InstructionTextTokenContents::Text, + )); + } + disassembly_lines.push(DisassemblyTextLine::from(attr_line)); + } + + disassembly_lines +} + +fn process_tree( + view: &BinaryView, + dwarf: &Dwarf, + unit: &Unit, + graph: &FlowGraph, + graph_parent: &FlowGraphNode, + die_node: EntriesTreeNode, +) { + // Namespaces only - really interesting to look at! + // if (die_node.entry().tag() == constants::DW_TAG_namespace) + // || (die_node.entry().tag() == constants::DW_TAG_class_type) + // || (die_node.entry().tag() == constants::DW_TAG_compile_unit) + // || (die_node.entry().tag() == constants::DW_TAG_subprogram) + // { + let new_node = FlowGraphNode::new(graph); + + let attr_string = get_info_string(view, dwarf, unit, die_node.entry()); + new_node.set_disassembly_lines(&attr_string); + + graph.append(&new_node); + graph_parent.add_outgoing_edge( + BranchType::UnconditionalBranch, + &new_node, + &EdgeStyle::default(), + ); + + let mut children = die_node.children(); + while let Some(child) = children.next().unwrap() { + process_tree(view, dwarf, unit, graph, &new_node, child); + } + // } +} + +fn dump_dwarf(bv: &BinaryView) { + let view = if bv.section_by_name(".debug_info").is_ok() { + bv.to_owned() + } else { + bv.parent_view().unwrap() + }; + + let graph = FlowGraph::new(); + graph.set_option(FlowGraphOption::FlowGraphUsesBlockHighlights, true); + graph.set_option(FlowGraphOption::FlowGraphUsesInstructionHighlights, true); + + let graph_root = FlowGraphNode::new(&graph); + graph_root.set_lines(vec!["Graph Root"]); + graph.append(&graph_root); + + let endian = dwarfreader::get_endian(bv); + let section_reader = |section_id: SectionId| -> _ { + dwarfreader::create_section_reader(section_id, bv, endian, false) + }; + let dwarf = Dwarf::load(§ion_reader).unwrap(); + + let mut iter = dwarf.units(); + while let Some(header) = iter.next().unwrap() { + let unit = dwarf.unit(header).unwrap(); + let mut entries = unit.entries(); + let mut depth = 0; + + if let Some((delta_depth, entry)) = entries.next_dfs().unwrap() { + depth += delta_depth; + assert!(depth >= 0); + + let mut tree = unit.entries_tree(Some(entry.offset())).unwrap(); + let root = tree.root().unwrap(); + + process_tree(&view, &dwarf, &unit, &graph, &graph_root, root); + } + } + + view.show_graph_report("DWARF", graph); +} + +struct DWARFDump; + +impl Command for DWARFDump { + fn action(&self, view: &BinaryView) { + dump_dwarf(view); + } + + fn valid(&self, view: &BinaryView) -> bool { + is_valid(view) + } +} + +#[no_mangle] +pub extern "C" fn UIPluginInit() -> bool { + register( + "DWARF Dump", + "Show embedded DWARF info as a tree structure for you to navigate", + DWARFDump {}, + ); + true +} diff --git a/examples/dwarf/shared/Cargo.toml b/examples/dwarf/shared/Cargo.toml new file mode 100644 index 0000000..9cbd5a2 --- /dev/null +++ b/examples/dwarf/shared/Cargo.toml @@ -0,0 +1,9 @@ +[package] +name = "dwarfreader" +version = "0.1.0" +authors = ["Kyle Martin "] +edition = "2021" + +[dependencies] +binaryninja = {path="../../../"} +gimli = "0.27" diff --git a/examples/dwarf/shared/src/lib.rs b/examples/dwarf/shared/src/lib.rs new file mode 100644 index 0000000..718dcb8 --- /dev/null +++ b/examples/dwarf/shared/src/lib.rs @@ -0,0 +1,159 @@ +// Copyright 2021-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 gimli::{EndianRcSlice, Endianity, Error, RunTimeEndian, SectionId}; + +use binaryninja::binaryninjacore_sys::*; + +use binaryninja::{ + binaryview::{BinaryView, BinaryViewBase, BinaryViewExt}, + databuffer::DataBuffer, + Endianness, +}; + +use std::{ffi::CString, rc::Rc}; + +////////////////////// +// Dwarf Validation + +pub fn is_non_dwo_dwarf(view: &BinaryView) -> bool { + view.section_by_name(".debug_info").is_ok() || view.section_by_name("__debug_info").is_ok() +} + +pub fn is_dwo_dwarf(view: &BinaryView) -> bool { + view.section_by_name(".debug_info.dwo").is_ok() +} + +pub fn is_raw_non_dwo_dwarf(view: &BinaryView) -> bool { + if let Ok(raw_view) = view.raw_view() { + raw_view.section_by_name(".debug_info").is_ok() + || view.section_by_name("__debug_info").is_ok() + } else { + false + } +} + +pub fn is_raw_dwo_dwarf(view: &BinaryView) -> bool { + if let Ok(raw_view) = view.raw_view() { + raw_view.section_by_name(".debug_info.dwo").is_ok() + } else { + false + } +} + +pub fn is_valid(view: &BinaryView) -> bool { + is_non_dwo_dwarf(view) + || is_raw_non_dwo_dwarf(view) + || is_dwo_dwarf(view) + || is_raw_dwo_dwarf(view) +} + +pub fn get_endian(view: &BinaryView) -> RunTimeEndian { + match view.default_endianness() { + Endianness::LittleEndian => RunTimeEndian::Little, + Endianness::BigEndian => RunTimeEndian::Big, + } +} + +pub fn create_section_reader<'a, Endian: 'a + Endianity>( + section_id: SectionId, + view: &'a BinaryView, + endian: Endian, + dwo_file: bool, +) -> Result, Error> { + let section_name = if dwo_file && section_id.dwo_name().is_some() { + section_id.dwo_name().unwrap() + } else { + section_id.name() + }; + + if let Ok(section) = view.section_by_name(section_name) { + // TODO : This is kinda broke....should add rust wrappers for some of this + if let Some(symbol) = view + .symbols() + .iter() + .find(|symbol| symbol.full_name().as_str() == "__elf_section_headers") + { + if let Some(data_var) = view + .data_variables() + .iter() + .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 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 (endian.read_u64(¤t_section_header[8..16]) & 2048) != 0 { + // Get section, trim header, decompress, return + let offset = section.start() + 24; + let len = section.len() - 24; + + if let Ok(buffer) = view.read_buffer(offset, len) { + use std::ptr; + let transform_name = + CString::new("Zlib").unwrap().into_bytes_with_nul(); + let transform = + unsafe { BNGetTransformByName(transform_name.as_ptr() as *mut _) }; + + // Omega broke + let raw_buf: *mut BNDataBuffer = + unsafe { BNCreateDataBuffer(ptr::null_mut(), 0) }; + if unsafe { + BNDecode( + transform, + std::mem::transmute(buffer), + raw_buf, + ptr::null_mut(), + 0, + ) + } { + let output_buffer: DataBuffer = + unsafe { std::mem::transmute(raw_buf) }; + + return Ok(EndianRcSlice::new( + output_buffer.get_data().into(), + endian, + )); + } + } + } + } + } + } + let offset = section.start(); + let len = section.len(); + if len == 0 { + Ok(EndianRcSlice::new(Rc::from([]), endian)) + } else { + Ok(EndianRcSlice::new( + Rc::from(view.read_vec(offset, len).as_slice()), + endian, + )) + } + } else if let Ok(section) = view.section_by_name("__".to_string() + §ion_name[1..]) { + Ok(EndianRcSlice::new( + Rc::from(view.read_vec(section.start(), section.len()).as_slice()), + endian, + )) + } else { + Ok(EndianRcSlice::new(Rc::from([]), endian)) + } +} diff --git a/examples/flowgraph/Cargo.lock b/examples/flowgraph/Cargo.lock new file mode 100644 index 0000000..3dc7b2e --- /dev/null +++ b/examples/flowgraph/Cargo.lock @@ -0,0 +1,353 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "aho-corasick" +version = "0.7.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e37cfd5e7657ada45f742d6e99ca5788580b5c529dc78faf11ece6dc702656f" +dependencies = [ + "memchr", +] + +[[package]] +name = "ansi_term" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d52a9bb7ec0cf484c551830a7ce27bd20d67eac647e1befb56b0be4ee39a55d2" +dependencies = [ + "winapi", +] + +[[package]] +name = "atty" +version = "0.2.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8" +dependencies = [ + "hermit-abi", + "libc", + "winapi", +] + +[[package]] +name = "binaryninja" +version = "0.1.0" +dependencies = [ + "binaryninjacore-sys", + "libc", + "log", +] + +[[package]] +name = "binaryninjacore-sys" +version = "0.1.0" +dependencies = [ + "bindgen", +] + +[[package]] +name = "bindgen" +version = "0.58.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0f8523b410d7187a43085e7e064416ea32ded16bd0a4e6fc025e21616d01258f" +dependencies = [ + "bitflags", + "cexpr", + "clang-sys", + "clap", + "env_logger", + "lazy_static", + "lazycell", + "log", + "peeking_take_while", + "proc-macro2", + "quote", + "regex", + "rustc-hash", + "shlex", + "which", +] + +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + +[[package]] +name = "cexpr" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f4aedb84272dbe89af497cf81375129abda4fc0a9e7c5d317498c15cc30c0d27" +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.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "853eda514c284c2287f4bf20ae614f8781f40a81d32ecda6e91449304dfe077c" +dependencies = [ + "glob", + "libc", + "libloading", +] + +[[package]] +name = "clap" +version = "2.34.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a0610544180c38b88101fecf2dd634b174a62eef6946f84dfc6a7127512b381c" +dependencies = [ + "ansi_term", + "atty", + "bitflags", + "strsim", + "textwrap", + "unicode-width", + "vec_map", +] + +[[package]] +name = "env_logger" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a19187fea3ac7e84da7dacf48de0c45d63c6a76f9490dae389aead16c243fce3" +dependencies = [ + "atty", + "humantime", + "log", + "regex", + "termcolor", +] + +[[package]] +name = "flowgraph" +version = "0.1.0" +dependencies = [ + "binaryninja", +] + +[[package]] +name = "glob" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b919933a397b79c37e33b77bb2aa3dc8eb6e165ad809e58ff75bc7db2e34574" + +[[package]] +name = "hermit-abi" +version = "0.1.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33" +dependencies = [ + "libc", +] + +[[package]] +name = "humantime" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4" + +[[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.112" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b03d17f364a3a042d5e5d46b053bbbf82c92c9430c592dd4c064dc6ee997125" + +[[package]] +name = "libloading" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6f84d96438c15fcd6c3f244c8fce01d1e2b9c6b5623e9c711dc9286d8fc92d6a" +dependencies = [ + "cfg-if", + "winapi", +] + +[[package]] +name = "log" +version = "0.4.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "51b9bbe6c47d51fc3e1a9b945965946b4c44142ab8792c50835a980d362c2710" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "memchr" +version = "2.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "308cc39be01b73d0d18f82a0e7b2a3df85245f84af96fdddc5d202d27e47b86a" + +[[package]] +name = "nom" +version = "5.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ffb4262d26ed83a1c0a33a38fe2bb15797329c85770da05e6b828ddb782627af" +dependencies = [ + "memchr", + "version_check", +] + +[[package]] +name = "peeking_take_while" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "19b17cddbe7ec3f8bc800887bab5e717348c95ea2ca0b1bf0837fb964dc67099" + +[[package]] +name = "proc-macro2" +version = "1.0.36" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c7342d5883fbccae1cc37a2353b09c87c9b0f3afd73f5fb9bba687a1f733b029" +dependencies = [ + "unicode-xid", +] + +[[package]] +name = "quote" +version = "1.0.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "47aa80447ce4daf1717500037052af176af5d38cc3e571d9ec1c7353fc10c87d" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "regex" +version = "1.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d07a8629359eb56f1e2fb1652bb04212c072a87ba68546a04065d525673ac461" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] + +[[package]] +name = "regex-syntax" +version = "0.6.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f497285884f3fcff424ffc933e56d7cbca511def0c9831a7f9b5f6153e3cc89b" + +[[package]] +name = "rustc-hash" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" + +[[package]] +name = "shlex" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42a568c8f2cd051a4d283bd6eb0343ac214c1b0f1ac19f93e1175b2dee38c73d" + +[[package]] +name = "strsim" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ea5119cdb4c55b55d432abb513a0429384878c15dde60cc77b1c99de1a95a6a" + +[[package]] +name = "termcolor" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2dfed899f0eb03f32ee8c6a0aabdb8a7949659e3466561fc0adf54e26d88c5f4" +dependencies = [ + "winapi-util", +] + +[[package]] +name = "textwrap" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d326610f408c7a4eb6f51c37c330e496b08506c9457c9d34287ecc38809fb060" +dependencies = [ + "unicode-width", +] + +[[package]] +name = "unicode-width" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3ed742d4ea2bd1176e236172c8429aaf54486e7ac098db29ffe6529e0ce50973" + +[[package]] +name = "unicode-xid" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ccb82d61f80a663efe1f787a51b16b5a51e3314d6ac365b08639f52387b33f3" + +[[package]] +name = "vec_map" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1bddf1187be692e79c5ffeab891132dfb0f236ed36a43c7ed39f1165ee20191" + +[[package]] +name = "version_check" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" + +[[package]] +name = "which" +version = "3.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d011071ae14a2f6671d0b74080ae0cd8ebf3a6f8c9589a2cd45f23126fe29724" +dependencies = [ + "libc", +] + +[[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-util" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178" +dependencies = [ + "winapi", +] + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" diff --git a/examples/flowgraph/Cargo.toml b/examples/flowgraph/Cargo.toml new file mode 100644 index 0000000..84db987 --- /dev/null +++ b/examples/flowgraph/Cargo.toml @@ -0,0 +1,11 @@ +[package] +name = "flowgraph" +version = "0.1.0" +authors = ["Kyle Martin "] +edition = "2021" + +[lib] +crate-type = ["cdylib"] + +[dependencies] +binaryninja = {path="../../"} diff --git a/examples/flowgraph/src/lib.rs b/examples/flowgraph/src/lib.rs new file mode 100644 index 0000000..f80879e --- /dev/null +++ b/examples/flowgraph/src/lib.rs @@ -0,0 +1,51 @@ +use binaryninja::{ + binaryview::{BinaryView, BinaryViewExt}, + 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), + ])]; + + let node_a = FlowGraphNode::new(&graph); + node_a.set_disassembly_lines(&disassembly_lines_a); + + let node_b = FlowGraphNode::new(&graph); + let disassembly_lines_b = vec![DisassemblyTextLine::from(&vec!["Li", "ne", " 2"])]; + node_b.set_disassembly_lines(&disassembly_lines_b); + + let node_c = FlowGraphNode::new(&graph); + node_c.set_lines(vec!["Line 3", "Line 4", "Line 5"]); + + graph.append(&node_a); + graph.append(&node_b); + graph.append(&node_c); + + let edge = EdgeStyle::new(EdgePenStyle::DashDotDotLine, 2, ThemeColor::AddressColor); + node_a.add_outgoing_edge(BranchType::UserDefinedBranch, &node_b, &edge); + node_a.add_outgoing_edge( + BranchType::UnconditionalBranch, + &node_c, + &EdgeStyle::default(), + ); + + view.show_graph_report("Rust Graph Title", graph); +} + +#[no_mangle] +pub extern "C" fn UIPluginInit() -> bool { + register( + "Rust Graph Test Title", + "Rust Graph Test Description", + test_graph, + ); + true +} diff --git a/examples/minidump/.gitignore b/examples/minidump/.gitignore new file mode 100644 index 0000000..4fffb2f --- /dev/null +++ b/examples/minidump/.gitignore @@ -0,0 +1,2 @@ +/target +/Cargo.lock diff --git a/examples/minidump/Cargo.toml b/examples/minidump/Cargo.toml new file mode 100644 index 0000000..b002271 --- /dev/null +++ b/examples/minidump/Cargo.toml @@ -0,0 +1,13 @@ +[package] +name = "minidump_bn" +version = "0.1.0" +edition = "2021" + +[lib] +crate-type = ["cdylib"] + +[dependencies] +binaryninja = {path="../../"} +log = "0.4.17" +minidump = "0.15.2" +time = "=0.3.16" # Remove this when we update from stable-2022-12-15 - it's pinning a dependency that requires a more recent version of rustc diff --git a/examples/minidump/README.md b/examples/minidump/README.md new file mode 100644 index 0000000..3c54ca8 --- /dev/null +++ b/examples/minidump/README.md @@ -0,0 +1,65 @@ +# Binary Ninja Minidump Loader + +A Minidump memory dump loader plugin for Binary Ninja. + +![Screenshot of Binary Ninja using the "Minidump" Binary View, with a minidump loaded and the virtual addresses of the memory segments of the minidump showing in the Memory Map window](images/loaded-minidump-screenshot-border.png) + +This plugin adds a new _Minidump_ binary view type. When a binary with the magic number `MDMP` is opened, this plugin will automatically try to load in the binary as a minidump, and create a new _Minidump_ binary view to view the contents. + +The architecture is determined automatically from the platform information embedded in the minidump. + +![Screenshot showing the Minidump binary view type in the dropdown list of available binary views for an open binary](images/minidump-binary-view-type-screenshot-border.png) + +The loaded minidump's memory regions and modules can be navigated via the _Memory Map_ window. In the _Minidump_ binary view, the meanings of "Segments" and "Sections" in the Memory Map window are modified to mean the following: + +- The memory regions in the minidump are loaded as _Segments_. The _Data Offset_ and _Data Length_ fields of each segment are the corresponding addresses in the minidump file where the data for that memory region is located. +- The modules in the minidump are loaded as _Sections_, with the name of each section being the path to the module. + +![Screenshot showing the Memory Map window with the loaded minidump's memory segments and modules (i.e. "sections")](images/minidump-segments-sections-screenshot-border.png) + +## Supported Minidump Types + +This plugin currently only supports loading minidump files generated by the Windows [`MiniDumpWriteDump` API](https://learn.microsoft.com/en-us/windows/win32/api/minidumpapiset/nf-minidumpapiset-minidumpwritedump). + +This includes dumps generated from: + +- The [`.dump` command](https://learn.microsoft.com/en-us/windows-hardware/drivers/debugger/-dump--create-dump-file-) in WinDbg. +- The `.dump` command in Binary Ninja's debugger for Windows targets (which uses the same debugging engine as WinDbg). + +For both of the above, it's recommended to generate a full dump: + +``` +.dump /ma dumpfile.dmp +``` + +- The [`minidump` command](https://help.x64dbg.com/en/latest/commands/memory-operations/minidump.html) in x64dbg. + +``` +minidump dumpfile.dmp +``` + +- Right clicking on a listed process and then clicking "Create dump file" / "Create full dump" from Windows Task Manager, Process Hacker, Sysinternals Process Explorer, etc... + +## Unsupported Features (for now) + +- Loading Minidump files from platforms or APIs other than Windows' `MinidumpWriteDump`, such as those generated by [Google Breakpad](https://chromium.googlesource.com/breakpad/breakpad/). +- Loading and applyng debug information from the minidump file. In Windows minidump files, `MinidumpModuleList` streams contain information about the PDB file which contains the debug information for the module; this isn't currently read or applied, however. +- Integration with Binary Ninja's built-in debugger. Minidump files can contain information about threads, register values, and stack frames, and it would be nice in the future for minidump files to be loadable back into the debugger in order to resume a debugging session. This isn't currently done, however. + +## Building and Installing + +This plugin currently needs to be built from source, then copied into your user plugin folder. + +``` +cargo build --release +cp target/release/libminidump_bn.so ~/.binaryninja/plugins/ +``` + +The code in this plugin targets the `dev` branch of the [Binary Ninja Rust API](https://github.com/Vector35/binaryninja-api/tree/dev/rust). + +To update the Binary Ninja Rust API dependency: + +``` +cargo update -p binaryninja +cargo build --release +``` \ No newline at end of file diff --git a/examples/minidump/build.rs b/examples/minidump/build.rs new file mode 100644 index 0000000..5ba9bcd --- /dev/null +++ b/examples/minidump/build.rs @@ -0,0 +1,68 @@ +use std::env; +use std::fs::File; +use std::io::BufReader; +use std::path::PathBuf; + +#[cfg(target_os = "macos")] +static LASTRUN_PATH: (&str, &str) = ("HOME", "Library/Application Support/Binary Ninja/lastrun"); + +#[cfg(target_os = "linux")] +static LASTRUN_PATH: (&str, &str) = ("HOME", ".binaryninja/lastrun"); + +#[cfg(windows)] +static LASTRUN_PATH: (&str, &str) = ("APPDATA", "Binary Ninja\\lastrun"); + +// Check last run location for path to BinaryNinja; Otherwise check the default install locations +fn link_path() -> PathBuf { + use std::io::prelude::*; + + let home = PathBuf::from(env::var(LASTRUN_PATH.0).unwrap()); + let lastrun = PathBuf::from(&home).join(LASTRUN_PATH.1); + + File::open(lastrun) + .and_then(|f| { + let mut binja_path = String::new(); + let mut reader = BufReader::new(f); + + reader.read_line(&mut binja_path)?; + Ok(PathBuf::from(binja_path.trim())) + }) + .unwrap_or_else(|_| { + #[cfg(target_os = "macos")] + return PathBuf::from("/Applications/Binary Ninja.app/Contents/MacOS"); + + #[cfg(target_os = "linux")] + return home.join("binaryninja"); + + #[cfg(windows)] + return PathBuf::from(env::var("PROGRAMFILES").unwrap()) + .join("Vector35\\BinaryNinja\\"); + }) +} + +fn main() { + // Use BINARYNINJADIR first for custom BN builds/configurations (BN devs/build server), fallback on defaults + let install_path = env::var("BINARYNINJADIR") + .map(PathBuf::from) + .unwrap_or_else(|_| link_path()); + + #[cfg(target_os = "linux")] + println!( + "cargo:rustc-link-arg=-Wl,-rpath,{},-L{},-l:libbinaryninjacore.so.1", + install_path.to_str().unwrap(), + install_path.to_str().unwrap(), + ); + + #[cfg(target_os = "macos")] + println!( + "cargo:rustc-link-arg=-Wl,-rpath,{},-L{},-lbinaryninjacore", + install_path.to_str().unwrap(), + install_path.to_str().unwrap(), + ); + + #[cfg(target_os = "windows")] + { + println!("cargo:rustc-link-lib=binaryninjacore"); + println!("cargo:rustc-link-search={}", install_path.to_str().unwrap()); + } +} diff --git a/examples/minidump/images/loaded-minidump-screenshot-border.png b/examples/minidump/images/loaded-minidump-screenshot-border.png new file mode 100644 index 0000000..51b8282 Binary files /dev/null and b/examples/minidump/images/loaded-minidump-screenshot-border.png differ diff --git a/examples/minidump/images/minidump-binary-view-type-screenshot-border.png b/examples/minidump/images/minidump-binary-view-type-screenshot-border.png new file mode 100644 index 0000000..9a52dd6 Binary files /dev/null and b/examples/minidump/images/minidump-binary-view-type-screenshot-border.png differ diff --git a/examples/minidump/images/minidump-segments-sections-screenshot-border.png b/examples/minidump/images/minidump-segments-sections-screenshot-border.png new file mode 100644 index 0000000..0035acc Binary files /dev/null and b/examples/minidump/images/minidump-segments-sections-screenshot-border.png differ diff --git a/examples/minidump/src/command.rs b/examples/minidump/src/command.rs new file mode 100644 index 0000000..0b10c65 --- /dev/null +++ b/examples/minidump/src/command.rs @@ -0,0 +1,44 @@ +use std::str; + +use log::{debug, error, info}; +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(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) { + Ok(_) => { + if let Ok(memory_info_str) = str::from_utf8(&memory_info_list_writer) { + info!("{memory_info_str}"); + } else { + error!("Could not convert the memory information description from minidump into a valid string"); + } + } + Err(_) => { + error!("Could not get memory information from minidump"); + } + } + } else { + error!( + "Could not parse a valid MinidumpMemoryInfoList stream from the minidump" + ); + } + } else { + error!("Could not parse a valid minidump file from the parent binary view's data buffer"); + } + } else { + error!("Could not read data from parent binary view"); + } + } else { + error!("Could not get the parent binary view"); + } +} diff --git a/examples/minidump/src/lib.rs b/examples/minidump/src/lib.rs new file mode 100644 index 0000000..9d8b405 --- /dev/null +++ b/examples/minidump/src/lib.rs @@ -0,0 +1,37 @@ +use binaryninja::binaryview::BinaryView; +use binaryninja::command::{register, Command}; +use binaryninja::custombinaryview::register_view_type; +use log::{debug, LevelFilter}; + +mod command; +mod view; + +struct PrintMemoryInformationCommand; + +impl Command for PrintMemoryInformationCommand { + fn action(&self, binary_view: &BinaryView) { + command::print_memory_information(binary_view); + } + + fn valid(&self, _binary_view: &BinaryView) -> bool { + true // TODO: Of course, the command will not always be valid! + } +} + +#[no_mangle] +#[allow(non_snake_case)] +pub extern "C" fn CorePluginInit() -> bool { + binaryninja::logger::init(LevelFilter::Trace).expect("failed to initialize logging"); + + debug!("Registering minidump binary view type"); + register_view_type("Minidump", "Minidump", view::MinidumpBinaryViewType::new); + + debug!("Registering minidump plugin commands"); + register( + "Minidump\\[DEBUG] Print Minidump Memory Information", + "Print a human-readable description of the contents of the MinidumpMemoryInfoList stream in the loaded minidump", + PrintMemoryInformationCommand {}, + ); + + true +} diff --git a/examples/minidump/src/view.rs b/examples/minidump/src/view.rs new file mode 100644 index 0000000..9eee6aa --- /dev/null +++ b/examples/minidump/src/view.rs @@ -0,0 +1,426 @@ +use std::collections::HashMap; +use std::ops::{Deref, Range}; +use std::sync::Arc; + +use binaryninja::section::Section; +use binaryninja::segment::Segment; +use log::{debug, error, info, warn}; +use minidump::format::MemoryProtection; +use minidump::{ + Minidump, MinidumpMemory64List, MinidumpMemoryInfoList, MinidumpMemoryList, MinidumpModuleList, + MinidumpStream, MinidumpSystemInfo, Module, +}; + +use binaryninja::binaryview::{BinaryView, BinaryViewBase, BinaryViewExt}; +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. +/// +/// If this view type is valid for an opened binary (determined by `is_valid_for`), +/// the Binary Ninja core then uses this view type to create an actual instance of the _Minidump_ +/// binary view (via `create_custom_view`). +pub struct MinidumpBinaryViewType { + view_type: BinaryViewType, +} + +impl MinidumpBinaryViewType { + pub fn new(view_type: BinaryViewType) -> Self { + MinidumpBinaryViewType { view_type } + } +} + +impl AsRef for MinidumpBinaryViewType { + fn as_ref(&self) -> &BinaryViewType { + &self.view_type + } +} + +impl BinaryViewTypeBase for MinidumpBinaryViewType { + fn is_deprecated(&self) -> bool { + false + } + + fn is_valid_for(&self, data: &BinaryView) -> bool { + let mut magic_number = Vec::::new(); + data.read_into_vec(&mut magic_number, 0, 4); + + magic_number == b"MDMP" + } +} + +impl CustomBinaryViewType for MinidumpBinaryViewType { + fn create_custom_view<'builder>( + &self, + data: &BinaryView, + builder: CustomViewBuilder<'builder, Self>, + ) -> BinaryViewResult> { + debug!("Creating MinidumpBinaryView from registered MinidumpBinaryViewType"); + + let binary_view = builder.create::(data, ()); + binary_view + } +} + +#[derive(Debug)] +struct SegmentData { + rva_range: Range, + mapped_addr_range: Range, +} + +impl SegmentData { + fn from_addresses_and_size(rva: u64, mapped_addr: u64, size: u64) -> Self { + SegmentData { + rva_range: Range { + start: rva, + end: rva + size, + }, + mapped_addr_range: Range { + start: mapped_addr, + end: mapped_addr + size, + }, + } + } +} + +#[derive(Debug)] +struct SegmentMemoryProtection { + readable: bool, + writable: bool, + executable: bool, +} + +/// An instance of the actual _Minidump_ custom binary view. +/// This contains the main logic to load the memory segments inside a minidump file into the binary view. +pub struct MinidumpBinaryView { + /// The handle to the "real" BinaryView object, in the Binary Ninja core. + inner: binaryninja::rc::Ref, +} + +impl MinidumpBinaryView { + fn new(view: &BinaryView) -> Self { + MinidumpBinaryView { + inner: view.to_owned(), + } + } + + 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) { + // Architecture, platform information + if let Ok(minidump_system_info) = minidump_obj.get_stream::() { + if let Some(platform) = MinidumpBinaryView::translate_minidump_platform( + minidump_system_info.cpu, + minidump_obj.endian, + minidump_system_info.os, + ) { + self.set_default_platform(&platform); + } else { + error!( + "Could not parse valid system information from minidump: could not map system information in MinidumpSystemInfo stream (arch {:?}, endian {:?}, os {:?}) to a known architecture", + minidump_system_info.cpu, + minidump_obj.endian, + minidump_system_info.os, + ); + return Err(()); + } + } else { + error!("Could not parse system information from minidump: could not find a valid MinidumpSystemInfo stream"); + return Err(()); + } + + // Memory segments + let mut segment_data = Vec::::new(); + + // Memory segments in a full memory dump (MinidumpMemory64List) + // Grab the shared base RVA for all entries in the MinidumpMemory64List, + // since the minidump crate doesn't expose this to us + if let Ok(raw_stream) = minidump_obj.get_raw_stream(MinidumpMemory64List::STREAM_TYPE) { + if let Ok(base_rva_array) = raw_stream[8..16].try_into() { + let base_rva = u64::from_le_bytes(base_rva_array); + debug!("Found BaseRVA value {:#x}", base_rva); + + if let Ok(minidump_memory_list) = + minidump_obj.get_stream::() + { + let mut current_rva = base_rva; + for memory_segment in minidump_memory_list.iter() { + debug!( + "Found memory segment at RVA {:#x} with virtual address {:#x} and size {:#x}", + current_rva, + memory_segment.base_address, + memory_segment.size, + ); + segment_data.push(SegmentData::from_addresses_and_size( + current_rva, + memory_segment.base_address, + memory_segment.size, + )); + current_rva += memory_segment.size; + } + } + } else { + error!("Could not parse BaseRVA value shared by all entries in the MinidumpMemory64List stream") + } + } else { + warn!("Could not read memory from minidump: could not find a valid MinidumpMemory64List stream. This minidump may not be a full memory dump. Trying to find partial dump memory from a MinidumpMemoryList now..."); + // Memory segments in a regular memory dump (MinidumpMemoryList), + // i.e. one that does not include the full process memory data. + if let Ok(minidump_memory_list) = minidump_obj.get_stream::() { + for memory_segment in minidump_memory_list.by_addr() { + debug!( + "Found memory segment at RVA {:#x} with virtual address {:#x} and size {:#x}", + memory_segment.desc.memory.rva, + memory_segment.base_address, + memory_segment.size + ); + segment_data.push(SegmentData::from_addresses_and_size( + memory_segment.desc.memory.rva as u64, + memory_segment.base_address, + memory_segment.size, + )); + } + } else { + error!("Could not read any memory from minidump: could not find a valid MinidumpMemory64List stream or a valid MinidumpMemoryList stream."); + } + } + + // Memory protection information + let mut segment_protection_data = HashMap::new(); + + if let Ok(minidump_memory_info_list) = + minidump_obj.get_stream::() + { + for memory_info in minidump_memory_info_list.iter() { + if let Some(memory_range) = memory_info.memory_range() { + debug!( + "Found memory protection info for memory segment ranging from virtual address {:#x} to {:#x}: {:#?}", + memory_range.start, + memory_range.end, + memory_info.protection + ); + segment_protection_data.insert( + // The range returned to us by MinidumpMemoryInfoList is an + // end-inclusive range_map::Range; we need to add 1 to + // the end index to make it into an end-exclusive std::ops::Range. + Range { + start: memory_range.start, + end: memory_range.end + 1, + }, + memory_info.protection, + ); + } + } + } + + for segment in segment_data.iter() { + if let Some(segment_protection) = + segment_protection_data.get(&segment.mapped_addr_range) + { + let segment_memory_protection = + MinidumpBinaryView::translate_memory_protection(*segment_protection); + + info!( + "Adding memory segment at virtual address {:#x} to {:#x}, from data range {:#x} to {:#x}, with protections readable {}, writable {}, executable {}", + segment.mapped_addr_range.start, + segment.mapped_addr_range.end, + segment.rva_range.start, + segment.rva_range.end, + segment_memory_protection.readable, + segment_memory_protection.writable, + segment_memory_protection.executable, + ); + + self.add_segment( + Segment::builder(segment.mapped_addr_range.clone()) + .parent_backing(segment.rva_range.clone()) + .is_auto(true) + .readable(segment_memory_protection.readable) + .writable(segment_memory_protection.writable) + .executable(segment_memory_protection.executable), + ); + } else { + error!( + "Could not find memory protection information for memory segment from {:#x} to {:#x}", segment.mapped_addr_range.start, + segment.mapped_addr_range.end, + ); + } + } + + // Module information + // This stretches the concept a bit, but we can add each module as a + // separate "section" of the binary. + // Sections can be named, and can span multiple segments. + if let Ok(minidump_module_list) = minidump_obj.get_stream::() { + for module_info in minidump_module_list.by_addr() { + info!( + "Found module with name {} at virtual address {:#x} with size {:#x}", + module_info.name, + module_info.base_address(), + module_info.size(), + ); + let module_address_range = Range { + start: module_info.base_address(), + end: module_info.base_address() + module_info.size(), + }; + self.add_section( + Section::builder(module_info.name.clone(), module_address_range) + .is_auto(true), + ); + } + } else { + warn!("Could not find valid module information in minidump: could not find a valid MinidumpModuleList stream"); + } + } else { + error!("Could not parse data as minidump"); + return Err(()); + } + Ok(()) + } + + fn translate_minidump_platform( + minidump_cpu_arch: minidump::system_info::Cpu, + minidump_endian: minidump::Endian, + minidump_os: minidump::system_info::Os, + ) -> Option> { + match minidump_os { + minidump::system_info::Os::Windows => match minidump_cpu_arch { + minidump::system_info::Cpu::Arm64 => Platform::by_name("windows-aarch64"), + minidump::system_info::Cpu::Arm => Platform::by_name("windows-armv7"), + minidump::system_info::Cpu::X86 => Platform::by_name("windows-x86"), + minidump::system_info::Cpu::X86_64 => Platform::by_name("windows-x86_64"), + _ => None, + }, + minidump::system_info::Os::MacOs => match minidump_cpu_arch { + minidump::system_info::Cpu::Arm64 => Platform::by_name("mac-aarch64"), + minidump::system_info::Cpu::Arm => Platform::by_name("mac-armv7"), + minidump::system_info::Cpu::X86 => Platform::by_name("mac-x86"), + minidump::system_info::Cpu::X86_64 => Platform::by_name("mac-x86_64"), + _ => None, + }, + minidump::system_info::Os::Linux => match minidump_cpu_arch { + minidump::system_info::Cpu::Arm64 => Platform::by_name("linux-aarch64"), + minidump::system_info::Cpu::Arm => Platform::by_name("linux-armv7"), + minidump::system_info::Cpu::X86 => Platform::by_name("linux-x86"), + minidump::system_info::Cpu::X86_64 => Platform::by_name("linux-x86_64"), + minidump::system_info::Cpu::Ppc => match minidump_endian { + minidump::Endian::Little => Platform::by_name("linux-ppc32_le"), + minidump::Endian::Big => Platform::by_name("linux-ppc32"), + }, + minidump::system_info::Cpu::Ppc64 => match minidump_endian { + minidump::Endian::Little => Platform::by_name("linux-ppc64_le"), + minidump::Endian::Big => Platform::by_name("linux-ppc64"), + }, + _ => None, + }, + minidump::system_info::Os::NaCl => None, + minidump::system_info::Os::Android => None, + minidump::system_info::Os::Ios => None, + minidump::system_info::Os::Ps3 => None, + minidump::system_info::Os::Solaris => None, + _ => None, + } + } + + fn translate_memory_protection( + minidump_memory_protection: MemoryProtection, + ) -> SegmentMemoryProtection { + let (readable, writable, executable) = match minidump_memory_protection { + MemoryProtection::PAGE_NOACCESS => (false, false, false), + MemoryProtection::PAGE_READONLY => (true, false, false), + MemoryProtection::PAGE_READWRITE => (true, true, false), + MemoryProtection::PAGE_WRITECOPY => (true, true, false), + MemoryProtection::PAGE_EXECUTE => (false, false, true), + MemoryProtection::PAGE_EXECUTE_READ => (true, false, true), + MemoryProtection::PAGE_EXECUTE_READWRITE => (true, true, true), + MemoryProtection::PAGE_EXECUTE_WRITECOPY => (true, true, true), + MemoryProtection::ACCESS_MASK => (false, false, false), + MemoryProtection::PAGE_GUARD => (false, false, false), + MemoryProtection::PAGE_NOCACHE => (false, false, false), + MemoryProtection::PAGE_WRITECOMBINE => (false, false, false), + _ => (false, false, false), + }; + SegmentMemoryProtection { + readable, + writable, + executable, + } + } +} + +impl AsRef for MinidumpBinaryView { + fn as_ref(&self) -> &BinaryView { + &self.inner + } +} + +impl BinaryViewBase for MinidumpBinaryView { + // TODO: This should be filled out with the actual address size + // from the platform information in the minidump. + fn address_size(&self) -> usize { + 0 + } + + fn default_endianness(&self) -> Endianness { + // TODO: This should be filled out with the actual endianness + // from the platform information in the minidump. + Endianness::LittleEndian + } + + fn entry_point(&self) -> u64 { + // TODO: We should fill this out with a real entry point. + // This can be done by getting the main module of the minidump + // with MinidumpModuleList::main_module, + // then parsing the PE metadata of the main module to find its entry point(s). + 0 + } +} + +unsafe impl CustomBinaryView for MinidumpBinaryView { + type Args = (); + + fn new(handle: &BinaryView, _args: &Self::Args) -> BinaryViewResult { + Ok(MinidumpBinaryView::new(handle)) + } + + fn init(&self, _args: Self::Args) -> BinaryViewResult<()> { + self.init() + } +} diff --git a/examples/mlil_lifter/Cargo.toml b/examples/mlil_lifter/Cargo.toml new file mode 100644 index 0000000..4ea158f --- /dev/null +++ b/examples/mlil_lifter/Cargo.toml @@ -0,0 +1,16 @@ +[package] +name = "mlil_lifter" +version = "0.1.0" +edition = "2021" + +# Uncomment this if you're writing a plugin (plugins are shared objects loaded by the core): +# [lib] +# crate-type = ["cdylib"] + +# You can point at the BinaryNinja dependency in one of two ways, via path: +[dependencies] +binaryninja = {path="../../"} + +# Or directly at the git repo: +# [dependencies] +# binaryninja = {git = "https://github.com/Vector35/binaryninja-api.git", branch = "dev"} diff --git a/examples/mlil_lifter/build.rs b/examples/mlil_lifter/build.rs new file mode 100644 index 0000000..5ba9bcd --- /dev/null +++ b/examples/mlil_lifter/build.rs @@ -0,0 +1,68 @@ +use std::env; +use std::fs::File; +use std::io::BufReader; +use std::path::PathBuf; + +#[cfg(target_os = "macos")] +static LASTRUN_PATH: (&str, &str) = ("HOME", "Library/Application Support/Binary Ninja/lastrun"); + +#[cfg(target_os = "linux")] +static LASTRUN_PATH: (&str, &str) = ("HOME", ".binaryninja/lastrun"); + +#[cfg(windows)] +static LASTRUN_PATH: (&str, &str) = ("APPDATA", "Binary Ninja\\lastrun"); + +// Check last run location for path to BinaryNinja; Otherwise check the default install locations +fn link_path() -> PathBuf { + use std::io::prelude::*; + + let home = PathBuf::from(env::var(LASTRUN_PATH.0).unwrap()); + let lastrun = PathBuf::from(&home).join(LASTRUN_PATH.1); + + File::open(lastrun) + .and_then(|f| { + let mut binja_path = String::new(); + let mut reader = BufReader::new(f); + + reader.read_line(&mut binja_path)?; + Ok(PathBuf::from(binja_path.trim())) + }) + .unwrap_or_else(|_| { + #[cfg(target_os = "macos")] + return PathBuf::from("/Applications/Binary Ninja.app/Contents/MacOS"); + + #[cfg(target_os = "linux")] + return home.join("binaryninja"); + + #[cfg(windows)] + return PathBuf::from(env::var("PROGRAMFILES").unwrap()) + .join("Vector35\\BinaryNinja\\"); + }) +} + +fn main() { + // Use BINARYNINJADIR first for custom BN builds/configurations (BN devs/build server), fallback on defaults + let install_path = env::var("BINARYNINJADIR") + .map(PathBuf::from) + .unwrap_or_else(|_| link_path()); + + #[cfg(target_os = "linux")] + println!( + "cargo:rustc-link-arg=-Wl,-rpath,{},-L{},-l:libbinaryninjacore.so.1", + install_path.to_str().unwrap(), + install_path.to_str().unwrap(), + ); + + #[cfg(target_os = "macos")] + println!( + "cargo:rustc-link-arg=-Wl,-rpath,{},-L{},-lbinaryninjacore", + install_path.to_str().unwrap(), + install_path.to_str().unwrap(), + ); + + #[cfg(target_os = "windows")] + { + println!("cargo:rustc-link-lib=binaryninjacore"); + println!("cargo:rustc-link-search={}", install_path.to_str().unwrap()); + } +} diff --git a/examples/mlil_lifter/src/main.rs b/examples/mlil_lifter/src/main.rs new file mode 100644 index 0000000..b827e0c --- /dev/null +++ b/examples/mlil_lifter/src/main.rs @@ -0,0 +1,49 @@ +use std::env; + +use binaryninja::binaryview::BinaryViewExt; + +// Standalone executables need to provide a main function for rustc +// Plugins should refer to `binaryninja::command::*` for the various registration callbacks. +fn main() { + let mut args = env::args(); + let _ = args.next().unwrap(); + let Some(filename) = args.next() else { + panic!("Expected input filename\n"); + }; + + // This loads all the core architecture, platform, etc plugins + // Standalone executables probably need to call this, but plugins do not + println!("Loading plugins..."); + let _headless_session = binaryninja::headless::Session::new(); + + // Your code here... + println!("Loading binary..."); + let bv = binaryninja::load(filename).expect("Couldn't open binary file"); + + // Go through all functions in the binary + for func in bv.functions().iter() { + let sym = func.symbol(); + println!("Function {}:", sym.full_name()); + + let Ok(il) = func.medium_level_il() else { + println!(" Does not have MLIL\n"); + continue; + }; + // Get the SSA form for this function + let il = il.ssa_form(); + + // Loop through all blocks in the function + for block in il.basic_blocks().iter() { + // Loop though each instruction in the block + for instr in block.iter() { + // Uplift the instruction into a native rust format + let lifted = instr.lift(); + let address = instr.address(); + + // print the lifted instruction + println!("{address:08x}: {lifted:x?}"); + } + } + println!(); + } +} diff --git a/examples/mlil_visitor/Cargo.toml b/examples/mlil_visitor/Cargo.toml new file mode 100644 index 0000000..76ca122 --- /dev/null +++ b/examples/mlil_visitor/Cargo.toml @@ -0,0 +1,16 @@ +[package] +name = "mlil_visitor" +version = "0.1.0" +edition = "2021" + +# Uncomment this if you're writing a plugin (plugins are shared objects loaded by the core): +# [lib] +# crate-type = ["cdylib"] + +# You can point at the BinaryNinja dependency in one of two ways, via path: +[dependencies] +binaryninja = {path="../../"} + +# Or directly at the git repo: +# [dependencies] +# binaryninja = {git = "https://github.com/Vector35/binaryninja-api.git", branch = "dev"} diff --git a/examples/mlil_visitor/build.rs b/examples/mlil_visitor/build.rs new file mode 100644 index 0000000..5ba9bcd --- /dev/null +++ b/examples/mlil_visitor/build.rs @@ -0,0 +1,68 @@ +use std::env; +use std::fs::File; +use std::io::BufReader; +use std::path::PathBuf; + +#[cfg(target_os = "macos")] +static LASTRUN_PATH: (&str, &str) = ("HOME", "Library/Application Support/Binary Ninja/lastrun"); + +#[cfg(target_os = "linux")] +static LASTRUN_PATH: (&str, &str) = ("HOME", ".binaryninja/lastrun"); + +#[cfg(windows)] +static LASTRUN_PATH: (&str, &str) = ("APPDATA", "Binary Ninja\\lastrun"); + +// Check last run location for path to BinaryNinja; Otherwise check the default install locations +fn link_path() -> PathBuf { + use std::io::prelude::*; + + let home = PathBuf::from(env::var(LASTRUN_PATH.0).unwrap()); + let lastrun = PathBuf::from(&home).join(LASTRUN_PATH.1); + + File::open(lastrun) + .and_then(|f| { + let mut binja_path = String::new(); + let mut reader = BufReader::new(f); + + reader.read_line(&mut binja_path)?; + Ok(PathBuf::from(binja_path.trim())) + }) + .unwrap_or_else(|_| { + #[cfg(target_os = "macos")] + return PathBuf::from("/Applications/Binary Ninja.app/Contents/MacOS"); + + #[cfg(target_os = "linux")] + return home.join("binaryninja"); + + #[cfg(windows)] + return PathBuf::from(env::var("PROGRAMFILES").unwrap()) + .join("Vector35\\BinaryNinja\\"); + }) +} + +fn main() { + // Use BINARYNINJADIR first for custom BN builds/configurations (BN devs/build server), fallback on defaults + let install_path = env::var("BINARYNINJADIR") + .map(PathBuf::from) + .unwrap_or_else(|_| link_path()); + + #[cfg(target_os = "linux")] + println!( + "cargo:rustc-link-arg=-Wl,-rpath,{},-L{},-l:libbinaryninjacore.so.1", + install_path.to_str().unwrap(), + install_path.to_str().unwrap(), + ); + + #[cfg(target_os = "macos")] + println!( + "cargo:rustc-link-arg=-Wl,-rpath,{},-L{},-lbinaryninjacore", + install_path.to_str().unwrap(), + install_path.to_str().unwrap(), + ); + + #[cfg(target_os = "windows")] + { + println!("cargo:rustc-link-lib=binaryninjacore"); + println!("cargo:rustc-link-search={}", install_path.to_str().unwrap()); + } +} diff --git a/examples/mlil_visitor/src/main.rs b/examples/mlil_visitor/src/main.rs new file mode 100644 index 0000000..3df005a --- /dev/null +++ b/examples/mlil_visitor/src/main.rs @@ -0,0 +1,266 @@ +use std::env; + +use binaryninja::binaryview::BinaryViewExt; +use binaryninja::mlil::operation::MediumLevelILOperand; +use binaryninja::mlil::{MediumLevelILFunction, MediumLevelILInstruction, MediumLevelILOperation}; +use binaryninja::types::Variable; + +fn print_indent(indent: usize) { + print!("{: 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"), + } +} + +fn print_variable(func: &MediumLevelILFunction, var: &Variable) { + print!("{}", func.get_function().get_variable_name(var)); +} + +fn print_il_expr(instr: &MediumLevelILInstruction, mut indent: usize) { + print_indent(indent); + print_operation(instr.operation()); + println!(""); + + indent += 1; + + use MediumLevelILOperand::*; + for (_name, operand) in instr.operands() { + match operand { + Int(int) => { + print_indent(indent); + println!("int 0x{:x}", int); + } + Float(float) => { + print_indent(indent); + println!("int {:e}", float); + } + Expr(expr) => print_il_expr(&expr, indent), + Var(var) => { + print_indent(indent); + print!("var "); + print_variable(instr.function(), &var); + println!(); + } + VarSsa(var) => { + print_indent(indent); + print!("ssa var "); + print_variable(instr.function(), &var.variable); + println!("#{}", var.version); + } + IntList(list) => { + print_indent(indent); + print!("index list "); + for i in list { + print!("{i} "); + } + println!(); + } + VarList(list) => { + print_indent(indent); + print!("var list "); + for i in list { + print_variable(instr.function(), &i); + print!(" "); + } + println!(); + } + VarSsaList(list) => { + print_indent(indent); + print!("ssa var list "); + for i in list { + print_variable(instr.function(), &i.variable); + print!("#{} ", i.version); + } + println!(); + } + ExprList(list) => { + print_indent(indent); + println!("expr list"); + for i in list { + print_il_expr(&i, indent + 1); + } + } + TargetMap(list) => { + print_indent(indent); + print!("target map "); + for (i, f) in list { + print!("({i}, {f}) "); + } + println!(); + } + } + } +} + +// Standalone executables need to provide a main function for rustc +// Plugins should refer to `binaryninja::command::*` for the various registration callbacks. +fn main() { + let mut args = env::args(); + let _ = args.next().unwrap(); + let Some(filename) = args.next() else { + panic!("Expected input filename\n"); + }; + + // This loads all the core architecture, platform, etc plugins + // Standalone executables probably need to call this, but plugins do not + println!("Loading plugins..."); + let _headless_session = binaryninja::headless::Session::new(); + + // Your code here... + println!("Loading binary..."); + let bv = binaryninja::load(filename).expect("Couldn't open binary file"); + + // Go through all functions in the binary + for func in bv.functions().iter() { + let sym = func.symbol(); + println!("Function {}:", sym.full_name()); + + let Ok(il) = func.medium_level_il() else { + println!(" Does not have MLIL\n"); + continue; + }; + + // Loop through all blocks in the function + for block in il.basic_blocks().iter() { + // Loop though each instruction in the block + for instr in block.iter() { + // Generically parse the IL tree and display the parts + print_il_expr(&instr, 2); + } + } + println!(); + } +} diff --git a/examples/template/Cargo.lock b/examples/template/Cargo.lock new file mode 100644 index 0000000..57f01d5 --- /dev/null +++ b/examples/template/Cargo.lock @@ -0,0 +1,355 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "aho-corasick" +version = "0.7.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e37cfd5e7657ada45f742d6e99ca5788580b5c529dc78faf11ece6dc702656f" +dependencies = [ + "memchr", +] + +[[package]] +name = "ansi_term" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d52a9bb7ec0cf484c551830a7ce27bd20d67eac647e1befb56b0be4ee39a55d2" +dependencies = [ + "winapi", +] + +[[package]] +name = "atty" +version = "0.2.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8" +dependencies = [ + "hermit-abi", + "libc", + "winapi", +] + +[[package]] +name = "binaryninja" +version = "0.1.0" +source = "git+https://github.com/Vector35/binaryninja-api.git?branch=dev#83d7a7800ac4a970d618ad125cdbb18e3b29b53e" +dependencies = [ + "binaryninjacore-sys", + "libc", + "log", +] + +[[package]] +name = "binaryninjacore-sys" +version = "0.1.0" +source = "git+https://github.com/Vector35/binaryninja-api.git?branch=dev#83d7a7800ac4a970d618ad125cdbb18e3b29b53e" +dependencies = [ + "bindgen", +] + +[[package]] +name = "bindgen" +version = "0.58.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0f8523b410d7187a43085e7e064416ea32ded16bd0a4e6fc025e21616d01258f" +dependencies = [ + "bitflags", + "cexpr", + "clang-sys", + "clap", + "env_logger", + "lazy_static", + "lazycell", + "log", + "peeking_take_while", + "proc-macro2", + "quote", + "regex", + "rustc-hash", + "shlex", + "which", +] + +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + +[[package]] +name = "cexpr" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f4aedb84272dbe89af497cf81375129abda4fc0a9e7c5d317498c15cc30c0d27" +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.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa66045b9cb23c2e9c1520732030608b02ee07e5cfaa5a521ec15ded7fa24c90" +dependencies = [ + "glob", + "libc", + "libloading", +] + +[[package]] +name = "clap" +version = "2.34.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a0610544180c38b88101fecf2dd634b174a62eef6946f84dfc6a7127512b381c" +dependencies = [ + "ansi_term", + "atty", + "bitflags", + "strsim", + "textwrap", + "unicode-width", + "vec_map", +] + +[[package]] +name = "env_logger" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a19187fea3ac7e84da7dacf48de0c45d63c6a76f9490dae389aead16c243fce3" +dependencies = [ + "atty", + "humantime", + "log", + "regex", + "termcolor", +] + +[[package]] +name = "glob" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b919933a397b79c37e33b77bb2aa3dc8eb6e165ad809e58ff75bc7db2e34574" + +[[package]] +name = "hermit-abi" +version = "0.1.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33" +dependencies = [ + "libc", +] + +[[package]] +name = "humantime" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4" + +[[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.117" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e74d72e0f9b65b5b4ca49a346af3976df0f9c61d550727f349ecd559f251a26c" + +[[package]] +name = "libloading" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "efbc0f03f9a775e9f6aed295c6a1ba2253c5757a9e03d55c6caa46a681abcddd" +dependencies = [ + "cfg-if", + "winapi", +] + +[[package]] +name = "log" +version = "0.4.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "51b9bbe6c47d51fc3e1a9b945965946b4c44142ab8792c50835a980d362c2710" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "memchr" +version = "2.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "308cc39be01b73d0d18f82a0e7b2a3df85245f84af96fdddc5d202d27e47b86a" + +[[package]] +name = "nom" +version = "5.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ffb4262d26ed83a1c0a33a38fe2bb15797329c85770da05e6b828ddb782627af" +dependencies = [ + "memchr", + "version_check", +] + +[[package]] +name = "peeking_take_while" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "19b17cddbe7ec3f8bc800887bab5e717348c95ea2ca0b1bf0837fb964dc67099" + +[[package]] +name = "proc-macro2" +version = "1.0.36" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c7342d5883fbccae1cc37a2353b09c87c9b0f3afd73f5fb9bba687a1f733b029" +dependencies = [ + "unicode-xid", +] + +[[package]] +name = "quote" +version = "1.0.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "864d3e96a899863136fc6e99f3d7cae289dafe43bf2c5ac19b70df7210c0a145" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "regex" +version = "1.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d07a8629359eb56f1e2fb1652bb04212c072a87ba68546a04065d525673ac461" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] + +[[package]] +name = "regex-syntax" +version = "0.6.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f497285884f3fcff424ffc933e56d7cbca511def0c9831a7f9b5f6153e3cc89b" + +[[package]] +name = "rustc-hash" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" + +[[package]] +name = "shlex" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "43b2853a4d09f215c24cc5489c992ce46052d359b5109343cbafbf26bc62f8a3" + +[[package]] +name = "strsim" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ea5119cdb4c55b55d432abb513a0429384878c15dde60cc77b1c99de1a95a6a" + +[[package]] +name = "template" +version = "0.1.0" +dependencies = [ + "binaryninja", +] + +[[package]] +name = "termcolor" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2dfed899f0eb03f32ee8c6a0aabdb8a7949659e3466561fc0adf54e26d88c5f4" +dependencies = [ + "winapi-util", +] + +[[package]] +name = "textwrap" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d326610f408c7a4eb6f51c37c330e496b08506c9457c9d34287ecc38809fb060" +dependencies = [ + "unicode-width", +] + +[[package]] +name = "unicode-width" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3ed742d4ea2bd1176e236172c8429aaf54486e7ac098db29ffe6529e0ce50973" + +[[package]] +name = "unicode-xid" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ccb82d61f80a663efe1f787a51b16b5a51e3314d6ac365b08639f52387b33f3" + +[[package]] +name = "vec_map" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1bddf1187be692e79c5ffeab891132dfb0f236ed36a43c7ed39f1165ee20191" + +[[package]] +name = "version_check" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" + +[[package]] +name = "which" +version = "3.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d011071ae14a2f6671d0b74080ae0cd8ebf3a6f8c9589a2cd45f23126fe29724" +dependencies = [ + "libc", +] + +[[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-util" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178" +dependencies = [ + "winapi", +] + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" diff --git a/examples/template/Cargo.toml b/examples/template/Cargo.toml new file mode 100644 index 0000000..f571c46 --- /dev/null +++ b/examples/template/Cargo.toml @@ -0,0 +1,16 @@ +[package] +name = "template" +version = "0.1.0" +edition = "2021" + +# Uncomment this if you're writing a plugin (plugins are shared objects loaded by the core): +# [lib] +# crate-type = ["cdylib"] + +# You can point at the BinaryNinja dependency in one of two ways, via path: +[dependencies] +binaryninja = {path="../../"} + +# Or directly at the git repo: +# [dependencies] +# binaryninja = {git = "https://github.com/Vector35/binaryninja-api.git", branch = "dev"} diff --git a/examples/template/README.md b/examples/template/README.md new file mode 100644 index 0000000..1466ef2 --- /dev/null +++ b/examples/template/README.md @@ -0,0 +1,19 @@ +# Template + +[The only official method of providing linker arguments to a crate is through that crate's `build.rs`](https://github.com/rust-lang/cargo/issues/9554), thus this template. + +Please see `Cargo.toml` for further configuration options. + +## Plugins + +Enable +``` +[lib] +crate-type = ["cdylib"] +``` +in `Cargo.toml`. + +## Standalone executables + +All standalone executables should call both `binaryninja::headless::init()` and `binaryninja::headless::shutdown()` (see [`src/main.rs`](src/main.rs)). +Standalone executables will fail to link if you do not provide a `build.rs`. The one provided here should work. diff --git a/examples/template/build.rs b/examples/template/build.rs new file mode 100644 index 0000000..5ba9bcd --- /dev/null +++ b/examples/template/build.rs @@ -0,0 +1,68 @@ +use std::env; +use std::fs::File; +use std::io::BufReader; +use std::path::PathBuf; + +#[cfg(target_os = "macos")] +static LASTRUN_PATH: (&str, &str) = ("HOME", "Library/Application Support/Binary Ninja/lastrun"); + +#[cfg(target_os = "linux")] +static LASTRUN_PATH: (&str, &str) = ("HOME", ".binaryninja/lastrun"); + +#[cfg(windows)] +static LASTRUN_PATH: (&str, &str) = ("APPDATA", "Binary Ninja\\lastrun"); + +// Check last run location for path to BinaryNinja; Otherwise check the default install locations +fn link_path() -> PathBuf { + use std::io::prelude::*; + + let home = PathBuf::from(env::var(LASTRUN_PATH.0).unwrap()); + let lastrun = PathBuf::from(&home).join(LASTRUN_PATH.1); + + File::open(lastrun) + .and_then(|f| { + let mut binja_path = String::new(); + let mut reader = BufReader::new(f); + + reader.read_line(&mut binja_path)?; + Ok(PathBuf::from(binja_path.trim())) + }) + .unwrap_or_else(|_| { + #[cfg(target_os = "macos")] + return PathBuf::from("/Applications/Binary Ninja.app/Contents/MacOS"); + + #[cfg(target_os = "linux")] + return home.join("binaryninja"); + + #[cfg(windows)] + return PathBuf::from(env::var("PROGRAMFILES").unwrap()) + .join("Vector35\\BinaryNinja\\"); + }) +} + +fn main() { + // Use BINARYNINJADIR first for custom BN builds/configurations (BN devs/build server), fallback on defaults + let install_path = env::var("BINARYNINJADIR") + .map(PathBuf::from) + .unwrap_or_else(|_| link_path()); + + #[cfg(target_os = "linux")] + println!( + "cargo:rustc-link-arg=-Wl,-rpath,{},-L{},-l:libbinaryninjacore.so.1", + install_path.to_str().unwrap(), + install_path.to_str().unwrap(), + ); + + #[cfg(target_os = "macos")] + println!( + "cargo:rustc-link-arg=-Wl,-rpath,{},-L{},-lbinaryninjacore", + install_path.to_str().unwrap(), + install_path.to_str().unwrap(), + ); + + #[cfg(target_os = "windows")] + { + println!("cargo:rustc-link-lib=binaryninjacore"); + println!("cargo:rustc-link-search={}", install_path.to_str().unwrap()); + } +} diff --git a/examples/template/src/main.rs b/examples/template/src/main.rs new file mode 100644 index 0000000..5b21efb --- /dev/null +++ b/examples/template/src/main.rs @@ -0,0 +1,42 @@ +use binaryninja::architecture::Architecture; +use binaryninja::binaryview::{BinaryViewBase, BinaryViewExt}; + +// Standalone executables need to provide a main function for rustc +// Plugins should refer to `binaryninja::command::*` for the various registration callbacks. +fn main() { + // This loads all the core architecture, platform, etc plugins + // Standalone executables probably need to call this, but plugins do not + println!("Loading plugins..."); + let headless_session = binaryninja::headless::Session::new(); + + // Your code here... + println!("Loading binary..."); + let bv = headless_session + .load("/bin/cat") + .expect("Couldn't open `/bin/cat`"); + + println!("Filename: `{}`", bv.file().filename()); + println!("File size: `{:#x}`", bv.len()); + println!("Function count: {}", bv.functions().len()); + + for func in &bv.functions() { + println!(" `{}`:", func.symbol().full_name()); + for basic_block in &func.basic_blocks() { + // TODO : This is intended to be refactored to be more nice to work with soon(TM) + for addr in basic_block.as_ref() { + print!(" {} ", addr); + if let Some((_, tokens)) = func.arch().instruction_text( + bv.read_buffer(addr, func.arch().max_instr_len()) + .unwrap() + .get_data(), + addr, + ) { + tokens + .iter() + .for_each(|token| print!("{}", token.text().as_str())); + println!(); + } + } + } + } +} diff --git a/source b/source new file mode 160000 index 0000000..3dd22f4 --- /dev/null +++ b/source @@ -0,0 +1 @@ +Subproject commit 3dd22f40996fc128ffce6026e8e747ca66bcc21d diff --git a/src/architecture.rs b/src/architecture.rs new file mode 100644 index 0000000..f5aed09 --- /dev/null +++ b/src/architecture.rs @@ -0,0 +1,2884 @@ +// Copyright 2021-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. + +//! Architectures provide disassembly, lifting, and associated metadata about a CPU to inform analysis and decompilation. + +// container abstraction to avoid Vec<> (want CoreArchFlagList, CoreArchRegList) +// RegisterInfo purge +use binaryninjacore_sys::*; + +use std::{ + borrow::{Borrow, Cow}, + collections::HashMap, + ffi::{c_char, c_int, CStr, CString}, + hash::Hash, + mem::zeroed, + ops, ptr, slice, +}; + +use crate::{ + callingconvention::CallingConvention, + databuffer::DataBuffer, + disassembly::InstructionTextToken, + llil::{ + get_default_flag_cond_llil, get_default_flag_write_llil, FlagWriteOp, LiftedExpr, Lifter, + }, + platform::Platform, + rc::*, + relocation::CoreRelocationHandler, + string::BnStrCompatible, + string::*, + types::{Conf, NameAndType, Type}, + {BranchType, Endianness}, +}; + +#[derive(Copy, Clone, PartialEq, Eq, Hash, Debug)] +pub enum BranchInfo { + Unconditional(u64), + False(u64), + True(u64), + Call(u64), + FunctionReturn, + SystemCall, + Indirect, + Exception, + Unresolved, + UserDefined, +} + +pub struct BranchIter<'a>(&'a InstructionInfo, ops::Range); +impl<'a> Iterator for BranchIter<'a> { + type Item = (BranchInfo, Option); + + fn next(&mut self) -> Option { + use crate::BranchType::*; + + match self.1.next() { + Some(i) => { + let target = (self.0).0.branchTarget[i]; + let arch = (self.0).0.branchArch[i]; + let arch = if arch.is_null() { + None + } else { + Some(CoreArchitecture(arch)) + }; + + let res = match (self.0).0.branchType[i] { + UnconditionalBranch => BranchInfo::Unconditional(target), + FalseBranch => BranchInfo::False(target), + TrueBranch => BranchInfo::True(target), + CallDestination => BranchInfo::Call(target), + FunctionReturn => BranchInfo::FunctionReturn, + SystemCall => BranchInfo::SystemCall, + IndirectBranch => BranchInfo::Indirect, + ExceptionBranch => BranchInfo::Exception, + UnresolvedBranch => BranchInfo::Unresolved, + UserDefinedBranch => BranchInfo::UserDefined, + }; + + Some((res, arch)) + } + _ => None, + } + } +} + +#[repr(C)] +pub struct InstructionInfo(BNInstructionInfo); +impl InstructionInfo { + pub fn new(len: usize, branch_delay: bool) -> Self { + InstructionInfo(BNInstructionInfo { + length: len, + archTransitionByTargetAddr: false, + branchDelay: branch_delay, + branchCount: 0usize, + branchType: [BranchType::UnresolvedBranch; 3], + branchTarget: [0u64; 3], + branchArch: [ptr::null_mut(); 3], + }) + } + + pub fn len(&self) -> usize { + self.0.length + } + + pub fn is_empty(&self) -> bool { + self.0.length == 0 + } + + pub fn branch_count(&self) -> usize { + self.0.branchCount + } + + pub fn branch_delay(&self) -> bool { + self.0.branchDelay + } + + pub fn branches(&self) -> BranchIter { + BranchIter(self, 0..self.branch_count()) + } + + pub fn allow_arch_transition_by_target_addr(&mut self, transition: bool) { + self.0.archTransitionByTargetAddr = transition; + } + + pub fn add_branch(&mut self, branch: BranchInfo, arch: Option) { + if self.0.branchCount < self.0.branchType.len() { + let idx = self.0.branchCount; + + let ty = match branch { + BranchInfo::Unconditional(t) => { + self.0.branchTarget[idx] = t; + BranchType::UnconditionalBranch + } + BranchInfo::False(t) => { + self.0.branchTarget[idx] = t; + BranchType::FalseBranch + } + BranchInfo::True(t) => { + self.0.branchTarget[idx] = t; + BranchType::TrueBranch + } + BranchInfo::Call(t) => { + self.0.branchTarget[idx] = t; + BranchType::CallDestination + } + BranchInfo::FunctionReturn => BranchType::FunctionReturn, + BranchInfo::SystemCall => BranchType::SystemCall, + BranchInfo::Indirect => BranchType::IndirectBranch, + BranchInfo::Exception => BranchType::ExceptionBranch, + BranchInfo::Unresolved => BranchType::UnresolvedBranch, + BranchInfo::UserDefined => BranchType::UserDefinedBranch, + }; + + self.0.branchType[idx] = ty; + self.0.branchArch[idx] = match arch { + Some(a) => a.0, + _ => ptr::null_mut(), + }; + + self.0.branchCount += 1; + } else { + error!("Attempt to branch to instruction with no additional branch space!"); + } + } +} + +use crate::functionrecognizer::FunctionRecognizer; +use crate::relocation::{CustomRelocationHandlerHandle, RelocationHandler}; +pub use binaryninjacore_sys::BNFlagRole as FlagRole; +pub use binaryninjacore_sys::BNImplicitRegisterExtend as ImplicitRegisterExtend; +pub use binaryninjacore_sys::BNLowLevelILFlagCondition as FlagCondition; + +pub trait RegisterInfo: Sized { + type RegType: Register; + + fn parent(&self) -> Option; + fn size(&self) -> usize; + fn offset(&self) -> usize; + fn implicit_extend(&self) -> ImplicitRegisterExtend; +} + +pub trait Register: Sized + Clone + Copy { + type InfoType: RegisterInfo; + + fn name(&self) -> Cow; + fn info(&self) -> Self::InfoType; + + /// Unique identifier for this `Register`. + /// + /// *MUST* be in the range [0, 0x7fff_ffff] + fn id(&self) -> u32; +} + +pub trait RegisterStackInfo: Sized { + type RegStackType: RegisterStack; + type RegType: Register; + type RegInfoType: RegisterInfo; + + fn storage_regs(&self) -> (Self::RegType, u32); + fn top_relative_regs(&self) -> Option<(Self::RegType, u32)>; + fn stack_top_reg(&self) -> Self::RegType; +} + +pub trait RegisterStack: Sized + Clone + Copy { + type InfoType: RegisterStackInfo< + RegType = Self::RegType, + RegInfoType = Self::RegInfoType, + RegStackType = Self, + >; + type RegType: Register; + type RegInfoType: RegisterInfo; + + fn name(&self) -> Cow; + fn info(&self) -> Self::InfoType; + + /// Unique identifier for this `RegisterStack`. + /// + /// *MUST* be in the range [0, 0x7fff_ffff] + fn id(&self) -> u32; +} + +pub trait Flag: Sized + Clone + Copy { + type FlagClass: FlagClass; + + fn name(&self) -> Cow; + fn role(&self, class: Option) -> FlagRole; + + /// Unique identifier for this `Flag`. + /// + /// *MUST* be in the range [0, 0x7fff_ffff] + fn id(&self) -> u32; +} + +pub trait FlagWrite: Sized + Clone + Copy { + type FlagType: Flag; + type FlagClass: FlagClass; + + fn name(&self) -> Cow; + fn class(&self) -> Option; + + /// Unique identifier for this `FlagWrite`. + /// + /// *MUST NOT* be 0. + /// *MUST* be in the range [1, 0x7fff_ffff] + fn id(&self) -> u32; + + fn flags_written(&self) -> Vec; +} + +pub trait FlagClass: Sized + Clone + Copy + Hash + Eq { + fn name(&self) -> Cow; + + /// Unique identifier for this `FlagClass`. + /// + /// *MUST NOT* be 0. + /// *MUST* be in the range [1, 0x7fff_ffff] + fn id(&self) -> u32; +} + +pub trait FlagGroup: Sized + Clone + Copy { + type FlagType: Flag; + type FlagClass: FlagClass; + + fn name(&self) -> Cow; + + /// Unique identifier for this `FlagGroup`. + /// + /// *MUST* be in the range [0, 0x7fff_ffff] + fn id(&self) -> u32; + + /// Returns the list of flags that need to be resolved in order + /// to take the clean flag resolution path -- at time of writing, + /// all required flags must have been set by the same instruction, + /// and the 'querying' instruction must be reachable from *one* + /// instruction that sets all of these flags. + fn flags_required(&self) -> Vec; + + /// Returns the mapping of Semantic Flag Classes to Flag Conditions, + /// in the context of this Flag Group. + /// + /// Example: + /// + /// If we have a group representing `cr1_lt` (as in PowerPC), we would + /// have multiple Semantic Flag Classes used by the different Flag Write + /// Types to represent the different comparisons, so for `cr1_lt` we + /// would return a mapping along the lines of: + /// + /// ``` + /// cr1_signed -> LLFC_SLT, + /// cr1_unsigned -> LLFC_ULT, + /// ``` + /// + /// This allows the core to recover the semantics of the comparison and + /// inline it into conditional branches when appropriate. + fn flag_conditions(&self) -> HashMap; +} + +pub trait Intrinsic: Sized + Clone + Copy { + fn name(&self) -> Cow; + + /// Unique identifier for this `Intrinsic`. + fn id(&self) -> u32; + + /// Reeturns the list of the input names and types for this intrinsic. + fn inputs(&self) -> Vec>; + + /// Returns the list of the output types for this intrinsic. + fn outputs(&self) -> Vec>>; +} + +pub trait Architecture: 'static + Sized + AsRef { + type Handle: Borrow + Clone; + + type RegisterInfo: RegisterInfo; + type Register: Register; + type RegisterStackInfo: RegisterStackInfo< + RegType = Self::Register, + RegInfoType = Self::RegisterInfo, + RegStackType = Self::RegisterStack, + >; + type RegisterStack: RegisterStack< + InfoType = Self::RegisterStackInfo, + RegType = Self::Register, + RegInfoType = Self::RegisterInfo, + >; + + type Flag: Flag; + type FlagWrite: FlagWrite; + type FlagClass: FlagClass; + type FlagGroup: FlagGroup; + + type Intrinsic: Intrinsic; + + fn endianness(&self) -> Endianness; + fn address_size(&self) -> usize; + fn default_integer_size(&self) -> usize; + fn instruction_alignment(&self) -> usize; + fn max_instr_len(&self) -> usize; + fn opcode_display_len(&self) -> usize; + + fn associated_arch_by_addr(&self, addr: &mut u64) -> CoreArchitecture; + + fn instruction_info(&self, data: &[u8], addr: u64) -> Option; + fn instruction_text( + &self, + data: &[u8], + addr: u64, + ) -> Option<(usize, Vec)>; + fn instruction_llil( + &self, + data: &[u8], + addr: u64, + il: &mut Lifter, + ) -> Option<(usize, bool)>; + + /// Fallback flag value calculation path. This method is invoked when the core is unable to + /// recover flag use semantics, and resorts to emitting instructions that explicitly set each + /// observed flag to the value of an expression returned by this function. + /// + /// This function *MUST NOT* append instructions that have side effects. + /// + /// This function *MUST NOT* observe the values of other flags. + /// + /// This function *MUST* return `None` or an expression representing a boolean value. + fn flag_write_llil<'a>( + &self, + flag: Self::Flag, + flag_write_type: Self::FlagWrite, + op: FlagWriteOp, + il: &'a mut Lifter, + ) -> Option> { + let role = flag.role(flag_write_type.class()); + Some(get_default_flag_write_llil(self, role, op, il)) + } + + /// Determines what flags need to be examined in order to attempt automatic recovery of the + /// semantics of this flag use. + /// + /// If automatic recovery is not possible, the `flag_cond_llil` method will be invoked to give + /// this `Architecture` implementation arbitrary control over the expression to be evaluated. + fn flags_required_for_flag_condition( + &self, + _condition: FlagCondition, + _class: Option, + ) -> Vec { + Vec::new() + } + + /// This function *MUST NOT* append instructions that have side effects. + /// + /// This function *MUST NOT* observe the values of flags not returned by + /// `flags_required_for_flag_condition`. + /// + /// This function *MUST* return `None` or an expression representing a boolean value. + fn flag_cond_llil<'a>( + &self, + cond: FlagCondition, + class: Option, + il: &'a mut Lifter, + ) -> Option> { + Some(get_default_flag_cond_llil(self, cond, class, il)) + } + + /// Performs fallback resolution when the core was unable to recover the semantics of a + /// `LLIL_FLAG_GROUP` expression. This occurs when multiple instructions may have set the flags + /// at the flag group query, or when the `FlagGroup::flag_conditions()` map doesn't have an entry + /// for the `FlagClass` associated with the `FlagWrite` type of the expression that last set + /// the flags required by the `FlagGroup` `group`. + /// + /// In this fallback path, the `Architecture` must generate the boolean expression in terms of + /// the values of that flags returned by `group`'s `flags_required` method. + /// + /// This function must return an expression representing a boolean (as in, size of `0`) value. + /// It is not allowed to add any instructions that can cause side effects. + /// + /// This function must not observe the values of any flag not returned by `group`'s + /// `flags_required` method. + fn flag_group_llil<'a>( + &self, + _group: Self::FlagGroup, + _il: &'a mut Lifter, + ) -> Option> { + None + } + + fn registers_all(&self) -> Vec; + fn registers_full_width(&self) -> Vec; + fn registers_global(&self) -> Vec { + Vec::new() + } + fn registers_system(&self) -> Vec { + Vec::new() + } + + fn register_stacks(&self) -> Vec { + Vec::new() + } + + fn flags(&self) -> Vec { + Vec::new() + } + fn flag_write_types(&self) -> Vec { + Vec::new() + } + fn flag_classes(&self) -> Vec { + Vec::new() + } + fn flag_groups(&self) -> Vec { + Vec::new() + } + + fn stack_pointer_reg(&self) -> Option; + fn link_reg(&self) -> Option { + None + } + + fn register_from_id(&self, id: u32) -> Option; + + fn register_stack_from_id(&self, _id: u32) -> Option { + None + } + + fn flag_from_id(&self, _id: u32) -> Option { + None + } + fn flag_write_from_id(&self, _id: u32) -> Option { + None + } + fn flag_class_from_id(&self, _id: u32) -> Option { + None + } + fn flag_group_from_id(&self, _id: u32) -> Option { + None + } + + fn intrinsics(&self) -> Vec { + Vec::new() + } + fn intrinsic_from_id(&self, _id: u32) -> Option { + None + } + + fn can_assemble(&self) -> bool { + false + } + fn assemble(&self, _code: &str, _addr: u64) -> Result, String> { + Err("Assemble unsupported".into()) + } + + fn is_never_branch_patch_available(&self, _data: &[u8], _addr: u64) -> bool { + false + } + fn is_always_branch_patch_available(&self, _data: &[u8], _addr: u64) -> bool { + false + } + fn is_invert_branch_patch_available(&self, _data: &[u8], _addr: u64) -> bool { + false + } + fn is_skip_and_return_zero_patch_available(&self, _data: &[u8], _addr: u64) -> bool { + false + } + fn is_skip_and_return_value_patch_available(&self, _data: &[u8], _addr: u64) -> bool { + false + } + + fn convert_to_nop(&self, _data: &mut [u8], _addr: u64) -> bool { + false + } + + fn always_branch(&self, _data: &mut [u8], _addr: u64) -> bool { + false + } + + fn invert_branch(&self, _data: &mut [u8], _addr: u64) -> bool { + false + } + + fn skip_and_return_value(&self, _data: &mut [u8], _addr: u64, _value: u64) -> bool { + false + } + + fn handle(&self) -> Self::Handle; +} + +/// Type for architrectures that do not use register stacks. Will panic if accessed as a register stack. +#[derive(Clone, Copy, PartialEq, Eq, Hash)] +pub struct UnusedRegisterStackInfo { + _reg: std::marker::PhantomData, +} + +#[derive(Clone, Copy, PartialEq, Eq, Hash)] +pub struct UnusedRegisterStack { + _reg: std::marker::PhantomData, +} + +impl RegisterStackInfo for UnusedRegisterStackInfo { + type RegStackType = UnusedRegisterStack; + type RegType = R; + type RegInfoType = R::InfoType; + + fn storage_regs(&self) -> (Self::RegType, u32) { + unreachable!() + } + fn top_relative_regs(&self) -> Option<(Self::RegType, u32)> { + unreachable!() + } + fn stack_top_reg(&self) -> Self::RegType { + unreachable!() + } +} + +impl RegisterStack for UnusedRegisterStack { + type InfoType = UnusedRegisterStackInfo; + type RegType = R; + type RegInfoType = R::InfoType; + + fn name(&self) -> Cow { + unreachable!() + } + fn id(&self) -> u32 { + unreachable!() + } + fn info(&self) -> Self::InfoType { + unreachable!() + } +} + +/// Type for architrectures that do not use flags. Will panic if accessed as a flag. +#[derive(Clone, Copy, PartialEq, Eq, Hash)] +pub struct UnusedFlag; + +impl Flag for UnusedFlag { + type FlagClass = Self; + fn name(&self) -> Cow { + unreachable!() + } + fn role(&self, _class: Option) -> FlagRole { + unreachable!() + } + fn id(&self) -> u32 { + unreachable!() + } +} + +impl FlagWrite for UnusedFlag { + type FlagType = Self; + type FlagClass = Self; + fn name(&self) -> Cow { + unreachable!() + } + fn class(&self) -> Option { + unreachable!() + } + fn id(&self) -> u32 { + unreachable!() + } + fn flags_written(&self) -> Vec { + unreachable!() + } +} + +impl FlagClass for UnusedFlag { + fn name(&self) -> Cow { + unreachable!() + } + fn id(&self) -> u32 { + unreachable!() + } +} + +impl FlagGroup for UnusedFlag { + type FlagType = Self; + type FlagClass = Self; + fn name(&self) -> Cow { + unreachable!() + } + fn id(&self) -> u32 { + unreachable!() + } + fn flags_required(&self) -> Vec { + unreachable!() + } + fn flag_conditions(&self) -> HashMap { + unreachable!() + } +} + +/// Type for architrectures that do not use intrinsics. Will panic if accessed as an intrinsic. +#[derive(Clone, Copy, PartialEq, Eq, Hash)] +pub struct UnusedIntrinsic; + +impl Intrinsic for UnusedIntrinsic { + fn name(&self) -> Cow { + unreachable!() + } + fn id(&self) -> u32 { + unreachable!() + } + fn inputs(&self) -> Vec> { + unreachable!() + } + fn outputs(&self) -> Vec>> { + unreachable!() + } +} + +pub struct CoreRegisterInfo(*mut BNArchitecture, u32, BNRegisterInfo); +impl RegisterInfo for CoreRegisterInfo { + type RegType = CoreRegister; + + fn parent(&self) -> Option { + if self.1 != self.2.fullWidthRegister { + Some(CoreRegister(self.0, self.2.fullWidthRegister)) + } else { + None + } + } + + fn size(&self) -> usize { + self.2.size + } + + fn offset(&self) -> usize { + self.2.offset + } + + fn implicit_extend(&self) -> ImplicitRegisterExtend { + self.2.extend + } +} + +#[derive(Copy, Clone, Eq, PartialEq, Hash)] +pub struct CoreRegister(*mut BNArchitecture, u32); +impl Register for CoreRegister { + type InfoType = CoreRegisterInfo; + + fn name(&self) -> Cow { + unsafe { + let name = BNGetArchitectureRegisterName(self.0, self.1); + + // We need to guarantee ownership, as if we're still + // a Borrowed variant we're about to free the underlying + // memory. + let res = CStr::from_ptr(name); + let res = res.to_string_lossy().into_owned().into(); + + BNFreeString(name); + + res + } + } + + fn info(&self) -> CoreRegisterInfo { + CoreRegisterInfo(self.0, self.1, unsafe { + BNGetArchitectureRegisterInfo(self.0, self.1) + }) + } + + fn id(&self) -> u32 { + self.1 + } +} + +pub struct CoreRegisterStackInfo(*mut BNArchitecture, BNRegisterStackInfo); + +impl RegisterStackInfo for CoreRegisterStackInfo { + type RegStackType = CoreRegisterStack; + type RegType = CoreRegister; + type RegInfoType = CoreRegisterInfo; + + fn storage_regs(&self) -> (Self::RegType, u32) { + ( + CoreRegister(self.0, self.1.firstStorageReg), + self.1.storageCount, + ) + } + + fn top_relative_regs(&self) -> Option<(Self::RegType, u32)> { + if self.1.topRelativeCount == 0 { + None + } else { + Some(( + CoreRegister(self.0, self.1.firstTopRelativeReg), + self.1.topRelativeCount, + )) + } + } + + fn stack_top_reg(&self) -> Self::RegType { + CoreRegister(self.0, self.1.stackTopReg) + } +} + +#[derive(Copy, Clone, Eq, PartialEq, Hash)] +pub struct CoreRegisterStack(*mut BNArchitecture, u32); + +impl RegisterStack for CoreRegisterStack { + type InfoType = CoreRegisterStackInfo; + type RegType = CoreRegister; + type RegInfoType = CoreRegisterInfo; + + fn name(&self) -> Cow { + unsafe { + let name = BNGetArchitectureRegisterStackName(self.0, self.1); + + // We need to guarantee ownership, as if we're still + // a Borrowed variant we're about to free the underlying + // memory. + let res = CStr::from_ptr(name); + let res = res.to_string_lossy().into_owned().into(); + + BNFreeString(name); + + res + } + } + + fn info(&self) -> CoreRegisterStackInfo { + CoreRegisterStackInfo(self.0, unsafe { + BNGetArchitectureRegisterStackInfo(self.0, self.1) + }) + } + + fn id(&self) -> u32 { + self.1 + } +} + +#[derive(Copy, Clone, Eq, PartialEq, Hash)] +pub struct CoreFlag(*mut BNArchitecture, u32); +impl Flag for CoreFlag { + type FlagClass = CoreFlagClass; + + fn name(&self) -> Cow { + unsafe { + let name = BNGetArchitectureFlagName(self.0, self.1); + + // We need to guarantee ownership, as if we're still + // a Borrowed variant we're about to free the underlying + // memory. + let res = CStr::from_ptr(name); + let res = res.to_string_lossy().into_owned().into(); + + BNFreeString(name); + + res + } + } + + fn role(&self, class: Option) -> FlagRole { + let class_id = match class { + Some(class) => class.1, + _ => 0, + }; + + unsafe { BNGetArchitectureFlagRole(self.0, self.1, class_id) } + } + + fn id(&self) -> u32 { + self.1 + } +} + +#[derive(Copy, Clone, Eq, PartialEq, Hash)] +pub struct CoreFlagWrite(*mut BNArchitecture, u32); +impl FlagWrite for CoreFlagWrite { + type FlagType = CoreFlag; + type FlagClass = CoreFlagClass; + + fn name(&self) -> Cow { + unsafe { + let name = BNGetArchitectureFlagWriteTypeName(self.0, self.1); + + // We need to guarantee ownership, as if we're still + // a Borrowed variant we're about to free the underlying + // memory. + let res = CStr::from_ptr(name); + let res = res.to_string_lossy().into_owned().into(); + + BNFreeString(name); + + res + } + } + + fn id(&self) -> u32 { + self.1 + } + + fn flags_written(&self) -> Vec { + let mut count: usize = 0; + let regs: *mut u32 = unsafe { + BNGetArchitectureFlagsWrittenByFlagWriteType(self.0, self.1, &mut count as *mut _) + }; + + let ret = unsafe { + slice::from_raw_parts_mut(regs, count) + .iter() + .map(|reg| CoreFlag(self.0, *reg)) + .collect() + }; + + unsafe { + BNFreeRegisterList(regs); + } + + ret + } + + fn class(&self) -> Option { + let class = unsafe { BNGetArchitectureSemanticClassForFlagWriteType(self.0, self.1) }; + + match class { + 0 => None, + id => Some(CoreFlagClass(self.0, id)), + } + } +} + +#[derive(Copy, Clone, Eq, PartialEq, Hash)] +pub struct CoreFlagClass(*mut BNArchitecture, u32); +impl FlagClass for CoreFlagClass { + fn name(&self) -> Cow { + unsafe { + let name = BNGetArchitectureSemanticFlagClassName(self.0, self.1); + + // We need to guarantee ownership, as if we're still + // a Borrowed variant we're about to free the underlying + // memory. + let res = CStr::from_ptr(name); + let res = res.to_string_lossy().into_owned().into(); + + BNFreeString(name); + + res + } + } + + fn id(&self) -> u32 { + self.1 + } +} + +#[derive(Copy, Clone, Eq, PartialEq)] +pub struct CoreFlagGroup(*mut BNArchitecture, u32); +impl FlagGroup for CoreFlagGroup { + type FlagType = CoreFlag; + type FlagClass = CoreFlagClass; + + fn name(&self) -> Cow { + unsafe { + let name = BNGetArchitectureSemanticFlagGroupName(self.0, self.1); + + // We need to guarantee ownership, as if we're still + // a Borrowed variant we're about to free the underlying + // memory. + let res = CStr::from_ptr(name); + let res = res.to_string_lossy().into_owned().into(); + + BNFreeString(name); + + res + } + } + + fn id(&self) -> u32 { + self.1 + } + + fn flags_required(&self) -> Vec { + let mut count: usize = 0; + let regs: *mut u32 = unsafe { + BNGetArchitectureFlagsRequiredForSemanticFlagGroup(self.0, self.1, &mut count as *mut _) + }; + + let ret = unsafe { + slice::from_raw_parts_mut(regs, count) + .iter() + .map(|reg| CoreFlag(self.0, *reg)) + .collect() + }; + + unsafe { + BNFreeRegisterList(regs); + } + + ret + } + + fn flag_conditions(&self) -> HashMap { + let mut count: usize = 0; + + unsafe { + let flag_conds = BNGetArchitectureFlagConditionsForSemanticFlagGroup( + self.0, + self.1, + &mut count as *mut _, + ); + + let ret = slice::from_raw_parts_mut(flag_conds, count) + .iter() + .map(|class_cond| { + ( + CoreFlagClass(self.0, class_cond.semanticClass), + class_cond.condition, + ) + }) + .collect(); + + BNFreeFlagConditionsForSemanticFlagGroup(flag_conds); + + ret + } + } +} + +#[derive(Copy, Clone, Eq, PartialEq)] +pub struct CoreIntrinsic(*mut BNArchitecture, u32); + +impl Intrinsic for crate::architecture::CoreIntrinsic { + fn name(&self) -> Cow { + unsafe { + let name = BNGetArchitectureIntrinsicName(self.0, self.1); + + // We need to guarantee ownership, as if we're still + // a Borrowed variant we're about to free the underlying + // memory. + let res = CStr::from_ptr(name); + let res = res.to_string_lossy().into_owned().into(); + + BNFreeString(name); + + res + } + } + + fn id(&self) -> u32 { + self.1 + } + + fn inputs(&self) -> Vec> { + let mut count: usize = 0; + + unsafe { + let inputs = BNGetArchitectureIntrinsicInputs(self.0, self.1, &mut count as *mut _); + + let ret = slice::from_raw_parts_mut(inputs, count) + .iter() + .map(|input| NameAndType::from_raw(input)) + .collect(); + + BNFreeNameAndTypeList(inputs, count); + + ret + } + } + + fn outputs(&self) -> Vec>> { + let mut count: usize = 0; + + unsafe { + let inputs = BNGetArchitectureIntrinsicOutputs(self.0, self.1, &mut count as *mut _); + + let ret = slice::from_raw_parts_mut(inputs, count) + .iter() + .map(|input| (*input).into()) + .collect(); + + BNFreeOutputTypeList(inputs, count); + + ret + } + } +} + +pub struct CoreArchitectureList(*mut *mut BNArchitecture, usize); +impl ops::Deref for CoreArchitectureList { + type Target = [CoreArchitecture]; + + fn deref(&self) -> &Self::Target { + unsafe { slice::from_raw_parts_mut(self.0 as *mut CoreArchitecture, self.1) } + } +} + +impl Drop for CoreArchitectureList { + fn drop(&mut self) { + unsafe { + BNFreeArchitectureList(self.0); + } + } +} + +#[derive(Copy, Clone, Eq, PartialEq, Hash, Debug)] +pub struct CoreArchitecture(pub(crate) *mut BNArchitecture); + +unsafe impl Send for CoreArchitecture {} +unsafe impl Sync for CoreArchitecture {} + +impl CoreArchitecture { + pub(crate) unsafe fn from_raw(raw: *mut BNArchitecture) -> Self { + CoreArchitecture(raw) + } + + pub fn list_all() -> CoreArchitectureList { + let mut count: usize = 0; + let archs = unsafe { BNGetArchitectureList(&mut count as *mut _) }; + + CoreArchitectureList(archs, count) + } + + pub fn by_name(name: &str) -> Option { + let res = unsafe { BNGetArchitectureByName(name.into_bytes_with_nul().as_ptr() as *mut _) }; + + match res.is_null() { + false => Some(CoreArchitecture(res)), + true => None, + } + } + + pub fn name(&self) -> BnString { + unsafe { BnString::from_raw(BNGetArchitectureName(self.0)) } + } +} + +impl AsRef for CoreArchitecture { + fn as_ref(&self) -> &Self { + self + } +} + +impl Architecture for CoreArchitecture { + type Handle = Self; + + type RegisterInfo = CoreRegisterInfo; + type Register = CoreRegister; + type RegisterStackInfo = CoreRegisterStackInfo; + type RegisterStack = CoreRegisterStack; + type Flag = CoreFlag; + type FlagWrite = CoreFlagWrite; + type FlagClass = CoreFlagClass; + type FlagGroup = CoreFlagGroup; + type Intrinsic = CoreIntrinsic; + + fn endianness(&self) -> Endianness { + unsafe { BNGetArchitectureEndianness(self.0) } + } + + fn address_size(&self) -> usize { + unsafe { BNGetArchitectureAddressSize(self.0) } + } + + fn default_integer_size(&self) -> usize { + unsafe { BNGetArchitectureDefaultIntegerSize(self.0) } + } + + fn instruction_alignment(&self) -> usize { + unsafe { BNGetArchitectureInstructionAlignment(self.0) } + } + + fn max_instr_len(&self) -> usize { + unsafe { BNGetArchitectureMaxInstructionLength(self.0) } + } + + fn opcode_display_len(&self) -> usize { + unsafe { BNGetArchitectureOpcodeDisplayLength(self.0) } + } + + fn associated_arch_by_addr(&self, addr: &mut u64) -> CoreArchitecture { + let arch = unsafe { BNGetAssociatedArchitectureByAddress(self.0, addr as *mut _) }; + + CoreArchitecture(arch) + } + + fn instruction_info(&self, data: &[u8], addr: u64) -> Option { + let mut info = unsafe { zeroed::() }; + let success = unsafe { + BNGetInstructionInfo( + self.0, + data.as_ptr(), + addr, + data.len(), + &mut (info.0) as *mut _, + ) + }; + + if success { + Some(info) + } else { + None + } + } + + fn instruction_text( + &self, + data: &[u8], + addr: u64, + ) -> Option<(usize, Vec)> { + let mut consumed = data.len(); + let mut count: usize = 0; + let mut result: *mut BNInstructionTextToken = ptr::null_mut(); + + unsafe { + if BNGetInstructionText( + self.0, + data.as_ptr(), + addr, + &mut consumed as *mut _, + &mut result as *mut _, + &mut count as *mut _, + ) { + let vec = Vec::::from_raw_parts(result, count, count) + .iter() + .map(|x| InstructionTextToken::from_raw(x)) + .collect(); + Some((consumed, vec)) + } else { + None + } + } + } + + fn instruction_llil( + &self, + _data: &[u8], + _addr: u64, + _il: &mut Lifter, + ) -> Option<(usize, bool)> { + None + } + + fn flag_write_llil<'a>( + &self, + _flag: Self::Flag, + _flag_write: Self::FlagWrite, + _op: FlagWriteOp, + _il: &'a mut Lifter, + ) -> Option> { + None + } + + fn flag_cond_llil<'a>( + &self, + _cond: FlagCondition, + _class: Option, + _il: &'a mut Lifter, + ) -> Option> { + None + } + + fn flag_group_llil<'a>( + &self, + _group: Self::FlagGroup, + _il: &'a mut Lifter, + ) -> Option> { + None + } + + fn registers_all(&self) -> Vec { + unsafe { + let mut count: usize = 0; + let regs = BNGetAllArchitectureRegisters(self.0, &mut count as *mut _); + + let ret = slice::from_raw_parts_mut(regs, count) + .iter() + .map(|reg| CoreRegister(self.0, *reg)) + .collect(); + + BNFreeRegisterList(regs); + + ret + } + } + + fn registers_full_width(&self) -> Vec { + unsafe { + let mut count: usize = 0; + let regs = BNGetFullWidthArchitectureRegisters(self.0, &mut count as *mut _); + + let ret = slice::from_raw_parts_mut(regs, count) + .iter() + .map(|reg| CoreRegister(self.0, *reg)) + .collect(); + + BNFreeRegisterList(regs); + + ret + } + } + + fn registers_global(&self) -> Vec { + unsafe { + let mut count: usize = 0; + let regs = BNGetArchitectureGlobalRegisters(self.0, &mut count as *mut _); + + let ret = slice::from_raw_parts_mut(regs, count) + .iter() + .map(|reg| CoreRegister(self.0, *reg)) + .collect(); + + BNFreeRegisterList(regs); + + ret + } + } + + fn registers_system(&self) -> Vec { + unsafe { + let mut count: usize = 0; + let regs = BNGetArchitectureSystemRegisters(self.0, &mut count as *mut _); + + let ret = slice::from_raw_parts_mut(regs, count) + .iter() + .map(|reg| CoreRegister(self.0, *reg)) + .collect(); + + BNFreeRegisterList(regs); + + ret + } + } + + fn register_stacks(&self) -> Vec { + unsafe { + let mut count: usize = 0; + let regs = BNGetAllArchitectureRegisterStacks(self.0, &mut count as *mut _); + + let ret = slice::from_raw_parts_mut(regs, count) + .iter() + .map(|reg| CoreRegisterStack(self.0, *reg)) + .collect(); + + BNFreeRegisterList(regs); + + ret + } + } + + fn flags(&self) -> Vec { + unsafe { + let mut count: usize = 0; + let regs = BNGetAllArchitectureFlags(self.0, &mut count as *mut _); + + let ret = slice::from_raw_parts_mut(regs, count) + .iter() + .map(|reg| CoreFlag(self.0, *reg)) + .collect(); + + BNFreeRegisterList(regs); + + ret + } + } + + fn flag_write_types(&self) -> Vec { + unsafe { + let mut count: usize = 0; + let regs = BNGetAllArchitectureFlagWriteTypes(self.0, &mut count as *mut _); + + let ret = slice::from_raw_parts_mut(regs, count) + .iter() + .map(|reg| CoreFlagWrite(self.0, *reg)) + .collect(); + + BNFreeRegisterList(regs); + + ret + } + } + + fn flag_classes(&self) -> Vec { + unsafe { + let mut count: usize = 0; + let regs = BNGetAllArchitectureSemanticFlagClasses(self.0, &mut count as *mut _); + + let ret = slice::from_raw_parts_mut(regs, count) + .iter() + .map(|reg| CoreFlagClass(self.0, *reg)) + .collect(); + + BNFreeRegisterList(regs); + + ret + } + } + + fn flag_groups(&self) -> Vec { + unsafe { + let mut count: usize = 0; + let regs = BNGetAllArchitectureSemanticFlagGroups(self.0, &mut count as *mut _); + + let ret = slice::from_raw_parts_mut(regs, count) + .iter() + .map(|reg| CoreFlagGroup(self.0, *reg)) + .collect(); + + BNFreeRegisterList(regs); + + ret + } + } + + fn flags_required_for_flag_condition( + &self, + condition: FlagCondition, + class: Option, + ) -> Vec { + let class_id = class.map(|c| c.id()).unwrap_or(0); + + unsafe { + let mut count: usize = 0; + let flags = BNGetArchitectureFlagsRequiredForFlagCondition( + self.0, + condition, + class_id, + &mut count as *mut _, + ); + + let ret = slice::from_raw_parts_mut(flags, count) + .iter() + .map(|flag| CoreFlag(self.0, *flag)) + .collect(); + + BNFreeRegisterList(flags); + + ret + } + } + + fn stack_pointer_reg(&self) -> Option { + match unsafe { BNGetArchitectureStackPointerRegister(self.0) } { + 0xffff_ffff => None, + reg => Some(CoreRegister(self.0, reg)), + } + } + + fn link_reg(&self) -> Option { + match unsafe { BNGetArchitectureLinkRegister(self.0) } { + 0xffff_ffff => None, + reg => Some(CoreRegister(self.0, reg)), + } + } + + fn register_from_id(&self, id: u32) -> Option { + // TODO validate in debug builds + Some(CoreRegister(self.0, id)) + } + + fn register_stack_from_id(&self, id: u32) -> Option { + // TODO validate in debug builds + Some(CoreRegisterStack(self.0, id)) + } + + fn flag_from_id(&self, id: u32) -> Option { + // TODO validate in debug builds + Some(CoreFlag(self.0, id)) + } + + fn flag_write_from_id(&self, id: u32) -> Option { + // TODO validate in debug builds + Some(CoreFlagWrite(self.0, id)) + } + + fn flag_class_from_id(&self, id: u32) -> Option { + // TODO validate in debug builds + Some(CoreFlagClass(self.0, id)) + } + + fn flag_group_from_id(&self, id: u32) -> Option { + // TODO validate in debug builds + Some(CoreFlagGroup(self.0, id)) + } + + fn intrinsics(&self) -> Vec { + unsafe { + let mut count: usize = 0; + let intrinsics = BNGetAllArchitectureIntrinsics(self.0, &mut count as *mut _); + + let ret = slice::from_raw_parts_mut(intrinsics, count) + .iter() + .map(|reg| CoreIntrinsic(self.0, *reg)) + .collect(); + + BNFreeRegisterList(intrinsics); + + ret + } + } + + fn intrinsic_from_id(&self, id: u32) -> Option { + // TODO validate in debug builds + Some(CoreIntrinsic(self.0, id)) + } + + fn can_assemble(&self) -> bool { + unsafe { BNCanArchitectureAssemble(self.0) } + } + + fn assemble(&self, code: &str, addr: u64) -> Result, String> { + let code = CString::new(code).map_err(|_| "Invalid encoding in code string".to_string())?; + + let result = match DataBuffer::new(&[]) { + Ok(result) => result, + Err(_) => return Err("Result buffer allocation failed".to_string()), + }; + let mut error_raw: *mut c_char = ptr::null_mut(); + let res = unsafe { + BNAssemble( + self.0, + code.as_ptr(), + addr, + result.as_raw(), + &mut error_raw as *mut *mut c_char, + ) + }; + + let error = raw_to_string(error_raw); + unsafe { + BNFreeString(error_raw); + } + + if res { + Ok(result.get_data().to_vec()) + } else { + Err(error.unwrap_or_else(|| "Assemble failed".into())) + } + } + + fn is_never_branch_patch_available(&self, data: &[u8], addr: u64) -> bool { + unsafe { + BNIsArchitectureNeverBranchPatchAvailable(self.0, data.as_ptr(), addr, data.len()) + } + } + + fn is_always_branch_patch_available(&self, data: &[u8], addr: u64) -> bool { + unsafe { + BNIsArchitectureAlwaysBranchPatchAvailable(self.0, data.as_ptr(), addr, data.len()) + } + } + + fn is_invert_branch_patch_available(&self, data: &[u8], addr: u64) -> bool { + unsafe { + BNIsArchitectureInvertBranchPatchAvailable(self.0, data.as_ptr(), addr, data.len()) + } + } + + fn is_skip_and_return_zero_patch_available(&self, data: &[u8], addr: u64) -> bool { + unsafe { + BNIsArchitectureSkipAndReturnZeroPatchAvailable(self.0, data.as_ptr(), addr, data.len()) + } + } + + fn is_skip_and_return_value_patch_available(&self, data: &[u8], addr: u64) -> bool { + unsafe { + BNIsArchitectureSkipAndReturnValuePatchAvailable( + self.0, + data.as_ptr(), + addr, + data.len(), + ) + } + } + + fn convert_to_nop(&self, data: &mut [u8], addr: u64) -> bool { + unsafe { BNArchitectureConvertToNop(self.0, data.as_mut_ptr(), addr, data.len()) } + } + + fn always_branch(&self, data: &mut [u8], addr: u64) -> bool { + unsafe { BNArchitectureAlwaysBranch(self.0, data.as_mut_ptr(), addr, data.len()) } + } + + fn invert_branch(&self, data: &mut [u8], addr: u64) -> bool { + unsafe { BNArchitectureInvertBranch(self.0, data.as_mut_ptr(), addr, data.len()) } + } + + fn skip_and_return_value(&self, data: &mut [u8], addr: u64, value: u64) -> bool { + unsafe { + BNArchitectureSkipAndReturnValue(self.0, data.as_mut_ptr(), addr, data.len(), value) + } + } + + fn handle(&self) -> CoreArchitecture { + *self + } +} + +macro_rules! cc_func { + ($get_name:ident, $get_api:ident, $set_name:ident, $set_api:ident) => { + fn $get_name(&self) -> Option>> { + let handle = self.as_ref(); + + unsafe { + let cc = $get_api(handle.0); + + if cc.is_null() { + None + } else { + Some(CallingConvention::ref_from_raw(cc, self.handle())) + } + } + } + + fn $set_name(&self, cc: &CallingConvention) { + let handle = self.as_ref(); + + assert!( + cc.arch_handle.borrow().as_ref().0 == handle.0, + "use of calling convention with non-matching architecture!" + ); + + unsafe { + $set_api(handle.0, cc.handle); + } + } + }; +} + +/// Contains helper methods for all types implementing 'Architecture' +pub trait ArchitectureExt: Architecture { + fn register_by_name(&self, name: S) -> Option { + let name = name.into_bytes_with_nul(); + + match unsafe { + BNGetArchitectureRegisterByName(self.as_ref().0, name.as_ref().as_ptr() as *mut _) + } { + 0xffff_ffff => None, + reg => self.register_from_id(reg), + } + } + + cc_func!( + get_default_calling_convention, + BNGetArchitectureDefaultCallingConvention, + set_default_calling_convention, + BNSetArchitectureDefaultCallingConvention + ); + + cc_func!( + get_cdecl_calling_convention, + BNGetArchitectureCdeclCallingConvention, + set_cdecl_calling_convention, + BNSetArchitectureCdeclCallingConvention + ); + + cc_func!( + get_stdcall_calling_convention, + BNGetArchitectureStdcallCallingConvention, + set_stdcall_calling_convention, + BNSetArchitectureStdcallCallingConvention + ); + + cc_func!( + get_fastcall_calling_convention, + BNGetArchitectureFastcallCallingConvention, + set_fastcall_calling_convention, + BNSetArchitectureFastcallCallingConvention + ); + + fn standalone_platform(&self) -> Option> { + unsafe { + let handle = BNGetArchitectureStandalonePlatform(self.as_ref().0); + + if handle.is_null() { + return None; + } + + Some(Platform::ref_from_raw(handle)) + } + } + + fn relocation_handler(&self, view_name: &str) -> Option> { + let view_name = match CString::new(view_name) { + Ok(view_name) => view_name, + Err(_) => return None, + }; + + unsafe { + let handle = BNArchitectureGetRelocationHandler(self.as_ref().0, view_name.as_ptr()); + + if handle.is_null() { + return None; + } + + Some(CoreRelocationHandler::ref_from_raw(handle)) + } + } + + fn register_relocation_handler(&self, name: S, func: F) + where + S: BnStrCompatible, + R: 'static + + RelocationHandler> + + Send + + Sync + + Sized, + F: FnOnce(CustomRelocationHandlerHandle, CoreRelocationHandler) -> R, + { + crate::relocation::register_relocation_handler(self.as_ref(), name, func); + } + + fn register_function_recognizer(&self, recognizer: R) + where + R: 'static + FunctionRecognizer + Send + Sync + Sized, + { + crate::functionrecognizer::register_arch_function_recognizer(self.as_ref(), recognizer); + } +} + +impl ArchitectureExt for T {} + +pub fn register_architecture(name: S, func: F) -> &'static A +where + S: BnStrCompatible, + A: 'static + Architecture> + Send + Sync + Sized, + F: FnOnce(CustomArchitectureHandle, CoreArchitecture) -> A, +{ + use std::mem; + use std::os::raw::{c_char, c_void}; + + #[repr(C)] + struct ArchitectureBuilder + where + A: 'static + Architecture> + Send + Sync, + F: FnOnce(CustomArchitectureHandle, CoreArchitecture) -> A, + { + arch: A, + func: F, + } + + extern "C" fn cb_init(ctxt: *mut c_void, obj: *mut BNArchitecture) + where + A: 'static + Architecture> + Send + Sync, + F: FnOnce(CustomArchitectureHandle, CoreArchitecture) -> A, + { + unsafe { + let custom_arch = &mut *(ctxt as *mut ArchitectureBuilder); + let custom_arch_handle = CustomArchitectureHandle { + handle: ctxt as *mut A, + }; + + let create = ptr::read(&custom_arch.func); + ptr::write( + &mut custom_arch.arch, + create(custom_arch_handle, CoreArchitecture(obj)), + ); + } + } + + extern "C" fn cb_endianness(ctxt: *mut c_void) -> BNEndianness + where + A: 'static + Architecture> + Send + Sync, + { + let custom_arch = unsafe { &*(ctxt as *mut A) }; + custom_arch.endianness() + } + + extern "C" fn cb_address_size(ctxt: *mut c_void) -> usize + where + A: 'static + Architecture> + Send + Sync, + { + let custom_arch = unsafe { &*(ctxt as *mut A) }; + custom_arch.address_size() + } + + extern "C" fn cb_default_integer_size(ctxt: *mut c_void) -> usize + where + A: 'static + Architecture> + Send + Sync, + { + let custom_arch = unsafe { &*(ctxt as *mut A) }; + custom_arch.default_integer_size() + } + + extern "C" fn cb_instruction_alignment(ctxt: *mut c_void) -> usize + where + A: 'static + Architecture> + Send + Sync, + { + let custom_arch = unsafe { &*(ctxt as *mut A) }; + custom_arch.instruction_alignment() + } + + extern "C" fn cb_max_instr_len(ctxt: *mut c_void) -> usize + where + A: 'static + Architecture> + Send + Sync, + { + let custom_arch = unsafe { &*(ctxt as *mut A) }; + custom_arch.max_instr_len() + } + + extern "C" fn cb_opcode_display_len(ctxt: *mut c_void) -> usize + where + A: 'static + Architecture> + Send + Sync, + { + let custom_arch = unsafe { &*(ctxt as *mut A) }; + custom_arch.opcode_display_len() + } + + extern "C" fn cb_associated_arch_by_addr( + ctxt: *mut c_void, + addr: *mut u64, + ) -> *mut BNArchitecture + where + A: 'static + Architecture> + Send + Sync, + { + let custom_arch = unsafe { &*(ctxt as *mut A) }; + let addr = unsafe { &mut *(addr) }; + + custom_arch.associated_arch_by_addr(addr).0 + } + + extern "C" fn cb_instruction_info( + ctxt: *mut c_void, + data: *const u8, + addr: u64, + len: usize, + result: *mut BNInstructionInfo, + ) -> bool + where + A: 'static + Architecture> + Send + Sync, + { + let custom_arch = unsafe { &*(ctxt as *mut A) }; + let data = unsafe { slice::from_raw_parts(data, len) }; + let result = unsafe { &mut *(result as *mut InstructionInfo) }; + + match custom_arch.instruction_info(data, addr) { + Some(info) => { + result.0 = info.0; + true + } + None => false, + } + } + + extern "C" fn cb_get_instruction_text( + ctxt: *mut c_void, + data: *const u8, + addr: u64, + len: *mut usize, + result: *mut *mut BNInstructionTextToken, + count: *mut usize, + ) -> bool + where + A: 'static + Architecture> + Send + Sync, + { + let custom_arch = unsafe { &*(ctxt as *mut A) }; + 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); + + *result = &mut (*r_ptr).0; + *count = r_count; + *len = res_size; + } + true + } + None => false, + } + } + + 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) }; + } + + extern "C" fn cb_instruction_llil( + ctxt: *mut c_void, + data: *const u8, + addr: u64, + len: *mut usize, + il: *mut BNLowLevelILFunction, + ) -> bool + where + A: 'static + Architecture> + Send + Sync, + { + let custom_arch = unsafe { &*(ctxt as *mut A) }; + let custom_arch_handle = CustomArchitectureHandle { + handle: ctxt as *mut A, + }; + + let data = unsafe { slice::from_raw_parts(data, *len) }; + let mut lifter = unsafe { Lifter::from_raw(custom_arch_handle, il) }; + + match custom_arch.instruction_llil(data, addr, &mut lifter) { + Some((res_len, res_value)) => { + unsafe { *len = res_len }; + res_value + } + None => false, + } + } + + extern "C" fn cb_reg_name(ctxt: *mut c_void, reg: u32) -> *mut c_char + where + A: 'static + Architecture> + Send + Sync, + { + let custom_arch = unsafe { &*(ctxt as *mut A) }; + + match custom_arch.register_from_id(reg) { + Some(reg) => BnString::new(reg.name().as_ref()).into_raw(), + None => BnString::new("invalid_reg").into_raw(), + } + } + + extern "C" fn cb_flag_name(ctxt: *mut c_void, flag: u32) -> *mut c_char + where + A: 'static + Architecture> + Send + Sync, + { + let custom_arch = unsafe { &*(ctxt as *mut A) }; + + match custom_arch.flag_from_id(flag) { + Some(flag) => BnString::new(flag.name().as_ref()).into_raw(), + None => BnString::new("invalid_flag").into_raw(), + } + } + + extern "C" fn cb_flag_write_name(ctxt: *mut c_void, flag_write: u32) -> *mut c_char + where + A: 'static + Architecture> + Send + Sync, + { + let custom_arch = unsafe { &*(ctxt as *mut A) }; + + match custom_arch.flag_write_from_id(flag_write) { + Some(flag_write) => BnString::new(flag_write.name().as_ref()).into_raw(), + None => BnString::new("invalid_flag_write").into_raw(), + } + } + + extern "C" fn cb_semantic_flag_class_name(ctxt: *mut c_void, class: u32) -> *mut c_char + where + A: 'static + Architecture> + Send + Sync, + { + let custom_arch = unsafe { &*(ctxt as *mut A) }; + + match custom_arch.flag_class_from_id(class) { + Some(class) => BnString::new(class.name().as_ref()).into_raw(), + None => BnString::new("invalid_flag_class").into_raw(), + } + } + + extern "C" fn cb_semantic_flag_group_name(ctxt: *mut c_void, group: u32) -> *mut c_char + where + A: 'static + Architecture> + Send + Sync, + { + let custom_arch = unsafe { &*(ctxt as *mut A) }; + + match custom_arch.flag_group_from_id(group) { + Some(group) => BnString::new(group.name().as_ref()).into_raw(), + None => BnString::new("invalid_flag_group").into_raw(), + } + } + + fn alloc_register_list + ExactSizeIterator>( + items: I, + count: &mut usize, + ) -> *mut u32 { + let len = items.len(); + *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) } + } + } + + extern "C" fn cb_registers_full_width(ctxt: *mut c_void, count: *mut usize) -> *mut u32 + where + A: 'static + Architecture> + Send + Sync, + { + let custom_arch = unsafe { &*(ctxt as *mut A) }; + let regs = custom_arch.registers_full_width(); + + alloc_register_list(regs.iter().map(|r| r.id()), unsafe { &mut *count }) + } + + extern "C" fn cb_registers_all(ctxt: *mut c_void, count: *mut usize) -> *mut u32 + where + A: 'static + Architecture> + Send + Sync, + { + let custom_arch = unsafe { &*(ctxt as *mut A) }; + let regs = custom_arch.registers_all(); + + alloc_register_list(regs.iter().map(|r| r.id()), unsafe { &mut *count }) + } + + extern "C" fn cb_registers_global(ctxt: *mut c_void, count: *mut usize) -> *mut u32 + where + A: 'static + Architecture> + Send + Sync, + { + let custom_arch = unsafe { &*(ctxt as *mut A) }; + let regs = custom_arch.registers_global(); + + alloc_register_list(regs.iter().map(|r| r.id()), unsafe { &mut *count }) + } + + extern "C" fn cb_registers_system(ctxt: *mut c_void, count: *mut usize) -> *mut u32 + where + A: 'static + Architecture> + Send + Sync, + { + let custom_arch = unsafe { &*(ctxt as *mut A) }; + let regs = custom_arch.registers_system(); + + alloc_register_list(regs.iter().map(|r| r.id()), unsafe { &mut *count }) + } + + extern "C" fn cb_flags(ctxt: *mut c_void, count: *mut usize) -> *mut u32 + where + A: 'static + Architecture> + Send + Sync, + { + let custom_arch = unsafe { &*(ctxt as *mut A) }; + let flags = custom_arch.flags(); + + alloc_register_list(flags.iter().map(|r| r.id()), unsafe { &mut *count }) + } + + extern "C" fn cb_flag_write_types(ctxt: *mut c_void, count: *mut usize) -> *mut u32 + where + A: 'static + Architecture> + Send + Sync, + { + let custom_arch = unsafe { &*(ctxt as *mut A) }; + let flag_writes = custom_arch.flag_write_types(); + + alloc_register_list(flag_writes.iter().map(|r| r.id()), unsafe { &mut *count }) + } + + extern "C" fn cb_semantic_flag_classes(ctxt: *mut c_void, count: *mut usize) -> *mut u32 + where + A: 'static + Architecture> + Send + Sync, + { + let custom_arch = unsafe { &*(ctxt as *mut A) }; + let flag_classes = custom_arch.flag_classes(); + + alloc_register_list(flag_classes.iter().map(|r| r.id()), unsafe { &mut *count }) + } + + extern "C" fn cb_semantic_flag_groups(ctxt: *mut c_void, count: *mut usize) -> *mut u32 + where + A: 'static + Architecture> + Send + Sync, + { + let custom_arch = unsafe { &*(ctxt as *mut A) }; + let flag_groups = custom_arch.flag_groups(); + + alloc_register_list(flag_groups.iter().map(|r| r.id()), unsafe { &mut *count }) + } + + extern "C" fn cb_flag_role(ctxt: *mut c_void, flag: u32, class: u32) -> BNFlagRole + where + A: 'static + Architecture> + Send + Sync, + { + let custom_arch = unsafe { &*(ctxt as *mut A) }; + + if let (Some(flag), class) = ( + custom_arch.flag_from_id(flag), + custom_arch.flag_class_from_id(class), + ) { + flag.role(class) + } else { + FlagRole::SpecialFlagRole + } + } + + extern "C" fn cb_flags_required_for_flag_cond( + ctxt: *mut c_void, + cond: BNLowLevelILFlagCondition, + class: u32, + count: *mut usize, + ) -> *mut u32 + where + A: 'static + Architecture> + Send + Sync, + { + let custom_arch = unsafe { &*(ctxt as *mut A) }; + let class = custom_arch.flag_class_from_id(class); + let flags = custom_arch.flags_required_for_flag_condition(cond, class); + + alloc_register_list(flags.iter().map(|r| r.id()), unsafe { &mut *count }) + } + + extern "C" fn cb_flags_required_for_semantic_flag_group( + ctxt: *mut c_void, + group: u32, + count: *mut usize, + ) -> *mut u32 + where + A: 'static + Architecture> + Send + Sync, + { + let custom_arch = unsafe { &*(ctxt as *mut A) }; + + if let Some(group) = custom_arch.flag_group_from_id(group) { + let flags = group.flags_required(); + alloc_register_list(flags.iter().map(|r| r.id()), unsafe { &mut *count }) + } else { + unsafe { + *count = 0; + } + ptr::null_mut() + } + } + + extern "C" fn cb_flag_conditions_for_semantic_flag_group( + ctxt: *mut c_void, + group: u32, + count: *mut usize, + ) -> *mut BNFlagConditionForSemanticClass + where + A: 'static + Architecture> + Send + Sync, + { + let custom_arch = unsafe { &*(ctxt as *mut A) }; + + if let Some(group) = custom_arch.flag_group_from_id(group) { + let flag_conditions = group.flag_conditions(); + + unsafe { + let allocation_size = + mem::size_of::() * flag_conditions.len(); + let result = libc::malloc(allocation_size) as *mut BNFlagConditionForSemanticClass; + let out_slice = slice::from_raw_parts_mut(result, flag_conditions.len()); + + for (i, (class, cond)) in flag_conditions.iter().enumerate() { + let out = out_slice.get_unchecked_mut(i); + + out.semanticClass = class.id(); + out.condition = *cond; + } + + *count = flag_conditions.len(); + result + } + } else { + unsafe { + *count = 0; + } + ptr::null_mut() + } + } + + extern "C" fn cb_free_flag_conditions_for_semantic_flag_group( + _ctxt: *mut c_void, + conds: *mut BNFlagConditionForSemanticClass, + ) where + A: 'static + Architecture> + Send + Sync, + { + unsafe { + libc::free(conds as *mut _); + } + } + + extern "C" fn cb_flags_written_by_write_type( + ctxt: *mut c_void, + write_type: u32, + count: *mut usize, + ) -> *mut u32 + where + A: 'static + Architecture> + Send + Sync, + { + let custom_arch = unsafe { &*(ctxt as *mut A) }; + + if let Some(write_type) = custom_arch.flag_write_from_id(write_type) { + let written = write_type.flags_written(); + alloc_register_list(written.iter().map(|f| f.id()), unsafe { &mut *count }) + } else { + unsafe { + *count = 0; + } + ptr::null_mut() + } + } + + extern "C" fn cb_semantic_class_for_flag_write_type( + ctxt: *mut c_void, + write_type: u32, + ) -> u32 + where + A: 'static + Architecture> + Send + Sync, + { + let custom_arch = unsafe { &*(ctxt as *mut A) }; + custom_arch + .flag_write_from_id(write_type) + .map(|w| w.class()) + .and_then(|c| c.map(|c| c.id())) + .unwrap_or(0) + } + + extern "C" fn cb_flag_write_llil( + ctxt: *mut c_void, + op: BNLowLevelILOperation, + size: usize, + flag_write: u32, + flag: u32, + operands_raw: *mut BNRegisterOrConstant, + operand_count: usize, + il: *mut BNLowLevelILFunction, + ) -> usize + where + A: 'static + Architecture> + Send + Sync, + { + let custom_arch = unsafe { &*(ctxt as *mut A) }; + let custom_arch_handle = CustomArchitectureHandle { + handle: ctxt as *mut A, + }; + + let flag_write = custom_arch.flag_write_from_id(flag_write); + let flag = custom_arch.flag_from_id(flag); + let operands = unsafe { slice::from_raw_parts(operands_raw, operand_count) }; + let mut lifter = unsafe { Lifter::from_raw(custom_arch_handle, il) }; + + if let (Some(flag_write), Some(flag)) = (flag_write, flag) { + if let Some(op) = FlagWriteOp::from_op(custom_arch, size, op, operands) { + if let Some(expr) = custom_arch.flag_write_llil(flag, flag_write, op, &mut lifter) { + // TODO verify that returned expr is a bool value + return expr.expr_idx; + } + } else { + warn!( + "unable to unpack flag write op: {:?} with {} operands", + op, + operands.len() + ); + } + + let role = flag.role(flag_write.class()); + + unsafe { + BNGetDefaultArchitectureFlagWriteLowLevelIL( + custom_arch.as_ref().0, + op, + size, + role, + operands_raw, + operand_count, + il, + ) + } + } else { + // TODO this should be impossible; requires bad flag/flag_write ids passed in; + // explode more violently + lifter.unimplemented().expr_idx + } + } + + extern "C" fn cb_flag_cond_llil( + ctxt: *mut c_void, + cond: FlagCondition, + class: u32, + il: *mut BNLowLevelILFunction, + ) -> usize + where + A: 'static + Architecture> + Send + Sync, + { + let custom_arch = unsafe { &*(ctxt as *mut A) }; + let custom_arch_handle = CustomArchitectureHandle { + handle: ctxt as *mut A, + }; + + let class = custom_arch.flag_class_from_id(class); + + let mut lifter = unsafe { Lifter::from_raw(custom_arch_handle, il) }; + if let Some(expr) = custom_arch.flag_cond_llil(cond, class, &mut lifter) { + // TODO verify that returned expr is a bool value + return expr.expr_idx; + } + + lifter.unimplemented().expr_idx + } + + extern "C" fn cb_flag_group_llil( + ctxt: *mut c_void, + group: u32, + il: *mut BNLowLevelILFunction, + ) -> usize + where + A: 'static + Architecture> + Send + Sync, + { + let custom_arch = unsafe { &*(ctxt as *mut A) }; + let custom_arch_handle = CustomArchitectureHandle { + handle: ctxt as *mut A, + }; + + let mut lifter = unsafe { Lifter::from_raw(custom_arch_handle, il) }; + + if let Some(group) = custom_arch.flag_group_from_id(group) { + if let Some(expr) = custom_arch.flag_group_llil(group, &mut lifter) { + // TODO verify that returned expr is a bool value + return expr.expr_idx; + } + } + + lifter.unimplemented().expr_idx + } + + extern "C" fn cb_free_register_list(_ctxt: *mut c_void, regs: *mut u32) { + if regs.is_null() { + return; + } + + 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); + } + } + + extern "C" fn cb_register_info(ctxt: *mut c_void, reg: u32, result: *mut BNRegisterInfo) + where + A: 'static + Architecture> + Send + Sync, + { + let custom_arch = unsafe { &*(ctxt as *mut A) }; + let result = unsafe { &mut *result }; + + if let Some(reg) = custom_arch.register_from_id(reg) { + let info = reg.info(); + + result.fullWidthRegister = match info.parent() { + Some(p) => p.id(), + None => reg.id(), + }; + + result.offset = info.offset(); + result.size = info.size(); + result.extend = info.implicit_extend(); + } + } + + extern "C" fn cb_stack_pointer(ctxt: *mut c_void) -> u32 + where + A: 'static + Architecture> + Send + Sync, + { + let custom_arch = unsafe { &*(ctxt as *mut A) }; + + if let Some(reg) = custom_arch.stack_pointer_reg() { + reg.id() + } else { + 0xffff_ffff + } + } + + extern "C" fn cb_link_reg(ctxt: *mut c_void) -> u32 + where + A: 'static + Architecture> + Send + Sync, + { + let custom_arch = unsafe { &*(ctxt as *mut A) }; + + if let Some(reg) = custom_arch.link_reg() { + reg.id() + } else { + 0xffff_ffff + } + } + + extern "C" fn cb_reg_stack_name(ctxt: *mut c_void, stack: u32) -> *mut c_char + where + A: 'static + Architecture> + Send + Sync, + { + let custom_arch = unsafe { &*(ctxt as *mut A) }; + + match custom_arch.register_stack_from_id(stack) { + Some(stack) => BnString::new(stack.name().as_ref()).into_raw(), + None => BnString::new("invalid_reg_stack").into_raw(), + } + } + + extern "C" fn cb_reg_stacks(ctxt: *mut c_void, count: *mut usize) -> *mut u32 + where + A: 'static + Architecture> + Send + Sync, + { + let custom_arch = unsafe { &*(ctxt as *mut A) }; + let regs = custom_arch.register_stacks(); + + alloc_register_list(regs.iter().map(|r| r.id()), unsafe { &mut *count }) + } + + extern "C" fn cb_reg_stack_info( + ctxt: *mut c_void, + stack: u32, + result: *mut BNRegisterStackInfo, + ) where + A: 'static + Architecture> + Send + Sync, + { + let custom_arch = unsafe { &*(ctxt as *mut A) }; + let result = unsafe { &mut *result }; + + if let Some(stack) = custom_arch.register_stack_from_id(stack) { + let info = stack.info(); + + let (reg, count) = info.storage_regs(); + result.firstStorageReg = reg.id(); + result.storageCount = count; + + if let Some((reg, count)) = info.top_relative_regs() { + result.firstTopRelativeReg = reg.id(); + result.topRelativeCount = count; + } else { + result.firstTopRelativeReg = 0xffff_ffff; + result.topRelativeCount = 0; + } + + result.stackTopReg = info.stack_top_reg().id(); + } + } + + extern "C" fn cb_intrinsic_name(ctxt: *mut c_void, intrinsic: u32) -> *mut c_char + where + A: 'static + Architecture> + Send + Sync, + { + let custom_arch = unsafe { &*(ctxt as *mut A) }; + match custom_arch.intrinsic_from_id(intrinsic) { + Some(intrinsic) => BnString::new(intrinsic.name().as_ref()).into_raw(), + None => BnString::new("invalid_intrinsic").into_raw(), + } + } + + extern "C" fn cb_intrinsics(ctxt: *mut c_void, count: *mut usize) -> *mut u32 + where + A: 'static + Architecture> + Send + Sync, + { + let custom_arch = unsafe { &*(ctxt as *mut A) }; + let intrinsics = custom_arch.intrinsics(); + alloc_register_list(intrinsics.iter().map(|i| i.id()), unsafe { &mut *count }) + } + + extern "C" fn cb_intrinsic_inputs( + ctxt: *mut c_void, + intrinsic: u32, + count: *mut usize, + ) -> *mut BNNameAndType + where + A: 'static + Architecture> + Send + Sync, + { + 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.len() == 0 { + ptr::null_mut() + } else { + let raw = res.as_mut_ptr(); + mem::forget(res); + raw + } + } + } else { + unsafe { + *count = 0; + } + ptr::null_mut() + } + } + + extern "C" fn cb_free_name_and_types(ctxt: *mut c_void, nt: *mut BNNameAndType, count: usize) + where + A: 'static + Architecture> + Send + Sync, + { + let _custom_arch = unsafe { &*(ctxt as *mut A) }; + + if !nt.is_null() { + unsafe { + let list = Vec::from_raw_parts(nt, count, count); + for nt in list { + BnString::from_raw(nt.name); + } + } + } + } + + extern "C" fn cb_intrinsic_outputs( + ctxt: *mut c_void, + intrinsic: u32, + count: *mut usize, + ) -> *mut BNTypeWithConfidence + where + A: 'static + Architecture> + Send + Sync, + { + let custom_arch = unsafe { &*(ctxt as *mut A) }; + + 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()); + } + + unsafe { + *count = res.len(); + if res.len() == 0 { + ptr::null_mut() + } else { + let raw = res.as_mut_ptr(); + mem::forget(res); + raw + } + } + } else { + unsafe { + *count = 0; + } + ptr::null_mut() + } + } + + extern "C" fn cb_free_type_list( + ctxt: *mut c_void, + tl: *mut BNTypeWithConfidence, + count: usize, + ) where + A: 'static + Architecture> + Send + Sync, + { + let _custom_arch = unsafe { &*(ctxt as *mut A) }; + if !tl.is_null() { + unsafe { + let _list = Vec::from_raw_parts(tl, count, count); + } + } + } + + extern "C" fn cb_can_assemble(ctxt: *mut c_void) -> bool + where + A: 'static + Architecture> + Send + Sync, + { + let custom_arch = unsafe { &*(ctxt as *mut A) }; + custom_arch.can_assemble() + } + + extern "C" fn cb_assemble( + ctxt: *mut c_void, + code: *const c_char, + addr: u64, + buffer: *mut BNDataBuffer, + errors: *mut *mut c_char, + ) -> bool + where + A: 'static + Architecture> + Send + Sync, + { + let custom_arch = unsafe { &*(ctxt as *mut A) }; + let code = raw_to_string(code).unwrap_or("".into()); + let mut buffer = DataBuffer::from_raw(buffer); + + let result = match custom_arch.assemble(&code, addr) { + Ok(result) => { + buffer.set_data(&result); + unsafe { + *errors = BnString::new("").into_raw(); + } + true + } + Err(result) => { + unsafe { + *errors = BnString::new(result).into_raw(); + } + false + } + }; + + // Caller owns the data buffer, don't free it + mem::forget(buffer); + + result + } + + extern "C" fn cb_is_never_branch_patch_available( + ctxt: *mut c_void, + data: *const u8, + addr: u64, + len: usize, + ) -> bool + where + A: 'static + Architecture> + Send + Sync, + { + let custom_arch = unsafe { &*(ctxt as *mut A) }; + let data = unsafe { slice::from_raw_parts(data, len) }; + custom_arch.is_never_branch_patch_available(data, addr) + } + + extern "C" fn cb_is_always_branch_patch_available( + ctxt: *mut c_void, + data: *const u8, + addr: u64, + len: usize, + ) -> bool + where + A: 'static + Architecture> + Send + Sync, + { + let custom_arch = unsafe { &*(ctxt as *mut A) }; + let data = unsafe { slice::from_raw_parts(data, len) }; + custom_arch.is_always_branch_patch_available(data, addr) + } + + extern "C" fn cb_is_invert_branch_patch_available( + ctxt: *mut c_void, + data: *const u8, + addr: u64, + len: usize, + ) -> bool + where + A: 'static + Architecture> + Send + Sync, + { + let custom_arch = unsafe { &*(ctxt as *mut A) }; + let data = unsafe { slice::from_raw_parts(data, len) }; + custom_arch.is_invert_branch_patch_available(data, addr) + } + + extern "C" fn cb_is_skip_and_return_zero_patch_available( + ctxt: *mut c_void, + data: *const u8, + addr: u64, + len: usize, + ) -> bool + where + A: 'static + Architecture> + Send + Sync, + { + let custom_arch = unsafe { &*(ctxt as *mut A) }; + let data = unsafe { slice::from_raw_parts(data, len) }; + custom_arch.is_skip_and_return_zero_patch_available(data, addr) + } + + extern "C" fn cb_is_skip_and_return_value_patch_available( + ctxt: *mut c_void, + data: *const u8, + addr: u64, + len: usize, + ) -> bool + where + A: 'static + Architecture> + Send + Sync, + { + let custom_arch = unsafe { &*(ctxt as *mut A) }; + let data = unsafe { slice::from_raw_parts(data, len) }; + custom_arch.is_skip_and_return_value_patch_available(data, addr) + } + + extern "C" fn cb_convert_to_nop( + ctxt: *mut c_void, + data: *mut u8, + addr: u64, + len: usize, + ) -> bool + where + A: 'static + Architecture> + Send + Sync, + { + let custom_arch = unsafe { &*(ctxt as *mut A) }; + let data = unsafe { slice::from_raw_parts_mut(data, len) }; + custom_arch.convert_to_nop(data, addr) + } + + extern "C" fn cb_always_branch( + ctxt: *mut c_void, + data: *mut u8, + addr: u64, + len: usize, + ) -> bool + where + A: 'static + Architecture> + Send + Sync, + { + let custom_arch = unsafe { &*(ctxt as *mut A) }; + let data = unsafe { slice::from_raw_parts_mut(data, len) }; + custom_arch.always_branch(data, addr) + } + + extern "C" fn cb_invert_branch( + ctxt: *mut c_void, + data: *mut u8, + addr: u64, + len: usize, + ) -> bool + where + A: 'static + Architecture> + Send + Sync, + { + let custom_arch = unsafe { &*(ctxt as *mut A) }; + let data = unsafe { slice::from_raw_parts_mut(data, len) }; + custom_arch.invert_branch(data, addr) + } + + extern "C" fn cb_skip_and_return_value( + ctxt: *mut c_void, + data: *mut u8, + addr: u64, + len: usize, + val: u64, + ) -> bool + where + A: 'static + Architecture> + Send + Sync, + { + let custom_arch = unsafe { &*(ctxt as *mut A) }; + let data = unsafe { slice::from_raw_parts_mut(data, len) }; + custom_arch.skip_and_return_value(data, addr, val) + } + + let name = name.into_bytes_with_nul(); + + let uninit_arch = ArchitectureBuilder { + arch: unsafe { zeroed() }, + func, + }; + + let raw = Box::into_raw(Box::new(uninit_arch)); + let mut custom_arch = BNCustomArchitecture { + context: raw as *mut _, + init: Some(cb_init::), + getEndianness: Some(cb_endianness::), + getAddressSize: Some(cb_address_size::), + getDefaultIntegerSize: Some(cb_default_integer_size::), + getInstructionAlignment: Some(cb_instruction_alignment::), + getMaxInstructionLength: Some(cb_max_instr_len::), + getOpcodeDisplayLength: Some(cb_opcode_display_len::), + getAssociatedArchitectureByAddress: Some(cb_associated_arch_by_addr::), + getInstructionInfo: Some(cb_instruction_info::), + getInstructionText: Some(cb_get_instruction_text::), + freeInstructionText: Some(cb_free_instruction_text), + getInstructionLowLevelIL: Some(cb_instruction_llil::), + + getRegisterName: Some(cb_reg_name::), + getFlagName: Some(cb_flag_name::), + getFlagWriteTypeName: Some(cb_flag_write_name::), + getSemanticFlagClassName: Some(cb_semantic_flag_class_name::), + getSemanticFlagGroupName: Some(cb_semantic_flag_group_name::), + + getFullWidthRegisters: Some(cb_registers_full_width::), + getAllRegisters: Some(cb_registers_all::), + getAllFlags: Some(cb_flags::), + getAllFlagWriteTypes: Some(cb_flag_write_types::), + getAllSemanticFlagClasses: Some(cb_semantic_flag_classes::), + getAllSemanticFlagGroups: Some(cb_semantic_flag_groups::), + + getFlagRole: Some(cb_flag_role::), + getFlagsRequiredForFlagCondition: Some(cb_flags_required_for_flag_cond::), + + getFlagsRequiredForSemanticFlagGroup: Some(cb_flags_required_for_semantic_flag_group::), + getFlagConditionsForSemanticFlagGroup: Some( + cb_flag_conditions_for_semantic_flag_group::, + ), + freeFlagConditionsForSemanticFlagGroup: Some( + cb_free_flag_conditions_for_semantic_flag_group::, + ), + + getFlagsWrittenByFlagWriteType: Some(cb_flags_written_by_write_type::), + getSemanticClassForFlagWriteType: Some(cb_semantic_class_for_flag_write_type::), + + getFlagWriteLowLevelIL: Some(cb_flag_write_llil::), + getFlagConditionLowLevelIL: Some(cb_flag_cond_llil::), + getSemanticFlagGroupLowLevelIL: Some(cb_flag_group_llil::), + + freeRegisterList: Some(cb_free_register_list), + getRegisterInfo: Some(cb_register_info::), + getStackPointerRegister: Some(cb_stack_pointer::), + getLinkRegister: Some(cb_link_reg::), + getGlobalRegisters: Some(cb_registers_global::), + getSystemRegisters: Some(cb_registers_system::), + + getRegisterStackName: Some(cb_reg_stack_name::), + getAllRegisterStacks: Some(cb_reg_stacks::), + getRegisterStackInfo: Some(cb_reg_stack_info::), + + getIntrinsicName: Some(cb_intrinsic_name::), + getAllIntrinsics: Some(cb_intrinsics::), + getIntrinsicInputs: Some(cb_intrinsic_inputs::), + freeNameAndTypeList: Some(cb_free_name_and_types::), + getIntrinsicOutputs: Some(cb_intrinsic_outputs::), + freeTypeList: Some(cb_free_type_list::), + + canAssemble: Some(cb_can_assemble::), + assemble: Some(cb_assemble::), + + isNeverBranchPatchAvailable: Some(cb_is_never_branch_patch_available::), + isAlwaysBranchPatchAvailable: Some(cb_is_always_branch_patch_available::), + isInvertBranchPatchAvailable: Some(cb_is_invert_branch_patch_available::), + isSkipAndReturnZeroPatchAvailable: Some(cb_is_skip_and_return_zero_patch_available::), + isSkipAndReturnValuePatchAvailable: Some(cb_is_skip_and_return_value_patch_available::), + + convertToNop: Some(cb_convert_to_nop::), + alwaysBranch: Some(cb_always_branch::), + invertBranch: Some(cb_invert_branch::), + skipAndReturnValue: Some(cb_skip_and_return_value::), + }; + + unsafe { + let res = + BNRegisterArchitecture(name.as_ref().as_ptr() as *mut _, &mut custom_arch as *mut _); + + assert!(!res.is_null()); + + &(*raw).arch + } +} + +pub struct CustomArchitectureHandle +where + A: 'static + Architecture> + Send + Sync, +{ + handle: *mut A, +} + +unsafe impl Send for CustomArchitectureHandle where + A: 'static + Architecture> + Send + Sync +{ +} + +unsafe impl Sync for CustomArchitectureHandle where + A: 'static + Architecture> + Send + Sync +{ +} + +impl Clone for CustomArchitectureHandle +where + A: 'static + Architecture + Send + Sync, +{ + fn clone(&self) -> Self { + *self + } +} + +impl Copy for CustomArchitectureHandle where + A: 'static + Architecture + Send + Sync +{ +} + +impl Borrow for CustomArchitectureHandle +where + A: 'static + Architecture + Send + Sync, +{ + fn borrow(&self) -> &A { + unsafe { &*self.handle } + } +} + +#[repr(i32)] +pub enum LlvmServicesDialect { + Unspecified = 0, + Att = 1, + Intel = 2, +} + +#[repr(i32)] +pub enum LlvmServicesCodeModel { + Default = 0, + Small = 1, + Kernel = 2, + Medium = 3, + Large = 4, +} + +#[repr(i32)] +pub enum LlvmServicesRelocMode { + Static = 0, + PIC = 1, + DynamicNoPIC = 2, +} + +pub fn llvm_assemble( + code: &str, + dialect: LlvmServicesDialect, + arch_triple: &str, + code_model: LlvmServicesCodeModel, + reloc_mode: LlvmServicesRelocMode, +) -> Result, String> { + let code = CString::new(code).map_err(|_| "Invalid encoding in code string".to_string())?; + let arch_triple = CString::new(arch_triple) + .map_err(|_| "Invalid encoding in architecture triple string".to_string())?; + let mut out_bytes: *mut c_char = ptr::null_mut(); + let mut out_bytes_len: c_int = 0; + let mut err_bytes: *mut c_char = ptr::null_mut(); + let mut err_len: c_int = 0; + + unsafe { + BNLlvmServicesInit(); + } + + let result = unsafe { + BNLlvmServicesAssemble( + code.as_ptr(), + dialect as i32, + arch_triple.as_ptr(), + code_model as i32, + reloc_mode as i32, + &mut out_bytes as *mut *mut c_char, + &mut out_bytes_len as *mut c_int, + &mut err_bytes as *mut *mut c_char, + &mut err_len as *mut c_int, + ) + }; + + let out = if out_bytes_len == 0 { + Vec::new() + } else { + unsafe { + slice::from_raw_parts( + out_bytes as *const c_char as *const u8, + out_bytes_len as usize, + ) + } + .to_vec() + }; + + let errors = if err_len == 0 { + "".into() + } else { + String::from_utf8_lossy(unsafe { + slice::from_raw_parts(err_bytes as *const c_char as *const u8, err_len as usize) + }) + .into_owned() + }; + + unsafe { + BNLlvmServicesAssembleFree(out_bytes, err_bytes); + } + + if result == 0 { + Ok(out) + } else { + Err(errors) + } +} diff --git a/src/backgroundtask.rs b/src/backgroundtask.rs new file mode 100644 index 0000000..1eb090d --- /dev/null +++ b/src/backgroundtask.rs @@ -0,0 +1,135 @@ +// Copyright 2021-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. + +//! Background tasks provide plugins the ability to run tasks in the background so they don't hand the UI + +use binaryninjacore_sys::*; + +use std::result; + +use crate::rc::*; +use crate::string::*; + +pub type Result = result::Result; + +#[derive(PartialEq, Eq, Hash)] +pub struct BackgroundTask { + pub(crate) handle: *mut BNBackgroundTask, +} + +impl BackgroundTask { + pub(crate) unsafe fn from_raw(handle: *mut BNBackgroundTask) -> Self { + debug_assert!(!handle.is_null()); + + Self { handle } + } + + pub fn new(initial_text: S, can_cancel: bool) -> Result> { + let text = initial_text.into_bytes_with_nul(); + + let handle = unsafe { BNBeginBackgroundTask(text.as_ref().as_ptr() as *mut _, can_cancel) }; + + if handle.is_null() { + return Err(()); + } + + unsafe { Ok(Ref::new(Self { handle })) } + } + + pub fn can_cancel(&self) -> bool { + unsafe { BNCanCancelBackgroundTask(self.handle) } + } + + pub fn is_cancelled(&self) -> bool { + unsafe { BNIsBackgroundTaskCancelled(self.handle) } + } + + pub fn is_finished(&self) -> bool { + unsafe { BNIsBackgroundTaskFinished(self.handle) } + } + + pub fn get_progress_text(&self) -> BnString { + unsafe { BnString::from_raw(BNGetBackgroundTaskProgressText(self.handle)) } + } + + pub fn cancel(&self) { + unsafe { BNCancelBackgroundTask(self.handle) } + } + + pub fn finish(&self) { + unsafe { BNFinishBackgroundTask(self.handle) } + } + + pub fn set_progress_text(&self, text: S) { + let progress_text = text.into_bytes_with_nul(); + + unsafe { + BNSetBackgroundTaskProgressText(self.handle, progress_text.as_ref().as_ptr() as *mut _) + } + } + + pub fn running_tasks() -> Array { + unsafe { + let mut count = 0; + let handles = BNGetRunningBackgroundTasks(&mut count); + + Array::new(handles, count, ()) + } + } +} + +unsafe impl RefCountable for BackgroundTask { + unsafe fn inc_ref(handle: &Self) -> Ref { + Ref::new(Self { + handle: BNNewBackgroundTaskReference(handle.handle), + }) + } + + unsafe fn dec_ref(handle: &Self) { + BNFreeBackgroundTask(handle.handle); + } +} + +impl CoreArrayProvider for BackgroundTask { + type Raw = *mut BNBackgroundTask; + type Context = (); +} + +unsafe impl CoreOwnedArrayProvider 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> { + Guard::new(BackgroundTask::from_raw(*raw), context) + } +} + +impl ToOwned for BackgroundTask { + type Owned = Ref; + + fn to_owned(&self) -> Self::Owned { + unsafe { RefCountable::inc_ref(self) } + } +} + +unsafe impl Send for BackgroundTask {} +unsafe impl Sync for BackgroundTask {} diff --git a/src/basicblock.rs b/src/basicblock.rs new file mode 100644 index 0000000..73ad936 --- /dev/null +++ b/src/basicblock.rs @@ -0,0 +1,318 @@ +// Copyright 2021-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::fmt; + +use crate::architecture::CoreArchitecture; +use crate::function::Function; +use binaryninjacore_sys::*; + +use crate::rc::*; + +enum EdgeDirection { + Incoming, + Outgoing, +} + +pub struct Edge<'a, C: 'a + BlockContext> { + branch: super::BranchType, + back_edge: bool, + source: Guard<'a, BasicBlock>, + target: Guard<'a, BasicBlock>, +} + +impl<'a, C: 'a + BlockContext> Edge<'a, C> { + pub fn branch_type(&self) -> super::BranchType { + self.branch + } + + pub fn back_edge(&self) -> bool { + self.back_edge + } + + pub fn source(&self) -> &BasicBlock { + &self.source + } + + pub fn target(&self) -> &BasicBlock { + &self.target + } +} + +impl<'a, C: 'a + fmt::Debug + BlockContext> fmt::Debug for Edge<'a, C> { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!( + f, + "{:?} ({}) {:?} -> {:?}", + self.branch, self.back_edge, &*self.source, &*self.target + ) + } +} + +pub struct EdgeContext<'a, C: 'a + BlockContext> { + dir: EdgeDirection, + orig_block: &'a BasicBlock, +} + +impl<'a, C: 'a + BlockContext> CoreArrayProvider for Edge<'a, C> { + type Raw = BNBasicBlockEdge; + type Context = EdgeContext<'a, C>; +} + +unsafe impl<'a, C: 'a + BlockContext> CoreOwnedArrayProvider 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> { + let edge_target = Guard::new( + BasicBlock::from_raw(raw.target, context.orig_block.context.clone()), + raw, + ); + let orig_block = Guard::new( + BasicBlock::from_raw( + context.orig_block.handle, + context.orig_block.context.clone(), + ), + raw, + ); + + let (source, target) = match context.dir { + EdgeDirection::Incoming => (edge_target, orig_block), + EdgeDirection::Outgoing => (orig_block, edge_target), + }; + + Edge { + branch: raw.type_, + back_edge: raw.backEdge, + source, + target, + } + } +} + +pub trait BlockContext: Clone + Sync + Send + Sized { + type Instruction; + type Iter: Iterator; + + fn start(&self, block: &BasicBlock) -> Self::Instruction; + fn iter(&self, block: &BasicBlock) -> Self::Iter; +} + +#[derive(PartialEq, Eq, Hash)] +pub struct BasicBlock { + pub(crate) handle: *mut BNBasicBlock, + context: C, +} + +unsafe impl Send for BasicBlock {} +unsafe impl Sync for BasicBlock {} + +impl BasicBlock { + pub(crate) unsafe fn from_raw(handle: *mut BNBasicBlock, context: C) -> Self { + Self { handle, context } + } + + // TODO native bb vs il bbs + pub fn function(&self) -> Ref { + unsafe { + let func = BNGetBasicBlockFunction(self.handle); + Function::from_raw(func) + } + } + + pub fn arch(&self) -> CoreArchitecture { + unsafe { + let arch = BNGetBasicBlockArchitecture(self.handle); + CoreArchitecture::from_raw(arch) + } + } + + pub fn iter(&self) -> C::Iter { + self.context.iter(self) + } + + pub fn raw_start(&self) -> u64 { + unsafe { BNGetBasicBlockStart(self.handle) } + } + + pub fn raw_end(&self) -> u64 { + unsafe { BNGetBasicBlockEnd(self.handle) } + } + + pub fn raw_length(&self) -> u64 { + unsafe { BNGetBasicBlockLength(self.handle) } + } + + pub fn incoming_edges(&self) -> Array> { + unsafe { + let mut count = 0; + let edges = BNGetBasicBlockIncomingEdges(self.handle, &mut count); + + Array::new( + edges, + count, + EdgeContext { + dir: EdgeDirection::Incoming, + orig_block: self, + }, + ) + } + } + + pub fn outgoing_edges(&self) -> Array> { + unsafe { + let mut count = 0; + let edges = BNGetBasicBlockOutgoingEdges(self.handle, &mut count); + + Array::new( + edges, + count, + EdgeContext { + dir: EdgeDirection::Outgoing, + orig_block: self, + }, + ) + } + } + + // is this valid for il blocks? + pub fn has_undetermined_outgoing_edges(&self) -> bool { + unsafe { BNBasicBlockHasUndeterminedOutgoingEdges(self.handle) } + } + + pub fn can_exit(&self) -> bool { + unsafe { BNBasicBlockCanExit(self.handle) } + } + + pub fn index(&self) -> usize { + unsafe { BNGetBasicBlockIndex(self.handle) } + } + + pub fn immediate_dominator(&self) -> Option> { + unsafe { + let block = BNGetBasicBlockImmediateDominator(self.handle, false); + + if block.is_null() { + return None; + } + + Some(Ref::new(BasicBlock::from_raw(block, self.context.clone()))) + } + } + + pub fn dominators(&self) -> Array> { + unsafe { + let mut count = 0; + let blocks = BNGetBasicBlockDominators(self.handle, &mut count, false); + + Array::new(blocks, count, self.context.clone()) + } + } + + pub fn strict_dominators(&self) -> Array> { + unsafe { + let mut count = 0; + let blocks = BNGetBasicBlockStrictDominators(self.handle, &mut count, false); + + Array::new(blocks, count, self.context.clone()) + } + } + + pub fn dominator_tree_children(&self) -> Array> { + unsafe { + let mut count = 0; + let blocks = BNGetBasicBlockDominatorTreeChildren(self.handle, &mut count, false); + + Array::new(blocks, count, self.context.clone()) + } + } + + pub fn dominance_frontier(&self) -> Array> { + unsafe { + let mut count = 0; + let blocks = BNGetBasicBlockDominanceFrontier(self.handle, &mut count, false); + + Array::new(blocks, count, self.context.clone()) + } + } + + // TODO iterated dominance frontier +} + +impl<'a, C: BlockContext> IntoIterator for &'a BasicBlock { + type Item = C::Instruction; + type IntoIter = C::Iter; + + fn into_iter(self) -> Self::IntoIter { + self.iter() + } +} + +impl fmt::Debug for BasicBlock { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!( + f, + " {}>", + self.handle, + &self.context, + self.raw_start(), + self.raw_end() + ) + } +} + +impl ToOwned for BasicBlock { + type Owned = Ref; + + fn to_owned(&self) -> Self::Owned { + unsafe { RefCountable::inc_ref(self) } + } +} + +unsafe impl RefCountable for BasicBlock { + unsafe fn inc_ref(handle: &Self) -> Ref { + Ref::new(Self { + handle: BNNewBasicBlockReference(handle.handle), + context: handle.context.clone(), + }) + } + + unsafe fn dec_ref(handle: &Self) { + BNFreeBasicBlock(handle.handle); + } +} + +impl CoreArrayProvider for BasicBlock { + type Raw = *mut BNBasicBlock; + type Context = C; +} + +unsafe impl CoreOwnedArrayProvider 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 { + Guard::new(BasicBlock::from_raw(*raw, context.clone()), context) + } +} diff --git a/src/binaryreader.rs b/src/binaryreader.rs new file mode 100644 index 0000000..db681e7 --- /dev/null +++ b/src/binaryreader.rs @@ -0,0 +1,101 @@ +// 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. + +//! A convenience class for reading binary data + +use binaryninjacore_sys::*; + +use crate::binaryview::BinaryView; +use crate::Endianness; + +use std::io::{Read, Seek, SeekFrom}; + +pub struct BinaryReader { + handle: *mut BNBinaryReader, +} + +impl BinaryReader { + pub fn new(view: &BinaryView, endian: Endianness) -> Self { + let handle = unsafe { BNCreateBinaryReader(view.handle) }; + unsafe { + BNSetBinaryReaderEndianness(handle, endian); + } + Self { handle } + } + + pub fn endian(&self) -> Endianness { + unsafe { BNGetBinaryReaderEndianness(self.handle) } + } + + pub fn set_endian(&self, endian: Endianness) { + unsafe { BNSetBinaryReaderEndianness(self.handle, endian) } + } + + pub fn offset(&self) -> u64 { + unsafe { BNGetReaderPosition(self.handle) } + } + + pub fn eof(&self) -> bool { + unsafe { BNIsEndOfFile(self.handle) } + } +} + +impl Seek for BinaryReader { + /// Seek to the specified position. + /// + /// # Errors + /// Seeking relative to [SeekFrom::End] is unsupported and will return an error. + fn seek(&mut self, pos: SeekFrom) -> std::io::Result { + unsafe { + match pos { + SeekFrom::Current(offset) => BNSeekBinaryReaderRelative(self.handle, offset), + SeekFrom::Start(offset) => BNSeekBinaryReader(self.handle, offset), + _ => { + return Err(std::io::Error::new( + std::io::ErrorKind::Unsupported, + "Cannot seek end of BinaryReader", + )) + } + }; + } + + Ok(self.offset()) + } +} + +impl Read for BinaryReader { + fn read(&mut self, buf: &mut [u8]) -> std::io::Result { + let len = buf.len(); + + let result = unsafe { BNReadData(self.handle, buf.as_mut_ptr() as *mut _, len) }; + + if !result { + Err(std::io::Error::new( + std::io::ErrorKind::Other, + "Read out of bounds", + )) + } else { + Ok(len) + } + } +} + +impl Drop for BinaryReader { + fn drop(&mut self) { + unsafe { BNFreeBinaryReader(self.handle) } + } +} + +unsafe impl Sync for BinaryReader {} +unsafe impl Send for BinaryReader {} diff --git a/src/binaryview.rs b/src/binaryview.rs new file mode 100644 index 0000000..a6b2b54 --- /dev/null +++ b/src/binaryview.rs @@ -0,0 +1,1454 @@ +// Copyright 2021-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. + +//! A view on binary data and queryable interface of a binary file. +//! +//! One key job of BinaryView is file format parsing which allows Binary Ninja to read, write, insert, remove portions of the file given a virtual address. For the purposes of this documentation we define a virtual address as the memory address that the various pieces of the physical file will be loaded at. +//! TODO : Mirror the Python docs for this + +use binaryninjacore_sys::*; + +pub use binaryninjacore_sys::BNModificationStatus as ModificationStatus; + +use std::collections::HashMap; +use std::ffi::c_void; +use std::ops; +use std::ops::Range; +use std::os::raw::c_char; +use std::ptr; +use std::result; + +use crate::architecture::Architecture; +use crate::architecture::CoreArchitecture; +use crate::basicblock::BasicBlock; +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::metadata::Metadata; +use crate::platform::Platform; +use crate::relocation::Relocation; +use crate::section::{Section, SectionBuilder}; +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::Endianness; + +use crate::rc::*; +use crate::references::{CodeReference, DataReference}; +use crate::string::*; + +// TODO : general reorg of modules related to bv + +pub type Result = result::Result; + +#[allow(clippy::len_without_is_empty)] +pub trait BinaryViewBase: AsRef { + fn read(&self, _buf: &mut [u8], _offset: u64) -> usize { + 0 + } + + fn write(&self, _offset: u64, _data: &[u8]) -> usize { + 0 + } + + fn insert(&self, _offset: u64, _data: &[u8]) -> usize { + 0 + } + + fn remove(&self, _offset: u64, _len: usize) -> usize { + 0 + } + + fn offset_valid(&self, offset: u64) -> bool { + let mut buf = [0u8; 1]; + + // don't use self.read so that if segments were used we + // check against those as well + self.as_ref().read(&mut buf[..], offset) == buf.len() + } + + fn offset_readable(&self, offset: u64) -> bool { + self.offset_valid(offset) + } + + fn offset_writable(&self, offset: u64) -> bool { + self.offset_valid(offset) + } + + fn offset_executable(&self, offset: u64) -> bool { + self.offset_valid(offset) + } + + fn offset_backed_by_file(&self, offset: u64) -> bool { + self.offset_valid(offset) + } + + fn next_valid_offset_after(&self, offset: u64) -> u64 { + let start = self.as_ref().start(); + + if offset < start { + start + } else { + offset + } + } + + #[allow(unused)] + fn modification_status(&self, offset: u64) -> ModificationStatus { + ModificationStatus::Original + } + + fn start(&self) -> u64 { + 0 + } + + fn len(&self) -> usize { + 0 + } + + fn executable(&self) -> bool { + true + } + + fn relocatable(&self) -> bool { + true + } + + fn entry_point(&self) -> u64; + fn default_endianness(&self) -> Endianness; + fn address_size(&self) -> usize; + + // TODO saving fileaccessor + fn save(&self) -> bool { + self.as_ref() + .parent_view() + .map(|bv| bv.save()) + .unwrap_or(false) + } +} + +// TODO: Copied from debuginfo.rs, this should be consolidated +struct ProgressContext(Option Result<()>>>); + +extern "C" fn cb_progress(ctxt: *mut c_void, cur: usize, max: usize) -> bool { + ffi_wrap!("BinaryViewExt::cb_progress", unsafe { + let progress = ctxt as *mut ProgressContext; + match &(*progress).0 { + Some(func) => (func)(cur, max).is_ok(), + None => true, + } + }) +} + +pub trait BinaryViewExt: BinaryViewBase { + fn file(&self) -> Ref { + unsafe { + let raw = BNGetFileForView(self.as_ref().handle); + + Ref::new(FileMetadata::from_raw(raw)) + } + } + + fn type_name(&self) -> BnString { + let ptr: *mut c_char = unsafe { BNGetViewType(self.as_ref().handle) }; + unsafe { BnString::from_raw(ptr) } + } + + fn parent_view(&self) -> Result> { + let handle = unsafe { BNGetParentView(self.as_ref().handle) }; + + if handle.is_null() { + return Err(()); + } + + unsafe { Ok(BinaryView::from_raw(handle)) } + } + + fn raw_view(&self) -> Result> { + let raw = "Raw".into_bytes_with_nul(); + + let handle = + unsafe { BNGetFileViewOfType(self.file().as_ref().handle, raw.as_ptr() as *mut _) }; + + if handle.is_null() { + return Err(()); + } + + unsafe { Ok(BinaryView::from_raw(handle)) } + } + + fn view_type(&self) -> BnString { + let ptr: *mut c_char = unsafe { BNGetViewType(self.as_ref().handle) }; + unsafe { BnString::from_raw(ptr) } + } + + /// Reads up to `len` bytes from address `offset` + fn read_vec(&self, offset: u64, len: usize) -> Vec { + let mut ret = Vec::with_capacity(len); + + unsafe { + let res; + + { + let dest_slice = ret.get_unchecked_mut(0..len); + res = self.read(dest_slice, offset); + } + + ret.set_len(res); + } + + ret + } + + /// 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 + } + } + + fn notify_data_written(&self, offset: u64, len: usize) { + unsafe { + BNNotifyDataWritten(self.as_ref().handle, offset, len); + } + } + + fn notify_data_inserted(&self, offset: u64, len: usize) { + unsafe { + BNNotifyDataInserted(self.as_ref().handle, offset, len); + } + } + + fn notify_data_removed(&self, offset: u64, len: usize) { + unsafe { + BNNotifyDataRemoved(self.as_ref().handle, offset, len as u64); + } + } + + fn offset_has_code_semantics(&self, offset: u64) -> bool { + unsafe { BNIsOffsetCodeSemantics(self.as_ref().handle, offset) } + } + + fn offset_has_writable_semantics(&self, offset: u64) -> bool { + unsafe { BNIsOffsetWritableSemantics(self.as_ref().handle, offset) } + } + + fn end(&self) -> u64 { + unsafe { BNGetEndOffset(self.as_ref().handle) } + } + + fn update_analysis_and_wait(&self) { + unsafe { + BNUpdateAnalysisAndWait(self.as_ref().handle); + } + } + + fn update_analysis(&self) { + unsafe { + BNUpdateAnalysis(self.as_ref().handle); + } + } + + fn default_arch(&self) -> Option { + unsafe { + let raw = BNGetDefaultArchitecture(self.as_ref().handle); + + if raw.is_null() { + return None; + } + + Some(CoreArchitecture::from_raw(raw)) + } + } + + fn set_default_arch(&self, arch: &A) { + unsafe { + BNSetDefaultArchitecture(self.as_ref().handle, arch.as_ref().0); + } + } + + fn default_platform(&self) -> Option> { + unsafe { + let raw = BNGetDefaultPlatform(self.as_ref().handle); + + if raw.is_null() { + return None; + } + + Some(Platform::ref_from_raw(raw)) + } + } + + fn set_default_platform(&self, plat: &Platform) { + unsafe { + BNSetDefaultPlatform(self.as_ref().handle, plat.handle); + } + } + + fn instruction_len(&self, arch: &A, addr: u64) -> Option { + unsafe { + let size = BNGetInstructionLength(self.as_ref().handle, arch.as_ref().0, addr); + + if size > 0 { + Some(size) + } else { + None + } + } + } + + fn symbol_by_address(&self, addr: u64) -> Result> { + unsafe { + let raw_sym = BNGetSymbolByAddress(self.as_ref().handle, addr, ptr::null_mut()); + + if raw_sym.is_null() { + return Err(()); + } + + Ok(Symbol::ref_from_raw(raw_sym)) + } + } + + fn symbol_by_raw_name(&self, raw_name: S) -> Result> { + let raw_name = raw_name.into_bytes_with_nul(); + + unsafe { + let raw_sym = BNGetSymbolByRawName( + self.as_ref().handle, + raw_name.as_ref().as_ptr() as *mut _, + ptr::null_mut(), + ); + + if raw_sym.is_null() { + return Err(()); + } + + Ok(Symbol::ref_from_raw(raw_sym)) + } + } + + fn symbols(&self) -> Array { + unsafe { + let mut count = 0; + let handles = BNGetSymbols(self.as_ref().handle, &mut count, ptr::null_mut()); + + Array::new(handles, count, ()) + } + } + + fn symbols_by_name(&self, name: S) -> Array { + let raw_name = name.into_bytes_with_nul(); + + unsafe { + let mut count = 0; + let handles = BNGetSymbolsByName( + self.as_ref().handle, + raw_name.as_ref().as_ptr() as *mut _, + &mut count, + ptr::null_mut(), + ); + + Array::new(handles, count, ()) + } + } + + fn symbols_in_range(&self, range: ops::Range) -> Array { + unsafe { + let mut count = 0; + let len = range.end.wrapping_sub(range.start); + let handles = BNGetSymbolsInRange( + self.as_ref().handle, + range.start, + len, + &mut count, + ptr::null_mut(), + ); + + Array::new(handles, count, ()) + } + } + + fn symbols_of_type(&self, ty: SymbolType) -> Array { + unsafe { + let mut count = 0; + let handles = + BNGetSymbolsOfType(self.as_ref().handle, ty.into(), &mut count, ptr::null_mut()); + + Array::new(handles, count, ()) + } + } + + fn symbols_of_type_in_range(&self, ty: SymbolType, range: ops::Range) -> Array { + unsafe { + let mut count = 0; + let len = range.end.wrapping_sub(range.start); + let handles = BNGetSymbolsOfTypeInRange( + self.as_ref().handle, + ty.into(), + range.start, + len, + &mut count, + ptr::null_mut(), + ); + + Array::new(handles, count, ()) + } + } + + fn define_auto_symbol(&self, sym: &Symbol) { + unsafe { + BNDefineAutoSymbol(self.as_ref().handle, sym.handle); + } + } + + fn define_auto_symbol_with_type<'a, T: Into>>( + &self, + sym: &Symbol, + plat: &Platform, + ty: T, + ) -> Result> { + let raw_type = if let Some(t) = ty.into() { + t.handle + } else { + ptr::null_mut() + }; + + unsafe { + let raw_sym = BNDefineAutoSymbolAndVariableOrFunction( + self.as_ref().handle, + plat.handle, + sym.handle, + raw_type, + ); + + if raw_sym.is_null() { + return Err(()); + } + + Ok(Symbol::ref_from_raw(raw_sym)) + } + } + + fn undefine_auto_symbol(&self, sym: &Symbol) { + unsafe { + BNUndefineAutoSymbol(self.as_ref().handle, sym.handle); + } + } + + fn define_user_symbol(&self, sym: &Symbol) { + unsafe { + BNDefineUserSymbol(self.as_ref().handle, sym.handle); + } + } + + fn undefine_user_symbol(&self, sym: &Symbol) { + unsafe { + BNUndefineUserSymbol(self.as_ref().handle, sym.handle); + } + } + + fn data_variables(&self) -> Array { + unsafe { + let mut count = 0; + let vars = BNGetDataVariables(self.as_ref().handle, &mut count); + + Array::new(vars, count, ()) + } + } + + fn define_auto_data_var(&self, dv: DataVariable) { + unsafe { + BNDefineDataVariable(self.as_ref().handle, dv.address, &mut dv.t.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) { + unsafe { + BNDefineUserDataVariable(self.as_ref().handle, dv.address, &mut dv.t.into()); + } + } + + fn undefine_auto_data_var(&self, addr: u64) { + unsafe { + BNUndefineDataVariable(self.as_ref().handle, addr); + } + } + + fn undefine_user_data_var(&self, addr: u64) { + unsafe { + BNUndefineUserDataVariable(self.as_ref().handle, addr); + } + } + + fn define_auto_type( + &self, + name: S, + source: S, + type_obj: &Type, + ) -> QualifiedName { + let mut qualified_name = QualifiedName::from(name); + let source_str = source.into_bytes_with_nul(); + let name_handle = unsafe { + let id_str = BNGenerateAutoTypeId( + source_str.as_ref().as_ptr() as *const _, + &mut qualified_name.0, + ); + BNDefineAnalysisType( + self.as_ref().handle, + id_str, + &mut qualified_name.0, + type_obj.handle, + ) + }; + QualifiedName(name_handle) + } + + fn define_user_type(&self, name: S, type_obj: &Type) { + let mut qualified_name = QualifiedName::from(name); + unsafe { + BNDefineUserAnalysisType(self.as_ref().handle, &mut qualified_name.0, type_obj.handle) + } + } + + fn define_auto_types( + &self, + names_sources_and_types: Vec<(S, S, &Type)>, + progress: Option Result<()>>>, + ) -> HashMap { + let mut names = vec![]; + let mut ids = vec![]; + let mut types = vec![]; + let mut api_types = + Vec::::with_capacity(names_sources_and_types.len()); + for (name, source, type_obj) in names_sources_and_types.into_iter() { + names.push(QualifiedName::from(name)); + ids.push(source.into_bytes_with_nul()); + types.push(type_obj); + } + + for ((name, source), type_obj) in names.iter().zip(ids.iter()).zip(types.iter()) { + api_types.push(BNQualifiedNameTypeAndId { + name: name.0, + id: source.as_ref().as_ptr() as *mut _, + type_: type_obj.handle, + }); + } + + let mut progress_raw = ProgressContext(progress); + let mut result_ids: *mut *mut c_char = ptr::null_mut(); + let mut result_names: *mut BNQualifiedName = ptr::null_mut(); + let result_count = unsafe { + BNDefineAnalysisTypes( + self.as_ref().handle, + api_types.as_mut_ptr(), + api_types.len(), + Some(cb_progress), + &mut progress_raw as *mut _ as *mut c_void, + &mut result_ids as *mut _, + &mut result_names as *mut _, + ) + }; + + let mut result = HashMap::with_capacity(result_count); + + let id_array = unsafe { Array::::new(result_ids, result_count, ()) }; + 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 + } + + fn define_user_types( + &self, + names_and_types: Vec<(S, &Type)>, + progress: Option Result<()>>>, + ) { + let mut names = vec![]; + let mut types = vec![]; + let mut api_types = Vec::::with_capacity(names_and_types.len()); + for (name, type_obj) in names_and_types.into_iter() { + names.push(QualifiedName::from(name)); + types.push(type_obj); + } + + for (name, type_obj) in names.iter().zip(types.iter()) { + api_types.push(BNQualifiedNameAndType { + name: name.0, + type_: type_obj.handle, + }); + } + + let mut progress_raw = ProgressContext(progress); + unsafe { + BNDefineUserAnalysisTypes( + self.as_ref().handle, + api_types.as_mut_ptr(), + api_types.len(), + Some(cb_progress), + &mut progress_raw as *mut _ as *mut c_void, + ) + }; + } + + fn undefine_auto_type(&self, id: S) { + let id_str = id.into_bytes_with_nul(); + unsafe { + BNUndefineAnalysisType(self.as_ref().handle, id_str.as_ref().as_ptr() as *const _); + } + } + + fn undefine_user_type(&self, name: S) { + let mut qualified_name = QualifiedName::from(name); + unsafe { BNUndefineUserAnalysisType(self.as_ref().handle, &mut qualified_name.0) } + } + + fn types(&self) -> Array { + unsafe { + let mut count = 0usize; + let types = BNGetAnalysisTypeList(self.as_ref().handle, &mut count); + Array::new(types, count, ()) + } + } + + fn dependency_sorted_types(&self) -> Array { + unsafe { + let mut count = 0usize; + let types = BNGetAnalysisDependencySortedTypeList(self.as_ref().handle, &mut count); + Array::new(types, count, ()) + } + } + + fn get_type_by_name(&self, name: S) -> Option> { + unsafe { + let mut qualified_name = QualifiedName::from(name); + let type_handle = BNGetAnalysisTypeByName(self.as_ref().handle, &mut qualified_name.0); + if type_handle.is_null() { + return None; + } + Some(Type::ref_from_raw(type_handle)) + } + } + + fn get_type_by_ref(&self, ref_: &NamedTypeReference) -> Option> { + unsafe { + let type_handle = BNGetAnalysisTypeByRef(self.as_ref().handle, ref_.handle); + if type_handle.is_null() { + return None; + } + Some(Type::ref_from_raw(type_handle)) + } + } + + fn get_type_by_id(&self, id: S) -> Option> { + unsafe { + let id_str = id.into_bytes_with_nul(); + let type_handle = + BNGetAnalysisTypeById(self.as_ref().handle, id_str.as_ref().as_ptr() as *mut _); + if type_handle.is_null() { + return None; + } + Some(Type::ref_from_raw(type_handle)) + } + } + + fn get_type_name_by_id(&self, id: S) -> Option { + unsafe { + let id_str = id.into_bytes_with_nul(); + let name_handle = + BNGetAnalysisTypeNameById(self.as_ref().handle, id_str.as_ref().as_ptr() as *mut _); + let name = QualifiedName(name_handle); + if name.strings().is_empty() { + return None; + } + Some(name) + } + } + + fn get_type_id(&self, name: S) -> Option { + unsafe { + let mut qualified_name = QualifiedName::from(name); + let id_cstr = BNGetAnalysisTypeId(self.as_ref().handle, &mut qualified_name.0); + let id = BnString::from_raw(id_cstr); + if id.is_empty() { + return None; + } + Some(id) + } + } + + fn is_type_auto_defined(&self, name: S) -> bool { + unsafe { + let mut qualified_name = QualifiedName::from(name); + BNIsAnalysisTypeAutoDefined(self.as_ref().handle, &mut qualified_name.0) + } + } + + fn segments(&self) -> Array { + unsafe { + let mut count = 0; + let segs = BNGetSegments(self.as_ref().handle, &mut count); + + Array::new(segs, count, ()) + } + } + + fn segment_at(&self, addr: u64) -> Option { + unsafe { + let raw_seg = BNGetSegmentAt(self.as_ref().handle, addr); + if !raw_seg.is_null() { + Some(Segment::from_raw(raw_seg)) + } else { + None + } + } + } + + fn add_segment(&self, segment: SegmentBuilder) { + segment.create(self.as_ref()); + } + + fn add_section(&self, section: SectionBuilder) { + section.create(self.as_ref()); + } + + fn remove_auto_section(&self, name: S) { + let name = name.into_bytes_with_nul(); + let name_ptr = name.as_ref().as_ptr() as *mut _; + + unsafe { + BNRemoveAutoSection(self.as_ref().handle, name_ptr); + } + } + + fn remove_user_section(&self, name: S) { + let name = name.into_bytes_with_nul(); + let name_ptr = name.as_ref().as_ptr() as *mut _; + + unsafe { + BNRemoveUserSection(self.as_ref().handle, name_ptr); + } + } + + fn section_by_name(&self, name: S) -> Result
{ + unsafe { + let raw_name = name.into_bytes_with_nul(); + let name_ptr = raw_name.as_ref().as_ptr() as *mut _; + let raw_section = BNGetSectionByName(self.as_ref().handle, name_ptr); + + if raw_section.is_null() { + return Err(()); + } + + Ok(Section::from_raw(raw_section)) + } + } + + fn sections(&self) -> Array
{ + unsafe { + let mut count = 0; + let sections = BNGetSections(self.as_ref().handle, &mut count); + + Array::new(sections, count, ()) + } + } + + fn sections_at(&self, addr: u64) -> Array
{ + unsafe { + let mut count = 0; + let sections = BNGetSectionsAt(self.as_ref().handle, addr, &mut count); + + Array::new(sections, count, ()) + } + } + + fn add_auto_function(&self, plat: &Platform, addr: u64) -> Option> { + unsafe { + let handle = BNAddFunctionForAnalysis( + self.as_ref().handle, + plat.handle, + addr, + false, + ptr::null_mut(), + ); + + if handle.is_null() { + return None; + } + + Some(Function::from_raw(handle)) + } + } + + fn add_function_with_type( + &self, + plat: &Platform, + addr: u64, + auto_discovered: bool, + func_type: Option<&Type>, + ) -> Option> { + unsafe { + let func_type = match func_type { + Some(func_type) => func_type.handle, + None => ptr::null_mut(), + }; + + let handle = BNAddFunctionForAnalysis( + self.as_ref().handle, + plat.handle, + addr, + auto_discovered, + func_type, + ); + + if handle.is_null() { + return None; + } + + Some(Function::from_raw(handle)) + } + } + + fn add_entry_point(&self, plat: &Platform, addr: u64) { + unsafe { + BNAddEntryPointForAnalysis(self.as_ref().handle, plat.handle, addr); + } + } + + fn create_user_function(&self, plat: &Platform, addr: u64) -> Result> { + unsafe { + let func = BNCreateUserFunction(self.as_ref().handle, plat.handle, addr); + + if func.is_null() { + return Err(()); + } + + Ok(Function::from_raw(func)) + } + } + + fn has_functions(&self) -> bool { + unsafe { BNHasFunctions(self.as_ref().handle) } + } + + fn entry_point_function(&self) -> Result> { + unsafe { + let func = BNGetAnalysisEntryPoint(self.as_ref().handle); + + if func.is_null() { + return Err(()); + } + + Ok(Function::from_raw(func)) + } + } + + fn functions(&self) -> Array { + unsafe { + let mut count = 0; + let functions = BNGetAnalysisFunctionList(self.as_ref().handle, &mut count); + + Array::new(functions, count, ()) + } + } + + /// List of functions *starting* at `addr` + fn functions_at(&self, addr: u64) -> Array { + unsafe { + let mut count = 0; + let functions = + BNGetAnalysisFunctionsForAddress(self.as_ref().handle, addr, &mut count); + + Array::new(functions, count, ()) + } + } + + // List of functions containing `addr` + fn functions_containing(&self, addr: u64) -> Array { + unsafe { + let mut count = 0; + let functions = + BNGetAnalysisFunctionsContainingAddress(self.as_ref().handle, addr, &mut count); + + Array::new(functions, count, ()) + } + } + + fn function_at(&self, platform: &Platform, addr: u64) -> Result> { + unsafe { + let handle = BNGetAnalysisFunction(self.as_ref().handle, platform.handle, addr); + + if handle.is_null() { + return Err(()); + } + + Ok(Function::from_raw(handle)) + } + } + + fn basic_blocks_containing(&self, addr: u64) -> Array> { + unsafe { + let mut count = 0; + let blocks = BNGetBasicBlocksForAddress(self.as_ref().handle, addr, &mut count); + + Array::new(blocks, count, NativeBlock::new()) + } + } + + fn basic_blocks_starting_at(&self, addr: u64) -> Array> { + unsafe { + let mut count = 0; + let blocks = BNGetBasicBlocksStartingAtAddress(self.as_ref().handle, addr, &mut count); + + Array::new(blocks, count, NativeBlock::new()) + } + } + + fn is_new_auto_function_analysis_suppressed(&self) -> bool { + unsafe { BNGetNewAutoFunctionAnalysisSuppressed(self.as_ref().handle) } + } + + fn set_new_auto_function_analysis_suppressed(&self, suppress: bool) { + unsafe { + BNSetNewAutoFunctionAnalysisSuppressed(self.as_ref().handle, suppress); + } + } + + fn read_buffer(&self, offset: u64, len: usize) -> Result { + let read_buffer = unsafe { BNReadViewBuffer(self.as_ref().handle, offset, len) }; + if read_buffer.is_null() { + Err(()) + } else { + Ok(DataBuffer::from_raw(read_buffer)) + } + } + + fn debug_info(&self) -> Ref { + unsafe { DebugInfo::from_raw(BNGetDebugInfo(self.as_ref().handle)) } + } + + fn set_debug_info(&self, debug_info: &DebugInfo) { + unsafe { BNSetDebugInfo(self.as_ref().handle, debug_info.handle) } + } + + fn apply_debug_info(&self, debug_info: &DebugInfo) { + unsafe { BNApplyDebugInfo(self.as_ref().handle, debug_info.handle) } + } + + fn show_graph_report(&self, raw_name: S, graph: FlowGraph) { + let raw_name = raw_name.into_bytes_with_nul(); + unsafe { + BNShowGraphReport( + self.as_ref().handle, + raw_name.as_ref().as_ptr() as *mut _, + graph.handle, + ); + } + } + + fn load_settings(&self, view_type_name: S) -> Result> { + let view_type_name = view_type_name.into_bytes_with_nul(); + let settings_handle = unsafe { + BNBinaryViewGetLoadSettings( + self.as_ref().handle, + view_type_name.as_ref().as_ptr() as *mut _, + ) + }; + + if settings_handle.is_null() { + Err(()) + } else { + Ok(unsafe { Settings::from_raw(settings_handle) }) + } + } + + fn set_load_settings(&self, view_type_name: S, settings: &Settings) { + let view_type_name = view_type_name.into_bytes_with_nul(); + + unsafe { + BNBinaryViewSetLoadSettings( + self.as_ref().handle, + view_type_name.as_ref().as_ptr() as *mut _, + settings.handle, + ) + }; + } + + /// Creates a new [TagType] and adds it to the view. + /// + /// # Arguments + /// * `name` - the name for the tag + /// * `icon` - the icon (recommended 1 emoji or 2 chars) for the tag + fn create_tag_type( + &self, + name: N, + icon: I, + ) -> Ref { + let tag_type = TagType::create(self.as_ref(), name, icon); + unsafe { + BNAddTagType(self.as_ref().handle, tag_type.handle); + } + tag_type + } + + /// Removes a [TagType] and all tags that use it + fn remove_tag_type(&self, tag_type: &TagType) { + unsafe { BNRemoveTagType(self.as_ref().handle, tag_type.handle) } + } + + /// Get a tag type by its name. + fn get_tag_type(&self, name: S) -> Option> { + let name = name.into_bytes_with_nul(); + + unsafe { + let handle = BNGetTagType(self.as_ref().handle, name.as_ref().as_ptr() as *mut _); + if handle.is_null() { + return None; + } + Some(TagType::from_raw(handle)) + } + } + + /// Get a tag by its id. + /// + /// Note this does not tell you anything about where it is used. + fn get_tag(&self, id: S) -> Option> { + let id = id.into_bytes_with_nul(); + unsafe { + let handle = BNGetTag(self.as_ref().handle, id.as_ref().as_ptr() as *mut _); + if handle.is_null() { + return None; + } + Some(Tag::from_raw(handle)) + } + } + + /// Creates and adds a tag to an address + /// + /// User tag creations will be added to the undo buffer + fn add_tag(&self, addr: u64, t: &TagType, data: S, user: bool) { + let tag = Tag::new(t, data); + + unsafe { BNAddTag(self.as_ref().handle, tag.handle, user) } + + if user { + unsafe { BNAddUserDataTag(self.as_ref().handle, addr, tag.handle) } + } else { + unsafe { BNAddAutoDataTag(self.as_ref().handle, addr, tag.handle) } + } + } + + /// removes a Tag object at a data address. + fn remove_auto_data_tag(&self, addr: u64, tag: &Tag) { + unsafe { BNRemoveAutoDataTag(self.as_ref().handle, addr, tag.handle) } + } + + /// removes a Tag object at a data address. + /// Since this removes a user tag, it will be added to the current undo buffer. + fn remove_user_data_tag(&self, addr: u64, tag: &Tag) { + unsafe { BNRemoveUserDataTag(self.as_ref().handle, addr, tag.handle) } + } + + /// Retrieves a list of the next disassembly lines. + /// + /// `get_next_linear_disassembly_lines` retrieves an [Array] over [LinearDisassemblyLine] objects for the + /// next disassembly lines, and updates the [LinearViewCursor] passed in. This function can be called + /// repeatedly to get more lines of linear disassembly. + /// + /// # Arguments + /// * `pos` - Position to retrieve linear disassembly lines from + fn get_next_linear_disassembly_lines( + &self, + pos: &mut LinearViewCursor, + ) -> Array { + let mut result = unsafe { Array::new(std::ptr::null_mut(), 0, ()) }; + + while result.is_empty() { + result = pos.lines(); + if !pos.next() { + return result; + } + } + + result + } + + /// Retrieves a list of the previous disassembly lines. + /// + /// `get_previous_linear_disassembly_lines` retrieves an [Array] over [LinearDisassemblyLine] objects for the + /// previous disassembly lines, and updates the [LinearViewCursor] passed in. This function can be called + /// repeatedly to get more lines of linear disassembly. + /// + /// # Arguments + /// * `pos` - Position to retrieve linear disassembly lines relative to + fn get_previous_linear_disassembly_lines( + &self, + pos: &mut LinearViewCursor, + ) -> Array { + let mut result = unsafe { Array::new(std::ptr::null_mut(), 0, ()) }; + while result.is_empty() { + if !pos.previous() { + return result; + } + + result = pos.lines(); + } + + result + } + + fn query_metadata(&self, key: S) -> Option> { + let value: *mut BNMetadata = unsafe { + BNBinaryViewQueryMetadata( + self.as_ref().handle, + key.into_bytes_with_nul().as_ref().as_ptr() as *const c_char, + ) + }; + if value.is_null() { + None + } else { + Some(unsafe { Metadata::ref_from_raw(value) }) + } + } + + fn get_metadata(&self, key: S) -> Option> + where + T: for<'a> TryFrom<&'a Metadata>, + { + self.query_metadata(key) + .map(|md| T::try_from(md.as_ref()).map_err(|_| ())) + } + + fn store_metadata(&self, key: S, value: V, is_auto: bool) + where + V: Into>, + { + let md = value.into(); + unsafe { + BNBinaryViewStoreMetadata( + self.as_ref().handle, + key.into_bytes_with_nul().as_ref().as_ptr() as *const c_char, + md.as_ref().handle, + is_auto, + ) + }; + } + + fn remove_metadata(&self, key: S) { + unsafe { + BNBinaryViewRemoveMetadata( + self.as_ref().handle, + key.into_bytes_with_nul().as_ref().as_ptr() as *const c_char, + ) + }; + } + + /// Retrieves a list of [CodeReference]s pointing to a given address. + fn get_code_refs(&self, addr: u64) -> Array { + unsafe { + let mut count = 0; + let handle = BNGetCodeReferences(self.as_ref().handle, addr, &mut count); + Array::new(handle, count, ()) + } + } + + /// Retrieves a list of [CodeReference]s pointing into a given [Range]. + fn get_code_refs_in_range(&self, range: Range) -> Array { + unsafe { + let mut count = 0; + let handle = BNGetCodeReferencesInRange( + self.as_ref().handle, + range.start, + range.end - range.start, + &mut count, + ); + Array::new(handle, count, ()) + } + } + + /// Retrieves a list of [DataReference]s pointing to a given address. + fn get_data_refs(&self, addr: u64) -> Array { + unsafe { + let mut count = 0; + let handle = BNGetDataReferences(self.as_ref().handle, addr, &mut count); + Array::new(handle, count, ()) + } + } + + /// Retrieves a list of [DataReference]s originating from a given address. + fn get_data_refs_from(&self, addr: u64) -> Array { + unsafe { + let mut count = 0; + let handle = BNGetDataReferencesFrom(self.as_ref().handle, addr, &mut count); + Array::new(handle, count, ()) + } + } + + /// Retrieves a list of [DataReference]s pointing into a given [Range]. + fn get_data_refs_in_range(&self, range: Range) -> Array { + unsafe { + let mut count = 0; + let handle = BNGetDataReferencesInRange( + self.as_ref().handle, + range.start, + range.end - range.start, + &mut count, + ); + Array::new(handle, count, ()) + } + } + + /// Retrieves a list of [CodeReference]s for locations in code that use a given named type. + /// + /// TODO: It might be cleaner if this used an already allocated type from the core and + /// used its name instead of this slightly-gross [QualifiedName] hack. Since the returned + /// object doesn't have any [QualifiedName], I'm assuming the core does not alias + /// the [QualifiedName] we pass to it and it is safe to destroy it on [Drop], as in this function. + fn get_code_refs_for_type(&self, name: B) -> Array { + unsafe { + let mut count = 0; + let q_name = &mut QualifiedName::from(name).0; + let handle = BNGetCodeReferencesForType( + self.as_ref().handle, + q_name as *mut BNQualifiedName, + &mut count, + ); + Array::new(handle, count, ()) + } + } + /// Retrieves a list of [DataReference]s instances of a given named type in data. + /// + /// TODO: It might be cleaner if this used an already allocated type from the core and + /// used its name instead of this slightly-gross [QualifiedName] hack. Since the returned + /// object doesn't have any [QualifiedName], I'm assuming the core does not alias + /// the [QualifiedName] we pass to it and it is safe to destroy it on [Drop], as in this function. + fn get_data_refs_for_type(&self, name: B) -> Array { + unsafe { + let mut count = 0; + let q_name = &mut QualifiedName::from(name).0; + let handle = BNGetDataReferencesForType( + self.as_ref().handle, + q_name as *mut BNQualifiedName, + &mut count, + ); + Array::new(handle, count, ()) + } + } + + fn get_relocations_at(&self, addr: u64) -> Array { + unsafe { + let mut count = 0; + let handle = BNGetRelocationsAt(self.as_ref().handle, addr, &mut count); + Array::new(handle, count, ()) + } + } +} + +impl BinaryViewExt for T {} + +#[derive(PartialEq, Eq, Hash)] +pub struct BinaryView { + pub(crate) handle: *mut BNBinaryView, +} + +impl BinaryView { + pub(crate) unsafe fn from_raw(handle: *mut BNBinaryView) -> Ref { + debug_assert!(!handle.is_null()); + + Ref::new(Self { handle }) + } + + pub fn from_filename( + meta: &mut FileMetadata, + filename: S, + ) -> Result> { + let file = filename.into_bytes_with_nul(); + + let handle = unsafe { + BNCreateBinaryDataViewFromFilename(meta.handle, file.as_ref().as_ptr() as *mut _) + }; + + if handle.is_null() { + return Err(()); + } + + unsafe { Ok(Ref::new(Self { handle })) } + } + + pub fn from_accessor(meta: &FileMetadata, file: &mut FileAccessor) -> Result> { + let handle = + unsafe { BNCreateBinaryDataViewFromFile(meta.handle, &mut file.api_object as *mut _) }; + + if handle.is_null() { + return Err(()); + } + + unsafe { Ok(Ref::new(Self { handle })) } + } + + pub fn from_data(meta: &FileMetadata, data: &[u8]) -> Result> { + let handle = unsafe { + BNCreateBinaryDataViewFromData(meta.handle, data.as_ptr() as *mut _, data.len()) + }; + + if handle.is_null() { + return Err(()); + } + + unsafe { Ok(Ref::new(Self { handle })) } + } +} + +impl BinaryViewBase for BinaryView { + fn read(&self, buf: &mut [u8], offset: u64) -> usize { + unsafe { BNReadViewData(self.handle, buf.as_mut_ptr() as *mut _, offset, buf.len()) } + } + + fn write(&self, offset: u64, data: &[u8]) -> usize { + unsafe { BNWriteViewData(self.handle, offset, data.as_ptr() as *const _, data.len()) } + } + + fn insert(&self, offset: u64, data: &[u8]) -> usize { + unsafe { BNInsertViewData(self.handle, offset, data.as_ptr() as *const _, data.len()) } + } + + fn remove(&self, offset: u64, len: usize) -> usize { + unsafe { BNRemoveViewData(self.handle, offset, len as u64) } + } + + fn modification_status(&self, offset: u64) -> ModificationStatus { + unsafe { BNGetModification(self.handle, offset) } + } + + fn offset_valid(&self, offset: u64) -> bool { + unsafe { BNIsValidOffset(self.handle, offset) } + } + + fn offset_readable(&self, offset: u64) -> bool { + unsafe { BNIsOffsetReadable(self.handle, offset) } + } + + fn offset_writable(&self, offset: u64) -> bool { + unsafe { BNIsOffsetWritable(self.handle, offset) } + } + + fn offset_executable(&self, offset: u64) -> bool { + unsafe { BNIsOffsetExecutable(self.handle, offset) } + } + + fn offset_backed_by_file(&self, offset: u64) -> bool { + unsafe { BNIsOffsetBackedByFile(self.handle, offset) } + } + + fn next_valid_offset_after(&self, offset: u64) -> u64 { + unsafe { BNGetNextValidOffset(self.handle, offset) } + } + + fn default_endianness(&self) -> Endianness { + unsafe { BNGetDefaultEndianness(self.handle) } + } + + fn relocatable(&self) -> bool { + unsafe { BNIsRelocatable(self.handle) } + } + + fn address_size(&self) -> usize { + unsafe { BNGetViewAddressSize(self.handle) } + } + + fn start(&self) -> u64 { + unsafe { BNGetStartOffset(self.handle) } + } + + fn len(&self) -> usize { + unsafe { BNGetViewLength(self.handle) as usize } + } + + fn entry_point(&self) -> u64 { + unsafe { BNGetEntryPoint(self.handle) } + } + + fn executable(&self) -> bool { + unsafe { BNIsExecutableView(self.handle) } + } +} + +unsafe impl RefCountable for BinaryView { + unsafe fn inc_ref(handle: &Self) -> Ref { + Ref::new(Self { + handle: BNNewViewReference(handle.handle), + }) + } + + unsafe fn dec_ref(handle: &Self) { + BNFreeBinaryView(handle.handle); + } +} + +impl AsRef for BinaryView { + fn as_ref(&self) -> &Self { + self + } +} + +impl ToOwned for BinaryView { + type Owned = Ref; + + fn to_owned(&self) -> Self::Owned { + unsafe { RefCountable::inc_ref(self) } + } +} + +unsafe impl Send for BinaryView {} +unsafe impl Sync for BinaryView {} + +impl std::fmt::Debug for BinaryView { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!( + f, + "BinaryView (type: `{}`): '{}', len {:#x}", + self.view_type(), + self.file().filename(), + self.len() + ) + } +} diff --git a/src/binarywriter.rs b/src/binarywriter.rs new file mode 100644 index 0000000..802a9b3 --- /dev/null +++ b/src/binarywriter.rs @@ -0,0 +1,99 @@ +// 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. + +//! A convenience class for writing binary data + +use binaryninjacore_sys::*; + +use crate::binaryview::BinaryView; +use crate::Endianness; + +use std::io::{Seek, SeekFrom, Write}; + +pub struct BinaryWriter { + handle: *mut BNBinaryWriter, +} + +impl BinaryWriter { + pub fn new(view: &BinaryView, endian: Endianness) -> Self { + let handle = unsafe { BNCreateBinaryWriter(view.handle) }; + unsafe { + BNSetBinaryWriterEndianness(handle, endian); + } + Self { handle } + } + + pub fn endian(&self) -> Endianness { + unsafe { BNGetBinaryWriterEndianness(self.handle) } + } + + pub fn set_endian(&self, endian: Endianness) { + unsafe { BNSetBinaryWriterEndianness(self.handle, endian) } + } + + pub fn offset(&self) -> u64 { + unsafe { BNGetWriterPosition(self.handle) } + } +} + +impl Seek for BinaryWriter { + /// Seek to the specified position. + /// + /// # Errors + /// Seeking relative to [SeekFrom::End] is unsupported and will return an error. + fn seek(&mut self, pos: SeekFrom) -> std::io::Result { + unsafe { + match pos { + SeekFrom::Current(offset) => BNSeekBinaryWriterRelative(self.handle, offset), + SeekFrom::Start(offset) => BNSeekBinaryWriter(self.handle, offset), + _ => { + return Err(std::io::Error::new( + std::io::ErrorKind::Unsupported, + "Cannot seek end of BinaryWriter", + )) + } + }; + } + + Ok(self.offset()) + } +} + +impl Write for BinaryWriter { + fn write(&mut self, buf: &[u8]) -> std::io::Result { + let len = buf.len(); + let result = unsafe { BNWriteData(self.handle, buf.as_ptr() as *mut _, len) }; + if !result { + Err(std::io::Error::new( + std::io::ErrorKind::Other, + "write out of bounds", + )) + } else { + Ok(len) + } + } + + fn flush(&mut self) -> std::io::Result<()> { + Ok(()) + } +} + +impl Drop for BinaryWriter { + fn drop(&mut self) { + unsafe { BNFreeBinaryWriter(self.handle) } + } +} + +unsafe impl Sync for BinaryWriter {} +unsafe impl Send for BinaryWriter {} diff --git a/src/callingconvention.rs b/src/callingconvention.rs new file mode 100644 index 0000000..56755b3 --- /dev/null +++ b/src/callingconvention.rs @@ -0,0 +1,872 @@ +// Copyright 2021-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. + +//! Contains and provides information about different systems' calling conventions to analysis. + +use std::borrow::Borrow; +use std::fmt::{Debug, Formatter}; +use std::marker::PhantomData; +use std::mem; +use std::os::raw::c_void; +use std::ptr; +use std::slice; + +use binaryninjacore_sys::*; + +use crate::architecture::{Architecture, ArchitectureExt, CoreArchitecture, Register}; +use crate::rc::{ + CoreArrayProvider, CoreArrayWrapper, CoreOwnedArrayProvider, Guard, Ref, RefCountable, +}; +use crate::string::*; + +// TODO +// force valid registers once Arch has _from_id methods +// CallingConvention impl +// dataflow callbacks + +pub trait CallingConventionBase: Sync { + type Arch: Architecture; + + fn caller_saved_registers(&self) -> Vec<::Register>; + fn callee_saved_registers(&self) -> Vec<::Register>; + fn int_arg_registers(&self) -> Vec<::Register>; + fn float_arg_registers(&self) -> Vec<::Register>; + + fn arg_registers_shared_index(&self) -> bool; + fn reserved_stack_space_for_arg_registers(&self) -> bool; + fn stack_adjusted_on_return(&self) -> bool; + fn is_eligible_for_heuristics(&self) -> bool; + + fn return_int_reg(&self) -> Option<::Register>; + fn return_hi_int_reg(&self) -> Option<::Register>; + fn return_float_reg(&self) -> Option<::Register>; + + fn global_pointer_reg(&self) -> Option<::Register>; + + fn implicitly_defined_registers(&self) -> Vec<::Register>; + fn are_argument_registers_used_for_var_args(&self) -> bool; +} + +pub fn register_calling_convention(arch: &A, name: N, cc: C) -> Ref> +where + A: Architecture, + N: BnStrCompatible, + C: 'static + CallingConventionBase, +{ + struct CustomCallingConventionContext + where + C: CallingConventionBase, + { + raw_handle: *mut BNCallingConvention, + cc: C, + } + + extern "C" fn cb_free(ctxt: *mut c_void) + where + C: CallingConventionBase, + { + ffi_wrap!("CallingConvention::free", unsafe { + let _ctxt = Box::from_raw(ctxt as *mut CustomCallingConventionContext); + }) + } + + fn alloc_register_list + ExactSizeIterator>( + items: I, + count: &mut usize, + ) -> *mut u32 { + let len = items.len(); + *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) } + } + } + + extern "C" fn cb_free_register_list(_ctxt: *mut c_void, regs: *mut u32) { + ffi_wrap!("CallingConvention::free_register_list", unsafe { + if regs.is_null() { + return; + } + + 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); + }) + } + + extern "C" fn cb_caller_saved(ctxt: *mut c_void, count: *mut usize) -> *mut u32 + where + C: CallingConventionBase, + { + ffi_wrap!("CallingConvention::caller_saved_registers", unsafe { + let ctxt = &*(ctxt as *mut CustomCallingConventionContext); + let regs = ctxt.cc.caller_saved_registers(); + + alloc_register_list(regs.iter().map(|r| r.id()), &mut *count) + }) + } + + extern "C" fn cb_callee_saved(ctxt: *mut c_void, count: *mut usize) -> *mut u32 + where + C: CallingConventionBase, + { + ffi_wrap!("CallingConvention::_callee_saved_registers", unsafe { + let ctxt = &*(ctxt as *mut CustomCallingConventionContext); + let regs = ctxt.cc.callee_saved_registers(); + + alloc_register_list(regs.iter().map(|r| r.id()), &mut *count) + }) + } + + extern "C" fn cb_int_args(ctxt: *mut c_void, count: *mut usize) -> *mut u32 + where + C: CallingConventionBase, + { + ffi_wrap!("CallingConvention::int_arg_registers", unsafe { + let ctxt = &*(ctxt as *mut CustomCallingConventionContext); + let regs = ctxt.cc.int_arg_registers(); + + alloc_register_list(regs.iter().map(|r| r.id()), &mut *count) + }) + } + + extern "C" fn cb_float_args(ctxt: *mut c_void, count: *mut usize) -> *mut u32 + where + C: CallingConventionBase, + { + ffi_wrap!("CallingConvention::float_arg_registers", unsafe { + let ctxt = &*(ctxt as *mut CustomCallingConventionContext); + let regs = ctxt.cc.float_arg_registers(); + + alloc_register_list(regs.iter().map(|r| r.id()), &mut *count) + }) + } + + extern "C" fn cb_arg_shared_index(ctxt: *mut c_void) -> bool + where + C: CallingConventionBase, + { + ffi_wrap!("CallingConvention::arg_registers_shared_index", unsafe { + let ctxt = &*(ctxt as *mut CustomCallingConventionContext); + + ctxt.cc.arg_registers_shared_index() + }) + } + + extern "C" fn cb_stack_reserved_arg_regs(ctxt: *mut c_void) -> bool + where + C: CallingConventionBase, + { + ffi_wrap!( + "CallingConvention::reserved_stack_space_for_arg_registers", + unsafe { + let ctxt = &*(ctxt as *mut CustomCallingConventionContext); + + ctxt.cc.reserved_stack_space_for_arg_registers() + } + ) + } + + extern "C" fn cb_stack_adjusted_on_return(ctxt: *mut c_void) -> bool + where + C: CallingConventionBase, + { + ffi_wrap!("CallingConvention::stack_adjusted_on_return", unsafe { + let ctxt = &*(ctxt as *mut CustomCallingConventionContext); + + ctxt.cc.stack_adjusted_on_return() + }) + } + + extern "C" fn cb_is_eligible_for_heuristics(ctxt: *mut c_void) -> bool + where + C: CallingConventionBase, + { + ffi_wrap!("CallingConvention::is_eligible_for_heuristics", unsafe { + let ctxt = &*(ctxt as *mut CustomCallingConventionContext); + + ctxt.cc.is_eligible_for_heuristics() + }) + } + + extern "C" fn cb_return_int_reg(ctxt: *mut c_void) -> u32 + where + C: CallingConventionBase, + { + ffi_wrap!("CallingConvention::return_int_reg", unsafe { + let ctxt = &*(ctxt as *mut CustomCallingConventionContext); + + match ctxt.cc.return_int_reg() { + Some(r) => r.id(), + _ => 0xffff_ffff, + } + }) + } + + extern "C" fn cb_return_hi_int_reg(ctxt: *mut c_void) -> u32 + where + C: CallingConventionBase, + { + ffi_wrap!("CallingConvention::return_hi_int_reg", unsafe { + let ctxt = &*(ctxt as *mut CustomCallingConventionContext); + + match ctxt.cc.return_hi_int_reg() { + Some(r) => r.id(), + _ => 0xffff_ffff, + } + }) + } + + extern "C" fn cb_return_float_reg(ctxt: *mut c_void) -> u32 + where + C: CallingConventionBase, + { + ffi_wrap!("CallingConvention::return_float_reg", unsafe { + let ctxt = &*(ctxt as *mut CustomCallingConventionContext); + + match ctxt.cc.return_float_reg() { + Some(r) => r.id(), + _ => 0xffff_ffff, + } + }) + } + + extern "C" fn cb_global_pointer_reg(ctxt: *mut c_void) -> u32 + where + C: CallingConventionBase, + { + ffi_wrap!("CallingConvention::global_pointer_reg", unsafe { + let ctxt = &*(ctxt as *mut CustomCallingConventionContext); + + match ctxt.cc.global_pointer_reg() { + Some(r) => r.id(), + _ => 0xffff_ffff, + } + }) + } + + extern "C" fn cb_implicitly_defined_registers( + ctxt: *mut c_void, + count: *mut usize, + ) -> *mut u32 + where + C: CallingConventionBase, + { + ffi_wrap!("CallingConvention::implicitly_defined_registers", unsafe { + let ctxt = &*(ctxt as *mut CustomCallingConventionContext); + let regs = ctxt.cc.implicitly_defined_registers(); + + alloc_register_list(regs.iter().map(|r| r.id()), &mut *count) + }) + } + + #[allow(clippy::extra_unused_type_parameters)] // TODO : This is bad; need to finish this stub + extern "C" fn cb_incoming_reg_value( + _ctxt: *mut c_void, + _reg: u32, + _func: *mut BNFunction, + val: *mut BNRegisterValue, + ) where + C: CallingConventionBase, + { + ffi_wrap!("CallingConvention::incoming_reg_value", unsafe { + //let ctxt = &*(ctxt as *mut CustomCallingConventionContext); + let val = &mut *val; + + val.state = BNRegisterValueType::EntryValue; + val.value = _reg as i64; + }) + } + + #[allow(clippy::extra_unused_type_parameters)] // TODO : This is bad; need to finish this stub + extern "C" fn cb_incoming_flag_value( + _ctxt: *mut c_void, + _flag: u32, + _func: *mut BNFunction, + val: *mut BNRegisterValue, + ) where + C: CallingConventionBase, + { + ffi_wrap!("CallingConvention::incoming_flag_value", unsafe { + //let ctxt = &*(ctxt as *mut CustomCallingConventionContext); + let val = &mut *val; + + val.state = BNRegisterValueType::EntryValue; + val.value = _flag as i64; + }) + } + + extern "C" fn cb_incoming_var_for_param( + ctxt: *mut c_void, + var: *const BNVariable, + _func: *mut BNFunction, + param: *mut BNVariable, + ) where + C: CallingConventionBase, + { + ffi_wrap!("CallingConvention::incoming_var_for_param", unsafe { + let ctxt = &*(ctxt as *mut CustomCallingConventionContext); + ptr::write( + param, + BNGetDefaultIncomingVariableForParameterVariable(ctxt.raw_handle, var), + ); + }) + } + + extern "C" fn cb_incoming_param_for_var( + ctxt: *mut c_void, + var: *const BNVariable, + _func: *mut BNFunction, + param: *mut BNVariable, + ) where + C: CallingConventionBase, + { + ffi_wrap!("CallingConvention::incoming_param_for_var", unsafe { + let ctxt = &*(ctxt as *mut CustomCallingConventionContext); + ptr::write( + param, + BNGetDefaultParameterVariableForIncomingVariable(ctxt.raw_handle, var), + ); + }) + } + + extern "C" fn cb_are_argument_registers_used_for_var_args(ctxt: *mut c_void) -> bool + where + C: CallingConventionBase, + { + ffi_wrap!( + "CallingConvention::are_argument_registers_used_for_var_args", + unsafe { + let ctxt = &*(ctxt as *mut CustomCallingConventionContext); + + ctxt.cc.are_argument_registers_used_for_var_args() + } + ) + } + + let name = name.into_bytes_with_nul(); + let raw = Box::into_raw(Box::new(CustomCallingConventionContext { + raw_handle: ptr::null_mut(), + cc, + })); + let mut cc = BNCustomCallingConvention { + context: raw as *mut _, + + freeObject: Some(cb_free::), + + getCallerSavedRegisters: Some(cb_caller_saved::), + getCalleeSavedRegisters: Some(cb_callee_saved::), + getIntegerArgumentRegisters: Some(cb_int_args::), + getFloatArgumentRegisters: Some(cb_float_args::), + freeRegisterList: Some(cb_free_register_list), + + areArgumentRegistersSharedIndex: Some(cb_arg_shared_index::), + isStackReservedForArgumentRegisters: Some(cb_stack_reserved_arg_regs::), + isStackAdjustedOnReturn: Some(cb_stack_adjusted_on_return::), + isEligibleForHeuristics: Some(cb_is_eligible_for_heuristics::), + + getIntegerReturnValueRegister: Some(cb_return_int_reg::), + getHighIntegerReturnValueRegister: Some(cb_return_hi_int_reg::), + getFloatReturnValueRegister: Some(cb_return_float_reg::), + getGlobalPointerRegister: Some(cb_global_pointer_reg::), + + getImplicitlyDefinedRegisters: Some(cb_implicitly_defined_registers::), + getIncomingRegisterValue: Some(cb_incoming_reg_value::), + getIncomingFlagValue: Some(cb_incoming_flag_value::), + getIncomingVariableForParameterVariable: Some(cb_incoming_var_for_param::), + getParameterVariableForIncomingVariable: Some(cb_incoming_param_for_var::), + + areArgumentRegistersUsedForVarArgs: Some(cb_are_argument_registers_used_for_var_args::), + }; + + unsafe { + let cc_name = name.as_ref().as_ptr() as *mut _; + let result = BNCreateCallingConvention(arch.as_ref().0, cc_name, &mut cc); + + assert!(!result.is_null()); + + (*raw).raw_handle = result; + + BNRegisterCallingConvention(arch.as_ref().0, result); + + Ref::new(CallingConvention { + handle: result, + arch_handle: arch.handle(), + _arch: PhantomData, + }) + } +} + +pub struct CallingConvention { + pub(crate) handle: *mut BNCallingConvention, + pub(crate) arch_handle: A::Handle, + _arch: PhantomData<*mut A>, +} + +unsafe impl Send for CallingConvention {} +unsafe impl Sync for CallingConvention {} + +impl CallingConvention { + pub(crate) unsafe fn ref_from_raw( + handle: *mut BNCallingConvention, + arch: A::Handle, + ) -> Ref { + Ref::new(CallingConvention { + handle, + arch_handle: arch, + _arch: PhantomData, + }) + } + + pub fn name(&self) -> BnString { + unsafe { BnString::from_raw(BNGetCallingConventionName(self.handle)) } + } + + pub fn variables_for_parameters( + &self, + params: &[FunctionParameter], + permitted_registers: Option<&[A::Register]>, + ) -> Vec { + let mut bn_params: Vec = vec![]; + let mut name_strings = vec![]; + + 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()) { + 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 _, + type_: parameter.t.contents.handle, + typeConfidence: parameter.t.confidence, + defaultLocation: parameter.location.is_none(), + location, + }); + } + + let mut count: usize = 0; + let vars: *mut BNVariable = if let Some(permitted_args) = permitted_registers { + let mut permitted_regs = vec![]; + for r in permitted_args { + permitted_regs.push(r.id()); + } + + unsafe { + BNGetVariablesForParameters( + self.handle, + bn_params.as_ptr(), + bn_params.len(), + permitted_regs.as_ptr(), + permitted_regs.len(), + &mut count, + ) + } + } else { + unsafe { + BNGetVariablesForParametersDefaultPermittedArgs( + self.handle, + bn_params.as_ptr(), + bn_params.len(), + &mut count, + ) + } + }; + + // 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 { + result.push(unsafe { Variable::from_raw(*var) }); + } + + unsafe { BNFreeVariableList(vars) }; + result + } +} + +impl Eq for CallingConvention {} +impl PartialEq for CallingConvention { + fn eq(&self, rhs: &Self) -> bool { + self.handle == rhs.handle + } +} + +use crate::types::{FunctionParameter, Variable}; +use std::hash::{Hash, Hasher}; + +impl Hash for CallingConvention { + fn hash(&self, state: &mut H) { + self.handle.hash(state); + } +} + +impl CallingConventionBase for CallingConvention { + type Arch = A; + + fn caller_saved_registers(&self) -> Vec { + unsafe { + let mut count = 0; + let regs = BNGetCallerSavedRegisters(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 callee_saved_registers(&self) -> Vec { + unsafe { + let mut count = 0; + let regs = BNGetCalleeSavedRegisters(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 int_arg_registers(&self) -> Vec { + Vec::new() + } + + fn float_arg_registers(&self) -> Vec { + Vec::new() + } + + fn arg_registers_shared_index(&self) -> bool { + unsafe { BNAreArgumentRegistersSharedIndex(self.handle) } + } + + fn reserved_stack_space_for_arg_registers(&self) -> bool { + unsafe { BNIsStackReservedForArgumentRegisters(self.handle) } + } + + fn stack_adjusted_on_return(&self) -> bool { + unsafe { BNIsStackAdjustedOnReturn(self.handle) } + } + + fn is_eligible_for_heuristics(&self) -> bool { + false + } + + fn return_int_reg(&self) -> Option { + match unsafe { BNGetIntegerReturnValueRegister(self.handle) } { + id if id < 0x8000_0000 => self.arch_handle.borrow().register_from_id(id), + _ => None, + } + } + + fn return_hi_int_reg(&self) -> Option { + match unsafe { BNGetHighIntegerReturnValueRegister(self.handle) } { + id if id < 0x8000_0000 => self.arch_handle.borrow().register_from_id(id), + _ => None, + } + } + + fn return_float_reg(&self) -> Option { + match unsafe { BNGetFloatReturnValueRegister(self.handle) } { + id if id < 0x8000_0000 => self.arch_handle.borrow().register_from_id(id), + _ => None, + } + } + + fn global_pointer_reg(&self) -> Option { + match unsafe { BNGetGlobalPointerRegister(self.handle) } { + id if id < 0x8000_0000 => self.arch_handle.borrow().register_from_id(id), + _ => None, + } + } + + fn implicitly_defined_registers(&self) -> Vec { + Vec::new() + } + + fn are_argument_registers_used_for_var_args(&self) -> bool { + unsafe { BNAreArgumentRegistersUsedForVarArgs(self.handle) } + } +} + +impl ToOwned for CallingConvention { + type Owned = Ref; + + fn to_owned(&self) -> Self::Owned { + unsafe { RefCountable::inc_ref(self) } + } +} + +unsafe impl RefCountable for CallingConvention { + unsafe fn inc_ref(handle: &Self) -> Ref { + Ref::new(Self { + handle: BNNewCallingConventionReference(handle.handle), + arch_handle: handle.arch_handle.clone(), + _arch: PhantomData, + }) + } + + unsafe fn dec_ref(handle: &Self) { + BNFreeCallingConvention(handle.handle); + } +} + +impl CoreArrayProvider for CallingConvention { + type Raw = *mut BNCallingConvention; + type Context = A::Handle; +} + +unsafe impl CoreOwnedArrayProvider 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 { + Guard::new( + CallingConvention { + handle: *raw, + arch_handle: context.clone(), + _arch: Default::default(), + }, + context, + ) + } +} + +impl Debug for CallingConvention { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + write!(f, "", self.name(), self.arch_handle.name()) + } +} + +pub struct ConventionBuilder { + caller_saved_registers: Vec, + _callee_saved_registers: Vec, + int_arg_registers: Vec, + float_arg_registers: Vec, + + arg_registers_shared_index: bool, + reserved_stack_space_for_arg_registers: bool, + stack_adjusted_on_return: bool, + is_eligible_for_heuristics: bool, + + return_int_reg: Option, + return_hi_int_reg: Option, + return_float_reg: Option, + + global_pointer_reg: Option, + + implicitly_defined_registers: Vec, + + are_argument_registers_used_for_var_args: bool, + + arch_handle: A::Handle, + _arch: PhantomData<*const A>, +} + +unsafe impl Send for ConventionBuilder {} +unsafe impl Sync for ConventionBuilder {} + +macro_rules! bool_arg { + ($name:ident) => { + pub fn $name(mut self, val: bool) -> Self { + self.$name = val; + self + } + }; +} + +macro_rules! reg_list { + ($name:ident) => { + pub fn $name(mut self, regs: &[&str]) -> Self { + { + // FIXME NLL + let arch = self.arch_handle.borrow(); + let arch_regs = regs.iter().filter_map(|&r| arch.register_by_name(r)); + + self.$name = arch_regs.collect(); + } + + self + } + }; +} + +macro_rules! reg { + ($name:ident) => { + pub fn $name(mut self, reg: &str) -> Self { + { + // FIXME NLL + let arch = self.arch_handle.borrow(); + self.$name = arch.register_by_name(reg); + } + + self + } + }; +} + +impl ConventionBuilder { + pub fn new(arch: &A) -> Self { + Self { + caller_saved_registers: Vec::new(), + _callee_saved_registers: Vec::new(), + int_arg_registers: Vec::new(), + float_arg_registers: Vec::new(), + + arg_registers_shared_index: false, + reserved_stack_space_for_arg_registers: false, + stack_adjusted_on_return: false, + is_eligible_for_heuristics: false, + + return_int_reg: None, + return_hi_int_reg: None, + return_float_reg: None, + + global_pointer_reg: None, + + implicitly_defined_registers: Vec::new(), + + are_argument_registers_used_for_var_args: false, + + arch_handle: arch.handle(), + _arch: PhantomData, + } + } + + reg_list!(caller_saved_registers); + reg_list!(_callee_saved_registers); + reg_list!(int_arg_registers); + reg_list!(float_arg_registers); + + bool_arg!(arg_registers_shared_index); + bool_arg!(reserved_stack_space_for_arg_registers); + bool_arg!(stack_adjusted_on_return); + bool_arg!(is_eligible_for_heuristics); + + reg!(return_int_reg); + reg!(return_hi_int_reg); + reg!(return_float_reg); + + reg!(global_pointer_reg); + + reg_list!(implicitly_defined_registers); + + bool_arg!(are_argument_registers_used_for_var_args); + + pub fn register(self, name: &str) -> Ref> { + let arch = self.arch_handle.clone(); + + register_calling_convention(arch.borrow(), name, self) + } +} + +impl CallingConventionBase for ConventionBuilder { + type Arch = A; + + fn caller_saved_registers(&self) -> Vec { + self.caller_saved_registers.clone() + } + + fn callee_saved_registers(&self) -> Vec { + self.caller_saved_registers.clone() + } + + fn int_arg_registers(&self) -> Vec { + self.int_arg_registers.clone() + } + + fn float_arg_registers(&self) -> Vec { + self.float_arg_registers.clone() + } + + fn arg_registers_shared_index(&self) -> bool { + self.arg_registers_shared_index + } + + fn reserved_stack_space_for_arg_registers(&self) -> bool { + self.reserved_stack_space_for_arg_registers + } + + fn stack_adjusted_on_return(&self) -> bool { + self.stack_adjusted_on_return + } + + fn is_eligible_for_heuristics(&self) -> bool { + self.is_eligible_for_heuristics + } + + fn return_int_reg(&self) -> Option { + self.return_int_reg + } + + fn return_hi_int_reg(&self) -> Option { + self.return_hi_int_reg + } + + fn return_float_reg(&self) -> Option { + self.return_float_reg + } + + fn global_pointer_reg(&self) -> Option { + self.global_pointer_reg + } + + fn implicitly_defined_registers(&self) -> Vec { + self.implicitly_defined_registers.clone() + } + + fn are_argument_registers_used_for_var_args(&self) -> bool { + self.are_argument_registers_used_for_var_args + } +} diff --git a/src/command.rs b/src/command.rs new file mode 100644 index 0000000..f533273 --- /dev/null +++ b/src/command.rs @@ -0,0 +1,448 @@ +// Copyright 2021-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. + +//! Provides commands for registering plugins and plugin actions. +//! +//! All plugins need to provide one of the following functions for Binary Ninja to call: +//! +//! ```rust +//! pub extern "C" fn CorePluginInit() -> bool {} +//! ``` +//! +//! ```rust +//! pub extern "C" fn UIPluginInit() -> bool {} +//! ``` +//! +//! 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. +//! +//! The return value of these functions should indicate whether they successfully initialized themselves. + +use binaryninjacore_sys::{ + BNBinaryView, BNFunction, BNRegisterPluginCommand, BNRegisterPluginCommandForAddress, + BNRegisterPluginCommandForFunction, BNRegisterPluginCommandForRange, +}; + +use std::ops::Range; +use std::os::raw::c_void; + +use crate::binaryview::BinaryView; +use crate::function::Function; +use crate::string::BnStrCompatible; + +/// The trait required for generic commands. See [register] for example usage. +pub trait Command: 'static + Sync { + fn action(&self, view: &BinaryView); + fn valid(&self, view: &BinaryView) -> bool; +} + +impl Command for T +where + T: 'static + Sync + Fn(&BinaryView), +{ + fn action(&self, view: &BinaryView) { + self(view); + } + + fn valid(&self, _view: &BinaryView) -> bool { + true + } +} + +/// The function call required for generic commands; commands added in this way will be in the `Plugins` submenu of the menu bar. +/// +/// # Example +/// ```rust +/// struct MyCommand; +/// +/// impl Command for MyCommand { +/// fn action(&self, view: &BinaryView) { +/// // Your code here +/// } +/// +/// fn valid(&self, view: &BinaryView) -> bool { +/// // Your code here +/// true +/// } +/// } +/// +/// #[no_mangle] +/// pub extern "C" fn CorePluginInit() -> bool { +/// register( +/// "My Plugin Command", +/// "A description of my command", +/// MyCommand {}, +/// ); +/// true +/// } +/// ``` +pub fn register(name: S, desc: S, command: C) +where + S: BnStrCompatible, + C: Command, +{ + extern "C" fn cb_action(ctxt: *mut c_void, view: *mut BNBinaryView) + where + C: Command, + { + ffi_wrap!("Command::action", unsafe { + let cmd = &*(ctxt as *const C); + + debug_assert!(!view.is_null()); + let view = BinaryView { handle: view }; + + cmd.action(&view); + }) + } + + extern "C" fn cb_valid(ctxt: *mut c_void, view: *mut BNBinaryView) -> bool + where + C: Command, + { + ffi_wrap!("Command::valid", unsafe { + let cmd = &*(ctxt as *const C); + + debug_assert!(!view.is_null()); + let view = BinaryView { handle: view }; + + cmd.valid(&view) + }) + } + + let name = name.into_bytes_with_nul(); + let desc = desc.into_bytes_with_nul(); + + let name_ptr = name.as_ref().as_ptr() as *mut _; + let desc_ptr = desc.as_ref().as_ptr() as *mut _; + + let ctxt = Box::into_raw(Box::new(command)); + + unsafe { + BNRegisterPluginCommand( + name_ptr, + desc_ptr, + Some(cb_action::), + Some(cb_valid::), + ctxt as *mut _, + ); + } +} + +/// The trait required for address-associated commands. See [register_for_address] for example usage. +pub trait AddressCommand: 'static + Sync { + fn action(&self, view: &BinaryView, addr: u64); + fn valid(&self, view: &BinaryView, addr: u64) -> bool; +} + +impl AddressCommand for T +where + T: 'static + Sync + Fn(&BinaryView, u64), +{ + fn action(&self, view: &BinaryView, addr: u64) { + self(view, addr); + } + + fn valid(&self, _view: &BinaryView, _addr: u64) -> bool { + true + } +} + +/// The function call required for generic commands; commands added in this way will be in the `Plugins` submenu of the menu bar. +/// +/// # Example +/// ```rust +/// struct MyCommand; +/// +/// impl AddressCommand for MyCommand { +/// fn action(&self, view: &BinaryView, addr: u64) { +/// // Your code here +/// } +/// +/// fn valid(&self, view: &BinaryView, addr: u64) -> bool { +/// // Your code here +/// true +/// } +/// } +/// +/// #[no_mangle] +/// pub extern "C" fn CorePluginInit() -> bool { +/// register_for_address( +/// "My Plugin Command", +/// "A description of my command", +/// MyCommand {}, +/// ); +/// true +/// } +/// ``` +pub fn register_for_address(name: S, desc: S, command: C) +where + S: BnStrCompatible, + C: AddressCommand, +{ + extern "C" fn cb_action(ctxt: *mut c_void, view: *mut BNBinaryView, addr: u64) + where + C: AddressCommand, + { + ffi_wrap!("AddressCommand::action", unsafe { + let cmd = &*(ctxt as *const C); + + debug_assert!(!view.is_null()); + let view = BinaryView { handle: view }; + + cmd.action(&view, addr); + }) + } + + extern "C" fn cb_valid(ctxt: *mut c_void, view: *mut BNBinaryView, addr: u64) -> bool + where + C: AddressCommand, + { + ffi_wrap!("AddressCommand::valid", unsafe { + let cmd = &*(ctxt as *const C); + + debug_assert!(!view.is_null()); + let view = BinaryView { handle: view }; + + cmd.valid(&view, addr) + }) + } + + let name = name.into_bytes_with_nul(); + let desc = desc.into_bytes_with_nul(); + + let name_ptr = name.as_ref().as_ptr() as *mut _; + let desc_ptr = desc.as_ref().as_ptr() as *mut _; + + let ctxt = Box::into_raw(Box::new(command)); + + unsafe { + BNRegisterPluginCommandForAddress( + name_ptr, + desc_ptr, + Some(cb_action::), + Some(cb_valid::), + ctxt as *mut _, + ); + } +} + +/// The trait required for range-associated commands. See [register_for_range] for example usage. +pub trait RangeCommand: 'static + Sync { + fn action(&self, view: &BinaryView, range: Range); + fn valid(&self, view: &BinaryView, range: Range) -> bool; +} + +impl RangeCommand for T +where + T: 'static + Sync + Fn(&BinaryView, Range), +{ + fn action(&self, view: &BinaryView, range: Range) { + self(view, range); + } + + fn valid(&self, _view: &BinaryView, _range: Range) -> bool { + true + } +} + +/// The function call required for generic commands; commands added in this way will be in the `Plugins` submenu of the menu bar. +/// +/// # Example +/// ```rust +/// struct MyCommand; +/// +/// impl AddressCommand for MyCommand { +/// fn action(&self, view: &BinaryView, range: Range) { +/// // Your code here +/// } +/// +/// fn valid(&self, view: &BinaryView, range: Range) -> bool { +/// // Your code here +/// true +/// } +/// } +/// +/// #[no_mangle] +/// pub extern "C" fn CorePluginInit() -> bool { +/// register_for_range( +/// "My Plugin Command", +/// "A description of my command", +/// MyCommand {}, +/// ); +/// true +/// } +/// ``` +pub fn register_for_range(name: S, desc: S, command: C) +where + S: BnStrCompatible, + C: RangeCommand, +{ + extern "C" fn cb_action(ctxt: *mut c_void, view: *mut BNBinaryView, addr: u64, len: u64) + where + C: RangeCommand, + { + ffi_wrap!("RangeCommand::action", unsafe { + let cmd = &*(ctxt as *const C); + + debug_assert!(!view.is_null()); + let view = BinaryView { handle: view }; + + cmd.action(&view, addr..addr.wrapping_add(len)); + }) + } + + extern "C" fn cb_valid( + ctxt: *mut c_void, + view: *mut BNBinaryView, + addr: u64, + len: u64, + ) -> bool + where + C: RangeCommand, + { + ffi_wrap!("RangeCommand::valid", unsafe { + let cmd = &*(ctxt as *const C); + + debug_assert!(!view.is_null()); + let view = BinaryView { handle: view }; + + cmd.valid(&view, addr..addr.wrapping_add(len)) + }) + } + + let name = name.into_bytes_with_nul(); + let desc = desc.into_bytes_with_nul(); + + let name_ptr = name.as_ref().as_ptr() as *mut _; + let desc_ptr = desc.as_ref().as_ptr() as *mut _; + + let ctxt = Box::into_raw(Box::new(command)); + + unsafe { + BNRegisterPluginCommandForRange( + name_ptr, + desc_ptr, + Some(cb_action::), + Some(cb_valid::), + ctxt as *mut _, + ); + } +} + +/// The trait required for function-associated commands. See [register_for_function] for example usage. +pub trait FunctionCommand: 'static + Sync { + fn action(&self, view: &BinaryView, func: &Function); + fn valid(&self, view: &BinaryView, func: &Function) -> bool; +} + +impl FunctionCommand for T +where + T: 'static + Sync + Fn(&BinaryView, &Function), +{ + fn action(&self, view: &BinaryView, func: &Function) { + self(view, func); + } + + fn valid(&self, _view: &BinaryView, _func: &Function) -> bool { + true + } +} + +/// The function call required for generic commands; commands added in this way will be in the `Plugins` submenu of the menu bar. +/// +/// # Example +/// ```rust +/// struct MyCommand; +/// +/// impl AddressCommand for MyCommand { +/// fn action(&self, view: &BinaryView, func: &Function) { +/// // Your code here +/// } +/// +/// fn valid(&self, view: &BinaryView, func: &Function) -> bool { +/// // Your code here +/// true +/// } +/// } +/// +/// #[no_mangle] +/// pub extern "C" fn CorePluginInit() -> bool { +/// register_for_function( +/// "My Plugin Command", +/// "A description of my command", +/// MyCommand {}, +/// ); +/// true +/// } +/// ``` +pub fn register_for_function(name: S, desc: S, command: C) +where + S: BnStrCompatible, + C: FunctionCommand, +{ + extern "C" fn cb_action(ctxt: *mut c_void, view: *mut BNBinaryView, func: *mut BNFunction) + where + C: FunctionCommand, + { + ffi_wrap!("FunctionCommand::action", unsafe { + let cmd = &*(ctxt as *const C); + + debug_assert!(!view.is_null()); + let view = BinaryView { handle: view }; + + debug_assert!(!func.is_null()); + let func = Function { handle: func }; + + cmd.action(&view, &func); + }) + } + + extern "C" fn cb_valid( + ctxt: *mut c_void, + view: *mut BNBinaryView, + func: *mut BNFunction, + ) -> bool + where + C: FunctionCommand, + { + ffi_wrap!("FunctionCommand::valid", unsafe { + let cmd = &*(ctxt as *const C); + + debug_assert!(!view.is_null()); + let view = BinaryView { handle: view }; + + debug_assert!(!func.is_null()); + let func = Function { handle: func }; + + cmd.valid(&view, &func) + }) + } + + let name = name.into_bytes_with_nul(); + let desc = desc.into_bytes_with_nul(); + + let name_ptr = name.as_ref().as_ptr() as *mut _; + let desc_ptr = desc.as_ref().as_ptr() as *mut _; + + let ctxt = Box::into_raw(Box::new(command)); + + unsafe { + BNRegisterPluginCommandForFunction( + name_ptr, + desc_ptr, + Some(cb_action::), + Some(cb_valid::), + ctxt as *mut _, + ); + } +} diff --git a/src/custombinaryview.rs b/src/custombinaryview.rs new file mode 100644 index 0000000..21b63c9 --- /dev/null +++ b/src/custombinaryview.rs @@ -0,0 +1,807 @@ +// Copyright 2021-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. + +//! An interface for providing your own [BinaryView]s to Binary Ninja. + +use binaryninjacore_sys::*; + +pub use binaryninjacore_sys::BNModificationStatus as ModificationStatus; + +use std::marker::PhantomData; +use std::mem; +use std::os::raw::c_void; +use std::ptr; +use std::slice; + +use crate::architecture::Architecture; +use crate::binaryview::{BinaryView, BinaryViewBase, BinaryViewExt, Result}; +use crate::platform::Platform; +use crate::settings::Settings; +use crate::Endianness; + +use crate::rc::*; +use crate::string::*; + +/// Registers a custom `BinaryViewType` with the core. +/// +/// The `constructor` argument is called immediately after successful registration of the type with +/// the core. The `BinaryViewType` argument passed to `constructor` is the object that the +/// `AsRef` +/// implementation of the `CustomBinaryViewType` must return. +pub fn register_view_type(name: S, long_name: S, constructor: F) -> &'static T +where + S: BnStrCompatible, + T: CustomBinaryViewType, + F: FnOnce(BinaryViewType) -> T, +{ + extern "C" fn cb_valid(ctxt: *mut c_void, data: *mut BNBinaryView) -> bool + where + T: CustomBinaryViewType, + { + ffi_wrap!("BinaryViewTypeBase::is_valid_for", unsafe { + let view_type = &*(ctxt as *mut T); + let data = BinaryView::from_raw(BNNewViewReference(data)); + + view_type.is_valid_for(&data) + }) + } + + extern "C" fn cb_deprecated(ctxt: *mut c_void) -> bool + where + T: CustomBinaryViewType, + { + ffi_wrap!("BinaryViewTypeBase::is_deprecated", unsafe { + let view_type = &*(ctxt as *mut T); + view_type.is_deprecated() + }) + } + + extern "C" fn cb_create(ctxt: *mut c_void, data: *mut BNBinaryView) -> *mut BNBinaryView + where + T: CustomBinaryViewType, + { + ffi_wrap!("BinaryViewTypeBase::create", unsafe { + let view_type = &*(ctxt as *mut T); + let data = BinaryView::from_raw(BNNewViewReference(data)); + + let builder = CustomViewBuilder { + view_type, + actual_parent: &data, + }; + + if let Ok(bv) = view_type.create_custom_view(&data, builder) { + // force a leak of the Ref; failure to do this would result + // in the refcount going to 0 in the process of returning it + // to the core -- we're transferring ownership of the Ref here + Ref::into_raw(bv.handle).handle + } else { + error!("CustomBinaryViewType::create_custom_view returned Err"); + + ptr::null_mut() + } + }) + } + + #[allow(clippy::extra_unused_type_parameters)] // TODO : This is bad; need to finish this stub + extern "C" fn cb_parse(_ctxt: *mut c_void, _data: *mut BNBinaryView) -> *mut BNBinaryView + where + T: CustomBinaryViewType, + { + ffi_wrap!("BinaryViewTypeBase::parse", ptr::null_mut()) + } + + extern "C" fn cb_load_settings(ctxt: *mut c_void, data: *mut BNBinaryView) -> *mut BNSettings + where + T: CustomBinaryViewType, + { + ffi_wrap!("BinaryViewTypeBase::load_settings", unsafe { + let view_type = &*(ctxt as *mut T); + let data = BinaryView::from_raw(BNNewViewReference(data)); + + match view_type.load_settings_for_data(&data) { + Ok(settings) => Ref::into_raw(settings).handle, + _ => ptr::null_mut() as *mut _, + } + }) + } + + let name = name.into_bytes_with_nul(); + let name_ptr = name.as_ref().as_ptr() as *mut _; + + 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 mut bn_obj = BNCustomBinaryViewType { + context: ctxt as *mut _, + create: Some(cb_create::), + parse: Some(cb_parse::), + isValidForData: Some(cb_valid::), + isDeprecated: Some(cb_deprecated::), + getLoadSettingsForData: Some(cb_load_settings::), + }; + + unsafe { + let res = BNRegisterBinaryViewType(name_ptr, long_name_ptr, &mut bn_obj as *mut _); + + 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)); + + panic!("bvt registration failed"); + } + + ptr::write(ctxt, constructor(BinaryViewType(res))); + + &*ctxt + } +} + +pub trait BinaryViewTypeBase: AsRef { + fn is_valid_for(&self, data: &BinaryView) -> bool; + + fn is_deprecated(&self) -> bool; + + fn default_load_settings_for_data(&self, data: &BinaryView) -> Result> { + let settings_handle = + unsafe { BNGetBinaryViewDefaultLoadSettingsForData(self.as_ref().0, data.handle) }; + + if settings_handle.is_null() { + Err(()) + } else { + unsafe { Ok(Settings::from_raw(settings_handle)) } + } + } + + fn load_settings_for_data(&self, data: &BinaryView) -> Result> { + let settings_handle = + unsafe { BNGetBinaryViewLoadSettingsForData(self.as_ref().0, data.handle) }; + + if settings_handle.is_null() { + Err(()) + } else { + unsafe { Ok(Settings::from_raw(settings_handle)) } + } + } +} + +pub trait BinaryViewTypeExt: BinaryViewTypeBase { + fn name(&self) -> BnString { + unsafe { BnString::from_raw(BNGetBinaryViewTypeName(self.as_ref().0)) } + } + + fn long_name(&self) -> BnString { + unsafe { BnString::from_raw(BNGetBinaryViewTypeLongName(self.as_ref().0)) } + } + + fn register_arch(&self, id: u32, endianness: Endianness, arch: &A) { + unsafe { + BNRegisterArchitectureForViewType(self.as_ref().0, id, endianness, arch.as_ref().0); + } + } + + fn register_platform(&self, id: u32, plat: &Platform) { + let arch = plat.arch(); + + unsafe { + BNRegisterPlatformForViewType(self.as_ref().0, id, arch.0, plat.handle); + } + } + + fn open(&self, data: &BinaryView) -> Result> { + let handle = unsafe { BNCreateBinaryViewOfType(self.as_ref().0, data.handle) }; + + if handle.is_null() { + error!( + "failed to create BinaryView of BinaryViewType '{}'", + self.name() + ); + return Err(()); + } + + unsafe { Ok(BinaryView::from_raw(handle)) } + } +} + +impl BinaryViewTypeExt for T {} + +#[derive(Copy, Clone, PartialEq, Eq, Hash)] +pub struct BinaryViewType(pub *mut BNBinaryViewType); + +impl BinaryViewType { + pub fn list_all() -> Array { + unsafe { + let mut count: usize = 0; + let types = BNGetBinaryViewTypes(&mut count as *mut _); + + Array::new(types, count, ()) + } + } + + pub fn list_valid_types_for(data: &BinaryView) -> Array { + unsafe { + let mut count: usize = 0; + let types = BNGetBinaryViewTypesForData(data.handle, &mut count as *mut _); + + Array::new(types, count, ()) + } + } + + /// Looks up a BinaryViewType by its short name + pub fn by_name(name: N) -> Result { + let bytes = name.into_bytes_with_nul(); + + let res = unsafe { BNGetBinaryViewTypeByName(bytes.as_ref().as_ptr() as *const _) }; + + match res.is_null() { + false => Ok(BinaryViewType(res)), + true => Err(()), + } + } +} + +impl BinaryViewTypeBase for BinaryViewType { + fn is_valid_for(&self, data: &BinaryView) -> bool { + unsafe { BNIsBinaryViewTypeValidForData(self.0, data.handle) } + } + + fn is_deprecated(&self) -> bool { + unsafe { BNIsBinaryViewTypeDeprecated(self.0) } + } + + fn default_load_settings_for_data(&self, data: &BinaryView) -> Result> { + let settings_handle = + unsafe { BNGetBinaryViewDefaultLoadSettingsForData(self.0, data.handle) }; + + if settings_handle.is_null() { + Err(()) + } else { + unsafe { Ok(Settings::from_raw(settings_handle)) } + } + } + + fn load_settings_for_data(&self, data: &BinaryView) -> Result> { + let settings_handle = unsafe { BNGetBinaryViewLoadSettingsForData(self.0, data.handle) }; + + if settings_handle.is_null() { + Err(()) + } else { + unsafe { Ok(Settings::from_raw(settings_handle)) } + } + } +} + +impl CoreArrayProvider for BinaryViewType { + type Raw = *mut BNBinaryViewType; + type Context = (); +} + +unsafe impl CoreOwnedArrayProvider 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) + } +} + +impl AsRef for BinaryViewType { + fn as_ref(&self) -> &Self { + self + } +} + +unsafe impl Send for BinaryViewType {} +unsafe impl Sync for BinaryViewType {} + +pub trait CustomBinaryViewType: 'static + BinaryViewTypeBase + Sync { + fn create_custom_view<'builder>( + &self, + data: &BinaryView, + builder: CustomViewBuilder<'builder, Self>, + ) -> Result>; +} + +/// Represents a request from the core to instantiate a custom BinaryView +pub struct CustomViewBuilder<'a, T: CustomBinaryViewType + ?Sized> { + view_type: &'a T, + actual_parent: &'a BinaryView, +} + +pub unsafe trait CustomBinaryView: 'static + BinaryViewBase + Sync + Sized { + type Args: Send; + + fn new(handle: &BinaryView, args: &Self::Args) -> Result; + fn init(&self, args: Self::Args) -> Result<()>; +} + +/// Represents a partially initialized custom `BinaryView` that should be returned to the core +/// from the `create_custom_view` method of a `CustomBinaryViewType`. +#[must_use] +pub struct CustomView<'builder> { + // this object can't actually be treated like a real + // BinaryView as it isn't fully initialized until the + // core receives it from the BNCustomBinaryViewType::create + // callback. + handle: Ref, + _builder: PhantomData<&'builder ()>, +} + +impl<'a, T: CustomBinaryViewType> CustomViewBuilder<'a, T> { + /// Begins creating a custom BinaryView. + /// + /// This function may only be called from the `create_custom_view` function of a + /// `CustomBinaryViewType`. + /// + /// `parent` specifies the view that the core will treat as the parent view, that + /// Segments created against the created view will be backed by `parent`. It will + /// usually be (but is not required to be) the `data` argument of the `create_custom_view` + /// callback. + /// + /// `constructor` will not be called until well after the value returned by this function + /// has been returned by `create_custom_view` callback to the core, and may not ever + /// be called if the value returned by this function is dropped or leaked. + /// + /// # Errors + /// + /// This function will fail if the `FileMetadata` object associated with the *expected* parent + /// (i.e., the `data` argument passed to the `create_custom_view` function) already has an + /// associated `BinaryView` of the same `CustomBinaryViewType`. Multiple `BinaryView` objects + /// of the same `BinaryViewType` belonging to the same `FileMetadata` object is prohibited and + /// can cause strange, delayed segmentation faults. + /// + /// # Safety + /// + /// `constructor` should avoid doing anything with the object it returns, especially anything + /// that would cause the core to invoke any of the `BinaryViewBase` methods. The core isn't + /// going to consider the object fully initialized until after that callback has run. + /// + /// The `BinaryView` argument passed to the constructor function is the object that is expected + /// to be returned by the `AsRef` implementation required by the `BinaryViewBase` trait. + /// TODO FIXME welp this is broke going to need 2 init callbacks + pub fn create(self, parent: &BinaryView, view_args: V::Args) -> Result> + where + V: CustomBinaryView, + { + let file = self.actual_parent.file(); + let view_type = self.view_type; + + let view_name = view_type.name(); + + if let Ok(bv) = file.get_view_of_type(view_name.as_cstr()) { + // 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 + // will run for the new view before we've even finished initializing, + // and that's all she wrote. + // + // even if we deal with it gracefully in cb_free_object, + // BNCreateBinaryViewOfType is still going to crash, so we're just + // going to try and stop this from happening in the first place. + error!( + "attempt to create duplicate view of type '{}' (existing: {:?})", + view_name.as_str(), + bv.handle + ); + + return Err(()); + } + + // wildly unsafe struct representing the context of a BNCustomBinaryView + // this type should *never* be allowed to drop as the fields are in varying + // states of uninitialized/already consumed throughout the life of the object. + struct CustomViewContext + where + V: CustomBinaryView, + { + view: mem::MaybeUninit, + raw_handle: *mut BNBinaryView, + initialized: bool, + args: V::Args, + } + + extern "C" fn cb_init(ctxt: *mut c_void) -> bool + where + V: CustomBinaryView, + { + ffi_wrap!("BinaryViewBase::init", unsafe { + let context = &mut *(ctxt as *mut CustomViewContext); + let handle = BinaryView::from_raw(context.raw_handle); + + match V::new(handle.as_ref(), &context.args) { + Ok(v) => { + ptr::write(&mut context.view, mem::MaybeUninit::new(v)); + context.initialized = true; + + match context + .view + .assume_init_ref() + .init(ptr::read(&context.args)) + { + Ok(_) => true, + Err(_) => { + error!("CustomBinaryView::init failed; custom view returned Err"); + false + } + } + } + Err(_) => { + error!("CustomBinaryView::new failed; custom view returned Err"); + false + } + } + }) + } + + extern "C" fn cb_free_object(ctxt: *mut c_void) + where + V: CustomBinaryView, + { + ffi_wrap!("BinaryViewBase::freeObject", unsafe { + let context = ctxt as *mut CustomViewContext; + let context = *Box::from_raw(context); + + if context.initialized { + mem::forget(context.args); // already consumed + // mem::drop(context.view); // cb_init was called + } else { + mem::drop(context.args); // never consumed + // mem::forget(context.view); // cb_init was not called, is uninit + + if context.raw_handle.is_null() { + // being called here is essentially a guarantee that BNCreateBinaryViewOfType + // is above above us on the call stack somewhere -- no matter what we do, a crash + // is pretty much certain at this point. + // + // this has been observed when two views of the same BinaryViewType are created + // against the same BNFileMetaData object, and one of the views gets freed while + // the second one is being initialized -- somehow the partially initialized one + // gets freed before BNCreateBinaryViewOfType returns. + // + // multiples views of the same BinaryViewType in a BNFileMetaData object are + // prohibited, so an API contract was violated in order to get here. + // + // if we're here, it's too late to do anything about it, though we can at least not + // run the destructor on the custom view since that memory is unitialized. + error!( + "BinaryViewBase::freeObject called on partially initialized object! crash imminent!" + ); + } else if !context.initialized { + // making it here means somebody went out of their way to leak a BinaryView + // after calling BNCreateCustomView and never gave the BNBinaryView handle + // to the core (which would have called cb_init) + // + // the result is a half-initialized BinaryView that the core will happily hand out + // references to via BNGetFileViewofType even though it was never initialized + // all the way. + // + // TODO update when this corner case gets fixed in the core? + // + // we can't do anything to prevent this, but we can at least have the crash + // not be our fault. + error!("BinaryViewBase::freeObject called on leaked/never initialized custom view!"); + } + } + }) + } + + extern "C" fn cb_read( + ctxt: *mut c_void, + dest: *mut c_void, + offset: u64, + len: usize, + ) -> usize + where + V: CustomBinaryView, + { + ffi_wrap!("BinaryViewBase::read", unsafe { + let context = &*(ctxt as *mut CustomViewContext); + let dest = slice::from_raw_parts_mut(dest as *mut u8, len); + + context.view.assume_init_ref().read(dest, offset) + }) + } + + extern "C" fn cb_write( + ctxt: *mut c_void, + offset: u64, + src: *const c_void, + len: usize, + ) -> usize + where + V: CustomBinaryView, + { + ffi_wrap!("BinaryViewBase::write", unsafe { + let context = &*(ctxt as *mut CustomViewContext); + let src = slice::from_raw_parts(src as *const u8, len); + + context.view.assume_init_ref().write(offset, src) + }) + } + + extern "C" fn cb_insert( + ctxt: *mut c_void, + offset: u64, + src: *const c_void, + len: usize, + ) -> usize + where + V: CustomBinaryView, + { + ffi_wrap!("BinaryViewBase::insert", unsafe { + let context = &*(ctxt as *mut CustomViewContext); + let src = slice::from_raw_parts(src as *const u8, len); + + context.view.assume_init_ref().insert(offset, src) + }) + } + + extern "C" fn cb_remove(ctxt: *mut c_void, offset: u64, len: u64) -> usize + where + V: CustomBinaryView, + { + ffi_wrap!("BinaryViewBase::remove", unsafe { + let context = &*(ctxt as *mut CustomViewContext); + + context.view.assume_init_ref().remove(offset, len as usize) + }) + } + + extern "C" fn cb_modification(ctxt: *mut c_void, offset: u64) -> ModificationStatus + where + V: CustomBinaryView, + { + ffi_wrap!("BinaryViewBase::modification_status", unsafe { + let context = &*(ctxt as *mut CustomViewContext); + + context.view.assume_init_ref().modification_status(offset) + }) + } + + extern "C" fn cb_offset_valid(ctxt: *mut c_void, offset: u64) -> bool + where + V: CustomBinaryView, + { + ffi_wrap!("BinaryViewBase::offset_valid", unsafe { + let context = &*(ctxt as *mut CustomViewContext); + + context.view.assume_init_ref().offset_valid(offset) + }) + } + + extern "C" fn cb_offset_readable(ctxt: *mut c_void, offset: u64) -> bool + where + V: CustomBinaryView, + { + ffi_wrap!("BinaryViewBase::readable", unsafe { + let context = &*(ctxt as *mut CustomViewContext); + + context.view.assume_init_ref().offset_readable(offset) + }) + } + + extern "C" fn cb_offset_writable(ctxt: *mut c_void, offset: u64) -> bool + where + V: CustomBinaryView, + { + ffi_wrap!("BinaryViewBase::writable", unsafe { + let context = &*(ctxt as *mut CustomViewContext); + + context.view.assume_init_ref().offset_writable(offset) + }) + } + + extern "C" fn cb_offset_executable(ctxt: *mut c_void, offset: u64) -> bool + where + V: CustomBinaryView, + { + ffi_wrap!("BinaryViewBase::offset_executable", unsafe { + let context = &*(ctxt as *mut CustomViewContext); + + context.view.assume_init_ref().offset_executable(offset) + }) + } + + extern "C" fn cb_offset_backed_by_file(ctxt: *mut c_void, offset: u64) -> bool + where + V: CustomBinaryView, + { + ffi_wrap!("BinaryViewBase::offset_backed_by_file", unsafe { + let context = &*(ctxt as *mut CustomViewContext); + + context.view.assume_init_ref().offset_backed_by_file(offset) + }) + } + + extern "C" fn cb_next_valid_offset(ctxt: *mut c_void, offset: u64) -> u64 + where + V: CustomBinaryView, + { + ffi_wrap!("BinaryViewBase::next_valid_offset_after", unsafe { + let context = &*(ctxt as *mut CustomViewContext); + + context + .view + .assume_init_ref() + .next_valid_offset_after(offset) + }) + } + + extern "C" fn cb_start(ctxt: *mut c_void) -> u64 + where + V: CustomBinaryView, + { + ffi_wrap!("BinaryViewBase::start", unsafe { + let context = &*(ctxt as *mut CustomViewContext); + + context.view.assume_init_ref().start() + }) + } + + extern "C" fn cb_length(ctxt: *mut c_void) -> u64 + where + V: CustomBinaryView, + { + ffi_wrap!("BinaryViewBase::len", unsafe { + let context = &*(ctxt as *mut CustomViewContext); + + context.view.assume_init_ref().len() as u64 + }) + } + + extern "C" fn cb_entry_point(ctxt: *mut c_void) -> u64 + where + V: CustomBinaryView, + { + ffi_wrap!("BinaryViewBase::entry_point", unsafe { + let context = &*(ctxt as *mut CustomViewContext); + + context.view.assume_init_ref().entry_point() + }) + } + + extern "C" fn cb_executable(ctxt: *mut c_void) -> bool + where + V: CustomBinaryView, + { + ffi_wrap!("BinaryViewBase::executable", unsafe { + let context = &*(ctxt as *mut CustomViewContext); + + context.view.assume_init_ref().executable() + }) + } + + extern "C" fn cb_endianness(ctxt: *mut c_void) -> Endianness + where + V: CustomBinaryView, + { + ffi_wrap!("BinaryViewBase::default_endianness", unsafe { + let context = &*(ctxt as *mut CustomViewContext); + + context.view.assume_init_ref().default_endianness() + }) + } + + extern "C" fn cb_relocatable(ctxt: *mut c_void) -> bool + where + V: CustomBinaryView, + { + ffi_wrap!("BinaryViewBase::relocatable", unsafe { + let context = &*(ctxt as *mut CustomViewContext); + + context.view.assume_init_ref().relocatable() + }) + } + + extern "C" fn cb_address_size(ctxt: *mut c_void) -> usize + where + V: CustomBinaryView, + { + ffi_wrap!("BinaryViewBase::address_size", unsafe { + let context = &*(ctxt as *mut CustomViewContext); + + context.view.assume_init_ref().address_size() + }) + } + + extern "C" fn cb_save(ctxt: *mut c_void, _fa: *mut BNFileAccessor) -> bool + where + V: CustomBinaryView, + { + ffi_wrap!("BinaryViewBase::save", unsafe { + let _context = &*(ctxt as *mut CustomViewContext); + false + }) + } + + let ctxt = Box::new(CustomViewContext:: { + view: mem::MaybeUninit::uninit(), + raw_handle: ptr::null_mut(), + initialized: false, + args: view_args, + }); + + let ctxt = Box::into_raw(ctxt); + + let mut bn_obj = BNCustomBinaryView { + context: ctxt as *mut _, + init: Some(cb_init::), + freeObject: Some(cb_free_object::), + externalRefTaken: None, + externalRefReleased: None, + read: Some(cb_read::), + write: Some(cb_write::), + insert: Some(cb_insert::), + remove: Some(cb_remove::), + getModification: Some(cb_modification::), + isValidOffset: Some(cb_offset_valid::), + isOffsetReadable: Some(cb_offset_readable::), + isOffsetWritable: Some(cb_offset_writable::), + isOffsetExecutable: Some(cb_offset_executable::), + isOffsetBackedByFile: Some(cb_offset_backed_by_file::), + getNextValidOffset: Some(cb_next_valid_offset::), + getStart: Some(cb_start::), + getLength: Some(cb_length::), + getEntryPoint: Some(cb_entry_point::), + isExecutable: Some(cb_executable::), + getDefaultEndianness: Some(cb_endianness::), + isRelocatable: Some(cb_relocatable::), + getAddressSize: Some(cb_address_size::), + save: Some(cb_save::), + }; + + unsafe { + let res = BNCreateCustomBinaryView( + view_name.as_cstr().as_ptr(), + file.handle, + parent.handle, + &mut bn_obj, + ); + + if res.is_null() { + // TODO not sure when this can even happen, let alone what we're supposed to do about + // it. cb_init isn't normally called until later, and cb_free_object definitely won't + // have been called, so we'd at least be on the hook for freeing that stuff... + // probably. + // + // no idea how to force this to fail so I can test this, so just going to do the + // reasonable thing and panic. + panic!("failed to create custom binary view!"); + } + + (*ctxt).raw_handle = res; + + Ok(CustomView { + handle: BinaryView::from_raw(res), + _builder: PhantomData, + }) + } + } + + pub fn wrap_existing(self, wrapped_view: Ref) -> Result> { + Ok(CustomView { + handle: wrapped_view, + _builder: PhantomData, + }) + } +} diff --git a/src/databuffer.rs b/src/databuffer.rs new file mode 100644 index 0000000..ebe1984 --- /dev/null +++ b/src/databuffer.rs @@ -0,0 +1,95 @@ +// Copyright 2021-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. + +//! A basic wrapper around an array of binary data + +use binaryninjacore_sys::*; + +use std::ffi::c_void; +use std::ptr; +use std::slice; + +pub struct DataBuffer(*mut BNDataBuffer); + +impl DataBuffer { + pub(crate) fn from_raw(raw: *mut BNDataBuffer) -> Self { + DataBuffer(raw) + } + pub(crate) fn as_raw(&self) -> *mut BNDataBuffer { + self.0 + } + + 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() { + &[] + } else { + unsafe { slice::from_raw_parts(buffer as *const _, self.len()) } + } + } + + pub fn set_data(&mut self, data: &[u8]) { + unsafe { + BNSetDataBufferContents( + self.0, + data.as_ptr() as *const c_void as *mut c_void, + data.len(), + ); + } + } + + pub fn len(&self) -> usize { + unsafe { BNGetDataBufferLength(self.0) } + } + + pub fn is_empty(&self) -> bool { + unsafe { BNGetDataBufferLength(self.0) == 0 } + } + + pub fn new(data: &[u8]) -> Result { + let buffer = unsafe { BNCreateDataBuffer(data.as_ptr() as *const c_void, data.len()) }; + if buffer.is_null() { + Err(()) + } else { + Ok(DataBuffer::from_raw(buffer)) + } + } +} + +// TODO : delete this +impl Default for DataBuffer { + fn default() -> Self { + DataBuffer::from_raw(ptr::null_mut()) + } +} + +impl Drop for DataBuffer { + fn drop(&mut self) { + if !self.0.is_null() { + unsafe { + BNFreeDataBuffer(self.0); + } + } + } +} + +impl Clone for DataBuffer { + fn clone(&self) -> Self { + Self::from_raw(unsafe { BNDuplicateDataBuffer(self.0) }) + } +} diff --git a/src/debuginfo.rs b/src/debuginfo.rs new file mode 100644 index 0000000..ab4f8f6 --- /dev/null +++ b/src/debuginfo.rs @@ -0,0 +1,897 @@ +// Copyright 2021-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. + +// TODO : These docs are here, but could afford to be cleaned up + +//! Parsers and providers of debug information to Binary Ninja. +//! +//! The debug information is used by Binary Ninja as ground-truth information about the attributes of functions, +//! types, and variables that Binary Ninja's analysis pipeline would otherwise work to deduce. By providing +//! debug info, Binary Ninja's output can be generated quicker, more accurately, and more completely. +//! +//! A DebugInfoParser consists of: +//! 1. A name +//! 2. An `is_valid` function which takes a BV and returns a bool +//! 3. A `parse` function which takes a `DebugInfo` object and uses the member functions `add_type`, `add_function`, and `add_data_variable` to populate all the info it can. +//! And finally calling `binaryninja::debuginfo::DebugInfoParser::register` to register it with the core. +//! +//! Here's a minimal, complete example boilerplate-plugin: +//! ``` +//! use binaryninja::{ +//! binaryview::BinaryView, +//! debuginfo::{CustomDebugInfoParser, DebugInfo, DebugInfoParser}, +//! }; +//! +//! struct ExampleDebugInfoParser; +//! +//! impl CustomDebugInfoParser for ExampleDebugInfoParser { +//! fn is_valid(&self, _view: &BinaryView) -> bool { +//! true +//! } +//! +//! fn parse_info(&self, _debug_info: &mut DebugInfo, _view: &BinaryView, _debug_file: &BinaryView, _progress: Box bool>) { +//! println!("Parsing info"); +//! } +//! } +//! +//! #[no_mangle] +//! pub extern "C" fn CorePluginInit() -> bool { +//! DebugInfoParser::register("example debug info parser", ExampleDebugInfoParser {}); +//! true +//! } +//! ``` +//! +//! `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); +//! ``` +//! +//! Multiple debug-info parsers can manually contribute debug info for a binary view by simply calling `parse_debug_info` with the +//! `DebugInfo` object just returned. This is automatic when opening a binary view with multiple valid debug info parsers. If you +//! wish to set the debug info for a binary view without applying it as well, you can call `binaryninja::binaryview::BinaryView::set_debug_info`. + +use binaryninjacore_sys::*; + +use crate::{ + binaryview::BinaryView, + platform::Platform, + rc::*, + string::{raw_to_string, BnStrCompatible, BnString}, + types::{DataVariableAndName, NameAndType, Type}, +}; + +use std::{hash::Hash, mem, os::raw::c_void, ptr, slice}; + +struct ProgressContext(Option Result<(), ()>>>); + +////////////////////// +// DebugInfoParser + +/// Represents the registered parsers and providers of debug information to Binary Ninja. +/// See `binaryninja::debuginfo` for more information +#[derive(PartialEq, Eq, Hash)] +pub struct DebugInfoParser { + pub(crate) handle: *mut BNDebugInfoParser, +} + +impl DebugInfoParser { + pub(crate) unsafe fn from_raw(handle: *mut BNDebugInfoParser) -> Ref { + debug_assert!(!handle.is_null()); + + Ref::new(Self { handle }) + } + + /// Returns debug info parser of the given name, if it exists + pub fn from_name(name: S) -> Result, ()> { + let name = name.into_bytes_with_nul(); + let parser = unsafe { BNGetDebugInfoParserByName(name.as_ref().as_ptr() as *mut _) }; + + if parser.is_null() { + Err(()) + } else { + unsafe { Ok(Self::from_raw(parser)) } + } + } + + /// List all debug-info parsers + pub fn list() -> Array { + let mut count: usize = unsafe { mem::zeroed() }; + 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 raw_parsers = unsafe { BNGetDebugInfoParsersForView(bv.handle, &mut count as *mut _) }; + unsafe { Array::new(raw_parsers, count, ()) } + } + + /// Returns the name of the current parser + pub fn name(&self) -> BnString { + unsafe { BnString::from_raw(BNGetDebugInfoParserName(self.handle)) } + } + + /// Returns whether this debug-info parser is valid for the provided binary view + pub fn is_valid_for_view(&self, view: &BinaryView) -> bool { + unsafe { BNIsDebugInfoParserValidForView(self.handle, view.handle) } + } + + extern "C" fn cb_progress(ctxt: *mut c_void, cur: usize, max: usize) -> bool { + ffi_wrap!("DebugInfoParser::cb_progress", unsafe { + let progress = ctxt as *mut ProgressContext; + match &(*progress).0 { + Some(func) => (func)(cur, max).is_ok(), + None => true, + } + }) + } + + /// Returns a `DebugInfo` object populated with debug info by this debug-info parser. Only provide a `DebugInfo` object if you wish to append to the existing debug info + pub fn parse_debug_info( + &self, + view: &BinaryView, + debug_file: &BinaryView, + existing_debug_info: Option<&DebugInfo>, + progress: Option Result<(), ()>>>, + ) -> Option> { + let mut progress_raw = ProgressContext(progress); + let info: *mut BNDebugInfo = match existing_debug_info { + Some(debug_info) => unsafe { + BNParseDebugInfo( + self.handle, + view.handle, + debug_file.handle, + debug_info.handle, + Some(Self::cb_progress), + &mut progress_raw as *mut _ as *mut c_void, + ) + }, + None => unsafe { + BNParseDebugInfo( + self.handle, + view.handle, + debug_file.handle, + ptr::null_mut(), + Some(Self::cb_progress), + &mut progress_raw as *mut _ as *mut c_void, + ) + }, + }; + if info.is_null() { + return None; + } + Some(unsafe { DebugInfo::from_raw(info) }) + } + + // Registers a DebugInfoParser. See `binaryninja::debuginfo::DebugInfoParser` for more details. + pub fn register(name: S, parser_callbacks: C) -> Ref + where + S: BnStrCompatible, + C: CustomDebugInfoParser, + { + extern "C" fn cb_is_valid(ctxt: *mut c_void, view: *mut BNBinaryView) -> bool + where + C: CustomDebugInfoParser, + { + ffi_wrap!("CustomDebugInfoParser::is_valid", unsafe { + let cmd = &*(ctxt as *const C); + let view = BinaryView::from_raw(view); + + cmd.is_valid(&view) + }) + } + + extern "C" fn cb_parse_info( + ctxt: *mut c_void, + debug_info: *mut BNDebugInfo, + view: *mut BNBinaryView, + debug_file: *mut BNBinaryView, + progress: Option bool>, + progress_ctxt: *mut c_void, + ) -> bool + where + C: CustomDebugInfoParser, + { + ffi_wrap!("CustomDebugInfoParser::parse_info", unsafe { + let cmd = &*(ctxt as *const C); + let view = BinaryView::from_raw(view); + let debug_file = BinaryView::from_raw(debug_file); + let mut debug_info = DebugInfo::from_raw(debug_info); + + cmd.parse_info( + &mut debug_info, + &view, + &debug_file, + Box::new(move |cur: usize, max: usize| match progress { + Some(func) => { + if func(progress_ctxt, cur, max) { + Ok(()) + } else { + Err(()) + } + } + _ => Ok(()), + }), + ) + }) + } + + let name = name.into_bytes_with_nul(); + let name_ptr = name.as_ref().as_ptr() as *mut _; + let ctxt = Box::into_raw(Box::new(parser_callbacks)); + + unsafe { + DebugInfoParser::from_raw(BNRegisterDebugInfoParser( + name_ptr, + Some(cb_is_valid::), + Some(cb_parse_info::), + ctxt as *mut _, + )) + } + } +} + +unsafe impl RefCountable for DebugInfoParser { + unsafe fn inc_ref(handle: &Self) -> Ref { + Ref::new(Self { + handle: BNNewDebugInfoParserReference(handle.handle), + }) + } + + unsafe fn dec_ref(handle: &Self) { + BNFreeDebugInfoParserReference(handle.handle); + } +} + +impl ToOwned for DebugInfoParser { + type Owned = Ref; + + fn to_owned(&self) -> Self::Owned { + unsafe { RefCountable::inc_ref(self) } + } +} + +impl CoreArrayProvider for DebugInfoParser { + type Raw = *mut BNDebugInfoParser; + type Context = (); +} + +unsafe impl CoreOwnedArrayProvider for DebugInfoParser { + unsafe fn free(raw: *mut Self::Raw, count: usize, _: &Self::Context) { + BNFreeDebugInfoParserList(raw, count); + } +} + +/////////////////////// +// DebugFunctionInfo + +/// Collates ground-truth function-external attributes for use in BinaryNinja's internal analysis. +/// +/// When contributing function info, provide only what you know - BinaryNinja will figure out everything else that it can, as it usually does. +/// +/// Functions will not be created if an address is not provided, but will be able to be queried from debug info for later user analysis. +pub struct DebugFunctionInfo { + short_name: Option, + full_name: Option, + raw_name: Option, + type_: Option>, + address: u64, + platform: Option>, + components: Vec, +} + +impl From<&BNDebugFunctionInfo> for DebugFunctionInfo { + fn from(raw: &BNDebugFunctionInfo) -> Self { + let components = unsafe { slice::from_raw_parts(raw.components, raw.componentN) } + .iter() + .map(|component| raw_to_string(*component as *const _).unwrap()) + .collect(); + + Self { + short_name: raw_to_string(raw.shortName), + full_name: raw_to_string(raw.fullName), + raw_name: raw_to_string(raw.rawName), + type_: if raw.type_.is_null() { + None + } else { + Some(unsafe { Type::ref_from_raw(raw.type_) }) + }, + address: raw.address, + platform: if raw.platform.is_null() { + None + } else { + Some(unsafe { Platform::ref_from_raw(raw.platform) }) + }, + components, + } + } +} + +impl DebugFunctionInfo { + pub fn new( + short_name: Option, + full_name: Option, + raw_name: Option, + type_: Option>, + address: Option, + platform: Option>, + components: Vec, + ) -> Self { + Self { + short_name, + full_name, + raw_name, + type_, + address: match address { + Some(address) => address, + _ => 0, + }, + platform, + components, + } + } +} + +/////////////// +// DebugInfo + +/// Provides an interface to both provide and query debug info. The DebugInfo object is used +/// internally by the binary view to which it is applied to determine the attributes of functions, types, and variables +/// that would otherwise be costly to deduce. +/// +/// DebugInfo objects themselves are independent of binary views; their data can be sourced from any arbitrary binary +/// views and be applied to any other arbitrary binary view. A DebugInfo object can also contain debug info from multiple +/// DebugInfoParsers. This makes it possible to gather debug info that may be distributed across several different +/// formats and files. +/// +/// DebugInfo cannot be instantiated by the user, instead get it from either the binary view (see `binaryninja::binaryview::BinaryView::debug_info`) +/// or a debug-info parser (see `binaryninja::debuginfo::DebugInfoParser::parse_debug_info`). +/// +/// Please note that calling one of `add_*` functions will not work outside of a debuginfo plugin. +#[derive(PartialEq, Eq, Hash)] +pub struct DebugInfo { + pub(crate) handle: *mut BNDebugInfo, +} + +impl DebugInfo { + pub(crate) unsafe fn from_raw(handle: *mut BNDebugInfo) -> Ref { + debug_assert!(!handle.is_null()); + + Ref::new(Self { handle }) + } + + /// Returns a generator of all types provided by a named DebugInfoParser + pub fn types_by_name(&self, parser_name: S) -> Vec> { + let parser_name = parser_name.into_bytes_with_nul(); + + let mut count: usize = 0; + let debug_types_ptr = unsafe { + BNGetDebugTypes( + self.handle, + parser_name.as_ref().as_ptr() as *mut _, + &mut count, + ) + }; + let result: Vec> = unsafe { + slice::from_raw_parts_mut(debug_types_ptr, count) + .iter() + .map(NameAndType::::from_raw) + .collect() + }; + + unsafe { BNFreeDebugTypes(debug_types_ptr, count) }; + result + } + + /// A generator of all types provided by DebugInfoParsers + 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 { + slice::from_raw_parts_mut(debug_types_ptr, count) + .iter() + .map(NameAndType::::from_raw) + .collect() + }; + + unsafe { BNFreeDebugTypes(debug_types_ptr, count) }; + result + } + + /// Returns a generator of all functions provided by a named DebugInfoParser + pub fn functions_by_name( + &self, + parser_name: S, + ) -> Vec { + let parser_name = parser_name.into_bytes_with_nul(); + + let mut count: usize = 0; + let functions_ptr = unsafe { + BNGetDebugFunctions( + self.handle, + parser_name.as_ref().as_ptr() as *mut _, + &mut count, + ) + }; + + let result: Vec = unsafe { + slice::from_raw_parts_mut(functions_ptr, count) + .iter() + .map(DebugFunctionInfo::from) + .collect() + }; + + unsafe { BNFreeDebugFunctions(functions_ptr, count) }; + result + } + + /// A generator of all functions provided by DebugInfoParsers + pub fn functions(&self) -> Vec { + let mut count: usize = 0; + let functions_ptr = + unsafe { BNGetDebugFunctions(self.handle, ptr::null_mut(), &mut count) }; + + let result: Vec = unsafe { + slice::from_raw_parts_mut(functions_ptr, count) + .iter() + .map(DebugFunctionInfo::from) + .collect() + }; + + unsafe { BNFreeDebugFunctions(functions_ptr, count) }; + result + } + + /// Returns a generator of all data variables provided by a named DebugInfoParser + pub fn data_variables_by_name( + &self, + parser_name: S, + ) -> Vec> { + let parser_name = parser_name.into_bytes_with_nul(); + + let mut count: usize = 0; + let data_variables_ptr = unsafe { + BNGetDebugDataVariables( + self.handle, + parser_name.as_ref().as_ptr() as *mut _, + &mut count, + ) + }; + + let result: Vec> = unsafe { + slice::from_raw_parts_mut(data_variables_ptr, count) + .iter() + .map(DataVariableAndName::::from_raw) + .collect() + }; + + unsafe { BNFreeDataVariablesAndName(data_variables_ptr, count) }; + result + } + + /// A generator of all data variables provided by DebugInfoParsers + pub fn data_variables(&self) -> Vec> { + let mut count: usize = 0; + let data_variables_ptr = + unsafe { BNGetDebugDataVariables(self.handle, ptr::null_mut(), &mut count) }; + + let result: Vec> = unsafe { + slice::from_raw_parts_mut(data_variables_ptr, count) + .iter() + .map(DataVariableAndName::::from_raw) + .collect() + }; + + unsafe { BNFreeDataVariablesAndName(data_variables_ptr, count) }; + result + } + + /// May return nullptr + pub fn type_by_name(&self, parser_name: S, name: S) -> Option> { + let parser_name = parser_name.into_bytes_with_nul(); + let name = name.into_bytes_with_nul(); + + let result = unsafe { + BNGetDebugTypeByName( + self.handle, + parser_name.as_ref().as_ptr() as *mut _, + name.as_ref().as_ptr() as *mut _, + ) + }; + if !result.is_null() { + Some(unsafe { Type::ref_from_raw(result) }) + } else { + None + } + } + + pub fn get_data_variable_by_name( + &self, + parser_name: S, + name: S, + ) -> Option<(u64, Ref)> { + let parser_name = parser_name.into_bytes_with_nul(); + let name = name.into_bytes_with_nul(); + + let result = unsafe { + BNGetDebugDataVariableByName( + self.handle, + parser_name.as_ref().as_ptr() as *mut _, + name.as_ref().as_ptr() as *mut _, + ) + }; + + if !result.is_null() { + unsafe { BNFreeString((*result).name) }; + Some(unsafe { ((*result).address, Type::ref_from_raw((*result).type_)) }) + } else { + None + } + } + + pub fn get_data_variable_by_address( + &self, + parser_name: S, + address: u64, + ) -> Option<(String, Ref)> { + let parser_name = parser_name.into_bytes_with_nul(); + let name_and_var = unsafe { + BNGetDebugDataVariableByAddress( + self.handle, + parser_name.as_ref().as_ptr() as *mut _, + address, + ) + }; + + if !name_and_var.is_null() { + let result = unsafe { + ( + raw_to_string((*name_and_var).name).unwrap(), + Type::ref_from_raw((*name_and_var).type_), + ) + }; + unsafe { BNFreeString((*name_and_var).name) }; + Some(result) + } else { + None + } + } + + // The tuple is (DebugInfoParserName, type) + pub fn get_types_by_name(&self, name: S) -> Vec<(String, Ref)> { + let name = name.into_bytes_with_nul(); + + let mut count: usize = 0; + let raw_names_and_types = unsafe { + BNGetDebugTypesByName(self.handle, name.as_ref().as_ptr() as *mut _, &mut count) + }; + + let names_and_types: &[*mut BNNameAndType] = + unsafe { slice::from_raw_parts(raw_names_and_types as *mut _, count) }; + + let result = names_and_types + .iter() + .take(count) + .map(|&name_and_type| unsafe { + ( + raw_to_string((*name_and_type).name).unwrap(), + Type::ref_from_raw(BNNewTypeReference((*name_and_type).type_)), + ) + }) + .collect(); + + unsafe { BNFreeNameAndTypeList(raw_names_and_types, count) }; + result + } + + // The tuple is (DebugInfoParserName, address, type) + pub fn get_data_variables_by_name( + &self, + name: S, + ) -> Vec<(String, u64, Ref)> { + let name = name.into_bytes_with_nul(); + + let mut count: usize = 0; + let raw_variables_and_names = unsafe { + BNGetDebugDataVariablesByName(self.handle, name.as_ref().as_ptr() as *mut _, &mut count) + }; + + let variables_and_names: &[*mut BNDataVariableAndName] = + unsafe { slice::from_raw_parts(raw_variables_and_names as *mut _, count) }; + + let result = variables_and_names + .iter() + .take(count) + .map(|&variable_and_name| unsafe { + ( + raw_to_string((*variable_and_name).name).unwrap(), + (*variable_and_name).address, + Type::ref_from_raw(BNNewTypeReference((*variable_and_name).type_)), + ) + }) + .collect(); + + unsafe { BNFreeDataVariablesAndName(raw_variables_and_names, count) }; + result + } + + /// The tuple is (DebugInfoParserName, TypeName, type) + pub fn get_data_variables_by_address(&self, address: u64) -> Vec<(String, String, Ref)> { + let mut count: usize = 0; + let raw_variables_and_names = + unsafe { BNGetDebugDataVariablesByAddress(self.handle, address, &mut count) }; + + let variables_and_names: &[*mut BNDataVariableAndNameAndDebugParser] = + unsafe { slice::from_raw_parts(raw_variables_and_names as *mut _, count) }; + + let result = variables_and_names + .iter() + .take(count) + .map(|&variable_and_name| unsafe { + ( + raw_to_string((*variable_and_name).parser).unwrap(), + raw_to_string((*variable_and_name).name).unwrap(), + Type::ref_from_raw(BNNewTypeReference((*variable_and_name).type_)), + ) + }) + .collect(); + + unsafe { BNFreeDataVariableAndNameAndDebugParserList(raw_variables_and_names, count) }; + result + } + + pub fn remove_parser_info(&self, parser_name: S) -> bool { + let parser_name = parser_name.into_bytes_with_nul(); + + unsafe { BNRemoveDebugParserInfo(self.handle, parser_name.as_ref().as_ptr() as *mut _) } + } + + pub fn remove_parser_types(&self, parser_name: S) -> bool { + let parser_name = parser_name.into_bytes_with_nul(); + + unsafe { BNRemoveDebugParserTypes(self.handle, parser_name.as_ref().as_ptr() as *mut _) } + } + + pub fn remove_parser_functions(&self, parser_name: S) -> bool { + let parser_name = parser_name.into_bytes_with_nul(); + + unsafe { + BNRemoveDebugParserFunctions(self.handle, parser_name.as_ref().as_ptr() as *mut _) + } + } + + pub fn remove_parser_data_variables(&self, parser_name: S) -> bool { + let parser_name = parser_name.into_bytes_with_nul(); + + unsafe { + BNRemoveDebugParserDataVariables(self.handle, parser_name.as_ref().as_ptr() as *mut _) + } + } + + pub fn remove_type_by_name(&self, parser_name: S, name: S) -> bool { + let parser_name = parser_name.into_bytes_with_nul(); + let name = name.into_bytes_with_nul(); + + unsafe { + BNRemoveDebugTypeByName( + self.handle, + parser_name.as_ref().as_ptr() as *mut _, + name.as_ref().as_ptr() as *mut _, + ) + } + } + + pub fn remove_function_by_index( + &self, + parser_name: S, + index: usize, + ) -> bool { + let parser_name = parser_name.into_bytes_with_nul(); + + unsafe { + BNRemoveDebugFunctionByIndex( + self.handle, + parser_name.as_ref().as_ptr() as *mut _, + index, + ) + } + } + + pub fn remove_data_variable_by_address( + &self, + parser_name: S, + address: u64, + ) -> bool { + let parser_name = parser_name.into_bytes_with_nul(); + + unsafe { + BNRemoveDebugDataVariableByAddress( + self.handle, + parser_name.as_ref().as_ptr() as *mut _, + address, + ) + } + } + + /// Adds a type scoped under the current parser's name to the debug info + pub fn add_type( + &self, + name: S, + new_type: &Type, + components: &[&str], + ) -> bool { + let mut components_array: Vec<*const ::std::os::raw::c_char> = + Vec::with_capacity(components.len()); + for component in components { + components_array.push(component.as_ptr() as _); + } + + let name = name.into_bytes_with_nul(); + unsafe { + BNAddDebugType( + self.handle, + name.as_ref().as_ptr() as *mut _, + new_type.handle, + components_array.as_ptr() as _, + components.len(), + ) + } + } + + /// Adds a function scoped under the current parser's name to the debug info + pub fn add_function(&self, new_func: DebugFunctionInfo) -> bool { + 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 _ + }); + 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 _ + }); + 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 _ + }); + + let mut components_array: Vec<*const ::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 _); + } + + unsafe { + BNAddDebugFunction( + self.handle, + &mut BNDebugFunctionInfo { + shortName: short_name, + fullName: full_name, + rawName: raw_name, + address: new_func.address, + type_: match new_func.type_ { + Some(type_) => type_.handle, + _ => ptr::null_mut(), + }, + platform: match new_func.platform { + Some(platform) => platform.handle, + _ => ptr::null_mut(), + }, + components: components_array.as_ptr() as _, + componentN: new_func.components.len(), + }, + ) + } + } + + /// Adds a data variable scoped under the current parser's name to the debug info + pub fn add_data_variable( + &self, + address: u64, + t: &Type, + name: Option, + components: &[&str], + ) -> bool { + let mut components_array: Vec<*const ::std::os::raw::c_char> = + Vec::with_capacity(components.len()); + for component in components { + components_array.push(component.as_ptr() as _); + } + + match name { + Some(name) => { + let name = name.into_bytes_with_nul(); + unsafe { + BNAddDebugDataVariable( + self.handle, + address, + t.handle, + name.as_ref().as_ptr() as *mut _, + components.as_ptr() as _, + components.len(), + ) + } + } + None => unsafe { + BNAddDebugDataVariable( + self.handle, + address, + t.handle, + ptr::null_mut(), + components.as_ptr() as _, + components.len(), + ) + }, + } + } + + pub fn add_data_variable_info(&self, var: DataVariableAndName) -> bool { + let name = var.name.into_bytes_with_nul(); + unsafe { + BNAddDebugDataVariableInfo( + self.handle, + &BNDataVariableAndName { + address: var.address, + type_: var.t.contents.handle, + name: name.as_ref().as_ptr() as *mut _, + autoDiscovered: var.auto_discovered, + typeConfidence: var.t.confidence, + }, + ) + } + } +} + +unsafe impl RefCountable for DebugInfo { + unsafe fn inc_ref(handle: &Self) -> Ref { + Ref::new(Self { + handle: BNNewDebugInfoReference(handle.handle), + }) + } + + unsafe fn dec_ref(handle: &Self) { + BNFreeDebugInfoReference(handle.handle); + } +} + +impl ToOwned for DebugInfo { + type Owned = Ref; + + fn to_owned(&self) -> Self::Owned { + unsafe { RefCountable::inc_ref(self) } + } +} + +//////////////////////////// +// CustomDebugInfoParser + +/// Implement this trait to implement a debug info parser. See `DebugInfoParser` for more details. +pub trait CustomDebugInfoParser: 'static + Sync { + fn is_valid(&self, view: &BinaryView) -> bool; + fn parse_info( + &self, + debug_info: &mut DebugInfo, + view: &BinaryView, + debug_file: &BinaryView, + progress: Box Result<(), ()>>, + ) -> bool; +} diff --git a/src/demangle.rs b/src/demangle.rs new file mode 100644 index 0000000..2efa0ca --- /dev/null +++ b/src/demangle.rs @@ -0,0 +1,138 @@ +// 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. + +//! Interfaces for demangling and simplifying mangled names in binaries. + +use binaryninjacore_sys::*; +use std::os::raw::c_char; +use std::{ffi::CStr, result}; + +use crate::architecture::CoreArchitecture; +use crate::string::{BnStrCompatible, BnString}; +use crate::types::Type; + +use crate::rc::*; + +pub type Result = result::Result; + +pub fn demangle_gnu3( + arch: &CoreArchitecture, + mangled_name: S, + simplify: bool, +) -> 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_size: usize = 0; + let res = unsafe { + BNDemangleGNU3( + arch.0, + mangled_name_ptr.as_ptr() as *const c_char, + &mut out_type, + &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_gnu3: failed to parse mangled name"); + return Err(()); + } + }; + return Ok((None, vec![cstr.to_string_lossy().into_owned()])); + } + + let out_type = match out_type.is_null() { + true => { + log::debug!("demangle_gnu3: out_type is NULL"); + None + } + false => Some(unsafe { Type::ref_from_raw(out_type) }), + }; + + if out_name.is_null() { + log::error!("demangle_gnu3: out_name is NULL"); + return Err(()); + } + + let names = unsafe { ArrayGuard::::new(out_name, out_size, ()) } + .iter() + .map(|name| name.to_string()) + .collect(); + + unsafe { BNFreeDemangledName(&mut out_name, out_size) }; + + Ok((out_type, names)) +} + +pub fn demangle_ms( + arch: &CoreArchitecture, + mangled_name: S, + simplify: bool, +) -> 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_size: usize = 0; + let res = unsafe { + BNDemangleMS( + arch.0, + mangled_name_ptr.as_ptr() as *const c_char, + &mut out_type, + &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_ms: failed to parse mangled name"); + return Err(()); + } + }; + return Ok((None, vec![cstr.to_string_lossy().into_owned()])); + } + + let out_type = match out_type.is_null() { + true => { + log::debug!("demangle_ms: out_type is NULL"); + None + } + false => Some(unsafe { Type::ref_from_raw(out_type) }), + }; + + if out_name.is_null() { + log::error!("demangle_ms: out_name is NULL"); + return Err(()); + } + + let names = unsafe { ArrayGuard::::new(out_name, out_size, ()) } + .iter() + .map(|name| name.to_string()) + .collect(); + + unsafe { BNFreeDemangledName(&mut out_name, out_size) }; + + Ok((out_type, names)) +} diff --git a/src/disassembly.rs b/src/disassembly.rs new file mode 100644 index 0000000..161eebc --- /dev/null +++ b/src/disassembly.rs @@ -0,0 +1,458 @@ +// Copyright 2021-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. + +// TODO : Combine this with the architecture implementation + +use binaryninjacore_sys::*; + +use crate::string::{BnStr, BnString}; +use crate::{BN_FULL_CONFIDENCE, BN_INVALID_EXPR}; + +use crate::rc::*; + +use std::convert::From; +use std::mem; +use std::ptr; + +pub type InstructionTextTokenType = BNInstructionTextTokenType; +pub type InstructionTextTokenContext = BNInstructionTextTokenContext; + +// InstructionTextTokenType's; * = Implemented +// *TextToken = 0, +// *InstructionToken = 1, +// *OperandSeparatorToken = 2, +// *RegisterToken = 3, +// *IntegerToken = 4, +// *PossibleAddressToken = 5, +// *BeginMemoryOperandToken = 6, +// *EndMemoryOperandToken = 7, +// *FloatingPointToken = 8, +// AnnotationToken = 9, +// *CodeRelativeAddressToken = 10, +// ArgumentNameToken = 11, +// HexDumpByteValueToken = 12, +// HexDumpSkippedByteToken = 13, +// HexDumpInvalidByteToken = 14, +// HexDumpTextToken = 15, +// OpcodeToken = 16, +// *StringToken = 17, +// CharacterConstantToken = 18, +// *KeywordToken = 19, +// *TypeNameToken = 20, +// *FieldNameToken = 21, +// *NameSpaceToken = 22, +// NameSpaceSeparatorToken = 23, +// TagToken = 24, +// StructOffsetToken = 25, +// StructOffsetByteValueToken = 26, +// StructureHexDumpTextToken = 27, +// *GotoLabelToken = 28, +// CommentToken = 29, +// PossibleValueToken = 30, +// PossibleValueTypeToken = 31, +// ArrayIndexToken = 32, +// *IndentationToken = 33, +// UnknownMemoryToken = 34, +// CodeSymbolToken = 64, +// DataSymbolToken = 65, +// LocalVariableToken = 66, +// ImportToken = 67, +// AddressDisplayToken = 68, +// IndirectImportToken = 69, +// ExternalSymbolToken = 70, + +#[repr(C)] +pub struct InstructionTextToken(pub(crate) BNInstructionTextToken); + +#[derive(Copy, Clone, PartialEq, Eq, Hash, Debug)] +pub enum InstructionTextTokenContents { + Text, + Instruction, + OperandSeparator, + Register, + Integer(u64), // TODO size? + PossibleAddress(u64), // TODO size? + BeginMemoryOperand, + EndMemoryOperand, + FloatingPoint, + CodeRelativeAddress(u64), + String(u64), + Keyword, + TypeName, + FieldName, + NameSpace, + GotoLabel(u64), + Indentation, +} + +impl InstructionTextToken { + pub(crate) unsafe fn from_raw(raw: &BNInstructionTextToken) -> Self { + Self(*raw) + } + + pub fn new(text: BnString, contents: InstructionTextTokenContents) -> Self { + let (value, address) = match contents { + InstructionTextTokenContents::Integer(v) => (v, 0), + InstructionTextTokenContents::PossibleAddress(v) + | InstructionTextTokenContents::CodeRelativeAddress(v) + | InstructionTextTokenContents::GotoLabel(v) => (v, v), + InstructionTextTokenContents::String(v) => (v, 0), + _ => (0, 0), + }; + + let type_ = match contents { + InstructionTextTokenContents::Text => InstructionTextTokenType::TextToken, + InstructionTextTokenContents::Instruction => InstructionTextTokenType::InstructionToken, + InstructionTextTokenContents::OperandSeparator => { + InstructionTextTokenType::OperandSeparatorToken + } + InstructionTextTokenContents::Register => InstructionTextTokenType::RegisterToken, + InstructionTextTokenContents::Integer(_) => InstructionTextTokenType::IntegerToken, + InstructionTextTokenContents::PossibleAddress(_) => { + InstructionTextTokenType::PossibleAddressToken + } + InstructionTextTokenContents::BeginMemoryOperand => { + InstructionTextTokenType::BeginMemoryOperandToken + } + InstructionTextTokenContents::EndMemoryOperand => { + InstructionTextTokenType::EndMemoryOperandToken + } + InstructionTextTokenContents::FloatingPoint => { + InstructionTextTokenType::FloatingPointToken + } + InstructionTextTokenContents::CodeRelativeAddress(_) => { + InstructionTextTokenType::CodeRelativeAddressToken + } + InstructionTextTokenContents::String(_) => InstructionTextTokenType::StringToken, + InstructionTextTokenContents::Keyword => InstructionTextTokenType::KeywordToken, + InstructionTextTokenContents::TypeName => InstructionTextTokenType::TypeNameToken, + InstructionTextTokenContents::FieldName => InstructionTextTokenType::FieldNameToken, + InstructionTextTokenContents::NameSpace => InstructionTextTokenType::NameSpaceToken, + InstructionTextTokenContents::GotoLabel(_) => InstructionTextTokenType::GotoLabelToken, + InstructionTextTokenContents::Indentation => InstructionTextTokenType::IndentationToken, + }; + + let width = text.len() as u64; + + InstructionTextToken(BNInstructionTextToken { + type_, + text: text.into_raw(), + value, + width, + size: 0, + operand: 0xffff_ffff, + context: InstructionTextTokenContext::NoTokenContext, + confidence: BN_FULL_CONFIDENCE, + address, + typeNames: ptr::null_mut(), + namesCount: 0, + }) + } + + pub fn set_value(&mut self, value: u64) { + self.0.value = value; + } + + pub fn set_context(&mut self, context: InstructionTextTokenContext) { + self.0.context = context; + } + + pub fn text(&self) -> &BnStr { + unsafe { BnStr::from_raw(self.0.text) } + } + + pub fn contents(&self) -> InstructionTextTokenContents { + use self::BNInstructionTextTokenType::*; + use self::InstructionTextTokenContents::*; + + match self.0.type_ { + TextToken => Text, + InstructionToken => Instruction, + OperandSeparatorToken => OperandSeparator, + RegisterToken => Register, + IntegerToken => Integer(self.0.value), + PossibleAddressToken => PossibleAddress(self.0.value), + BeginMemoryOperandToken => BeginMemoryOperand, + EndMemoryOperandToken => EndMemoryOperand, + FloatingPointToken => FloatingPoint, + CodeRelativeAddressToken => CodeRelativeAddress(self.0.value), + _ => unimplemented!("woops"), + } + } + + pub fn context(&self) -> InstructionTextTokenContext { + self.0.context + } + + pub fn size(&self) -> usize { + self.0.size + } + + pub fn operand(&self) -> usize { + self.0.operand + } + + pub fn address(&self) -> u64 { + self.0.address + } +} + +impl Default for InstructionTextToken { + fn default() -> Self { + InstructionTextToken(BNInstructionTextToken { + type_: InstructionTextTokenType::TextToken, + text: ptr::null_mut(), + value: 0, + width: 0, + size: 0, + operand: 0, + context: InstructionTextTokenContext::NoTokenContext, + confidence: BN_FULL_CONFIDENCE, + address: 0, + typeNames: ptr::null_mut(), + namesCount: 0, + }) + } +} + +impl Clone for InstructionTextToken { + fn clone(&self) -> Self { + InstructionTextToken(BNInstructionTextToken { + type_: self.0.type_, + context: self.0.context, + address: self.0.address, + size: self.0.size, + operand: self.0.operand, + value: self.0.value, + width: 0, + text: BnString::new(self.text()).into_raw(), + confidence: 0xff, + typeNames: ptr::null_mut(), + namesCount: 0, + }) + } +} + +// 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) }; +// } +// } + +pub struct DisassemblyTextLine(pub(crate) BNDisassemblyTextLine); + +impl DisassemblyTextLine { + // TODO : this should probably be removed, though it doesn't actually hurt anything + pub fn debug_print(&self) { + println!("{}", self); + } + + pub fn addr(&self) -> u64 { + self.0.addr + } + + pub fn instr_idx(&self) -> usize { + self.0.instrIndex + } + + pub fn count(&self) -> usize { + self.0.count + } + + pub fn tag_count(&self) -> usize { + self.0.tagCount + } + + pub fn tokens(&self) -> Vec { + unsafe { + std::slice::from_raw_parts::(self.0.tokens, self.0.count) + .iter() + .map(|&x| InstructionTextToken::from_raw(&x)) + .collect() + } + } +} + +impl std::fmt::Display for DisassemblyTextLine { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + for token in self.tokens() { + write!(f, "{}", token.text())?; + } + + Ok(()) + } +} + +impl From> for DisassemblyTextLine { + fn from(mut tokens: Vec) -> Self { + tokens.shrink_to_fit(); + + 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(); + mem::forget(tokens); + + DisassemblyTextLine(BNDisassemblyTextLine { + addr: 0, + instrIndex: BN_INVALID_EXPR, + tokens: tokens_pointer as *mut _, + count: tokens_len, + highlight: BNHighlightColor { + style: BNHighlightColorStyle::StandardHighlightColor, + color: BNHighlightStandardColor::NoHighlightColor, + mixColor: BNHighlightStandardColor::NoHighlightColor, + mix: 0, + r: 0, + g: 0, + b: 0, + alpha: 0, + }, + tags: ptr::null_mut(), + tagCount: 0, + typeInfo: BNDisassemblyTextLineTypeInfo { + hasTypeInfo: false, + parentType: ptr::null_mut(), + fieldIndex: usize::MAX, + offset: 0, + }, + }) + } +} + +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 + })); + + 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(); + mem::forget(tokens); + + DisassemblyTextLine(BNDisassemblyTextLine { + addr: 0, + instrIndex: BN_INVALID_EXPR, + tokens: tokens_pointer as *mut _, + count: tokens_len, + highlight: BNHighlightColor { + style: BNHighlightColorStyle::StandardHighlightColor, + color: BNHighlightStandardColor::NoHighlightColor, + mixColor: BNHighlightStandardColor::NoHighlightColor, + mix: 0, + r: 0, + g: 0, + b: 0, + alpha: 0, + }, + tags: ptr::null_mut(), + tagCount: 0, + typeInfo: BNDisassemblyTextLineTypeInfo { + hasTypeInfo: false, + parentType: ptr::null_mut(), + fieldIndex: usize::MAX, + offset: 0, + }, + }) + } +} + +impl Default for DisassemblyTextLine { + fn default() -> Self { + DisassemblyTextLine(BNDisassemblyTextLine { + addr: 0, + instrIndex: BN_INVALID_EXPR, + tokens: ptr::null_mut(), + count: 0, + highlight: BNHighlightColor { + style: BNHighlightColorStyle::StandardHighlightColor, + color: BNHighlightStandardColor::NoHighlightColor, + mixColor: BNHighlightStandardColor::NoHighlightColor, + mix: 0, + r: 0, + g: 0, + b: 0, + alpha: 0, + }, + tags: ptr::null_mut(), + tagCount: 0, + typeInfo: BNDisassemblyTextLineTypeInfo { + hasTypeInfo: false, + parentType: ptr::null_mut(), + fieldIndex: usize::MAX, + offset: 0, + }, + }) + } +} + +impl Drop for DisassemblyTextLine { + fn drop(&mut self) { + unsafe { + Vec::from_raw_parts(self.0.tokens, self.0.count, self.0.count); + } + } +} + +pub type DisassemblyOption = BNDisassemblyOption; + +#[derive(PartialEq, Eq, Hash)] +pub struct DisassemblySettings { + pub(crate) handle: *mut BNDisassemblySettings, +} + +impl DisassemblySettings { + pub fn new() -> Ref { + unsafe { + let handle = BNCreateDisassemblySettings(); + + debug_assert!(!handle.is_null()); + + Ref::new(Self { handle }) + } + } + + pub fn set_option(&self, option: DisassemblyOption, state: bool) { + unsafe { BNSetDisassemblySettingsOption(self.handle, option, state) } + } + + pub fn is_option_set(&self, option: DisassemblyOption) -> bool { + unsafe { BNIsDisassemblySettingsOptionSet(self.handle, option) } + } +} + +impl ToOwned for DisassemblySettings { + type Owned = Ref; + + fn to_owned(&self) -> Self::Owned { + unsafe { RefCountable::inc_ref(self) } + } +} + +unsafe impl RefCountable for DisassemblySettings { + unsafe fn inc_ref(handle: &Self) -> Ref { + Ref::new(Self { + handle: BNNewDisassemblySettingsReference(handle.handle), + }) + } + + unsafe fn dec_ref(handle: &Self) { + BNFreeDisassemblySettings(handle.handle); + } +} diff --git a/src/downloadprovider.rs b/src/downloadprovider.rs new file mode 100644 index 0000000..4ba07db --- /dev/null +++ b/src/downloadprovider.rs @@ -0,0 +1,308 @@ +use crate::rc::{ + Array, CoreArrayProvider, CoreArrayWrapper, CoreOwnedArrayProvider, Ref, RefCountable, +}; +use crate::settings::Settings; +use crate::string::{BnStr, BnStrCompatible, BnString}; +use binaryninjacore_sys::*; +use std::collections::HashMap; +use std::ffi::c_void; +use std::os::raw::c_char; +use std::ptr::null_mut; +use std::slice; + +pub struct DownloadProvider { + handle: *mut BNDownloadProvider, +} + +impl DownloadProvider { + pub fn get(name: S) -> Option { + let result = unsafe { + BNGetDownloadProviderByName( + name.into_bytes_with_nul().as_ref().as_ptr() as *const c_char + ) + }; + if result.is_null() { + return None; + } + Some(DownloadProvider { handle: result }) + } + + pub fn list() -> Result, ()> { + let mut count = 0; + let list: *mut *mut BNDownloadProvider = unsafe { BNGetDownloadProviderList(&mut count) }; + + if list.is_null() { + return Err(()); + } + + Ok(unsafe { Array::new(list, count, ()) }) + } + + /// TODO : We may want to `impl Default`....excessive error checking might be preventing us from doing so + pub fn try_default() -> Result { + let s = Settings::new(""); + let dp_name = s.get_string("network.downloadProviderName", None, None); + Self::get(dp_name).ok_or(()) + } + + pub(crate) fn from_raw(handle: *mut BNDownloadProvider) -> DownloadProvider { + Self { handle } + } + + pub fn create_instance(&self) -> Result, ()> { + let result: *mut BNDownloadInstance = + unsafe { BNCreateDownloadProviderInstance(self.handle) }; + if result.is_null() { + return Err(()); + } + + Ok(unsafe { DownloadInstance::ref_from_raw(result) }) + } +} + +impl CoreArrayProvider for DownloadProvider { + type Raw = *mut BNDownloadProvider; + type Context = (); +} + +unsafe impl CoreOwnedArrayProvider 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) + } +} + +pub struct DownloadInstanceOutputCallbacks { + pub write: Option usize>>, + pub progress: Option bool>>, +} + +pub struct DownloadInstanceInputOutputCallbacks { + pub read: Option Option>>, + pub write: Option usize>>, + pub progress: Option bool>>, +} + +pub struct DownloadResponse { + pub status_code: u16, + pub headers: HashMap, +} + +pub struct DownloadInstance { + handle: *mut BNDownloadInstance, +} + +impl DownloadInstance { + pub(crate) unsafe fn from_raw(handle: *mut BNDownloadInstance) -> Self { + debug_assert!(!handle.is_null()); + + Self { handle } + } + + pub(crate) unsafe fn ref_from_raw(handle: *mut BNDownloadInstance) -> Ref { + Ref::new(Self::from_raw(handle)) + } + + fn get_error(&self) -> BnString { + let err: *mut c_char = unsafe { BNGetErrorForDownloadInstance(self.handle) }; + unsafe { BnString::from_raw(err) } + } + + unsafe extern "C" fn o_write_callback(data: *mut u8, len: u64, ctxt: *mut c_void) -> u64 { + let callbacks = ctxt as *mut DownloadInstanceOutputCallbacks; + if let Some(func) = &mut (*callbacks).write { + let slice = slice::from_raw_parts(data, len as usize); + let result = (func)(slice); + result as u64 + } else { + 0u64 + } + } + + unsafe extern "C" fn o_progress_callback(ctxt: *mut c_void, progress: u64, total: u64) -> bool { + let callbacks = ctxt as *mut DownloadInstanceOutputCallbacks; + if let Some(func) = &mut (*callbacks).progress { + (func)(progress, total) + } else { + true + } + } + + pub fn perform_request( + &mut self, + url: S, + callbacks: DownloadInstanceOutputCallbacks, + ) -> Result<(), BnString> { + let callbacks = Box::into_raw(Box::new(callbacks)); + let mut cbs = BNDownloadInstanceOutputCallbacks { + writeCallback: Some(Self::o_write_callback), + writeContext: callbacks as *mut c_void, + progressCallback: Some(Self::o_progress_callback), + progressContext: callbacks as *mut c_void, + }; + + let result = unsafe { + BNPerformDownloadRequest( + self.handle, + url.into_bytes_with_nul().as_ref().as_ptr() as *const c_char, + &mut cbs as *mut BNDownloadInstanceOutputCallbacks, + ) + }; + + // Drop it + unsafe { drop(Box::from_raw(callbacks)) }; + if result < 0 { + Err(self.get_error()) + } else { + Ok(()) + } + } + + unsafe extern "C" fn i_read_callback(data: *mut u8, len: u64, ctxt: *mut c_void) -> i64 { + let callbacks = ctxt as *mut DownloadInstanceInputOutputCallbacks; + if let Some(func) = &mut (*callbacks).read { + let slice = slice::from_raw_parts_mut(data, len as usize); + let result = (func)(slice); + if let Some(count) = result { + count as i64 + } else { + -1 + } + } else { + 0 + } + } + + unsafe extern "C" fn i_write_callback(data: *mut u8, len: u64, ctxt: *mut c_void) -> u64 { + let callbacks = ctxt as *mut DownloadInstanceInputOutputCallbacks; + if let Some(func) = &mut (*callbacks).write { + let slice = slice::from_raw_parts(data, len as usize); + let result = (func)(slice); + result as u64 + } else { + 0 + } + } + + unsafe extern "C" fn i_progress_callback(ctxt: *mut c_void, progress: u64, total: u64) -> bool { + let callbacks = ctxt as *mut DownloadInstanceInputOutputCallbacks; + if let Some(func) = &mut (*callbacks).progress { + (func)(progress, total) + } else { + true + } + } + + pub fn perform_custom_request< + M: BnStrCompatible, + U: BnStrCompatible, + HK: BnStrCompatible, + HV: BnStrCompatible, + I: IntoIterator, + >( + &mut self, + method: M, + url: U, + headers: I, + callbacks: DownloadInstanceInputOutputCallbacks, + ) -> Result { + let mut header_keys = vec![]; + let mut header_values = vec![]; + for (key, value) in headers { + header_keys.push(key.into_bytes_with_nul()); + header_values.push(value.into_bytes_with_nul()); + } + + let mut header_key_ptrs = vec![]; + let mut header_value_ptrs = vec![]; + + for (key, value) in header_keys.iter().zip(header_values.iter()) { + header_key_ptrs.push(key.as_ref().as_ptr() as *const c_char); + header_value_ptrs.push(value.as_ref().as_ptr() as *const c_char); + } + + let callbacks = Box::into_raw(Box::new(callbacks)); + let mut cbs = BNDownloadInstanceInputOutputCallbacks { + readCallback: Some(Self::i_read_callback), + readContext: callbacks as *mut c_void, + writeCallback: Some(Self::i_write_callback), + writeContext: callbacks as *mut c_void, + progressCallback: Some(Self::i_progress_callback), + progressContext: callbacks as *mut c_void, + }; + + let mut response: *mut BNDownloadInstanceResponse = null_mut(); + + let result = unsafe { + BNPerformCustomRequest( + self.handle, + method.into_bytes_with_nul().as_ref().as_ptr() as *const c_char, + url.into_bytes_with_nul().as_ref().as_ptr() as *const c_char, + header_key_ptrs.len() as u64, + header_key_ptrs.as_ptr(), + header_value_ptrs.as_ptr(), + &mut response as *mut *mut BNDownloadInstanceResponse, + &mut cbs as *mut BNDownloadInstanceInputOutputCallbacks, + ) + }; + + if result < 0 { + unsafe { BNFreeDownloadInstanceResponse(response) }; + return Err(self.get_error()); + } + + let mut response_headers = HashMap::new(); + unsafe { + let response_header_keys: &[*mut c_char] = + slice::from_raw_parts((*response).headerKeys, (*response).headerCount as usize); + let response_header_values: &[*mut c_char] = + slice::from_raw_parts((*response).headerValues, (*response).headerCount as usize); + + for (key, value) in response_header_keys + .iter() + .zip(response_header_values.iter()) + { + response_headers.insert( + BnStr::from_raw(*key).to_string(), + BnStr::from_raw(*value).to_string(), + ); + } + } + + let r = DownloadResponse { + status_code: unsafe { (*response).statusCode }, + headers: response_headers, + }; + + unsafe { BNFreeDownloadInstanceResponse(response) }; + + Ok(r) + } +} + +impl ToOwned for DownloadInstance { + type Owned = Ref; + + fn to_owned(&self) -> Self::Owned { + unsafe { RefCountable::inc_ref(self) } + } +} + +unsafe impl RefCountable for DownloadInstance { + unsafe fn inc_ref(handle: &Self) -> Ref { + Ref::new(Self { + handle: BNNewDownloadInstanceReference(handle.handle), + }) + } + + unsafe fn dec_ref(handle: &Self) { + BNFreeDownloadInstance(handle.handle); + } +} diff --git a/src/ffi.rs b/src/ffi.rs new file mode 100644 index 0000000..eb58482 --- /dev/null +++ b/src/ffi.rs @@ -0,0 +1,25 @@ +// Copyright 2021-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. + +macro_rules! ffi_wrap { + ($n:expr, $b:expr) => {{ + use std::panic; + use std::process; + + panic::catch_unwind(|| $b).unwrap_or_else(|_| { + error!("ffi callback caught panic: {}", $n); + process::abort() + }) + }}; +} diff --git a/src/fileaccessor.rs b/src/fileaccessor.rs new file mode 100644 index 0000000..cf94e90 --- /dev/null +++ b/src/fileaccessor.rs @@ -0,0 +1,90 @@ +// Copyright 2021-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 binaryninjacore_sys::BNFileAccessor; +use std::io::{Read, Seek, SeekFrom, Write}; +use std::marker::PhantomData; +use std::slice; + +pub struct FileAccessor<'a> { + pub(crate) api_object: BNFileAccessor, + _ref: PhantomData<&'a mut ()>, +} + +impl<'a> FileAccessor<'a> { + pub fn new(f: &'a mut F) -> Self + where + F: 'a + Read + Write + Seek + Sized, + { + use std::os::raw::c_void; + + extern "C" fn cb_get_length(ctxt: *mut c_void) -> u64 + where + F: Read + Write + Seek + Sized, + { + let f = unsafe { &mut *(ctxt as *mut F) }; + + f.seek(SeekFrom::End(0)).unwrap_or(0) + } + + extern "C" fn cb_read( + ctxt: *mut c_void, + dest: *mut c_void, + offset: u64, + len: usize, + ) -> usize + where + F: Read + Write + Seek + Sized, + { + let f = unsafe { &mut *(ctxt as *mut F) }; + let dest = unsafe { slice::from_raw_parts_mut(dest as *mut u8, len) }; + + if f.seek(SeekFrom::Start(offset)).is_err() { + debug!("Failed to seek to offset {:x}", offset); + 0 + } else { + f.read(dest).unwrap_or(0) + } + } + + extern "C" fn cb_write( + ctxt: *mut c_void, + offset: u64, + src: *const c_void, + len: usize, + ) -> usize + where + F: Read + Write + Seek + Sized, + { + let f = unsafe { &mut *(ctxt as *mut F) }; + let src = unsafe { slice::from_raw_parts(src as *const u8, len) }; + + if f.seek(SeekFrom::Start(offset)).is_err() { + 0 + } else { + f.write(src).unwrap_or(0) + } + } + + Self { + api_object: BNFileAccessor { + context: f as *mut F as *mut _, + getLength: Some(cb_get_length::), + read: Some(cb_read::), + write: Some(cb_write::), + }, + _ref: PhantomData, + } + } +} diff --git a/src/filemetadata.rs b/src/filemetadata.rs new file mode 100644 index 0000000..6f335c2 --- /dev/null +++ b/src/filemetadata.rs @@ -0,0 +1,290 @@ +// Copyright 2021-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 binaryninjacore_sys::{ + BNBeginUndoActions, + BNCloseFile, + BNCommitUndoActions, + BNCreateDatabase, + BNCreateFileMetadata, + BNFileMetadata, + BNFreeFileMetadata, + BNGetCurrentOffset, + BNGetCurrentView, + BNGetFileViewOfType, + BNGetFilename, + BNIsAnalysisChanged, + BNIsBackedByDatabase, + //BNSetFileMetadataNavigationHandler, + BNIsFileModified, + BNMarkFileModified, + BNMarkFileSaved, + BNNavigate, + BNNewFileReference, + BNOpenDatabaseForConfiguration, + BNOpenExistingDatabase, + BNRedo, + BNRevertUndoActions, + BNSaveAutoSnapshot, + BNSetFilename, + BNUndo, +}; + +use crate::binaryview::BinaryView; + +use crate::rc::*; +use crate::string::*; + +use std::ptr; + +#[derive(PartialEq, Eq, Hash)] +pub struct FileMetadata { + pub(crate) handle: *mut BNFileMetadata, +} + +unsafe impl Send for FileMetadata {} +unsafe impl Sync for FileMetadata {} + +impl FileMetadata { + pub(crate) fn from_raw(handle: *mut BNFileMetadata) -> Self { + Self { handle } + } + + pub fn new() -> Ref { + unsafe { + Ref::new(Self { + handle: BNCreateFileMetadata(), + }) + } + } + + pub fn with_filename(name: S) -> Ref { + let ret = FileMetadata::new(); + ret.set_filename(name); + ret + } + + pub fn close(&self) { + unsafe { + BNCloseFile(self.handle); + } + } + + pub fn filename(&self) -> BnString { + unsafe { + let raw = BNGetFilename(self.handle); + BnString::from_raw(raw) + } + } + + pub fn set_filename(&self, name: S) { + let name = name.into_bytes_with_nul(); + + unsafe { + BNSetFilename(self.handle, name.as_ref().as_ptr() as *mut _); + } + } + + pub fn modified(&self) -> bool { + unsafe { BNIsFileModified(self.handle) } + } + + pub fn mark_modified(&self) { + unsafe { + BNMarkFileModified(self.handle); + } + } + + pub fn mark_saved(&self) { + unsafe { + BNMarkFileSaved(self.handle); + } + } + + pub fn is_analysis_changed(&self) -> bool { + unsafe { BNIsAnalysisChanged(self.handle) } + } + + pub fn is_database_backed(&self, view_type: S) -> bool { + let view_type = view_type.into_bytes_with_nul(); + + unsafe { BNIsBackedByDatabase(self.handle, view_type.as_ref().as_ptr() as *const _) } + } + + pub fn run_undoable_transaction Result, T, E>( + &self, + func: F, + ) -> Result { + let undo = self.begin_undo_actions(false); + let result = func(); + match result { + Ok(t) => { + self.commit_undo_actions(undo); + Ok(t) + } + Err(e) => { + self.revert_undo_actions(undo); + Err(e) + } + } + } + + pub fn begin_undo_actions(&self, anonymous_allowed: bool) -> BnString { + unsafe { BnString::from_raw(BNBeginUndoActions(self.handle, anonymous_allowed)) } + } + + pub fn commit_undo_actions(&self, id: S) { + let id = id.into_bytes_with_nul(); + unsafe { + BNCommitUndoActions(self.handle, id.as_ref().as_ptr() as *const _); + } + } + + pub fn revert_undo_actions(&self, id: S) { + let id = id.into_bytes_with_nul(); + unsafe { + BNRevertUndoActions(self.handle, id.as_ref().as_ptr() as *const _); + } + } + + pub fn undo(&self) { + unsafe { + BNUndo(self.handle); + } + } + + pub fn redo(&self) { + unsafe { + BNRedo(self.handle); + } + } + + pub fn current_view(&self) -> BnString { + unsafe { BnString::from_raw(BNGetCurrentView(self.handle)) } + } + + pub fn current_offset(&self) -> u64 { + unsafe { BNGetCurrentOffset(self.handle) } + } + + pub fn navigate_to(&self, view: S, offset: u64) -> Result<(), ()> { + let view = view.into_bytes_with_nul(); + + unsafe { + if BNNavigate(self.handle, view.as_ref().as_ptr() as *const _, offset) { + Ok(()) + } else { + Err(()) + } + } + } + + pub fn get_view_of_type(&self, view: S) -> Result, ()> { + let view = view.into_bytes_with_nul(); + + unsafe { + let res = BNGetFileViewOfType(self.handle, view.as_ref().as_ptr() as *const _); + + if res.is_null() { + Err(()) + } else { + Ok(BinaryView::from_raw(res)) + } + } + } + + pub fn create_database(&self, filename: S) -> bool { + let filename = filename.into_bytes_with_nul(); + let raw = "Raw".into_bytes_with_nul(); + + unsafe { + BNCreateDatabase( + BNGetFileViewOfType(self.handle, raw.as_ptr() as *mut _), + filename.as_ref().as_ptr() as *mut _, + ptr::null_mut() as *mut _, + ) + } + } + + pub fn save_auto_snapshot(&self) -> bool { + let raw = "Raw".into_bytes_with_nul(); + unsafe { + BNSaveAutoSnapshot( + BNGetFileViewOfType(self.handle, raw.as_ptr() as *mut _), + ptr::null_mut() as *mut _, + ) + } + } + + pub fn open_database_for_configuration( + &self, + filename: S, + ) -> Result, ()> { + let filename = filename.into_bytes_with_nul(); + unsafe { + let bv = + BNOpenDatabaseForConfiguration(self.handle, filename.as_ref().as_ptr() as *const _); + + if bv.is_null() { + Err(()) + } else { + Ok(BinaryView::from_raw(bv)) + } + } + } + + pub fn open_database(&self, filename: S) -> 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))) + // }; + + if view.is_null() { + Err(()) + } else { + Ok(unsafe { BinaryView::from_raw(view) }) + } + } +} + +impl ToOwned for FileMetadata { + type Owned = Ref; + + fn to_owned(&self) -> Self::Owned { + unsafe { RefCountable::inc_ref(self) } + } +} + +unsafe impl RefCountable for FileMetadata { + unsafe fn inc_ref(handle: &Self) -> Ref { + Ref::new(Self { + handle: BNNewFileReference(handle.handle), + }) + } + + unsafe fn dec_ref(handle: &Self) { + BNFreeFileMetadata(handle.handle); + } +} + +/* +BNCreateDatabase, +BNCreateDatabaseWithProgress, +*/ diff --git a/src/flowgraph.rs b/src/flowgraph.rs new file mode 100644 index 0000000..9d95cc4 --- /dev/null +++ b/src/flowgraph.rs @@ -0,0 +1,164 @@ +// Copyright 2021-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. + +//! Interfaces for creating and displaying pretty CFGs in Binary Ninja. + +use binaryninjacore_sys::*; + +use crate::disassembly::DisassemblyTextLine; + +use crate::rc::*; + +use std::marker::PhantomData; + +pub type BranchType = BNBranchType; +pub type EdgePenStyle = BNEdgePenStyle; +pub type ThemeColor = BNThemeColor; +pub type FlowGraphOption = BNFlowGraphOption; + +#[repr(transparent)] +pub struct EdgeStyle(pub(crate) BNEdgeStyle); + +impl EdgeStyle { + pub fn new(style: EdgePenStyle, width: usize, color: ThemeColor) -> Self { + EdgeStyle(BNEdgeStyle { + style, + width, + color, + }) + } +} + +impl Default for EdgeStyle { + fn default() -> Self { + EdgeStyle(BNEdgeStyle { + style: EdgePenStyle::SolidLine, + width: 0, + color: ThemeColor::AddressColor, + }) + } +} + +#[derive(PartialEq, Eq, Hash)] +pub struct FlowGraphNode<'a> { + pub(crate) handle: *mut BNFlowGraphNode, + _data: PhantomData<&'a ()>, +} + +impl<'a> FlowGraphNode<'a> { + pub(crate) unsafe fn from_raw(raw: *mut BNFlowGraphNode) -> Self { + Self { + handle: raw, + _data: PhantomData, + } + } + + pub fn new(graph: &FlowGraph) -> Self { + unsafe { FlowGraphNode::from_raw(BNCreateFlowGraphNode(graph.handle)) } + } + + pub fn set_disassembly_lines(&self, lines: &'a Vec) { + 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? + } + } + + pub fn set_lines(&self, lines: Vec<&str>) { + let lines = lines + .iter() + .map(|&line| DisassemblyTextLine::from(&vec![line])) + .collect(); + self.set_disassembly_lines(&lines); + } + + pub fn add_outgoing_edge( + &self, + type_: BranchType, + target: &'a FlowGraphNode, + edge_style: &'a EdgeStyle, + ) { + unsafe { BNAddFlowGraphNodeOutgoingEdge(self.handle, type_, target.handle, edge_style.0) } + } +} + +unsafe impl<'a> RefCountable for FlowGraphNode<'a> { + unsafe fn inc_ref(handle: &Self) -> Ref { + Ref::new(Self { + handle: BNNewFlowGraphNodeReference(handle.handle), + _data: PhantomData, + }) + } + + unsafe fn dec_ref(handle: &Self) { + BNFreeFlowGraphNode(handle.handle); + } +} + +impl<'a> ToOwned for FlowGraphNode<'a> { + type Owned = Ref; + + fn to_owned(&self) -> Self::Owned { + unsafe { RefCountable::inc_ref(self) } + } +} + +// 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, +} + +impl FlowGraph { + pub(crate) unsafe fn from_raw(raw: *mut BNFlowGraph) -> Self { + Self { handle: raw } + } + + pub fn new() -> Self { + unsafe { FlowGraph::from_raw(BNCreateFlowGraph()) } + } + + pub fn append(&self, node: &FlowGraphNode) -> usize { + unsafe { BNAddFlowGraphNode(self.handle, node.handle) } + } + + pub fn set_option(&self, option: FlowGraphOption, value: bool) { + unsafe { BNSetFlowGraphOption(self.handle, option, value) } + } + + pub fn is_option_set(&self, option: FlowGraphOption) -> bool { + unsafe { BNIsFlowGraphOptionSet(self.handle, option) } + } +} + +unsafe impl RefCountable for FlowGraph { + unsafe fn inc_ref(handle: &Self) -> Ref { + Ref::new(Self { + handle: BNNewFlowGraphReference(handle.handle), + }) + } + + unsafe fn dec_ref(handle: &Self) { + BNFreeFlowGraph(handle.handle); + } +} + +impl ToOwned for FlowGraph { + type Owned = Ref; + + fn to_owned(&self) -> Self::Owned { + unsafe { RefCountable::inc_ref(self) } + } +} diff --git a/src/function.rs b/src/function.rs new file mode 100644 index 0000000..93af0dd --- /dev/null +++ b/src/function.rs @@ -0,0 +1,389 @@ +// Copyright 2021-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 binaryninjacore_sys::*; + +use crate::rc::*; +use crate::string::*; +use crate::types::Variable; +use crate::{ + architecture::CoreArchitecture, + basicblock::{BasicBlock, BlockContext}, + binaryview::{BinaryView, BinaryViewExt}, + llil, mlil, + platform::Platform, + symbol::Symbol, + types::{Conf, NamedTypedVariable, Type}, +}; + +use std::{fmt, mem}; + +pub struct Location { + pub arch: Option, + pub addr: u64, +} + +impl From for Location { + fn from(addr: u64) -> Self { + Location { arch: None, addr } + } +} + +impl From<(CoreArchitecture, u64)> for Location { + fn from(loc: (CoreArchitecture, u64)) -> Self { + Location { + arch: Some(loc.0), + addr: loc.1, + } + } +} + +pub struct NativeBlockIter { + arch: CoreArchitecture, + bv: Ref, + cur: u64, + end: u64, +} + +impl Iterator for NativeBlockIter { + type Item = u64; + + fn next(&mut self) -> Option { + let res = self.cur; + + if res >= self.end { + None + } else { + self.bv + .instruction_len(&self.arch, res) + .map(|x| { + self.cur += x as u64; + res + }) + .or_else(|| { + self.cur = self.end; + None + }) + } + } +} + +#[derive(Clone)] +pub struct NativeBlock { + _priv: (), +} + +impl NativeBlock { + pub(crate) fn new() -> Self { + NativeBlock { _priv: () } + } +} + +impl BlockContext for NativeBlock { + type Iter = NativeBlockIter; + type Instruction = u64; + + fn start(&self, block: &BasicBlock) -> u64 { + block.raw_start() + } + + fn iter(&self, block: &BasicBlock) -> NativeBlockIter { + NativeBlockIter { + arch: block.arch(), + bv: block.function().view(), + cur: block.raw_start(), + end: block.raw_end(), + } + } +} + +#[derive(PartialEq, Eq, Hash)] +pub struct Function { + pub(crate) handle: *mut BNFunction, +} + +unsafe impl Send for Function {} +unsafe impl Sync for Function {} + +impl Function { + pub(crate) unsafe fn from_raw(handle: *mut BNFunction) -> Ref { + Ref::new(Self { handle }) + } + + pub fn arch(&self) -> CoreArchitecture { + unsafe { + let arch = BNGetFunctionArchitecture(self.handle); + CoreArchitecture::from_raw(arch) + } + } + + pub fn platform(&self) -> Ref { + unsafe { + let plat = BNGetFunctionPlatform(self.handle); + Platform::ref_from_raw(plat) + } + } + + pub fn view(&self) -> Ref { + unsafe { + let view = BNGetFunctionData(self.handle); + BinaryView::from_raw(view) + } + } + + pub fn symbol(&self) -> Ref { + unsafe { + let sym = BNGetFunctionSymbol(self.handle); + Symbol::ref_from_raw(sym) + } + } + + pub fn start(&self) -> u64 { + unsafe { BNGetFunctionStart(self.handle) } + } + + pub fn highest_address(&self) -> u64 { + unsafe { BNGetFunctionHighestAddress(self.handle) } + } + + pub fn address_ranges(&self) -> Array { + unsafe { + let mut count = 0; + let addresses = BNGetFunctionAddressRanges(self.handle, &mut count); + + Array::new(addresses, count, ()) + } + } + + pub fn comment(&self) -> BnString { + unsafe { BnString::from_raw(BNGetFunctionComment(self.handle)) } + } + + pub fn set_comment(&self, comment: S) { + let raw = comment.into_bytes_with_nul(); + + unsafe { + BNSetFunctionComment(self.handle, raw.as_ref().as_ptr() as *mut _); + } + } + + pub fn comment_at(&self, addr: u64) -> BnString { + unsafe { BnString::from_raw(BNGetCommentForAddress(self.handle, addr)) } + } + + pub fn set_comment_at(&self, addr: u64, comment: S) { + let raw = comment.into_bytes_with_nul(); + + unsafe { + BNSetCommentForAddress(self.handle, addr, raw.as_ref().as_ptr() as *mut _); + } + } + + pub fn basic_blocks(&self) -> Array> { + unsafe { + let mut count = 0; + let blocks = BNGetFunctionBasicBlockList(self.handle, &mut count); + let context = NativeBlock { _priv: () }; + + Array::new(blocks, count, context) + } + } + + pub fn basic_block_containing( + &self, + arch: &CoreArchitecture, + addr: u64, + ) -> Option>> { + unsafe { + let block = BNGetFunctionBasicBlockAtAddress(self.handle, arch.0, addr); + let context = NativeBlock { _priv: () }; + + if block.is_null() { + return None; + } + + Some(Ref::new(BasicBlock::from_raw(block, context))) + } + } + + pub fn get_variable_name(&self, var: &Variable) -> BnString { + unsafe { + let raw_var = var.raw(); + let raw_name = BNGetVariableName(self.handle, &raw_var); + BnString::from_raw(raw_name) + } + } + + pub fn medium_level_il(&self) -> Result, ()> { + unsafe { + let mlil = BNGetFunctionMediumLevelIL(self.handle); + + if mlil.is_null() { + return Err(()); + } + + Ok(Ref::new(mlil::MediumLevelILFunction::from_raw(mlil))) + } + } + + pub fn low_level_il(&self) -> Result>, ()> { + unsafe { + let llil = BNGetFunctionLowLevelIL(self.handle); + + if llil.is_null() { + return Err(()); + } + + Ok(Ref::new(llil::RegularFunction::from_raw(self.arch(), llil))) + } + } + + pub fn lifted_il(&self) -> Result>, ()> { + unsafe { + let llil = BNGetFunctionLiftedIL(self.handle); + + if llil.is_null() { + return Err(()); + } + + Ok(Ref::new(llil::LiftedFunction::from_raw(self.arch(), llil))) + } + } + + pub fn return_type(&self) -> Conf> { + let result = unsafe { BNGetFunctionReturnType(self.handle) }; + + Conf::new( + unsafe { Type::ref_from_raw(result.type_) }, + result.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 stack_layout(&self) -> Array { + let mut count = 0; + unsafe { + let variables = BNGetStackLayout(self.handle, &mut count); + Array::new(variables, count, ()) + } + } + + pub fn apply_imported_types(&self, sym: &Symbol, t: Option<&Type>) { + unsafe { + BNApplyImportedTypes( + self.handle, + sym.handle, + if let Some(t) = t { + t.handle + } else { + core::ptr::null_mut() + }, + ); + } + } +} + +impl fmt::Debug for Function { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!( + f, + "", + self.symbol().full_name(), + self.platform().name(), + self.start() + ) + } +} + +impl ToOwned for Function { + type Owned = Ref; + + fn to_owned(&self) -> Self::Owned { + unsafe { RefCountable::inc_ref(self) } + } +} + +unsafe impl RefCountable for Function { + unsafe fn inc_ref(handle: &Self) -> Ref { + Ref::new(Self { + handle: BNNewFunctionReference(handle.handle), + }) + } + + unsafe fn dec_ref(handle: &Self) { + BNFreeFunction(handle.handle); + } +} + +impl CoreArrayProvider for Function { + type Raw = *mut BNFunction; + type Context = (); +} + +unsafe impl CoreOwnedArrayProvider 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> { + Guard::new(Function { handle: *raw }, context) + } +} + +///////////////// +// AddressRange + +#[repr(transparent)] +pub struct AddressRange(pub(crate) BNAddressRange); + +impl AddressRange { + pub fn start(&self) -> u64 { + self.0.start + } + + pub fn end(&self) -> u64 { + self.0.end + } +} + +impl CoreArrayProvider for AddressRange { + type Raw = BNAddressRange; + type Context = (); +} +unsafe impl CoreOwnedArrayProvider 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 { + mem::transmute(raw) + } +} diff --git a/src/functionrecognizer.rs b/src/functionrecognizer.rs new file mode 100644 index 0000000..ec56730 --- /dev/null +++ b/src/functionrecognizer.rs @@ -0,0 +1,107 @@ +use crate::architecture::Architecture; +use crate::{ + architecture::CoreArchitecture, binaryview::BinaryView, function::Function, llil, mlil, +}; +use binaryninjacore_sys::*; +use std::os::raw::c_void; + +pub trait FunctionRecognizer { + fn recognize_low_level_il( + &self, + _bv: &BinaryView, + _func: &Function, + _llil: &llil::RegularFunction, + ) -> bool { + false + } + + fn recognize_medium_level_il( + &self, + _bv: &BinaryView, + _func: &Function, + _mlil: &mlil::MediumLevelILFunction, + ) -> bool { + false + } +} + +fn create_function_recognizer_registration(recognizer: R) -> BNFunctionRecognizer +where + R: 'static + FunctionRecognizer + Send + Sync + Sized, +{ + #[repr(C)] + struct FunctionRecognizerHandlerContext + where + R: 'static + FunctionRecognizer + Send + Sync, + { + recognizer: R, + } + + extern "C" fn cb_recognize_low_level_il( + ctxt: *mut c_void, + bv: *mut BNBinaryView, + func: *mut BNFunction, + llil: *mut BNLowLevelILFunction, + ) -> bool + where + R: 'static + FunctionRecognizer + Send + Sync, + { + let custom_handler = unsafe { &*(ctxt as *mut R) }; + let bv = unsafe { BinaryView::from_raw(BNNewViewReference(bv)) }; + let arch = unsafe { BNGetFunctionArchitecture(func) }; + let func = unsafe { Function::from_raw(BNNewFunctionReference(func)) }; + if arch.is_null() { + return false; + } + let arch = unsafe { CoreArchitecture::from_raw(arch) }; + let llil = unsafe { llil::RegularFunction::from_raw(arch, llil) }; + custom_handler.recognize_low_level_il(bv.as_ref(), func.as_ref(), &llil) + } + + extern "C" fn cb_recognize_medium_level_il( + ctxt: *mut c_void, + bv: *mut BNBinaryView, + func: *mut BNFunction, + mlil: *mut BNMediumLevelILFunction, + ) -> bool + where + R: 'static + FunctionRecognizer + Send + Sync, + { + let custom_handler = unsafe { &*(ctxt as *mut R) }; + let bv = unsafe { BinaryView::from_raw(BNNewViewReference(bv)) }; + let func = unsafe { Function::from_raw(BNNewFunctionReference(func)) }; + let mlil = unsafe { mlil::MediumLevelILFunction::from_raw(mlil) }; + custom_handler.recognize_medium_level_il(bv.as_ref(), func.as_ref(), &mlil) + } + + let recognizer = FunctionRecognizerHandlerContext { recognizer }; + let raw = Box::into_raw(Box::new(recognizer)); + BNFunctionRecognizer { + context: raw as *mut _, + recognizeLowLevelIL: Some(cb_recognize_low_level_il::), + recognizeMediumLevelIL: Some(cb_recognize_medium_level_il::), + } +} + +pub fn register_global_function_recognizer(recognizer: R) +where + R: 'static + FunctionRecognizer + Send + Sync + Sized, +{ + let mut recognizer = create_function_recognizer_registration::(recognizer); + unsafe { + BNRegisterGlobalFunctionRecognizer(&mut recognizer as *mut _); + } +} + +pub(crate) fn register_arch_function_recognizer(arch: &CoreArchitecture, recognizer: R) +where + R: 'static + FunctionRecognizer + Send + Sync + Sized, +{ + let mut recognizer = create_function_recognizer_registration::(recognizer); + unsafe { + BNRegisterArchitectureFunctionRecognizer( + arch.handle().as_ref().0, + &mut recognizer as *mut _, + ); + } +} diff --git a/src/headless.rs b/src/headless.rs new file mode 100644 index 0000000..1520fc5 --- /dev/null +++ b/src/headless.rs @@ -0,0 +1,158 @@ +// Copyright 2021-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 crate::{ + binaryview, + metadata::Metadata, + rc::{self, Ref}, + string::BnStrCompatible, +}; + +use std::env; +use std::path::PathBuf; + +#[cfg(not(target_os = "windows"))] +fn binja_path() -> PathBuf { + use std::ffi::{CStr, OsStr}; + use std::mem; + use std::os::raw; + use std::os::unix::ffi::OsStrExt; + + #[repr(C)] + struct DlInfo { + dli_fname: *const raw::c_char, + dli_fbase: *mut raw::c_void, + dli_sname: *const raw::c_char, + dli_saddr: *mut raw::c_void, + } + + if let Ok(p) = env::var("BINJA_DIR") { + return PathBuf::from(p); + } + + extern "C" { + fn dladdr(addr: *mut raw::c_void, info: *mut DlInfo) -> raw::c_int; + } + + unsafe { + let mut info: DlInfo = mem::zeroed(); + + if dladdr(BNSetBundledPluginDirectory as *mut _, &mut info) == 0 { + panic!("Failed to find libbinaryninjacore path!"); + } + + if info.dli_fname.is_null() { + panic!("Failed to find libbinaryninjacore path!"); + } + + let path = CStr::from_ptr(info.dli_fname); + let path = OsStr::from_bytes(path.to_bytes()); + let mut path = PathBuf::from(path); + + path.pop(); + path + } +} + +#[cfg(target_os = "windows")] +fn binja_path() -> PathBuf { + PathBuf::from(env::var("PROGRAMFILES").unwrap()).join("Vector35\\BinaryNinja\\") +} + +use binaryninjacore_sys::{BNInitPlugins, BNInitRepoPlugins, BNSetBundledPluginDirectory}; + +/// Loads plugins, core architecture, platform, etc. +/// +/// ⚠️ Important! Must be called at the beginning of scripts. Plugins do not need to call this. ⚠️ +/// +/// You can instead call this through [`Session`] or [`script_helper`] +pub fn init() { + unsafe { + let path = binja_path().join("plugins").into_os_string(); + let path = path.into_string().unwrap(); + + BNSetBundledPluginDirectory(path.as_str().into_bytes_with_nul().as_ptr() as *mut _); + BNInitPlugins(true); + BNInitRepoPlugins(); + } +} + +/// Unloads plugins, stops all worker threads, and closes open logs +/// +/// ⚠️ Important! Must be called at the end of scripts. ⚠️ +pub fn shutdown() { + unsafe { binaryninjacore_sys::BNShutdown() }; +} + +/// Prelued-postlued helper function (calls [`init`] and [`shutdown`] for you) +/// ```rust +/// binaryninja::headless::script_helper(|| { +/// binaryninja::load("/bin/cat") +/// .expect("Couldn't open `/bin/cat`") +/// .iter() +/// .for_each(|func| println!(" `{}`", func.symbol().full_name())); +/// }); +/// ``` +pub fn script_helper(func: fn()) { + init(); + func(); + shutdown(); +} + +/// Wrapper for [`init`] and [`shutdown`]. Instantiating this at the top of your script will initialize everything correctly and then clean itself up at exit as well. +pub struct Session {} + +impl Session { + pub fn new() -> Self { + init(); + Self {} + } + + /// ```rust + /// let headless_session = binaryninja::headless::Session::new(); + /// + /// let bv = headless_session.load("/bin/cat").expect("Couldn't open `/bin/cat`"); + /// ``` + pub fn load(&self, filename: &str) -> Option> { + crate::load(filename) + } + + /// ```rust + /// let settings = [("analysis.linearSweep.autorun", false)].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( + &self, + filename: &str, + update_analysis_and_wait: bool, + options: Option>, + ) -> Option> { + crate::load_with_options(filename, update_analysis_and_wait, options) + } +} + +impl Default for Session { + fn default() -> Self { + Self::new() + } +} + +impl Drop for Session { + fn drop(&mut self) { + shutdown() + } +} diff --git a/src/interaction.rs b/src/interaction.rs new file mode 100644 index 0000000..7e54925 --- /dev/null +++ b/src/interaction.rs @@ -0,0 +1,575 @@ +// 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. + +//! Interfaces for asking the user for information: forms, opening files, etc. + +use binaryninjacore_sys::*; + +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}; + +pub fn get_text_line_input(prompt: &str, title: &str) -> Option { + let mut value: *mut libc::c_char = std::ptr::null_mut(); + + let result = unsafe { + BNGetTextLineInput( + &mut value, + prompt.into_bytes_with_nul().as_ptr() as *mut _, + title.into_bytes_with_nul().as_ptr() as *mut _, + ) + }; + if !result { + return None; + } + + Some(unsafe { BnString::from_raw(value).to_string() }) +} + +pub fn get_integer_input(prompt: &str, title: &str) -> Option { + let mut value: i64 = 0; + + let result = unsafe { + BNGetIntegerInput( + &mut value, + prompt.into_bytes_with_nul().as_ptr() as *mut _, + title.into_bytes_with_nul().as_ptr() as *mut _, + ) + }; + + if !result { + return None; + } + + Some(value) +} + +pub fn get_address_input(prompt: &str, title: &str) -> Option { + let mut value: u64 = 0; + + let result = unsafe { + BNGetAddressInput( + &mut value, + prompt.into_bytes_with_nul().as_ptr() as *mut _, + title.into_bytes_with_nul().as_ptr() as *mut _, + std::ptr::null_mut(), + 0, + ) + }; + + if !result { + return None; + } + + Some(value) +} + +pub fn get_open_filename_input(prompt: &str, extension: &str) -> Option { + let mut value: *mut libc::c_char = std::ptr::null_mut(); + + let result = unsafe { + BNGetOpenFileNameInput( + &mut value, + prompt.into_bytes_with_nul().as_ptr() as *mut _, + extension.into_bytes_with_nul().as_ptr() as *mut _, + ) + }; + if !result { + return None; + } + + let string = unsafe { BnString::from_raw(value) }; + Some(PathBuf::from(string.as_str())) +} + +pub fn get_save_filename_input(prompt: &str, title: &str, default_name: &str) -> Option { + let mut value: *mut libc::c_char = std::ptr::null_mut(); + + let result = unsafe { + BNGetSaveFileNameInput( + &mut value, + prompt.into_bytes_with_nul().as_ptr() as *mut _, + title.into_bytes_with_nul().as_ptr() as *mut _, + default_name.into_bytes_with_nul().as_ptr() as *mut _, + ) + }; + if !result { + return None; + } + + let string = unsafe { BnString::from_raw(value) }; + Some(PathBuf::from(string.as_str())) +} + +pub fn get_directory_name_input(prompt: &str, default_name: &str) -> Option { + let mut value: *mut libc::c_char = std::ptr::null_mut(); + + let result = unsafe { + BNGetDirectoryNameInput( + &mut value, + prompt.into_bytes_with_nul().as_ptr() as *mut _, + default_name.into_bytes_with_nul().as_ptr() as *mut _, + ) + }; + if !result { + return None; + } + + let string = unsafe { BnString::from_raw(value) }; + Some(PathBuf::from(string.as_str())) +} + +pub type MessageBoxButtonSet = BNMessageBoxButtonSet; +pub type MessageBoxIcon = BNMessageBoxIcon; +pub type MessageBoxButtonResult = BNMessageBoxButtonResult; +pub fn show_message_box( + title: &str, + text: &str, + buttons: MessageBoxButtonSet, + icon: MessageBoxIcon, +) -> MessageBoxButtonResult { + unsafe { + BNShowMessageBox( + title.into_bytes_with_nul().as_ptr() as *mut _, + text.into_bytes_with_nul().as_ptr() as *mut _, + buttons, + icon, + ) + } +} + +pub enum FormResponses { + None, + String(String), + Integer(i64), + Address(u64), + Index(usize), +} + +enum FormData { + Label { + _text: BnString, + }, + Text { + _prompt: BnString, + _default: Option, + }, + Choice { + _prompt: BnString, + _choices: Vec, + _raw: Vec<*const c_char>, + }, + File { + _prompt: BnString, + _ext: BnString, + _default: Option, + }, + FileSave { + _prompt: BnString, + _ext: BnString, + _default_name: BnString, + _default: Option, + }, +} + +pub struct FormInputBuilder { + fields: Vec, + data: Vec, +} + +impl FormInputBuilder { + pub fn new() -> Self { + Self { + fields: vec![], + data: vec![], + } + } + + /// Form Field: Text output + pub fn label_field(mut self, text: &str) -> Self { + let text = BnString::new(text); + + let mut result = unsafe { std::mem::zeroed::() }; + result.type_ = BNFormInputFieldType::LabelFormField; + result.hasDefault = false; + result.prompt = text.as_ref().as_ptr() as *const c_char; + self.fields.push(result); + + self.data.push(FormData::Label { _text: text }); + self + } + + /// Form Field: Vertical spacing + pub fn seperator_field(mut self) -> Self { + let mut result = unsafe { std::mem::zeroed::() }; + result.type_ = BNFormInputFieldType::SeparatorFormField; + result.hasDefault = false; + self.fields.push(result); + self + } + + /// Form Field: Prompt for a string value + pub fn text_field(mut self, prompt: &str, default: Option<&str>) -> Self { + let prompt = BnString::new(prompt); + let default = default.map(BnString::new); + + let mut result = unsafe { std::mem::zeroed::() }; + result.type_ = BNFormInputFieldType::TextLineFormField; + result.prompt = prompt.as_ref().as_ptr() as *const c_char; + result.hasDefault = default.is_some(); + if let Some(ref default) = default { + result.stringDefault = default.as_ref().as_ptr() as *const c_char; + } + self.fields.push(result); + + self.data.push(FormData::Text { + _prompt: prompt, + _default: default, + }); + self + } + + /// Form Field: Prompt for multi-line string value + pub fn multiline_field(mut self, prompt: &str, default: Option<&str>) -> Self { + let prompt = BnString::new(prompt); + let default = default.map(BnString::new); + + let mut result = unsafe { std::mem::zeroed::() }; + result.type_ = BNFormInputFieldType::MultilineTextFormField; + result.prompt = prompt.as_ref().as_ptr() as *const c_char; + result.hasDefault = default.is_some(); + if let Some(ref default) = default { + result.stringDefault = default.as_ref().as_ptr() as *const c_char; + } + self.fields.push(result); + + self.data.push(FormData::Text { + _prompt: prompt, + _default: default, + }); + self + } + + /// Form Field: Prompt for an integer + pub fn integer_field(mut self, prompt: &str, default: Option) -> Self { + let prompt = BnString::new(prompt); + + let mut result = unsafe { std::mem::zeroed::() }; + result.type_ = BNFormInputFieldType::IntegerFormField; + result.prompt = prompt.as_ref().as_ptr() as *const c_char; + result.hasDefault = default.is_some(); + if let Some(default) = default { + result.intDefault = default; + } + self.fields.push(result); + + self.data.push(FormData::Label { _text: prompt }); + self + } + + /// Form Field: Prompt for an address + pub fn address_field( + mut self, + prompt: &str, + view: Option>, + current_address: Option, + default: Option, + ) -> Self { + let prompt = BnString::new(prompt); + + let mut result = unsafe { std::mem::zeroed::() }; + result.type_ = BNFormInputFieldType::AddressFormField; + result.prompt = prompt.as_ref().as_ptr() as *const c_char; + if let Some(view) = view { + result.view = view.handle; + } + result.currentAddress = current_address.unwrap_or(0); + result.hasDefault = default.is_some(); + if let Some(default) = default { + result.addressDefault = default; + } + self.fields.push(result); + + self.data.push(FormData::Label { _text: prompt }); + self + } + + /// Form Field: Prompt for a choice from provided options + pub fn choice_field(mut self, prompt: &str, choices: &[&str], default: Option) -> Self { + let prompt = BnString::new(prompt); + let choices: Vec = choices.iter().map(|&s| BnString::new(s)).collect(); + + let mut result = unsafe { std::mem::zeroed::() }; + result.type_ = BNFormInputFieldType::ChoiceFormField; + result.prompt = prompt.as_ref().as_ptr() as *const c_char; + let mut raw_choices: Vec<*const c_char> = choices + .iter() + .map(|c| c.as_ref().as_ptr() as *const c_char) + .collect(); + result.choices = raw_choices.as_mut_ptr(); + result.count = choices.len(); + result.hasDefault = default.is_some(); + if let Some(default) = default { + result.indexDefault = default; + } + self.fields.push(result); + + self.data.push(FormData::Choice { + _prompt: prompt, + _choices: choices, + _raw: raw_choices, + }); + self + } + + /// Form Field: Prompt for file to open + pub fn open_file_field( + mut self, + prompt: &str, + ext: Option<&str>, + default: Option<&str>, + ) -> Self { + let prompt = BnString::new(prompt); + let ext = if let Some(ext) = ext { + BnString::new(ext) + } else { + BnString::new("") + }; + let default = default.map(BnString::new); + + let mut result = unsafe { std::mem::zeroed::() }; + result.type_ = BNFormInputFieldType::OpenFileNameFormField; + result.prompt = prompt.as_ref().as_ptr() as *const c_char; + result.ext = ext.as_ref().as_ptr() as *const c_char; + result.hasDefault = default.is_some(); + if let Some(ref default) = default { + result.stringDefault = default.as_ref().as_ptr() as *const c_char; + } + self.fields.push(result); + + self.data.push(FormData::File { + _prompt: prompt, + _ext: ext, + _default: default, + }); + self + } + + /// Form Field: Prompt for file to save to + pub fn save_file_field( + mut self, + prompt: &str, + ext: Option<&str>, + default_name: Option<&str>, + default: Option<&str>, + ) -> Self { + let prompt = BnString::new(prompt); + let ext = if let Some(ext) = ext { + BnString::new(ext) + } else { + BnString::new("") + }; + let default_name = if let Some(default_name) = default_name { + BnString::new(default_name) + } else { + BnString::new("") + }; + let default = default.map(BnString::new); + + let mut result = unsafe { std::mem::zeroed::() }; + result.type_ = BNFormInputFieldType::SaveFileNameFormField; + result.prompt = prompt.as_ref().as_ptr() as *const c_char; + result.ext = ext.as_ref().as_ptr() as *const c_char; + result.defaultName = default_name.as_ref().as_ptr() as *const c_char; + result.hasDefault = default.is_some(); + if let Some(ref default) = default { + result.stringDefault = default.as_ref().as_ptr() as *const c_char; + } + self.fields.push(result); + + self.data.push(FormData::FileSave { + _prompt: prompt, + _ext: ext, + _default_name: default_name, + _default: default, + }); + self + } + + /// Form Field: Prompt for directory name + pub fn directory_name_field( + mut self, + prompt: &str, + default_name: Option<&str>, + default: Option<&str>, + ) -> Self { + let prompt = BnString::new(prompt); + let default_name = if let Some(default_name) = default_name { + BnString::new(default_name) + } else { + BnString::new("") + }; + let default = default.map(BnString::new); + + let mut result = unsafe { std::mem::zeroed::() }; + result.type_ = BNFormInputFieldType::DirectoryNameFormField; + result.prompt = prompt.as_ref().as_ptr() as *const c_char; + result.defaultName = default_name.as_ref().as_ptr() as *const c_char; + result.hasDefault = default.is_some(); + if let Some(ref default) = default { + result.stringDefault = default.as_ref().as_ptr() as *const c_char; + } + self.fields.push(result); + + self.data.push(FormData::File { + _prompt: prompt, + _ext: default_name, + _default: default, + }); + self + } + + /// Prompts the user for a set of inputs specified in `fields` with given title. + /// The fields parameter is a list which can contain the following types: + /// + /// 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() + /// .text_field("First Name", None) + /// .text_field("Last Name", None) + /// .choice_field( + /// "Favorite Food", + /// &vec![ + /// "Pizza", + /// "Also Pizza", + /// "Also Pizza", + /// "Yummy Pizza", + /// "Wrong Answer", + /// ], + /// Some(0), + /// ) + /// .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", + /// _ => panic!("This person doesn't like pizza?!?"), + /// }; + /// + /// let interaction::FormResponses::String(last_name) = responses[0]; + /// let interaction::FormResponses::String(first_name) = responses[1]; + /// + /// println!("{} {} likes {}", &first_name, &last_name, food); + /// ``` + pub fn get_form_input(&mut self, title: &str) -> Vec { + if unsafe { + BNGetFormInput( + self.fields.as_mut_ptr(), + self.fields.len(), + title.into_bytes_with_nul().as_ptr() as *const _, + ) + } { + let result = self + .fields + .iter() + .map(|form_field| match form_field.type_ { + BNFormInputFieldType::LabelFormField + | BNFormInputFieldType::SeparatorFormField => FormResponses::None, + + BNFormInputFieldType::TextLineFormField + | BNFormInputFieldType::MultilineTextFormField + | BNFormInputFieldType::OpenFileNameFormField + | BNFormInputFieldType::SaveFileNameFormField + | BNFormInputFieldType::DirectoryNameFormField => { + FormResponses::String(unsafe { + BnStr::from_raw(form_field.stringResult).to_string() + }) + } + + BNFormInputFieldType::IntegerFormField => { + FormResponses::Integer(form_field.intResult) + } + BNFormInputFieldType::AddressFormField => { + FormResponses::Address(form_field.addressResult) + } + BNFormInputFieldType::ChoiceFormField => { + FormResponses::Index(form_field.indexResult) + } + }) + .collect(); + unsafe { BNFreeFormInputResults(self.fields.as_mut_ptr(), self.fields.len()) }; + result + } else { + vec![] + } + } +} + +impl Default for FormInputBuilder { + fn default() -> Self { + Self::new() + } +} + +struct TaskContext Result<(), ()>>)>(F); + +pub fn run_progress_dialog Result<(), ()>>)>( + title: &str, + can_cancel: bool, + task: F, +) -> Result<(), ()> { + let mut ctxt = TaskContext::(task); + + unsafe extern "C" fn cb_task Result<(), ()>>)>( + ctxt: *mut c_void, + progress: Option bool>, + progress_ctxt: *mut c_void, + ) { + ffi_wrap!("run_progress_dialog", { + let context = ctxt as *mut TaskContext; + let progress_fn = Box::new(move |cur: usize, max: usize| -> Result<(), ()> { + match progress { + Some(func) => { + if (func)(progress_ctxt, cur, max) { + Ok(()) + } else { + Err(()) + } + } + None => Ok(()), + } + }); + ((*context).0)(progress_fn); + }) + } + + if unsafe { + BNRunProgressDialog( + title.into_bytes_with_nul().as_ptr() as *mut _, + can_cancel, + Some(cb_task::), + &mut ctxt as *mut _ as *mut c_void, + ) + } { + Ok(()) + } else { + Err(()) + } +} diff --git a/src/lib.rs b/src/lib.rs new file mode 100644 index 0000000..fa33439 --- /dev/null +++ b/src/lib.rs @@ -0,0 +1,428 @@ +// Copyright 2021-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. + +// TODO : These clippy-allow are bad and needs to be removed +#![allow(clippy::missing_safety_doc)] +#![allow(clippy::result_unit_err)] +#![allow(clippy::type_complexity)] +#![doc(html_root_url = "https://dev-rust.binary.ninja/binaryninja/")] +#![doc(html_favicon_url = "/favicon.ico")] +#![doc(html_logo_url = "/logo.png")] +#![doc(issue_tracker_base_url = "https://github.com/Vector35/binaryninja-api/issues/")] + +//! This crate is the official [Binary Ninja] API wrapper for Rust. +//! +//! [Binary Ninja] is an interactive disassembler, decompiler, and binary analysis platform for reverse engineers, malware analysts, vulnerability researchers, and software developers that runs on Windows, macOS, and Linux. Our extensive API can be used to create and customize loaders, add or augment architectures, customize the UI, or automate any workflow (types, patches, decompilation...anything!). +//! +//! If you're just getting started with [Binary Ninja], you may wish to check out the [Getting Started Guide] +//! +//! If you have questions, we'd love to answer them in [our public Slack], and if you find any issues, please [file an issue] or [submit a PR]. +//! +//! --- +//! # Warning +//! +//! +//! > ⚠️ **These bindings are in a very early beta, only have partial support for the core APIs and are still actively under development. Compatibility _will_ break and conventions _will_ change! They are being used for core Binary Ninja features however, so we expect much of what is already there to be reliable enough to build on, just don't be surprised if your plugins/scripts need to hit a moving target.** +//! +//! > ⚠️ This project runs on Rust version `stable-2022-12-15` +//! +//! --- +//! +//! # Examples +//! +//! There are two distinct ways to use this crate: +//! 1. [Writing a Plugin](#writing-a-plugin) +//! 2. [Writing a Script](#writing-a-script) +//! +//! ## Writing a Plugin +//! +//! Create a new library (`cargo new --lib `) and include the following in your `Cargo.toml`: +//! +//! ``` +//! [lib] +//! crate-type = ["cdylib"] +//! +//! [dependencies] +//! binaryninja = {git = "https://github.com/Vector35/binaryninja-api.git", branch = "dev"} +//! ``` +//! +//! In `lib.rs` you'll need to provide a `CorePluginInit` or `UIPluginInit` function for Binary Ninja to call. +//! +//! See [`command`] for the different actions you can provide and how to register your plugin with [Binary Ninja]. +//! +//! ## Writing a Script: +//! +//! "Scripts" are binaries (`cargo new --bin `), and have some specific requirements: +//! +//! ### build.rs +//! +//! Because [the only official method of providing linker arguments to a crate is through that crate's `build.rs`], all scripts need to provide their own `build.rs` so they can probably link with Binary Ninja. +//! +//! The most up-to-date version of the suggested [`build.rs` is here]. +//! +//! ### `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(); +//! +//! println!("Loading binary..."); +//! let bv = headless_session.load("/bin/cat").expect("Couldn't open `/bin/cat`"); +//! +//! // Your code here... +//! } +//! ``` +//! +//! ### `Cargo.toml` +//! ``` +//! [dependencies] +//! binaryninja = { git = "https://github.com/Vector35/binaryninja-api.git", branch = "dev"} +//! ``` +//! +//! See the [examples] on GitHub for more comprehensive examples. +//! +//! [Binary Ninja]: https://binary.ninja/ +//! [Getting Started Guide]: https://docs.binary.ninja/ +//! [our public Slack]: https://join.slack.com/t/binaryninja/shared_invite/zt-3u4vu3ja-IGUF4ZWNlD7ER2ulvICvuQ +//! [file an issue]: https://github.com/Vector35/binaryninja-api/issues +//! [submit a PR]: https://github.com/Vector35/binaryninja-api/pulls +//! [the only official method of providing linker arguments to a crate is through that crate's `build.rs`]: https://github.com/rust-lang/cargo/issues/9554 +//! [`build.rs` is here]: https://github.com/Vector35/binaryninja-api/blob/dev/rust/examples/template/build.rs +//! [examples]: https://github.com/Vector35/binaryninja-api/tree/dev/rust/examples +//! + +#[macro_use] +extern crate log; +#[doc(hidden)] +pub extern crate binaryninjacore_sys; +extern crate libc; +#[cfg(feature = "rayon")] +extern crate rayon; + +// TODO +// move some options to results +// replace `fn handle` with `AsRef` bounds +// possible values +// arch rework +// cc possible values +// bv reorg +// core fileaccessor (for bv saving) +// platform cc + +#[macro_use] +mod ffi; + +pub mod architecture; +pub mod backgroundtask; +pub mod basicblock; +pub mod binaryreader; +pub mod binaryview; +pub mod binarywriter; +pub mod callingconvention; +pub mod command; +pub mod custombinaryview; +pub mod databuffer; +pub mod debuginfo; +pub mod demangle; +pub mod disassembly; +pub mod downloadprovider; +pub mod fileaccessor; +pub mod filemetadata; +pub mod flowgraph; +pub mod function; +pub mod functionrecognizer; +pub mod headless; +pub mod interaction; +pub mod linearview; +pub mod llil; +pub mod logger; +pub mod metadata; +pub mod mlil; +pub mod platform; +pub mod rc; +pub mod references; +pub mod relocation; +pub mod section; +pub mod segment; +pub mod settings; +pub mod string; +pub mod symbol; +pub mod tags; +pub mod templatesimplifier; +pub mod types; + +use std::path::PathBuf; + +pub use binaryninjacore_sys::BNBranchType as BranchType; +pub use binaryninjacore_sys::BNEndianness as Endianness; +use binaryview::BinaryView; +use metadata::Metadata; +use metadata::MetadataType; +use rc::Ref; +use string::BnStrCompatible; + +// Commented out to suppress unused warnings +// const BN_MAX_INSTRUCTION_LENGTH: u64 = 256; +// const BN_DEFAULT_INSTRUCTION_LENGTH: u64 = 16; +// const BN_DEFAULT_OPCODE_DISPLAY: u64 = 8; +// const BN_MAX_INSTRUCTION_BRANCHES: u64 = 3; +// const BN_MAX_STORED_DATA_LENGTH: u64 = 0x3fffffff; +// const BN_NULL_ID: i64 = -1; +// const BN_INVALID_REGISTER: usize = 0xffffffff; +// const BN_AUTOCOERCE_EXTERN_PTR: u64 = 0xfffffffd; +// const BN_NOCOERCE_EXTERN_PTR: u64 = 0xfffffffe; +// const BN_INVALID_OPERAND: u64 = 0xffffffff; +// const BN_MAX_STRING_LENGTH: u64 = 128; +// const BN_MAX_VARIABLE_OFFSET: u64 = 0x7fffffffff; +// const BN_MAX_VARIABLE_INDEX: u64 = 0xfffff; +// const BN_MINIMUM_CONFIDENCE: u8 = 1; +// const BN_HEURISTIC_CONFIDENCE: u8 = 192; + +const BN_FULL_CONFIDENCE: u8 = 255; +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 handle = unsafe { + binaryninjacore_sys::BNLoadFilename( + filename.as_ref().as_ptr() as *mut _, + true, + None, + metadata.handle, + ) + }; + + if handle.is_null() { + None + } else { + Some(unsafe { BinaryView::from_raw(handle) }) + } +} + +/// The main way to open and load files (with options) into Binary Ninja. Make sure you've properly initialized the core before calling this function. See [`crate::headless::init()`] +/// +/// ```rust +/// let settings = [("analysis.linearSweep.autorun", false)].into(); +/// +/// let bv = binaryninja::load_with_options("/bin/cat", true, Some(settings)) +/// .expect("Couldn't open `/bin/cat`"); +/// ``` +pub fn load_with_options( + filename: S, + update_analysis_and_wait: bool, + options: Option>, +) -> Option> { + let filename = filename.into_bytes_with_nul(); + + let handle = unsafe { + binaryninjacore_sys::BNLoadFilename( + filename.as_ref().as_ptr() as *mut _, + update_analysis_and_wait, + None, + if let Some(options) = options { + options.as_ref().handle + } else { + Metadata::new_of_type(MetadataType::KeyValueDataType).handle + }, + ) + }; + + if handle.is_null() { + None + } else { + Some(unsafe { BinaryView::from_raw(handle) }) + } +} + +pub fn install_directory() -> Result { + let s: *mut std::os::raw::c_char = unsafe { binaryninjacore_sys::BNGetInstallDirectory() }; + if s.is_null() { + return Err(()); + } + Ok(PathBuf::from( + unsafe { string::BnString::from_raw(s) }.to_string(), + )) +} + +pub fn bundled_plugin_directory() -> Result { + let s: *mut std::os::raw::c_char = + unsafe { binaryninjacore_sys::BNGetBundledPluginDirectory() }; + if s.is_null() { + return Err(()); + } + Ok(PathBuf::from( + unsafe { string::BnString::from_raw(s) }.to_string(), + )) +} + +pub fn set_bundled_plugin_directory(new_dir: S) { + unsafe { + binaryninjacore_sys::BNSetBundledPluginDirectory( + new_dir.into_bytes_with_nul().as_ref().as_ptr() as *const std::os::raw::c_char, + ) + }; +} + +pub fn user_directory() -> Result { + let s: *mut std::os::raw::c_char = unsafe { binaryninjacore_sys::BNGetUserDirectory() }; + if s.is_null() { + return Err(()); + } + Ok(PathBuf::from( + unsafe { string::BnString::from_raw(s) }.to_string(), + )) +} + +pub fn user_plugin_directory() -> Result { + let s: *mut std::os::raw::c_char = unsafe { binaryninjacore_sys::BNGetUserPluginDirectory() }; + if s.is_null() { + return Err(()); + } + Ok(PathBuf::from( + unsafe { string::BnString::from_raw(s) }.to_string(), + )) +} + +pub fn repositories_directory() -> Result { + let s: *mut std::os::raw::c_char = unsafe { binaryninjacore_sys::BNGetRepositoriesDirectory() }; + if s.is_null() { + return Err(()); + } + Ok(PathBuf::from( + unsafe { string::BnString::from_raw(s) }.to_string(), + )) +} + +pub fn settings_file_name() -> Result { + let s: *mut std::os::raw::c_char = unsafe { binaryninjacore_sys::BNGetSettingsFileName() }; + if s.is_null() { + return Err(()); + } + Ok(PathBuf::from( + unsafe { string::BnString::from_raw(s) }.to_string(), + )) +} + +pub fn save_last_run() { + unsafe { binaryninjacore_sys::BNSaveLastRun() }; +} + +pub fn path_relative_to_bundled_plugin_directory( + path: S, +) -> Result { + let s: *mut std::os::raw::c_char = unsafe { + binaryninjacore_sys::BNGetPathRelativeToBundledPluginDirectory( + path.into_bytes_with_nul().as_ref().as_ptr() as *const std::os::raw::c_char, + ) + }; + if s.is_null() { + return Err(()); + } + Ok(PathBuf::from( + unsafe { string::BnString::from_raw(s) }.to_string(), + )) +} + +pub fn path_relative_to_user_plugin_directory( + path: S, +) -> Result { + let s: *mut std::os::raw::c_char = unsafe { + binaryninjacore_sys::BNGetPathRelativeToUserPluginDirectory( + path.into_bytes_with_nul().as_ref().as_ptr() as *const std::os::raw::c_char, + ) + }; + if s.is_null() { + return Err(()); + } + Ok(PathBuf::from( + unsafe { string::BnString::from_raw(s) }.to_string(), + )) +} + +pub fn path_relative_to_user_directory(path: S) -> Result { + let s: *mut std::os::raw::c_char = unsafe { + binaryninjacore_sys::BNGetPathRelativeToUserDirectory( + path.into_bytes_with_nul().as_ref().as_ptr() as *const std::os::raw::c_char, + ) + }; + if s.is_null() { + return Err(()); + } + Ok(PathBuf::from( + unsafe { string::BnString::from_raw(s) }.to_string(), + )) +} + +pub fn version() -> string::BnString { + unsafe { string::BnString::from_raw(binaryninjacore_sys::BNGetVersionString()) } +} + +pub fn plugin_abi_version() -> u32 { + binaryninjacore_sys::BN_CURRENT_CORE_ABI_VERSION +} + +pub fn plugin_abi_minimum_version() -> u32 { + binaryninjacore_sys::BN_MINIMUM_CORE_ABI_VERSION +} + +pub fn core_abi_version() -> u32 { + unsafe { binaryninjacore_sys::BNGetCurrentCoreABIVersion() } +} + +pub fn core_abi_minimum_version() -> u32 { + unsafe { binaryninjacore_sys::BNGetMinimumCoreABIVersion() } +} + +pub fn plugin_ui_abi_version() -> u32 { + binaryninjacore_sys::BN_CURRENT_UI_ABI_VERSION +} + +pub fn plugin_ui_abi_minimum_version() -> u32 { + binaryninjacore_sys::BN_MINIMUM_UI_ABI_VERSION +} + +pub fn add_required_plugin_dependency(name: S) { + unsafe { + binaryninjacore_sys::BNAddRequiredPluginDependency( + name.into_bytes_with_nul().as_ref().as_ptr() as *const std::os::raw::c_char, + ) + }; +} + +pub fn add_optional_plugin_dependency(name: S) { + unsafe { + binaryninjacore_sys::BNAddOptionalPluginDependency( + name.into_bytes_with_nul().as_ref().as_ptr() as *const std::os::raw::c_char, + ) + }; +} + +// Provide ABI version automatically so that the core can verify binary compatibility +#[cfg(not(feature = "noexports"))] +#[no_mangle] +#[allow(non_snake_case)] +pub extern "C" fn CorePluginABIVersion() -> u32 { + plugin_abi_version() +} + +#[cfg(not(feature = "noexports"))] +#[no_mangle] +pub extern "C" fn UIPluginABIVersion() -> u32 { + plugin_ui_abi_version() +} diff --git a/src/linearview.rs b/src/linearview.rs new file mode 100644 index 0000000..82094f6 --- /dev/null +++ b/src/linearview.rs @@ -0,0 +1,436 @@ +// 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. + +//! APIs for accessing Binary Ninja's linear view + +use binaryninjacore_sys::*; + +use crate::binaryview::BinaryView; +use crate::disassembly::{DisassemblySettings, DisassemblyTextLine}; +use crate::function::Function; + +use crate::rc::*; +use std::ops::Deref; + +use std::mem; + +pub struct LinearViewObject { + pub(crate) handle: *mut BNLinearViewObject, +} + +impl LinearViewObject { + pub(crate) unsafe fn from_raw(handle: *mut BNLinearViewObject) -> Ref { + debug_assert!(!handle.is_null()); + + Ref::new(Self { handle }) + } + + pub fn data_only(view: &BinaryView, settings: &DisassemblySettings) -> Ref { + unsafe { + let handle = + binaryninjacore_sys::BNCreateLinearViewDataOnly(view.handle, settings.handle); + + Self::from_raw(handle) + } + } + + pub fn disassembly(view: &BinaryView, settings: &DisassemblySettings) -> Ref { + unsafe { + let handle = + binaryninjacore_sys::BNCreateLinearViewDisassembly(view.handle, settings.handle); + + Self::from_raw(handle) + } + } + + pub fn lifted_il(view: &BinaryView, settings: &DisassemblySettings) -> Ref { + unsafe { + let handle = + binaryninjacore_sys::BNCreateLinearViewLiftedIL(view.handle, settings.handle); + + Self::from_raw(handle) + } + } + + pub fn mlil(view: &BinaryView, settings: &DisassemblySettings) -> Ref { + unsafe { + let handle = + binaryninjacore_sys::BNCreateLinearViewMediumLevelIL(view.handle, settings.handle); + + Self::from_raw(handle) + } + } + + pub fn mlil_ssa(view: &BinaryView, settings: &DisassemblySettings) -> Ref { + unsafe { + let handle = binaryninjacore_sys::BNCreateLinearViewMediumLevelILSSAForm( + view.handle, + settings.handle, + ); + + Self::from_raw(handle) + } + } + + pub fn hlil(view: &BinaryView, settings: &DisassemblySettings) -> Ref { + unsafe { + let handle = + binaryninjacore_sys::BNCreateLinearViewHighLevelIL(view.handle, settings.handle); + + Self::from_raw(handle) + } + } + + pub fn hlil_ssa(view: &BinaryView, settings: &DisassemblySettings) -> Ref { + unsafe { + let handle = binaryninjacore_sys::BNCreateLinearViewHighLevelILSSAForm( + view.handle, + settings.handle, + ); + + Self::from_raw(handle) + } + } + + pub fn language_representation(view: &BinaryView, settings: &DisassemblySettings) -> Ref { + unsafe { + let handle = binaryninjacore_sys::BNCreateLinearViewLanguageRepresentation( + view.handle, + settings.handle, + ); + + Self::from_raw(handle) + } + } + + pub fn single_function_disassembly( + function: &Function, + settings: &DisassemblySettings, + ) -> Ref { + unsafe { + let handle = binaryninjacore_sys::BNCreateLinearViewSingleFunctionDisassembly( + function.handle, + settings.handle, + ); + + Self::from_raw(handle) + } + } + + pub fn single_function_lifted_il( + function: &Function, + settings: &DisassemblySettings, + ) -> Ref { + unsafe { + let handle = binaryninjacore_sys::BNCreateLinearViewSingleFunctionLiftedIL( + function.handle, + settings.handle, + ); + + Self::from_raw(handle) + } + } + + pub fn single_function_mlil(function: &Function, settings: &DisassemblySettings) -> Ref { + unsafe { + let handle = binaryninjacore_sys::BNCreateLinearViewSingleFunctionMediumLevelIL( + function.handle, + settings.handle, + ); + + Self::from_raw(handle) + } + } + + pub fn single_function_mlil_ssa( + function: &Function, + settings: &DisassemblySettings, + ) -> Ref { + unsafe { + let handle = binaryninjacore_sys::BNCreateLinearViewSingleFunctionMediumLevelILSSAForm( + function.handle, + settings.handle, + ); + + Self::from_raw(handle) + } + } + + pub fn single_function_hlil(function: &Function, settings: &DisassemblySettings) -> Ref { + unsafe { + let handle = binaryninjacore_sys::BNCreateLinearViewSingleFunctionHighLevelIL( + function.handle, + settings.handle, + ); + + Self::from_raw(handle) + } + } + + pub fn single_function_hlil_ssa( + function: &Function, + settings: &DisassemblySettings, + ) -> Ref { + unsafe { + let handle = binaryninjacore_sys::BNCreateLinearViewSingleFunctionHighLevelILSSAForm( + function.handle, + settings.handle, + ); + + Self::from_raw(handle) + } + } + + pub fn single_function_language_representation( + function: &Function, + settings: &DisassemblySettings, + ) -> Ref { + unsafe { + let handle = + binaryninjacore_sys::BNCreateLinearViewSingleFunctionLanguageRepresentation( + function.handle, + settings.handle, + ); + + Self::from_raw(handle) + } + } +} + +unsafe impl RefCountable for LinearViewObject { + unsafe fn inc_ref(handle: &Self) -> Ref { + Ref::new(Self { + handle: BNNewLinearViewObjectReference(handle.handle), + }) + } + + unsafe fn dec_ref(handle: &Self) { + BNFreeLinearViewObject(handle.handle); + } +} + +impl ToOwned for LinearViewObject { + type Owned = Ref; + + fn to_owned(&self) -> Self::Owned { + unsafe { RefCountable::inc_ref(self) } + } +} + +unsafe impl Send for LinearViewObject {} +unsafe impl Sync for LinearViewObject {} + +#[derive(Eq)] +pub struct LinearViewCursor { + pub(crate) handle: *mut binaryninjacore_sys::BNLinearViewCursor, +} + +impl LinearViewCursor { + pub(crate) unsafe fn from_raw(handle: *mut BNLinearViewCursor) -> Ref { + debug_assert!(!handle.is_null()); + + Ref::new(Self { handle }) + } + + pub fn new(root: &LinearViewObject) -> Ref { + unsafe { + let handle = BNCreateLinearViewCursor(root.handle); + Self::from_raw(handle) + } + } + + /// Gets the current [LinearViewObject] associated with this cursor. + pub fn current_object(&self) -> Ref { + unsafe { + let handle = BNGetLinearViewCursorCurrentObject(self.handle); + LinearViewObject::from_raw(handle) + } + } + + // FIXME: can we implement clone without shadowing ToOwned? + pub fn duplicate(&self) -> Ref { + unsafe { + let handle = BNDuplicateLinearViewCursor(self.handle); + Self::from_raw(handle) + } + } + + pub fn before_begin(&self) -> bool { + unsafe { BNIsLinearViewCursorBeforeBegin(self.handle) } + } + + pub fn after_end(&self) -> bool { + unsafe { BNIsLinearViewCursorAfterEnd(self.handle) } + } + + pub fn valid(&self) -> bool { + !(self.before_begin() || self.after_end()) + } + + pub fn seek_to_start(&self) { + unsafe { BNSeekLinearViewCursorToBegin(self.handle) } + } + + pub fn seek_to_end(&self) { + unsafe { BNSeekLinearViewCursorToEnd(self.handle) } + } + + pub fn seek_to_address(&self, address: u64) { + unsafe { BNSeekLinearViewCursorToAddress(self.handle, address) } + } + + pub fn ordering_index(&self) -> std::ops::Range { + unsafe { + let range = BNGetLinearViewCursorOrderingIndex(self.handle); + range.start..range.end + } + } + + pub fn ordering_index_total(&self) -> u64 { + unsafe { BNGetLinearViewCursorOrderingIndexTotal(self.handle) } + } + + pub fn seek_to_ordering_index(&self, idx: u64) { + unsafe { BNSeekLinearViewCursorToAddress(self.handle, idx) } + } + + pub fn previous(&self) -> bool { + unsafe { BNLinearViewCursorPrevious(self.handle) } + } + + pub fn next(&self) -> bool { + unsafe { BNLinearViewCursorNext(self.handle) } + } + + pub fn lines(&self) -> Array { + let mut count: usize = 0; + unsafe { + let handles = BNGetLinearViewCursorLines(self.handle, &mut count); + Array::new(handles, count, ()) + } + } +} + +impl PartialEq for LinearViewCursor { + fn eq(&self, other: &Self) -> bool { + unsafe { BNCompareLinearViewCursors(self.handle, other.handle) == 0 } + } +} + +impl PartialOrd for LinearViewCursor { + fn partial_cmp(&self, other: &Self) -> Option { + match unsafe { BNCompareLinearViewCursors(self.handle, other.handle) } { + i if i < 0 => Some(std::cmp::Ordering::Less), + i if i > 0 => Some(std::cmp::Ordering::Greater), + _ => Some(std::cmp::Ordering::Equal), + } + } +} + +impl Ord for LinearViewCursor { + fn cmp(&self, other: &Self) -> std::cmp::Ordering { + match unsafe { BNCompareLinearViewCursors(self.handle, other.handle) } { + i if i < 0 => std::cmp::Ordering::Less, + i if i > 0 => std::cmp::Ordering::Greater, + _ => std::cmp::Ordering::Equal, + } + } +} + +unsafe impl RefCountable for LinearViewCursor { + unsafe fn inc_ref(handle: &Self) -> Ref { + Ref::new(Self { + handle: BNNewLinearViewCursorReference(handle.handle), + }) + } + + unsafe fn dec_ref(handle: &Self) { + BNFreeLinearViewCursor(handle.handle); + } +} + +impl ToOwned for LinearViewCursor { + type Owned = Ref; + + fn to_owned(&self) -> Self::Owned { + unsafe { RefCountable::inc_ref(self) } + } +} + +unsafe impl Send for LinearViewCursor {} +unsafe impl Sync for LinearViewCursor {} + +pub type LinearDisassemblyLineType = BNLinearDisassemblyLineType; + +pub struct LinearDisassemblyLine { + t: LinearDisassemblyLineType, + + // These will be cleaned up by BNFreeLinearDisassemblyLines, so we + // don't drop them in the relevant deconstructors. + function: mem::ManuallyDrop>, + contents: mem::ManuallyDrop, +} + +impl LinearDisassemblyLine { + pub(crate) unsafe fn from_raw(raw: &BNLinearDisassemblyLine) -> Self { + let linetype = raw.type_; + let function = mem::ManuallyDrop::new(Function::from_raw(raw.function)); + let contents = mem::ManuallyDrop::new(DisassemblyTextLine(raw.contents)); + Self { + t: linetype, + function, + contents, + } + } + + pub fn function(&self) -> &Function { + self.function.as_ref() + } + + pub fn line_type(&self) -> LinearDisassemblyLineType { + self.t + } +} + +impl Deref for LinearDisassemblyLine { + type Target = DisassemblyTextLine; + fn deref(&self) -> &Self::Target { + self.contents.deref() + } +} + +impl std::fmt::Display for LinearDisassemblyLine { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "{}", self.deref()) + } +} + +impl CoreArrayProvider for LinearDisassemblyLine { + type Raw = BNLinearDisassemblyLine; + type Context = (); +} + +unsafe impl CoreOwnedArrayProvider 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 { + Guard::new(LinearDisassemblyLine::from_raw(raw), _context) + } +} diff --git a/src/llil/block.rs b/src/llil/block.rs new file mode 100644 index 0000000..d904d2d --- /dev/null +++ b/src/llil/block.rs @@ -0,0 +1,103 @@ +// Copyright 2021-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::ops::Range; + +use crate::architecture::Architecture; +use crate::basicblock::{BasicBlock, BlockContext}; + +use super::*; + +pub struct BlockIter<'func, A, M, F> +where + A: 'func + Architecture, + M: FunctionMutability, + F: FunctionForm, +{ + function: &'func Function, + range: Range, +} + +impl<'func, A, M, F> Iterator for BlockIter<'func, A, M, F> +where + A: 'func + Architecture, + M: FunctionMutability, + F: FunctionForm, +{ + type Item = Instruction<'func, A, M, F>; + + fn next(&mut self) -> Option { + self.range.next().map(|i| Instruction { + function: self.function, + instr_idx: i as usize, + }) + } +} + +pub struct Block<'func, A, M, F> +where + A: 'func + Architecture, + M: FunctionMutability, + F: FunctionForm, +{ + pub(crate) function: &'func Function, +} + +impl<'func, A, M, F> fmt::Debug for Block<'func, A, M, F> +where + A: 'func + Architecture, + M: FunctionMutability, + F: FunctionForm, +{ + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "llil_bb {:?}", self.function) + } +} + +impl<'func, A, M, F> BlockContext for Block<'func, A, M, F> +where + A: 'func + Architecture, + M: FunctionMutability, + F: FunctionForm, +{ + type Iter = BlockIter<'func, A, M, F>; + type Instruction = Instruction<'func, A, M, F>; + + fn start(&self, block: &BasicBlock) -> Instruction<'func, A, M, F> { + Instruction { + function: self.function, + instr_idx: block.raw_start() as usize, + } + } + + fn iter(&self, block: &BasicBlock) -> BlockIter<'func, A, M, F> { + BlockIter { + function: self.function, + range: block.raw_start()..block.raw_end(), + } + } +} + +impl<'func, A, M, F> Clone for Block<'func, A, M, F> +where + A: 'func + Architecture, + M: FunctionMutability, + F: FunctionForm, +{ + fn clone(&self) -> Self { + Block { + function: self.function, + } + } +} diff --git a/src/llil/expression.rs b/src/llil/expression.rs new file mode 100644 index 0000000..d8a3a64 --- /dev/null +++ b/src/llil/expression.rs @@ -0,0 +1,808 @@ +// Copyright 2021-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 binaryninjacore_sys::BNGetLowLevelILByIndex; +use binaryninjacore_sys::BNLowLevelILInstruction; + +use std::fmt; +use std::marker::PhantomData; + +use super::operation; +use super::operation::Operation; +use super::*; + +use crate::architecture::Architecture; +use crate::architecture::RegisterInfo; + +// used as a marker for Expressions that can produce a value +#[derive(Copy, Clone, Debug)] +pub struct ValueExpr; + +// used as a marker for Expressions that can not produce a value +#[derive(Copy, Clone, Debug)] +pub struct VoidExpr; + +pub trait ExpressionResultType: 'static {} +impl ExpressionResultType for ValueExpr {} +impl ExpressionResultType for VoidExpr {} + +pub struct Expression<'func, A, M, F, R> +where + A: 'func + Architecture, + M: FunctionMutability, + F: FunctionForm, + R: ExpressionResultType, +{ + pub(crate) function: &'func Function, + pub(crate) expr_idx: usize, + + // tag the 'return' type of this expression + pub(crate) _ty: PhantomData, +} + +impl<'func, A, M, F, R> Expression<'func, A, M, F, R> +where + A: 'func + Architecture, + M: FunctionMutability, + F: FunctionForm, + R: ExpressionResultType, +{ + pub(crate) fn new(function: &'func Function, expr_idx: usize) -> Self { + Self { + function, + expr_idx, + _ty: PhantomData, + } + } + + pub fn index(&self) -> usize { + self.expr_idx + } +} + +impl<'func, A, M, V> fmt::Debug for Expression<'func, A, M, NonSSA, ValueExpr> +where + A: 'func + Architecture, + M: FunctionMutability, + V: NonSSAVariant, +{ + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + let op_info = self.info(); + write!(f, "", self.expr_idx, op_info) + } +} + +fn common_info<'func, A, M, F>( + function: &'func Function, + op: BNLowLevelILInstruction, +) -> ExprInfo<'func, A, M, F> +where + A: 'func + Architecture, + M: FunctionMutability, + F: FunctionForm, +{ + use binaryninjacore_sys::BNLowLevelILOperation::*; + + match op.operation { + LLIL_CONST => ExprInfo::Const(Operation::new(function, op)), + LLIL_CONST_PTR => ExprInfo::ConstPtr(Operation::new(function, op)), + + LLIL_ADD => ExprInfo::Add(Operation::new(function, op)), + LLIL_ADC => ExprInfo::Adc(Operation::new(function, op)), + LLIL_SUB => ExprInfo::Sub(Operation::new(function, op)), + LLIL_SBB => ExprInfo::Sbb(Operation::new(function, op)), + LLIL_AND => ExprInfo::And(Operation::new(function, op)), + LLIL_OR => ExprInfo::Or(Operation::new(function, op)), + LLIL_XOR => ExprInfo::Xor(Operation::new(function, op)), + LLIL_LSL => ExprInfo::Lsl(Operation::new(function, op)), + LLIL_LSR => ExprInfo::Lsr(Operation::new(function, op)), + LLIL_ASR => ExprInfo::Asr(Operation::new(function, op)), + LLIL_ROL => ExprInfo::Rol(Operation::new(function, op)), + LLIL_RLC => ExprInfo::Rlc(Operation::new(function, op)), + LLIL_ROR => ExprInfo::Ror(Operation::new(function, op)), + LLIL_RRC => ExprInfo::Rrc(Operation::new(function, op)), + LLIL_MUL => ExprInfo::Mul(Operation::new(function, op)), + + LLIL_MULU_DP => ExprInfo::MuluDp(Operation::new(function, op)), + LLIL_MULS_DP => ExprInfo::MulsDp(Operation::new(function, op)), + + LLIL_DIVU => ExprInfo::Divu(Operation::new(function, op)), + LLIL_DIVS => ExprInfo::Divs(Operation::new(function, op)), + + LLIL_DIVU_DP => ExprInfo::DivuDp(Operation::new(function, op)), + LLIL_DIVS_DP => ExprInfo::DivsDp(Operation::new(function, op)), + + LLIL_MODU => ExprInfo::Modu(Operation::new(function, op)), + LLIL_MODS => ExprInfo::Mods(Operation::new(function, op)), + + LLIL_MODU_DP => ExprInfo::ModuDp(Operation::new(function, op)), + LLIL_MODS_DP => ExprInfo::ModsDp(Operation::new(function, op)), + + LLIL_NEG => ExprInfo::Neg(Operation::new(function, op)), + LLIL_NOT => ExprInfo::Not(Operation::new(function, op)), + + LLIL_SX => ExprInfo::Sx(Operation::new(function, op)), + LLIL_ZX => ExprInfo::Zx(Operation::new(function, op)), + LLIL_LOW_PART => ExprInfo::LowPart(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)), + LLIL_CMP_ULT => ExprInfo::CmpUlt(Operation::new(function, op)), + LLIL_CMP_SLE => ExprInfo::CmpSle(Operation::new(function, op)), + LLIL_CMP_ULE => ExprInfo::CmpUle(Operation::new(function, op)), + LLIL_CMP_SGE => ExprInfo::CmpSge(Operation::new(function, op)), + LLIL_CMP_UGE => ExprInfo::CmpUge(Operation::new(function, op)), + LLIL_CMP_SGT => ExprInfo::CmpSgt(Operation::new(function, op)), + LLIL_CMP_UGT => ExprInfo::CmpUgt(Operation::new(function, op)), + + LLIL_BOOL_TO_INT => ExprInfo::BoolToInt(Operation::new(function, op)), + + LLIL_FADD => ExprInfo::Fadd(Operation::new(function, op)), + LLIL_FSUB => ExprInfo::Fsub(Operation::new(function, op)), + LLIL_FMUL => ExprInfo::Fmul(Operation::new(function, op)), + LLIL_FDIV => ExprInfo::Fdiv(Operation::new(function, op)), + + LLIL_FSQRT => ExprInfo::Fsqrt(Operation::new(function, op)), + LLIL_FNEG => ExprInfo::Fneg(Operation::new(function, op)), + LLIL_FABS => ExprInfo::Fabs(Operation::new(function, op)), + LLIL_FLOAT_TO_INT => ExprInfo::FloatToInt(Operation::new(function, op)), + LLIL_INT_TO_FLOAT => ExprInfo::IntToFloat(Operation::new(function, op)), + LLIL_FLOAT_CONV => ExprInfo::FloatConv(Operation::new(function, op)), + LLIL_ROUND_TO_INT => ExprInfo::RoundToInt(Operation::new(function, op)), + LLIL_FLOOR => ExprInfo::Floor(Operation::new(function, op)), + LLIL_CEIL => ExprInfo::Ceil(Operation::new(function, op)), + LLIL_FTRUNC => ExprInfo::Ftrunc(Operation::new(function, op)), + + LLIL_FCMP_E => ExprInfo::FcmpE(Operation::new(function, op)), + LLIL_FCMP_NE => ExprInfo::FcmpNE(Operation::new(function, op)), + LLIL_FCMP_LT => ExprInfo::FcmpLT(Operation::new(function, op)), + LLIL_FCMP_LE => ExprInfo::FcmpLE(Operation::new(function, op)), + LLIL_FCMP_GT => ExprInfo::FcmpGT(Operation::new(function, op)), + LLIL_FCMP_GE => ExprInfo::FcmpGE(Operation::new(function, op)), + LLIL_FCMP_O => ExprInfo::FcmpO(Operation::new(function, op)), + LLIL_FCMP_UO => ExprInfo::FcmpUO(Operation::new(function, op)), + + LLIL_UNIMPL => ExprInfo::Unimpl(Operation::new(function, op)), + LLIL_UNIMPL_MEM => ExprInfo::UnimplMem(Operation::new(function, op)), + + // TODO TEST_BIT ADD_OVERFLOW + _ => { + #[cfg(debug_assertions)] + { + error!( + "Got unexpected operation {:?} in value expr at 0x{:x}", + op.operation, op.address + ); + } + + ExprInfo::Undef(Operation::new(function, op)) + } + } +} + +use super::VisitorAction; + +macro_rules! visit { + ($f:expr, $($e:expr),*) => { + if let VisitorAction::Halt = $f($($e,)*) { + return VisitorAction::Halt; + } + } +} + +fn common_visit<'func, A, M, F, CB>(info: &ExprInfo<'func, A, M, F>, f: &mut CB) -> VisitorAction +where + A: 'func + Architecture, + M: FunctionMutability, + F: FunctionForm, + CB: FnMut(&Expression<'func, A, M, F, ValueExpr>) -> VisitorAction, +{ + use self::ExprInfo::*; + + match *info { + CmpE(ref op) | CmpNe(ref op) | CmpSlt(ref op) | CmpUlt(ref op) | CmpSle(ref op) + | CmpUle(ref op) | CmpSge(ref op) | CmpUge(ref op) | CmpSgt(ref op) | CmpUgt(ref op) + | FcmpE(ref op) | FcmpNE(ref op) | FcmpLT(ref op) | FcmpLE(ref op) | FcmpGE(ref op) + | FcmpGT(ref op) | FcmpO(ref op) | FcmpUO(ref op) => { + visit!(f, &op.left()); + visit!(f, &op.right()); + } + + Adc(ref op) | Sbb(ref op) | Rlc(ref op) | Rrc(ref op) => { + visit!(f, &op.left()); + visit!(f, &op.right()); + visit!(f, &op.carry()); + } + + Add(ref op) | Sub(ref op) | And(ref op) | Or(ref op) | Xor(ref op) | Lsl(ref op) + | Lsr(ref op) | Asr(ref op) | Rol(ref op) | Ror(ref op) | Mul(ref op) | MulsDp(ref op) + | MuluDp(ref op) | Divu(ref op) | Divs(ref op) | Modu(ref op) | Mods(ref op) + | Fadd(ref op) | Fsub(ref op) | Fmul(ref op) | Fdiv(ref op) => { + visit!(f, &op.left()); + visit!(f, &op.right()); + } + + DivuDp(ref op) | DivsDp(ref op) | ModuDp(ref op) | ModsDp(ref op) => { + visit!(f, &op.high()); + visit!(f, &op.low()); + visit!(f, &op.right()); + } + + Neg(ref op) | Not(ref op) | Sx(ref op) | Zx(ref op) | LowPart(ref op) + | BoolToInt(ref op) | Fsqrt(ref op) | Fneg(ref op) | Fabs(ref op) | FloatToInt(ref op) + | IntToFloat(ref op) | FloatConv(ref op) | RoundToInt(ref op) | Floor(ref op) + | Ceil(ref op) | Ftrunc(ref op) => { + visit!(f, &op.operand()); + } + + UnimplMem(ref op) => { + visit!(f, &op.mem_expr()); + } + + _ => {} + }; + + VisitorAction::Sibling +} + +impl<'func, A, M, V> Expression<'func, A, M, NonSSA, ValueExpr> +where + A: 'func + Architecture, + M: FunctionMutability, + V: NonSSAVariant, +{ + pub(crate) unsafe fn info_from_op( + &self, + op: BNLowLevelILInstruction, + ) -> ExprInfo<'func, A, M, NonSSA> { + use binaryninjacore_sys::BNLowLevelILOperation::*; + + match op.operation { + 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_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 + LLIL_FLAG_GROUP => ExprInfo::FlagGroup(Operation::new(self.function, op)), // TODO lifted only + _ => common_info(self.function, op), + } + } + + pub fn info(&self) -> ExprInfo<'func, A, M, NonSSA> { + unsafe { + let op = BNGetLowLevelILByIndex(self.function.handle, self.expr_idx); + self.info_from_op(op) + } + } + + pub fn visit_tree(&self, f: &mut F) -> VisitorAction + where + F: FnMut(&Self, &ExprInfo<'func, A, M, NonSSA>) -> VisitorAction, + { + use self::ExprInfo::*; + + let info = self.info(); + + match f(self, &info) { + VisitorAction::Descend => {} + action => return action, + }; + + match info { + Load(ref op) => visit!(Self::visit_tree, &op.source_mem_expr(), f), + _ => { + let mut fb = |e: &Self| e.visit_tree(f); + visit!(common_visit, &info, &mut fb); + } + }; + + VisitorAction::Sibling + } +} + +impl<'func, A, M> Expression<'func, A, M, SSA, ValueExpr> +where + A: 'func + Architecture, + M: FunctionMutability, +{ + pub(crate) unsafe fn info_from_op( + &self, + op: BNLowLevelILInstruction, + ) -> ExprInfo<'func, A, M, SSA> { + use binaryninjacore_sys::BNLowLevelILOperation::*; + + 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_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), + } + } + + pub fn info(&self) -> ExprInfo<'func, A, M, SSA> { + unsafe { + let op = BNGetLowLevelILByIndex(self.function.handle, self.expr_idx); + self.info_from_op(op) + } + } + + pub fn visit_tree(&self, f: &mut F) -> VisitorAction + where + F: FnMut(&Self, &ExprInfo<'func, A, M, SSA>) -> VisitorAction, + { + use self::ExprInfo::*; + + let info = self.info(); + + match f(self, &info) { + VisitorAction::Descend => {} + action => return action, + }; + + match info { + // TODO ssa + Load(ref _op) => {} //visit!(Self::visit_tree, &op.source_mem_expr(), f), + _ => { + let mut fb = |e: &Self| e.visit_tree(f); + visit!(common_visit, &info, &mut fb); + } + }; + + VisitorAction::Sibling + } +} + +impl<'func, A, F> Expression<'func, A, Finalized, F, ValueExpr> +where + A: 'func + Architecture, + F: FunctionForm, +{ + // TODO possible values +} + +pub enum ExprInfo<'func, A, M, F> +where + A: 'func + Architecture, + M: FunctionMutability, + F: FunctionForm, +{ + Load(Operation<'func, A, M, F, operation::Load>), + Pop(Operation<'func, A, M, F, operation::Pop>), + Reg(Operation<'func, A, M, F, operation::Reg>), + Const(Operation<'func, A, M, F, operation::Const>), + ConstPtr(Operation<'func, A, M, F, operation::Const>), + Flag(Operation<'func, A, M, F, operation::Flag>), + FlagBit(Operation<'func, A, M, F, operation::FlagBit>), + + Add(Operation<'func, A, M, F, operation::BinaryOp>), + Adc(Operation<'func, A, M, F, operation::BinaryOpCarry>), + Sub(Operation<'func, A, M, F, operation::BinaryOp>), + Sbb(Operation<'func, A, M, F, operation::BinaryOpCarry>), + And(Operation<'func, A, M, F, operation::BinaryOp>), + Or(Operation<'func, A, M, F, operation::BinaryOp>), + Xor(Operation<'func, A, M, F, operation::BinaryOp>), + Lsl(Operation<'func, A, M, F, operation::BinaryOp>), + Lsr(Operation<'func, A, M, F, operation::BinaryOp>), + Asr(Operation<'func, A, M, F, operation::BinaryOp>), + Rol(Operation<'func, A, M, F, operation::BinaryOp>), + Rlc(Operation<'func, A, M, F, operation::BinaryOpCarry>), + Ror(Operation<'func, A, M, F, operation::BinaryOp>), + Rrc(Operation<'func, A, M, F, operation::BinaryOpCarry>), + Mul(Operation<'func, A, M, F, operation::BinaryOp>), + + MulsDp(Operation<'func, A, M, F, operation::BinaryOp>), + MuluDp(Operation<'func, A, M, F, operation::BinaryOp>), + + Divu(Operation<'func, A, M, F, operation::BinaryOp>), + Divs(Operation<'func, A, M, F, operation::BinaryOp>), + + DivuDp(Operation<'func, A, M, F, operation::DoublePrecDivOp>), + DivsDp(Operation<'func, A, M, F, operation::DoublePrecDivOp>), + + Modu(Operation<'func, A, M, F, operation::BinaryOp>), + Mods(Operation<'func, A, M, F, operation::BinaryOp>), + + ModuDp(Operation<'func, A, M, F, operation::DoublePrecDivOp>), + ModsDp(Operation<'func, A, M, F, operation::DoublePrecDivOp>), + + Neg(Operation<'func, A, M, F, operation::UnaryOp>), + Not(Operation<'func, A, M, F, operation::UnaryOp>), + Sx(Operation<'func, A, M, F, operation::UnaryOp>), + Zx(Operation<'func, A, M, F, operation::UnaryOp>), + LowPart(Operation<'func, A, M, F, operation::UnaryOp>), + + FlagCond(Operation<'func, A, M, F, operation::FlagCond>), + FlagGroup(Operation<'func, A, M, F, operation::FlagGroup>), + + CmpE(Operation<'func, A, M, F, operation::Condition>), + CmpNe(Operation<'func, A, M, F, operation::Condition>), + CmpSlt(Operation<'func, A, M, F, operation::Condition>), + CmpUlt(Operation<'func, A, M, F, operation::Condition>), + CmpSle(Operation<'func, A, M, F, operation::Condition>), + CmpUle(Operation<'func, A, M, F, operation::Condition>), + CmpSge(Operation<'func, A, M, F, operation::Condition>), + CmpUge(Operation<'func, A, M, F, operation::Condition>), + CmpSgt(Operation<'func, A, M, F, operation::Condition>), + CmpUgt(Operation<'func, A, M, F, operation::Condition>), + + //TestBit(Operation<'func, A, M, F, operation::TestBit>), // TODO + BoolToInt(Operation<'func, A, M, F, operation::UnaryOp>), + + Fadd(Operation<'func, A, M, F, operation::BinaryOp>), + Fsub(Operation<'func, A, M, F, operation::BinaryOp>), + Fmul(Operation<'func, A, M, F, operation::BinaryOp>), + Fdiv(Operation<'func, A, M, F, operation::BinaryOp>), + Fsqrt(Operation<'func, A, M, F, operation::UnaryOp>), + Fneg(Operation<'func, A, M, F, operation::UnaryOp>), + Fabs(Operation<'func, A, M, F, operation::UnaryOp>), + FloatToInt(Operation<'func, A, M, F, operation::UnaryOp>), + IntToFloat(Operation<'func, A, M, F, operation::UnaryOp>), + FloatConv(Operation<'func, A, M, F, operation::UnaryOp>), + RoundToInt(Operation<'func, A, M, F, operation::UnaryOp>), + Floor(Operation<'func, A, M, F, operation::UnaryOp>), + Ceil(Operation<'func, A, M, F, operation::UnaryOp>), + Ftrunc(Operation<'func, A, M, F, operation::UnaryOp>), + + FcmpE(Operation<'func, A, M, F, operation::Condition>), + FcmpNE(Operation<'func, A, M, F, operation::Condition>), + FcmpLT(Operation<'func, A, M, F, operation::Condition>), + FcmpLE(Operation<'func, A, M, F, operation::Condition>), + FcmpGE(Operation<'func, A, M, F, operation::Condition>), + FcmpGT(Operation<'func, A, M, F, operation::Condition>), + FcmpO(Operation<'func, A, M, F, operation::Condition>), + FcmpUO(Operation<'func, A, M, F, operation::Condition>), + + // TODO ADD_OVERFLOW + Unimpl(Operation<'func, A, M, F, operation::NoArgs>), + UnimplMem(Operation<'func, A, M, F, operation::UnimplMem>), + + Undef(Operation<'func, A, M, F, operation::NoArgs>), +} + +impl<'func, A, M, F> ExprInfo<'func, A, M, F> +where + A: 'func + Architecture, + M: FunctionMutability, + F: FunctionForm, +{ + /// Returns the size of the result of this expression + /// + /// If the expression is malformed or is `Unimpl` there + /// is no meaningful size associated with the result. + pub fn size(&self) -> Option { + use self::ExprInfo::*; + + match *self { + Undef(..) | Unimpl(..) => None, + + FlagCond(..) | FlagGroup(..) | CmpE(..) | CmpNe(..) | CmpSlt(..) | CmpUlt(..) + | CmpSle(..) | CmpUle(..) | CmpSge(..) | CmpUge(..) | CmpSgt(..) | CmpUgt(..) => { + Some(0) + } + + _ => Some(self.raw_struct().size), + //TestBit(Operation<'func, A, M, F, operation::TestBit>), // TODO + } + } + + pub fn address(&self) -> u64 { + self.raw_struct().address + } + + /// Determines if the expressions represent the same operation + /// + /// It does not examine the operands for equality. + pub fn is_same_op_as(&self, other: &Self) -> bool { + use self::ExprInfo::*; + + match (self, other) { + (&Reg(..), &Reg(..)) => true, + _ => self.raw_struct().operation == other.raw_struct().operation, + } + } + + pub fn as_cmp_op(&self) -> Option<&Operation<'func, A, M, F, operation::Condition>> { + use self::ExprInfo::*; + + match *self { + CmpE(ref op) | CmpNe(ref op) | CmpSlt(ref op) | CmpUlt(ref op) | CmpSle(ref op) + | CmpUle(ref op) | CmpSge(ref op) | CmpUge(ref op) | CmpSgt(ref op) + | CmpUgt(ref op) | FcmpE(ref op) | FcmpNE(ref op) | FcmpLT(ref op) | FcmpLE(ref op) + | FcmpGE(ref op) | FcmpGT(ref op) | FcmpO(ref op) | FcmpUO(ref op) => Some(op), + _ => None, + } + } + + pub fn as_binary_op(&self) -> Option<&Operation<'func, A, M, F, operation::BinaryOp>> { + use self::ExprInfo::*; + + match *self { + Add(ref op) | Sub(ref op) | And(ref op) | Or(ref op) | Xor(ref op) | Lsl(ref op) + | Lsr(ref op) | Asr(ref op) | Rol(ref op) | Ror(ref op) | Mul(ref op) + | MulsDp(ref op) | MuluDp(ref op) | Divu(ref op) | Divs(ref op) | Modu(ref op) + | Mods(ref op) | Fadd(ref op) | Fsub(ref op) | Fmul(ref op) | Fdiv(ref op) => Some(op), + _ => None, + } + } + + pub fn as_binary_op_carry( + &self, + ) -> Option<&Operation<'func, A, M, F, operation::BinaryOpCarry>> { + use self::ExprInfo::*; + + match *self { + Adc(ref op) | Sbb(ref op) | Rlc(ref op) | Rrc(ref op) => Some(op), + _ => None, + } + } + + pub fn as_double_prec_div_op( + &self, + ) -> Option<&Operation<'func, A, M, F, operation::DoublePrecDivOp>> { + use self::ExprInfo::*; + + match *self { + DivuDp(ref op) | DivsDp(ref op) | ModuDp(ref op) | ModsDp(ref op) => Some(op), + _ => None, + } + } + + pub fn as_unary_op(&self) -> Option<&Operation<'func, A, M, F, operation::UnaryOp>> { + use self::ExprInfo::*; + + match *self { + Neg(ref op) | Not(ref op) | Sx(ref op) | Zx(ref op) | LowPart(ref op) + | BoolToInt(ref op) | Fsqrt(ref op) | Fneg(ref op) | Fabs(ref op) + | FloatToInt(ref op) | IntToFloat(ref op) | FloatConv(ref op) | RoundToInt(ref op) + | Floor(ref op) | Ceil(ref op) | Ftrunc(ref op) => Some(op), + _ => None, + } + } + + pub(crate) fn raw_struct(&self) -> &BNLowLevelILInstruction { + use self::ExprInfo::*; + + match *self { + Undef(ref op) => &op.op, + + Unimpl(ref op) => &op.op, + + FlagCond(ref op) => &op.op, + FlagGroup(ref op) => &op.op, + + CmpE(ref op) | CmpNe(ref op) | CmpSlt(ref op) | CmpUlt(ref op) | CmpSle(ref op) + | CmpUle(ref op) | CmpSge(ref op) | CmpUge(ref op) | CmpSgt(ref op) + | CmpUgt(ref op) | FcmpE(ref op) | FcmpNE(ref op) | FcmpLT(ref op) | FcmpLE(ref op) + | FcmpGE(ref op) | FcmpGT(ref op) | FcmpO(ref op) | FcmpUO(ref op) => &op.op, + + Load(ref op) => &op.op, + + Pop(ref op) => &op.op, + + Reg(ref op) => &op.op, + + Flag(ref op) => &op.op, + + FlagBit(ref op) => &op.op, + + Const(ref op) | ConstPtr(ref op) => &op.op, + + Adc(ref op) | Sbb(ref op) | Rlc(ref op) | Rrc(ref op) => &op.op, + + Add(ref op) | Sub(ref op) | And(ref op) | Or(ref op) | Xor(ref op) | Lsl(ref op) + | Lsr(ref op) | Asr(ref op) | Rol(ref op) | Ror(ref op) | Mul(ref op) + | MulsDp(ref op) | MuluDp(ref op) | Divu(ref op) | Divs(ref op) | Modu(ref op) + | Mods(ref op) | Fadd(ref op) | Fsub(ref op) | Fmul(ref op) | Fdiv(ref op) => &op.op, + + DivuDp(ref op) | DivsDp(ref op) | ModuDp(ref op) | ModsDp(ref op) => &op.op, + + Neg(ref op) | Not(ref op) | Sx(ref op) | Zx(ref op) | LowPart(ref op) + | BoolToInt(ref op) | Fsqrt(ref op) | Fneg(ref op) | Fabs(ref op) + | FloatToInt(ref op) | IntToFloat(ref op) | FloatConv(ref op) | RoundToInt(ref op) + | Floor(ref op) | Ceil(ref op) | Ftrunc(ref op) => &op.op, + + UnimplMem(ref op) => &op.op, + //TestBit(Operation<'func, A, M, F, operation::TestBit>), // TODO + } + } +} + +impl<'func, A> ExprInfo<'func, A, Mutable, NonSSA> +where + A: 'func + Architecture, +{ + pub fn flag_write(&self) -> Option { + use self::ExprInfo::*; + + match *self { + Undef(ref _op) => None, + + Unimpl(ref _op) => None, + + FlagCond(ref _op) => None, + FlagGroup(ref _op) => None, + + CmpE(ref _op) | CmpNe(ref _op) | CmpSlt(ref _op) | CmpUlt(ref _op) + | CmpSle(ref _op) | CmpUle(ref _op) | CmpSge(ref _op) | CmpUge(ref _op) + | CmpSgt(ref _op) | CmpUgt(ref _op) | FcmpE(ref _op) | FcmpNE(ref _op) + | FcmpLT(ref _op) | FcmpLE(ref _op) | FcmpGE(ref _op) | FcmpGT(ref _op) + | FcmpO(ref _op) | FcmpUO(ref _op) => None, + + Load(ref op) => op.flag_write(), + + Pop(ref op) => op.flag_write(), + + Reg(ref op) => op.flag_write(), + + Flag(ref op) => op.flag_write(), + + FlagBit(ref op) => op.flag_write(), + + Const(ref op) | ConstPtr(ref op) => op.flag_write(), + + Adc(ref op) | Sbb(ref op) | Rlc(ref op) | Rrc(ref op) => op.flag_write(), + + Add(ref op) | Sub(ref op) | And(ref op) | Or(ref op) | Xor(ref op) | Lsl(ref op) + | Lsr(ref op) | Asr(ref op) | Rol(ref op) | Ror(ref op) | Mul(ref op) + | MulsDp(ref op) | MuluDp(ref op) | Divu(ref op) | Divs(ref op) | Modu(ref op) + | Mods(ref op) | Fadd(ref op) | Fsub(ref op) | Fmul(ref op) | Fdiv(ref op) => { + op.flag_write() + } + + DivuDp(ref op) | DivsDp(ref op) | ModuDp(ref op) | ModsDp(ref op) => op.flag_write(), + + Neg(ref op) | Not(ref op) | Sx(ref op) | Zx(ref op) | LowPart(ref op) + | BoolToInt(ref op) | Fsqrt(ref op) | Fneg(ref op) | Fabs(ref op) + | FloatToInt(ref op) | IntToFloat(ref op) | FloatConv(ref op) | RoundToInt(ref op) + | Floor(ref op) | Ceil(ref op) | Ftrunc(ref op) => op.flag_write(), + + UnimplMem(ref op) => op.flag_write(), + //TestBit(Operation<'func, A, M, F, operation::TestBit>), // TODO + } + } +} + +impl<'func, A, M, V> fmt::Debug for ExprInfo<'func, A, M, NonSSA> +where + A: 'func + Architecture, + M: FunctionMutability, + V: NonSSAVariant, +{ + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + use self::ExprInfo::*; + + match *self { + Undef(..) => f.write_str("undefined"), + + Unimpl(..) => f.write_str("unimplemented"), + + FlagCond(..) => f.write_str("some_flag_cond"), + FlagGroup(..) => f.write_str("some_flag_group"), + + CmpE(ref op) | CmpNe(ref op) | CmpSlt(ref op) | CmpUlt(ref op) | CmpSle(ref op) + | CmpUle(ref op) | CmpSge(ref op) | CmpUge(ref op) | CmpSgt(ref op) + | CmpUgt(ref op) | FcmpE(ref op) | FcmpNE(ref op) | FcmpLT(ref op) | FcmpLE(ref op) + | FcmpGE(ref op) | FcmpGT(ref op) | FcmpO(ref op) | FcmpUO(ref op) => { + let left = op.left(); + let right = op.right(); + + write!( + f, + "{:?}({}, {:?}, {:?})", + op.op.operation, + op.size(), + left, + right + ) + } + + Load(ref op) => { + let source = op.source_mem_expr(); + let size = op.size(); + + write!(f, "[{:?}].{}", source, size) + } + + Pop(ref op) => write!(f, "pop.{}", op.size()), + + Reg(ref op) => { + let reg = op.source_reg(); + let size = op.size(); + + let size = match reg { + Register::Temp(_) => Some(size), + Register::ArchReg(ref r) if r.info().size() != size => Some(size), + _ => None, + }; + + match size { + Some(s) => write!(f, "{:?}.{}", reg, s), + _ => write!(f, "{:?}", reg), + } + } + + Flag(ref _op) => write!(f, "flag"), // TODO + + FlagBit(ref _op) => write!(f, "flag_bit"), // TODO + + Const(ref op) | ConstPtr(ref op) => write!(f, "0x{:x}", op.value()), + + Adc(ref op) | Sbb(ref op) | Rlc(ref op) | Rrc(ref op) => { + let left = op.left(); + let right = op.right(); + let carry = op.carry(); + + write!( + f, + "{:?}({}, {:?}, {:?}, carry: {:?})", + op.op.operation, + op.size(), + left, + right, + carry + ) + } + + Add(ref op) | Sub(ref op) | And(ref op) | Or(ref op) | Xor(ref op) | Lsl(ref op) + | Lsr(ref op) | Asr(ref op) | Rol(ref op) | Ror(ref op) | Mul(ref op) + | MulsDp(ref op) | MuluDp(ref op) | Divu(ref op) | Divs(ref op) | Modu(ref op) + | Mods(ref op) | Fadd(ref op) | Fsub(ref op) | Fmul(ref op) | Fdiv(ref op) => { + let left = op.left(); + let right = op.right(); + + write!( + f, + "{:?}({}, {:?}, {:?})", + op.op.operation, + op.size(), + left, + right + ) + } + + DivuDp(ref op) | DivsDp(ref op) | ModuDp(ref op) | ModsDp(ref op) => { + let high = op.high(); + let low = op.low(); + let right = op.right(); + + write!( + f, + "{:?}({}, {:?}:{:?},{:?})", + op.op.operation, + op.size(), + high, + low, + right + ) + } + + Neg(ref op) | Not(ref op) | Sx(ref op) | Zx(ref op) | LowPart(ref op) + | BoolToInt(ref op) | Fsqrt(ref op) | Fneg(ref op) | Fabs(ref op) + | FloatToInt(ref op) | IntToFloat(ref op) | FloatConv(ref op) | RoundToInt(ref op) + | Floor(ref op) | Ceil(ref op) | Ftrunc(ref op) => write!( + f, + "{:?}({}, {:?})", + op.op.operation, + op.size(), + op.operand() + ), + + UnimplMem(ref op) => write!(f, "unimplemented_mem({:?})", op.mem_expr()), + //TestBit(Operation<'func, A, M, F, operation::TestBit>), // TODO + } + } +} diff --git a/src/llil/function.rs b/src/llil/function.rs new file mode 100644 index 0000000..46d3e6d --- /dev/null +++ b/src/llil/function.rs @@ -0,0 +1,208 @@ +// Copyright 2021-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 binaryninjacore_sys::BNFreeLowLevelILFunction; +use binaryninjacore_sys::BNLowLevelILFunction; +use binaryninjacore_sys::BNNewLowLevelILFunctionReference; + +use std::borrow::Borrow; +use std::marker::PhantomData; + +use crate::basicblock::BasicBlock; +use crate::rc::*; + +use super::*; + +#[derive(Copy, Clone, Debug)] +pub struct Mutable; +#[derive(Copy, Clone, Debug)] +pub struct Finalized; + +pub trait FunctionMutability: 'static {} +impl FunctionMutability for Mutable {} +impl FunctionMutability for Finalized {} + +#[derive(Copy, Clone, Debug)] +pub struct LiftedNonSSA; +#[derive(Copy, Clone, Debug)] +pub struct RegularNonSSA; + +pub trait NonSSAVariant: 'static {} +impl NonSSAVariant for LiftedNonSSA {} +impl NonSSAVariant for RegularNonSSA {} + +#[derive(Copy, Clone, Debug)] +pub struct SSA; +#[derive(Copy, Clone, Debug)] +pub struct NonSSA(V); + +pub trait FunctionForm: 'static {} +impl FunctionForm for SSA {} +impl FunctionForm for NonSSA {} + +pub struct Function { + pub(crate) borrower: A::Handle, + pub(crate) handle: *mut BNLowLevelILFunction, + _arch: PhantomData<*mut A>, + _mutability: PhantomData, + _form: PhantomData, +} + +unsafe impl Send for Function {} +unsafe impl Sync for Function {} + +impl Eq for Function {} +impl PartialEq for Function { + fn eq(&self, rhs: &Self) -> bool { + self.handle == rhs.handle + } +} + +use std::hash::{Hash, Hasher}; +impl Hash for Function { + fn hash(&self, state: &mut H) { + self.handle.hash(state); + } +} + +impl<'func, A, M, F> Function +where + A: 'func + Architecture, + M: FunctionMutability, + F: FunctionForm, +{ + pub(crate) unsafe fn from_raw(borrower: A::Handle, handle: *mut BNLowLevelILFunction) -> Self { + debug_assert!(!handle.is_null()); + + Self { + borrower, + handle, + _arch: PhantomData, + _mutability: PhantomData, + _form: PhantomData, + } + } + + pub(crate) fn arch(&self) -> &A { + self.borrower.borrow() + } + + pub fn instruction_at>(&self, loc: L) -> Option> { + use binaryninjacore_sys::BNGetLowLevelILInstructionCount; + use binaryninjacore_sys::BNLowLevelILGetInstructionStart; + + let loc: Location = loc.into(); + let arch_handle = loc.arch.unwrap_or_else(|| *self.arch().as_ref()); + + unsafe { + let instr_idx = BNLowLevelILGetInstructionStart(self.handle, arch_handle.0, loc.addr); + + if instr_idx >= BNGetLowLevelILInstructionCount(self.handle) { + None + } else { + Some(Instruction { + function: self, + instr_idx, + }) + } + } + } + + pub fn instruction_from_idx(&self, instr_idx: usize) -> Instruction { + unsafe { + use binaryninjacore_sys::BNGetLowLevelILInstructionCount; + if instr_idx >= BNGetLowLevelILInstructionCount(self.handle) { + panic!("instruction index {} out of bounds", instr_idx); + } + + Instruction { + function: self, + instr_idx, + } + } + } + + pub fn instruction_count(&self) -> usize { + unsafe { + use binaryninjacore_sys::BNGetLowLevelILInstructionCount; + BNGetLowLevelILInstructionCount(self.handle) + } + } +} + +// LLIL basic blocks are not available until the function object +// is finalized, so ensure we can't try requesting basic blocks +// during lifting +impl<'func, A, F> Function +where + A: 'func + Architecture, + F: FunctionForm, +{ + pub fn basic_blocks(&self) -> Array>> { + use binaryninjacore_sys::BNGetLowLevelILBasicBlockList; + + unsafe { + let mut count = 0; + let blocks = BNGetLowLevelILBasicBlockList(self.handle, &mut count); + let context = LowLevelBlock { function: self }; + + Array::new(blocks, count, context) + } + } +} + +impl<'func, A, M, F> ToOwned for Function +where + A: 'func + Architecture, + M: FunctionMutability, + F: FunctionForm, +{ + type Owned = Ref; + + fn to_owned(&self) -> Self::Owned { + unsafe { RefCountable::inc_ref(self) } + } +} + +unsafe impl<'func, A, M, F> RefCountable for Function +where + A: 'func + Architecture, + M: FunctionMutability, + F: FunctionForm, +{ + unsafe fn inc_ref(handle: &Self) -> Ref { + Ref::new(Self { + borrower: handle.borrower.clone(), + handle: BNNewLowLevelILFunctionReference(handle.handle), + _arch: PhantomData, + _mutability: PhantomData, + _form: PhantomData, + }) + } + + unsafe fn dec_ref(handle: &Self) { + BNFreeLowLevelILFunction(handle.handle); + } +} + +impl<'func, A, M, F> fmt::Debug for Function +where + A: 'func + Architecture, + M: FunctionMutability, + F: FunctionForm, +{ + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "", self.handle) + } +} diff --git a/src/llil/instruction.rs b/src/llil/instruction.rs new file mode 100644 index 0000000..9f2ad9b --- /dev/null +++ b/src/llil/instruction.rs @@ -0,0 +1,202 @@ +// Copyright 2021-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 binaryninjacore_sys::BNGetLowLevelILByIndex; +use binaryninjacore_sys::BNGetLowLevelILIndexForInstruction; +use binaryninjacore_sys::BNLowLevelILInstruction; + +use super::operation; +use super::operation::Operation; +use super::*; + +use crate::architecture::Architecture; + +pub struct Instruction<'func, A, M, F> +where + A: 'func + Architecture, + M: FunctionMutability, + F: FunctionForm, +{ + pub(crate) function: &'func Function, + pub(crate) instr_idx: usize, +} + +fn common_info<'func, A, M, F>( + function: &'func Function, + op: BNLowLevelILInstruction, +) -> Option> +where + A: 'func + Architecture, + M: FunctionMutability, + F: FunctionForm, +{ + use binaryninjacore_sys::BNLowLevelILOperation::*; + + match op.operation { + LLIL_NOP => InstrInfo::Nop(Operation::new(function, op)).into(), + LLIL_JUMP => InstrInfo::Jump(Operation::new(function, op)).into(), + LLIL_JUMP_TO => InstrInfo::JumpTo(Operation::new(function, op)).into(), + LLIL_RET => InstrInfo::Ret(Operation::new(function, op)).into(), + LLIL_NORET => InstrInfo::NoRet(Operation::new(function, op)).into(), + LLIL_IF => InstrInfo::If(Operation::new(function, op)).into(), + LLIL_GOTO => InstrInfo::Goto(Operation::new(function, op)).into(), + LLIL_BP => InstrInfo::Bp(Operation::new(function, op)).into(), + LLIL_TRAP => InstrInfo::Trap(Operation::new(function, op)).into(), + LLIL_UNDEF => InstrInfo::Undef(Operation::new(function, op)).into(), + _ => None, + } +} + +use super::VisitorAction; + +macro_rules! visit { + ($f:expr, $($e:expr),*) => { + if let VisitorAction::Halt = $f($($e,)*) { + return VisitorAction::Halt; + } + } +} + +fn common_visit<'func, A, M, F, CB>(info: &InstrInfo<'func, A, M, F>, f: &mut CB) -> VisitorAction +where + A: 'func + Architecture, + M: FunctionMutability, + F: FunctionForm, + CB: FnMut(&Expression<'func, A, M, F, ValueExpr>) -> VisitorAction, +{ + use self::InstrInfo::*; + + match *info { + Jump(ref op) => visit!(f, &op.target()), + JumpTo(ref op) => visit!(f, &op.target()), + Ret(ref op) => visit!(f, &op.target()), + If(ref op) => visit!(f, &op.condition()), + Value(ref e, _) => visit!(f, e), + _ => {} + }; + + VisitorAction::Sibling +} + +impl<'func, A, M, V> Instruction<'func, A, M, NonSSA> +where + A: 'func + Architecture, + M: FunctionMutability, + V: NonSSAVariant, +{ + pub fn info(&self) -> InstrInfo<'func, A, M, NonSSA> { + use binaryninjacore_sys::BNLowLevelILOperation::*; + + let expr_idx = + unsafe { BNGetLowLevelILIndexForInstruction(self.function.handle, self.instr_idx) }; + let op = unsafe { BNGetLowLevelILByIndex(self.function.handle, expr_idx) }; + + match op.operation { + LLIL_SET_REG => InstrInfo::SetReg(Operation::new(self.function, op)), + LLIL_SET_REG_SPLIT => InstrInfo::SetRegSplit(Operation::new(self.function, op)), + LLIL_SET_FLAG => InstrInfo::SetFlag(Operation::new(self.function, op)), + LLIL_STORE => InstrInfo::Store(Operation::new(self.function, op)), + LLIL_PUSH => InstrInfo::Push(Operation::new(self.function, op)), + LLIL_CALL | LLIL_CALL_STACK_ADJUST => { + InstrInfo::Call(Operation::new(self.function, op)) + } + LLIL_TAILCALL => InstrInfo::TailCall(Operation::new(self.function, op)), + LLIL_SYSCALL => InstrInfo::Syscall(Operation::new(self.function, op)), + LLIL_INTRINSIC => InstrInfo::Intrinsic(Operation::new(self.function, op)), + _ => { + common_info(self.function, op).unwrap_or_else(|| { + // Hopefully this is a bare value. If it isn't (expression + // from wrong function form or similar) it won't really cause + // any problems as it'll come back as undefined when queried. + let expr = Expression::new(self.function, expr_idx); + + let info = unsafe { expr.info_from_op(op) }; + + InstrInfo::Value(expr, info) + }) + } + } + } + + pub fn visit_tree(&self, f: &mut F) -> VisitorAction + where + F: FnMut( + &Expression<'func, A, M, NonSSA, ValueExpr>, + &ExprInfo<'func, A, M, NonSSA>, + ) -> VisitorAction, + { + use self::InstrInfo::*; + let info = self.info(); + + let fb = &mut |e: &Expression<'func, A, M, NonSSA, ValueExpr>| e.visit_tree(f); + + match info { + SetReg(ref op) => visit!(fb, &op.source_expr()), + SetRegSplit(ref op) => visit!(fb, &op.source_expr()), + SetFlag(ref op) => visit!(fb, &op.source_expr()), + Store(ref op) => { + visit!(fb, &op.dest_mem_expr()); + visit!(fb, &op.source_expr()); + } + Push(ref op) => visit!(fb, &op.operand()), + Call(ref op) | TailCall(ref op) => visit!(fb, &op.target()), + Intrinsic(ref _op) => { + // TODO: Use this when we support expression lists + // for expr in op.source_exprs() { + // visit!(fb, expr); + // } + } + _ => visit!(common_visit, &info, fb), + } + + VisitorAction::Sibling + } +} + +pub enum InstrInfo<'func, A, M, F> +where + A: 'func + Architecture, + M: FunctionMutability, + F: FunctionForm, +{ + Nop(Operation<'func, A, M, F, operation::NoArgs>), + SetReg(Operation<'func, A, M, F, operation::SetReg>), + SetRegSplit(Operation<'func, A, M, F, operation::SetRegSplit>), + SetFlag(Operation<'func, A, M, F, operation::SetFlag>), + Store(Operation<'func, A, M, F, operation::Store>), + Push(Operation<'func, A, M, F, operation::UnaryOp>), // TODO needs a real op + + Jump(Operation<'func, A, M, F, operation::Jump>), + JumpTo(Operation<'func, A, M, F, operation::JumpTo>), + + Call(Operation<'func, A, M, F, operation::Call>), + TailCall(Operation<'func, A, M, F, operation::Call>), + + Ret(Operation<'func, A, M, F, operation::Ret>), + NoRet(Operation<'func, A, M, F, operation::NoArgs>), + + If(Operation<'func, A, M, F, operation::If>), + Goto(Operation<'func, A, M, F, operation::Goto>), + + Syscall(Operation<'func, A, M, F, operation::Syscall>), + Intrinsic(Operation<'func, A, M, F, operation::Intrinsic>), + Bp(Operation<'func, A, M, F, operation::NoArgs>), + Trap(Operation<'func, A, M, F, operation::Trap>), + Undef(Operation<'func, A, M, F, operation::NoArgs>), + + Value( + Expression<'func, A, M, F, ValueExpr>, + ExprInfo<'func, A, M, F>, + ), +} diff --git a/src/llil/lifting.rs b/src/llil/lifting.rs new file mode 100644 index 0000000..daf95ac --- /dev/null +++ b/src/llil/lifting.rs @@ -0,0 +1,1435 @@ +// Copyright 2021-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::marker::PhantomData; +use std::mem; + +use crate::architecture::Architecture; +use crate::architecture::Register as ArchReg; +use crate::architecture::{ + Flag, FlagClass, FlagCondition, FlagGroup, FlagRole, FlagWrite, Intrinsic, +}; + +use super::*; + +pub trait Liftable<'func, A: 'func + Architecture> { + type Result: ExpressionResultType; + + fn lift( + il: &'func Function>, + expr: Self, + ) -> Expression<'func, A, Mutable, NonSSA, Self::Result>; +} + +pub trait LiftableWithSize<'func, A: 'func + Architecture>: + Liftable<'func, A, Result = ValueExpr> +{ + fn lift_with_size( + il: &'func Function>, + expr: Self, + size: usize, + ) -> Expression<'func, A, Mutable, NonSSA, ValueExpr>; +} + +use binaryninjacore_sys::BNRegisterOrConstant; + +#[derive(Copy, Clone)] +pub enum RegisterOrConstant { + Register(usize, Register), + Constant(usize, u64), +} + +impl RegisterOrConstant { + pub(crate) fn into_api(self) -> BNRegisterOrConstant { + match self { + RegisterOrConstant::Register(_, r) => BNRegisterOrConstant { + constant: false, + reg: r.id(), + value: 0, + }, + RegisterOrConstant::Constant(_, value) => BNRegisterOrConstant { + constant: true, + reg: 0, + value, + }, + } + } +} + +// TODO flesh way out +#[derive(Copy, Clone)] +pub enum FlagWriteOp { + SetReg(usize, RegisterOrConstant), + SetRegSplit(usize, RegisterOrConstant, RegisterOrConstant), + + Sub(usize, RegisterOrConstant, RegisterOrConstant), + Add(usize, RegisterOrConstant, RegisterOrConstant), + + Load(usize, RegisterOrConstant), + + Push(usize, RegisterOrConstant), + Neg(usize, RegisterOrConstant), + Not(usize, RegisterOrConstant), + Sx(usize, RegisterOrConstant), + Zx(usize, RegisterOrConstant), + LowPart(usize, RegisterOrConstant), + BoolToInt(usize, RegisterOrConstant), + FloatToInt(usize, RegisterOrConstant), + + Store(usize, RegisterOrConstant, RegisterOrConstant), + + And(usize, RegisterOrConstant, RegisterOrConstant), + Or(usize, RegisterOrConstant, RegisterOrConstant), + Xor(usize, RegisterOrConstant, RegisterOrConstant), + Lsl(usize, RegisterOrConstant, RegisterOrConstant), + Lsr(usize, RegisterOrConstant, RegisterOrConstant), + Asr(usize, RegisterOrConstant, RegisterOrConstant), + Rol(usize, RegisterOrConstant, RegisterOrConstant), + Ror(usize, RegisterOrConstant, RegisterOrConstant), + Mul(usize, RegisterOrConstant, RegisterOrConstant), + MuluDp(usize, RegisterOrConstant, RegisterOrConstant), + MulsDp(usize, RegisterOrConstant, RegisterOrConstant), + Divu(usize, RegisterOrConstant, RegisterOrConstant), + Divs(usize, RegisterOrConstant, RegisterOrConstant), + Modu(usize, RegisterOrConstant, RegisterOrConstant), + Mods(usize, RegisterOrConstant, RegisterOrConstant), + DivuDp(usize, RegisterOrConstant, RegisterOrConstant), + DivsDp(usize, RegisterOrConstant, RegisterOrConstant), + ModuDp(usize, RegisterOrConstant, RegisterOrConstant), + ModsDp(usize, RegisterOrConstant, RegisterOrConstant), + + TestBit(usize, RegisterOrConstant, RegisterOrConstant), + AddOverflow(usize, RegisterOrConstant, RegisterOrConstant), + + Adc( + usize, + RegisterOrConstant, + RegisterOrConstant, + RegisterOrConstant, + ), + Sbb( + usize, + RegisterOrConstant, + RegisterOrConstant, + RegisterOrConstant, + ), + Rlc( + usize, + RegisterOrConstant, + RegisterOrConstant, + RegisterOrConstant, + ), + Rrc( + usize, + RegisterOrConstant, + RegisterOrConstant, + RegisterOrConstant, + ), + + Pop(usize), + // TODO: floating point stuff, llil comparison ops that set flags, intrinsics +} + +impl FlagWriteOp { + pub(crate) fn from_op( + arch: &A, + size: usize, + op: BNLowLevelILOperation, + operands: &[BNRegisterOrConstant], + ) -> Option + where + A: Architecture, + R: ArchReg, + { + use self::FlagWriteOp::*; + use binaryninjacore_sys::BNLowLevelILOperation::*; + + fn build_op( + arch: &A, + size: usize, + operand: &BNRegisterOrConstant, + ) -> RegisterOrConstant + where + A: Architecture, + R: ArchReg, + { + if operand.constant { + RegisterOrConstant::Constant(size, operand.value) + } else { + let il_reg = if 0x8000_0000 & operand.reg == 0 { + Register::ArchReg(arch.register_from_id(operand.reg).unwrap()) + } else { + Register::Temp(operand.reg) + }; + + RegisterOrConstant::Register(size, il_reg) + } + } + + macro_rules! op { + ($x:ident, $($ops:expr),*) => { + ( $x(size, $( build_op(arch, size, &operands[$ops]), )* ) ) + }; + } + + Some(match (operands.len(), op) { + (1, LLIL_SET_REG) => op!(SetReg, 0), + (2, LLIL_SET_REG_SPLIT) => op!(SetRegSplit, 0, 1), + + (2, LLIL_SUB) => op!(Sub, 0, 1), + (2, LLIL_ADD) => op!(Add, 0, 1), + + (1, LLIL_LOAD) => op!(Load, 0), + + (1, LLIL_PUSH) => op!(Push, 0), + (1, LLIL_NEG) => op!(Neg, 0), + (1, LLIL_NOT) => op!(Not, 0), + (1, LLIL_SX) => op!(Sx, 0), + (1, LLIL_ZX) => op!(Zx, 0), + (1, LLIL_LOW_PART) => op!(LowPart, 0), + (1, LLIL_BOOL_TO_INT) => op!(BoolToInt, 0), + (1, LLIL_FLOAT_TO_INT) => op!(FloatToInt, 0), + + (2, LLIL_STORE) => op!(Store, 0, 1), + + (2, LLIL_AND) => op!(And, 0, 1), + (2, LLIL_OR) => op!(Or, 0, 1), + (2, LLIL_XOR) => op!(Xor, 0, 1), + (2, LLIL_LSL) => op!(Lsl, 0, 1), + (2, LLIL_LSR) => op!(Lsr, 0, 1), + (2, LLIL_ASR) => op!(Asr, 0, 1), + (2, LLIL_ROL) => op!(Rol, 0, 1), + (2, LLIL_ROR) => op!(Ror, 0, 1), + (2, LLIL_MUL) => op!(Mul, 0, 1), + (2, LLIL_MULU_DP) => op!(MuluDp, 0, 1), + (2, LLIL_MULS_DP) => op!(MulsDp, 0, 1), + (2, LLIL_DIVU) => op!(Divu, 0, 1), + (2, LLIL_DIVS) => op!(Divs, 0, 1), + (2, LLIL_MODU) => op!(Modu, 0, 1), + (2, LLIL_MODS) => op!(Mods, 0, 1), + (2, LLIL_DIVU_DP) => op!(DivuDp, 0, 1), + (2, LLIL_DIVS_DP) => op!(DivsDp, 0, 1), + (2, LLIL_MODU_DP) => op!(ModuDp, 0, 1), + (2, LLIL_MODS_DP) => op!(ModsDp, 0, 1), + + (2, LLIL_TEST_BIT) => op!(TestBit, 0, 1), + (2, LLIL_ADD_OVERFLOW) => op!(AddOverflow, 0, 1), + + (3, LLIL_ADC) => op!(Adc, 0, 1, 2), + (3, LLIL_SBB) => op!(Sbb, 0, 1, 2), + (3, LLIL_RLC) => op!(Rlc, 0, 1, 2), + (3, LLIL_RRC) => op!(Rrc, 0, 1, 2), + + (0, LLIL_POP) => op!(Pop,), + + _ => return None, + }) + } + + pub(crate) fn size_and_op(&self) -> (usize, BNLowLevelILOperation) { + use self::FlagWriteOp::*; + use binaryninjacore_sys::BNLowLevelILOperation::*; + + match *self { + SetReg(size, ..) => (size, LLIL_SET_REG), + SetRegSplit(size, ..) => (size, LLIL_SET_REG_SPLIT), + + Sub(size, ..) => (size, LLIL_SUB), + Add(size, ..) => (size, LLIL_ADD), + + Load(size, ..) => (size, LLIL_LOAD), + + Push(size, ..) => (size, LLIL_PUSH), + Neg(size, ..) => (size, LLIL_NEG), + Not(size, ..) => (size, LLIL_NOT), + Sx(size, ..) => (size, LLIL_SX), + Zx(size, ..) => (size, LLIL_ZX), + LowPart(size, ..) => (size, LLIL_LOW_PART), + BoolToInt(size, ..) => (size, LLIL_BOOL_TO_INT), + FloatToInt(size, ..) => (size, LLIL_FLOAT_TO_INT), + + Store(size, ..) => (size, LLIL_STORE), + + And(size, ..) => (size, LLIL_AND), + Or(size, ..) => (size, LLIL_OR), + Xor(size, ..) => (size, LLIL_XOR), + Lsl(size, ..) => (size, LLIL_LSL), + Lsr(size, ..) => (size, LLIL_LSR), + Asr(size, ..) => (size, LLIL_ASR), + Rol(size, ..) => (size, LLIL_ROL), + Ror(size, ..) => (size, LLIL_ROR), + Mul(size, ..) => (size, LLIL_MUL), + MuluDp(size, ..) => (size, LLIL_MULU_DP), + MulsDp(size, ..) => (size, LLIL_MULS_DP), + Divu(size, ..) => (size, LLIL_DIVU), + Divs(size, ..) => (size, LLIL_DIVS), + Modu(size, ..) => (size, LLIL_MODU), + Mods(size, ..) => (size, LLIL_MODS), + DivuDp(size, ..) => (size, LLIL_DIVU_DP), + DivsDp(size, ..) => (size, LLIL_DIVS_DP), + ModuDp(size, ..) => (size, LLIL_MODU_DP), + ModsDp(size, ..) => (size, LLIL_MODS_DP), + + TestBit(size, ..) => (size, LLIL_TEST_BIT), + AddOverflow(size, ..) => (size, LLIL_ADD_OVERFLOW), + + Adc(size, ..) => (size, LLIL_ADC), + Sbb(size, ..) => (size, LLIL_SBB), + Rlc(size, ..) => (size, LLIL_RLC), + Rrc(size, ..) => (size, LLIL_RRC), + + Pop(size) => (size, LLIL_POP), + } + } + + pub(crate) fn api_operands(&self) -> (usize, [BNRegisterOrConstant; 5]) { + use self::FlagWriteOp::*; + + let mut operands: [BNRegisterOrConstant; 5] = unsafe { mem::zeroed() }; + + let count = match *self { + Pop(_) => 0, + + SetReg(_, op0) + | Load(_, op0) + | Push(_, op0) + | Neg(_, op0) + | Not(_, op0) + | Sx(_, op0) + | Zx(_, op0) + | LowPart(_, op0) + | BoolToInt(_, op0) + | FloatToInt(_, op0) => { + operands[0] = op0.into_api(); + 1 + } + + SetRegSplit(_, op0, op1) + | Sub(_, op0, op1) + | Add(_, op0, op1) + | Store(_, op0, op1) + | And(_, op0, op1) + | Or(_, op0, op1) + | Xor(_, op0, op1) + | Lsl(_, op0, op1) + | Lsr(_, op0, op1) + | Asr(_, op0, op1) + | Rol(_, op0, op1) + | Ror(_, op0, op1) + | Mul(_, op0, op1) + | MuluDp(_, op0, op1) + | MulsDp(_, op0, op1) + | Divu(_, op0, op1) + | Divs(_, op0, op1) + | Modu(_, op0, op1) + | Mods(_, op0, op1) + | DivuDp(_, op0, op1) + | DivsDp(_, op0, op1) + | ModuDp(_, op0, op1) + | ModsDp(_, op0, op1) + | TestBit(_, op0, op1) + | AddOverflow(_, op0, op1) => { + operands[0] = op0.into_api(); + operands[1] = op1.into_api(); + 2 + } + + Adc(_, op0, op1, op2) + | Sbb(_, op0, op1, op2) + | Rlc(_, op0, op1, op2) + | Rrc(_, op0, op1, op2) => { + operands[0] = op0.into_api(); + operands[1] = op1.into_api(); + operands[2] = op2.into_api(); + 3 + } + }; + + (count, operands) + } +} + +pub fn get_default_flag_write_llil<'func, A>( + arch: &A, + role: FlagRole, + op: FlagWriteOp, + il: &'func Lifter, +) -> LiftedExpr<'func, A> +where + A: 'func + Architecture, +{ + let (size, operation) = op.size_and_op(); + let (count, operands) = op.api_operands(); + + let expr_idx = unsafe { + use binaryninjacore_sys::BNGetDefaultArchitectureFlagWriteLowLevelIL; + BNGetDefaultArchitectureFlagWriteLowLevelIL( + arch.as_ref().0, + operation, + size, + role, + operands.as_ptr() as *mut _, + count, + il.handle, + ) + }; + + Expression::new(il, expr_idx) +} + +pub fn get_default_flag_cond_llil<'func, A>( + arch: &A, + cond: FlagCondition, + class: Option, + il: &'func Lifter, +) -> LiftedExpr<'func, A> +where + A: 'func + Architecture, +{ + use binaryninjacore_sys::BNGetDefaultArchitectureFlagConditionLowLevelIL; + + let handle = arch.as_ref(); + let class_id = class.map(|c| c.id()).unwrap_or(0); + + unsafe { + let expr_idx = + BNGetDefaultArchitectureFlagConditionLowLevelIL(handle.0, cond, class_id, il.handle); + + Expression::new(il, expr_idx) + } +} + +macro_rules! prim_int_lifter { + ($x:ty) => { + impl<'a, A: 'a + Architecture> Liftable<'a, A> for $x { + type Result = ValueExpr; + + fn lift(il: &'a Function>, val: Self) + -> Expression<'a, A, Mutable, NonSSA, Self::Result> + { + il.const_int(mem::size_of::(), val as i64 as u64) + } + } + + impl<'a, A: 'a + Architecture> LiftableWithSize<'a, A> for $x { + fn lift_with_size(il: &'a Function>, val: Self, size: usize) + -> Expression<'a, A, Mutable, NonSSA, ValueExpr> + { + let raw = val as i64; + + #[cfg(debug_assertions)] + { + let is_safe = match raw.overflowing_shr(size as u32 * 8) { + (_, true) => true, + (res, false) => [-1, 0].contains(&res), + }; + + if !is_safe { + error!("il @ {:x} attempted to lift constant 0x{:x} as {} byte expr (won't fit!)", + il.current_address(), val, size); + } + } + + il.const_int(size, raw as u64) + } + } + } +} + +prim_int_lifter!(i8); +prim_int_lifter!(i16); +prim_int_lifter!(i32); +prim_int_lifter!(i64); + +prim_int_lifter!(u8); +prim_int_lifter!(u16); +prim_int_lifter!(u32); +prim_int_lifter!(u64); + +impl<'a, R: ArchReg, A: 'a + Architecture> Liftable<'a, A> for Register +where + R: Liftable<'a, A, Result = ValueExpr> + Into>, +{ + type Result = ValueExpr; + + fn lift( + il: &'a Function>, + reg: Self, + ) -> Expression<'a, A, Mutable, NonSSA, Self::Result> { + match reg { + Register::ArchReg(r) => R::lift(il, r), + Register::Temp(t) => il.reg(il.arch().default_integer_size(), Register::Temp(t)), + } + } +} + +impl<'a, R: ArchReg, A: 'a + Architecture> LiftableWithSize<'a, A> for Register +where + R: LiftableWithSize<'a, A> + Into>, +{ + fn lift_with_size( + il: &'a Function>, + reg: Self, + size: usize, + ) -> Expression<'a, A, Mutable, NonSSA, ValueExpr> { + match reg { + Register::ArchReg(r) => R::lift_with_size(il, r, size), + Register::Temp(t) => il.reg(size, Register::Temp(t)), + } + } +} + +impl<'a, R: ArchReg, A: 'a + Architecture> Liftable<'a, A> for RegisterOrConstant +where + R: LiftableWithSize<'a, A, Result = ValueExpr> + Into>, +{ + type Result = ValueExpr; + + fn lift( + il: &'a Function>, + reg: Self, + ) -> Expression<'a, A, Mutable, NonSSA, Self::Result> { + match reg { + RegisterOrConstant::Register(size, r) => Register::::lift_with_size(il, r, size), + RegisterOrConstant::Constant(size, value) => u64::lift_with_size(il, value, size), + } + } +} + +impl<'a, R: ArchReg, A: 'a + Architecture> LiftableWithSize<'a, A> for RegisterOrConstant +where + R: LiftableWithSize<'a, A> + Into>, +{ + fn lift_with_size( + il: &'a Function>, + reg: Self, + size: usize, + ) -> Expression<'a, A, Mutable, NonSSA, ValueExpr> { + // TODO ensure requested size is compatible with size of this constant + match reg { + RegisterOrConstant::Register(_, r) => Register::::lift_with_size(il, r, size), + RegisterOrConstant::Constant(_, value) => u64::lift_with_size(il, value, size), + } + } +} + +impl<'a, A, R> Liftable<'a, A> for Expression<'a, A, Mutable, NonSSA, R> +where + A: 'a + Architecture, + R: ExpressionResultType, +{ + type Result = R; + + fn lift( + il: &'a Function>, + expr: Self, + ) -> Expression<'a, A, Mutable, NonSSA, Self::Result> { + debug_assert!(expr.function.handle == il.handle); + expr + } +} + +impl<'a, A: 'a + Architecture> LiftableWithSize<'a, A> + for Expression<'a, A, Mutable, NonSSA, ValueExpr> +{ + fn lift_with_size( + il: &'a Function>, + expr: Self, + _size: usize, + ) -> Expression<'a, A, Mutable, NonSSA, Self::Result> { + #[cfg(debug_assertions)] + { + if let Some(expr_size) = expr.info().size() { + if expr_size != _size { + warn!( + "il @ {:x} attempted to lift {} byte expression as {} bytes", + il.current_address(), + expr_size, + _size + ); + } + } + } + + Liftable::lift(il, expr) + } +} + +impl<'func, A, R> Expression<'func, A, Mutable, NonSSA, R> +where + A: 'func + Architecture, + R: ExpressionResultType, +{ + pub fn with_source_operand(self, op: u32) -> Self { + use binaryninjacore_sys::BNLowLevelILSetExprSourceOperand; + + unsafe { BNLowLevelILSetExprSourceOperand(self.function.handle, self.expr_idx, op) } + + self + } + + pub fn append(self) { + let il = self.function; + il.instruction(self); + } +} + +use binaryninjacore_sys::BNLowLevelILOperation; +pub struct ExpressionBuilder<'func, A, R> +where + A: 'func + Architecture, + R: ExpressionResultType, +{ + function: &'func Function>, + op: BNLowLevelILOperation, + size: usize, + flags: u32, + op1: u64, + op2: u64, + op3: u64, + op4: u64, + _ty: PhantomData, +} + +impl<'a, A, R> ExpressionBuilder<'a, A, R> +where + A: 'a + Architecture, + R: ExpressionResultType, +{ + pub fn with_flag_write(mut self, flag_write: A::FlagWrite) -> Self { + // TODO verify valid id + self.flags = flag_write.id(); + self + } + + pub fn build(self) -> Expression<'a, A, Mutable, NonSSA, R> { + use binaryninjacore_sys::BNLowLevelILAddExpr; + + let expr_idx = unsafe { + BNLowLevelILAddExpr( + self.function.handle, + self.op, + self.size, + self.flags, + self.op1, + self.op2, + self.op3, + self.op4, + ) + }; + + Expression::new(self.function, expr_idx) + } + + pub fn with_source_operand( + self, + op: u32, + ) -> Expression<'a, A, Mutable, NonSSA, R> { + self.build().with_source_operand(op) + } + + pub fn append(self) { + let expr = self.build(); + let il = expr.function; + + il.instruction(expr); + } +} + +impl<'a, A, R> Liftable<'a, A> for ExpressionBuilder<'a, A, R> +where + A: 'a + Architecture, + R: ExpressionResultType, +{ + type Result = R; + + fn lift( + il: &'a Function>, + expr: Self, + ) -> Expression<'a, A, Mutable, NonSSA, Self::Result> { + debug_assert!(expr.function.handle == il.handle); + + expr.build() + } +} + +impl<'a, A> LiftableWithSize<'a, A> for ExpressionBuilder<'a, A, ValueExpr> +where + A: 'a + Architecture, +{ + fn lift_with_size( + il: &'a Function>, + expr: Self, + _size: usize, + ) -> Expression<'a, A, Mutable, NonSSA, ValueExpr> { + #[cfg(debug_assertions)] + { + use binaryninjacore_sys::BNLowLevelILOperation::{LLIL_UNIMPL, LLIL_UNIMPL_MEM}; + + if expr.size != _size && ![LLIL_UNIMPL, LLIL_UNIMPL_MEM].contains(&expr.op) { + warn!( + "il @ {:x} attempted to lift {} byte expression builder as {} bytes", + il.current_address(), + expr.size, + _size + ); + } + } + + Liftable::lift(il, expr) + } +} + +macro_rules! no_arg_lifter { + ($name:ident, $op:ident, $result:ty) => { + pub fn $name(&self) -> Expression, $result> { + use binaryninjacore_sys::BNLowLevelILAddExpr; + use binaryninjacore_sys::BNLowLevelILOperation::$op; + + let expr_idx = unsafe { BNLowLevelILAddExpr(self.handle, $op, 0, 0, 0, 0, 0, 0) }; + + Expression::new(self, expr_idx) + } + }; +} + +macro_rules! sized_no_arg_lifter { + ($name:ident, $op:ident, $result:ty) => { + pub fn $name(&self, size: usize) -> ExpressionBuilder { + use binaryninjacore_sys::BNLowLevelILOperation::$op; + + ExpressionBuilder { + function: self, + op: $op, + size, + flags: 0, + op1: 0, + op2: 0, + op3: 0, + op4: 0, + _ty: PhantomData, + } + } + }; +} + +macro_rules! unsized_unary_op_lifter { + ($name:ident, $op:ident, $result:ty) => { + pub fn $name<'a, E>( + &'a self, + expr: E, + ) -> Expression<'a, A, Mutable, NonSSA, $result> + where + E: Liftable<'a, A, Result = ValueExpr>, + { + use binaryninjacore_sys::BNLowLevelILAddExpr; + use binaryninjacore_sys::BNLowLevelILOperation::$op; + + let expr = E::lift(self, expr); + + let expr_idx = unsafe { + BNLowLevelILAddExpr(self.handle, $op, 0, 0, expr.expr_idx as u64, 0, 0, 0) + }; + + Expression::new(self, expr_idx) + } + }; +} + +macro_rules! sized_unary_op_lifter { + ($name:ident, $op:ident, $result:ty) => { + pub fn $name<'a, E>(&'a self, size: usize, expr: E) -> ExpressionBuilder<'a, A, $result> + where + E: LiftableWithSize<'a, A>, + { + use binaryninjacore_sys::BNLowLevelILOperation::$op; + + let expr = E::lift_with_size(self, expr, size); + + ExpressionBuilder { + function: self, + op: $op, + size, + flags: 0, + op1: expr.expr_idx as u64, + op2: 0, + op3: 0, + op4: 0, + _ty: PhantomData, + } + } + }; +} + +macro_rules! size_changing_unary_op_lifter { + ($name:ident, $op:ident, $result:ty) => { + pub fn $name<'a, E>(&'a self, size: usize, expr: E) -> ExpressionBuilder<'a, A, $result> + where + E: LiftableWithSize<'a, A>, + { + use binaryninjacore_sys::BNLowLevelILOperation::$op; + + let expr = E::lift(self, expr); + + ExpressionBuilder { + function: self, + op: $op, + size, + flags: 0, + op1: expr.expr_idx as u64, + op2: 0, + op3: 0, + op4: 0, + _ty: PhantomData, + } + } + }; +} + +macro_rules! binary_op_lifter { + ($name:ident, $op:ident) => { + pub fn $name<'a, L, R>( + &'a self, + size: usize, + left: L, + right: R, + ) -> ExpressionBuilder<'a, A, ValueExpr> + where + L: LiftableWithSize<'a, A>, + R: LiftableWithSize<'a, A>, + { + use binaryninjacore_sys::BNLowLevelILOperation::$op; + + let left = L::lift_with_size(self, left, size); + let right = R::lift_with_size(self, right, size); + + ExpressionBuilder { + function: self, + op: $op, + size, + flags: 0, + op1: left.expr_idx as u64, + op2: right.expr_idx as u64, + op3: 0, + op4: 0, + _ty: PhantomData, + } + } + }; +} + +macro_rules! binary_op_carry_lifter { + ($name:ident, $op:ident) => { + pub fn $name<'a, L, R, C>( + &'a self, + size: usize, + left: L, + right: R, + carry: C, + ) -> ExpressionBuilder<'a, A, ValueExpr> + where + L: LiftableWithSize<'a, A>, + R: LiftableWithSize<'a, A>, + C: LiftableWithSize<'a, A>, + { + use binaryninjacore_sys::BNLowLevelILOperation::$op; + + let left = L::lift_with_size(self, left, size); + let right = R::lift_with_size(self, right, size); + let carry = C::lift_with_size(self, carry, 1); // TODO 0? + + ExpressionBuilder { + function: self, + op: $op, + size, + flags: 0, + op1: left.expr_idx as u64, + op2: right.expr_idx as u64, + op3: carry.expr_idx as u64, + op4: 0, + _ty: PhantomData, + } + } + }; +} + +impl Function> +where + A: Architecture, +{ + pub const NO_INPUTS: [ExpressionBuilder<'static, A, ValueExpr>; 0] = []; + pub const NO_OUTPUTS: [Register; 0] = []; + + pub fn expression<'a, E: Liftable<'a, A>>( + &'a self, + expr: E, + ) -> Expression<'a, A, Mutable, NonSSA, E::Result> { + E::lift(self, expr) + } + + pub fn instruction<'a, E: Liftable<'a, A>>(&'a self, expr: E) { + let expr = self.expression(expr); + + unsafe { + use binaryninjacore_sys::BNLowLevelILAddInstruction; + BNLowLevelILAddInstruction(self.handle, expr.expr_idx); + } + } + + pub unsafe fn replace_expression<'a, E: Liftable<'a, A>>( + &'a self, + replaced_expr_index: usize, + replacement: E, + ) { + use binaryninjacore_sys::BNGetLowLevelILExprCount; + use binaryninjacore_sys::BNReplaceLowLevelILExpr; + + if replaced_expr_index >= BNGetLowLevelILExprCount(self.handle) { + panic!( + "bad expr idx used: {} exceeds function bounds", + replaced_expr_index + ); + } + + let expr = self.expression(replacement); + BNReplaceLowLevelILExpr(self.handle, replaced_expr_index, expr.expr_idx); + } + + pub fn const_int( + &self, + size: usize, + val: u64, + ) -> Expression, ValueExpr> { + use binaryninjacore_sys::BNLowLevelILAddExpr; + use binaryninjacore_sys::BNLowLevelILOperation::LLIL_CONST; + + let expr_idx = + unsafe { BNLowLevelILAddExpr(self.handle, LLIL_CONST, size, 0, val, 0, 0, 0) }; + + Expression::new(self, expr_idx) + } + + pub fn const_ptr_sized( + &self, + size: usize, + val: u64, + ) -> Expression, ValueExpr> { + use binaryninjacore_sys::BNLowLevelILAddExpr; + use binaryninjacore_sys::BNLowLevelILOperation::LLIL_CONST_PTR; + + let expr_idx = + unsafe { BNLowLevelILAddExpr(self.handle, LLIL_CONST_PTR, size, 0, val, 0, 0, 0) }; + + Expression::new(self, expr_idx) + } + + pub fn const_ptr(&self, val: u64) -> Expression, ValueExpr> { + self.const_ptr_sized(self.arch().address_size(), val) + } + + pub fn trap(&self, val: u64) -> Expression, VoidExpr> { + use binaryninjacore_sys::BNLowLevelILAddExpr; + use binaryninjacore_sys::BNLowLevelILOperation::LLIL_TRAP; + + let expr_idx = unsafe { BNLowLevelILAddExpr(self.handle, LLIL_TRAP, 0, 0, val, 0, 0, 0) }; + + Expression::new(self, expr_idx) + } + + no_arg_lifter!(unimplemented, LLIL_UNIMPL, ValueExpr); + no_arg_lifter!(undefined, LLIL_UNDEF, VoidExpr); + no_arg_lifter!(nop, LLIL_NOP, VoidExpr); + + no_arg_lifter!(no_ret, LLIL_NORET, VoidExpr); + no_arg_lifter!(syscall, LLIL_SYSCALL, VoidExpr); + no_arg_lifter!(bp, LLIL_BP, VoidExpr); + + unsized_unary_op_lifter!(call, LLIL_CALL, VoidExpr); + unsized_unary_op_lifter!(ret, LLIL_RET, VoidExpr); + unsized_unary_op_lifter!(jump, LLIL_JUMP, VoidExpr); + // JumpTo TODO + + pub fn if_expr<'a: 'b, 'b, C>( + &'a self, + cond: C, + t: &'b Label, + f: &'b Label, + ) -> Expression<'a, A, Mutable, NonSSA, VoidExpr> + where + C: Liftable<'b, A, Result = ValueExpr>, + { + use binaryninjacore_sys::BNLowLevelILIf; + + let cond = C::lift(self, cond); + + let expr_idx = unsafe { + BNLowLevelILIf( + self.handle, + cond.expr_idx as u64, + &t.0 as *const _ as *mut _, + &f.0 as *const _ as *mut _, + ) + }; + + Expression::new(self, expr_idx) + } + + pub fn goto<'a: 'b, 'b>( + &'a self, + l: &'b Label, + ) -> Expression<'a, A, Mutable, NonSSA, VoidExpr> { + use binaryninjacore_sys::BNLowLevelILGoto; + + let expr_idx = unsafe { BNLowLevelILGoto(self.handle, &l.0 as *const _ as *mut _) }; + + Expression::new(self, expr_idx) + } + + pub fn reg>>( + &self, + size: usize, + reg: R, + ) -> Expression, ValueExpr> { + use binaryninjacore_sys::BNLowLevelILAddExpr; + use binaryninjacore_sys::BNLowLevelILOperation::LLIL_REG; + + // TODO verify valid id + let reg = match reg.into() { + Register::ArchReg(r) => r.id(), + Register::Temp(r) => 0x8000_0000 | r, + }; + + let expr_idx = + unsafe { BNLowLevelILAddExpr(self.handle, LLIL_REG, size, 0, reg as u64, 0, 0, 0) }; + + Expression::new(self, expr_idx) + } + + pub fn set_reg<'a, R, E>( + &'a self, + size: usize, + dest_reg: R, + expr: E, + ) -> ExpressionBuilder<'a, A, VoidExpr> + where + R: Into>, + E: LiftableWithSize<'a, A>, + { + use binaryninjacore_sys::BNLowLevelILOperation::LLIL_SET_REG; + + // TODO verify valid id + let dest_reg = match dest_reg.into() { + Register::ArchReg(r) => r.id(), + Register::Temp(r) => 0x8000_0000 | r, + }; + + let expr = E::lift_with_size(self, expr, size); + + ExpressionBuilder { + function: self, + op: LLIL_SET_REG, + size, + flags: 0, + op1: dest_reg as u64, + op2: expr.expr_idx as u64, + op3: 0, + op4: 0, + _ty: PhantomData, + } + } + + pub fn set_reg_split<'a, H, L, E>( + &'a self, + size: usize, + hi_reg: H, + lo_reg: L, + expr: E, + ) -> ExpressionBuilder<'a, A, VoidExpr> + where + H: Into>, + L: Into>, + E: LiftableWithSize<'a, A>, + { + use binaryninjacore_sys::BNLowLevelILOperation::LLIL_SET_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 = E::lift_with_size(self, expr, size); + + ExpressionBuilder { + function: self, + op: LLIL_SET_REG_SPLIT, + size, + flags: 0, + op1: hi_reg as u64, + op2: lo_reg as u64, + op3: expr.expr_idx as u64, + op4: 0, + _ty: PhantomData, + } + } + + pub fn flag(&self, flag: A::Flag) -> Expression, ValueExpr> { + use binaryninjacore_sys::BNLowLevelILAddExpr; + use binaryninjacore_sys::BNLowLevelILOperation::LLIL_FLAG; + + // TODO verify valid id + let expr_idx = + unsafe { BNLowLevelILAddExpr(self.handle, LLIL_FLAG, 0, 0, flag.id() as u64, 0, 0, 0) }; + + Expression::new(self, expr_idx) + } + + pub fn flag_cond( + &self, + cond: FlagCondition, + ) -> Expression, ValueExpr> { + use binaryninjacore_sys::BNLowLevelILAddExpr; + use binaryninjacore_sys::BNLowLevelILOperation::LLIL_FLAG_COND; + + // TODO verify valid id + let expr_idx = + unsafe { BNLowLevelILAddExpr(self.handle, LLIL_FLAG_COND, 0, 0, cond as u64, 0, 0, 0) }; + + Expression::new(self, expr_idx) + } + + pub fn flag_group( + &self, + group: A::FlagGroup, + ) -> Expression, ValueExpr> { + use binaryninjacore_sys::BNLowLevelILAddExpr; + use binaryninjacore_sys::BNLowLevelILOperation::LLIL_FLAG_GROUP; + + // TODO verify valid id + let expr_idx = unsafe { + BNLowLevelILAddExpr( + self.handle, + LLIL_FLAG_GROUP, + 0, + 0, + group.id() as u64, + 0, + 0, + 0, + ) + }; + + Expression::new(self, expr_idx) + } + + pub fn set_flag<'a, E>( + &'a self, + dest_flag: A::Flag, + expr: E, + ) -> ExpressionBuilder<'a, A, VoidExpr> + where + E: LiftableWithSize<'a, A>, + { + use binaryninjacore_sys::BNLowLevelILOperation::LLIL_SET_FLAG; + + // TODO verify valid id + + let expr = E::lift_with_size(self, expr, 0); + + ExpressionBuilder { + function: self, + op: LLIL_SET_FLAG, + size: 0, + flags: 0, + op1: dest_flag.id() as u64, + op2: expr.expr_idx as u64, + op3: 0, + op4: 0, + _ty: PhantomData, + } + } + + /* + * TODO + FlagBit(usize, Flag, u64), + */ + + pub fn load<'a, E>(&'a self, size: usize, source_mem: E) -> ExpressionBuilder<'a, A, ValueExpr> + where + E: Liftable<'a, A, Result = ValueExpr>, + { + use binaryninjacore_sys::BNLowLevelILOperation::LLIL_LOAD; + + let expr = E::lift(self, source_mem); + + ExpressionBuilder { + function: self, + op: LLIL_LOAD, + size, + flags: 0, + op1: expr.expr_idx as u64, + op2: 0, + op3: 0, + op4: 0, + _ty: PhantomData, + } + } + + pub fn store<'a, D, V>( + &'a self, + size: usize, + dest_mem: D, + value: V, + ) -> ExpressionBuilder<'a, A, VoidExpr> + where + D: Liftable<'a, A, Result = ValueExpr>, + V: LiftableWithSize<'a, A>, + { + use binaryninjacore_sys::BNLowLevelILOperation::LLIL_STORE; + + let dest_mem = D::lift(self, dest_mem); + let value = V::lift_with_size(self, value, size); + + ExpressionBuilder { + function: self, + op: LLIL_STORE, + size, + flags: 0, + op1: dest_mem.expr_idx as u64, + op2: value.expr_idx as u64, + op3: 0, + op4: 0, + _ty: PhantomData, + } + } + + pub fn intrinsic<'a, O, OL, I, P, PL>( + &'a self, + outputs: OL, + intrinsic: I, + inputs: PL, + ) -> ExpressionBuilder<'a, A, VoidExpr> + where + O: Into>, + OL: IntoIterator, + I: Into, + P: Liftable<'a, A, Result = ValueExpr>, + PL: IntoIterator, + { + use binaryninjacore_sys::BNLowLevelILOperation::{LLIL_CALL_PARAM, LLIL_INTRINSIC}; + use binaryninjacore_sys::{BNLowLevelILAddExpr, BNLowLevelILAddOperandList}; + + let mut outputs: Vec = outputs + .into_iter() + .map(|output| { + // TODO verify valid id + let output = match output.into() { + Register::ArchReg(r) => r.id(), + Register::Temp(r) => 0x8000_0000 | r, + }; + output as u64 + }) + .collect(); + let output_expr_idx = + unsafe { BNLowLevelILAddOperandList(self.handle, outputs.as_mut_ptr(), outputs.len()) }; + + let intrinsic: A::Intrinsic = intrinsic.into(); + + let mut inputs: Vec = inputs + .into_iter() + .map(|input| { + let input = P::lift(self, input); + input.expr_idx as u64 + }) + .collect(); + let input_list_expr_idx = + unsafe { BNLowLevelILAddOperandList(self.handle, inputs.as_mut_ptr(), inputs.len()) }; + let input_expr_idx = unsafe { + BNLowLevelILAddExpr( + self.handle, + LLIL_CALL_PARAM, + 0, + 0, + inputs.len() as u64, + input_list_expr_idx as u64, + 0, + 0, + ) + }; + + ExpressionBuilder { + function: self, + op: LLIL_INTRINSIC, + size: 0, + flags: 0, + op1: outputs.len() as u64, + op2: output_expr_idx as u64, + op3: intrinsic.id() as u64, + op4: input_expr_idx as u64, + _ty: PhantomData, + } + } + + sized_unary_op_lifter!(push, LLIL_PUSH, VoidExpr); + sized_no_arg_lifter!(pop, LLIL_POP, ValueExpr); + + size_changing_unary_op_lifter!(unimplemented_mem, LLIL_UNIMPL_MEM, ValueExpr); + + sized_unary_op_lifter!(neg, LLIL_NEG, ValueExpr); + sized_unary_op_lifter!(not, LLIL_NOT, ValueExpr); + + size_changing_unary_op_lifter!(sx, LLIL_SX, ValueExpr); + size_changing_unary_op_lifter!(zx, LLIL_ZX, ValueExpr); + size_changing_unary_op_lifter!(low_part, LLIL_LOW_PART, ValueExpr); + + binary_op_lifter!(add, LLIL_ADD); + binary_op_lifter!(add_overflow, LLIL_ADD_OVERFLOW); + binary_op_lifter!(sub, LLIL_SUB); + binary_op_lifter!(and, LLIL_AND); + binary_op_lifter!(or, LLIL_OR); + binary_op_lifter!(xor, LLIL_XOR); + binary_op_lifter!(lsl, LLIL_LSL); + binary_op_lifter!(lsr, LLIL_LSR); + binary_op_lifter!(asr, LLIL_ASR); + + binary_op_lifter!(rol, LLIL_ROL); + binary_op_lifter!(rlc, LLIL_RLC); + binary_op_lifter!(ror, LLIL_ROR); + binary_op_lifter!(rrc, LLIL_RRC); + binary_op_lifter!(mul, LLIL_MUL); + binary_op_lifter!(muls_dp, LLIL_MULS_DP); + binary_op_lifter!(mulu_dp, LLIL_MULU_DP); + binary_op_lifter!(divs, LLIL_DIVS); + binary_op_lifter!(divu, LLIL_DIVU); + binary_op_lifter!(mods, LLIL_MODS); + binary_op_lifter!(modu, LLIL_MODU); + + binary_op_carry_lifter!(adc, LLIL_ADC); + binary_op_carry_lifter!(sbb, LLIL_SBB); + + /* + DivsDp(usize, Expr, Expr, Expr, Option), + DivuDp(usize, Expr, Expr, Expr, Option), + ModsDp(usize, Expr, Expr, Expr, Option), + ModuDp(usize, Expr, Expr, Expr, Option), + */ + + // FlagCond(u32), // TODO + + binary_op_lifter!(cmp_e, LLIL_CMP_E); + binary_op_lifter!(cmp_ne, LLIL_CMP_NE); + binary_op_lifter!(cmp_slt, LLIL_CMP_SLT); + binary_op_lifter!(cmp_ult, LLIL_CMP_ULT); + binary_op_lifter!(cmp_sle, LLIL_CMP_SLE); + binary_op_lifter!(cmp_ule, LLIL_CMP_ULE); + binary_op_lifter!(cmp_sge, LLIL_CMP_SGE); + binary_op_lifter!(cmp_uge, LLIL_CMP_UGE); + binary_op_lifter!(cmp_sgt, LLIL_CMP_SGT); + binary_op_lifter!(cmp_ugt, LLIL_CMP_UGT); + binary_op_lifter!(test_bit, LLIL_TEST_BIT); + + // TODO no flags + size_changing_unary_op_lifter!(bool_to_int, LLIL_BOOL_TO_INT, ValueExpr); + + binary_op_lifter!(fadd, LLIL_FADD); + binary_op_lifter!(fsub, LLIL_FSUB); + binary_op_lifter!(fmul, LLIL_FMUL); + binary_op_lifter!(fdiv, LLIL_FDIV); + sized_unary_op_lifter!(fsqrt, LLIL_FSQRT, ValueExpr); + sized_unary_op_lifter!(fneg, LLIL_FNEG, ValueExpr); + sized_unary_op_lifter!(fabs, LLIL_FABS, ValueExpr); + sized_unary_op_lifter!(float_to_int, LLIL_FLOAT_TO_INT, ValueExpr); + sized_unary_op_lifter!(int_to_float, LLIL_INT_TO_FLOAT, ValueExpr); + sized_unary_op_lifter!(float_conv, LLIL_FLOAT_CONV, ValueExpr); + sized_unary_op_lifter!(round_to_int, LLIL_ROUND_TO_INT, ValueExpr); + sized_unary_op_lifter!(floor, LLIL_FLOOR, ValueExpr); + sized_unary_op_lifter!(ceil, LLIL_CEIL, ValueExpr); + sized_unary_op_lifter!(ftrunc, LLIL_FTRUNC, ValueExpr); + binary_op_lifter!(fcmp_e, LLIL_FCMP_E); + binary_op_lifter!(fcmp_ne, LLIL_FCMP_NE); + binary_op_lifter!(fcmp_lt, LLIL_FCMP_LT); + binary_op_lifter!(fcmp_le, LLIL_FCMP_LE); + binary_op_lifter!(fcmp_ge, LLIL_FCMP_GE); + binary_op_lifter!(fcmp_gt, LLIL_FCMP_GT); + binary_op_lifter!(fcmp_o, LLIL_FCMP_O); + binary_op_lifter!(fcmp_uo, LLIL_FCMP_UO); + + pub fn current_address(&self) -> u64 { + use binaryninjacore_sys::BNLowLevelILGetCurrentAddress; + unsafe { BNLowLevelILGetCurrentAddress(self.handle) } + } + + pub fn set_current_address>(&self, loc: L) { + use binaryninjacore_sys::BNLowLevelILSetCurrentAddress; + + let loc: Location = loc.into(); + let arch = loc.arch.unwrap_or_else(|| *self.arch().as_ref()); + + unsafe { + BNLowLevelILSetCurrentAddress(self.handle, arch.0, loc.addr); + } + } + + pub fn label_for_address>(&self, loc: L) -> Option<&Label> { + use binaryninjacore_sys::BNGetLowLevelILLabelForAddress; + + let loc: Location = loc.into(); + let arch = loc.arch.unwrap_or_else(|| *self.arch().as_ref()); + + let res = unsafe { BNGetLowLevelILLabelForAddress(self.handle, arch.0, loc.addr) }; + + if res.is_null() { + None + } else { + Some(unsafe { &*(res as *mut Label) }) + } + } + + pub fn mark_label(&self, label: &mut Label) { + use binaryninjacore_sys::BNLowLevelILMarkLabel; + + unsafe { + BNLowLevelILMarkLabel(self.handle, &mut label.0 as *mut _); + } + } +} + +use binaryninjacore_sys::BNLowLevelILLabel; + +#[repr(C)] +pub struct Label(BNLowLevelILLabel); +impl Label { + pub fn new() -> Self { + use binaryninjacore_sys::BNLowLevelILInitLabel; + + unsafe { + // This is one instance where it'd be easy to use mem::MaybeUninit, but *shrug* this is easier + let mut res = Label(mem::zeroed()); + BNLowLevelILInitLabel(&mut res.0 as *mut _); + res + } + } +} + +impl Default for Label { + fn default() -> Self { + Self::new() + } +} diff --git a/src/llil/mod.rs b/src/llil/mod.rs new file mode 100644 index 0000000..b9424e8 --- /dev/null +++ b/src/llil/mod.rs @@ -0,0 +1,95 @@ +// Copyright 2021-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::fmt; + +// TODO : provide some way to forbid emitting register reads for certain registers +// also writing for certain registers (e.g. zero register must prohibit il.set_reg and il.reg +// (replace with nop or const(0) respectively) +// requirements on load/store memory address sizes? +// can reg/set_reg be used with sizes that differ from what is in BNRegisterInfo? + +use crate::architecture::Architecture; +use crate::architecture::Register as ArchReg; +use crate::function::Location; + +mod block; +mod expression; +mod function; +mod instruction; +mod lifting; +pub mod operation; + +pub use self::expression::*; +pub use self::function::*; +pub use self::instruction::*; +pub use self::lifting::get_default_flag_cond_llil; +pub use self::lifting::get_default_flag_write_llil; +pub use self::lifting::{ + ExpressionBuilder, FlagWriteOp, Label, Liftable, LiftableWithSize, RegisterOrConstant, +}; + +pub use self::block::Block as LowLevelBlock; +pub use self::block::BlockIter as LowLevelBlockIter; + +pub type Lifter = Function>; +pub type LiftedFunction = Function>; +pub type LiftedExpr<'a, Arch> = Expression<'a, Arch, Mutable, NonSSA, ValueExpr>; +pub type RegularFunction = Function>; +pub type SSAFunction = Function; + +#[derive(Copy, Clone, PartialEq, Eq)] +pub enum Register { + ArchReg(R), + Temp(u32), +} + +impl Register { + fn id(&self) -> u32 { + match *self { + Register::ArchReg(ref r) => r.id(), + Register::Temp(id) => 0x8000_0000 | id, + } + } +} + +impl fmt::Debug for Register { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match *self { + Register::ArchReg(ref r) => write!(f, "{}", r.name().as_ref()), + Register::Temp(id) => write!(f, "temp{}", id), + } + } +} + +#[derive(Copy, Clone, Debug)] +pub enum SSARegister { + Full(Register, u32), // no such thing as partial access to a temp register, I think + Partial(R, u32, R), // partial accesses only possible for arch registers, I think +} + +impl SSARegister { + pub fn version(&self) -> u32 { + match *self { + SSARegister::Full(_, ver) | SSARegister::Partial(_, ver, _) => ver, + } + } +} + +#[derive(Copy, Clone, PartialEq, Eq, Hash, Debug)] +pub enum VisitorAction { + Descend, + Sibling, + Halt, +} diff --git a/src/llil/operation.rs b/src/llil/operation.rs new file mode 100644 index 0000000..3c40f20 --- /dev/null +++ b/src/llil/operation.rs @@ -0,0 +1,663 @@ +// Copyright 2021-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 binaryninjacore_sys::BNLowLevelILInstruction; + +use std::marker::PhantomData; +use std::mem; + +use super::*; + +pub struct Operation<'func, A, M, F, O> +where + A: 'func + Architecture, + M: FunctionMutability, + F: FunctionForm, + O: OperationArguments, +{ + pub(crate) function: &'func Function, + pub(crate) op: BNLowLevelILInstruction, + _args: PhantomData, +} + +impl<'func, A, M, F, O> Operation<'func, A, M, F, O> +where + A: 'func + Architecture, + M: FunctionMutability, + F: FunctionForm, + O: OperationArguments, +{ + pub(crate) fn new(function: &'func Function, op: BNLowLevelILInstruction) -> Self { + Self { + function, + op, + _args: PhantomData, + } + } + + pub fn address(&self) -> u64 { + self.op.address + } +} + +impl<'func, A, M, O> Operation<'func, A, M, NonSSA, O> +where + A: 'func + Architecture, + M: FunctionMutability, + O: OperationArguments, +{ + pub fn flag_write(&self) -> Option { + match self.op.flags { + 0 => None, + id => self.function.arch().flag_write_from_id(id), + } + } +} + +// LLIL_NOP, LLIL_NORET, LLIL_BP, LLIL_UNDEF, LLIL_UNIMPL +pub struct NoArgs; + +// LLIL_POP +pub struct Pop; + +impl<'func, A, M, F> Operation<'func, A, M, F, Pop> +where + A: 'func + Architecture, + M: FunctionMutability, + F: FunctionForm, +{ + pub fn size(&self) -> usize { + self.op.size + } +} + +// LLIL_SYSCALL, LLIL_SYSCALL_SSA +pub struct Syscall; + +// LLIL_INTRINSIC, LLIL_INTRINSIC_SSA +pub struct Intrinsic; + +impl<'func, A, M, V> Operation<'func, A, M, NonSSA, Intrinsic> + where + A: 'func + Architecture, + M: FunctionMutability, + V: NonSSAVariant, +{ + // TODO: Support register and expression lists + pub fn intrinsic(&self) -> Option { + let raw_id = self.op.operands[2] as u32; + self.function.arch().intrinsic_from_id(raw_id) + } +} + +// LLIL_SET_REG, LLIL_SET_REG_SSA, LLIL_SET_REG_PARTIAL_SSA +pub struct SetReg; + +impl<'func, A, M, V> Operation<'func, A, M, NonSSA, SetReg> +where + A: 'func + Architecture, + M: FunctionMutability, + V: NonSSAVariant, +{ + pub fn size(&self) -> usize { + self.op.size + } + + pub fn dest_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_SET_REG @ 0x{:x}", + self.op.address + ); + + Register::Temp(0) + }) + } + } + + pub fn source_expr(&self) -> Expression<'func, A, M, NonSSA, ValueExpr> { + Expression::new(self.function, self.op.operands[1] as usize) + } +} + +// LLIL_SET_REG_SPLIT, LLIL_SET_REG_SPLIT_SSA +pub struct SetRegSplit; + +impl<'func, A, M, V> Operation<'func, A, M, NonSSA, SetRegSplit> +where + A: 'func + Architecture, + M: FunctionMutability, + V: NonSSAVariant, +{ + pub fn size(&self) -> usize { + self.op.size + } + + pub fn dest_reg_high(&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_SET_REG_SPLIT @ 0x{:x}", + self.op.address + ); + + Register::Temp(0) + }) + } + } + + pub fn dest_reg_low(&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_SET_REG_SPLIT @ 0x{:x}", + self.op.address + ); + + Register::Temp(0) + }) + } + } + + pub fn source_expr(&self) -> Expression<'func, A, M, NonSSA, ValueExpr> { + Expression::new(self.function, self.op.operands[2] as usize) + } +} + +// LLIL_SET_FLAG, LLIL_SET_FLAG_SSA +pub struct SetFlag; + +impl<'func, A, M, V> Operation<'func, A, M, NonSSA, SetFlag> +where + A: 'func + Architecture, + M: FunctionMutability, + V: NonSSAVariant, +{ + pub fn source_expr(&self) -> Expression<'func, A, M, NonSSA, ValueExpr> { + Expression::new(self.function, self.op.operands[1] as usize) + } +} + +// LLIL_LOAD, LLIL_LOAD_SSA +pub struct Load; + +impl<'func, A, M, V> Operation<'func, A, M, NonSSA, Load> +where + A: 'func + Architecture, + M: FunctionMutability, + V: NonSSAVariant, +{ + pub fn size(&self) -> usize { + self.op.size + } + + pub fn source_mem_expr(&self) -> Expression<'func, A, M, NonSSA, ValueExpr> { + Expression::new(self.function, self.op.operands[0] as usize) + } +} + +// LLIL_STORE, LLIL_STORE_SSA +pub struct Store; + +impl<'func, A, M, V> Operation<'func, A, M, NonSSA, Store> +where + A: 'func + Architecture, + M: FunctionMutability, + V: NonSSAVariant, +{ + pub fn size(&self) -> usize { + self.op.size + } + + pub fn dest_mem_expr(&self) -> Expression<'func, A, M, NonSSA, ValueExpr> { + Expression::new(self.function, self.op.operands[0] as usize) + } + + pub fn source_expr(&self) -> Expression<'func, A, M, NonSSA, ValueExpr> { + Expression::new(self.function, self.op.operands[1] as usize) + } +} + +// LLIL_REG, LLIL_REG_SSA, LLIL_REG_SSA_PARTIAL +pub struct Reg; + +impl<'func, A, M, V> Operation<'func, A, M, NonSSA, Reg> +where + A: 'func + Architecture, + M: FunctionMutability, + V: NonSSAVariant, +{ + pub fn size(&self) -> usize { + self.op.size + } + + pub fn source_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) + }) + } + } +} + +// LLIL_FLAG, LLIL_FLAG_SSA +pub struct Flag; + +// LLIL_FLAG_BIT, LLIL_FLAG_BIT_SSA +pub struct FlagBit; + +// LLIL_JUMP +pub struct Jump; + +impl<'func, A, M, F> Operation<'func, A, M, F, Jump> +where + A: 'func + Architecture, + M: FunctionMutability, + F: FunctionForm, +{ + pub fn target(&self) -> Expression<'func, A, M, F, ValueExpr> { + Expression::new(self.function, self.op.operands[0] as usize) + } +} + +// LLIL_JUMP_TO +pub struct JumpTo; + +impl<'func, A, M, F> Operation<'func, A, M, F, JumpTo> +where + A: 'func + Architecture, + M: FunctionMutability, + F: FunctionForm, +{ + pub fn target(&self) -> Expression<'func, A, M, F, ValueExpr> { + Expression::new(self.function, self.op.operands[0] as usize) + } + // TODO target list +} + +// LLIL_CALL, LLIL_CALL_SSA +pub struct Call; + +impl<'func, A, M, V> Operation<'func, A, M, NonSSA, Call> +where + A: 'func + Architecture, + M: FunctionMutability, + V: NonSSAVariant, +{ + pub fn target(&self) -> Expression<'func, A, M, NonSSA, ValueExpr> { + Expression::new(self.function, self.op.operands[0] as usize) + } + + pub fn stack_adjust(&self) -> Option { + use binaryninjacore_sys::BNLowLevelILOperation::LLIL_CALL_STACK_ADJUST; + + if self.op.operation == LLIL_CALL_STACK_ADJUST { + Some(self.op.operands[1]) + } else { + None + } + } +} + +// LLIL_RET +pub struct Ret; + +impl<'func, A, M, F> Operation<'func, A, M, F, Ret> +where + A: 'func + Architecture, + M: FunctionMutability, + F: FunctionForm, +{ + pub fn target(&self) -> Expression<'func, A, M, F, ValueExpr> { + Expression::new(self.function, self.op.operands[0] as usize) + } +} + +// LLIL_IF +pub struct If; + +impl<'func, A, M, F> Operation<'func, A, M, F, If> +where + A: 'func + Architecture, + M: FunctionMutability, + F: FunctionForm, +{ + pub fn condition(&self) -> Expression<'func, A, M, F, ValueExpr> { + Expression::new(self.function, self.op.operands[0] as usize) + } + + pub fn true_target(&self) -> Instruction<'func, A, M, F> { + Instruction { + function: self.function, + instr_idx: 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, + } + } +} + +// LLIL_GOTO +pub struct Goto; + +impl<'func, A, M, F> Operation<'func, A, M, F, Goto> +where + A: 'func + Architecture, + M: FunctionMutability, + F: FunctionForm, +{ + pub fn target(&self) -> Instruction<'func, A, M, F> { + Instruction { + function: self.function, + instr_idx: self.op.operands[0] as usize, + } + } +} + +// LLIL_FLAG_COND +pub struct FlagCond; + +// LLIL_FLAG_GROUP +pub struct FlagGroup; + +impl<'func, A, M> Operation<'func, A, M, NonSSA, FlagGroup> +where + A: 'func + Architecture, + M: FunctionMutability, +{ + pub fn flag_group(&self) -> A::FlagGroup { + let id = self.op.operands[0] as u32; + self.function.arch().flag_group_from_id(id).unwrap() + } +} + +// LLIL_TRAP +pub struct Trap; + +impl<'func, A, M, F> Operation<'func, A, M, F, Trap> +where + A: 'func + Architecture, + M: FunctionMutability, + F: FunctionForm, +{ + pub fn vector(&self) -> u64 { + self.op.operands[0] + } +} + +// LLIL_REG_PHI +pub struct RegPhi; + +// LLIL_FLAG_PHI +pub struct FlagPhi; + +// LLIL_MEM_PHI +pub struct MemPhi; + +// LLIL_CONST, LLIL_CONST_PTR +pub struct Const; + +impl<'func, A, M, F> Operation<'func, A, M, F, Const> +where + A: 'func + Architecture, + M: FunctionMutability, + F: FunctionForm, +{ + pub fn size(&self) -> usize { + self.op.size + } + + pub fn value(&self) -> u64 { + #[cfg(debug_assertions)] + { + let raw = self.op.operands[0] as i64; + + let is_safe = match raw.overflowing_shr(self.op.size as u32 * 8) { + (_, true) => true, + (res, false) => [-1, 0].contains(&res), + }; + + if !is_safe { + error!( + "il expr @ {:x} contains constant 0x{:x} as {} byte value (doesn't fit!)", + self.op.address, self.op.operands[0], self.op.size + ); + } + } + + let mut mask = -1i64 as u64; + + if self.op.size < mem::size_of::() { + mask <<= self.op.size * 8; + mask = !mask; + } + + self.op.operands[0] & mask + } +} + +// LLIL_ADD, LLIL_SUB, LLIL_AND, LLIL_OR +// LLIL_XOR, LLIL_LSL, LLIL_LSR, LLIL_ASR +// LLIL_ROL, LLIL_ROR, LLIL_MUL, LLIL_MULU_DP, +// LLIL_MULS_DP, LLIL_DIVU, LLIL_DIVS, LLIL_MODU, +// LLIL_MODS +pub struct BinaryOp; + +impl<'func, A, M, F> Operation<'func, A, M, F, BinaryOp> +where + A: 'func + Architecture, + M: FunctionMutability, + F: FunctionForm, +{ + pub fn size(&self) -> usize { + self.op.size + } + + pub fn left(&self) -> Expression<'func, A, M, F, ValueExpr> { + Expression::new(self.function, self.op.operands[0] as usize) + } + + pub fn right(&self) -> Expression<'func, A, M, F, ValueExpr> { + Expression::new(self.function, self.op.operands[1] as usize) + } +} + +// LLIL_ADC, LLIL_SBB, LLIL_RLC, LLIL_RRC +pub struct BinaryOpCarry; + +impl<'func, A, M, F> Operation<'func, A, M, F, BinaryOpCarry> +where + A: 'func + Architecture, + M: FunctionMutability, + F: FunctionForm, +{ + pub fn size(&self) -> usize { + self.op.size + } + + pub fn left(&self) -> Expression<'func, A, M, F, ValueExpr> { + Expression::new(self.function, self.op.operands[0] as usize) + } + + pub fn right(&self) -> Expression<'func, A, M, F, ValueExpr> { + Expression::new(self.function, self.op.operands[1] as usize) + } + + pub fn carry(&self) -> Expression<'func, A, M, F, ValueExpr> { + Expression::new(self.function, self.op.operands[2] as usize) + } +} + +// LLIL_DIVS_DP, LLIL_DIVU_DP, LLIL_MODU_DP, LLIL_MODS_DP +pub struct DoublePrecDivOp; + +impl<'func, A, M, F> Operation<'func, A, M, F, DoublePrecDivOp> +where + A: 'func + Architecture, + M: FunctionMutability, + F: FunctionForm, +{ + pub fn size(&self) -> usize { + self.op.size + } + + pub fn high(&self) -> Expression<'func, A, M, F, ValueExpr> { + Expression::new(self.function, self.op.operands[0] as usize) + } + + pub fn low(&self) -> Expression<'func, A, M, F, ValueExpr> { + Expression::new(self.function, self.op.operands[1] as usize) + } + + pub fn right(&self) -> Expression<'func, A, M, F, ValueExpr> { + Expression::new(self.function, self.op.operands[2] as usize) + } +} + +// LLIL_PUSH, LLIL_NEG, LLIL_NOT, LLIL_SX, +// LLIL_ZX, LLIL_LOW_PART, LLIL_BOOL_TO_INT, LLIL_UNIMPL_MEM +pub struct UnaryOp; + +impl<'func, A, M, F> Operation<'func, A, M, F, UnaryOp> +where + A: 'func + Architecture, + M: FunctionMutability, + F: FunctionForm, +{ + pub fn size(&self) -> usize { + self.op.size + } + + pub fn operand(&self) -> Expression<'func, A, M, F, ValueExpr> { + Expression::new(self.function, self.op.operands[0] as usize) + } +} + +// LLIL_CMP_X +pub struct Condition; + +impl<'func, A, M, F> Operation<'func, A, M, F, Condition> +where + A: 'func + Architecture, + M: FunctionMutability, + F: FunctionForm, +{ + pub fn size(&self) -> usize { + self.op.size + } + + pub fn left(&self) -> Expression<'func, A, M, F, ValueExpr> { + Expression::new(self.function, self.op.operands[0] as usize) + } + + pub fn right(&self) -> Expression<'func, A, M, F, ValueExpr> { + Expression::new(self.function, self.op.operands[1] as usize) + } +} + +// LLIL_UNIMPL_MEM +pub struct UnimplMem; + +impl<'func, A, M, F> Operation<'func, A, M, F, UnimplMem> +where + A: 'func + Architecture, + M: FunctionMutability, + F: FunctionForm, +{ + pub fn size(&self) -> usize { + self.op.size + } + + pub fn mem_expr(&self) -> Expression<'func, A, M, F, ValueExpr> { + Expression::new(self.function, self.op.operands[0] as usize) + } +} + +// TODO TEST_BIT + +pub trait OperationArguments: 'static {} + +impl OperationArguments for NoArgs {} +impl OperationArguments for Pop {} +impl OperationArguments for Syscall {} +impl OperationArguments for Intrinsic {} +impl OperationArguments for SetReg {} +impl OperationArguments for SetRegSplit {} +impl OperationArguments for SetFlag {} +impl OperationArguments for Load {} +impl OperationArguments for Store {} +impl OperationArguments for Reg {} +impl OperationArguments for Flag {} +impl OperationArguments for FlagBit {} +impl OperationArguments for Jump {} +impl OperationArguments for JumpTo {} +impl OperationArguments for Call {} +impl OperationArguments for Ret {} +impl OperationArguments for If {} +impl OperationArguments for Goto {} +impl OperationArguments for FlagCond {} +impl OperationArguments for FlagGroup {} +impl OperationArguments for Trap {} +impl OperationArguments for RegPhi {} +impl OperationArguments for FlagPhi {} +impl OperationArguments for MemPhi {} +impl OperationArguments for Const {} +impl OperationArguments for BinaryOp {} +impl OperationArguments for BinaryOpCarry {} +impl OperationArguments for DoublePrecDivOp {} +impl OperationArguments for UnaryOp {} +impl OperationArguments for Condition {} +impl OperationArguments for UnimplMem {} diff --git a/src/logger.rs b/src/logger.rs new file mode 100644 index 0000000..8651eec --- /dev/null +++ b/src/logger.rs @@ -0,0 +1,175 @@ +#![allow(clippy::needless_doctest_main)] + +//! To use logging in your script, do something like: +//! +//! ``` +//! use binaryninja::logger; +//! use log::{info, LevelFilter}; +//! +//! fn main() { +//! logger::init(LevelFilter::Warn).expect("Unable to initialize logger"); +//! info!("The logger has been initialized!"); +//! // Your code here... +//! } +//! ``` +//! +//! or +//! +//!``` +//! use binaryninja::logger; +//! use log::{info, LevelFilter}; +//! +//! #[no_mangle] +//! pub extern "C" fn CorePluginInit() -> bool { +//! logger::init(LevelFilter::Warn).expect("Unable to initialize logger"); +//! info!("The logger has been initialized!"); +//! // Your code here... +//! true +//! } +//! ``` +//! + +use crate::string::BnStr; + +pub use binaryninjacore_sys::BNLogLevel as Level; +use binaryninjacore_sys::{BNLogListener, BNUpdateLogListeners}; + +use log; +use std::os::raw::{c_char, c_void}; + +struct Logger; +static LOGGER: Logger = Logger; + +impl log::Log for Logger { + fn enabled(&self, _metadata: &log::Metadata) -> bool { + true + } + + fn log(&self, record: &log::Record) { + use self::Level::*; + use binaryninjacore_sys::BNLog; + use log::Level; + use std::ffi::CString; + + let level = match record.level() { + Level::Error => ErrorLog, + Level::Warn => WarningLog, + Level::Info => InfoLog, + Level::Debug | Level::Trace => DebugLog, + }; + + if let Ok(msg) = CString::new(format!("{}", record.args())) { + let percent_s = CString::new("%s").expect("'%s' has no null bytes"); + unsafe { + BNLog( + 0, + level, + std::ptr::null(), + 0, + percent_s.as_ptr(), + msg.as_ptr(), + ); + } + }; + } + + fn flush(&self) {} +} + +/// Uses BinaryNinja's logging functionality as the sink for +/// Rust's `log` crate. +pub fn init(filter: log::LevelFilter) -> Result<(), log::SetLoggerError> { + log::set_max_level(filter); + log::set_logger(&LOGGER) +} + +pub trait LogListener: 'static + Sync { + fn log(&self, session: usize, level: Level, msg: &BnStr, logger_name: &BnStr, tid: usize); + fn level(&self) -> Level; + fn close(&self) {} +} + +pub struct LogGuard { + ctxt: *mut L, +} + +impl Drop for LogGuard { + fn drop(&mut self) { + use binaryninjacore_sys::BNUnregisterLogListener; + + let mut bn_obj = BNLogListener { + context: self.ctxt as *mut _, + log: Some(cb_log::), + close: Some(cb_close::), + getLogLevel: Some(cb_level::), + }; + + unsafe { + BNUnregisterLogListener(&mut bn_obj); + BNUpdateLogListeners(); + + let _listener = Box::from_raw(self.ctxt); + } + } +} + +pub fn register_listener(listener: L) -> LogGuard { + use binaryninjacore_sys::BNRegisterLogListener; + + let raw = Box::into_raw(Box::new(listener)); + let mut bn_obj = BNLogListener { + context: raw as *mut _, + log: Some(cb_log::), + close: Some(cb_close::), + getLogLevel: Some(cb_level::), + }; + + unsafe { + BNRegisterLogListener(&mut bn_obj); + BNUpdateLogListeners(); + } + + LogGuard { ctxt: raw } +} + +extern "C" fn cb_log( + ctxt: *mut c_void, + session: usize, + level: Level, + msg: *const c_char, + logger_name: *const c_char, + tid: usize, +) where + L: LogListener, +{ + ffi_wrap!("LogListener::log", unsafe { + let listener = &*(ctxt as *const L); + listener.log( + session, + level, + BnStr::from_raw(msg), + BnStr::from_raw(logger_name), + tid, + ); + }) +} + +extern "C" fn cb_close(ctxt: *mut c_void) +where + L: LogListener, +{ + ffi_wrap!("LogListener::close", unsafe { + let listener = &*(ctxt as *const L); + listener.close(); + }) +} + +extern "C" fn cb_level(ctxt: *mut c_void) -> Level +where + L: LogListener, +{ + ffi_wrap!("LogListener::log", unsafe { + let listener = &*(ctxt as *const L); + listener.level() + }) +} diff --git a/src/metadata.rs b/src/metadata.rs new file mode 100644 index 0000000..e29789e --- /dev/null +++ b/src/metadata.rs @@ -0,0 +1,716 @@ +use crate::rc::{ + Array, CoreArrayProvider, CoreArrayWrapper, CoreOwnedArrayProvider, Guard, Ref, RefCountable, +}; +use crate::string::{BnStrCompatible, BnString}; +use binaryninjacore_sys::*; +use std::collections::HashMap; +use std::os::raw::c_char; +use std::slice; + +pub type MetadataType = BNMetadataType; + +pub struct Metadata { + pub(crate) handle: *mut BNMetadata, +} + +impl Metadata { + pub(crate) unsafe fn from_raw(handle: *mut BNMetadata) -> Self { + debug_assert!(!handle.is_null()); + + Self { handle } + } + + pub(crate) unsafe fn ref_from_raw(handle: *mut BNMetadata) -> Ref { + Ref::new(Self::from_raw(handle)) + } + + pub fn new_of_type(metadata_type: MetadataType) -> Ref { + unsafe { Self::ref_from_raw(BNCreateMetadataOfType(metadata_type)) } + } + + pub fn get_type(&self) -> MetadataType { + unsafe { BNMetadataGetType(self.handle) } + } + + pub fn get_boolean(&self) -> Result { + match self.get_type() { + MetadataType::BooleanDataType => Ok(unsafe { BNMetadataGetBoolean(self.handle) }), + _ => Err(()), + } + } + + pub fn get_unsigned_integer(&self) -> Result { + match self.get_type() { + MetadataType::UnsignedIntegerDataType => { + Ok(unsafe { BNMetadataGetUnsignedInteger(self.handle) }) + } + _ => Err(()), + } + } + + pub fn get_signed_integer(&self) -> Result { + match self.get_type() { + MetadataType::SignedIntegerDataType => { + Ok(unsafe { BNMetadataGetSignedInteger(self.handle) }) + } + _ => Err(()), + } + } + + pub fn get_double(&self) -> Result { + match self.get_type() { + MetadataType::DoubleDataType => Ok(unsafe { BNMetadataGetDouble(self.handle) }), + _ => Err(()), + } + } + + pub fn get_string(&self) -> Result { + match self.get_type() { + MetadataType::StringDataType => { + let ptr: *mut c_char = unsafe { BNMetadataGetString(self.handle) }; + if ptr.is_null() { + return Err(()); + } + Ok(unsafe { BnString::from_raw(ptr) }) + } + _ => Err(()), + } + } + + pub fn get_boolean_list(&self) -> Result, ()> { + match self.get_type() { + MetadataType::ArrayDataType => { + let mut size: usize = 0; + let ptr: *mut bool = unsafe { BNMetadataGetBooleanList(self.handle, &mut size) }; + if ptr.is_null() { + return Err(()); + } + let list = unsafe { slice::from_raw_parts(ptr, size) }; + let vec = Vec::from(list); + unsafe { BNFreeMetadataBooleanList(ptr, size) }; + Ok(vec) + } + _ => Err(()), + } + } + + pub fn get_unsigned_integer_list(&self) -> Result, ()> { + match self.get_type() { + MetadataType::ArrayDataType => { + let mut size: usize = 0; + let ptr: *mut u64 = + unsafe { BNMetadataGetUnsignedIntegerList(self.handle, &mut size) }; + if ptr.is_null() { + return Err(()); + } + let list = unsafe { slice::from_raw_parts(ptr, size) }; + let vec = Vec::from(list); + unsafe { BNFreeMetadataUnsignedIntegerList(ptr, size) }; + Ok(vec) + } + _ => Err(()), + } + } + + pub fn get_signed_integer_list(&self) -> Result, ()> { + match self.get_type() { + MetadataType::ArrayDataType => { + let mut size: usize = 0; + let ptr: *mut i64 = + unsafe { BNMetadataGetSignedIntegerList(self.handle, &mut size) }; + if ptr.is_null() { + return Err(()); + } + let list = unsafe { slice::from_raw_parts(ptr, size) }; + let vec = Vec::from(list); + unsafe { BNFreeMetadataSignedIntegerList(ptr, size) }; + Ok(vec) + } + _ => Err(()), + } + } + + pub fn get_double_list(&self) -> Result, ()> { + match self.get_type() { + MetadataType::ArrayDataType => { + let mut size: usize = 0; + let ptr: *mut f64 = unsafe { BNMetadataGetDoubleList(self.handle, &mut size) }; + if ptr.is_null() { + return Err(()); + } + let list = unsafe { slice::from_raw_parts(ptr, size) }; + let vec = Vec::from(list); + unsafe { BNFreeMetadataDoubleList(ptr, size) }; + Ok(vec) + } + _ => Err(()), + } + } + + pub fn get_string_list(&self) -> Result, ()> { + match self.get_type() { + MetadataType::ArrayDataType => { + let mut size: usize = 0; + let ptr: *mut *mut c_char = + unsafe { BNMetadataGetStringList(self.handle, &mut size) }; + if ptr.is_null() { + return Err(()); + } + let list = unsafe { slice::from_raw_parts(ptr, size) }; + let vec = list + .iter() + .map(|ptr| unsafe { BnString::from_raw(*ptr) }) + .collect::>(); + unsafe { BNFreeMetadataStringList(ptr, size) }; + Ok(vec) + } + _ => Err(()), + } + } + + pub fn get_raw(&self) -> Result, ()> { + match self.get_type() { + MetadataType::RawDataType => { + let mut size: usize = 0; + let ptr: *mut u8 = unsafe { BNMetadataGetRaw(self.handle, &mut size) }; + if ptr.is_null() { + return Err(()); + } + + let list = unsafe { slice::from_raw_parts(ptr, size) }; + let vec = Vec::from(list); + unsafe { BNFreeMetadataRaw(ptr) }; + Ok(vec) + } + _ => Err(()), + } + } + + pub fn get_array(&self) -> Result, ()> { + match self.get_type() { + MetadataType::ArrayDataType => { + let mut size: usize = 0; + let ptr: *mut *mut BNMetadata = + unsafe { BNMetadataGetArray(self.handle, &mut size) }; + if ptr.is_null() { + return Err(()); + } + + Ok(unsafe { Array::new(ptr, size, ()) }) + } + _ => Err(()), + } + } + + pub fn get_value_store(&self) -> Result>, ()> { + match self.get_type() { + MetadataType::KeyValueDataType => { + let ptr: *mut BNMetadataValueStore = + unsafe { BNMetadataGetValueStore(self.handle) }; + if ptr.is_null() { + return Err(()); + } + + let size = unsafe { (*ptr).size }; + let keys_ptr: *mut *mut c_char = unsafe { (*ptr).keys }; + let keys = unsafe { slice::from_raw_parts(keys_ptr, size) }; + let values_ptr: *mut *mut BNMetadata = unsafe { (*ptr).values }; + let values: &[*mut BNMetadata] = unsafe { slice::from_raw_parts(values_ptr, size) }; + + let mut map = HashMap::new(); + for i in 0..size { + let key = unsafe { BnString::from_raw(keys[i]) }; + + let value = unsafe { + Ref::::new(Self { + handle: BNNewMetadataReference(values[i]), + }) + }; + map.insert(key, value); + } + + Ok(map) + } + _ => Err(()), + } + } + + pub fn len(&self) -> usize { + unsafe { BNMetadataSize(self.handle) } + } + + pub fn is_empty(&self) -> bool { + unsafe { BNMetadataSize(self.handle) == 0 } + } + + pub fn index(&self, index: usize) -> Result>, ()> { + if self.get_type() != MetadataType::ArrayDataType { + return Err(()); + } + let ptr: *mut BNMetadata = unsafe { BNMetadataGetForIndex(self.handle, index) }; + if ptr.is_null() { + return Ok(None); + } + Ok(Some(unsafe { Self::ref_from_raw(ptr) })) + } + + pub fn get(&self, key: S) -> Result>, ()> { + if self.get_type() != MetadataType::KeyValueDataType { + return Err(()); + } + let ptr: *mut BNMetadata = unsafe { + BNMetadataGetForKey( + self.handle, + key.into_bytes_with_nul().as_ref().as_ptr() as *const c_char, + ) + }; + if ptr.is_null() { + return Ok(None); + } + Ok(Some(unsafe { Self::ref_from_raw(ptr) })) + } + + pub fn push(&self, value: &Metadata) -> Result<(), ()> { + if self.get_type() != MetadataType::ArrayDataType { + return Err(()); + } + unsafe { BNMetadataArrayAppend(self.handle, value.handle) }; + Ok(()) + } + + pub fn insert(&self, key: S, value: &Metadata) -> Result<(), ()> { + if self.get_type() != MetadataType::KeyValueDataType { + return Err(()); + } + + unsafe { + BNMetadataSetValueForKey( + self.handle, + key.into_bytes_with_nul().as_ref().as_ptr() as *const c_char, + value.handle, + ) + }; + Ok(()) + } + + pub fn remove_index(&self, index: usize) -> Result<(), ()> { + if self.get_type() != MetadataType::ArrayDataType { + return Err(()); + } + + unsafe { BNMetadataRemoveIndex(self.handle, index) }; + Ok(()) + } + + pub fn remove_key(&self, key: S) -> Result<(), ()> { + if self.get_type() != MetadataType::KeyValueDataType { + return Err(()); + } + + unsafe { + BNMetadataRemoveKey( + self.handle, + key.into_bytes_with_nul().as_ref().as_ptr() as *const c_char, + ) + }; + Ok(()) + } +} + +unsafe impl Sync for Metadata {} +unsafe impl Send for Metadata {} + +unsafe impl RefCountable for Metadata { + unsafe fn inc_ref(handle: &Self) -> Ref { + Ref::new(Self { + handle: BNNewMetadataReference(handle.handle), + }) + } + + unsafe fn dec_ref(handle: &Self) { + BNFreeMetadata(handle.handle); + } +} + +impl CoreArrayProvider for Metadata { + type Raw = *mut BNMetadata; + type Context = (); +} + +unsafe impl CoreOwnedArrayProvider 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> { + Guard::new(Metadata::from_raw(*raw), context) + } +} + +impl ToOwned for Metadata { + type Owned = Ref; + + fn to_owned(&self) -> Self::Owned { + unsafe { RefCountable::inc_ref(self) } + } +} + +impl From for Ref { + fn from(value: bool) -> Self { + unsafe { Metadata::ref_from_raw(BNCreateMetadataBooleanData(value)) } + } +} + +impl From for Ref { + fn from(value: u64) -> Self { + unsafe { Metadata::ref_from_raw(BNCreateMetadataUnsignedIntegerData(value)) } + } +} + +impl From for Ref { + fn from(value: i64) -> Self { + unsafe { Metadata::ref_from_raw(BNCreateMetadataSignedIntegerData(value)) } + } +} + +impl From for Ref { + fn from(value: f64) -> Self { + unsafe { Metadata::ref_from_raw(BNCreateMetadataDoubleData(value)) } + } +} + +impl From for Ref { + fn from(value: String) -> Self { + unsafe { + Metadata::ref_from_raw(BNCreateMetadataStringData( + value.into_bytes_with_nul().as_ptr() as *const c_char, + )) + } + } +} + +impl From<&str> for Ref { + fn from(value: &str) -> Self { + unsafe { + Metadata::ref_from_raw(BNCreateMetadataStringData( + value.into_bytes_with_nul().as_ptr() as *const c_char, + )) + } + } +} + +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())) } + } +} + +impl From<&Vec>> for Ref { + fn from(value: &Vec>) -> Self { + let mut pointers: Vec<*mut BNMetadata> = vec![]; + for v in value.iter() { + pointers.push(v.as_ref().handle); + } + unsafe { + Metadata::ref_from_raw(BNCreateMetadataArray(pointers.as_mut_ptr(), pointers.len())) + } + } +} + +impl From<&Array> for Ref { + fn from(value: &Array) -> Self { + let mut pointers: Vec<*mut BNMetadata> = vec![]; + for v in value.iter() { + pointers.push(v.as_ref().handle); + } + unsafe { + Metadata::ref_from_raw(BNCreateMetadataArray(pointers.as_mut_ptr(), pointers.len())) + } + } +} + +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); + } + + unsafe { + Metadata::ref_from_raw(BNCreateMetadataValueStore( + keys.as_mut_ptr(), + values.as_mut_ptr(), + keys.len(), + )) + } + } +} + +impl>> From<&[(S, T)]> for Ref { + 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); + } + + unsafe { + Metadata::ref_from_raw(BNCreateMetadataValueStore( + keys.as_mut_ptr(), + values.as_mut_ptr(), + keys.len(), + )) + } + } +} + +impl>, const N: usize> From<[(S, T); N]> + for Ref +{ + 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(), + )) + } + } +} + +impl From<&Vec> for Ref { + fn from(value: &Vec) -> Self { + unsafe { + Metadata::ref_from_raw(BNCreateMetadataBooleanListData( + value.as_ptr() as *mut bool, + value.len(), + )) + } + } +} + +impl From<&Vec> for Ref { + fn from(value: &Vec) -> Self { + unsafe { + Metadata::ref_from_raw(BNCreateMetadataUnsignedIntegerListData( + value.as_ptr() as *mut u64, + value.len(), + )) + } + } +} + +impl From<&Vec> for Ref { + fn from(value: &Vec) -> Self { + unsafe { + Metadata::ref_from_raw(BNCreateMetadataSignedIntegerListData( + value.as_ptr() as *mut i64, + value.len(), + )) + } + } +} + +impl From<&Vec> for Ref { + fn from(value: &Vec) -> Self { + unsafe { + Metadata::ref_from_raw(BNCreateMetadataDoubleListData( + value.as_ptr() as *mut f64, + value.len(), + )) + } + } +} + +impl From> for Ref { + fn from(value: Vec) -> Self { + let mut refs = vec![]; + for v in value { + refs.push(v.into_bytes_with_nul()); + } + let mut pointers = vec![]; + for r in &refs { + pointers.push(r.as_ref().as_ptr() as *const c_char); + } + unsafe { + Metadata::ref_from_raw(BNCreateMetadataStringListData( + pointers.as_ptr() as *mut *const c_char, + pointers.len(), + )) + } + } +} + +impl PartialEq for Metadata { + fn eq(&self, other: &Self) -> bool { + unsafe { BNMetadataIsEqual(self.handle, other.handle) } + } +} + +impl Eq for Ref {} + +impl TryFrom<&Metadata> for bool { + type Error = (); + + fn try_from(value: &Metadata) -> Result { + value.get_boolean() + } +} + +impl TryFrom<&Metadata> for u64 { + type Error = (); + + fn try_from(value: &Metadata) -> Result { + value.get_unsigned_integer() + } +} + +impl TryFrom<&Metadata> for i64 { + type Error = (); + + fn try_from(value: &Metadata) -> Result { + value.get_signed_integer() + } +} + +impl TryFrom<&Metadata> for f64 { + type Error = (); + + fn try_from(value: &Metadata) -> Result { + value.get_double() + } +} + +impl TryFrom<&Metadata> for BnString { + type Error = (); + + fn try_from(value: &Metadata) -> Result { + value.get_string() + } +} + +impl TryFrom<&Metadata> for String { + type Error = (); + + fn try_from(value: &Metadata) -> Result { + value.get_string().map(|s| s.to_string()) + } +} + +impl TryFrom<&Metadata> for Vec { + type Error = (); + + fn try_from(value: &Metadata) -> Result { + value.get_boolean_list() + } +} + +impl TryFrom<&Metadata> for Vec { + type Error = (); + + fn try_from(value: &Metadata) -> Result { + value.get_unsigned_integer_list() + } +} + +impl TryFrom<&Metadata> for Vec { + type Error = (); + + fn try_from(value: &Metadata) -> Result { + value.get_signed_integer_list() + } +} + +impl TryFrom<&Metadata> for Vec { + type Error = (); + + fn try_from(value: &Metadata) -> Result { + value.get_double_list() + } +} + +impl TryFrom<&Metadata> for Vec { + type Error = (); + + fn try_from(value: &Metadata) -> Result { + value.get_string_list() + } +} + +impl TryFrom<&Metadata> for Vec { + type Error = (); + + fn try_from(value: &Metadata) -> Result { + value + .get_string_list() + .map(|v| v.into_iter().map(|s| s.to_string()).collect()) + } +} + +impl TryFrom<&Metadata> for Vec { + type Error = (); + + fn try_from(value: &Metadata) -> Result { + value.get_raw() + } +} + +impl TryFrom<&Metadata> for Array { + type Error = (); + + fn try_from(value: &Metadata) -> Result { + value.get_array() + } +} + +impl TryFrom<&Metadata> for HashMap> { + type Error = (); + + fn try_from(value: &Metadata) -> Result { + value.get_value_store() + } +} + +impl TryFrom<&Metadata> for HashMap> { + type Error = (); + + fn try_from(value: &Metadata) -> Result { + value + .get_value_store() + .map(|m| m.into_iter().map(|(k, v)| (k.to_string(), v)).collect()) + } +} diff --git a/src/mlil/block.rs b/src/mlil/block.rs new file mode 100644 index 0000000..734d651 --- /dev/null +++ b/src/mlil/block.rs @@ -0,0 +1,63 @@ +use std::ops::Range; + +use binaryninjacore_sys::BNGetMediumLevelILIndexForInstruction; + +use crate::basicblock::{BasicBlock, BlockContext}; +use crate::rc::Ref; + +use super::{MediumLevelILFunction, MediumLevelILInstruction}; + +pub struct MediumLevelILBlockIter { + function: Ref, + range: Range, +} + +impl Iterator for MediumLevelILBlockIter { + type Item = MediumLevelILInstruction; + + fn next(&mut self) -> Option { + self.range + .next() + .map(|i| unsafe { + BNGetMediumLevelILIndexForInstruction(self.function.handle, i as usize) + }) + .map(|i| MediumLevelILInstruction::new(&self.function, i)) + } +} + +pub struct MediumLevelILBlock { + pub(crate) function: Ref, +} + +impl core::fmt::Debug for MediumLevelILBlock { + fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result { + write!(f, "mlil_bb {:?}", self.function) + } +} + +impl BlockContext for MediumLevelILBlock { + type Iter = MediumLevelILBlockIter; + type Instruction = MediumLevelILInstruction; + + fn start(&self, block: &BasicBlock) -> MediumLevelILInstruction { + let expr_idx = unsafe { + BNGetMediumLevelILIndexForInstruction(self.function.handle, block.raw_start() as usize) + }; + MediumLevelILInstruction::new(&self.function, expr_idx) + } + + fn iter(&self, block: &BasicBlock) -> MediumLevelILBlockIter { + MediumLevelILBlockIter { + function: self.function.to_owned(), + range: block.raw_start()..block.raw_end(), + } + } +} + +impl Clone for MediumLevelILBlock { + fn clone(&self) -> Self { + MediumLevelILBlock { + function: self.function.to_owned(), + } + } +} diff --git a/src/mlil/function.rs b/src/mlil/function.rs new file mode 100644 index 0000000..63c63a3 --- /dev/null +++ b/src/mlil/function.rs @@ -0,0 +1,116 @@ +use core::hash::{Hash, Hasher}; + +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 crate::basicblock::BasicBlock; +use crate::function::Function; +use crate::function::Location; +use crate::rc::{Array, Ref, RefCountable}; + +use super::{MediumLevelILBlock, MediumLevelILInstruction}; + +pub struct MediumLevelILFunction { + pub(crate) handle: *mut BNMediumLevelILFunction, +} + +unsafe impl Send for MediumLevelILFunction {} +unsafe impl Sync for MediumLevelILFunction {} + +impl Eq for MediumLevelILFunction {} +impl PartialEq for MediumLevelILFunction { + fn eq(&self, rhs: &Self) -> bool { + self.handle == rhs.handle + } +} + +impl Hash for MediumLevelILFunction { + fn hash(&self, state: &mut H) { + self.handle.hash(state); + } +} + +impl MediumLevelILFunction { + pub(crate) unsafe fn from_raw(handle: *mut BNMediumLevelILFunction) -> Self { + debug_assert!(!handle.is_null()); + + Self { handle } + } + + pub fn instruction_at>(&self, loc: L) -> Option { + let loc: Location = loc.into(); + let arch_handle = loc.arch.unwrap(); + + let expr_idx = + unsafe { BNMediumLevelILGetInstructionStart(self.handle, arch_handle.0, loc.addr) }; + + if expr_idx >= self.instruction_count() { + None + } else { + Some(MediumLevelILInstruction::new(self, expr_idx)) + } + } + + pub fn instruction_from_idx(&self, expr_idx: usize) -> MediumLevelILInstruction { + MediumLevelILInstruction::new(self, expr_idx) + } + + pub fn instruction_count(&self) -> usize { + unsafe { BNGetMediumLevelILInstructionCount(self.handle) } + } + + pub fn ssa_form(&self) -> MediumLevelILFunction { + let ssa = unsafe { BNGetMediumLevelILSSAForm(self.handle) }; + assert!(!ssa.is_null()); + MediumLevelILFunction { handle: ssa } + } + + pub fn get_function(&self) -> Ref { + unsafe { + let func = BNGetMediumLevelILOwnerFunction(self.handle); + Function::from_raw(func) + } + } + + pub fn basic_blocks(&self) -> Array> { + let mut count = 0; + let blocks = unsafe { BNGetMediumLevelILBasicBlockList(self.handle, &mut count) }; + let context = MediumLevelILBlock { + function: self.to_owned(), + }; + + unsafe { Array::new(blocks, count, context) } + } +} + +impl ToOwned for MediumLevelILFunction { + type Owned = Ref; + + fn to_owned(&self) -> Self::Owned { + unsafe { RefCountable::inc_ref(self) } + } +} + +unsafe impl RefCountable for MediumLevelILFunction { + unsafe fn inc_ref(handle: &Self) -> Ref { + Ref::new(Self { + handle: BNNewMediumLevelILFunctionReference(handle.handle), + }) + } + + unsafe fn dec_ref(handle: &Self) { + BNFreeMediumLevelILFunction(handle.handle); + } +} + +impl core::fmt::Debug for MediumLevelILFunction { + fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result { + write!(f, "", self.handle) + } +} diff --git a/src/mlil/instruction.rs b/src/mlil/instruction.rs new file mode 100644 index 0000000..139abe2 --- /dev/null +++ b/src/mlil/instruction.rs @@ -0,0 +1,824 @@ +use binaryninjacore_sys::BNGetMediumLevelILByIndex; +use binaryninjacore_sys::BNMediumLevelILOperation; + +use crate::mlil::MediumLevelILLiftedOperation; +use crate::rc::Ref; + +use super::operation::*; +use super::{MediumLevelILFunction, MediumLevelILLiftedInstruction}; + +#[derive(Clone)] +pub struct MediumLevelILInstruction { + pub(crate) function: Ref, + pub(crate) address: u64, + pub(crate) operation: MediumLevelILOperation, +} + +#[derive(Copy, Clone)] +pub enum MediumLevelILOperation { + Nop(NoArgs), + Noret(NoArgs), + Bp(NoArgs), + Undef(NoArgs), + Unimpl(NoArgs), + If(MediumLevelILOperationIf), + FloatConst(FloatConst), + Const(Constant), + ConstPtr(Constant), + Import(Constant), + ExternPtr(ExternPtr), + ConstData(ConstData), + Jump(Jump), + RetHint(Jump), + StoreSsa(StoreSsa), + StoreStructSsa(StoreStructSsa), + StoreStruct(StoreStruct), + Store(Store), + JumpTo(JumpTo), + Goto(Goto), + FreeVarSlot(FreeVarSlot), + SetVarField(SetVarField), + SetVar(SetVar), + FreeVarSlotSsa(FreeVarSlotSsa), + SetVarSsaField(SetVarSsaField), + SetVarAliasedField(SetVarSsaField), + SetVarAliased(SetVarAliased), + SetVarSsa(SetVarSsa), + VarPhi(VarPhi), + MemPhi(MemPhi), + VarSplit(VarSplit), + SetVarSplit(SetVarSplit), + VarSplitSsa(VarSplitSsa), + SetVarSplitSsa(SetVarSplitSsa), + Add(BinaryOp), + Sub(BinaryOp), + And(BinaryOp), + Or(BinaryOp), + Xor(BinaryOp), + Lsl(BinaryOp), + Lsr(BinaryOp), + Asr(BinaryOp), + Rol(BinaryOp), + Ror(BinaryOp), + Mul(BinaryOp), + MuluDp(BinaryOp), + MulsDp(BinaryOp), + Divu(BinaryOp), + DivuDp(BinaryOp), + Divs(BinaryOp), + DivsDp(BinaryOp), + Modu(BinaryOp), + ModuDp(BinaryOp), + Mods(BinaryOp), + ModsDp(BinaryOp), + CmpE(BinaryOp), + CmpNe(BinaryOp), + CmpSlt(BinaryOp), + CmpUlt(BinaryOp), + CmpSle(BinaryOp), + CmpUle(BinaryOp), + CmpSge(BinaryOp), + CmpUge(BinaryOp), + CmpSgt(BinaryOp), + CmpUgt(BinaryOp), + TestBit(BinaryOp), + AddOverflow(BinaryOp), + FcmpE(BinaryOp), + FcmpNe(BinaryOp), + FcmpLt(BinaryOp), + FcmpLe(BinaryOp), + FcmpGe(BinaryOp), + FcmpGt(BinaryOp), + FcmpO(BinaryOp), + FcmpUo(BinaryOp), + Fadd(BinaryOp), + Fsub(BinaryOp), + Fmul(BinaryOp), + Fdiv(BinaryOp), + Adc(BinaryOpCarry), + Sbb(BinaryOpCarry), + Rlc(BinaryOpCarry), + Rrc(BinaryOpCarry), + Call(Call), + Tailcall(Call), + Syscall(Syscall), + Intrinsic(Intrinsic), + IntrinsicSsa(IntrinsicSsa), + CallSsa(CallSsa), + TailcallSsa(CallSsa), + CallUntypedSsa(CallUntypedSsa), + TailcallUntypedSsa(CallUntypedSsa), + SyscallSsa(SyscallSsa), + SyscallUntypedSsa(SyscallUntypedSsa), + CallUntyped(CallUntyped), + TailcallUntyped(CallUntyped), + SyscallUntyped(SyscallUntyped), + SeparateParamList(SeparateParamList), + SharedParamSlot(SharedParamSlot), + Neg(UnaryOp), + Not(UnaryOp), + Sx(UnaryOp), + Zx(UnaryOp), + LowPart(UnaryOp), + BoolToInt(UnaryOp), + UnimplMem(UnaryOp), + Fsqrt(UnaryOp), + Fneg(UnaryOp), + Fabs(UnaryOp), + FloatToInt(UnaryOp), + IntToFloat(UnaryOp), + FloatConv(UnaryOp), + RoundToInt(UnaryOp), + Floor(UnaryOp), + Ceil(UnaryOp), + Ftrunc(UnaryOp), + Load(UnaryOp), + LoadStruct(LoadStruct), + LoadStructSsa(LoadStructSsa), + LoadSsa(LoadSsa), + Ret(Ret), + Var(Var), + AddressOf(Var), + VarField(Field), + AddressOfField(Field), + VarSsa(VarSsa), + VarAliased(VarSsa), + VarSsaField(VarSsaField), + VarAliasedField(VarSsaField), + Trap(Trap), +} + +impl core::fmt::Debug for MediumLevelILInstruction { + fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result { + write!( + f, + "<{} at 0x{:08}>", + core::any::type_name::(), + self.address, + ) + } +} + +impl MediumLevelILInstruction { + pub(crate) fn new(function: &MediumLevelILFunction, idx: usize) -> Self { + let op = unsafe { BNGetMediumLevelILByIndex(function.handle, idx) }; + use BNMediumLevelILOperation::*; + use MediumLevelILOperation as Op; + let info = match op.operation { + MLIL_NOP => Op::Nop(NoArgs::default()), + MLIL_NORET => Op::Noret(NoArgs::default()), + MLIL_BP => Op::Bp(NoArgs::default()), + MLIL_UNDEF => Op::Undef(NoArgs::default()), + MLIL_UNIMPL => Op::Unimpl(NoArgs::default()), + MLIL_IF => Op::If(MediumLevelILOperationIf::new( + op.operands[0] as usize, + op.operands[1], + op.operands[2], + )), + MLIL_FLOAT_CONST => Op::FloatConst(FloatConst::new(op.operands[0], op.size)), + MLIL_CONST => Op::Const(Constant::new(op.operands[0])), + MLIL_CONST_PTR => Op::ConstPtr(Constant::new(op.operands[0])), + MLIL_IMPORT => Op::Import(Constant::new(op.operands[0])), + MLIL_EXTERN_PTR => Op::ExternPtr(ExternPtr::new(op.operands[0], op.operands[1])), + MLIL_CONST_DATA => Op::ConstData(ConstData::new((op.operands[0], op.operands[1]))), + MLIL_JUMP => Op::Jump(Jump::new(op.operands[0] as usize)), + MLIL_RET_HINT => Op::RetHint(Jump::new(op.operands[0] as usize)), + MLIL_STORE_SSA => Op::StoreSsa(StoreSsa::new( + op.operands[0] as usize, + op.operands[1], + op.operands[2], + op.operands[3] as usize, + )), + MLIL_STORE_STRUCT_SSA => Op::StoreStructSsa(StoreStructSsa::new( + op.operands[0] as usize, + op.operands[1], + op.operands[2], + op.operands[3], + op.operands[4] as usize, + )), + MLIL_STORE_STRUCT => Op::StoreStruct(StoreStruct::new( + op.operands[0] as usize, + op.operands[1], + op.operands[2] as usize, + )), + MLIL_STORE => Op::Store(Store::new(op.operands[0] as usize, op.operands[1] as usize)), + MLIL_JUMP_TO => Op::JumpTo(JumpTo::new( + op.operands[0] as usize, + (op.operands[1] as usize, op.operands[2] as usize), + )), + MLIL_GOTO => Op::Goto(Goto::new(op.operands[0])), + MLIL_FREE_VAR_SLOT => Op::FreeVarSlot(FreeVarSlot::new(op.operands[0])), + MLIL_SET_VAR_FIELD => Op::SetVarField(SetVarField::new( + op.operands[0], + op.operands[1], + op.operands[2] as usize, + )), + MLIL_SET_VAR => Op::SetVar(SetVar::new(op.operands[0], op.operands[1] as usize)), + MLIL_FREE_VAR_SLOT_SSA => Op::FreeVarSlotSsa(FreeVarSlotSsa::new( + (op.operands[0], op.operands[1] as usize), + (op.operands[0], op.operands[2] as usize), + )), + MLIL_SET_VAR_SSA_FIELD => Op::SetVarSsaField(SetVarSsaField::new( + (op.operands[0], op.operands[1] as usize), + (op.operands[0], op.operands[2] as usize), + op.operands[3], + op.operands[4] as usize, + )), + MLIL_SET_VAR_ALIASED_FIELD => Op::SetVarAliasedField(SetVarSsaField::new( + (op.operands[0], op.operands[1] as usize), + (op.operands[0], op.operands[2] as usize), + op.operands[3], + op.operands[4] as usize, + )), + MLIL_SET_VAR_ALIASED => Op::SetVarAliased(SetVarAliased::new( + (op.operands[0], op.operands[1] as usize), + (op.operands[0], op.operands[2] as usize), + op.operands[3] as usize, + )), + MLIL_SET_VAR_SSA => Op::SetVarSsa(SetVarSsa::new( + (op.operands[0], op.operands[1] as usize), + op.operands[2] as usize, + )), + MLIL_VAR_PHI => Op::VarPhi(VarPhi::new( + (op.operands[0], op.operands[1] as usize), + (op.operands[2] as usize, op.operands[3] as usize), + )), + MLIL_MEM_PHI => Op::MemPhi(MemPhi::new( + op.operands[0], + (op.operands[1] as usize, op.operands[2] as usize), + )), + MLIL_VAR_SPLIT => Op::VarSplit(VarSplit::new(op.operands[0], op.operands[1])), + MLIL_SET_VAR_SPLIT => Op::SetVarSplit(SetVarSplit::new( + op.operands[0], + op.operands[1], + op.operands[2] as usize, + )), + MLIL_VAR_SPLIT_SSA => Op::VarSplitSsa(VarSplitSsa::new( + (op.operands[0], op.operands[1] as usize), + (op.operands[2], op.operands[3] as usize), + )), + MLIL_SET_VAR_SPLIT_SSA => Op::SetVarSplitSsa(SetVarSplitSsa::new( + (op.operands[0], op.operands[1] as usize), + (op.operands[2], op.operands[3] as usize), + op.operands[4] as usize, + )), + MLIL_ADD => Op::Add(BinaryOp::new( + op.operands[0] as usize, + op.operands[1] as usize, + )), + MLIL_SUB => Op::Sub(BinaryOp::new( + op.operands[0] as usize, + op.operands[1] as usize, + )), + MLIL_AND => Op::And(BinaryOp::new( + op.operands[0] as usize, + op.operands[1] as usize, + )), + MLIL_OR => Op::Or(BinaryOp::new( + op.operands[0] as usize, + op.operands[1] as usize, + )), + MLIL_XOR => Op::Xor(BinaryOp::new( + op.operands[0] as usize, + op.operands[1] as usize, + )), + MLIL_LSL => Op::Lsl(BinaryOp::new( + op.operands[0] as usize, + op.operands[1] as usize, + )), + MLIL_LSR => Op::Lsr(BinaryOp::new( + op.operands[0] as usize, + op.operands[1] as usize, + )), + MLIL_ASR => Op::Asr(BinaryOp::new( + op.operands[0] as usize, + op.operands[1] as usize, + )), + MLIL_ROL => Op::Rol(BinaryOp::new( + op.operands[0] as usize, + op.operands[1] as usize, + )), + MLIL_ROR => Op::Ror(BinaryOp::new( + op.operands[0] as usize, + op.operands[1] as usize, + )), + MLIL_MUL => Op::Mul(BinaryOp::new( + op.operands[0] as usize, + op.operands[1] as usize, + )), + MLIL_MULU_DP => Op::MuluDp(BinaryOp::new( + op.operands[0] as usize, + op.operands[1] as usize, + )), + MLIL_MULS_DP => Op::MulsDp(BinaryOp::new( + op.operands[0] as usize, + op.operands[1] as usize, + )), + MLIL_DIVU => Op::Divu(BinaryOp::new( + op.operands[0] as usize, + op.operands[1] as usize, + )), + MLIL_DIVU_DP => Op::DivuDp(BinaryOp::new( + op.operands[0] as usize, + op.operands[1] as usize, + )), + MLIL_DIVS => Op::Divs(BinaryOp::new( + op.operands[0] as usize, + op.operands[1] as usize, + )), + MLIL_DIVS_DP => Op::DivsDp(BinaryOp::new( + op.operands[0] as usize, + op.operands[1] as usize, + )), + MLIL_MODU => Op::Modu(BinaryOp::new( + op.operands[0] as usize, + op.operands[1] as usize, + )), + MLIL_MODU_DP => Op::ModuDp(BinaryOp::new( + op.operands[0] as usize, + op.operands[1] as usize, + )), + MLIL_MODS => Op::Mods(BinaryOp::new( + op.operands[0] as usize, + op.operands[1] as usize, + )), + MLIL_MODS_DP => Op::ModsDp(BinaryOp::new( + op.operands[0] as usize, + op.operands[1] as usize, + )), + MLIL_CMP_E => Op::CmpE(BinaryOp::new( + op.operands[0] as usize, + op.operands[1] as usize, + )), + MLIL_CMP_NE => Op::CmpNe(BinaryOp::new( + op.operands[0] as usize, + op.operands[1] as usize, + )), + MLIL_CMP_SLT => Op::CmpSlt(BinaryOp::new( + op.operands[0] as usize, + op.operands[1] as usize, + )), + MLIL_CMP_ULT => Op::CmpUlt(BinaryOp::new( + op.operands[0] as usize, + op.operands[1] as usize, + )), + MLIL_CMP_SLE => Op::CmpSle(BinaryOp::new( + op.operands[0] as usize, + op.operands[1] as usize, + )), + MLIL_CMP_ULE => Op::CmpUle(BinaryOp::new( + op.operands[0] as usize, + op.operands[1] as usize, + )), + MLIL_CMP_SGE => Op::CmpSge(BinaryOp::new( + op.operands[0] as usize, + op.operands[1] as usize, + )), + MLIL_CMP_UGE => Op::CmpUge(BinaryOp::new( + op.operands[0] as usize, + op.operands[1] as usize, + )), + MLIL_CMP_SGT => Op::CmpSgt(BinaryOp::new( + op.operands[0] as usize, + op.operands[1] as usize, + )), + MLIL_CMP_UGT => Op::CmpUgt(BinaryOp::new( + op.operands[0] as usize, + op.operands[1] as usize, + )), + MLIL_TEST_BIT => Op::TestBit(BinaryOp::new( + op.operands[0] as usize, + op.operands[1] as usize, + )), + MLIL_ADD_OVERFLOW => Op::AddOverflow(BinaryOp::new( + op.operands[0] as usize, + op.operands[1] as usize, + )), + MLIL_FCMP_E => Op::FcmpE(BinaryOp::new( + op.operands[0] as usize, + op.operands[1] as usize, + )), + MLIL_FCMP_NE => Op::FcmpNe(BinaryOp::new( + op.operands[0] as usize, + op.operands[1] as usize, + )), + MLIL_FCMP_LT => Op::FcmpLt(BinaryOp::new( + op.operands[0] as usize, + op.operands[1] as usize, + )), + MLIL_FCMP_LE => Op::FcmpLe(BinaryOp::new( + op.operands[0] as usize, + op.operands[1] as usize, + )), + MLIL_FCMP_GE => Op::FcmpGe(BinaryOp::new( + op.operands[0] as usize, + op.operands[1] as usize, + )), + MLIL_FCMP_GT => Op::FcmpGt(BinaryOp::new( + op.operands[0] as usize, + op.operands[1] as usize, + )), + MLIL_FCMP_O => Op::FcmpO(BinaryOp::new( + op.operands[0] as usize, + op.operands[1] as usize, + )), + MLIL_FCMP_UO => Op::FcmpUo(BinaryOp::new( + op.operands[0] as usize, + op.operands[1] as usize, + )), + MLIL_FADD => Op::Fadd(BinaryOp::new( + op.operands[0] as usize, + op.operands[1] as usize, + )), + MLIL_FSUB => Op::Fsub(BinaryOp::new( + op.operands[0] as usize, + op.operands[1] as usize, + )), + MLIL_FMUL => Op::Fmul(BinaryOp::new( + op.operands[0] as usize, + op.operands[1] as usize, + )), + MLIL_FDIV => Op::Fdiv(BinaryOp::new( + op.operands[0] as usize, + op.operands[1] as usize, + )), + MLIL_ADC => Op::Adc(BinaryOpCarry::new( + op.operands[0] as usize, + op.operands[1] as usize, + op.operands[2] as usize, + )), + MLIL_SBB => Op::Sbb(BinaryOpCarry::new( + op.operands[0] as usize, + op.operands[1] as usize, + op.operands[2] as usize, + )), + MLIL_RLC => Op::Rlc(BinaryOpCarry::new( + op.operands[0] as usize, + op.operands[1] as usize, + op.operands[2] as usize, + )), + MLIL_RRC => Op::Rrc(BinaryOpCarry::new( + op.operands[0] as usize, + op.operands[1] as usize, + op.operands[2] as usize, + )), + MLIL_CALL => Op::Call(Call::new( + (op.operands[0] as usize, op.operands[1] as usize), + op.operands[2] as usize, + (op.operands[3] as usize, op.operands[4] as usize), + )), + MLIL_TAILCALL => Op::Tailcall(Call::new( + (op.operands[0] as usize, op.operands[1] as usize), + op.operands[2] as usize, + (op.operands[3] as usize, op.operands[4] as usize), + )), + MLIL_SYSCALL => Op::Syscall(Syscall::new( + (op.operands[0] as usize, op.operands[1] as usize), + (op.operands[2] as usize, op.operands[3] as usize), + )), + MLIL_INTRINSIC => Op::Intrinsic(Intrinsic::new( + (op.operands[0] as usize, op.operands[1] as usize), + op.operands[2] as usize, + (op.operands[3] as usize, op.operands[4] as usize), + )), + MLIL_INTRINSIC_SSA => Op::IntrinsicSsa(IntrinsicSsa::new( + (op.operands[0] as usize, op.operands[1] as usize), + op.operands[2] as usize, + (op.operands[3] as usize, op.operands[4] as usize), + )), + MLIL_CALL_SSA => Op::CallSsa(CallSsa::new( + op.operands[0] as usize, + op.operands[1] as usize, + (op.operands[2] as usize, op.operands[3] as usize), + op.operands[4], + )), + MLIL_TAILCALL_SSA => Op::TailcallSsa(CallSsa::new( + op.operands[0] as usize, + op.operands[1] as usize, + (op.operands[2] as usize, op.operands[3] as usize), + op.operands[4], + )), + MLIL_CALL_UNTYPED_SSA => Op::CallUntypedSsa(CallUntypedSsa::new( + op.operands[0] as usize, + op.operands[1] as usize, + op.operands[2] as usize, + op.operands[3] as usize, + )), + MLIL_TAILCALL_UNTYPED_SSA => Op::TailcallUntypedSsa(CallUntypedSsa::new( + op.operands[0] as usize, + op.operands[1] as usize, + op.operands[2] as usize, + op.operands[3] as usize, + )), + MLIL_SYSCALL_SSA => Op::SyscallSsa(SyscallSsa::new( + op.operands[0] as usize, + (op.operands[1] as usize, op.operands[2] as usize), + op.operands[3], + )), + MLIL_SYSCALL_UNTYPED_SSA => Op::SyscallUntypedSsa(SyscallUntypedSsa::new( + op.operands[0] as usize, + op.operands[1] as usize, + op.operands[2] as usize, + )), + MLIL_CALL_UNTYPED => Op::CallUntyped(CallUntyped::new( + op.operands[0] as usize, + op.operands[1] as usize, + op.operands[2] as usize, + op.operands[3] as usize, + )), + MLIL_TAILCALL_UNTYPED => Op::TailcallUntyped(CallUntyped::new( + op.operands[0] as usize, + op.operands[1] as usize, + op.operands[2] as usize, + op.operands[3] as usize, + )), + MLIL_SYSCALL_UNTYPED => Op::SyscallUntyped(SyscallUntyped::new( + op.operands[0] as usize, + op.operands[1] as usize, + op.operands[2] as usize, + )), + MLIL_SEPARATE_PARAM_LIST => Op::SeparateParamList(SeparateParamList::new( + (op.operands[0] as usize, op.operands[1] as usize) + )), + MLIL_SHARED_PARAM_SLOT => Op::SharedParamSlot(SharedParamSlot::new( + (op.operands[0] as usize, op.operands[1] as usize) + )), + MLIL_NEG => Op::Neg(UnaryOp::new(op.operands[0] as usize)), + MLIL_NOT => Op::Not(UnaryOp::new(op.operands[0] as usize)), + MLIL_SX => Op::Sx(UnaryOp::new(op.operands[0] as usize)), + MLIL_ZX => Op::Zx(UnaryOp::new(op.operands[0] as usize)), + MLIL_LOW_PART => Op::LowPart(UnaryOp::new(op.operands[0] as usize)), + MLIL_BOOL_TO_INT => Op::BoolToInt(UnaryOp::new(op.operands[0] as usize)), + MLIL_UNIMPL_MEM => Op::UnimplMem(UnaryOp::new(op.operands[0] as usize)), + MLIL_FSQRT => Op::Fsqrt(UnaryOp::new(op.operands[0] as usize)), + MLIL_FNEG => Op::Fneg(UnaryOp::new(op.operands[0] as usize)), + MLIL_FABS => Op::Fabs(UnaryOp::new(op.operands[0] as usize)), + MLIL_FLOAT_TO_INT => Op::FloatToInt(UnaryOp::new(op.operands[0] as usize)), + MLIL_INT_TO_FLOAT => Op::IntToFloat(UnaryOp::new(op.operands[0] as usize)), + MLIL_FLOAT_CONV => Op::FloatConv(UnaryOp::new(op.operands[0] as usize)), + MLIL_ROUND_TO_INT => Op::RoundToInt(UnaryOp::new(op.operands[0] as usize)), + MLIL_FLOOR => Op::Floor(UnaryOp::new(op.operands[0] as usize)), + MLIL_CEIL => Op::Ceil(UnaryOp::new(op.operands[0] as usize)), + MLIL_FTRUNC => Op::Ftrunc(UnaryOp::new(op.operands[0] as usize)), + MLIL_LOAD => Op::Load(UnaryOp::new(op.operands[0] as usize)), + MLIL_LOAD_STRUCT => { + Op::LoadStruct(LoadStruct::new(op.operands[0] as usize, op.operands[1])) + } + MLIL_LOAD_STRUCT_SSA => Op::LoadStructSsa(LoadStructSsa::new( + op.operands[0] as usize, + op.operands[1], + op.operands[2], + )), + MLIL_LOAD_SSA => Op::LoadSsa(LoadSsa::new(op.operands[0] as usize, op.operands[1])), + MLIL_RET => Op::Ret(Ret::new((op.operands[0] as usize, op.operands[1] as usize))), + MLIL_VAR => Op::Var(Var::new(op.operands[0])), + MLIL_ADDRESS_OF => Op::AddressOf(Var::new(op.operands[0])), + MLIL_VAR_FIELD => Op::VarField(Field::new(op.operands[0], op.operands[1])), + MLIL_ADDRESS_OF_FIELD => Op::AddressOfField(Field::new(op.operands[0], op.operands[1])), + MLIL_VAR_SSA => Op::VarSsa(VarSsa::new((op.operands[0], op.operands[1] as usize))), + MLIL_VAR_ALIASED => { + Op::VarAliased(VarSsa::new((op.operands[0], op.operands[1] as usize))) + } + MLIL_VAR_SSA_FIELD => Op::VarSsaField(VarSsaField::new( + (op.operands[0], op.operands[1] as usize), + op.operands[2], + )), + MLIL_VAR_ALIASED_FIELD => Op::VarAliasedField(VarSsaField::new( + (op.operands[0], op.operands[1] as usize), + op.operands[2], + )), + MLIL_TRAP => Op::Trap(Trap::new(op.operands[0])), + // translated directly into a list for Expression or Variables + MLIL_CALL_OUTPUT | MLIL_CALL_PARAM | MLIL_CALL_PARAM_SSA | MLIL_CALL_OUTPUT_SSA => { + unreachable!() + } + }; + Self { + function: function.to_owned(), + address: op.address, + operation: info, + } + } + + pub fn function(&self) -> &MediumLevelILFunction { + &self.function + } + + pub fn address(&self) -> u64 { + self.address + } + + pub fn operation(&self) -> &MediumLevelILOperation { + &self.operation + } + + pub fn lift(&self) -> MediumLevelILLiftedInstruction { + use MediumLevelILLiftedOperation as Lifted; + use MediumLevelILOperation::*; + + let operation = match self.operation { + Nop(op) => Lifted::Nop(op), + Noret(op) => Lifted::Noret(op), + Bp(op) => Lifted::Bp(op), + Undef(op) => Lifted::Undef(op), + Unimpl(op) => Lifted::Unimpl(op), + If(op) => Lifted::If(op.lift(&self.function)), + FloatConst(op) => Lifted::FloatConst(op), + Const(op) => Lifted::Const(op), + ConstPtr(op) => Lifted::ConstPtr(op), + Import(op) => Lifted::Import(op), + ExternPtr(op) => Lifted::ExternPtr(op), + ConstData(op) => Lifted::ConstData(op.lift(&self.function)), + Jump(op) => Lifted::Jump(op.lift(&self.function)), + RetHint(op) => Lifted::RetHint(op.lift(&self.function)), + StoreSsa(op) => Lifted::StoreSsa(op.lift(&self.function)), + StoreStructSsa(op) => Lifted::StoreStructSsa(op.lift(&self.function)), + StoreStruct(op) => Lifted::StoreStruct(op.lift(&self.function)), + Store(op) => Lifted::Store(op.lift(&self.function)), + JumpTo(op) => Lifted::JumpTo(op.lift(&self.function)), + Goto(op) => Lifted::Goto(op), + FreeVarSlot(op) => Lifted::FreeVarSlot(op), + SetVarField(op) => Lifted::SetVarField(op.lift(&self.function)), + SetVar(op) => Lifted::SetVar(op.lift(&self.function)), + FreeVarSlotSsa(op) => Lifted::FreeVarSlotSsa(op.lift()), + SetVarSsaField(op) => Lifted::SetVarSsaField(op.lift(&self.function)), + SetVarAliasedField(op) => Lifted::SetVarAliasedField(op.lift(&self.function)), + SetVarAliased(op) => Lifted::SetVarAliased(op.lift(&self.function)), + SetVarSsa(op) => Lifted::SetVarSsa(op.lift(&self.function)), + VarPhi(op) => Lifted::VarPhi(op.lift(&self.function)), + MemPhi(op) => Lifted::MemPhi(op.lift(&self.function)), + VarSplit(op) => Lifted::VarSplit(op.lift()), + SetVarSplit(op) => Lifted::SetVarSplit(op.lift(&self.function)), + VarSplitSsa(op) => Lifted::VarSplitSsa(op.lift()), + SetVarSplitSsa(op) => Lifted::SetVarSplitSsa(op.lift(&self.function)), + Add(op) => Lifted::Add(op.lift(&self.function)), + Sub(op) => Lifted::Sub(op.lift(&self.function)), + And(op) => Lifted::And(op.lift(&self.function)), + Or(op) => Lifted::Or(op.lift(&self.function)), + Xor(op) => Lifted::Xor(op.lift(&self.function)), + Lsl(op) => Lifted::Lsl(op.lift(&self.function)), + Lsr(op) => Lifted::Lsr(op.lift(&self.function)), + Asr(op) => Lifted::Asr(op.lift(&self.function)), + Rol(op) => Lifted::Rol(op.lift(&self.function)), + Ror(op) => Lifted::Ror(op.lift(&self.function)), + Mul(op) => Lifted::Mul(op.lift(&self.function)), + MuluDp(op) => Lifted::MuluDp(op.lift(&self.function)), + MulsDp(op) => Lifted::MulsDp(op.lift(&self.function)), + Divu(op) => Lifted::Divu(op.lift(&self.function)), + DivuDp(op) => Lifted::DivuDp(op.lift(&self.function)), + Divs(op) => Lifted::Divs(op.lift(&self.function)), + DivsDp(op) => Lifted::DivsDp(op.lift(&self.function)), + Modu(op) => Lifted::Modu(op.lift(&self.function)), + ModuDp(op) => Lifted::ModuDp(op.lift(&self.function)), + Mods(op) => Lifted::Mods(op.lift(&self.function)), + ModsDp(op) => Lifted::ModsDp(op.lift(&self.function)), + CmpE(op) => Lifted::CmpE(op.lift(&self.function)), + CmpNe(op) => Lifted::CmpNe(op.lift(&self.function)), + CmpSlt(op) => Lifted::CmpSlt(op.lift(&self.function)), + CmpUlt(op) => Lifted::CmpUlt(op.lift(&self.function)), + CmpSle(op) => Lifted::CmpSle(op.lift(&self.function)), + CmpUle(op) => Lifted::CmpUle(op.lift(&self.function)), + CmpSge(op) => Lifted::CmpSge(op.lift(&self.function)), + CmpUge(op) => Lifted::CmpUge(op.lift(&self.function)), + CmpSgt(op) => Lifted::CmpSgt(op.lift(&self.function)), + CmpUgt(op) => Lifted::CmpUgt(op.lift(&self.function)), + TestBit(op) => Lifted::TestBit(op.lift(&self.function)), + AddOverflow(op) => Lifted::AddOverflow(op.lift(&self.function)), + FcmpE(op) => Lifted::FcmpE(op.lift(&self.function)), + FcmpNe(op) => Lifted::FcmpNe(op.lift(&self.function)), + FcmpLt(op) => Lifted::FcmpLt(op.lift(&self.function)), + FcmpLe(op) => Lifted::FcmpLe(op.lift(&self.function)), + FcmpGe(op) => Lifted::FcmpGe(op.lift(&self.function)), + FcmpGt(op) => Lifted::FcmpGt(op.lift(&self.function)), + FcmpO(op) => Lifted::FcmpO(op.lift(&self.function)), + FcmpUo(op) => Lifted::FcmpUo(op.lift(&self.function)), + Fadd(op) => Lifted::Fadd(op.lift(&self.function)), + Fsub(op) => Lifted::Fsub(op.lift(&self.function)), + Fmul(op) => Lifted::Fmul(op.lift(&self.function)), + Fdiv(op) => Lifted::Fdiv(op.lift(&self.function)), + Adc(op) => Lifted::Adc(op.lift(&self.function)), + Sbb(op) => Lifted::Sbb(op.lift(&self.function)), + Rlc(op) => Lifted::Rlc(op.lift(&self.function)), + Rrc(op) => Lifted::Rrc(op.lift(&self.function)), + Call(op) => Lifted::Call(op.lift(&self.function)), + Tailcall(op) => Lifted::Tailcall(op.lift(&self.function)), + Intrinsic(op) => Lifted::Intrinsic(op.lift(&self.function)), + Syscall(op) => Lifted::Syscall(op.lift(&self.function)), + IntrinsicSsa(op) => Lifted::IntrinsicSsa(op.lift(&self.function)), + CallSsa(op) => Lifted::CallSsa(op.lift(&self.function)), + TailcallSsa(op) => Lifted::TailcallSsa(op.lift(&self.function)), + CallUntypedSsa(op) => Lifted::CallUntypedSsa(op.lift(&self.function)), + TailcallUntypedSsa(op) => Lifted::TailcallUntypedSsa(op.lift(&self.function)), + SyscallSsa(op) => Lifted::SyscallSsa(op.lift(&self.function)), + SyscallUntypedSsa(op) => Lifted::SyscallUntypedSsa(op.lift(&self.function)), + CallUntyped(op) => Lifted::CallUntyped(op.lift(&self.function)), + TailcallUntyped(op) => Lifted::TailcallUntyped(op.lift(&self.function)), + SyscallUntyped(op) => Lifted::SyscallUntyped(op.lift(&self.function)), + SeparateParamList(op) => Lifted::SeparateParamList(op.lift(&self.function)), + SharedParamSlot(op) => Lifted::SharedParamSlot(op.lift(&self.function)), + Neg(op) => Lifted::Neg(op.lift(&self.function)), + Not(op) => Lifted::Not(op.lift(&self.function)), + Sx(op) => Lifted::Sx(op.lift(&self.function)), + Zx(op) => Lifted::Zx(op.lift(&self.function)), + LowPart(op) => Lifted::LowPart(op.lift(&self.function)), + BoolToInt(op) => Lifted::BoolToInt(op.lift(&self.function)), + UnimplMem(op) => Lifted::UnimplMem(op.lift(&self.function)), + Fsqrt(op) => Lifted::Fsqrt(op.lift(&self.function)), + Fneg(op) => Lifted::Fneg(op.lift(&self.function)), + Fabs(op) => Lifted::Fabs(op.lift(&self.function)), + FloatToInt(op) => Lifted::FloatToInt(op.lift(&self.function)), + IntToFloat(op) => Lifted::IntToFloat(op.lift(&self.function)), + FloatConv(op) => Lifted::FloatConv(op.lift(&self.function)), + RoundToInt(op) => Lifted::RoundToInt(op.lift(&self.function)), + Floor(op) => Lifted::Floor(op.lift(&self.function)), + Ceil(op) => Lifted::Ceil(op.lift(&self.function)), + Ftrunc(op) => Lifted::Ftrunc(op.lift(&self.function)), + Load(op) => Lifted::Load(op.lift(&self.function)), + LoadStruct(op) => Lifted::LoadStruct(op.lift(&self.function)), + LoadStructSsa(op) => Lifted::LoadStructSsa(op.lift(&self.function)), + LoadSsa(op) => Lifted::LoadSsa(op.lift(&self.function)), + Ret(op) => Lifted::Ret(op.lift(&self.function)), + Var(op) => Lifted::Var(op), + AddressOf(op) => Lifted::AddressOf(op), + VarField(op) => Lifted::VarField(op), + AddressOfField(op) => Lifted::AddressOfField(op), + VarSsa(op) => Lifted::VarSsa(op), + VarAliased(op) => Lifted::VarAliased(op), + VarSsaField(op) => Lifted::VarSsaField(op), + VarAliasedField(op) => Lifted::VarAliasedField(op), + Trap(op) => Lifted::Trap(op), + }; + MediumLevelILLiftedInstruction { + address: self.address, + operation, + } + } + + pub fn operands(&self) -> Box> { + use MediumLevelILOperation::*; + match &self.operation { + Nop(_op) | Noret(_op) | Bp(_op) | Undef(_op) | Unimpl(_op) => Box::new([].into_iter()), + If(op) => Box::new(op.operands(&self.function)), + FloatConst(op) => Box::new(op.operands()), + Const(op) | ConstPtr(op) | Import(op) => Box::new(op.operands()), + ExternPtr(op) => Box::new(op.operands(&self.function)), + ConstData(op) => Box::new(op.operands(&self.function)), + Jump(op) | RetHint(op) => Box::new(op.operands(&self.function)), + StoreSsa(op) => Box::new(op.operands(&self.function)), + StoreStructSsa(op) => Box::new(op.operands(&self.function)), + StoreStruct(op) => Box::new(op.operands(&self.function)), + Store(op) => Box::new(op.operands(&self.function)), + JumpTo(op) => Box::new(op.operands(&self.function)), + Goto(op) => Box::new(op.operands()), + FreeVarSlot(op) => Box::new(op.operands()), + SetVarField(op) => Box::new(op.operands(&self.function)), + SetVar(op) => Box::new(op.operands(&self.function)), + FreeVarSlotSsa(op) => Box::new(op.operands()), + SetVarSsaField(op) | SetVarAliasedField(op) => Box::new(op.operands(&self.function)), + SetVarAliased(op) => Box::new(op.operands(&self.function)), + SetVarSsa(op) => Box::new(op.operands(&self.function)), + VarPhi(op) => Box::new(op.operands(&self.function)), + MemPhi(op) => Box::new(op.operands(&self.function)), + VarSplit(op) => Box::new(op.operands()), + SetVarSplit(op) => Box::new(op.operands(&self.function)), + VarSplitSsa(op) => Box::new(op.operands()), + SetVarSplitSsa(op) => Box::new(op.operands(&self.function)), + Add(op) | Sub(op) | And(op) | Or(op) | Xor(op) | Lsl(op) | Lsr(op) | Asr(op) + | Rol(op) | Ror(op) | Mul(op) | MuluDp(op) | MulsDp(op) | Divu(op) | DivuDp(op) + | Divs(op) | DivsDp(op) | Modu(op) | ModuDp(op) | Mods(op) | ModsDp(op) | CmpE(op) + | CmpNe(op) | CmpSlt(op) | CmpUlt(op) | CmpSle(op) | CmpUle(op) | CmpSge(op) + | CmpUge(op) | CmpSgt(op) | CmpUgt(op) | TestBit(op) | AddOverflow(op) | FcmpE(op) + | FcmpNe(op) | FcmpLt(op) | FcmpLe(op) | FcmpGe(op) | FcmpGt(op) | FcmpO(op) + | FcmpUo(op) | Fadd(op) | Fsub(op) | Fmul(op) | Fdiv(op) => { + Box::new(op.operands(&self.function)) + } + Adc(op) | Sbb(op) | Rlc(op) | Rrc(op) => Box::new(op.operands(&self.function)), + Call(op) | Tailcall(op) => Box::new(op.operands(&self.function)), + Syscall(op) => Box::new(op.operands(&self.function)), + Intrinsic(op) => Box::new(op.operands(&self.function)), + IntrinsicSsa(op) => Box::new(op.operands(&self.function)), + CallSsa(op) | TailcallSsa(op) => Box::new(op.operands(&self.function)), + CallUntypedSsa(op) | TailcallUntypedSsa(op) => Box::new(op.operands(&self.function)), + SyscallSsa(op) => Box::new(op.operands(&self.function)), + SyscallUntypedSsa(op) => Box::new(op.operands(&self.function)), + CallUntyped(op) | TailcallUntyped(op) => Box::new(op.operands(&self.function)), + SyscallUntyped(op) => Box::new(op.operands(&self.function)), + SeparateParamList(op) => Box::new(op.operands(&self.function)), + SharedParamSlot(op) => Box::new(op.operands(&self.function)), + Neg(op) | Not(op) | Sx(op) | Zx(op) | LowPart(op) | BoolToInt(op) | UnimplMem(op) + | Fsqrt(op) | Fneg(op) | Fabs(op) | FloatToInt(op) | IntToFloat(op) | FloatConv(op) + | RoundToInt(op) | Floor(op) | Ceil(op) | Ftrunc(op) | Load(op) => { + Box::new(op.operands(&self.function)) + } + LoadStruct(op) => Box::new(op.operands(&self.function)), + LoadStructSsa(op) => Box::new(op.operands(&self.function)), + LoadSsa(op) => Box::new(op.operands(&self.function)), + Ret(op) => Box::new(op.operands(&self.function)), + Var(op) | AddressOf(op) => Box::new(op.operands()), + VarField(op) | AddressOfField(op) => Box::new(op.operands()), + VarSsa(op) | VarAliased(op) => Box::new(op.operands()), + VarSsaField(op) | VarAliasedField(op) => Box::new(op.operands()), + Trap(op) => Box::new(op.operands()), + } + } +} diff --git a/src/mlil/lift.rs b/src/mlil/lift.rs new file mode 100644 index 0000000..fc996cb --- /dev/null +++ b/src/mlil/lift.rs @@ -0,0 +1,141 @@ +use super::operation::*; + +#[derive(Clone, Debug, PartialEq)] +pub struct MediumLevelILLiftedInstruction { + pub address: u64, + pub operation: MediumLevelILLiftedOperation, +} + +#[derive(Clone, Debug, PartialEq)] +pub enum MediumLevelILLiftedOperation { + Nop(NoArgs), + Noret(NoArgs), + Bp(NoArgs), + Undef(NoArgs), + Unimpl(NoArgs), + If(LiftedIf), + FloatConst(FloatConst), + Const(Constant), + ConstPtr(Constant), + Import(Constant), + ExternPtr(ExternPtr), + ConstData(ConstantData), + Jump(LiftedJump), + RetHint(LiftedJump), + StoreSsa(LiftedStoreSsa), + StoreStructSsa(LiftedStoreStructSsa), + StoreStruct(LiftedStoreStruct), + Store(LiftedStore), + JumpTo(LiftedJumpTo), + Goto(Goto), + FreeVarSlot(FreeVarSlot), + SetVarField(LiftedSetVarField), + SetVar(LiftedSetVar), + FreeVarSlotSsa(FreeVarSlotSsa), + SetVarSsaField(LiftedSetVarSsaField), + SetVarAliasedField(LiftedSetVarSsaField), + SetVarAliased(LiftedSetVarAliased), + SetVarSsa(LiftedSetVarSsa), + VarPhi(LiftedVarPhi), + MemPhi(LiftedMemPhi), + VarSplit(VarSplit), + SetVarSplit(LiftedSetVarSplit), + VarSplitSsa(VarSplitSsa), + SetVarSplitSsa(LiftedSetVarSplitSsa), + Add(LiftedBinaryOp), + Sub(LiftedBinaryOp), + And(LiftedBinaryOp), + Or(LiftedBinaryOp), + Xor(LiftedBinaryOp), + Lsl(LiftedBinaryOp), + Lsr(LiftedBinaryOp), + Asr(LiftedBinaryOp), + Rol(LiftedBinaryOp), + Ror(LiftedBinaryOp), + Mul(LiftedBinaryOp), + MuluDp(LiftedBinaryOp), + MulsDp(LiftedBinaryOp), + Divu(LiftedBinaryOp), + DivuDp(LiftedBinaryOp), + Divs(LiftedBinaryOp), + DivsDp(LiftedBinaryOp), + Modu(LiftedBinaryOp), + ModuDp(LiftedBinaryOp), + Mods(LiftedBinaryOp), + ModsDp(LiftedBinaryOp), + CmpE(LiftedBinaryOp), + CmpNe(LiftedBinaryOp), + CmpSlt(LiftedBinaryOp), + CmpUlt(LiftedBinaryOp), + CmpSle(LiftedBinaryOp), + CmpUle(LiftedBinaryOp), + CmpSge(LiftedBinaryOp), + CmpUge(LiftedBinaryOp), + CmpSgt(LiftedBinaryOp), + CmpUgt(LiftedBinaryOp), + TestBit(LiftedBinaryOp), + AddOverflow(LiftedBinaryOp), + FcmpE(LiftedBinaryOp), + FcmpNe(LiftedBinaryOp), + FcmpLt(LiftedBinaryOp), + FcmpLe(LiftedBinaryOp), + FcmpGe(LiftedBinaryOp), + FcmpGt(LiftedBinaryOp), + FcmpO(LiftedBinaryOp), + FcmpUo(LiftedBinaryOp), + Fadd(LiftedBinaryOp), + Fsub(LiftedBinaryOp), + Fmul(LiftedBinaryOp), + Fdiv(LiftedBinaryOp), + Adc(LiftedBinaryOpCarry), + Sbb(LiftedBinaryOpCarry), + Rlc(LiftedBinaryOpCarry), + Rrc(LiftedBinaryOpCarry), + Call(LiftedCall), + Tailcall(LiftedCall), + Intrinsic(LiftedInnerCall), + Syscall(LiftedInnerCall), + IntrinsicSsa(LiftedIntrinsicSsa), + CallSsa(LiftedCallSsa), + TailcallSsa(LiftedCallSsa), + CallUntypedSsa(LiftedCallUntypedSsa), + TailcallUntypedSsa(LiftedCallUntypedSsa), + SyscallSsa(LiftedSyscallSsa), + SyscallUntypedSsa(LiftedSyscallUntypedSsa), + CallUntyped(LiftedCallUntyped), + TailcallUntyped(LiftedCallUntyped), + SyscallUntyped(LiftedSyscallUntyped), + SeparateParamList(LiftedSeparateParamList), + SharedParamSlot(LiftedSharedParamSlot), + Neg(LiftedUnaryOp), + Not(LiftedUnaryOp), + Sx(LiftedUnaryOp), + Zx(LiftedUnaryOp), + LowPart(LiftedUnaryOp), + BoolToInt(LiftedUnaryOp), + UnimplMem(LiftedUnaryOp), + Fsqrt(LiftedUnaryOp), + Fneg(LiftedUnaryOp), + Fabs(LiftedUnaryOp), + FloatToInt(LiftedUnaryOp), + IntToFloat(LiftedUnaryOp), + FloatConv(LiftedUnaryOp), + RoundToInt(LiftedUnaryOp), + Floor(LiftedUnaryOp), + Ceil(LiftedUnaryOp), + Ftrunc(LiftedUnaryOp), + Load(LiftedUnaryOp), + LoadStruct(LiftedLoadStruct), + LoadStructSsa(LiftedLoadStructSsa), + LoadSsa(LiftedLoadSsa), + Ret(LiftedRet), + Var(Var), + AddressOf(Var), + VarField(Field), + AddressOfField(Field), + VarSsa(VarSsa), + VarAliased(VarSsa), + VarSsaField(VarSsaField), + VarAliasedField(VarSsaField), + Trap(Trap), +} diff --git a/src/mlil/mod.rs b/src/mlil/mod.rs new file mode 100644 index 0000000..8a9103a --- /dev/null +++ b/src/mlil/mod.rs @@ -0,0 +1,10 @@ +mod block; +mod function; +mod instruction; +mod lift; +pub mod operation; + +pub use self::block::*; +pub use self::function::*; +pub use self::instruction::*; +pub use self::lift::*; diff --git a/src/mlil/operation.rs b/src/mlil/operation.rs new file mode 100644 index 0000000..880155e --- /dev/null +++ b/src/mlil/operation.rs @@ -0,0 +1,2238 @@ +use std::collections::HashMap; + +use binaryninjacore_sys::BNFromVariableIdentifier; +use binaryninjacore_sys::BNGetMediumLevelILByIndex; +use binaryninjacore_sys::BNMediumLevelILInstruction; +use binaryninjacore_sys::BNMediumLevelILOperation; + +use crate::rc::Ref; +use crate::types::{SSAVariable, Variable}; + +use super::{MediumLevelILFunction, MediumLevelILInstruction, MediumLevelILLiftedInstruction}; + +pub enum MediumLevelILOperand { + //TODO + //ConstantData(!), + //TODO + //Intrinsic(!), + Expr(MediumLevelILInstruction), + ExprList(OperandExprList), + Float(f64), + Int(u64), + IntList(OperandList), + TargetMap(OperandDubleList), + Var(Variable), + VarList(OperandVariableList), + VarSsa(SSAVariable), + VarSsaList(OperandSSAVariableList), +} + +// Iterator for the get_list, this is better then a inline iterator because +// this also implement ExactSizeIterator, what a inline iterator does not. +pub struct OperandList { + function: Ref, + remaining: usize, + next_node_idx: Option, + + current_node: core::array::IntoIter, +} +impl OperandList { + fn new(function: &MediumLevelILFunction, idx: usize, number: usize) -> Self { + // alternative to core::array::IntoIter::empty(); + let mut iter = [0; 4].into_iter(); + for _ in 0..4 { + let _ = iter.next(); + } + Self { + function: function.to_owned(), + remaining: number, + next_node_idx: Some(idx), + current_node: iter, + } + } + fn duble(self) -> OperandDubleList { + assert_eq!(self.len() % 2, 0); + OperandDubleList(self) + } + fn map_expr(self) -> OperandExprList { + OperandExprList(self) + } + fn map_var(self) -> OperandVariableList { + OperandVariableList(self) + } + fn map_ssa_var(self) -> OperandSSAVariableList { + OperandSSAVariableList(self.duble()) + } +} +impl Iterator for OperandList { + type Item = u64; + + fn next(&mut self) -> Option { + // if there is an item in this node, return it + if let Some(current_node) = self.current_node.next() { + return Some(current_node); + } + + // no more items to fetch + if self.remaining == 0 { + return None; + } + + // otherwise get the next node + let next_idx = self.next_node_idx?; + let node = unsafe { BNGetMediumLevelILByIndex(self.function.handle, next_idx) }; + assert_eq!(node.operation, BNMediumLevelILOperation::MLIL_UNDEF); + + // each node contains at most 4, the last is reserved to next node idx + let consume = if self.remaining > 4 { + // there are more nodes after this one + self.next_node_idx = Some(node.operands[4] as usize); + self.remaining -= 4; + &node.operands[0..4] + } else { + // last part of the list, there is no next node + self.next_node_idx = None; + let nodes = &node.operands[0..self.remaining]; + self.remaining = 0; + nodes + }; + // the iter need to have a space of 4, but we may have less then that, + // solution is create a dummy elements at the start and discard it + let mut nodes = [0; 4]; + let dummy_values = 4 - consume.len(); + nodes[dummy_values..4].copy_from_slice(consume); + self.current_node = nodes.into_iter(); + for _ in 0..dummy_values { + let _ = self.current_node.next(); + } + + self.current_node.next() + } + + fn size_hint(&self) -> (usize, Option) { + (self.len(), Some(self.len())) + } +} +impl ExactSizeIterator for OperandList { + fn len(&self) -> usize { + self.remaining + self.current_node.len() + } +} + +// Iterator similar to OperationList, but returns two elements +pub struct OperandDubleList(OperandList); +impl Iterator for OperandDubleList { + type Item = (u64, u64); + + fn next(&mut self) -> Option { + let first = self.0.next()?; + let second = self.0.next().unwrap(); + Some((first, second)) + } + + fn size_hint(&self) -> (usize, Option) { + (self.len(), Some(self.len())) + } +} +impl ExactSizeIterator for OperandDubleList { + fn len(&self) -> usize { + self.0.len() / 2 + } +} + +pub struct OperandExprList(OperandList); +impl Iterator for OperandExprList { + type Item = MediumLevelILInstruction; + + fn next(&mut self) -> Option { + self.0 + .next() + .map(|idx| get_operation(&self.0.function, idx as usize)) + } + + fn size_hint(&self) -> (usize, Option) { + (self.0.len(), Some(self.0.len())) + } +} +impl ExactSizeIterator for OperandExprList { + fn len(&self) -> usize { + self.0.len() + } +} + +pub struct OperandVariableList(OperandList); +impl Iterator for OperandVariableList { + type Item = Variable; + + fn next(&mut self) -> Option { + self.0.next().map(get_var) + } + + fn size_hint(&self) -> (usize, Option) { + (self.0.len(), Some(self.0.len())) + } +} +impl ExactSizeIterator for OperandVariableList { + fn len(&self) -> usize { + self.0.len() + } +} + +pub struct OperandSSAVariableList(OperandDubleList); +impl Iterator for OperandSSAVariableList { + type Item = SSAVariable; + + fn next(&mut self) -> Option { + self.0.next().map(|(id, version)| { + let raw = unsafe { BNFromVariableIdentifier(id) }; + let var = unsafe { Variable::from_raw(raw) }; + SSAVariable::new(var, version as usize) + }) + } + + fn size_hint(&self) -> (usize, Option) { + (self.len(), Some(self.len())) + } +} +impl ExactSizeIterator for OperandSSAVariableList { + fn len(&self) -> usize { + self.0.len() + } +} + +fn get_float(value: u64, size: usize) -> f64 { + match size { + 4 => f32::from_bits(value as u32) as f64, + 8 => f64::from_bits(value), + // TODO how to handle this value? + size => todo!("float size {}", size), + } +} + +// TODO implement ConstantData +fn get_constant_data( + _function: &MediumLevelILFunction, + _value: u64, + _state: u64, + _size: usize, +) -> ! { + todo!() +} + +// TODO implement Intrinsic +fn get_intrinsic(_function: &MediumLevelILFunction, _idx: usize) -> ! { + todo!() +} + +fn get_operation(function: &MediumLevelILFunction, idx: usize) -> MediumLevelILInstruction { + function.instruction_from_idx(idx) +} + +fn get_raw_operation(function: &MediumLevelILFunction, idx: usize) -> BNMediumLevelILInstruction { + unsafe { BNGetMediumLevelILByIndex(function.handle, idx) } +} + +fn get_var(id: u64) -> Variable { + unsafe { Variable::from_raw(BNFromVariableIdentifier(id)) } +} + +fn get_var_ssa(id: u64, version: usize) -> SSAVariable { + let raw = unsafe { BNFromVariableIdentifier(id) }; + let var = unsafe { Variable::from_raw(raw) }; + SSAVariable::new(var, version) +} + +fn get_call_list( + function: &MediumLevelILFunction, + op_type: BNMediumLevelILOperation, + idx: usize, +) -> OperandVariableList { + let op = unsafe { BNGetMediumLevelILByIndex(function.handle, idx) }; + assert_eq!(op.operation, op_type); + OperandList::new(function, op.operands[1] as usize, op.operands[0] as usize).map_var() +} + +fn get_call_exprs( + function: &MediumLevelILFunction, + op_type: BNMediumLevelILOperation, + idx: usize, +) -> OperandExprList { + let op = unsafe { BNGetMediumLevelILByIndex(function.handle, idx) }; + assert_eq!(op.operation, op_type); + OperandList::new(function, op.operands[1] as usize, op.operands[0] as usize).map_expr() +} + +fn get_call_output(function: &MediumLevelILFunction, idx: usize) -> OperandVariableList { + get_call_list(function, BNMediumLevelILOperation::MLIL_CALL_OUTPUT, idx) +} + +fn get_call_params(function: &MediumLevelILFunction, idx: usize) -> OperandExprList { + get_call_exprs(function, BNMediumLevelILOperation::MLIL_CALL_PARAM, idx) +} + +fn get_call_list_ssa( + function: &MediumLevelILFunction, + op_type: BNMediumLevelILOperation, + idx: usize, +) -> OperandSSAVariableList { + let op = get_raw_operation(function, idx); + assert_eq!(op.operation, op_type); + OperandList::new(function, op.operands[2] as usize, op.operands[1] as usize).map_ssa_var() +} + +fn get_call_exprs_ssa( + function: &MediumLevelILFunction, + op_type: BNMediumLevelILOperation, + idx: usize, +) -> OperandExprList { + let op = get_raw_operation(function, idx); + assert_eq!(op.operation, op_type); + OperandList::new(function, op.operands[2] as usize, op.operands[1] as usize).map_expr() +} + +fn get_call_output_ssa(function: &MediumLevelILFunction, idx: usize) -> OperandSSAVariableList { + get_call_list_ssa( + function, + BNMediumLevelILOperation::MLIL_CALL_OUTPUT_SSA, + idx, + ) +} + +fn get_call_params_ssa(function: &MediumLevelILFunction, idx: usize) -> OperandExprList { + get_call_exprs_ssa(function, BNMediumLevelILOperation::MLIL_CALL_PARAM_SSA, idx) +} + +// NOP, NORET, BP, UNDEF, UNIMPL +#[derive(Default, Copy, Clone, Debug, Hash, PartialEq, Eq)] +pub struct NoArgs {} + +// IF +#[derive(Copy, Clone)] +pub struct MediumLevelILOperationIf { + condition: usize, + dest_true: u64, + dest_false: u64, +} +#[derive(Clone, Debug, PartialEq)] +pub struct LiftedIf { + pub condition: Box, + pub dest_true: u64, + pub dest_false: u64, +} +impl MediumLevelILOperationIf { + pub fn new(condition: usize, dest_true: u64, dest_false: u64) -> Self { + Self { + condition, + dest_true, + dest_false, + } + } + pub fn condition(&self, function: &MediumLevelILFunction) -> MediumLevelILInstruction { + get_operation(function, self.condition) + } + pub fn dest_true(&self) -> u64 { + self.dest_true + } + pub fn dest_false(&self) -> u64 { + self.dest_false + } + pub fn lift(&self, function: &MediumLevelILFunction) -> LiftedIf { + LiftedIf { + condition: Box::new(self.condition(function).lift()), + dest_true: self.dest_true(), + dest_false: self.dest_false(), + } + } + pub fn operands( + &self, + function: &MediumLevelILFunction, + ) -> impl Iterator { + use MediumLevelILOperand::*; + [ + ("condition", Expr(self.condition(function))), + ("dest_true", Int(self.dest_true())), + ("dest_false", Int(self.dest_false())), + ] + .into_iter() + } +} + +// FLOAT_CONST +#[derive(Copy, Clone, Debug, PartialEq)] +pub struct FloatConst { + pub constant: f64, +} +impl FloatConst { + pub fn new(constant: u64, size: usize) -> Self { + Self { + constant: get_float(constant, size), + } + } + pub fn constant(&self) -> f64 { + self.constant + } + pub fn operands(&self) -> impl Iterator { + [("constant", MediumLevelILOperand::Float(self.constant()))].into_iter() + } +} + +// CONST, CONST_PTR, IMPORT +#[derive(Copy, Clone, Debug, Hash, PartialEq, Eq)] +pub struct Constant { + pub constant: u64, +} +impl Constant { + pub fn new(constant: u64) -> Self { + Self { constant } + } + pub fn constant(&self) -> u64 { + self.constant + } + pub fn operands(&self) -> impl Iterator { + [("constant", MediumLevelILOperand::Int(self.constant()))].into_iter() + } +} + +// EXTERN_PTR +#[derive(Copy, Clone, Debug, Hash, PartialEq, Eq)] +pub struct ExternPtr { + pub constant: u64, + pub offset: u64, +} +impl ExternPtr { + pub fn new(constant: u64, offset: u64) -> Self { + Self { constant, offset } + } + pub fn constant(&self) -> u64 { + self.constant + } + pub fn offset(&self) -> u64 { + self.offset + } + pub fn operands( + &self, + _function: &MediumLevelILFunction, + ) -> impl Iterator { + [ + ("constant", MediumLevelILOperand::Int(self.constant())), + ("offset", MediumLevelILOperand::Int(self.offset())), + ] + .into_iter() + } +} + +// CONST_DATA +#[derive(Copy, Clone)] +pub struct ConstData { + constant_data: (u64, u64), +} +#[derive(Clone, Debug, Hash, PartialEq, Eq)] +pub struct ConstantData { + //pub constant_data: !, +} +impl ConstData { + pub fn new(constant_data: (u64, u64)) -> Self { + Self { constant_data } + } + pub fn constant_data(&self, function: &MediumLevelILFunction, size: usize) -> ! { + get_constant_data(function, self.constant_data.0, self.constant_data.1, size) + } + pub fn lift(&self, _function: &MediumLevelILFunction) -> ConstantData { + ConstantData { + // TODO + } + } + pub fn operands( + &self, + _function: &MediumLevelILFunction, + ) -> impl Iterator { + // TODO + [ + //("contant_data", MediumLevelILOperand::ConstData(_self.constant_data(function, self.size))) + ] + .into_iter() + } +} + +// JUMP, RET_HINT +#[derive(Copy, Clone)] +pub struct Jump { + dest: usize, +} +#[derive(Clone, Debug, PartialEq)] +pub struct LiftedJump { + pub dest: Box, +} +impl Jump { + pub fn new(dest: usize) -> Self { + Self { dest } + } + pub fn dest(&self, function: &MediumLevelILFunction) -> MediumLevelILInstruction { + get_operation(function, self.dest) + } + pub fn lift(&self, function: &MediumLevelILFunction) -> LiftedJump { + LiftedJump { + dest: Box::new(self.dest(function).lift()), + } + } + pub fn operands( + &self, + function: &MediumLevelILFunction, + ) -> impl Iterator { + [("dest", MediumLevelILOperand::Expr(self.dest(function)))].into_iter() + } +} + +// STORE_SSA +#[derive(Copy, Clone)] +pub struct StoreSsa { + dest: usize, + dest_memory: u64, + src_memory: u64, + src: usize, +} +#[derive(Clone, Debug, PartialEq)] +pub struct LiftedStoreSsa { + pub dest: Box, + pub dest_memory: u64, + pub src_memory: u64, + pub src: Box, +} +impl StoreSsa { + pub fn new(dest: usize, dest_memory: u64, src_memory: u64, src: usize) -> Self { + Self { + dest, + dest_memory, + src_memory, + src, + } + } + pub fn dest(&self, function: &MediumLevelILFunction) -> MediumLevelILInstruction { + get_operation(function, self.dest) + } + pub fn dest_memory(&self) -> u64 { + self.dest_memory + } + pub fn src_memory(&self) -> u64 { + self.src_memory + } + pub fn src(&self, function: &MediumLevelILFunction) -> MediumLevelILInstruction { + get_operation(function, self.src) + } + pub fn lift(&self, function: &MediumLevelILFunction) -> LiftedStoreSsa { + LiftedStoreSsa { + dest: Box::new(self.dest(function).lift()), + dest_memory: self.dest_memory(), + src_memory: self.src_memory(), + src: Box::new(self.src(function).lift()), + } + } + pub fn operands( + &self, + function: &MediumLevelILFunction, + ) -> impl Iterator { + [ + ("dest", MediumLevelILOperand::Expr(self.dest(function))), + ("dest_memory", MediumLevelILOperand::Int(self.dest_memory())), + ("src_memory", MediumLevelILOperand::Int(self.src_memory())), + ("src", MediumLevelILOperand::Expr(self.src(function))), + ] + .into_iter() + } +} + +// STORE_STRUCT_SSA +#[derive(Copy, Clone)] +pub struct StoreStructSsa { + dest: usize, + offset: u64, + dest_memory: u64, + src_memory: u64, + src: usize, +} +#[derive(Clone, Debug, PartialEq)] +pub struct LiftedStoreStructSsa { + pub dest: Box, + pub offset: u64, + pub dest_memory: u64, + pub src_memory: u64, + pub src: Box, +} +impl StoreStructSsa { + pub fn new(dest: usize, offset: u64, dest_memory: u64, src_memory: u64, src: usize) -> Self { + Self { + dest, + offset, + dest_memory, + src_memory, + src, + } + } + pub fn dest(&self, function: &MediumLevelILFunction) -> MediumLevelILInstruction { + get_operation(function, self.dest) + } + pub fn offset(&self) -> u64 { + self.offset + } + pub fn dest_memory(&self) -> u64 { + self.dest_memory + } + pub fn src_memory(&self) -> u64 { + self.src_memory + } + pub fn src(&self, function: &MediumLevelILFunction) -> MediumLevelILInstruction { + get_operation(function, self.src) + } + pub fn lift(&self, function: &MediumLevelILFunction) -> LiftedStoreStructSsa { + LiftedStoreStructSsa { + dest: Box::new(self.dest(function).lift()), + offset: self.offset(), + dest_memory: self.dest_memory(), + src_memory: self.src_memory(), + src: Box::new(self.src(function).lift()), + } + } + pub fn operands( + &self, + function: &MediumLevelILFunction, + ) -> impl Iterator { + [ + ("dest", MediumLevelILOperand::Expr(self.dest(function))), + ("offset", MediumLevelILOperand::Int(self.offset())), + ("dest_memory", MediumLevelILOperand::Int(self.dest_memory())), + ("src_memory", MediumLevelILOperand::Int(self.src_memory())), + ("src", MediumLevelILOperand::Expr(self.src(function))), + ] + .into_iter() + } +} + +// STORE_STRUCT +#[derive(Copy, Clone)] +pub struct StoreStruct { + dest: usize, + offset: u64, + src: usize, +} +#[derive(Clone, Debug, PartialEq)] +pub struct LiftedStoreStruct { + pub dest: Box, + pub offset: u64, + pub src: Box, +} +impl StoreStruct { + pub fn new(dest: usize, offset: u64, src: usize) -> Self { + Self { dest, offset, src } + } + pub fn dest(&self, function: &MediumLevelILFunction) -> MediumLevelILInstruction { + get_operation(function, self.dest) + } + pub fn offset(&self) -> u64 { + self.offset + } + pub fn src(&self, function: &MediumLevelILFunction) -> MediumLevelILInstruction { + get_operation(function, self.src) + } + pub fn lift(&self, function: &MediumLevelILFunction) -> LiftedStoreStruct { + LiftedStoreStruct { + dest: Box::new(self.dest(function).lift()), + offset: self.offset(), + src: Box::new(self.src(function).lift()), + } + } + pub fn operands( + &self, + function: &MediumLevelILFunction, + ) -> impl Iterator { + [ + ("dest", MediumLevelILOperand::Expr(self.dest(function))), + ("offset", MediumLevelILOperand::Int(self.offset())), + ("src", MediumLevelILOperand::Expr(self.src(function))), + ] + .into_iter() + } +} + +// STORE +#[derive(Copy, Clone)] +pub struct Store { + dest: usize, + src: usize, +} +#[derive(Clone, Debug, PartialEq)] +pub struct LiftedStore { + pub dest: Box, + pub src: Box, +} +impl Store { + pub fn new(dest: usize, src: usize) -> Self { + Self { dest, src } + } + pub fn dest(&self, function: &MediumLevelILFunction) -> MediumLevelILInstruction { + get_operation(function, self.dest) + } + pub fn src(&self, function: &MediumLevelILFunction) -> MediumLevelILInstruction { + get_operation(function, self.src) + } + pub fn lift(&self, function: &MediumLevelILFunction) -> LiftedStore { + LiftedStore { + dest: Box::new(self.dest(function).lift()), + src: Box::new(self.src(function).lift()), + } + } + pub fn operands( + &self, + function: &MediumLevelILFunction, + ) -> impl Iterator { + [ + ("dest", MediumLevelILOperand::Expr(self.dest(function))), + ("src", MediumLevelILOperand::Expr(self.src(function))), + ] + .into_iter() + } +} + +// JUMP_TO +#[derive(Copy, Clone)] +pub struct JumpTo { + dest: usize, + targets: (usize, usize), +} +#[derive(Clone, Debug, PartialEq)] +pub struct LiftedJumpTo { + pub dest: Box, + pub targets: HashMap, +} +impl JumpTo { + pub fn new(dest: usize, targets: (usize, usize)) -> Self { + Self { dest, targets } + } + pub fn dest(&self, function: &MediumLevelILFunction) -> MediumLevelILInstruction { + get_operation(function, self.dest) + } + pub fn targets(&self, function: &MediumLevelILFunction) -> OperandDubleList { + OperandList::new(function, self.targets.1, self.targets.0).duble() + } + pub fn lift(&self, function: &MediumLevelILFunction) -> LiftedJumpTo { + LiftedJumpTo { + dest: Box::new(self.dest(function).lift()), + targets: self.targets(function).collect(), + } + } + pub fn operands( + &self, + function: &MediumLevelILFunction, + ) -> impl Iterator { + use MediumLevelILOperand::*; + [ + ("dest", Expr(self.dest(function))), + ("targets", TargetMap(self.targets(function))), + ] + .into_iter() + } +} + +// GOTO +#[derive(Clone, Copy, Debug, Hash, PartialEq, Eq)] +pub struct Goto { + pub dest: u64, +} +impl Goto { + pub fn new(dest: u64) -> Self { + Self { dest } + } + pub fn dest(&self) -> u64 { + self.dest + } + pub fn operands(&self) -> impl Iterator { + [("dest", MediumLevelILOperand::Int(self.dest()))].into_iter() + } +} + +// FREE_VAR_SLOT +#[derive(Copy, Clone, Debug, Hash, PartialEq, Eq)] +pub struct FreeVarSlot { + pub dest: Variable, +} +impl FreeVarSlot { + pub fn new(dest: u64) -> Self { + Self { + dest: get_var(dest), + } + } + pub fn dest(&self) -> Variable { + self.dest + } + pub fn operands(&self) -> impl Iterator { + [("dest", MediumLevelILOperand::Var(self.dest()))].into_iter() + } +} + +// SET_VAR_FIELD +#[derive(Copy, Clone)] +pub struct SetVarField { + dest: u64, + offset: u64, + src: usize, +} +#[derive(Clone, Debug, PartialEq)] +pub struct LiftedSetVarField { + pub dest: Variable, + pub offset: u64, + pub src: Box, +} +impl SetVarField { + pub fn new(dest: u64, offset: u64, src: usize) -> Self { + Self { dest, offset, src } + } + pub fn dest(&self) -> Variable { + get_var(self.dest) + } + pub fn offset(&self) -> u64 { + self.offset + } + pub fn src(&self, function: &MediumLevelILFunction) -> MediumLevelILInstruction { + get_operation(function, self.src) + } + pub fn lift(&self, function: &MediumLevelILFunction) -> LiftedSetVarField { + LiftedSetVarField { + dest: self.dest(), + offset: self.offset(), + src: Box::new(self.src(function).lift()), + } + } + pub fn operands( + &self, + function: &MediumLevelILFunction, + ) -> impl Iterator { + [ + ("dest", MediumLevelILOperand::Var(self.dest())), + ("offset", MediumLevelILOperand::Int(self.offset())), + ("src", MediumLevelILOperand::Expr(self.src(function))), + ] + .into_iter() + } +} + +// SET_VAR +#[derive(Copy, Clone)] +pub struct SetVar { + dest: u64, + src: usize, +} +#[derive(Clone, Debug, PartialEq)] +pub struct LiftedSetVar { + pub dest: Variable, + pub src: Box, +} +impl SetVar { + pub fn new(dest: u64, src: usize) -> Self { + Self { dest, src } + } + pub fn dest(&self) -> Variable { + get_var(self.dest) + } + pub fn src(&self, function: &MediumLevelILFunction) -> MediumLevelILInstruction { + get_operation(function, self.src) + } + pub fn lift(&self, function: &MediumLevelILFunction) -> LiftedSetVar { + LiftedSetVar { + dest: self.dest(), + src: Box::new(self.src(function).lift()), + } + } + pub fn operands( + &self, + function: &MediumLevelILFunction, + ) -> impl Iterator { + [ + ("dest", MediumLevelILOperand::Var(self.dest())), + ("src", MediumLevelILOperand::Expr(self.src(function))), + ] + .into_iter() + } +} + +// FREE_VAR_SLOT_SSA +#[derive(Copy, Clone, Debug, Hash, PartialEq, Eq)] +pub struct FreeVarSlotSsa { + pub dest: SSAVariable, + pub prev: SSAVariable, +} +impl FreeVarSlotSsa { + pub fn new(dest: (u64, usize), prev: (u64, usize)) -> Self { + Self { + dest: get_var_ssa(dest.0, dest.1), + prev: get_var_ssa(prev.0, prev.1), + } + } + pub fn dest(&self) -> SSAVariable { + self.dest + } + pub fn prev(&self) -> SSAVariable { + self.prev + } + pub fn lift(self) -> FreeVarSlotSsa { + FreeVarSlotSsa { + dest: self.dest(), + prev: self.prev(), + } + } + pub fn operands(&self) -> impl Iterator { + [ + ("dest", MediumLevelILOperand::VarSsa(self.dest())), + ("prev", MediumLevelILOperand::VarSsa(self.prev())), + ] + .into_iter() + } +} + +// SET_VAR_SSA_FIELD, SET_VAR_ALIASED_FIELD +#[derive(Copy, Clone)] +pub struct SetVarSsaField { + dest: (u64, usize), + prev: (u64, usize), + offset: u64, + src: usize, +} +#[derive(Clone, Debug, PartialEq)] +pub struct LiftedSetVarSsaField { + pub dest: SSAVariable, + pub prev: SSAVariable, + pub offset: u64, + pub src: Box, +} +impl SetVarSsaField { + pub fn new(dest: (u64, usize), prev: (u64, usize), offset: u64, src: usize) -> Self { + Self { + dest, + prev, + offset, + src, + } + } + pub fn dest(&self) -> SSAVariable { + get_var_ssa(self.dest.0, self.dest.1) + } + pub fn prev(&self) -> SSAVariable { + get_var_ssa(self.prev.0, self.prev.1) + } + pub fn offset(&self) -> u64 { + self.offset + } + pub fn src(&self, function: &MediumLevelILFunction) -> MediumLevelILInstruction { + get_operation(function, self.src) + } + pub fn lift(&self, function: &MediumLevelILFunction) -> LiftedSetVarSsaField { + LiftedSetVarSsaField { + dest: self.dest(), + prev: self.prev(), + offset: self.offset(), + src: Box::new(self.src(function).lift()), + } + } + pub fn operands( + &self, + function: &MediumLevelILFunction, + ) -> impl Iterator { + [ + ("dest", MediumLevelILOperand::VarSsa(self.dest())), + ("prev", MediumLevelILOperand::VarSsa(self.prev())), + ("offset", MediumLevelILOperand::Int(self.offset())), + ("src", MediumLevelILOperand::Expr(self.src(function))), + ] + .into_iter() + } +} + +// SET_VAR_ALIASED +#[derive(Copy, Clone)] +pub struct SetVarAliased { + dest: (u64, usize), + prev: (u64, usize), + src: usize, +} +#[derive(Clone, Debug, PartialEq)] +pub struct LiftedSetVarAliased { + pub dest: SSAVariable, + pub prev: SSAVariable, + pub src: Box, +} +impl SetVarAliased { + pub fn new(dest: (u64, usize), prev: (u64, usize), src: usize) -> Self { + Self { dest, prev, src } + } + pub fn dest(&self) -> SSAVariable { + get_var_ssa(self.dest.0, self.dest.1) + } + pub fn prev(&self) -> SSAVariable { + get_var_ssa(self.prev.0, self.prev.1) + } + pub fn src(&self, function: &MediumLevelILFunction) -> MediumLevelILInstruction { + get_operation(function, self.src) + } + pub fn lift(&self, function: &MediumLevelILFunction) -> LiftedSetVarAliased { + LiftedSetVarAliased { + dest: self.dest(), + prev: self.prev(), + src: Box::new(self.src(function).lift()), + } + } + pub fn operands( + &self, + function: &MediumLevelILFunction, + ) -> impl Iterator { + [ + ("dest", MediumLevelILOperand::VarSsa(self.dest())), + ("prev", MediumLevelILOperand::VarSsa(self.prev())), + ("src", MediumLevelILOperand::Expr(self.src(function))), + ] + .into_iter() + } +} + +// SET_VAR_SSA +#[derive(Copy, Clone)] +pub struct SetVarSsa { + dest: (u64, usize), + src: usize, +} +#[derive(Clone, Debug, PartialEq)] +pub struct LiftedSetVarSsa { + pub dest: SSAVariable, + pub src: Box, +} +impl SetVarSsa { + pub fn new(dest: (u64, usize), src: usize) -> Self { + Self { dest, src } + } + pub fn dest(&self) -> SSAVariable { + get_var_ssa(self.dest.0, self.dest.1) + } + pub fn src(&self, function: &MediumLevelILFunction) -> MediumLevelILInstruction { + get_operation(function, self.src) + } + pub fn lift(&self, function: &MediumLevelILFunction) -> LiftedSetVarSsa { + LiftedSetVarSsa { + dest: self.dest(), + src: Box::new(self.src(function).lift()), + } + } + pub fn operands( + &self, + function: &MediumLevelILFunction, + ) -> impl Iterator { + [ + ("dest", MediumLevelILOperand::VarSsa(self.dest())), + ("src", MediumLevelILOperand::Expr(self.src(function))), + ] + .into_iter() + } +} + +// VAR_PHI +#[derive(Copy, Clone)] +pub struct VarPhi { + dest: (u64, usize), + src: (usize, usize), +} +#[derive(Clone, Debug, Hash, PartialEq, Eq)] +pub struct LiftedVarPhi { + pub dest: SSAVariable, + pub src: Vec, +} +impl VarPhi { + pub fn new(dest: (u64, usize), src: (usize, usize)) -> Self { + Self { dest, src } + } + pub fn dest(&self) -> SSAVariable { + get_var_ssa(self.dest.0, self.dest.1) + } + pub fn src(&self, function: &MediumLevelILFunction) -> OperandSSAVariableList { + OperandList::new(function, self.src.1, self.src.0).map_ssa_var() + } + pub fn lift(&self, function: &MediumLevelILFunction) -> LiftedVarPhi { + LiftedVarPhi { + dest: self.dest(), + src: self.src(function).collect(), + } + } + pub fn operands( + &self, + function: &MediumLevelILFunction, + ) -> impl Iterator { + [ + ("dest", MediumLevelILOperand::VarSsa(self.dest())), + ("src", MediumLevelILOperand::VarSsaList(self.src(function))), + ] + .into_iter() + } +} + +// MEM_PHI +#[derive(Copy, Clone)] +pub struct MemPhi { + dest_memory: u64, + src_memory: (usize, usize), +} +#[derive(Clone, Debug, Hash, PartialEq, Eq)] +pub struct LiftedMemPhi { + pub dest_memory: u64, + pub src_memory: Vec, +} +impl MemPhi { + pub fn new(dest_memory: u64, src_memory: (usize, usize)) -> Self { + Self { + dest_memory, + src_memory, + } + } + pub fn dest_memory(&self) -> u64 { + self.dest_memory + } + pub fn src_memory(&self, function: &MediumLevelILFunction) -> OperandList { + OperandList::new(function, self.src_memory.1, self.src_memory.0) + } + pub fn lift(&self, function: &MediumLevelILFunction) -> LiftedMemPhi { + LiftedMemPhi { + dest_memory: self.dest_memory(), + src_memory: self.src_memory(function).collect(), + } + } + pub fn operands( + &self, + function: &MediumLevelILFunction, + ) -> impl Iterator { + use MediumLevelILOperand::*; + [ + ("dest_memory", Int(self.dest_memory())), + ("src_memory", IntList(self.src_memory(function))), + ] + .into_iter() + } +} + +// VAR_SPLIT +#[derive(Copy, Clone, Debug, Hash, PartialEq, Eq)] +pub struct VarSplit { + pub high: Variable, + pub low: Variable, +} +impl VarSplit { + pub fn new(high: u64, low: u64) -> Self { + Self { + high: get_var(high), + low: get_var(low), + } + } + pub fn high(&self) -> Variable { + self.high + } + pub fn low(&self) -> Variable { + self.low + } + pub fn lift(self) -> VarSplit { + VarSplit { + high: self.high(), + low: self.low(), + } + } + pub fn operands(&self) -> impl Iterator { + [ + ("high", MediumLevelILOperand::Var(self.high())), + ("low", MediumLevelILOperand::Var(self.low())), + ] + .into_iter() + } +} + +// SET_VAR_SPLIT +#[derive(Copy, Clone)] +pub struct SetVarSplit { + high: u64, + low: u64, + src: usize, +} +#[derive(Clone, Debug, PartialEq)] +pub struct LiftedSetVarSplit { + pub high: Variable, + pub low: Variable, + pub src: Box, +} +impl SetVarSplit { + pub fn new(high: u64, low: u64, src: usize) -> Self { + Self { high, low, src } + } + pub fn high(&self) -> Variable { + get_var(self.high) + } + pub fn low(&self) -> Variable { + get_var(self.low) + } + pub fn src(&self, function: &MediumLevelILFunction) -> MediumLevelILInstruction { + get_operation(function, self.src) + } + pub fn lift(&self, function: &MediumLevelILFunction) -> LiftedSetVarSplit { + LiftedSetVarSplit { + high: self.high(), + low: self.low(), + src: Box::new(self.src(function).lift()), + } + } + pub fn operands( + &self, + function: &MediumLevelILFunction, + ) -> impl Iterator { + [ + ("high", MediumLevelILOperand::Var(self.high())), + ("low", MediumLevelILOperand::Var(self.low())), + ("src", MediumLevelILOperand::Expr(self.src(function))), + ] + .into_iter() + } +} + +// VAR_SPLIT_SSA +#[derive(Copy, Clone, Debug, Hash, PartialEq, Eq)] +pub struct VarSplitSsa { + pub high: SSAVariable, + pub low: SSAVariable, +} +impl VarSplitSsa { + pub fn new(high: (u64, usize), low: (u64, usize)) -> Self { + Self { + high: get_var_ssa(high.0, high.1), + low: get_var_ssa(low.0, low.1), + } + } + pub fn high(&self) -> SSAVariable { + self.high + } + pub fn low(&self) -> SSAVariable { + self.low + } + pub fn lift(self) -> VarSplitSsa { + VarSplitSsa { + high: self.high(), + low: self.low(), + } + } + pub fn operands(&self) -> impl Iterator { + [ + ("high", MediumLevelILOperand::VarSsa(self.high())), + ("low", MediumLevelILOperand::VarSsa(self.low())), + ] + .into_iter() + } +} + +// SET_VAR_SPLIT_SSA +#[derive(Copy, Clone)] +pub struct SetVarSplitSsa { + high: (u64, usize), + low: (u64, usize), + src: usize, +} +#[derive(Clone, Debug, PartialEq)] +pub struct LiftedSetVarSplitSsa { + pub high: SSAVariable, + pub low: SSAVariable, + pub src: Box, +} +impl SetVarSplitSsa { + pub fn new(high: (u64, usize), low: (u64, usize), src: usize) -> Self { + Self { high, low, src } + } + pub fn high(&self) -> SSAVariable { + get_var_ssa(self.high.0, self.high.1) + } + pub fn low(&self) -> SSAVariable { + get_var_ssa(self.low.0, self.low.1) + } + pub fn src(&self, function: &MediumLevelILFunction) -> MediumLevelILInstruction { + get_operation(function, self.src) + } + pub fn lift(&self, function: &MediumLevelILFunction) -> LiftedSetVarSplitSsa { + LiftedSetVarSplitSsa { + high: self.high(), + low: self.low(), + src: Box::new(self.src(function).lift()), + } + } + pub fn operands( + &self, + function: &MediumLevelILFunction, + ) -> impl Iterator { + [ + ("high", MediumLevelILOperand::VarSsa(self.high())), + ("low", MediumLevelILOperand::VarSsa(self.low())), + ("src", MediumLevelILOperand::Expr(self.src(function))), + ] + .into_iter() + } +} + +// ADD, SUB, AND, OR, XOR, LSL, LSR, ASR, ROL, ROR, MUL, MULU_DP, MULS_DP, DIVU, DIVU_DP, DIVS, DIVS_DP, MODU, MODU_DP, MODS, MODS_DP, CMP_E, CMP_NE, CMP_SLT, CMP_ULT, CMP_SLE, CMP_ULE, CMP_SGE, CMP_UGE, CMP_SGT, CMP_UGT, TEST_BIT, ADD_OVERFLOW, FCMP_E, FCMP_NE, FCMP_LT, FCMP_LE, FCMP_GE, FCMP_GT, FCMP_O, FCMP_UO, FADD, FSUB, FMUL, FDIV +#[derive(Copy, Clone)] +pub struct BinaryOp { + left: usize, + right: usize, +} +#[derive(Clone, Debug, PartialEq)] +pub struct LiftedBinaryOp { + pub left: Box, + pub right: Box, +} +impl BinaryOp { + pub fn new(left: usize, right: usize) -> Self { + Self { left, right } + } + pub fn left(&self, function: &MediumLevelILFunction) -> MediumLevelILInstruction { + get_operation(function, self.left) + } + pub fn right(&self, function: &MediumLevelILFunction) -> MediumLevelILInstruction { + get_operation(function, self.right) + } + pub fn lift(&self, function: &MediumLevelILFunction) -> LiftedBinaryOp { + LiftedBinaryOp { + left: Box::new(self.left(function).lift()), + right: Box::new(self.right(function).lift()), + } + } + pub fn operands( + &self, + function: &MediumLevelILFunction, + ) -> impl Iterator { + [ + ("left", MediumLevelILOperand::Expr(self.left(function))), + ("right", MediumLevelILOperand::Expr(self.right(function))), + ] + .into_iter() + } +} + +// ADC, SBB, RLC, RRC +#[derive(Copy, Clone)] +pub struct BinaryOpCarry { + left: usize, + right: usize, + carry: usize, +} +#[derive(Clone, Debug, PartialEq)] +pub struct LiftedBinaryOpCarry { + pub left: Box, + pub right: Box, + pub carry: Box, +} +impl BinaryOpCarry { + pub fn new(left: usize, right: usize, carry: usize) -> Self { + Self { left, right, carry } + } + pub fn left(&self, function: &MediumLevelILFunction) -> MediumLevelILInstruction { + get_operation(function, self.left) + } + pub fn right(&self, function: &MediumLevelILFunction) -> MediumLevelILInstruction { + get_operation(function, self.right) + } + pub fn carry(&self, function: &MediumLevelILFunction) -> MediumLevelILInstruction { + get_operation(function, self.carry) + } + pub fn lift(&self, function: &MediumLevelILFunction) -> LiftedBinaryOpCarry { + LiftedBinaryOpCarry { + left: Box::new(self.left(function).lift()), + right: Box::new(self.right(function).lift()), + carry: Box::new(self.carry(function).lift()), + } + } + pub fn operands( + &self, + function: &MediumLevelILFunction, + ) -> impl Iterator { + [ + ("left", MediumLevelILOperand::Expr(self.left(function))), + ("right", MediumLevelILOperand::Expr(self.right(function))), + ("carry", MediumLevelILOperand::Expr(self.carry(function))), + ] + .into_iter() + } +} + +// CALL, TAILCALL +#[derive(Copy, Clone)] +pub struct Call { + output: (usize, usize), + dest: usize, + params: (usize, usize), +} +#[derive(Clone, Debug, PartialEq)] +pub struct LiftedCall { + pub output: Vec, + pub dest: Box, + pub params: Vec, +} +impl Call { + pub fn new(output: (usize, usize), dest: usize, params: (usize, usize)) -> Self { + Self { + output, + dest, + params, + } + } + pub fn output(&self, function: &MediumLevelILFunction) -> OperandVariableList { + OperandList::new(function, self.output.1, self.output.0).map_var() + } + pub fn dest(&self, function: &MediumLevelILFunction) -> MediumLevelILInstruction { + get_operation(function, self.dest) + } + pub fn params(&self, function: &MediumLevelILFunction) -> OperandExprList { + OperandList::new(function, self.params.1, self.params.0).map_expr() + } + pub fn lift(&self, function: &MediumLevelILFunction) -> LiftedCall { + LiftedCall { + output: self.output(function).collect(), + dest: Box::new(self.dest(function).lift()), + params: self.params(function).map(|instr| instr.lift()).collect(), + } + } + pub fn operands( + &self, + function: &MediumLevelILFunction, + ) -> impl Iterator { + [ + ( + "output", + MediumLevelILOperand::VarList(self.output(function)), + ), + ("dest", MediumLevelILOperand::Expr(self.dest(function))), + ( + "params", + MediumLevelILOperand::ExprList(self.params(function)), + ), + ] + .into_iter() + } +} + +// SYSCALL +#[derive(Copy, Clone)] +pub struct Syscall { + output: (usize, usize), + params: (usize, usize), +} +#[derive(Clone, Debug, PartialEq)] +pub struct LiftedInnerCall { + pub output: Vec, + pub params: Vec, +} +impl Syscall { + pub fn new(output: (usize, usize), params: (usize, usize)) -> Self { + Self { output, params } + } + pub fn output(&self, function: &MediumLevelILFunction) -> OperandVariableList { + OperandList::new(function, self.output.1, self.output.0).map_var() + } + pub fn params(&self, function: &MediumLevelILFunction) -> OperandExprList { + OperandList::new(function, self.params.1, self.params.0).map_expr() + } + pub fn lift(&self, function: &MediumLevelILFunction) -> LiftedInnerCall { + LiftedInnerCall { + output: self.output(function).collect(), + params: self.params(function).map(|instr| instr.lift()).collect(), + } + } + pub fn operands( + &self, + function: &MediumLevelILFunction, + ) -> impl Iterator { + use MediumLevelILOperand::*; + [ + ("output", VarList(self.output(function))), + ("params", ExprList(self.params(function))), + ] + .into_iter() + } +} + +// INTRINSIC +#[derive(Copy, Clone)] +pub struct Intrinsic { + output: (usize, usize), + intrinsic: usize, + params: (usize, usize), +} +#[derive(Clone, Debug, PartialEq)] +pub struct MediumLevelILLiftedIntrinsic { + pub output: Vec, + //pub intrinsic: !, + pub params: Vec, +} +impl Intrinsic { + pub fn new(output: (usize, usize), intrinsic: usize, params: (usize, usize)) -> Self { + Self { + output, + intrinsic, + params, + } + } + pub fn output(&self, function: &MediumLevelILFunction) -> OperandVariableList { + OperandList::new(function, self.output.1, self.output.0).map_var() + } + pub fn intrinsic(&self, function: &MediumLevelILFunction) -> ! { + get_intrinsic(function, self.intrinsic) + } + pub fn params(&self, function: &MediumLevelILFunction) -> OperandExprList { + OperandList::new(function, self.params.1, self.params.0).map_expr() + } + pub fn lift(&self, function: &MediumLevelILFunction) -> LiftedInnerCall { + LiftedInnerCall { + output: self.output(function).collect(), + //intrinsic: get_intrinsic(function, self.intrinsic), + params: self.params(function).map(|instr| instr.lift()).collect(), + } + } + pub fn operands( + &self, + function: &MediumLevelILFunction, + ) -> impl Iterator { + use MediumLevelILOperand::*; + [ + ("output", VarList(self.output(function))), + //("intrinsic", VarList(self.output(function))), + ("params", ExprList(self.params(function))), + ] + .into_iter() + } +} + +// INTRINSIC_SSA +#[derive(Copy, Clone)] +pub struct IntrinsicSsa { + output: (usize, usize), + intrinsic: usize, + params: (usize, usize), +} +#[derive(Clone, Debug, PartialEq)] +pub struct LiftedIntrinsicSsa { + pub output: Vec, + //pub intrinsic: !, + pub params: Vec, +} +impl IntrinsicSsa { + pub fn new(output: (usize, usize), intrinsic: usize, params: (usize, usize)) -> Self { + Self { + output, + intrinsic, + params, + } + } + pub fn output(&self, function: &MediumLevelILFunction) -> OperandSSAVariableList { + OperandList::new(function, self.output.1, self.output.0).map_ssa_var() + } + pub fn intrinsic(&self, function: &MediumLevelILFunction) -> ! { + get_intrinsic(function, self.intrinsic) + } + pub fn params(&self, function: &MediumLevelILFunction) -> OperandExprList { + OperandList::new(function, self.params.1, self.params.0).map_expr() + } + pub fn lift(&self, function: &MediumLevelILFunction) -> LiftedIntrinsicSsa { + LiftedIntrinsicSsa { + output: self.output(function).collect(), + //intrinsic: get_intrinsic(function, self.intrinsic), + params: self.params(function).map(|instr| instr.lift()).collect(), + } + } + pub fn operands( + &self, + function: &MediumLevelILFunction, + ) -> impl Iterator { + use MediumLevelILOperand::*; + [ + ("output", VarSsaList(self.output(function))), + ("params", ExprList(self.params(function))), + ] + .into_iter() + } +} + +// CALL_SSA, TAILCALL_SSA +#[derive(Copy, Clone)] +pub struct CallSsa { + output: usize, + dest: usize, + params: (usize, usize), + src_memory: u64, +} +#[derive(Clone, Debug, PartialEq)] +pub struct LiftedCallSsa { + pub output: Vec, + pub dest: Box, + pub params: Vec, + pub src_memory: u64, +} +impl CallSsa { + pub fn new(output: usize, dest: usize, params: (usize, usize), src_memory: u64) -> Self { + Self { + output, + dest, + params, + src_memory, + } + } + pub fn output(&self, function: &MediumLevelILFunction) -> OperandSSAVariableList { + get_call_output_ssa(function, self.output) + } + pub fn dest(&self, function: &MediumLevelILFunction) -> MediumLevelILInstruction { + get_operation(function, self.dest) + } + pub fn params(&self, function: &MediumLevelILFunction) -> OperandExprList { + OperandList::new(function, self.params.1, self.params.0).map_expr() + } + pub fn src_memory(&self) -> u64 { + self.src_memory + } + pub fn lift(&self, function: &MediumLevelILFunction) -> LiftedCallSsa { + LiftedCallSsa { + output: self.output(function).collect(), + dest: Box::new(self.dest(function).lift()), + params: self.params(function).map(|instr| instr.lift()).collect(), + src_memory: self.src_memory(), + } + } + pub fn operands( + &self, + function: &MediumLevelILFunction, + ) -> impl Iterator { + use MediumLevelILOperand::*; + [ + ("output", VarSsaList(self.output(function))), + ("dest", Expr(self.dest(function))), + ("params", ExprList(self.params(function))), + ("src_memory", Int(self.src_memory())), + ] + .into_iter() + } +} + +// CALL_UNTYPED_SSA, TAILCALL_UNTYPED_SSA +#[derive(Copy, Clone)] +pub struct CallUntypedSsa { + output: usize, + dest: usize, + params: usize, + stack: usize, +} +#[derive(Clone, Debug, PartialEq)] +pub struct LiftedCallUntypedSsa { + pub output: Vec, + pub dest: Box, + pub params: Vec, + pub stack: Box, +} +impl CallUntypedSsa { + pub fn new(output: usize, dest: usize, params: usize, stack: usize) -> Self { + Self { + output, + dest, + params, + stack, + } + } + pub fn output(&self, function: &MediumLevelILFunction) -> OperandSSAVariableList { + get_call_output_ssa(function, self.output) + } + pub fn dest(&self, function: &MediumLevelILFunction) -> MediumLevelILInstruction { + get_operation(function, self.dest) + } + pub fn params(&self, function: &MediumLevelILFunction) -> OperandExprList { + get_call_params_ssa(function, self.params) + } + pub fn stack(&self, function: &MediumLevelILFunction) -> MediumLevelILInstruction { + get_operation(function, self.stack) + } + pub fn lift(&self, function: &MediumLevelILFunction) -> LiftedCallUntypedSsa { + LiftedCallUntypedSsa { + output: self.output(function).collect(), + dest: Box::new(self.dest(function).lift()), + params: self.params(function).map(|instr| instr.lift()).collect(), + stack: Box::new(self.stack(function).lift()), + } + } + pub fn operands( + &self, + function: &MediumLevelILFunction, + ) -> impl Iterator { + use MediumLevelILOperand::*; + [ + ("output", VarSsaList(self.output(function))), + ("dest", Expr(self.dest(function))), + ("params", ExprList(self.params(function))), + ("stack", Expr(self.stack(function))), + ] + .into_iter() + } +} + +// SYSCALL_SSA +#[derive(Copy, Clone)] +pub struct SyscallSsa { + output: usize, + params: (usize, usize), + src_memory: u64, +} +#[derive(Clone, Debug, PartialEq)] +pub struct LiftedSyscallSsa { + pub output: Vec, + pub params: Vec, + pub src_memory: u64, +} +impl SyscallSsa { + pub fn new(output: usize, params: (usize, usize), src_memory: u64) -> Self { + Self { + output, + params, + src_memory, + } + } + pub fn output(&self, function: &MediumLevelILFunction) -> OperandSSAVariableList { + get_call_output_ssa(function, self.output) + } + pub fn params(&self, function: &MediumLevelILFunction) -> OperandExprList { + OperandList::new(function, self.params.1, self.params.0).map_expr() + } + pub fn src_memory(&self) -> u64 { + self.src_memory + } + pub fn lift(&self, function: &MediumLevelILFunction) -> LiftedSyscallSsa { + LiftedSyscallSsa { + output: self.output(function).collect(), + params: self.params(function).map(|instr| instr.lift()).collect(), + src_memory: self.src_memory(), + } + } + pub fn operands( + &self, + function: &MediumLevelILFunction, + ) -> impl Iterator { + use MediumLevelILOperand::*; + [ + ("output", VarSsaList(self.output(function))), + ("params", ExprList(self.params(function))), + ("src_memory", MediumLevelILOperand::Int(self.src_memory())), + ] + .into_iter() + } +} + +// SYSCALL_UNTYPED_SSA +#[derive(Copy, Clone)] +pub struct SyscallUntypedSsa { + output: usize, + params: usize, + stack: usize, +} +#[derive(Clone, Debug, PartialEq)] +pub struct LiftedSyscallUntypedSsa { + pub output: Vec, + pub params: Vec, + pub stack: Box, +} +impl SyscallUntypedSsa { + pub fn new(output: usize, params: usize, stack: usize) -> Self { + Self { + output, + params, + stack, + } + } + pub fn output(&self, function: &MediumLevelILFunction) -> OperandSSAVariableList { + get_call_output_ssa(function, self.output) + } + pub fn params(&self, function: &MediumLevelILFunction) -> OperandExprList { + get_call_params_ssa(function, self.params) + } + pub fn stack(&self, function: &MediumLevelILFunction) -> MediumLevelILInstruction { + get_operation(function, self.stack) + } + pub fn lift(&self, function: &MediumLevelILFunction) -> LiftedSyscallUntypedSsa { + LiftedSyscallUntypedSsa { + output: self.output(function).collect(), + params: self.params(function).map(|instr| instr.lift()).collect(), + stack: Box::new(self.stack(function).lift()), + } + } + pub fn operands( + &self, + function: &MediumLevelILFunction, + ) -> impl Iterator { + use MediumLevelILOperand::*; + [ + ("output", VarSsaList(self.output(function))), + ("params", ExprList(self.params(function))), + ("stack", Expr(self.stack(function))), + ] + .into_iter() + } +} + +// CALL_UNTYPED, TAILCALL_UNTYPED +#[derive(Copy, Clone)] +pub struct CallUntyped { + output: usize, + dest: usize, + params: usize, + stack: usize, +} +#[derive(Clone, Debug, PartialEq)] +pub struct LiftedCallUntyped { + pub output: Vec, + pub dest: Box, + pub params: Vec, + pub stack: Box, +} +impl CallUntyped { + pub fn new(output: usize, dest: usize, params: usize, stack: usize) -> Self { + Self { + output, + dest, + params, + stack, + } + } + pub fn output(&self, function: &MediumLevelILFunction) -> OperandVariableList { + get_call_output(function, self.output) + } + pub fn dest(&self, function: &MediumLevelILFunction) -> MediumLevelILInstruction { + get_operation(function, self.dest) + } + pub fn params(&self, function: &MediumLevelILFunction) -> OperandExprList { + get_call_params(function, self.params) + } + pub fn stack(&self, function: &MediumLevelILFunction) -> MediumLevelILInstruction { + get_operation(function, self.stack) + } + pub fn lift(&self, function: &MediumLevelILFunction) -> LiftedCallUntyped { + LiftedCallUntyped { + output: self.output(function).collect(), + dest: Box::new(self.dest(function).lift()), + params: self.params(function).map(|instr| instr.lift()).collect(), + stack: Box::new(self.stack(function).lift()), + } + } + pub fn operands( + &self, + function: &MediumLevelILFunction, + ) -> impl Iterator { + use MediumLevelILOperand::*; + [ + ("output", VarList(self.output(function))), + ("dest", Expr(self.dest(function))), + ("params", ExprList(self.params(function))), + ("stack", Expr(self.stack(function))), + ] + .into_iter() + } +} + +// SYSCALL_UNTYPED +#[derive(Copy, Clone)] +pub struct SyscallUntyped { + output: usize, + params: usize, + stack: usize, +} +#[derive(Clone, Debug, PartialEq)] +pub struct LiftedSyscallUntyped { + pub output: Vec, + pub params: Vec, + pub stack: Box, +} +impl SyscallUntyped { + pub fn new(output: usize, params: usize, stack: usize) -> Self { + Self { + output, + params, + stack, + } + } + pub fn output(&self, function: &MediumLevelILFunction) -> OperandVariableList { + get_call_output(function, self.output) + } + pub fn params(&self, function: &MediumLevelILFunction) -> OperandExprList { + get_call_params(function, self.params) + } + pub fn stack(&self, function: &MediumLevelILFunction) -> MediumLevelILInstruction { + get_operation(function, self.stack) + } + pub fn lift(&self, function: &MediumLevelILFunction) -> LiftedSyscallUntyped { + LiftedSyscallUntyped { + output: self.output(function).collect(), + params: self.params(function).map(|instr| instr.lift()).collect(), + stack: Box::new(self.stack(function).lift()), + } + } + pub fn operands( + &self, + function: &MediumLevelILFunction, + ) -> impl Iterator { + use MediumLevelILOperand::*; + [ + ("output", VarList(self.output(function))), + ("params", ExprList(self.params(function))), + ("stack", Expr(self.stack(function))), + ] + .into_iter() + } +} + +// NEG, NOT, SX, ZX, LOW_PART, BOOL_TO_INT, UNIMPL_MEM, FSQRT, FNEG, FABS, FLOAT_TO_INT, INT_TO_FLOAT, FLOAT_CONV, ROUND_TO_INT, FLOOR, CEIL, FTRUNC, LOAD +#[derive(Copy, Clone)] +pub struct UnaryOp { + src: usize, +} +#[derive(Clone, Debug, PartialEq)] +pub struct LiftedUnaryOp { + pub src: Box, +} +impl UnaryOp { + pub fn new(src: usize) -> Self { + Self { src } + } + pub fn src(&self, function: &MediumLevelILFunction) -> MediumLevelILInstruction { + get_operation(function, self.src) + } + pub fn lift(&self, function: &MediumLevelILFunction) -> LiftedUnaryOp { + LiftedUnaryOp { + src: Box::new(self.src(function).lift()), + } + } + pub fn operands( + &self, + function: &MediumLevelILFunction, + ) -> impl Iterator { + [("src", MediumLevelILOperand::Expr(self.src(function)))].into_iter() + } +} + +// LOAD_STRUCT +#[derive(Copy, Clone)] +pub struct LoadStruct { + src: usize, + offset: u64, +} +#[derive(Clone, Debug, PartialEq)] +pub struct LiftedLoadStruct { + pub src: Box, + pub offset: u64, +} +impl LoadStruct { + pub fn new(src: usize, offset: u64) -> Self { + Self { src, offset } + } + pub fn src(&self, function: &MediumLevelILFunction) -> MediumLevelILInstruction { + get_operation(function, self.src) + } + pub fn offset(&self) -> u64 { + self.offset + } + pub fn lift(&self, function: &MediumLevelILFunction) -> LiftedLoadStruct { + LiftedLoadStruct { + src: Box::new(self.src(function).lift()), + offset: self.offset(), + } + } + pub fn operands( + &self, + function: &MediumLevelILFunction, + ) -> impl Iterator { + [ + ("src", MediumLevelILOperand::Expr(self.src(function))), + ("offset", MediumLevelILOperand::Int(self.offset())), + ] + .into_iter() + } +} + +// LOAD_STRUCT_SSA +#[derive(Copy, Clone)] +pub struct LoadStructSsa { + src: usize, + offset: u64, + src_memory: u64, +} +#[derive(Clone, Debug, PartialEq)] +pub struct LiftedLoadStructSsa { + pub src: Box, + pub offset: u64, + pub src_memory: u64, +} +impl LoadStructSsa { + pub fn new(src: usize, offset: u64, src_memory: u64) -> Self { + Self { + src, + offset, + src_memory, + } + } + pub fn src(&self, function: &MediumLevelILFunction) -> MediumLevelILInstruction { + get_operation(function, self.src) + } + pub fn offset(&self) -> u64 { + self.offset + } + pub fn src_memory(&self) -> u64 { + self.src_memory + } + pub fn lift(&self, function: &MediumLevelILFunction) -> LiftedLoadStructSsa { + LiftedLoadStructSsa { + src: Box::new(self.src(function).lift()), + offset: self.offset(), + src_memory: self.src_memory(), + } + } + pub fn operands( + &self, + function: &MediumLevelILFunction, + ) -> impl Iterator { + [ + ("src", MediumLevelILOperand::Expr(self.src(function))), + ("offset", MediumLevelILOperand::Int(self.offset())), + ("src_memory", MediumLevelILOperand::Int(self.src_memory())), + ] + .into_iter() + } +} + +// LOAD_SSA +#[derive(Copy, Clone)] +pub struct LoadSsa { + src: usize, + src_memory: u64, +} +#[derive(Clone, Debug, PartialEq)] +pub struct LiftedLoadSsa { + pub src: Box, + pub src_memory: u64, +} +impl LoadSsa { + pub fn new(src: usize, src_memory: u64) -> Self { + Self { src, src_memory } + } + pub fn src(&self, function: &MediumLevelILFunction) -> MediumLevelILInstruction { + get_operation(function, self.src) + } + pub fn src_memory(&self) -> u64 { + self.src_memory + } + pub fn lift(&self, function: &MediumLevelILFunction) -> LiftedLoadSsa { + LiftedLoadSsa { + src: Box::new(self.src(function).lift()), + src_memory: self.src_memory(), + } + } + pub fn operands( + &self, + function: &MediumLevelILFunction, + ) -> impl Iterator { + [ + ("src", MediumLevelILOperand::Expr(self.src(function))), + ("src_memory", MediumLevelILOperand::Int(self.src_memory())), + ] + .into_iter() + } +} + +// RET +#[derive(Copy, Clone)] +pub struct Ret { + src: (usize, usize), +} +#[derive(Clone, Debug, PartialEq)] +pub struct LiftedRet { + pub src: Vec, +} +impl Ret { + pub fn new(src: (usize, usize)) -> Self { + Self { src } + } + pub fn src(&self, function: &MediumLevelILFunction) -> OperandExprList { + OperandList::new(function, self.src.1, self.src.0).map_expr() + } + pub fn lift(&self, function: &MediumLevelILFunction) -> LiftedRet { + LiftedRet { + src: self.src(function).map(|instr| instr.lift()).collect(), + } + } + pub fn operands( + &self, + function: &MediumLevelILFunction, + ) -> impl Iterator { + [("src", MediumLevelILOperand::ExprList(self.src(function)))].into_iter() + } +} + +// SEPARATE_PARAM_LIST +#[derive(Copy, Clone)] +pub struct SeparateParamList { + params: (usize, usize), +} +#[derive(Clone, Debug, PartialEq)] +pub struct LiftedSeparateParamList { + pub params: Vec, +} +impl SeparateParamList { + pub fn new(params: (usize, usize)) -> Self { + Self { params } + } + pub fn params(&self, function: &MediumLevelILFunction) -> OperandExprList { + OperandList::new(function, self.params.1, self.params.0).map_expr() + } + pub fn lift(&self, function: &MediumLevelILFunction) -> LiftedSeparateParamList { + LiftedSeparateParamList { + params: self.params(function).map(|instr| instr.lift()).collect(), + } + } + pub fn operands( + &self, + function: &MediumLevelILFunction, + ) -> impl Iterator { + [("params", MediumLevelILOperand::ExprList(self.params(function)))].into_iter() + } +} + +// SHARED_PARAM_SLOT +#[derive(Copy, Clone)] +pub struct SharedParamSlot { + params: (usize, usize), +} +#[derive(Clone, Debug, PartialEq)] +pub struct LiftedSharedParamSlot { + pub params: Vec, +} +impl SharedParamSlot { + pub fn new(params: (usize, usize)) -> Self { + Self { params } + } + pub fn params(&self, function: &MediumLevelILFunction) -> OperandExprList { + OperandList::new(function, self.params.1, self.params.0).map_expr() + } + pub fn lift(&self, function: &MediumLevelILFunction) -> LiftedSharedParamSlot { + LiftedSharedParamSlot { + params: self.params(function).map(|instr| instr.lift()).collect(), + } + } + pub fn operands( + &self, + function: &MediumLevelILFunction, + ) -> impl Iterator { + [("params", MediumLevelILOperand::ExprList(self.params(function)))].into_iter() + } +} + +// VAR, ADDRESS_OF +#[derive(Copy, Clone, Debug, Hash, PartialEq, Eq)] +pub struct Var { + pub src: Variable, +} +impl Var { + pub fn new(src: u64) -> Self { + Self { src: get_var(src) } + } + pub fn src(&self) -> Variable { + self.src + } + pub fn operands(&self) -> impl Iterator { + [("src", MediumLevelILOperand::Var(self.src()))].into_iter() + } +} + +// VAR_FIELD, ADDRESS_OF_FIELD +#[derive(Copy, Clone, Debug, Hash, PartialEq, Eq)] +pub struct Field { + pub src: Variable, + pub offset: u64, +} +impl Field { + pub fn new(src: u64, offset: u64) -> Self { + Self { + src: get_var(src), + offset, + } + } + pub fn src(&self) -> Variable { + self.src + } + pub fn offset(&self) -> u64 { + self.offset + } + pub fn operands(&self) -> impl Iterator { + [ + ("src", MediumLevelILOperand::Var(self.src())), + ("offset", MediumLevelILOperand::Int(self.offset())), + ] + .into_iter() + } +} + +// VAR_SSA, VAR_ALIASED +#[derive(Copy, Clone, Debug, Hash, PartialEq, Eq)] +pub struct VarSsa { + pub src: SSAVariable, +} +impl VarSsa { + pub fn new(src: (u64, usize)) -> Self { + Self { + src: get_var_ssa(src.0, src.1), + } + } + pub fn src(&self) -> SSAVariable { + self.src + } + pub fn operands(&self) -> impl Iterator { + [("src", MediumLevelILOperand::VarSsa(self.src()))].into_iter() + } +} + +// VAR_SSA_FIELD, VAR_ALIASED_FIELD +#[derive(Copy, Clone, Debug, Hash, PartialEq, Eq)] +pub struct VarSsaField { + pub src: SSAVariable, + pub offset: u64, +} +impl VarSsaField { + pub fn new(src: (u64, usize), offset: u64) -> Self { + Self { + src: get_var_ssa(src.0, src.1), + offset, + } + } + pub fn src(&self) -> SSAVariable { + self.src + } + pub fn offset(&self) -> u64 { + self.offset + } + pub fn operands(&self) -> impl Iterator { + [ + ("src", MediumLevelILOperand::VarSsa(self.src())), + ("offset", MediumLevelILOperand::Int(self.offset())), + ] + .into_iter() + } +} + +// TRAP +#[derive(Copy, Clone, Debug, Hash, PartialEq, Eq)] +pub struct Trap { + pub vector: u64, +} +impl Trap { + pub fn new(vector: u64) -> Self { + Self { vector } + } + pub fn vector(&self) -> u64 { + self.vector + } + pub fn operands(&self) -> impl Iterator { + [("vector", MediumLevelILOperand::Int(self.vector()))].into_iter() + } +} diff --git a/src/platform.rs b/src/platform.rs new file mode 100644 index 0000000..3df5e7c --- /dev/null +++ b/src/platform.rs @@ -0,0 +1,383 @@ +// Copyright 2021-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. + +//! Contains all information related to the execution environment of the binary, mainly the calling conventions used + +use std::{borrow::Borrow, collections::HashMap, os::raw, path::Path, ptr, slice}; + +use binaryninjacore_sys::*; + +use crate::{ + architecture::{Architecture, CoreArchitecture}, + callingconvention::CallingConvention, + rc::*, + string::*, + types::{QualifiedName, QualifiedNameAndType, Type}, +}; + +#[derive(PartialEq, Eq, Hash)] +pub struct Platform { + pub(crate) handle: *mut BNPlatform, +} + +unsafe impl Send for Platform {} +unsafe impl Sync for Platform {} + +macro_rules! cc_func { + ($get_name:ident, $get_api:ident, $set_name:ident, $set_api:ident) => { + pub fn $get_name(&self) -> Option>> { + let arch = self.arch(); + + unsafe { + let cc = $get_api(self.handle); + + if cc.is_null() { + None + } else { + Some(CallingConvention::ref_from_raw(cc, arch)) + } + } + } + + pub fn $set_name(&self, cc: &CallingConvention) { + let arch = self.arch(); + + assert!( + cc.arch_handle.borrow().as_ref().0 == arch.0, + "use of calling convention with non-matching Platform architecture!" + ); + + unsafe { + $set_api(self.handle, cc.handle); + } + } + }; +} + +impl Platform { + pub(crate) unsafe fn ref_from_raw(handle: *mut BNPlatform) -> Ref { + debug_assert!(!handle.is_null()); + + Ref::new(Self { handle }) + } + + pub fn by_name(name: S) -> Option> { + let raw_name = name.into_bytes_with_nul(); + unsafe { + let res = BNGetPlatformByName(raw_name.as_ref().as_ptr() as *mut _); + + if res.is_null() { + None + } else { + Some(Ref::new(Self { handle: res })) + } + } + } + + pub fn list_all() -> Array { + unsafe { + let mut count = 0; + let handles = BNGetPlatformList(&mut count); + + Array::new(handles, count, ()) + } + } + + pub fn list_by_arch(arch: &CoreArchitecture) -> Array { + unsafe { + let mut count = 0; + let handles = BNGetPlatformListByArchitecture(arch.0, &mut count); + + Array::new(handles, count, ()) + } + } + + pub fn list_by_os(name: S) -> Array { + let raw_name = name.into_bytes_with_nul(); + + unsafe { + let mut count = 0; + let handles = BNGetPlatformListByOS(raw_name.as_ref().as_ptr() as *mut _, &mut count); + + Array::new(handles, count, ()) + } + } + + pub fn list_by_os_and_arch( + name: S, + arch: &CoreArchitecture, + ) -> Array { + let raw_name = name.into_bytes_with_nul(); + + unsafe { + let mut count = 0; + let handles = BNGetPlatformListByOSAndArchitecture( + raw_name.as_ref().as_ptr() as *mut _, + arch.0, + &mut count, + ); + + Array::new(handles, count, ()) + } + } + + pub fn list_available_os() -> Array { + unsafe { + let mut count = 0; + let list = BNGetPlatformOSList(&mut count); + + Array::new(list, count, ()) + } + } + + pub fn new(arch: &A, name: S) -> Ref { + let name = name.into_bytes_with_nul(); + unsafe { + let handle = BNCreatePlatform(arch.as_ref().0, name.as_ref().as_ptr() as *mut _); + + assert!(!handle.is_null()); + + Ref::new(Self { handle }) + } + } + + pub fn name(&self) -> BnString { + unsafe { + let raw_name = BNGetPlatformName(self.handle); + BnString::from_raw(raw_name) + } + } + + pub fn arch(&self) -> CoreArchitecture { + unsafe { CoreArchitecture::from_raw(BNGetPlatformArchitecture(self.handle)) } + } + + pub fn register_os(&self, os: S) { + let os = os.into_bytes_with_nul(); + + unsafe { + BNRegisterPlatform(os.as_ref().as_ptr() as *mut _, self.handle); + } + } + + cc_func!( + get_default_calling_convention, + BNGetPlatformDefaultCallingConvention, + set_default_calling_convention, + BNRegisterPlatformDefaultCallingConvention + ); + + cc_func!( + get_cdecl_calling_convention, + BNGetPlatformCdeclCallingConvention, + set_cdecl_calling_convention, + BNRegisterPlatformCdeclCallingConvention + ); + + cc_func!( + get_stdcall_calling_convention, + BNGetPlatformStdcallCallingConvention, + set_stdcall_calling_convention, + BNRegisterPlatformStdcallCallingConvention + ); + + cc_func!( + get_fastcall_calling_convention, + BNGetPlatformFastcallCallingConvention, + set_fastcall_calling_convention, + BNRegisterPlatformFastcallCallingConvention + ); + + cc_func!( + get_syscall_convention, + BNGetPlatformSystemCallConvention, + set_syscall_convention, + BNSetPlatformSystemCallConvention + ); + + pub fn calling_conventions(&self) -> Array> { + unsafe { + let mut count = 0; + let handles = BNGetPlatformCallingConventions(self.handle, &mut count); + + Array::new(handles, count, self.arch()) + } + } + + pub fn types(&self) -> Array { + unsafe { + let mut count = 0; + let handles = BNGetPlatformTypes(self.handle, &mut count); + + Array::new(handles, count, ()) + } + } + + pub fn variables(&self) -> Array { + unsafe { + let mut count = 0; + let handles = BNGetPlatformVariables(self.handle, &mut count); + + Array::new(handles, count, ()) + } + } + + pub fn functions(&self) -> Array { + unsafe { + let mut count = 0; + let handles = BNGetPlatformFunctions(self.handle, &mut count); + + Array::new(handles, count, ()) + } + } +} + +pub trait TypeParser { + fn parse_types_from_source>( + &self, + _source: S, + _filename: S, + _include_directories: &[P], + _auto_type_source: S, + ) -> Result { + Err(String::new()) + } +} + +#[derive(Clone, Default)] +pub struct TypeParserResult { + pub types: HashMap>, + pub variables: HashMap>, + pub functions: HashMap>, +} + +impl TypeParser for Platform { + fn parse_types_from_source>( + &self, + source: S, + filename: S, + include_directories: &[P], + auto_type_source: S, + ) -> Result { + let mut result = BNTypeParserResult { + functionCount: 0, + typeCount: 0, + variableCount: 0, + functions: ptr::null_mut(), + types: ptr::null_mut(), + variables: ptr::null_mut(), + }; + + let mut type_parser_result = TypeParserResult::default(); + + let mut error_string: *mut raw::c_char = ptr::null_mut(); + + let src = source.into_bytes_with_nul(); + let filename = filename.into_bytes_with_nul(); + let auto_type_source = auto_type_source.into_bytes_with_nul(); + + let mut include_dirs = vec![]; + + for dir in include_directories.iter() { + let d = dir + .as_ref() + .to_string_lossy() + .to_string() + .into_bytes_with_nul(); + include_dirs.push(d.as_ptr() as _); + } + + unsafe { + let success = BNParseTypesFromSource( + self.handle, + src.as_ref().as_ptr() as _, + filename.as_ref().as_ptr() as _, + &mut result, + &mut error_string, + include_dirs.as_mut_ptr(), + include_dirs.len(), + auto_type_source.as_ref().as_ptr() as _, + ); + + let error_msg = BnString::from_raw(error_string); + + if !success { + return Err(error_msg.to_string()); + } + + for i in slice::from_raw_parts(result.types, result.typeCount) { + let name = QualifiedName(i.name); + type_parser_result + .types + .insert(name.string(), Type::ref_from_raw(i.type_)); + } + + for i in slice::from_raw_parts(result.functions, result.functionCount) { + let name = QualifiedName(i.name); + type_parser_result + .functions + .insert(name.string(), Type::ref_from_raw(i.type_)); + } + + for i in slice::from_raw_parts(result.variables, result.variableCount) { + let name = QualifiedName(i.name); + type_parser_result + .variables + .insert(name.string(), Type::ref_from_raw(i.type_)); + } + } + + Ok(type_parser_result) + } +} + +impl ToOwned for Platform { + type Owned = Ref; + + fn to_owned(&self) -> Self::Owned { + unsafe { RefCountable::inc_ref(self) } + } +} + +unsafe impl RefCountable for Platform { + unsafe fn inc_ref(handle: &Self) -> Ref { + Ref::new(Self { + handle: BNNewPlatformReference(handle.handle), + }) + } + + unsafe fn dec_ref(handle: &Self) { + BNFreePlatform(handle.handle); + } +} + +impl CoreArrayProvider for Platform { + type Raw = *mut BNPlatform; + type Context = (); +} + +unsafe impl CoreOwnedArrayProvider 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> { + debug_assert!(!raw.is_null()); + Guard::new(Platform { handle: *raw }, context) + } +} diff --git a/src/rc.rs b/src/rc.rs new file mode 100644 index 0000000..cdcae17 --- /dev/null +++ b/src/rc.rs @@ -0,0 +1,521 @@ +// Copyright 2021-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. + +//! Reference counting for core Binary Ninja objects. + +use std::borrow::Borrow; +use std::fmt::{Debug, Display, Formatter}; +use std::hash::{Hash, Hasher}; +use std::marker::PhantomData; +use std::mem; +use std::ops::{Deref, DerefMut}; +use std::ptr; +use std::slice; + +// RefCountable provides an abstraction over the various +// core-allocated refcounted resources. +// +// It is important that consumers don't acquire ownership +// of a `RefCountable` object directly -- they should only +// ever get their hands on a `Ref` or `&T`, otherwise it +// would be possible for the allocation in the core to +// be trivially leaked, as `T` does not have the `Drop` impl +// +// `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 { + unsafe fn inc_ref(handle: &Self) -> Ref; + unsafe fn dec_ref(handle: &Self); +} + +// Represents an 'owned' reference tracked by the core +// that we are responsible for cleaning up once we're +// done with the encapsulated value. +pub struct Ref { + contents: T, +} + +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 { + Self { contents } + } + + pub(crate) unsafe fn into_raw(obj: Self) -> T { + let res = ptr::read(&obj.contents); + + mem::forget(obj); + + res + } +} + +impl AsRef for Ref { + fn as_ref(&self) -> &T { + &self.contents + } +} + +impl Deref for Ref { + type Target = T; + + fn deref(&self) -> &T { + &self.contents + } +} + +impl DerefMut for Ref { + fn deref_mut(&mut self) -> &mut T { + &mut self.contents + } +} + +impl Borrow for Ref { + fn borrow(&self) -> &T { + &self.contents + } +} + +impl Drop for Ref { + fn drop(&mut self) { + unsafe { + RefCountable::dec_ref(&self.contents); + } + } +} + +impl Clone for Ref { + fn clone(&self) -> Self { + unsafe { RefCountable::inc_ref(&self.contents) } + } +} + +impl Display for Ref { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + self.contents.fmt(f) + } +} + +impl Debug for Ref { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + self.contents.fmt(f) + } +} + +impl PartialEq for Ref { + fn eq(&self, other: &Self) -> bool { + self.contents.eq(&other.contents) + } +} + +impl Eq for Ref {} + +impl Hash for Ref { + fn hash(&self, state: &mut H) { + self.contents.hash(state); + } +} + +// Guard provides access to a core-allocated resource whose +// reference is held indirectly (e.g. a core-allocated array +// of raw `*mut BNRawT`). +// +// This wrapper is necessary because `binja-rs` wrappers around +// core objects can be bigger than the raw pointer to the core +// object. This lets us create the full wrapper object and ensure +// that it does not outlive the core-allocated array (or similar) +// that our object came from. +pub struct Guard<'a, T> { + contents: T, + _guard: PhantomData<&'a ()>, +} + +impl<'a, T> Guard<'a, T> { + pub(crate) unsafe fn new(contents: T, _owner: &O) -> Self { + Self { + contents, + _guard: PhantomData, + } + } +} + +impl<'a, T> Guard<'a, T> +where + T: RefCountable, +{ + #[allow(clippy::should_implement_trait)] // This _is_ out own (lite) version of that trait + pub fn clone(&self) -> Ref { + unsafe { ::inc_ref(&self.contents) } + } +} + +impl<'a, T> AsRef for Guard<'a, T> { + fn as_ref(&self) -> &T { + &self.contents + } +} + +impl<'a, T> Deref for Guard<'a, T> { + type Target = T; + + fn deref(&self) -> &T { + &self.contents + } +} + +impl<'a, T> DerefMut for Guard<'a, T> { + fn deref_mut(&mut self) -> &mut T { + &mut self.contents + } +} + +impl<'a, T> Borrow for Guard<'a, T> { + fn borrow(&self) -> &T { + &self.contents + } +} + +pub trait CoreArrayProvider { + type Raw; + type Context; +} + +pub unsafe trait CoreOwnedArrayProvider: CoreArrayProvider { + unsafe fn free(raw: *mut Self::Raw, count: usize, context: &Self::Context); +} + +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 { + contents: *mut P::Raw, + count: usize, + context: P::Context, +} + +unsafe impl

Sync for Array

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

Send for Array

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

{ + pub(crate) unsafe fn new(raw: *mut P::Raw, count: usize, context: P::Context) -> Self { + Self { + contents: raw, + count, + context, + } + } + + #[inline] + pub fn len(&self) -> usize { + self.count + } + + #[inline] + 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

{ + #[inline] + pub fn get(&'a 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> { + ArrayIter { + it: unsafe { slice::from_raw_parts(self.contents, self.count).iter() }, + context: &self.context, + } + } +} + +impl<'a, P: 'a + CoreArrayWrapper<'a> + CoreOwnedArrayProvider> IntoIterator for &'a Array

{ + type Item = P::Wrapped; + type IntoIter = ArrayIter<'a, P>; + + fn into_iter(self) -> Self::IntoIter { + self.iter() + } +} + +impl Drop for Array

{ + fn drop(&mut self) { + unsafe { + P::free(self.contents, self.count, &self.context); + } + } +} + +pub struct ArrayGuard { + contents: *mut P::Raw, + count: usize, + context: P::Context, +} + +unsafe impl

Sync for ArrayGuard

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

Send for ArrayGuard

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

{ + pub(crate) unsafe fn new(raw: *mut P::Raw, count: usize, context: P::Context) -> Self { + Self { + contents: raw, + count, + context, + } + } + + #[inline] + pub fn len(&self) -> usize { + self.count + } + + #[inline] + pub fn is_empty(&self) -> bool { + self.count == 0 + } +} + +impl<'a, P: 'a + CoreArrayWrapper<'a> + CoreArrayProvider> ArrayGuard

{ + #[inline] + pub fn get(&'a 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> { + ArrayIter { + it: unsafe { slice::from_raw_parts(self.contents, self.count).iter() }, + context: &self.context, + } + } +} + +impl<'a, P: 'a + CoreArrayWrapper<'a> + CoreArrayProvider> IntoIterator for &'a ArrayGuard

{ + type Item = P::Wrapped; + type IntoIter = ArrayIter<'a, P>; + + fn into_iter(self) -> Self::IntoIter { + self.iter() + } +} + +pub struct ArrayIter<'a, P> +where + P: 'a + CoreArrayWrapper<'a>, +{ + it: slice::Iter<'a, P::Raw>, + context: &'a P::Context, +} + +unsafe impl<'a, P> Send for ArrayIter<'a, P> +where + P: CoreArrayWrapper<'a>, + P::Context: Sync, +{ +} + +impl<'a, P> Iterator for ArrayIter<'a, P> +where + P: 'a + CoreArrayWrapper<'a>, +{ + type Item = P::Wrapped; + + #[inline] + fn next(&mut self) -> Option { + self.it + .next() + .map(|r| unsafe { P::wrap_raw(r, self.context) }) + } + + #[inline] + fn size_hint(&self) -> (usize, Option) { + self.it.size_hint() + } +} + +impl<'a, P> ExactSizeIterator for ArrayIter<'a, P> +where + P: 'a + CoreArrayWrapper<'a>, +{ + #[inline] + fn len(&self) -> usize { + self.it.len() + } +} + +impl<'a, P> DoubleEndedIterator for ArrayIter<'a, P> +where + P: 'a + CoreArrayWrapper<'a>, +{ + #[inline] + fn next_back(&mut self) -> Option { + self.it + .next_back() + .map(|r| unsafe { P::wrap_raw(r, self.context) }) + } +} + +#[cfg(feature = "rayon")] +use rayon::prelude::*; + +#[cfg(feature = "rayon")] +use rayon::iter::plumbing::*; + +#[cfg(feature = "rayon")] +impl<'a, P> Array

+where + P: 'a + CoreArrayWrapper<'a> + CoreOwnedArrayProvider, + P::Context: Sync, + P::Wrapped: Send, +{ + pub fn par_iter(&'a self) -> ParArrayIter<'a, P> { + ParArrayIter { it: self.iter() } + } +} +#[cfg(feature = "rayon")] +pub struct ParArrayIter<'a, P> +where + P: 'a + CoreArrayWrapper<'a>, + ArrayIter<'a, P>: Send, +{ + it: ArrayIter<'a, P>, +} + +#[cfg(feature = "rayon")] +impl<'a, P> ParallelIterator for ParArrayIter<'a, P> +where + P: 'a + CoreArrayWrapper<'a>, + P::Wrapped: Send, + ArrayIter<'a, P>: Send, +{ + type Item = P::Wrapped; + + fn drive_unindexed(self, consumer: C) -> C::Result + where + C: UnindexedConsumer, + { + bridge(self, consumer) + } + + fn opt_len(&self) -> Option { + Some(self.it.len()) + } +} + +#[cfg(feature = "rayon")] +impl<'a, P> IndexedParallelIterator for ParArrayIter<'a, P> +where + P: 'a + CoreArrayWrapper<'a>, + P::Wrapped: Send, + ArrayIter<'a, P>: Send, +{ + fn drive(self, consumer: C) -> C::Result + where + C: Consumer, + { + bridge(self, consumer) + } + + fn len(&self) -> usize { + self.it.len() + } + + fn with_producer(self, callback: CB) -> CB::Output + where + CB: ProducerCallback, + { + callback.callback(ArrayIterProducer { it: self.it }) + } +} + +#[cfg(feature = "rayon")] +struct ArrayIterProducer<'a, P> +where + P: 'a + CoreArrayWrapper<'a>, + ArrayIter<'a, P>: Send, +{ + it: ArrayIter<'a, P>, +} + +#[cfg(feature = "rayon")] +impl<'a, P> Producer for ArrayIterProducer<'a, P> +where + P: 'a + CoreArrayWrapper<'a>, + ArrayIter<'a, P>: Send, +{ + type Item = P::Wrapped; + type IntoIter = ArrayIter<'a, P>; + + fn into_iter(self) -> ArrayIter<'a, P> { + self.it + } + + fn split_at(self, index: usize) -> (Self, Self) { + let (l, r) = self.it.it.as_slice().split_at(index); + + ( + Self { + it: ArrayIter { + it: l.iter(), + context: self.it.context, + }, + }, + Self { + it: ArrayIter { + it: r.iter(), + context: self.it.context, + }, + }, + ) + } +} diff --git a/src/references.rs b/src/references.rs new file mode 100644 index 0000000..76ac449 --- /dev/null +++ b/src/references.rs @@ -0,0 +1,94 @@ +use crate::architecture::CoreArchitecture; +use crate::function::Function; +use crate::rc::{CoreArrayProvider, CoreArrayWrapper, CoreOwnedArrayProvider, Ref}; +use binaryninjacore_sys::{BNFreeCodeReferences, BNFreeDataReferences, BNReferenceSource}; +use std::mem::ManuallyDrop; + +/// A struct representing a single code cross-reference. +/// Taking a cue from [`crate::linearview::LinearDisassemblyLine`], this struct uses [ManuallyDrop] to +/// prevent destructors from being run on the [`Function`] object allocated by +/// the core in `BNGetCodeReferences` (et al). The reference is cleaned up on [Drop] of +/// the enclosing array object. +#[derive(Debug)] +pub struct CodeReference { + arch: CoreArchitecture, + func: ManuallyDrop>, + pub address: u64, +} + +/// A struct representing a single data cross-reference. +/// Data references have no associated metadata, so this object has only +/// a single [`DataReference::address`] attribute. +pub struct DataReference { + pub address: u64, +} + +impl CodeReference { + pub(crate) unsafe fn new(handle: &BNReferenceSource) -> Self { + let func = ManuallyDrop::new(Function::from_raw(handle.func)); + let arch = CoreArchitecture::from_raw(handle.arch); + let address = handle.addr; + Self { + func, + arch, + address, + } + } +} + +impl<'a> CodeReference { + /// A handle to the referenced function bound by the [CodeReference] object's lifetime. + /// A user can call `.to_owned()` to promote this into its own ref-counted struct + /// and use it after the lifetime of the [CodeReference]. + pub fn function(&'a self) -> &'a Function { + self.func.as_ref() + } + + /// A handle to the [CodeReference]'s [CoreArchitecture]. This type is [Copy] so reference + /// shenanigans are not needed here. + pub fn architecture(&self) -> CoreArchitecture { + self.arch + } +} + +// Code Reference Array boilerplate + +impl CoreArrayProvider for CodeReference { + type Raw = BNReferenceSource; + type Context = (); +} + +unsafe impl CoreOwnedArrayProvider 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) + } +} + +// Data Reference Array boilerplate + +impl CoreArrayProvider for DataReference { + type Raw = u64; + type Context = (); +} + +unsafe impl CoreOwnedArrayProvider 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 { + DataReference { address: *raw } + } +} diff --git a/src/relocation.rs b/src/relocation.rs new file mode 100644 index 0000000..7be7861 --- /dev/null +++ b/src/relocation.rs @@ -0,0 +1,570 @@ +use crate::string::BnStrCompatible; +use crate::{ + architecture::{Architecture, CoreArchitecture}, + binaryview::BinaryView, + llil, + rc::{CoreArrayProvider, CoreArrayWrapper, CoreOwnedArrayProvider, Ref, RefCountable}, + symbol::Symbol, +}; +use binaryninjacore_sys::*; +use std::borrow::Borrow; +use std::os::raw::c_void; + +#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] +pub enum RelocationType { + ELFGlobalRelocationType, + ELFCopyRelocationType, + ELFJumpSlotRelocationType, + StandardRelocationType, + IgnoredRelocation, + UnhandledRelocation, +} + +#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] +pub enum RelocationOperand { + Operand(usize), + AutocoerceExternPtr, + NocoerceExternPtr, + Invalid, +} + +impl From for RelocationType { + fn from(t: BNRelocationType) -> Self { + match t { + BNRelocationType::ELFGlobalRelocationType => RelocationType::ELFGlobalRelocationType, + BNRelocationType::ELFCopyRelocationType => RelocationType::ELFCopyRelocationType, + BNRelocationType::ELFJumpSlotRelocationType => { + RelocationType::ELFJumpSlotRelocationType + } + BNRelocationType::StandardRelocationType => RelocationType::StandardRelocationType, + BNRelocationType::IgnoredRelocation => RelocationType::IgnoredRelocation, + BNRelocationType::UnhandledRelocation => RelocationType::UnhandledRelocation, + } + } +} + +impl From for BNRelocationType { + fn from(t: RelocationType) -> Self { + match t { + RelocationType::ELFGlobalRelocationType => BNRelocationType::ELFGlobalRelocationType, + RelocationType::ELFCopyRelocationType => BNRelocationType::ELFCopyRelocationType, + RelocationType::ELFJumpSlotRelocationType => { + BNRelocationType::ELFJumpSlotRelocationType + } + RelocationType::StandardRelocationType => BNRelocationType::StandardRelocationType, + RelocationType::IgnoredRelocation => BNRelocationType::IgnoredRelocation, + RelocationType::UnhandledRelocation => BNRelocationType::UnhandledRelocation, + } + } +} + +impl From for RelocationOperand { + fn from(operand: usize) -> Self { + match operand { + 0xfffffffd => RelocationOperand::AutocoerceExternPtr, + 0xfffffffe => RelocationOperand::NocoerceExternPtr, + 0xffffffff => RelocationOperand::Invalid, + _ => RelocationOperand::Operand(operand), + } + } +} + +impl From for usize { + fn from(operand: RelocationOperand) -> Self { + match operand { + RelocationOperand::Operand(operand) => operand, + RelocationOperand::AutocoerceExternPtr => 0xfffffffd, + RelocationOperand::NocoerceExternPtr => 0xfffffffe, + RelocationOperand::Invalid => 0xffffffff, + } + } +} + +// TODO: How to handle related relocation linked lists? +#[derive(Clone)] +pub struct RelocationInfo { + pub type_: RelocationType, + pub pc_relative: bool, + pub base_relative: bool, + pub base: u64, + pub size: usize, + pub truncate_size: usize, + pub native_type: u64, + pub addend: usize, + pub has_sign: bool, + pub implicit_addend: bool, + pub external: bool, + pub symbol_index: usize, + pub section_index: usize, + pub address: u64, + pub target: u64, + pub data_relocation: bool, + relocation_data_cache: [u8; MAX_RELOCATION_SIZE as usize], +} + +impl RelocationInfo { + pub fn new() -> Self { + RelocationInfo { + type_: RelocationType::UnhandledRelocation, + pc_relative: false, + base_relative: false, + base: 0, + size: 0, + truncate_size: 0, + native_type: 0, + addend: 0, + has_sign: false, + implicit_addend: false, + external: false, + symbol_index: 0, + section_index: 0, + address: 0, + target: 0, + data_relocation: false, + relocation_data_cache: [0; MAX_RELOCATION_SIZE as usize], + } + } + + pub(crate) fn from_raw(reloc: &BNRelocationInfo) -> Self { + RelocationInfo { + type_: reloc.type_.into(), + pc_relative: reloc.pcRelative, + base_relative: reloc.baseRelative, + base: reloc.base, + size: reloc.size, + truncate_size: reloc.truncateSize, + native_type: reloc.nativeType, + addend: reloc.addend, + has_sign: reloc.hasSign, + implicit_addend: reloc.implicitAddend, + external: reloc.external, + symbol_index: reloc.symbolIndex, + section_index: reloc.sectionIndex, + address: reloc.address, + target: reloc.target, + data_relocation: reloc.dataRelocation, + relocation_data_cache: reloc.relocationDataCache, + } + } + + pub(crate) fn as_raw(&self) -> BNRelocationInfo { + BNRelocationInfo { + type_: self.type_.into(), + pcRelative: self.pc_relative, + baseRelative: self.base_relative, + base: self.base, + size: self.size, + truncateSize: self.truncate_size, + nativeType: self.native_type, + addend: self.addend, + hasSign: self.has_sign, + implicitAddend: self.implicit_addend, + external: self.external, + symbolIndex: self.symbol_index, + sectionIndex: self.section_index, + address: self.address, + target: self.target, + dataRelocation: self.data_relocation, + relocationDataCache: self.relocation_data_cache, + prev: core::ptr::null_mut(), + next: core::ptr::null_mut(), + } + } +} + +pub struct Relocation(*mut BNRelocation); + +impl Relocation { + pub(crate) unsafe fn from_raw(reloc: *mut BNRelocation) -> Self { + Relocation(reloc) + } + + pub fn info(&self) -> RelocationInfo { + RelocationInfo::from_raw(unsafe { &BNRelocationGetInfo(self.0) }) + } + + pub fn architecture(&self) -> Option { + let raw = unsafe { BNRelocationGetArchitecture(self.0) }; + if raw.is_null() { + return None; + } + + Some(unsafe { CoreArchitecture::from_raw(raw) }) + } + + pub fn target(&self) -> u64 { + unsafe { BNRelocationGetTarget(self.0) } + } + + pub fn address(&self) -> u64 { + unsafe { BNRelocationGetReloc(self.0) } + } + + pub fn symbol(&self) -> Option> { + let raw = unsafe { BNRelocationGetSymbol(self.0) }; + if raw.is_null() { + return None; + } + + Some(unsafe { Symbol::ref_from_raw(raw) }) + } +} + +impl CoreArrayProvider for Relocation { + type Raw = *mut BNRelocation; + type Context = (); +} + +unsafe impl CoreOwnedArrayProvider 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) + } +} + +pub trait RelocationHandler: 'static + Sized + AsRef { + type Handle: Borrow; + + fn get_relocation_info( + &self, + _bv: &BinaryView, + _arch: &CoreArchitecture, + _info: &mut [RelocationInfo], + ) -> bool { + false + } + + fn apply_relocation( + &self, + bv: &BinaryView, + arch: &CoreArchitecture, + reloc: &Relocation, + dest: &mut [u8], + ) -> bool { + self.default_apply_relocation(bv, arch, reloc, dest) + } + + fn get_operand_for_external_relocation( + &self, + _data: &[u8], + _addr: u64, + _il: &llil::RegularFunction, + _reloc: &Relocation, + ) -> RelocationOperand { + RelocationOperand::AutocoerceExternPtr + } + + fn handle(&self) -> Self::Handle; +} + +pub trait RelocationHandlerExt: RelocationHandler { + fn default_apply_relocation( + &self, + bv: &BinaryView, + arch: &CoreArchitecture, + reloc: &Relocation, + dest: &mut [u8], + ) -> bool { + unsafe { + BNRelocationHandlerDefaultApplyRelocation( + self.as_ref().0, + bv.handle, + arch.handle().as_ref().0, + reloc.0, + dest.as_mut_ptr(), + dest.len(), + ) + } + } +} + +impl RelocationHandlerExt for T {} + +#[derive(Eq, PartialEq, Hash, Debug)] +pub struct CoreRelocationHandler(*mut BNRelocationHandler); + +unsafe impl Send for CoreRelocationHandler {} +unsafe impl Sync for CoreRelocationHandler {} + +impl CoreRelocationHandler { + pub(crate) unsafe fn ref_from_raw(raw: *mut BNRelocationHandler) -> Ref { + unsafe { Ref::new(CoreRelocationHandler(raw)) } + } +} + +impl AsRef for CoreRelocationHandler { + fn as_ref(&self) -> &Self { + self + } +} + +impl RelocationHandler for CoreRelocationHandler { + type Handle = Self; + + fn get_relocation_info( + &self, + bv: &BinaryView, + arch: &CoreArchitecture, + info: &mut [RelocationInfo], + ) -> bool { + let mut raw_info = info.iter().map(|i| i.as_raw()).collect::>(); + let res = unsafe { + BNRelocationHandlerGetRelocationInfo( + self.0, + bv.handle, + arch.handle().as_ref().0, + raw_info.as_mut_ptr(), + raw_info.len(), + ) + }; + for (info, raw) in info.iter_mut().zip(raw_info.iter()) { + *info = RelocationInfo::from_raw(raw); + } + res + } + + fn apply_relocation( + &self, + bv: &BinaryView, + arch: &CoreArchitecture, + reloc: &Relocation, + dest: &mut [u8], + ) -> bool { + unsafe { + BNRelocationHandlerApplyRelocation( + self.0, + bv.handle, + arch.handle().as_ref().0, + reloc.0, + dest.as_mut_ptr(), + dest.len(), + ) + } + } + + fn get_operand_for_external_relocation( + &self, + data: &[u8], + addr: u64, + il: &llil::RegularFunction, + reloc: &Relocation, + ) -> RelocationOperand { + unsafe { + BNRelocationHandlerGetOperandForExternalRelocation( + self.0, + data.as_ptr(), + addr, + data.len(), + il.handle, + reloc.0, + ) + .into() + } + } + + fn handle(&self) -> CoreRelocationHandler { + CoreRelocationHandler(self.0) + } +} + +impl ToOwned for CoreRelocationHandler { + type Owned = Ref; + + fn to_owned(&self) -> Self::Owned { + unsafe { RefCountable::inc_ref(self) } + } +} + +unsafe impl RefCountable for CoreRelocationHandler { + unsafe fn inc_ref(handle: &Self) -> Ref { + Ref::new(Self(BNNewRelocationHandlerReference(handle.0))) + } + + unsafe fn dec_ref(handle: &Self) { + BNFreeRelocationHandler(handle.0); + } +} + +pub(crate) fn register_relocation_handler(arch: &CoreArchitecture, name: S, func: F) +where + S: BnStrCompatible, + R: 'static + RelocationHandler> + Send + Sync + Sized, + F: FnOnce(CustomRelocationHandlerHandle, CoreRelocationHandler) -> R, +{ + #[repr(C)] + struct RelocationHandlerBuilder + where + R: 'static + RelocationHandler> + Send + Sync, + { + handler: R, + } + + extern "C" fn cb_free(ctxt: *mut c_void) + where + R: 'static + RelocationHandler> + Send + Sync, + { + unsafe { + let _handler = Box::from_raw(ctxt as *mut RelocationHandlerBuilder); + } + } + + extern "C" fn cb_get_relocation_info( + ctxt: *mut c_void, + bv: *mut BNBinaryView, + arch: *mut BNArchitecture, + result: *mut BNRelocationInfo, + count: usize, + ) -> bool + where + R: 'static + RelocationHandler> + Send + Sync, + { + let custom_handler = unsafe { &*(ctxt as *mut R) }; + let bv = unsafe { BinaryView::from_raw(BNNewViewReference(bv)) }; + let arch = unsafe { CoreArchitecture::from_raw(arch) }; + let result = unsafe { core::slice::from_raw_parts_mut(result, count) }; + let mut info = result + .iter() + .map(|i| RelocationInfo::from_raw(i)) + .collect::>(); + let ok = + custom_handler.get_relocation_info(bv.as_ref(), arch.as_ref(), info.as_mut_slice()); + for (result, info) in result.iter_mut().zip(info.iter()) { + *result = info.as_raw(); + } + ok + } + + extern "C" fn cb_apply_relocation( + ctxt: *mut c_void, + bv: *mut BNBinaryView, + arch: *mut BNArchitecture, + reloc: *mut BNRelocation, + dest: *mut u8, + len: usize, + ) -> bool + where + R: 'static + RelocationHandler> + Send + Sync, + { + let custom_handler = unsafe { &*(ctxt as *mut R) }; + let bv = unsafe { BinaryView::from_raw(BNNewViewReference(bv)) }; + let arch = unsafe { CoreArchitecture::from_raw(arch) }; + let reloc = unsafe { Relocation::from_raw(reloc) }; + let dest = unsafe { core::slice::from_raw_parts_mut(dest, len) }; + custom_handler.apply_relocation(bv.as_ref(), arch.as_ref(), &reloc, dest) + } + + extern "C" fn cb_get_operand_for_external_relocation( + ctxt: *mut c_void, + data: *const u8, + addr: u64, + len: usize, + il: *mut BNLowLevelILFunction, + reloc: *mut BNRelocation, + ) -> usize + where + R: 'static + RelocationHandler> + Send + Sync, + { + let custom_handler = unsafe { &*(ctxt as *mut R) }; + let data = unsafe { core::slice::from_raw_parts(data, len) }; + let reloc = unsafe { Relocation::from_raw(reloc) }; + + let func = unsafe { BNGetLowLevelILOwnerFunction(il) }; + if func.is_null() { + return RelocationOperand::Invalid.into(); + } + + let arch = unsafe { BNGetFunctionArchitecture(func) }; + unsafe { BNFreeFunction(func) }; + if arch.is_null() { + return RelocationOperand::Invalid.into(); + } + let arch = unsafe { CoreArchitecture::from_raw(arch) }; + + let il = unsafe { llil::RegularFunction::from_raw(arch, il) }; + + custom_handler + .get_operand_for_external_relocation(data, addr, &il, &reloc) + .into() + } + + 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 mut custom_handler = BNCustomRelocationHandler { + context: raw as *mut _, + freeObject: Some(cb_free::), + getRelocationInfo: Some(cb_get_relocation_info::), + applyRelocation: Some(cb_apply_relocation::), + getOperandForExternalRelocation: Some(cb_get_operand_for_external_relocation::), + }; + + let handle_raw = unsafe { BNCreateRelocationHandler(&mut custom_handler) }; + assert!(!handle_raw.is_null()); + let handle = CoreRelocationHandler(handle_raw); + let custom_handle = CustomRelocationHandlerHandle { + handle: raw as *mut R, + }; + unsafe { + core::ptr::write( + &mut raw.as_mut().unwrap().handler, + func(custom_handle, CoreRelocationHandler(handle.0)), + ); + + BNArchitectureRegisterRelocationHandler( + arch.handle().as_ref().0, + name.as_ref().as_ptr() as *const _, + handle.handle().as_ref().0, + ); + } +} + +pub struct CustomRelocationHandlerHandle +where + R: 'static + RelocationHandler> + Send + Sync, +{ + handle: *mut R, +} + +unsafe impl Send for CustomRelocationHandlerHandle where + R: 'static + RelocationHandler> + Send + Sync +{ +} + +unsafe impl Sync for CustomRelocationHandlerHandle where + R: 'static + RelocationHandler> + Send + Sync +{ +} + +impl Clone for CustomRelocationHandlerHandle +where + R: 'static + RelocationHandler + Send + Sync, +{ + fn clone(&self) -> Self { + Self { + handle: self.handle, + } + } +} + +impl Copy for CustomRelocationHandlerHandle where + R: 'static + RelocationHandler + Send + Sync +{ +} + +impl Borrow for CustomRelocationHandlerHandle +where + R: 'static + RelocationHandler + Send + Sync, +{ + fn borrow(&self) -> &R { + unsafe { &*self.handle } + } +} diff --git a/src/section.rs b/src/section.rs new file mode 100644 index 0000000..25e8ea5 --- /dev/null +++ b/src/section.rs @@ -0,0 +1,308 @@ +// Copyright 2021-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. + +//! Sections are [crate::segment::Segment]s that are loaded into memory at run time + +use std::fmt; +use std::ops::Range; + +use binaryninjacore_sys::*; + +use crate::binaryview::BinaryView; +use crate::rc::*; +use crate::string::*; + +#[derive(Copy, Clone, PartialEq, Eq, Hash, Debug)] +pub enum Semantics { + DefaultSection, + ReadOnlyCode, + ReadOnlyData, + ReadWriteData, + External, +} + +impl From for Semantics { + fn from(bn: BNSectionSemantics) -> Self { + use self::BNSectionSemantics::*; + + match bn { + DefaultSectionSemantics => Semantics::DefaultSection, + ReadOnlyCodeSectionSemantics => Semantics::ReadOnlyCode, + ReadOnlyDataSectionSemantics => Semantics::ReadOnlyData, + ReadWriteDataSectionSemantics => Semantics::ReadWriteData, + ExternalSectionSemantics => Semantics::External, + } + } +} + +impl From for BNSectionSemantics { + fn from(semantics: Semantics) -> Self { + use self::BNSectionSemantics::*; + + match semantics { + Semantics::DefaultSection => DefaultSectionSemantics, + Semantics::ReadOnlyCode => ReadOnlyCodeSectionSemantics, + Semantics::ReadOnlyData => ReadOnlyDataSectionSemantics, + Semantics::ReadWriteData => ReadWriteDataSectionSemantics, + Semantics::External => ExternalSectionSemantics, + } + } +} + +#[derive(PartialEq, Eq, Hash)] +pub struct Section { + handle: *mut BNSection, +} + +impl Section { + pub(crate) unsafe fn from_raw(raw: *mut BNSection) -> Self { + Self { handle: raw } + } + + /// 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)) + /// ``` + pub fn builder(name: S, range: Range) -> SectionBuilder { + SectionBuilder::new(name, range) + } + + pub fn name(&self) -> BnString { + unsafe { BnString::from_raw(BNSectionGetName(self.handle)) } + } + + pub fn section_type(&self) -> BnString { + unsafe { BnString::from_raw(BNSectionGetType(self.handle)) } + } + + pub fn start(&self) -> u64 { + unsafe { BNSectionGetStart(self.handle) } + } + + pub fn end(&self) -> u64 { + unsafe { BNSectionGetEnd(self.handle) } + } + + pub fn len(&self) -> usize { + unsafe { BNSectionGetLength(self.handle) as usize } + } + + pub fn is_empty(&self) -> bool { + unsafe { BNSectionGetLength(self.handle) as usize == 0 } + } + + pub fn address_range(&self) -> Range { + self.start()..self.end() + } + + pub fn semantics(&self) -> Semantics { + unsafe { BNSectionGetSemantics(self.handle).into() } + } + + pub fn linked_section(&self) -> BnString { + unsafe { BnString::from_raw(BNSectionGetLinkedSection(self.handle)) } + } + + pub fn info_section(&self) -> BnString { + unsafe { BnString::from_raw(BNSectionGetInfoSection(self.handle)) } + } + + pub fn info_data(&self) -> u64 { + unsafe { BNSectionGetInfoData(self.handle) } + } + + pub fn align(&self) -> u64 { + unsafe { BNSectionGetAlign(self.handle) } + } + + pub fn entry_size(&self) -> usize { + unsafe { BNSectionGetEntrySize(self.handle) as usize } + } + + pub fn auto_defined(&self) -> bool { + unsafe { BNSectionIsAutoDefined(self.handle) } + } +} + +impl fmt::Debug for Section { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!( + f, + "

", + self.name(), + self.start(), + self.end() + ) + } +} + +impl ToOwned for Section { + type Owned = Ref; + + fn to_owned(&self) -> Self::Owned { + unsafe { RefCountable::inc_ref(self) } + } +} + +unsafe impl RefCountable for Section { + unsafe fn inc_ref(handle: &Self) -> Ref { + Ref::new(Self { + handle: BNNewSectionReference(handle.handle), + }) + } + + unsafe fn dec_ref(handle: &Self) { + BNFreeSection(handle.handle); + } +} + +impl CoreArrayProvider for Section { + type Raw = *mut BNSection; + type Context = (); +} + +unsafe impl CoreOwnedArrayProvider 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 { + Guard::new(Section::from_raw(*raw), context) + } +} + +#[must_use] +pub struct SectionBuilder { + is_auto: bool, + name: S, + range: Range, + semantics: Semantics, + _ty: Option, + align: u64, + entry_size: u64, + linked_section: Option, + info_section: Option, + info_data: u64, +} + +impl SectionBuilder { + pub fn new(name: S, range: Range) -> Self { + Self { + is_auto: false, + name, + range, + semantics: Semantics::DefaultSection, + _ty: None, + align: 1, + entry_size: 1, + linked_section: None, + info_section: None, + info_data: 0, + } + } + + pub fn semantics(mut self, semantics: Semantics) -> Self { + self.semantics = semantics; + self + } + + pub fn section_type(mut self, ty: S) -> Self { + self._ty = Some(ty); + self + } + + pub fn align(mut self, align: u64) -> Self { + self.align = align; + self + } + + pub fn entry_size(mut self, entry_size: u64) -> Self { + self.entry_size = entry_size; + self + } + + pub fn linked_section(mut self, linked_section: S) -> Self { + self.linked_section = Some(linked_section); + self + } + + pub fn info_section(mut self, info_section: S) -> Self { + self.info_section = Some(info_section); + self + } + + pub fn info_data(mut self, info_data: u64) -> Self { + self.info_data = info_data; + self + } + + pub fn is_auto(mut self, is_auto: bool) -> Self { + self.is_auto = is_auto; + self + } + + pub(crate) fn create(self, view: &BinaryView) { + let name = self.name.into_bytes_with_nul(); + let ty = self._ty.map(|s| s.into_bytes_with_nul()); + let linked_section = self.linked_section.map(|s| s.into_bytes_with_nul()); + let info_section = self.info_section.map(|s| s.into_bytes_with_nul()); + + let start = self.range.start; + let len = self.range.end.wrapping_sub(start); + + unsafe { + let nul_str = std::ffi::CStr::from_bytes_with_nul_unchecked(b"\x00").as_ptr(); + let name_ptr = name.as_ref().as_ptr() as *mut _; + let ty_ptr = ty.map_or(nul_str, |s| s.as_ref().as_ptr() as *mut _); + let linked_section_ptr = + linked_section.map_or(nul_str, |s| s.as_ref().as_ptr() as *mut _); + let info_section_ptr = info_section.map_or(nul_str, |s| s.as_ref().as_ptr() as *mut _); + + if self.is_auto { + BNAddAutoSection( + view.handle, + name_ptr, + start, + len, + self.semantics.into(), + ty_ptr, + self.align, + self.entry_size, + linked_section_ptr, + info_section_ptr, + self.info_data, + ); + } else { + BNAddUserSection( + view.handle, + name_ptr, + start, + len, + self.semantics.into(), + ty_ptr, + self.align, + self.entry_size, + linked_section_ptr, + info_section_ptr, + self.info_data, + ); + } + } + } +} diff --git a/src/segment.rs b/src/segment.rs new file mode 100644 index 0000000..2de785c --- /dev/null +++ b/src/segment.rs @@ -0,0 +1,218 @@ +// Copyright 2021-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. + +//! Labeled segments in a binary file that aren't loaded in to memory + +use binaryninjacore_sys::*; + +use std::ops::Range; + +use crate::binaryview::BinaryView; +use crate::rc::*; + +fn set_bit(val: u32, bit_mask: u32, new_val: bool) -> u32 { + (val & !bit_mask) | if new_val { bit_mask } else { 0 } +} + +#[must_use] +pub struct SegmentBuilder { + ea: Range, + parent_backing: Option>, + flags: u32, + is_auto: bool, +} + +impl SegmentBuilder { + pub fn new(ea: Range) -> Self { + SegmentBuilder { + ea, + parent_backing: None, + flags: 0, + is_auto: false, + } + } + + pub fn parent_backing(mut self, parent_backing: Range) -> Self { + self.parent_backing = Some(parent_backing); + self + } + + pub fn executable(mut self, executable: bool) -> Self { + self.flags = set_bit(self.flags, 0x01, executable); + self + } + + pub fn writable(mut self, writable: bool) -> Self { + self.flags = set_bit(self.flags, 0x02, writable); + self + } + + pub fn readable(mut self, readable: bool) -> Self { + self.flags = set_bit(self.flags, 0x04, readable); + self + } + + pub fn contains_data(mut self, contains_data: bool) -> Self { + self.flags = set_bit(self.flags, 0x08, contains_data); + self + } + + pub fn contains_code(mut self, contains_code: bool) -> Self { + self.flags = set_bit(self.flags, 0x10, contains_code); + self + } + + pub fn deny_write(mut self, deny_write: bool) -> Self { + self.flags = set_bit(self.flags, 0x20, deny_write); + self + } + + pub fn deny_execute(mut self, deny_execute: bool) -> Self { + self.flags = set_bit(self.flags, 0x40, deny_execute); + self + } + + pub fn is_auto(mut self, is_auto: bool) -> Self { + self.is_auto = is_auto; + self + } + + pub(crate) fn create(self, view: &BinaryView) { + let ea_start = self.ea.start; + let ea_len = self.ea.end.wrapping_sub(ea_start); + let (b_start, b_len) = self + .parent_backing + .map_or((0, 0), |s| (s.start, s.end.wrapping_sub(s.start))); + + unsafe { + if self.is_auto { + BNAddAutoSegment(view.handle, ea_start, ea_len, b_start, b_len, self.flags); + } else { + BNAddUserSegment(view.handle, ea_start, ea_len, b_start, b_len, self.flags); + } + } + } +} + +#[derive(PartialEq, Eq, Hash)] +pub struct Segment { + handle: *mut BNSegment, +} + +impl Segment { + pub(crate) unsafe fn from_raw(raw: *mut BNSegment) -> Self { + Self { handle: raw } + } + + /// 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)) + /// ``` + pub fn builder(ea_range: Range) -> SegmentBuilder { + SegmentBuilder::new(ea_range) + } + + pub fn address_range(&self) -> Range { + let start = unsafe { BNSegmentGetStart(self.handle) }; + let end = unsafe { BNSegmentGetEnd(self.handle) }; + start..end + } + + pub fn parent_backing(&self) -> Option> { + let start = unsafe { BNSegmentGetDataOffset(self.handle) }; + let end = unsafe { BNSegmentGetDataEnd(self.handle) }; + + if start != end { + Some(start..end) + } else { + None + } + } + + fn flags(&self) -> u32 { + unsafe { BNSegmentGetFlags(self.handle) } + } + + pub fn executable(&self) -> bool { + self.flags() & 0x01 != 0 + } + + pub fn writable(&self) -> bool { + self.flags() & 0x02 != 0 + } + + pub fn readable(&self) -> bool { + self.flags() & 0x04 != 0 + } + + pub fn contains_data(&self) -> bool { + self.flags() & 0x08 != 0 + } + + pub fn contains_code(&self) -> bool { + self.flags() & 0x10 != 0 + } + + pub fn deny_write(&self) -> bool { + self.flags() & 0x20 != 0 + } + + pub fn deny_execute(&self) -> bool { + self.flags() & 0x40 != 0 + } + + pub fn auto_defined(&self) -> bool { + unsafe { BNSegmentIsAutoDefined(self.handle) } + } +} + +impl ToOwned for Segment { + type Owned = Ref; + + fn to_owned(&self) -> Self::Owned { + unsafe { RefCountable::inc_ref(self) } + } +} + +unsafe impl RefCountable for Segment { + unsafe fn inc_ref(handle: &Self) -> Ref { + Ref::new(Self { + handle: BNNewSegmentReference(handle.handle), + }) + } + + unsafe fn dec_ref(handle: &Self) { + BNFreeSegment(handle.handle); + } +} + +impl CoreArrayProvider for Segment { + type Raw = *mut BNSegment; + type Context = (); +} + +unsafe impl CoreOwnedArrayProvider 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 { + Guard::new(Segment::from_raw(*raw), context) + } +} diff --git a/src/settings.rs b/src/settings.rs new file mode 100644 index 0000000..3ec3e40 --- /dev/null +++ b/src/settings.rs @@ -0,0 +1,469 @@ +// Copyright 2021-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. + +//! An interface for reading, writing, and creating new settings + +pub use binaryninjacore_sys::BNSettingsScope as SettingsScope; +use binaryninjacore_sys::*; +use std::iter::FromIterator; +use std::os::raw::c_char; + +use crate::binaryview::BinaryView; +use crate::rc::*; +use crate::string::{BnStrCompatible, BnString}; + +use std::ptr; + +#[derive(PartialEq, Eq, Hash)] +pub struct Settings { + pub(crate) handle: *mut BNSettings, +} + +unsafe impl Send for Settings {} +unsafe impl Sync for Settings {} + +impl Settings { + pub(crate) unsafe fn from_raw(handle: *mut BNSettings) -> Ref { + debug_assert!(!handle.is_null()); + + Ref::new(Self { handle }) + } + + pub fn new(instance_id: S) -> Ref { + let instance_id = instance_id.into_bytes_with_nul(); + unsafe { + let handle = BNCreateSettings(instance_id.as_ref().as_ptr() as *mut _); + + debug_assert!(!handle.is_null()); + + Ref::new(Self { handle }) + } + } + + pub fn set_resource_id(&self, resource_id: S) { + let resource_id = resource_id.into_bytes_with_nul(); + unsafe { BNSettingsSetResourceId(self.handle, resource_id.as_ref().as_ptr() as *mut _) }; + } + + pub fn serialize_schema(&self) -> BnString { + unsafe { BnString::from_raw(BNSettingsSerializeSchema(self.handle)) } + } + + pub fn deserialize_schema(&self, schema: S) -> bool { + let schema = schema.into_bytes_with_nul(); + unsafe { + BNSettingsDeserializeSchema( + self.handle, + schema.as_ref().as_ptr() as *mut _, + BNSettingsScope::SettingsAutoScope, + true, + ) + } + } + + pub fn contains(&self, key: S) -> bool { + let key = key.into_bytes_with_nul(); + + unsafe { BNSettingsContains(self.handle, key.as_ref().as_ptr() as *mut _) } + } + + pub fn get_bool( + &self, + key: S, + view: Option<&BinaryView>, + scope: Option>, + ) -> bool { + let key = key.into_bytes_with_nul(); + let view_handle = match view { + Some(view) => view.handle, + _ => ptr::null_mut() as *mut _, + }; + let scope_ptr = match scope { + Some(mut scope) => scope.as_mut(), + _ => ptr::null_mut() as *mut _, + }; + unsafe { + BNSettingsGetBool( + self.handle, + key.as_ref().as_ptr() as *mut _, + view_handle, + scope_ptr, + ) + } + } + + pub fn get_double( + &self, + key: S, + view: Option<&BinaryView>, + scope: Option>, + ) -> f64 { + let key = key.into_bytes_with_nul(); + let view_handle = match view { + Some(view) => view.handle, + _ => ptr::null_mut() as *mut _, + }; + let scope_ptr = match scope { + Some(mut scope) => scope.as_mut(), + _ => ptr::null_mut() as *mut _, + }; + unsafe { + BNSettingsGetDouble( + self.handle, + key.as_ref().as_ptr() as *mut _, + view_handle, + scope_ptr, + ) + } + } + + pub fn get_integer( + &self, + key: S, + view: Option<&BinaryView>, + scope: Option>, + ) -> u64 { + let key = key.into_bytes_with_nul(); + let view_handle = match view { + Some(view) => view.handle, + _ => ptr::null_mut() as *mut _, + }; + let scope_ptr = match scope { + Some(mut scope) => scope.as_mut(), + _ => ptr::null_mut() as *mut _, + }; + unsafe { + BNSettingsGetUInt64( + self.handle, + key.as_ref().as_ptr() as *mut _, + view_handle, + scope_ptr, + ) + } + } + + pub fn get_string( + &self, + key: S, + view: Option<&BinaryView>, + scope: Option>, + ) -> BnString { + let key = key.into_bytes_with_nul(); + let view_handle = match view { + Some(view) => view.handle, + _ => ptr::null_mut() as *mut _, + }; + let scope_ptr = match scope { + Some(mut scope) => scope.as_mut(), + _ => ptr::null_mut() as *mut _, + }; + unsafe { + BnString::from_raw(BNSettingsGetString( + self.handle, + key.as_ref().as_ptr() as *mut _, + view_handle, + scope_ptr, + )) + } + } + + pub fn get_string_list( + &self, + key: S, + view: Option<&BinaryView>, + scope: Option>, + ) -> Array { + let key = key.into_bytes_with_nul(); + let view_handle = match view { + Some(view) => view.handle, + _ => ptr::null_mut() as *mut _, + }; + let scope_ptr = match scope { + Some(mut scope) => scope.as_mut(), + _ => ptr::null_mut() as *mut _, + }; + let mut size: usize = 0; + unsafe { + Array::new( + BNSettingsGetStringList( + self.handle, + key.as_ref().as_ptr() as *mut _, + view_handle, + scope_ptr, + &mut size, + ) as *mut *mut c_char, + size, + (), + ) + } + } + + pub fn get_json( + &self, + key: S, + view: Option<&BinaryView>, + scope: Option>, + ) -> BnString { + let key = key.into_bytes_with_nul(); + let view_handle = match view { + Some(view) => view.handle, + _ => ptr::null_mut() as *mut _, + }; + let scope_ptr = match scope { + Some(mut scope) => scope.as_mut(), + _ => ptr::null_mut() as *mut _, + }; + unsafe { + BnString::from_raw(BNSettingsGetJson( + self.handle, + key.as_ref().as_ptr() as *mut _, + view_handle, + scope_ptr, + )) + } + } + + pub fn set_bool( + &self, + key: S, + value: bool, + view: Option<&BinaryView>, + scope: Option, + ) { + let key = key.into_bytes_with_nul(); + let view_handle = match view { + Some(view) => view.handle, + _ => ptr::null_mut() as *mut _, + }; + let scope = match scope { + Some(scope) => scope, + _ => SettingsScope::SettingsAutoScope, + }; + unsafe { + BNSettingsSetBool( + self.handle, + view_handle, + scope, + key.as_ref().as_ptr() as *mut _, + value, + ); + } + } + + pub fn set_double( + &self, + key: S, + value: f64, + view: Option<&BinaryView>, + scope: Option, + ) { + let key = key.into_bytes_with_nul(); + let view_handle = match view { + Some(view) => view.handle, + _ => ptr::null_mut() as *mut _, + }; + let scope = match scope { + Some(scope) => scope, + _ => SettingsScope::SettingsAutoScope, + }; + unsafe { + BNSettingsSetDouble( + self.handle, + view_handle, + scope, + key.as_ref().as_ptr() as *mut _, + value, + ); + } + } + + pub fn set_integer( + &self, + key: S, + value: u64, + view: Option<&BinaryView>, + scope: Option, + ) { + let key = key.into_bytes_with_nul(); + let view_handle = match view { + Some(view) => view.handle, + _ => ptr::null_mut() as *mut _, + }; + let scope = match scope { + Some(scope) => scope, + _ => SettingsScope::SettingsAutoScope, + }; + unsafe { + BNSettingsSetUInt64( + self.handle, + view_handle, + scope, + key.as_ref().as_ptr() as *mut _, + value, + ); + } + } + + pub fn set_string( + &self, + key: S1, + value: S2, + view: Option<&BinaryView>, + scope: Option, + ) { + let key = key.into_bytes_with_nul(); + let value = value.into_bytes_with_nul(); + let view_handle = match view { + Some(view) => view.handle, + _ => ptr::null_mut() as *mut _, + }; + let scope = match scope { + Some(scope) => scope, + _ => SettingsScope::SettingsAutoScope, + }; + unsafe { + BNSettingsSetString( + self.handle, + view_handle, + scope, + key.as_ref().as_ptr() as *mut _, + value.as_ref().as_ptr() as *mut _, + ); + } + } + + pub fn set_string_list>( + &self, + key: S1, + value: I, + view: Option<&BinaryView>, + scope: Option, + ) -> bool { + let key = key.into_bytes_with_nul(); + let v = Vec::from_iter(value); + + let mut value = vec![]; + for item in v { + value.push(item.into_bytes_with_nul().as_ref().as_ptr() as *const c_char); + } + + let view_handle = match view { + Some(view) => view.handle, + None => ptr::null_mut() as *mut _, + }; + + let scope = match scope { + Some(scope) => scope, + None => SettingsScope::SettingsAutoScope, + }; + + unsafe { + BNSettingsSetStringList( + self.handle, + view_handle, + scope, + key.as_ref().as_ptr() as *mut _, + value.as_mut_ptr(), + value.len(), + ) + } + } + + pub fn set_json( + &self, + key: S1, + value: S2, + view: Option<&BinaryView>, + scope: Option, + ) -> bool { + let key = key.into_bytes_with_nul(); + let value = value.into_bytes_with_nul(); + + let view_handle = match view { + Some(view) => view.handle, + None => ptr::null_mut() as *mut _, + }; + + let scope = match scope { + Some(scope) => scope, + None => SettingsScope::SettingsAutoScope, + }; + + unsafe { + BNSettingsSetJson( + self.handle, + view_handle, + scope, + key.as_ref().as_ptr() as *mut _, + value.as_ref().as_ptr() as *mut _, + ) + } + } + + pub fn register_group( + &self, + group: S1, + title: S2, + ) -> bool { + let group = group.into_bytes_with_nul(); + let title = title.into_bytes_with_nul(); + + unsafe { + BNSettingsRegisterGroup( + self.handle, + group.as_ref().as_ptr() as *mut _, + title.as_ref().as_ptr() as *mut _, + ) + } + } + + pub fn register_setting_json( + &self, + group: S1, + properties: S2, + ) -> bool { + let group = group.into_bytes_with_nul(); + let properties = properties.into_bytes_with_nul(); + + unsafe { + BNSettingsRegisterSetting( + self.handle, + group.as_ref().as_ptr() as *mut _, + properties.as_ref().as_ptr() as *mut _, + ) + } + } + + // TODO: register_setting but type-safely turn it into json +} + +impl ToOwned for Settings { + type Owned = Ref; + + fn to_owned(&self) -> Self::Owned { + unsafe { RefCountable::inc_ref(self) } + } +} + +unsafe impl RefCountable for Settings { + unsafe fn inc_ref(handle: &Self) -> Ref { + Ref::new(Self { + handle: BNNewSettingsReference(handle.handle), + }) + } + + unsafe fn dec_ref(handle: &Self) { + BNFreeSettings(handle.handle); + } +} diff --git a/src/string.rs b/src/string.rs new file mode 100644 index 0000000..55521f0 --- /dev/null +++ b/src/string.rs @@ -0,0 +1,296 @@ +// Copyright 2021-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. + +//! String wrappers for core-owned strings and strings being passed to the core + +use std::borrow::{Borrow, Cow}; +use std::ffi::{CStr, CString}; +use std::fmt; +use std::hash::{Hash, Hasher}; +use std::mem; +use std::ops::Deref; +use std::os::raw; + +use crate::rc::*; +use crate::types::QualifiedName; + +pub(crate) fn raw_to_string(ptr: *const raw::c_char) -> Option { + if ptr.is_null() { + None + } else { + Some(unsafe { CStr::from_ptr(ptr).to_string_lossy().into_owned() }) + } +} + +/// 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)] +pub struct BnString { + raw: *mut raw::c_char, +} + +/// A nul-terminated C string allocated by the core. +/// +/// Received from a variety of core function calls, and +/// must be used when giving strings to the core from many +/// core-invoked callbacks. +/// +/// These are strings we're responsible for freeing, such as +/// strings allocated by the core and given to us through the API +/// and then forgotten about by the core. +impl BnString { + pub fn new(s: S) -> Self { + use binaryninjacore_sys::BNAllocString; + + let raw = s.into_bytes_with_nul(); + + unsafe { + let ptr = raw.as_ref().as_ptr() as *mut _; + + Self { + raw: BNAllocString(ptr), + } + } + } + + /// Construct a BnString from an owned const char* allocated by BNAllocString + pub(crate) unsafe fn from_raw(raw: *mut raw::c_char) -> Self { + Self { raw } + } + + pub(crate) fn into_raw(self) -> *mut raw::c_char { + let res = self.raw; + + // we're surrendering ownership over the *mut c_char to + // the core, so ensure we don't free it + mem::forget(self); + + res + } + + pub fn as_str(&self) -> &str { + unsafe { BnStr::from_raw(self.raw).as_str() } + } +} + +impl Drop for BnString { + fn drop(&mut self) { + use binaryninjacore_sys::BNFreeString; + + unsafe { + BNFreeString(self.raw); + } + } +} + +impl Clone for BnString { + fn clone(&self) -> Self { + use binaryninjacore_sys::BNAllocString; + unsafe { + Self { + raw: BNAllocString(self.raw), + } + } + } +} + +impl Deref for BnString { + type Target = BnStr; + + fn deref(&self) -> &BnStr { + unsafe { BnStr::from_raw(self.raw) } + } +} + +impl AsRef<[u8]> for BnString { + fn as_ref(&self) -> &[u8] { + self.as_cstr().to_bytes_with_nul() + } +} + +impl Hash for BnString { + fn hash(&self, state: &mut H) { + self.raw.hash(state) + } +} + +impl PartialEq for BnString { + fn eq(&self, other: &Self) -> bool { + self.deref() == other.deref() + } +} + +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()) + } +} + +impl fmt::Debug for BnString { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "{}", self.as_cstr().to_string_lossy()) + } +} + +impl CoreArrayProvider for BnString { + type Raw = *mut raw::c_char; + type Context = (); +} + +unsafe impl CoreOwnedArrayProvider 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) + } +} + +pub unsafe trait BnStrCompatible { + type Result: AsRef<[u8]>; + fn into_bytes_with_nul(self) -> Self::Result; +} + +unsafe impl<'a> BnStrCompatible for &'a BnStr { + type Result = &'a [u8]; + + fn into_bytes_with_nul(self) -> Self::Result { + self.as_cstr().to_bytes_with_nul() + } +} + +unsafe impl BnStrCompatible for BnString { + type Result = Self; + + fn into_bytes_with_nul(self) -> Self::Result { + self + } +} + +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; + + fn into_bytes_with_nul(self) -> Self::Result { + self.into_bytes_with_nul() + } +} + +unsafe impl<'a> BnStrCompatible for &'a str { + type Result = Vec; + + fn into_bytes_with_nul(self) -> Self::Result { + let ret = CString::new(self).expect("can't pass strings with internal nul bytes to core!"); + ret.into_bytes_with_nul() + } +} + +unsafe impl BnStrCompatible for String { + type Result = Vec; + + fn into_bytes_with_nul(self) -> Self::Result { + self.as_str().into_bytes_with_nul() + } +} + +unsafe impl<'a> BnStrCompatible for &'a String { + type Result = Vec; + + fn into_bytes_with_nul(self) -> Self::Result { + self.as_str().into_bytes_with_nul() + } +} + +unsafe impl<'a> BnStrCompatible for &'a Cow<'a, str> { + type Result = Vec; + + fn into_bytes_with_nul(self) -> Self::Result { + self.to_string().into_bytes_with_nul() + } +} + +unsafe impl BnStrCompatible for &QualifiedName { + type Result = Vec; + + fn into_bytes_with_nul(self) -> Self::Result { + self.string().into_bytes_with_nul() + } +} diff --git a/src/symbol.rs b/src/symbol.rs new file mode 100644 index 0000000..25801ee --- /dev/null +++ b/src/symbol.rs @@ -0,0 +1,355 @@ +// Copyright 2021-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. + +//! Interfaces for the various kinds of symbols in a binary. + +use std::fmt; +use std::hash::{Hash, Hasher}; +use std::ptr; + +use crate::rc::*; +use crate::string::*; +use binaryninjacore_sys::*; + +// TODO : Rename +#[derive(Copy, Clone, PartialEq, Eq, Hash, Debug)] +pub enum SymbolType { + Function, + LibraryFunction, + ImportAddress, + ImportedFunction, + Data, + ImportedData, + External, + Symbolic, + LocalLabel, +} + +impl From for SymbolType { + fn from(bn: BNSymbolType) -> SymbolType { + use self::BNSymbolType::*; + + match bn { + FunctionSymbol => SymbolType::Function, + LibraryFunctionSymbol => SymbolType::LibraryFunction, + ImportAddressSymbol => SymbolType::ImportAddress, + ImportedFunctionSymbol => SymbolType::ImportedFunction, + DataSymbol => SymbolType::Data, + ImportedDataSymbol => SymbolType::ImportedData, + ExternalSymbol => SymbolType::External, + SymbolicFunctionSymbol => SymbolType::Symbolic, + LocalLabelSymbol => SymbolType::LocalLabel, + } + } +} + +impl From for BNSymbolType { + fn from(symbol_type: SymbolType) -> Self { + use self::BNSymbolType::*; + + match symbol_type { + SymbolType::Function => FunctionSymbol, + SymbolType::LibraryFunction => LibraryFunctionSymbol, + SymbolType::ImportAddress => ImportAddressSymbol, + SymbolType::ImportedFunction => ImportedFunctionSymbol, + SymbolType::Data => DataSymbol, + SymbolType::ImportedData => ImportedDataSymbol, + SymbolType::External => ExternalSymbol, + SymbolType::Symbolic => SymbolicFunctionSymbol, + SymbolType::LocalLabel => LocalLabelSymbol, + } + } +} + +#[derive(Copy, Clone, PartialEq, Eq, Hash, Debug)] +pub enum Binding { + None, + Local, + Global, + Weak, +} + +impl From for Binding { + fn from(bn: BNSymbolBinding) -> Binding { + use self::BNSymbolBinding::*; + + match bn { + NoBinding => Binding::None, + LocalBinding => Binding::Local, + GlobalBinding => Binding::Global, + WeakBinding => Binding::Weak, + } + } +} + +impl From for BNSymbolBinding { + fn from(binding: Binding) -> Self { + use self::BNSymbolBinding::*; + + match binding { + Binding::None => NoBinding, + Binding::Local => LocalBinding, + Binding::Global => GlobalBinding, + Binding::Weak => WeakBinding, + } + } +} + +// TODO : Clean this up +#[must_use] +pub struct SymbolBuilder { + ty: SymbolType, + binding: Binding, + addr: u64, + raw_name: String, + short_name: Option, + full_name: Option, + ordinal: u64, +} + +impl SymbolBuilder { + pub fn new(ty: SymbolType, raw_name: &str, addr: u64) -> Self { + Self { + ty, + binding: Binding::None, + addr, + raw_name: raw_name.to_owned(), + short_name: None, + full_name: None, + ordinal: 0, + } + } + + pub fn binding(mut self, binding: Binding) -> Self { + self.binding = binding; + self + } + + pub fn short_name(mut self, short_name: &str) -> Self { + self.short_name = Some(short_name.to_owned()); + self + } + + pub fn full_name(mut self, full_name: &str) -> Self { + self.full_name = Some(full_name.to_owned()); + self + } + + pub fn ordinal(mut self, ordinal: u64) -> Self { + self.ordinal = ordinal; + self + } + + pub fn create(self) -> Ref { + let raw_name = self.raw_name.into_bytes_with_nul(); + let short_name = self.short_name.map(|s| s.into_bytes_with_nul()); + let full_name = self.full_name.map(|s| s.into_bytes_with_nul()); + + // Lifetimes, man + let raw_name = raw_name.as_ptr() as _; + unsafe { + if let Some(short_name) = short_name { + if let Some(full_name) = full_name { + let res = BNCreateSymbol( + self.ty.into(), + short_name.as_ptr() as _, + full_name.as_ptr() as _, + raw_name, + self.addr, + self.binding.into(), + ptr::null_mut(), + self.ordinal, + ); + Symbol::ref_from_raw(res) + } else { + let res = BNCreateSymbol( + self.ty.into(), + short_name.as_ptr() as _, + raw_name, + raw_name, + self.addr, + self.binding.into(), + ptr::null_mut(), + self.ordinal, + ); + Symbol::ref_from_raw(res) + } + } else if let Some(full_name) = full_name { + let res = BNCreateSymbol( + self.ty.into(), + raw_name, + full_name.as_ptr() as _, + raw_name, + self.addr, + self.binding.into(), + ptr::null_mut(), + self.ordinal, + ); + Symbol::ref_from_raw(res) + } else { + let res = BNCreateSymbol( + self.ty.into(), + raw_name, + raw_name, + raw_name, + self.addr, + self.binding.into(), + ptr::null_mut(), + self.ordinal, + ); + Symbol::ref_from_raw(res) + } + } + } +} + +#[derive(Eq)] +pub struct Symbol { + pub(crate) handle: *mut BNSymbol, +} + +impl Symbol { + pub(crate) unsafe fn ref_from_raw(raw: *mut BNSymbol) -> Ref { + Ref::new(Self { handle: raw }) + } + + pub(crate) unsafe fn from_raw(raw: *mut BNSymbol) -> Self { + Self { handle: raw } + } + + /// 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(); + /// ``` + pub fn builder(ty: SymbolType, raw_name: &str, addr: u64) -> SymbolBuilder { + SymbolBuilder::new(ty, raw_name, addr) + } + + pub fn sym_type(&self) -> SymbolType { + unsafe { BNGetSymbolType(self.handle).into() } + } + + pub fn binding(&self) -> Binding { + unsafe { BNGetSymbolBinding(self.handle).into() } + } + + pub fn full_name(&self) -> BnString { + unsafe { + let name = BNGetSymbolFullName(self.handle); + BnString::from_raw(name) + } + } + + pub fn short_name(&self) -> BnString { + unsafe { + let name = BNGetSymbolShortName(self.handle); + BnString::from_raw(name) + } + } + + pub fn raw_name(&self) -> BnString { + unsafe { + let name = BNGetSymbolRawName(self.handle); + BnString::from_raw(name) + } + } + + pub fn address(&self) -> u64 { + unsafe { BNGetSymbolAddress(self.handle) } + } + + pub fn auto_defined(&self) -> bool { + unsafe { BNIsSymbolAutoDefined(self.handle) } + } + + /// Wether this symbol has external linkage + pub fn external(&self) -> bool { + self.binding() == Binding::Weak || self.binding() == Binding::Global + } + + pub fn imported_function_from_import_address_symbol(sym: &Symbol, addr: u64) -> Ref { + unsafe { + let res = BNImportedFunctionFromImportAddressSymbol(sym.handle, addr); + Symbol::ref_from_raw(res) + } + } +} + +unsafe impl Send for Symbol {} +unsafe impl Sync for Symbol {} + +impl fmt::Debug for Symbol { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!( + f, + "", + self.sym_type(), + self.full_name(), + self.address(), + self.handle + ) + } +} + +impl ToOwned for Symbol { + type Owned = Ref; + + fn to_owned(&self) -> Self::Owned { + unsafe { RefCountable::inc_ref(self) } + } +} + +unsafe impl RefCountable for Symbol { + unsafe fn inc_ref(handle: &Self) -> Ref { + Ref::new(Self { + handle: BNNewSymbolReference(handle.handle), + }) + } + + unsafe fn dec_ref(handle: &Self) { + BNFreeSymbol(handle.handle); + } +} + +impl CoreArrayProvider for Symbol { + type Raw = *mut BNSymbol; + type Context = (); +} + +unsafe impl CoreOwnedArrayProvider 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 { + Guard::new(Symbol::from_raw(*raw), context) + } +} + +impl Hash for Symbol { + fn hash(&self, state: &mut H) { + self.handle.hash(state); + } +} + +impl PartialEq for Symbol { + fn eq(&self, other: &Self) -> bool { + self.handle == other.handle + } +} diff --git a/src/tags.rs b/src/tags.rs new file mode 100644 index 0000000..65680b8 --- /dev/null +++ b/src/tags.rs @@ -0,0 +1,178 @@ +// 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. + +//! Interfaces for creating and modifying tags in a BinaryView. + +use binaryninjacore_sys::*; + +use crate::binaryview::BinaryView; + +use crate::rc::*; +use crate::string::*; + +pub struct Tag { + pub(crate) handle: *mut BNTag, +} + +impl Tag { + pub(crate) unsafe fn from_raw(handle: *mut BNTag) -> Ref { + debug_assert!(!handle.is_null()); + + Ref::new(Self { handle }) + } + + pub fn new(t: &TagType, data: S) -> Ref { + let data = data.into_bytes_with_nul(); + unsafe { Self::from_raw(BNCreateTag(t.handle, data.as_ref().as_ptr() as *mut _)) } + } + + pub fn id(&self) -> BnString { + unsafe { BnString::from_raw(BNTagGetId(self.handle)) } + } + + pub fn data(&self) -> BnString { + unsafe { BnString::from_raw(BNTagGetData(self.handle)) } + } + + pub fn t(&self) -> Ref { + unsafe { TagType::from_raw(BNTagGetType(self.handle)) } + } + + pub fn set_data(&self, data: S) { + let data = data.into_bytes_with_nul(); + unsafe { + BNTagSetData(self.handle, data.as_ref().as_ptr() as *mut _); + } + } +} + +unsafe impl RefCountable for Tag { + unsafe fn inc_ref(handle: &Self) -> Ref { + Ref::new(Self { + handle: BNNewTagReference(handle.handle), + }) + } + + unsafe fn dec_ref(handle: &Self) { + BNFreeTag(handle.handle); + } +} + +impl ToOwned for Tag { + type Owned = Ref; + + fn to_owned(&self) -> Self::Owned { + unsafe { RefCountable::inc_ref(self) } + } +} + +unsafe impl Send for Tag {} +unsafe impl Sync for Tag {} + +pub type TagTypeType = BNTagTypeType; + +pub struct TagType { + pub(crate) handle: *mut BNTagType, +} + +impl TagType { + pub(crate) unsafe fn from_raw(handle: *mut BNTagType) -> Ref { + debug_assert!(!handle.is_null()); + + Ref::new(Self { handle }) + } + + pub fn create( + view: &BinaryView, + name: N, + icon: I, + ) -> Ref { + let tag_type = unsafe { Self::from_raw(BNCreateTagType(view.handle)) }; + tag_type.set_name(name); + tag_type.set_icon(icon); + tag_type + } + + pub fn id(&self) -> BnString { + unsafe { BnString::from_raw(BNTagTypeGetId(self.handle)) } + } + + pub fn icon(&self) -> BnString { + unsafe { BnString::from_raw(BNTagTypeGetIcon(self.handle)) } + } + + 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 _); + } + } + + pub fn name(&self) -> BnString { + unsafe { BnString::from_raw(BNTagTypeGetName(self.handle)) } + } + + pub fn set_name(&self, name: S) { + let name = name.into_bytes_with_nul(); + unsafe { + BNTagTypeSetName(self.handle, name.as_ref().as_ptr() as *mut _); + } + } + + pub fn visible(&self) -> bool { + unsafe { BNTagTypeGetVisible(self.handle) } + } + + pub fn set_visible(&self, visible: bool) { + unsafe { BNTagTypeSetVisible(self.handle, visible) } + } + + pub fn t(&self) -> TagTypeType { + unsafe { BNTagTypeGetType(self.handle) } + } + + pub fn set_type(&self, t: S) { + let t = t.into_bytes_with_nul(); + unsafe { + BNTagTypeSetName(self.handle, t.as_ref().as_ptr() as *mut _); + } + } + + pub fn view(&self) -> Ref { + unsafe { BinaryView::from_raw(BNTagTypeGetView(self.handle)) } + } +} + +unsafe impl RefCountable for TagType { + unsafe fn inc_ref(handle: &Self) -> Ref { + Ref::new(Self { + handle: BNNewTagTypeReference(handle.handle), + }) + } + + unsafe fn dec_ref(handle: &Self) { + BNFreeTagType(handle.handle); + } +} + +impl ToOwned for TagType { + type Owned = Ref; + + fn to_owned(&self) -> Self::Owned { + unsafe { RefCountable::inc_ref(self) } + } +} + +unsafe impl Send for TagType {} +unsafe impl Sync for TagType {} diff --git a/src/templatesimplifier.rs b/src/templatesimplifier.rs new file mode 100644 index 0000000..5959167 --- /dev/null +++ b/src/templatesimplifier.rs @@ -0,0 +1,20 @@ +use crate::{ + string::{BnStrCompatible, BnString}, + types::QualifiedName, +}; +use binaryninjacore_sys::{BNRustSimplifyStrToFQN, BNRustSimplifyStrToStr}; + +pub fn simplify_str_to_str(input: S) -> BnString { + let name = input.into_bytes_with_nul(); + unsafe { BnString::from_raw(BNRustSimplifyStrToStr(name.as_ref().as_ptr() as *mut _)) } +} + +pub fn simplify_str_to_fqn(input: S, simplify: bool) -> QualifiedName { + let name = input.into_bytes_with_nul(); + unsafe { + QualifiedName(BNRustSimplifyStrToFQN( + name.as_ref().as_ptr() as *mut _, + simplify, + )) + } +} diff --git a/src/types.rs b/src/types.rs new file mode 100644 index 0000000..2d67522 --- /dev/null +++ b/src/types.rs @@ -0,0 +1,2607 @@ +// Copyright 2021-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. + +// TODO : More widely enforce the use of ref_from_raw vs just from_raw to simplify internal binding usage? Perhaps remove from_raw functions? +// TODO : Add documentation and fix examples +// TODO : Test the get_enumeration and get_structure methods + +use binaryninjacore_sys::*; + +use crate::{ + architecture::{Architecture, CoreArchitecture}, + binaryview::{BinaryView, BinaryViewExt}, + callingconvention::CallingConvention, + filemetadata::FileMetadata, + rc::*, + string::{raw_to_string, BnStr, BnStrCompatible, BnString}, + symbol::Symbol, +}; + +use lazy_static::lazy_static; +use std::{ + borrow::Cow, + collections::HashSet, + ffi::CStr, + fmt, + fmt::{Debug, Display, Formatter}, + hash::{Hash, Hasher}, + iter::{zip, IntoIterator}, + mem, + mem::ManuallyDrop, + os::raw::c_char, + ptr, result, slice, + sync::Mutex, +}; + +pub type Result = result::Result; + +pub type ReferenceType = BNReferenceType; +pub type TypeClass = BNTypeClass; +pub type NamedTypeReferenceClass = BNNamedTypeReferenceClass; +pub type MemberAccess = BNMemberAccess; +pub type MemberScope = BNMemberScope; + +//////////////// +// Confidence + +pub struct Conf { + pub contents: T, + pub confidence: u8, +} + +pub trait ConfMergable { + type Result; + /// Merge two confidence types' values depending on whichever has higher confidence + /// In the event of a tie, the LHS (caller's) value is used. + fn merge(self, other: O) -> Self::Result; +} + +impl Conf { + pub fn new(contents: T, confidence: u8) -> Self { + Self { + contents, + confidence, + } + } + + pub fn map(self, f: F) -> Conf + where + F: FnOnce(T) -> U, + { + Conf::new(f(self.contents), self.confidence) + } + + pub fn as_ref(&self) -> Conf<&U> + where + T: AsRef, + { + Conf::new(self.contents.as_ref(), self.confidence) + } +} + +/// Returns best value or LHS on tie +/// +/// `Conf` + `Conf` → `Conf` +impl ConfMergable> for Conf { + type Result = Conf; + fn merge(self, other: Conf) -> Conf { + if other.confidence > self.confidence { + other + } else { + self + } + } +} + +/// Returns LHS if RHS is None +/// +/// `Conf` + `Option>` → `Conf` +impl ConfMergable>> for Conf { + type Result = Conf; + fn merge(self, other: Option>) -> Conf { + match other { + Some(c @ Conf { confidence, .. }) if confidence > self.confidence => c, + _ => self, + } + } +} + +/// Returns RHS if LHS is None +/// +/// `Option>` + `Conf` → `Conf` +impl ConfMergable> for Option> { + type Result = Conf; + fn merge(self, other: Conf) -> Conf { + match self { + Some(c @ Conf { confidence, .. }) if confidence >= other.confidence => c, + _ => other, + } + } +} + +/// Returns best non-None value or None +/// +/// `Option>` + `Option>` → `Option>` +impl ConfMergable>> for Option> { + type Result = Option>; + fn merge(self, other: Option>) -> Option> { + match (self, other) { + ( + Some( + this @ Conf { + confidence: this_confidence, + .. + }, + ), + Some( + other @ Conf { + confidence: other_confidence, + .. + }, + ), + ) => { + if this_confidence >= other_confidence { + Some(this) + } else { + Some(other) + } + } + (None, Some(c)) => Some(c), + (Some(c), None) => Some(c), + (None, None) => None, + } + } +} + +impl Debug for Conf { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + write!(f, "{:?} ({} confidence)", self.contents, self.confidence) + } +} + +impl Display for Conf { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + write!(f, "{} ({} confidence)", self.contents, self.confidence) + } +} + +impl<'a, T> From<&'a Conf> for Conf<&'a T> { + fn from(c: &'a Conf) -> Self { + Conf::new(&c.contents, c.confidence) + } +} + +impl<'a, T: RefCountable> From<&'a Conf>> for Conf<&'a T> { + fn from(c: &'a Conf>) -> Self { + Conf::new(c.contents.as_ref(), c.confidence) + } +} + +impl<'a, T: RefCountable> From<&'a Ref> for Conf<&'a T> { + fn from(r: &'a Ref) -> Self { + r.as_ref().into() + } +} + +#[inline] +pub fn min_confidence() -> u8 { + u8::MIN +} + +#[inline] +pub fn max_confidence() -> u8 { + u8::MAX +} + +impl Clone for Conf { + fn clone(&self) -> Self { + Self { + contents: self.contents.clone(), + confidence: self.confidence, + } + } +} + +impl From for Conf { + fn from(contents: T) -> Self { + Self::new(contents, max_confidence()) + } +} + +impl From for Conf> { + fn from(type_with_confidence: BNTypeWithConfidence) -> Self { + Self::new( + unsafe { Type::ref_from_raw(type_with_confidence.type_) }, + type_with_confidence.confidence, + ) + } +} + +impl From for Conf { + fn from(bool_with_confidence: BNBoolWithConfidence) -> Self { + Self::new(bool_with_confidence.value, bool_with_confidence.confidence) + } +} + +impl From for Conf>> { + fn from(cc_with_confidence: BNCallingConventionWithConfidence) -> Self { + Self::new( + unsafe { + CallingConvention::ref_from_raw( + cc_with_confidence.convention, + CoreArchitecture::from_raw(BNGetCallingConventionArchitecture( + cc_with_confidence.convention, + )), + ) + }, + cc_with_confidence.confidence, + ) + } +} + +impl From for Conf { + fn from(offset_with_confidence: BNOffsetWithConfidence) -> Self { + Self::new( + offset_with_confidence.value, + offset_with_confidence.confidence, + ) + } +} + +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 { + type_: conf.contents.handle, + confidence: conf.confidence, + } + } +} + +impl From> for BNBoolWithConfidence { + fn from(conf: Conf) -> Self { + Self { + value: conf.contents, + confidence: conf.confidence, + } + } +} + +impl From>> for BNCallingConventionWithConfidence { + fn from(conf: Conf<&CallingConvention>) -> Self { + Self { + convention: conf.contents.handle, + confidence: conf.confidence, + } + } +} + +impl From> for BNOffsetWithConfidence { + fn from(conf: Conf) -> Self { + Self { + value: conf.contents, + confidence: conf.confidence, + } + } +} + +////////////////// +// Type Builder + +#[derive(PartialEq, Eq, Hash)] +pub struct TypeBuilder { + pub(crate) handle: *mut BNTypeBuilder, +} + +impl TypeBuilder { + pub fn new(t: &Type) -> Self { + unsafe { Self::from_raw(BNCreateTypeBuilderFromType(t.handle)) } + } + + pub(crate) unsafe fn from_raw(handle: *mut BNTypeBuilder) -> Self { + debug_assert!(!handle.is_null()); + Self { handle } + } + + // Chainable terminal + pub fn finalize(&self) -> Ref { + unsafe { Type::ref_from_raw(BNFinalizeTypeBuilder(self.handle)) } + } + + // Settable properties + + pub fn set_can_return>>(&self, value: T) -> &Self { + let mut bool_with_confidence = value.into().into(); + unsafe { BNSetFunctionTypeBuilderCanReturn(self.handle, &mut bool_with_confidence) }; + self + } + + pub fn set_pure>>(&self, value: T) -> &Self { + let mut bool_with_confidence = value.into().into(); + unsafe { BNSetTypeBuilderPure(self.handle, &mut bool_with_confidence) }; + self + } + + pub fn set_const>>(&self, value: T) -> &Self { + let mut bool_with_confidence = value.into().into(); + unsafe { BNTypeBuilderSetConst(self.handle, &mut bool_with_confidence) }; + self + } + + pub fn set_volatile>>(&self, value: T) -> &Self { + let mut bool_with_confidence = value.into().into(); + unsafe { BNTypeBuilderSetVolatile(self.handle, &mut bool_with_confidence) }; + self + } + + // Readable properties + + pub fn type_class(&self) -> TypeClass { + unsafe { BNGetTypeBuilderClass(self.handle) } + } + + pub fn width(&self) -> u64 { + unsafe { BNGetTypeBuilderWidth(self.handle) } + } + + pub fn alignment(&self) -> usize { + unsafe { BNGetTypeBuilderAlignment(self.handle) } + } + + pub fn is_signed(&self) -> Conf { + unsafe { BNIsTypeBuilderSigned(self.handle).into() } + } + + pub fn is_const(&self) -> Conf { + unsafe { BNIsTypeBuilderConst(self.handle).into() } + } + + pub fn is_volatile(&self) -> Conf { + unsafe { BNIsTypeBuilderVolatile(self.handle).into() } + } + + pub fn is_floating_point(&self) -> bool { + unsafe { BNIsTypeBuilderFloatingPoint(self.handle) } + } + + pub fn target(&self) -> Result>> { + let raw_target = unsafe { BNGetTypeBuilderChildType(self.handle) }; + if raw_target.type_.is_null() { + Err(()) + } else { + Ok(raw_target.into()) + } + } + + pub fn element_type(&self) -> Result>> { + let raw_target = unsafe { BNGetTypeBuilderChildType(self.handle) }; + if raw_target.type_.is_null() { + Err(()) + } else { + Ok(raw_target.into()) + } + } + + pub fn return_value(&self) -> Result>> { + let raw_target = unsafe { BNGetTypeBuilderChildType(self.handle) }; + if raw_target.type_.is_null() { + Err(()) + } else { + Ok(raw_target.into()) + } + } + + pub fn calling_convention(&self) -> Result>>> { + let convention_confidence = unsafe { BNGetTypeBuilderCallingConvention(self.handle) }; + if convention_confidence.convention.is_null() { + Err(()) + } else { + Ok(convention_confidence.into()) + } + } + + pub fn parameters(&self) -> Result>> { + unsafe { + let mut count: usize = mem::zeroed(); + let parameters_raw = BNGetTypeBuilderParameters(self.handle, &mut count); + if parameters_raw.is_null() { + Err(()) + } else { + let parameters: &[BNFunctionParameter] = + slice::from_raw_parts(parameters_raw, count); + + let result = (0..count) + .map(|i| FunctionParameter::from_raw(parameters[i])) + .collect(); + + BNFreeTypeParameterList(parameters_raw, count); + + Ok(result) + } + } + } + + pub fn has_variable_arguments(&self) -> Conf { + unsafe { BNTypeBuilderHasVariableArguments(self.handle).into() } + } + + pub fn can_return(&self) -> Conf { + unsafe { BNFunctionTypeBuilderCanReturn(self.handle).into() } + } + + pub fn pure(&self) -> Conf { + unsafe { BNIsTypeBuilderPure(self.handle).into() } + } + + pub fn get_structure(&self) -> Result> { + let result = unsafe { BNGetTypeBuilderStructure(self.handle) }; + if result.is_null() { + Err(()) + } else { + Ok(unsafe { Structure::ref_from_raw(result) }) + } + } + + pub fn get_enumeration(&self) -> Result> { + let result = unsafe { BNGetTypeBuilderEnumeration(self.handle) }; + if result.is_null() { + Err(()) + } else { + Ok(unsafe { Enumeration::ref_from_raw(result) }) + } + } + + pub fn get_named_type_reference(&self) -> Result> { + let result = unsafe { BNGetTypeBuilderNamedTypeReference(self.handle) }; + if result.is_null() { + Err(()) + } else { + Ok(unsafe { NamedTypeReference::ref_from_raw(result) }) + } + } + + pub fn count(&self) -> u64 { + unsafe { BNGetTypeBuilderElementCount(self.handle) } + } + + pub fn offset(&self) -> u64 { + unsafe { BNGetTypeBuilderOffset(self.handle) } + } + + pub fn stack_adjustment(&self) -> Conf { + unsafe { BNGetTypeBuilderStackAdjustment(self.handle).into() } + } + + // TODO : This and properties + // pub fn tokens(&self) -> ? {} + + pub fn void() -> Self { + unsafe { Self::from_raw(BNCreateVoidTypeBuilder()) } + } + + pub fn bool() -> Self { + unsafe { Self::from_raw(BNCreateBoolTypeBuilder()) } + } + + pub fn char() -> Self { + Self::int(1, true) + } + + pub fn int(width: usize, is_signed: bool) -> Self { + let mut is_signed = Conf::new(is_signed, max_confidence()).into(); + + unsafe { + Self::from_raw(BNCreateIntegerTypeBuilder( + width, + &mut is_signed, + BnString::new("").as_ptr() as *mut _, + )) + } + } + + pub fn named_int(width: usize, is_signed: bool, alt_name: S) -> Self { + let mut is_signed = Conf::new(is_signed, max_confidence()).into(); + // let alt_name = BnString::new(alt_name); + let alt_name = alt_name.into_bytes_with_nul(); // This segfaulted once, so the above version is there if we need to change to it, but in theory this is copied into a `const string&` on the C++ side; I'm just not 100% confident that a constant reference copies data + + unsafe { + Self::from_raw(BNCreateIntegerTypeBuilder( + width, + &mut is_signed, + alt_name.as_ref().as_ptr() as _, + )) + } + } + + pub fn float(width: usize) -> Self { + unsafe { + Self::from_raw(BNCreateFloatTypeBuilder( + width, + BnString::new("").as_ptr() as *mut _, + )) + } + } + + pub fn named_float(width: usize, alt_name: S) -> Self { + // let alt_name = BnString::new(alt_name); + let alt_name = alt_name.into_bytes_with_nul(); // See same line in `named_int` above + + unsafe { + Self::from_raw(BNCreateFloatTypeBuilder( + width, + alt_name.as_ref().as_ptr() as _, + )) + } + } + + pub fn array<'a, T: Into>>(t: T, count: u64) -> Self { + unsafe { Self::from_raw(BNCreateArrayTypeBuilder(&t.into().into(), count)) } + } + + /// The C/C++ APIs require an associated architecture, but in the core we only query the default_int_size if the given width is 0 + /// For simplicity's sake, that convention isn't followed and you can query the default_int_size from an arch, if you have it, if you need to + pub fn enumeration>>( + enumeration: &Enumeration, + width: usize, + is_signed: T, + ) -> Self { + unsafe { + // TODO : This is _extremely fragile_, we should change the internals of BNCreateEnumerationTypeBuilder instead of doing this + let mut fake_arch: BNArchitecture = mem::zeroed(); + Self::from_raw(BNCreateEnumerationTypeBuilder( + &mut fake_arch, + enumeration.handle, + width, + &mut is_signed.into().into(), + )) + } + } + + pub fn structure(structure_type: &Structure) -> Self { + unsafe { Self::from_raw(BNCreateStructureTypeBuilder(structure_type.handle)) } + } + + pub fn named_type(type_reference: NamedTypeReference) -> Self { + let mut is_const = Conf::new(false, min_confidence()).into(); + let mut is_volatile = Conf::new(false, min_confidence()).into(); + unsafe { + Self::from_raw(BNCreateNamedTypeReferenceBuilder( + type_reference.handle, + 0, + 1, + &mut is_const, + &mut is_volatile, + )) + } + } + + pub fn named_type_from_type(name: S, t: &Type) -> Self { + let mut name = QualifiedName::from(name); + + unsafe { + Self::from_raw(BNCreateNamedTypeReferenceBuilderFromTypeAndId( + BnString::new("").as_ptr() as *mut _, + &mut name.0, + t.handle, + )) + } + } + + // TODO : BNCreateFunctionTypeBuilder + + pub fn pointer<'a, A: Architecture, T: Into>>(arch: &A, t: T) -> Self { + let mut is_const = Conf::new(false, min_confidence()).into(); + let mut is_volatile = Conf::new(false, min_confidence()).into(); + + unsafe { + Self::from_raw(BNCreatePointerTypeBuilder( + arch.as_ref().0, + &t.into().into(), + &mut is_const, + &mut is_volatile, + ReferenceType::PointerReferenceType, + )) + } + } + + pub fn const_pointer<'a, A: Architecture, T: Into>>(arch: &A, t: T) -> Self { + let mut is_const = Conf::new(true, max_confidence()).into(); + let mut is_volatile = Conf::new(false, min_confidence()).into(); + + unsafe { + Self::from_raw(BNCreatePointerTypeBuilder( + arch.as_ref().0, + &t.into().into(), + &mut is_const, + &mut is_volatile, + ReferenceType::PointerReferenceType, + )) + } + } + + pub fn pointer_of_width<'a, T: Into>>( + t: T, + size: usize, + is_const: bool, + is_volatile: bool, + ref_type: Option, + ) -> Self { + let mut is_const = Conf::new(is_const, max_confidence()).into(); + let mut is_volatile = Conf::new(is_volatile, max_confidence()).into(); + + unsafe { + Self::from_raw(BNCreatePointerTypeBuilderOfWidth( + size, + &t.into().into(), + &mut is_const, + &mut is_volatile, + ref_type.unwrap_or(ReferenceType::PointerReferenceType), + )) + } + } + + pub fn pointer_with_options<'a, A: Architecture, T: Into>>( + arch: &A, + t: T, + is_const: bool, + is_volatile: bool, + ref_type: Option, + ) -> Self { + let mut is_const = Conf::new(is_const, max_confidence()).into(); + let mut is_volatile = Conf::new(is_volatile, max_confidence()).into(); + unsafe { + Self::from_raw(BNCreatePointerTypeBuilder( + arch.as_ref().0, + &t.into().into(), + &mut is_const, + &mut is_volatile, + ref_type.unwrap_or(ReferenceType::PointerReferenceType), + )) + } + } +} + +impl fmt::Display for TypeBuilder { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{}", unsafe { + BnString::from_raw(BNGetTypeBuilderString(self.handle, ptr::null_mut())) + }) + } +} + +impl Drop for TypeBuilder { + fn drop(&mut self) { + unsafe { BNFreeTypeBuilder(self.handle) }; + } +} + +////////// +// Type + +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); +/// bv.define_user_type("int_1", &my_custom_type_1); +/// bv.define_user_type("int_2", &my_custom_type_2); +/// ``` +impl Type { + unsafe fn from_raw(handle: *mut BNType) -> Self { + debug_assert!(!handle.is_null()); + Self { handle } + } + + pub(crate) unsafe fn ref_from_raw(handle: *mut BNType) -> Ref { + debug_assert!(!handle.is_null()); + Ref::new(Self { handle }) + } + + pub fn to_builder(&self) -> TypeBuilder { + TypeBuilder::new(self) + } + + // Readable properties + + pub fn type_class(&self) -> TypeClass { + unsafe { BNGetTypeClass(self.handle) } + } + + pub fn width(&self) -> u64 { + unsafe { BNGetTypeWidth(self.handle) } + } + + pub fn alignment(&self) -> usize { + unsafe { BNGetTypeAlignment(self.handle) } + } + + pub fn is_signed(&self) -> Conf { + unsafe { BNIsTypeSigned(self.handle).into() } + } + + pub fn is_const(&self) -> Conf { + unsafe { BNIsTypeConst(self.handle).into() } + } + + pub fn is_volatile(&self) -> Conf { + unsafe { BNIsTypeVolatile(self.handle).into() } + } + + pub fn is_floating_point(&self) -> bool { + unsafe { BNIsTypeFloatingPoint(self.handle) } + } + + pub fn target(&self) -> Result>> { + let raw_target = unsafe { BNGetChildType(self.handle) }; + if raw_target.type_.is_null() { + Err(()) + } else { + Ok(raw_target.into()) + } + } + + pub fn element_type(&self) -> Result>> { + let raw_target = unsafe { BNGetChildType(self.handle) }; + if raw_target.type_.is_null() { + Err(()) + } else { + Ok(raw_target.into()) + } + } + + pub fn return_value(&self) -> Result>> { + let raw_target = unsafe { BNGetChildType(self.handle) }; + if raw_target.type_.is_null() { + Err(()) + } else { + Ok(raw_target.into()) + } + } + + pub fn calling_convention(&self) -> Result>>> { + let convention_confidence = unsafe { BNGetTypeCallingConvention(self.handle) }; + if convention_confidence.convention.is_null() { + Err(()) + } else { + Ok(convention_confidence.into()) + } + } + + pub fn parameters(&self) -> Result>> { + unsafe { + let mut count: usize = mem::zeroed(); + let parameters_raw: *mut BNFunctionParameter = + BNGetTypeParameters(self.handle, &mut count); + if parameters_raw.is_null() { + Err(()) + } else { + let parameters: &[BNFunctionParameter] = + slice::from_raw_parts(parameters_raw, count); + + let result = (0..count) + .map(|i| FunctionParameter::from_raw(parameters[i])) + .collect(); + + BNFreeTypeParameterList(parameters_raw, count); + + Ok(result) + } + } + } + + pub fn has_variable_arguments(&self) -> Conf { + unsafe { BNTypeHasVariableArguments(self.handle).into() } + } + + pub fn can_return(&self) -> Conf { + unsafe { BNFunctionTypeCanReturn(self.handle).into() } + } + + pub fn pure(&self) -> Conf { + unsafe { BNIsTypePure(self.handle).into() } + } + + pub fn get_structure(&self) -> Result> { + let result = unsafe { BNGetTypeStructure(self.handle) }; + if result.is_null() { + Err(()) + } else { + Ok(unsafe { Structure::ref_from_raw(result) }) + } + } + + pub fn get_enumeration(&self) -> Result> { + let result = unsafe { BNGetTypeEnumeration(self.handle) }; + if result.is_null() { + Err(()) + } else { + Ok(unsafe { Enumeration::ref_from_raw(result) }) + } + } + + pub fn get_named_type_reference(&self) -> Result> { + let result = unsafe { BNGetTypeNamedTypeReference(self.handle) }; + if result.is_null() { + Err(()) + } else { + Ok(unsafe { NamedTypeReference::ref_from_raw(result) }) + } + } + + pub fn count(&self) -> u64 { + unsafe { BNGetTypeElementCount(self.handle) } + } + + pub fn offset(&self) -> u64 { + unsafe { BNGetTypeOffset(self.handle) } + } + + pub fn stack_adjustment(&self) -> Conf { + unsafe { BNGetTypeStackAdjustment(self.handle).into() } + } + + pub fn registered_name(&self) -> Result> { + let result = unsafe { BNGetRegisteredTypeName(self.handle) }; + if result.is_null() { + Err(()) + } else { + Ok(unsafe { NamedTypeReference::ref_from_raw(result) }) + } + } + + // TODO : This and properties + // pub fn tokens(&self) -> ? {} + + pub fn void() -> Ref { + unsafe { Self::ref_from_raw(BNCreateVoidType()) } + } + + pub fn bool() -> Ref { + unsafe { Self::ref_from_raw(BNCreateBoolType()) } + } + + pub fn char() -> Ref { + Self::int(1, true) + } + + pub fn wide_char(width: usize) -> Ref { + unsafe { + Self::ref_from_raw(BNCreateWideCharType( + width, + BnString::new("").as_ptr() as *mut _, + )) + } + } + + pub fn int(width: usize, is_signed: bool) -> Ref { + let mut is_signed = Conf::new(is_signed, max_confidence()).into(); + unsafe { + Self::ref_from_raw(BNCreateIntegerType( + width, + &mut is_signed, + BnString::new("").as_ptr() as *mut _, + )) + } + } + + pub fn named_int(width: usize, is_signed: bool, alt_name: S) -> Ref { + let mut is_signed = Conf::new(is_signed, max_confidence()).into(); + // let alt_name = BnString::new(alt_name); + let alt_name = alt_name.into_bytes_with_nul(); // This segfaulted once, so the above version is there if we need to change to it, but in theory this is copied into a `const string&` on the C++ side; I'm just not 100% confident that a constant reference copies data + + unsafe { + Self::ref_from_raw(BNCreateIntegerType( + width, + &mut is_signed, + alt_name.as_ref().as_ptr() as _, + )) + } + } + + pub fn float(width: usize) -> Ref { + unsafe { + Self::ref_from_raw(BNCreateFloatType( + width, + BnString::new("").as_ptr() as *mut _, + )) + } + } + + pub fn named_float(width: usize, alt_name: S) -> Ref { + // let alt_name = BnString::new(alt_name); + let alt_name = alt_name.into_bytes_with_nul(); // See same line in `named_int` above + + unsafe { Self::ref_from_raw(BNCreateFloatType(width, alt_name.as_ref().as_ptr() as _)) } + } + + pub fn array<'a, T: Into>>(t: T, count: u64) -> Ref { + unsafe { Self::ref_from_raw(BNCreateArrayType(&t.into().into(), count)) } + } + + /// The C/C++ APIs require an associated architecture, but in the core we only query the default_int_size if the given width is 0 + /// + /// For simplicity's sake, that convention isn't followed and you can query the default_int_size from an arch, if you have it, if you need to + pub fn enumeration>>( + enumeration: &Enumeration, + width: usize, + is_signed: T, + ) -> Ref { + unsafe { + // TODO : This is _extremely fragile_, we should change the internals of BNCreateEnumerationType instead of doing this + let mut fake_arch: BNArchitecture = mem::zeroed(); + Self::ref_from_raw(BNCreateEnumerationType( + &mut fake_arch, + enumeration.handle, + width, + &mut is_signed.into().into(), + )) + } + } + + pub fn structure(structure: &Structure) -> Ref { + unsafe { Self::ref_from_raw(BNCreateStructureType(structure.handle)) } + } + + pub fn named_type(type_reference: &NamedTypeReference) -> Ref { + let mut is_const = Conf::new(false, min_confidence()).into(); + let mut is_volatile = Conf::new(false, min_confidence()).into(); + unsafe { + Self::ref_from_raw(BNCreateNamedTypeReference( + type_reference.handle, + 0, + 1, + &mut is_const, + &mut is_volatile, + )) + } + } + + pub fn named_type_from_type(name: S, t: &Type) -> Ref { + let mut name = QualifiedName::from(name); + + unsafe { + Self::ref_from_raw(BNCreateNamedTypeReferenceFromTypeAndId( + BnString::new("").as_ptr() as *mut _, + &mut name.0, + t.handle, + )) + } + } + + pub fn function<'a, S: BnStrCompatible + Clone, T: Into>>( + return_type: T, + parameters: &[FunctionParameter], + variable_arguments: bool, + ) -> Ref { + let mut return_type = return_type.into().into(); + let mut variable_arguments = Conf::new(variable_arguments, max_confidence()).into(); + let mut can_return = Conf::new(true, min_confidence()).into(); + let mut pure = Conf::new(false, min_confidence()).into(); + + let mut raw_calling_convention: BNCallingConventionWithConfidence = + BNCallingConventionWithConfidence { + convention: ptr::null_mut(), + confidence: min_confidence(), + }; + + let mut stack_adjust = Conf::::new(0, min_confidence()).into(); + 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 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 _, + type_: parameter.t.contents.handle, + typeConfidence: parameter.t.confidence, + defaultLocation: parameter.location.is_none(), + location, + }); + parameter_name_references.push(raw_name); + } + let reg_stack_adjust_regs = ptr::null_mut(); + let reg_stack_adjust_values = ptr::null_mut(); + + let mut return_regs: BNRegisterSetWithConfidence = BNRegisterSetWithConfidence { + regs: ptr::null_mut(), + count: 0, + confidence: 0, + }; + + unsafe { + Self::ref_from_raw(BNNewTypeReference(BNCreateFunctionType( + &mut return_type, + &mut raw_calling_convention, + raw_parameters.as_mut_ptr(), + raw_parameters.len(), + &mut variable_arguments, + &mut can_return, + &mut stack_adjust, + reg_stack_adjust_regs, + reg_stack_adjust_values, + 0, + &mut return_regs, + BNNameType::NoNameType, + &mut pure, + ))) + } + } + + pub fn function_with_options< + 'a, + A: Architecture, + S: BnStrCompatible + Clone, + T: Into>, + C: Into>>, + >( + return_type: T, + parameters: &[FunctionParameter], + variable_arguments: bool, + calling_convention: C, + stack_adjust: Conf, + ) -> Ref { + let mut return_type = return_type.into().into(); + let mut variable_arguments = Conf::new(variable_arguments, max_confidence()).into(); + let mut can_return = Conf::new(true, min_confidence()).into(); + let mut pure = Conf::new(false, min_confidence()).into(); + let mut raw_calling_convention: BNCallingConventionWithConfidence = + calling_convention.into().into(); + let mut stack_adjust = stack_adjust.into(); + + let mut raw_parameters = Vec::::with_capacity(parameters.len()); + let mut parameter_name_references = Vec::with_capacity(parameters.len()); + let mut name_ptrs = vec![]; + for parameter in parameters { + name_ptrs.push(parameter.name.clone()); + } + + for (name, parameter) in zip(name_ptrs, parameters) { + let raw_name = name.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 _, + type_: parameter.t.contents.handle, + typeConfidence: parameter.t.confidence, + defaultLocation: parameter.location.is_none(), + location, + }); + parameter_name_references.push(raw_name); + } + + // TODO: Update type signature and include these (will be a breaking change) + let reg_stack_adjust_regs = ptr::null_mut(); + let reg_stack_adjust_values = ptr::null_mut(); + + let mut return_regs: BNRegisterSetWithConfidence = BNRegisterSetWithConfidence { + regs: ptr::null_mut(), + count: 0, + confidence: 0, + }; + + unsafe { + Self::ref_from_raw(BNCreateFunctionType( + &mut return_type, + &mut raw_calling_convention, + raw_parameters.as_mut_ptr(), + raw_parameters.len(), + &mut variable_arguments, + &mut can_return, + &mut stack_adjust, + reg_stack_adjust_regs, + reg_stack_adjust_values, + 0, + &mut return_regs, + BNNameType::NoNameType, + &mut pure, + )) + } + } + + pub fn pointer<'a, A: Architecture, T: Into>>(arch: &A, t: T) -> Ref { + let mut is_const = Conf::new(false, min_confidence()).into(); + let mut is_volatile = Conf::new(false, min_confidence()).into(); + unsafe { + Self::ref_from_raw(BNCreatePointerType( + arch.as_ref().0, + &t.into().into(), + &mut is_const, + &mut is_volatile, + ReferenceType::PointerReferenceType, + )) + } + } + + pub fn const_pointer<'a, A: Architecture, T: Into>>( + arch: &A, + t: T, + ) -> Ref { + let mut is_const = Conf::new(true, max_confidence()).into(); + let mut is_volatile = Conf::new(false, min_confidence()).into(); + unsafe { + Self::ref_from_raw(BNCreatePointerType( + arch.as_ref().0, + &t.into().into(), + &mut is_const, + &mut is_volatile, + ReferenceType::PointerReferenceType, + )) + } + } + + pub fn pointer_of_width<'a, T: Into>>( + t: T, + size: usize, + is_const: bool, + is_volatile: bool, + ref_type: Option, + ) -> Ref { + let mut is_const = Conf::new(is_const, max_confidence()).into(); + let mut is_volatile = Conf::new(is_volatile, max_confidence()).into(); + unsafe { + Self::ref_from_raw(BNCreatePointerTypeOfWidth( + size, + &t.into().into(), + &mut is_const, + &mut is_volatile, + ref_type.unwrap_or(ReferenceType::PointerReferenceType), + )) + } + } + + pub fn pointer_with_options<'a, A: Architecture, T: Into>>( + arch: &A, + t: T, + is_const: bool, + is_volatile: bool, + ref_type: Option, + ) -> Ref { + let mut is_const = Conf::new(is_const, max_confidence()).into(); + let mut is_volatile = Conf::new(is_volatile, max_confidence()).into(); + unsafe { + Self::ref_from_raw(BNCreatePointerType( + arch.as_ref().0, + &t.into().into(), + &mut is_const, + &mut is_volatile, + ref_type.unwrap_or(ReferenceType::PointerReferenceType), + )) + } + } + + pub fn generate_auto_demangled_type_id<'a, S: BnStrCompatible>(name: S) -> &'a BnStr { + let mut name = QualifiedName::from(name); + unsafe { BnStr::from_raw(BNGenerateAutoDemangledTypeId(&mut name.0)) } + } +} + +impl fmt::Display for Type { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{}", unsafe { + BnString::from_raw(BNGetTypeString( + self.handle, + ptr::null_mut(), + BNTokenEscapingType::NoTokenEscapingType, + )) + }) + } +} + +lazy_static! { + static ref TYPE_DEBUG_BV: Mutex>> = + Mutex::new(BinaryView::from_data(&FileMetadata::new(), &[]).ok()); +} + +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( + self.handle, + container, + "".as_ptr() as *const c_char, + 80, + false, + BNTokenEscapingType::NoTokenEscapingType, + &mut count as *mut usize, + ) + }; + unsafe { + BNFreeTypeContainer(container); + } + + if lines.is_null() { + return Err(fmt::Error); + } + + let line_slice: &[BNTypeDefinitionLine] = + unsafe { slice::from_raw_parts(lines, count) }; + + for (i, line) in line_slice.iter().enumerate() { + if i > 0 { + writeln!(f)?; + } + + let tokens: &[BNInstructionTextToken] = + unsafe { slice::from_raw_parts(line.tokens, line.count) }; + + for token in tokens { + let text: *const c_char = token.text; + let str = unsafe { CStr::from_ptr(text) }; + write!(f, "{}", str.to_string_lossy())?; + } + } + + unsafe { + BNFreeTypeDefinitionLineList(lines, count); + } + return Ok(()); + } + } + Err(fmt::Error) + } +} + +impl PartialEq for Type { + fn eq(&self, other: &Self) -> bool { + unsafe { BNTypesEqual(self.handle, other.handle) } + } +} + +impl Eq for Type {} + +impl Hash for Type { + fn hash(&self, state: &mut H) { + self.handle.hash(state); + } +} + +unsafe impl Send for Type {} +unsafe impl Sync for Type {} + +unsafe impl RefCountable for Type { + unsafe fn inc_ref(handle: &Self) -> Ref { + Self::ref_from_raw(BNNewTypeReference(handle.handle)) + } + + unsafe fn dec_ref(handle: &Self) { + BNFreeType(handle.handle); + } +} + +impl ToOwned for Type { + type Owned = Ref; + + fn to_owned(&self) -> Self::Owned { + unsafe { RefCountable::inc_ref(self) } + } +} + +/////////////////////// +// FunctionParameter + +#[derive(Clone, Debug)] +pub struct FunctionParameter { + pub t: Conf>, + pub name: S, + pub location: Option, +} + +impl FunctionParameter { + pub fn new>>>(t: T, name: S, 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)) + } else { + BnString::new("") + } + } else { + BnString::new(unsafe { BnStr::from_raw(member.name) }) + }; + + Self { + t: Conf::new( + unsafe { Type::ref_from_raw(BNNewTypeReference(member.type_)) }, + member.typeConfidence, + ), + name, + location: if member.defaultLocation { + None + } else { + Some(unsafe { Variable::from_raw(member.location) }) + }, + } + } +} + +////////////// +// Variable + +#[derive(Clone, Copy, Debug, Hash, Eq, PartialEq)] +pub struct Variable { + pub t: BNVariableSourceType, + pub index: u32, + pub storage: i64, +} + +impl Variable { + pub fn new(t: BNVariableSourceType, index: u32, storage: i64) -> Self { + Self { t, index, storage } + } + + pub(crate) unsafe fn from_raw(var: BNVariable) -> Self { + Self { + t: var.type_, + index: var.index, + storage: var.storage, + } + } + + pub(crate) fn raw(&self) -> BNVariable { + BNVariable { + type_: self.t, + index: self.index, + storage: self.storage, + } + } +} + +////////////// +// SSAVariable + +#[derive(Clone, Copy, Debug, Hash, Eq, PartialEq)] +pub struct SSAVariable { + pub variable: Variable, + pub version: usize, +} + +impl SSAVariable { + pub fn new(variable: Variable, version: usize) -> Self { + Self { variable, version } + } +} + +/////////////// +// NamedVariable + +pub struct NamedTypedVariable { + var: BNVariable, + auto_defined: bool, + type_confidence: u8, + name: *mut c_char, + ty: *mut BNType, +} + +impl NamedTypedVariable { + pub fn name(&self) -> &str { + unsafe { BnStr::from_raw(self.name).as_str() } + } + + pub fn var(&self) -> Variable { + unsafe { Variable::from_raw(self.var) } + } + + pub fn auto_defined(&self) -> bool { + self.auto_defined + } + + pub fn type_confidence(&self) -> u8 { + self.type_confidence + } + + pub fn var_type(&self) -> Ref { + unsafe { Ref::new(Type::from_raw(self.ty)) } + } +} + +impl CoreArrayProvider for NamedTypedVariable { + type Raw = BNVariableNameAndType; + type Context = (); +} + +unsafe impl CoreOwnedArrayProvider 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, + }) + } +} + +//////////////////////// +// EnumerationBuilder + +#[derive(Debug, Clone)] +pub struct EnumerationMember { + pub name: BnString, + pub value: u64, + pub is_default: bool, +} + +impl EnumerationMember { + pub fn new(name: S, value: u64, is_default: bool) -> Self { + Self { + name: BnString::new(name), + value, + is_default, + } + } + + pub(crate) unsafe fn from_raw(member: BNEnumerationMember) -> Self { + Self { + name: BnString::new(raw_to_string(member.name).unwrap()), + value: member.value, + is_default: member.isDefault, + } + } +} + +#[derive(PartialEq, Eq, Hash)] +pub struct EnumerationBuilder { + pub(crate) handle: *mut BNEnumerationBuilder, +} + +impl EnumerationBuilder { + pub fn new() -> Self { + Self { + handle: unsafe { BNCreateEnumerationBuilder() }, + } + } + + pub(crate) unsafe fn from_raw(handle: *mut BNEnumerationBuilder) -> Self { + Self { handle } + } + + pub fn finalize(&self) -> Ref { + unsafe { Enumeration::ref_from_raw(BNFinalizeEnumerationBuilder(self.handle)) } + } + + pub fn append(&self, name: S) -> &Self { + let name = name.into_bytes_with_nul(); + unsafe { + BNAddEnumerationBuilderMember(self.handle, name.as_ref().as_ptr() as _); + } + self + } + + pub fn insert(&self, name: S, value: u64) -> &Self { + let name = name.into_bytes_with_nul(); + unsafe { + BNAddEnumerationBuilderMemberWithValue(self.handle, name.as_ref().as_ptr() as _, value); + } + self + } + + pub fn replace(&self, id: usize, name: S, value: u64) -> &Self { + let name = name.into_bytes_with_nul(); + unsafe { + BNReplaceEnumerationBuilderMember(self.handle, id, name.as_ref().as_ptr() as _, value); + } + self + } + + pub fn remove(&self, id: usize) -> &Self { + unsafe { + BNRemoveEnumerationBuilderMember(self.handle, id); + } + + self + } + + pub fn members(&self) -> Vec { + unsafe { + let mut count: usize = mem::zeroed(); + let members_raw = BNGetEnumerationBuilderMembers(self.handle, &mut count); + let members: &[BNEnumerationMember] = slice::from_raw_parts(members_raw, count); + + let result = (0..count) + .map(|i| EnumerationMember::from_raw(members[i])) + .collect(); + + BNFreeEnumerationMemberList(members_raw, count); + + result + } + } +} + +impl Default for EnumerationBuilder { + fn default() -> Self { + Self::new() + } +} + +impl From<&Enumeration> for EnumerationBuilder { + fn from(enumeration: &Enumeration) -> Self { + unsafe { + Self::from_raw(BNCreateEnumerationBuilderFromEnumeration( + enumeration.handle, + )) + } + } +} + +impl Drop for EnumerationBuilder { + fn drop(&mut self) { + unsafe { BNFreeEnumerationBuilder(self.handle) }; + } +} + +///////////////// +// Enumeration + +#[derive(PartialEq, Eq, Hash)] +pub struct Enumeration { + pub(crate) handle: *mut BNEnumeration, +} + +impl Enumeration { + pub(crate) unsafe fn ref_from_raw(handle: *mut BNEnumeration) -> Ref { + debug_assert!(!handle.is_null()); + Ref::new(Self { handle }) + } + + pub fn builder() -> EnumerationBuilder { + EnumerationBuilder::new() + } + + pub fn members(&self) -> Vec { + unsafe { + let mut count: usize = mem::zeroed(); + let members_raw = BNGetEnumerationMembers(self.handle, &mut count); + let members: &[BNEnumerationMember] = slice::from_raw_parts(members_raw, count); + + let result = (0..count) + .map(|i| EnumerationMember::from_raw(members[i])) + .collect(); + + BNFreeEnumerationMemberList(members_raw, count); + + result + } + } +} + +unsafe impl RefCountable for Enumeration { + unsafe fn inc_ref(handle: &Self) -> Ref { + Self::ref_from_raw(BNNewEnumerationReference(handle.handle)) + } + + unsafe fn dec_ref(handle: &Self) { + BNFreeEnumeration(handle.handle); + } +} + +impl ToOwned for Enumeration { + type Owned = Ref; + + fn to_owned(&self) -> Self::Owned { + unsafe { RefCountable::inc_ref(self) } + } +} + +////////////////////// +// StructureBuilder + +pub type StructureType = BNStructureVariant; + +#[derive(PartialEq, Eq, Hash)] +pub struct StructureBuilder { + pub(crate) handle: *mut BNStructureBuilder, +} + +/// ```rust +/// // Includes +/// use binaryninja::types::{Structure, Type}; +/// +/// // Define struct, set size (in bytes) +/// let mut my_custom_struct = StructureBuilder::new(); +/// let field_1 = Type::named_int(5, false, "my_weird_int_type"); +/// let field_2 = Type::int(4, false); +/// 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); +/// +/// // Convert structure to type +/// let my_custom_structure_type = Self::structure_type(&mut my_custom_struct); +/// +/// // Add the struct to the binary view to use in analysis +/// let bv = unsafe { BinaryView::from_raw(view) }; +/// bv.define_user_type("my_custom_struct", &my_custom_structure_type); +/// ``` +impl StructureBuilder { + pub fn new() -> Self { + Self { + handle: unsafe { BNCreateStructureBuilder() }, + } + } + + pub(crate) unsafe fn from_raw(handle: *mut BNStructureBuilder) -> Self { + debug_assert!(!handle.is_null()); + Self { handle } + } + + // Chainable terminal + pub fn finalize(&self) -> Ref { + unsafe { Structure::ref_from_raw(BNFinalizeStructureBuilder(self.handle)) } + } + + // Chainable builders/setters + + pub fn set_width(&self, width: u64) -> &Self { + unsafe { + BNSetStructureBuilderWidth(self.handle, width); + } + + self + } + + pub fn set_alignment(&self, alignment: usize) -> &Self { + unsafe { + BNSetStructureBuilderAlignment(self.handle, alignment); + } + + self + } + + pub fn set_packed(&self, packed: bool) -> &Self { + unsafe { + BNSetStructureBuilderPacked(self.handle, packed); + } + + self + } + + pub fn set_structure_type(&self, t: StructureType) -> &Self { + unsafe { BNSetStructureBuilderType(self.handle, t) }; + self + } + + pub fn set_pointer_offset(&self, offset: i64) -> &Self { + unsafe { BNSetStructureBuilderPointerOffset(self.handle, offset) }; + self + } + + pub fn set_propagates_data_var_refs(&self, does: bool) -> &Self { + unsafe { BNSetStructureBuilderPropagatesDataVariableReferences(self.handle, does) }; + self + } + + pub fn set_base_structures(&self, bases: Vec) -> &Self { + let mut bases_api = vec![]; + for base in bases { + bases_api.push(BNBaseStructure { + type_: base.ty.handle, + offset: base.offset, + width: base.width, + }); + } + + unsafe { + BNSetBaseStructuresForStructureBuilder( + self.handle, + bases_api.as_mut_ptr(), + bases_api.len(), + ) + }; + + self + } + + pub fn append<'a, S: BnStrCompatible, T: Into>>( + &self, + t: T, + name: S, + access: MemberAccess, + scope: MemberScope, + ) -> &Self { + let name = name.into_bytes_with_nul(); + unsafe { + BNAddStructureBuilderMember( + self.handle, + &t.into().into(), + name.as_ref().as_ptr() as _, + access, + scope, + ); + } + + self + } + + pub fn insert_member(&self, member: &StructureMember, overwrite_existing: bool) -> &Self { + let ty = member.ty.clone(); + self.insert( + ty.as_ref(), + member.name.clone(), + member.offset, + overwrite_existing, + member.access, + member.scope, + ); + self + } + + pub fn insert<'a, S: BnStrCompatible, T: Into>>( + &self, + t: T, + name: S, + offset: u64, + overwrite_existing: bool, + access: MemberAccess, + scope: MemberScope, + ) -> &Self { + let name = name.into_bytes_with_nul(); + unsafe { + BNAddStructureBuilderMemberAtOffset( + self.handle, + &t.into().into(), + name.as_ref().as_ptr() as _, + offset, + overwrite_existing, + access, + scope, + ); + } + + self + } + + pub fn with_members<'a, S: BnStrCompatible, T: Into>>( + &self, + members: impl IntoIterator, + ) -> &Self { + for (t, name) in members.into_iter() { + self.append(t, name, MemberAccess::NoAccess, MemberScope::NoScope); + } + self + } + + // Getters + + pub fn width(&self) -> u64 { + unsafe { BNGetStructureBuilderWidth(self.handle) } + } + + pub fn alignment(&self) -> usize { + unsafe { BNGetStructureBuilderAlignment(self.handle) } + } + + pub fn packed(&self) -> bool { + unsafe { BNIsStructureBuilderPacked(self.handle) } + } + + pub fn structure_type(&self) -> StructureType { + unsafe { BNGetStructureBuilderType(self.handle) } + } + + pub fn pointer_offset(&self) -> i64 { + unsafe { BNGetStructureBuilderPointerOffset(self.handle) } + } + + pub fn propagates_data_var_refs(&self) -> bool { + unsafe { BNStructureBuilderPropagatesDataVariableReferences(self.handle) } + } + + pub fn base_structures(&self) -> Result> { + let mut count = 0usize; + let bases = unsafe { BNGetBaseStructuresForStructureBuilder(self.handle, &mut count) }; + if bases.is_null() { + Err(()) + } else { + let bases_slice = unsafe { slice::from_raw_parts_mut(bases, count) }; + + let result = bases_slice + .iter() + .map(|base| unsafe { BaseStructure::from_raw(*base) }) + .collect::>(); + + unsafe { + BNFreeBaseStructureList(bases, count); + } + + Ok(result) + } + } + + // TODO : The other methods in the python version (type, members, remove, replace, etc) +} + +impl From<&Structure> for StructureBuilder { + fn from(structure: &Structure) -> StructureBuilder { + unsafe { Self::from_raw(BNCreateStructureBuilderFromStructure(structure.handle)) } + } +} + +impl From> for StructureBuilder { + fn from(members: Vec) -> StructureBuilder { + let builder = StructureBuilder::new(); + for m in members { + builder.insert_member(&m, false); + } + builder + } +} + +impl Debug for StructureBuilder { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + write!(f, "StructureBuilder {{ ... }}") + } +} + +impl Drop for StructureBuilder { + fn drop(&mut self) { + unsafe { BNFreeStructureBuilder(self.handle) }; + } +} + +impl Default for StructureBuilder { + fn default() -> Self { + Self::new() + } +} + +/////////////// +// Structure + +#[derive(PartialEq, Eq, Hash)] +pub struct Structure { + pub(crate) handle: *mut BNStructure, +} + +impl Structure { + unsafe fn from_raw(handle: *mut BNStructure) -> Self { + debug_assert!(!handle.is_null()); + Self { handle } + } + + pub(crate) unsafe fn ref_from_raw(handle: *mut BNStructure) -> Ref { + debug_assert!(!handle.is_null()); + Ref::new(Self { handle }) + } + + pub fn builder() -> StructureBuilder { + StructureBuilder::new() + } + + pub fn width(&self) -> u64 { + unsafe { BNGetStructureWidth(self.handle) } + } + + pub fn structure_type(&self) -> StructureType { + unsafe { BNGetStructureType(self.handle) } + } + + pub fn members(&self) -> Result> { + unsafe { + let mut count: usize = mem::zeroed(); + let members_raw: *mut BNStructureMember = + BNGetStructureMembers(self.handle, &mut count); + if members_raw.is_null() { + return Err(()); + } + let members = slice::from_raw_parts(members_raw, count); + + let result = (0..count) + .map(|i| StructureMember::from_raw(members[i])) + .collect(); + + BNFreeStructureMemberList(members_raw, count); + + Ok(result) + } + } + + pub fn base_structures(&self) -> Result> { + let mut count = 0usize; + let bases = unsafe { BNGetBaseStructuresForStructure(self.handle, &mut count) }; + if bases.is_null() { + Err(()) + } else { + let bases_slice = unsafe { slice::from_raw_parts_mut(bases, count) }; + + let result = bases_slice + .iter() + .map(|base| unsafe { BaseStructure::from_raw(*base) }) + .collect::>(); + + unsafe { + BNFreeBaseStructureList(bases, count); + } + + Ok(result) + } + } + + // TODO : The other methods in the python version (alignment, packed, type, members, remove, replace, etc) +} + +impl Debug for Structure { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + write!(f, "Structure {{")?; + if let Ok(members) = self.members() { + for member in members { + write!(f, " {:?}", member)?; + } + } + write!(f, "}}") + } +} + +unsafe impl RefCountable for Structure { + unsafe fn inc_ref(handle: &Self) -> Ref { + Ref::new(Self::from_raw(BNNewStructureReference(handle.handle))) + } + + unsafe fn dec_ref(handle: &Self) { + BNFreeStructure(handle.handle); + } +} + +impl ToOwned for Structure { + type Owned = Ref; + + fn to_owned(&self) -> Self::Owned { + unsafe { RefCountable::inc_ref(self) } + } +} + +#[derive(Debug, Clone)] +pub struct StructureMember { + pub ty: Conf>, + pub name: BnString, + pub offset: u64, + pub access: MemberAccess, + pub scope: MemberScope, +} + +impl StructureMember { + pub fn new( + ty: Conf>, + name: T, + offset: u64, + access: MemberAccess, + scope: MemberScope, + ) -> Self { + Self { + ty, + name: BnString::new(name), + offset, + access, + scope, + } + } + + pub(crate) unsafe fn from_raw(handle: BNStructureMember) -> Self { + Self { + ty: Conf::new( + RefCountable::inc_ref(&Type::from_raw(handle.type_)), + handle.typeConfidence, + ), + name: BnString::new(BnStr::from_raw(handle.name)), + offset: handle.offset, + access: handle.access, + scope: handle.scope, + } + } +} + +#[derive(Debug, Clone)] +pub struct InheritedStructureMember { + pub base: Ref, + pub base_offset: u64, + pub member: StructureMember, + pub member_index: usize, +} + +impl InheritedStructureMember { + pub fn new( + base: Ref, + base_offset: u64, + member: StructureMember, + member_index: usize, + ) -> Self { + Self { + base, + base_offset, + member, + member_index, + } + } + + // pub(crate) unsafe fn from_raw(handle: BNInheritedStructureMember) -> Self { + // Self { + // base: RefCountable::inc_ref(&NamedTypeReference::from_raw(handle.base)), + // base_offset: handle.baseOffset, + // member: StructureMember::from_raw(handle.member), + // member_index: handle.memberIndex, + // } + // } +} + +#[derive(Debug, Clone)] +pub struct BaseStructure { + pub ty: Ref, + pub offset: u64, + pub width: u64, +} + +impl BaseStructure { + pub fn new(ty: Ref, offset: u64, width: u64) -> Self { + Self { ty, offset, width } + } + + pub(crate) unsafe fn from_raw(handle: BNBaseStructure) -> Self { + Self { + ty: RefCountable::inc_ref(&NamedTypeReference::from_raw(handle.type_)), + offset: handle.offset, + width: handle.width, + } + } +} + +//////////////////////// +// NamedTypeReference + +#[derive(PartialEq, Eq, Hash)] +pub struct NamedTypeReference { + pub(crate) handle: *mut BNNamedTypeReference, +} + +impl NamedTypeReference { + pub(crate) unsafe fn from_raw(handle: *mut BNNamedTypeReference) -> Self { + debug_assert!(!handle.is_null()); + + Self { handle } + } + + pub(crate) unsafe fn ref_from_raw(handle: *mut BNNamedTypeReference) -> Ref { + debug_assert!(!handle.is_null()); + Ref::new(Self { handle }) + } + + /// Create an NTR to a type that did not come directly from a BinaryView's types list. + /// That is to say, if you're referencing a new type you're GOING to add, use this. + /// You should not assign type ids yourself, that is the responsibility of the BinaryView + /// implementation after your types have been added. Just make sure the names match up and + /// the core will do the id stuff for you. + pub fn new(type_class: NamedTypeReferenceClass, mut name: QualifiedName) -> Ref { + unsafe { + RefCountable::inc_ref(&Self { + handle: BNCreateNamedType(type_class, ptr::null() as *const _, &mut name.0), + }) + } + } + + /// Create an NTR to a type with an existing type id, which generally means it came directly + /// from a BinaryView's types list and its id was looked up using `BinaryView::get_type_id`. + /// You should not assign type ids yourself: if you use this to reference a type you are going + /// to create but have not yet created, you may run into problems when giving your types to + /// a BinaryView. + pub fn new_with_id( + type_class: NamedTypeReferenceClass, + type_id: S, + mut name: QualifiedName, + ) -> Ref { + let type_id = type_id.into_bytes_with_nul(); + + unsafe { + RefCountable::inc_ref(&Self { + handle: BNCreateNamedType(type_class, type_id.as_ref().as_ptr() as _, &mut name.0), + }) + } + } + + pub fn name(&self) -> QualifiedName { + let named_ref: BNQualifiedName = unsafe { BNGetTypeReferenceName(self.handle) }; + QualifiedName(named_ref) + } + + pub fn id(&self) -> BnString { + unsafe { BnString::from_raw(BNGetTypeReferenceId(self.handle)) } + } + + pub fn class(&self) -> NamedTypeReferenceClass { + unsafe { BNGetTypeReferenceClass(self.handle) } + } + + fn target_helper(&self, bv: &BinaryView, visited: &mut HashSet) -> Option> { + // TODO : This is a clippy bug (#10088, I think); remove after we upgrade past 2022-12-12 + #[allow(clippy::manual_filter)] + if let Some(t) = bv.get_type_by_id(self.id()) { + if t.type_class() != TypeClass::NamedTypeReferenceClass { + Some(t) + } else { + let t = t.get_named_type_reference().unwrap(); + if visited.contains(&t.id()) { + error!("Can't get target for recursively defined type!"); + None + } else { + visited.insert(t.id()); + t.target_helper(bv, visited) + } + } + } else { + None + } + } + + pub fn target(&self, bv: &BinaryView) -> Option> { + //! Returns the type referenced by this named type reference + self.target_helper(bv, &mut HashSet::new()) + } +} + +impl ToOwned for NamedTypeReference { + type Owned = Ref; + + fn to_owned(&self) -> Self::Owned { + unsafe { RefCountable::inc_ref(self) } + } +} + +unsafe impl RefCountable for NamedTypeReference { + unsafe fn inc_ref(handle: &Self) -> Ref { + Self::ref_from_raw(BNNewNamedTypeReference(handle.handle)) + } + + unsafe fn dec_ref(handle: &Self) { + BNFreeNamedTypeReference(handle.handle) + } +} + +impl Debug for NamedTypeReference { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + write!(f, "{} (id: {})", self.name(), self.id()) + } +} + +/////////////////// +// QualifiedName + +#[repr(transparent)] +pub struct QualifiedName(pub(crate) BNQualifiedName); + +impl QualifiedName { + // TODO : I think this is bad + pub fn string(&self) -> String { + unsafe { + slice::from_raw_parts(self.0.name, self.0.nameCount) + .iter() + .map(|c| CStr::from_ptr(*c).to_string_lossy()) + .collect::>() + .join("::") + } + } + + pub fn join(&self) -> Cow { + let join: *mut c_char = self.0.join; + unsafe { CStr::from_ptr(join) }.to_string_lossy() + } + + pub fn strings(&self) -> Vec> { + let names: *mut *mut c_char = self.0.name; + unsafe { + slice::from_raw_parts(names, self.0.nameCount) + .iter() + .map(|name| CStr::from_ptr(*name).to_string_lossy()) + .collect::>() + } + } + + pub fn len(&self) -> usize { + self.0.nameCount + } + + pub fn is_empty(&self) -> bool { + self.0.nameCount == 0 + } +} + +impl From for QualifiedName { + fn from(name: S) -> Self { + let join = BnString::new("::"); + let name = name.into_bytes_with_nul(); + let mut list = vec![name.as_ref().as_ptr() as *const _]; + + QualifiedName(BNQualifiedName { + name: unsafe { BNAllocStringList(list.as_mut_ptr(), 1) }, + join: join.into_raw(), + nameCount: 1, + }) + } +} + +impl From> for QualifiedName { + fn from(names: Vec) -> Self { + let join = BnString::new("::"); + let names = names + .into_iter() + .map(|n| n.into_bytes_with_nul()) + .collect::>(); + let mut list = names + .iter() + .map(|n| n.as_ref().as_ptr() as *const _) + .collect::>(); + + QualifiedName(BNQualifiedName { + name: unsafe { BNAllocStringList(list.as_mut_ptr(), list.len()) }, + join: join.into_raw(), + nameCount: list.len(), + }) + } +} + +impl Clone for QualifiedName { + fn clone(&self) -> Self { + let strings = self.strings(); + let name = Self::from(strings.iter().collect::>>()); + name + } +} + +impl Hash for QualifiedName { + fn hash(&self, state: &mut H) { + self.join().hash(state); + self.strings().hash(state); + } +} + +impl Debug for QualifiedName { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + write!(f, "{}", self.string()) + } +} + +impl Display for QualifiedName { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + write!(f, "{}", self.string()) + } +} + +impl PartialEq for QualifiedName { + fn eq(&self, other: &Self) -> bool { + self.strings() == other.strings() + } +} + +impl Eq for QualifiedName {} + +impl Drop for QualifiedName { + fn drop(&mut self) { + unsafe { + BNFreeQualifiedName(&mut self.0); + } + } +} + +impl CoreArrayProvider for QualifiedName { + type Raw = BNQualifiedName; + type Context = (); +} +unsafe impl CoreOwnedArrayProvider 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 { + mem::transmute(raw) + } +} + +////////////////////////// +// QualifiedNameAndType + +#[repr(transparent)] +pub struct QualifiedNameAndType(pub(crate) BNQualifiedNameAndType); + +impl QualifiedNameAndType { + pub fn name(&self) -> &QualifiedName { + unsafe { mem::transmute(&self.0.name) } + } + + pub fn type_object(&self) -> Guard { + unsafe { Guard::new(Type::from_raw(self.0.type_), self) } + } +} + +impl Drop for QualifiedNameAndType { + fn drop(&mut self) { + unsafe { + BNFreeQualifiedNameAndType(&mut self.0); + } + } +} + +impl CoreArrayProvider for QualifiedNameAndType { + type Raw = BNQualifiedNameAndType; + type Context = (); +} +unsafe impl CoreOwnedArrayProvider 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 { + mem::transmute(raw) + } +} + +////////////////////////// +// QualifiedNameTypeAndId + +#[repr(transparent)] +pub struct QualifiedNameTypeAndId(pub(crate) BNQualifiedNameTypeAndId); + +impl QualifiedNameTypeAndId { + pub fn name(&self) -> &QualifiedName { + unsafe { mem::transmute(&self.0.name) } + } + + pub fn id(&self) -> &BnStr { + unsafe { BnStr::from_raw(self.0.id) } + } + + pub fn type_object(&self) -> Guard { + unsafe { Guard::new(Type::from_raw(self.0.type_), self) } + } +} + +impl Drop for QualifiedNameTypeAndId { + fn drop(&mut self) { + unsafe { + BNFreeQualifiedNameTypeAndId(&mut self.0); + } + } +} + +impl CoreArrayProvider for QualifiedNameTypeAndId { + type Raw = BNQualifiedNameTypeAndId; + type Context = (); +} +unsafe impl CoreOwnedArrayProvider 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 { + mem::transmute(raw) + } +} + +////////////////////////// +// NameAndType + +pub struct NameAndType { + pub name: S, + pub t: Conf>, +} + +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 fn new(name: S, t: &Ref, confidence: u8) -> Self { + Self { + name, + t: Conf::new(t.clone(), 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 type_with_confidence(&self) -> Conf> { + self.t.clone() + } +} + +impl CoreArrayProvider for NameAndType { + type Raw = BNNameAndType; + type Context = (); +} + +unsafe impl CoreOwnedArrayProvider 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) + } +} + +////////////////// +// DataVariable + +pub struct DataVariable { + pub address: u64, + pub t: Conf>, + pub auto_discovered: bool, +} + +// 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, +// } +// } +// } + +impl DataVariable { + pub fn type_with_confidence(&self) -> Conf> { + Conf::new(self.t.contents.clone(), self.t.confidence) + } + + pub fn symbol(&self, bv: &BinaryView) -> Option> { + bv.symbol_by_address(self.address).ok() + } +} + +impl CoreArrayProvider for DataVariable { + type Raw = BNDataVariable; + type Context = (); +} +unsafe impl CoreOwnedArrayProvider 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 { + mem::transmute(raw) + } +} + +///////////////////////// +// DataVariableAndName + +pub struct DataVariableAndName { + pub address: u64, + pub t: Conf>, + pub auto_discovered: bool, + pub name: S, +} + +impl DataVariableAndName { + pub(crate) fn from_raw(var: &BNDataVariableAndName) -> Self { + Self { + address: var.address, + t: Conf::new(unsafe { Type::ref_from_raw(var.type_) }, var.typeConfidence), + auto_discovered: var.autoDiscovered, + name: raw_to_string(var.name).unwrap(), + } + } +} + +impl DataVariableAndName { + pub fn new(address: u64, t: Conf>, auto_discovered: bool, name: S) -> Self { + Self { + address, + t, + auto_discovered, + name, + } + } + + pub fn type_with_confidence(&self) -> Conf> { + Conf::new(self.t.contents.clone(), self.t.confidence) + } +} + +// unsafe impl CoreArrayProvider for DataVariableAndName { +// type Raw = BNDataVariableAndName; +// type Context = (); +// } + +// unsafe impl CoreOwnedArrayProvider for DataVariableAndName { +// unsafe fn free(raw: *mut Self::Raw, count: usize, _context: &Self::Context) { +// BNFreeDataVariablesAndName(raw, count); +// } +// } + +// unsafe impl<'a, S: 'a + BnStrCompatible> CoreArrayWrapper<'a> for DataVariableAndName { +// type Wrapped = &'a DataVariableAndName; +// } + +// unsafe impl<'a, S: 'a + BnStrCompatible> CoreArrayWrapper<'a> for DataVariableAndName { +// unsafe fn wrap_raw(raw: &'a Self::Raw, _context: &'a Self::Context) -> Self::Wrapped { +// mem::transmute(raw) +// } +// } diff --git a/under_construction.png b/under_construction.png new file mode 100644 index 0000000..e39b1ed Binary files /dev/null and b/under_construction.png differ