diff options
| author | Rusty Wagner <rusty.wagner@gmail.com> | 2023-12-19 22:39:33 -0700 |
|---|---|---|
| committer | Rusty Wagner <rusty.wagner@gmail.com> | 2024-01-04 12:42:11 -0700 |
| commit | a9cfac7ff14d93ff22092366f99eb2dd3213ea7e (patch) | |
| tree | b61163e052a1827846b0c3b8330b0289aac8cabf | |
| parent | a82a2105fd1b1a99bddb835ed1d9dbf2e2a5c56f (diff) | |
Add RISC-V architecture plugin
| -rw-r--r-- | arch/riscv/CMakeLists.txt | 114 | ||||
| -rw-r--r-- | arch/riscv/Cargo.lock | 368 | ||||
| -rw-r--r-- | arch/riscv/Cargo.toml | 22 | ||||
| -rw-r--r-- | arch/riscv/README.md | 17 | ||||
| -rw-r--r-- | arch/riscv/disasm/Cargo.toml | 8 | ||||
| -rw-r--r-- | arch/riscv/disasm/src/lib.rs | 3141 | ||||
| -rw-r--r-- | arch/riscv/src/lib.rs | 2570 | ||||
| -rw-r--r-- | arch/riscv/src/liftcheck.rs | 427 | ||||
| -rw-r--r-- | platform/linux/platform_linux.cpp | 48 |
9 files changed, 6715 insertions, 0 deletions
diff --git a/arch/riscv/CMakeLists.txt b/arch/riscv/CMakeLists.txt new file mode 100644 index 00000000..ffa28b7b --- /dev/null +++ b/arch/riscv/CMakeLists.txt @@ -0,0 +1,114 @@ +cmake_minimum_required(VERSION 3.9 FATAL_ERROR) + +project(arch_riscv) + +file(GLOB PLUGIN_SOURCES CONFIGURE_DEPENDS + ../../binaryninjacore.h + ${PROJECT_SOURCE_DIR}/Cargo.toml + ${PROJECT_SOURCE_DIR}/src/*.rs + ${PROJECT_SOURCE_DIR}/disasm/src/*.rs) + +if(CMAKE_BUILD_TYPE MATCHES Debug) + set(TARGET_DIR ${PROJECT_BINARY_DIR}/target/debug) + set(CARGO_OPTS --target-dir=${PROJECT_BINARY_DIR}/target) +else() + set(TARGET_DIR ${PROJECT_BINARY_DIR}/target/release) + set(CARGO_OPTS --target-dir=${PROJECT_BINARY_DIR}/target --release) +endif() + +if(FORCE_COLORED_OUTPUT) + set(CARGO_OPTS ${CARGO_OPTS} --color always) +endif() + +set(CARGO_FEATURES "") +set(OUTPUT_FILE_NAME ${CMAKE_SHARED_LIBRARY_PREFIX}arch_riscv${CMAKE_SHARED_LIBRARY_SUFFIX}) +set(OUTPUT_PDB_NAME ${CMAKE_SHARED_LIBRARY_PREFIX}arch_riscv.pdb) +set(OUTPUT_FILE_PATH ${BN_CORE_PLUGIN_DIR}/${OUTPUT_FILE_NAME}) +set(OUTPUT_PDB_PATH ${BN_CORE_PLUGIN_DIR}/${OUTPUT_PDB_NAME}) + +set(BINJA_LIB_DIR ${BN_INSTALL_BIN_DIR}) + +add_custom_target(arch_riscv ALL DEPENDS ${OUTPUT_FILE_PATH}) +add_dependencies(arch_riscv binaryninjaapi) +get_target_property(BN_API_SOURCE_DIR binaryninjaapi SOURCE_DIR) +list(APPEND CMAKE_MODULE_PATH "${BN_API_SOURCE_DIR}/cmake") +find_package(BinaryNinjaCore REQUIRED) + +set_property(TARGET arch_riscv PROPERTY OUTPUT_FILE_PATH ${OUTPUT_FILE_PATH}) + +# Add the whole api to the depends too +file(GLOB_RECURSE API_SOURCES CONFIGURE_DEPENDS + ${BN_API_SOURCE_DIR}/rust/src/*.rs + ${BN_API_SOURCE_DIR}/rust/binaryninjacore-sys/src/*.rs) + +find_program(RUSTUP_PATH rustup REQUIRED HINTS ~/.cargo/bin) +set(RUSTUP_COMMAND ${RUSTUP_PATH} run ${CARGO_STABLE_VERSION} cargo) + +if(APPLE) + if(UNIVERSAL) + if(CMAKE_BUILD_TYPE MATCHES Debug) + set(AARCH64_LIB_PATH ${PROJECT_BINARY_DIR}/target/aarch64-apple-darwin/debug/${OUTPUT_FILE_NAME}) + set(X86_64_LIB_PATH ${PROJECT_BINARY_DIR}/target/x86_64-apple-darwin/debug/${OUTPUT_FILE_NAME}) + else() + set(AARCH64_LIB_PATH ${PROJECT_BINARY_DIR}/target/aarch64-apple-darwin/release/${OUTPUT_FILE_NAME}) + set(X86_64_LIB_PATH ${PROJECT_BINARY_DIR}/target/x86_64-apple-darwin/release/${OUTPUT_FILE_NAME}) + endif() + + add_custom_command( + OUTPUT ${OUTPUT_FILE_PATH} + COMMAND ${CMAKE_COMMAND} -E env + MACOSX_DEPLOYMENT_TARGET=10.14 BINARYNINJADIR=${BINJA_LIB_DIR} + ${RUSTUP_COMMAND} clean --target=aarch64-apple-darwin ${CARGO_OPTS} + COMMAND ${CMAKE_COMMAND} -E env + MACOSX_DEPLOYMENT_TARGET=10.14 BINARYNINJADIR=${BINJA_LIB_DIR} + ${RUSTUP_COMMAND} clean --target=x86_64-apple-darwin ${CARGO_OPTS} + COMMAND ${CMAKE_COMMAND} -E env + MACOSX_DEPLOYMENT_TARGET=10.14 BINARYNINJADIR=${BINJA_LIB_DIR} + ${RUSTUP_COMMAND} build --target=aarch64-apple-darwin ${CARGO_OPTS} ${CARGO_FEATURES} + COMMAND ${CMAKE_COMMAND} -E env + MACOSX_DEPLOYMENT_TARGET=10.14 BINARYNINJADIR=${BINJA_LIB_DIR} + ${RUSTUP_COMMAND} build --target=x86_64-apple-darwin ${CARGO_OPTS} ${CARGO_FEATURES} + COMMAND lipo -create ${AARCH64_LIB_PATH} ${X86_64_LIB_PATH} -output ${OUTPUT_FILE_PATH} + WORKING_DIRECTORY ${PROJECT_SOURCE_DIR} + DEPENDS ${PLUGIN_SOURCES} ${API_SOURCES} + ) + else() + if(CMAKE_BUILD_TYPE MATCHES Debug) + set(LIB_PATH ${PROJECT_BINARY_DIR}/target/debug/${OUTPUT_FILE_NAME}) + else() + set(LIB_PATH ${PROJECT_BINARY_DIR}/target/release/${OUTPUT_FILE_NAME}) + endif() + + add_custom_command( + OUTPUT ${OUTPUT_FILE_PATH} + COMMAND ${CMAKE_COMMAND} -E env + MACOSX_DEPLOYMENT_TARGET=10.14 BINARYNINJADIR=${BINJA_LIB_DIR} + ${RUSTUP_COMMAND} clean ${CARGO_OPTS} + COMMAND ${CMAKE_COMMAND} -E env + MACOSX_DEPLOYMENT_TARGET=10.14 BINARYNINJADIR=${BINJA_LIB_DIR} + ${RUSTUP_COMMAND} build ${CARGO_OPTS} ${CARGO_FEATURES} + COMMAND ${CMAKE_COMMAND} -E copy ${LIB_PATH} ${OUTPUT_FILE_PATH} + WORKING_DIRECTORY ${PROJECT_SOURCE_DIR} + DEPENDS ${PLUGIN_SOURCES} ${API_SOURCES} + ) + endif() +elseif(WIN32) + add_custom_command( + OUTPUT ${OUTPUT_FILE_PATH} + COMMAND ${CMAKE_COMMAND} -E env BINARYNINJADIR=${BINJA_LIB_DIR} ${RUSTUP_COMMAND} clean ${CARGO_OPTS} + COMMAND ${CMAKE_COMMAND} -E env BINARYNINJADIR=${BINJA_LIB_DIR} ${RUSTUP_COMMAND} build ${CARGO_OPTS} ${CARGO_FEATURES} + COMMAND ${CMAKE_COMMAND} -E copy ${TARGET_DIR}/${OUTPUT_FILE_NAME} ${OUTPUT_FILE_PATH} + COMMAND ${CMAKE_COMMAND} -E copy ${TARGET_DIR}/${OUTPUT_PDB_NAME} ${OUTPUT_PDB_PATH} + WORKING_DIRECTORY ${PROJECT_SOURCE_DIR} + DEPENDS ${PLUGIN_SOURCES} ${API_SOURCES} + ) +else() + add_custom_command( + OUTPUT ${OUTPUT_FILE_PATH} + COMMAND ${CMAKE_COMMAND} -E env BINARYNINJADIR=${BINJA_LIB_DIR} ${RUSTUP_COMMAND} clean ${CARGO_OPTS} + COMMAND ${CMAKE_COMMAND} -E env BINARYNINJADIR=${BINJA_LIB_DIR} ${RUSTUP_COMMAND} build ${CARGO_OPTS} ${CARGO_FEATURES} + COMMAND ${CMAKE_COMMAND} -E copy ${TARGET_DIR}/${OUTPUT_FILE_NAME} ${OUTPUT_FILE_PATH} + WORKING_DIRECTORY ${PROJECT_SOURCE_DIR} + DEPENDS ${PLUGIN_SOURCES} ${API_SOURCES} + ) +endif() diff --git a/arch/riscv/Cargo.lock b/arch/riscv/Cargo.lock new file mode 100644 index 00000000..b98a9faa --- /dev/null +++ b/arch/riscv/Cargo.lock @@ -0,0 +1,368 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "aho-corasick" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2969dcb958b36655471fc61f7e416fa76033bdd4bfed0678d8fee1e2d07a1f0" +dependencies = [ + "memchr", +] + +[[package]] +name = "arch_riscv" +version = "0.1.0" +dependencies = [ + "binaryninja", + "log", + "rayon", + "riscv-dis", +] + +[[package]] +name = "autocfg" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" + +[[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", + "cexpr", + "clang-sys", + "lazy_static", + "lazycell", + "log", + "peeking_take_while", + "prettyplease", + "proc-macro2", + "quote", + "regex", + "rustc-hash", + "shlex", + "syn", + "which", +] + +[[package]] +name = "bitflags" +version = "2.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "327762f6e5a765692301e5bb513e0d9fef63be86bbc14528052b1cd3e6f03e07" + +[[package]] +name = "byteorder" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" + +[[package]] +name = "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.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a050e2153c5be08febd6734e29298e844fdb0fa21aeddd63b4eb7baa106c69b" +dependencies = [ + "glob", + "libc", + "libloading", +] + +[[package]] +name = "crossbeam-deque" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fca89a0e215bab21874660c67903c5f143333cab1da83d041c7ded6053774751" +dependencies = [ + "cfg-if", + "crossbeam-epoch", + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-epoch" +version = "0.9.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2d2fe95351b870527a5d09bf563ed3c97c0cffb87cf1c78a591bf48bb218d9aa" +dependencies = [ + "autocfg", + "cfg-if", + "crossbeam-utils", + "memoffset", +] + +[[package]] +name = "crossbeam-utils" +version = "0.8.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c06d96137f14f244c37f989d9fff8f95e6c18b918e71f36638f8c49112e4c78f" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "either" +version = "1.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a26ae43d7bcc3b814de94796a5e736d4029efb0ee900c12e2d54c993ad1a1e07" + +[[package]] +name = "glob" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2fabcfbdc87f4758337ca535fb41a6d701b65693ce38287d856d1674551ec9b" + +[[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.151" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "302d7ab3130588088d277783b1e2d2e10c9e9e4a16dd9050e6ec93fb3e7048f4" + +[[package]] +name = "libloading" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b67380fd3b2fbe7527a606e18729d21c6f3951633d0500574c4dc22d2d638b9f" +dependencies = [ + "cfg-if", + "winapi", +] + +[[package]] +name = "log" +version = "0.4.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b5e6163cb8c49088c2c36f57875e58ccd8c87c7427f7fbd50ea6710b2f3f2e8f" + +[[package]] +name = "memchr" +version = "2.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f665ee40bc4a3c5590afb1e9677db74a508659dfd71e126420da8274909a0167" + +[[package]] +name = "memoffset" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a634b1c61a95585bd15607c6ab0c4e5b226e695ff2800ba0cdccddf208c406c" +dependencies = [ + "autocfg", +] + +[[package]] +name = "minimal-lexical" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" + +[[package]] +name = "nom" +version = "7.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a" +dependencies = [ + "memchr", + "minimal-lexical", +] + +[[package]] +name = "peeking_take_while" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "19b17cddbe7ec3f8bc800887bab5e717348c95ea2ca0b1bf0837fb964dc67099" + +[[package]] +name = "prettyplease" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae005bd773ab59b4725093fd7df83fd7892f7d8eafb48dbd7de6e024e4215f9d" +dependencies = [ + "proc-macro2", + "syn", +] + +[[package]] +name = "proc-macro2" +version = "1.0.70" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "39278fbbf5fb4f646ce651690877f89d1c5811a3d4acb27700c1cb3cdb78fd3b" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.33" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5267fca4496028628a95160fc423a33e8b2e6af8a5302579e322e4b520293cae" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "rayon" +version = "1.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c27db03db7734835b3f53954b534c91069375ce6ccaa2e065441e07d9b6cdb1" +dependencies = [ + "either", + "rayon-core", +] + +[[package]] +name = "rayon-core" +version = "1.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5ce3fb6ad83f861aac485e76e1985cd109d9a3713802152be56c3b1f0e0658ed" +dependencies = [ + "crossbeam-deque", + "crossbeam-utils", +] + +[[package]] +name = "regex" +version = "1.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "380b951a9c5e80ddfd6136919eef32310721aa4aacd4889a8d39124b026ab343" +dependencies = [ + "aho-corasick", + "memchr", + "regex-automata", + "regex-syntax", +] + +[[package]] +name = "regex-automata" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f804c7828047e88b2d32e2d7fe5a105da8ee3264f01902f796c8e067dc2483f" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] + +[[package]] +name = "regex-syntax" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c08c74e62047bb2de4ff487b251e4a92e24f48745648451635cec7d591162d9f" + +[[package]] +name = "riscv-dis" +version = "0.1.0" +dependencies = [ + "byteorder", +] + +[[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.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7cee0529a6d40f580e7a5e6c495c8fbfe21b7b52795ed4bb5e62cdf92bc6380" + +[[package]] +name = "syn" +version = "2.0.41" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "44c8b28c477cc3bf0e7966561e3460130e1255f7a1cf71931075f1c5e7a7e269" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "unicode-ident" +version = "1.0.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" + +[[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" diff --git a/arch/riscv/Cargo.toml b/arch/riscv/Cargo.toml new file mode 100644 index 00000000..a9148e6e --- /dev/null +++ b/arch/riscv/Cargo.toml @@ -0,0 +1,22 @@ +[package] +name = "arch_riscv" +version = "0.1.0" +authors = ["Ryan Snyder <ryan.snyder.or@gmail.com>"] +edition = "2021" + +[dependencies] +binaryninja = { path = "../../rust" } +riscv-dis = { path = "disasm" } +log = "0.4" +rayon = { version = "1.0", optional = true } + +[features] +default = [] +liftcheck = ["rayon", "binaryninja/rayon"] + +[lib] +crate-type = ["cdylib"] + +[profile.release] +panic = "abort" +lto = true diff --git a/arch/riscv/README.md b/arch/riscv/README.md new file mode 100644 index 00000000..822f3c7a --- /dev/null +++ b/arch/riscv/README.md @@ -0,0 +1,17 @@ +# arch-riscv + +This is the RISC-V architecture plugin that ships with Binary Ninja. + +## Building + +Building the architecture plugin requires the Rust language development tools. + +Run `cargo build --release` to build the plugin. The plugin can be found in the `target/release` directory as `libarch_riscv.so`, `libarch_riscv.dylib` or `arch_riscv.dll` depending on your platform. + +To install the plugin, first launch Binary Ninja and uncheck the "RISC-V architecture plugin" option in the "Core Plugins" section. This will cause Binary Ninja to stop loading the bundled plugin so that its replacement can be loaded. Once this is complete, you can copy the plugin into the user plugins directory (you can locate this by using the "Open Plugin Folder" option in the Binary Ninja UI). + +**Do not replace the architecture plugin in the Binary Ninja install directory. This will be overwritten every time there is a Binary Ninja update. Use the above process to ensure that updates do not automatically uninstall your custom build.** + +## Pull Requests + +Please follow whatever formatting conventions are present in the file you edit. Pay attention to curly brackets, spacing, tabs vs. spaces, etc. diff --git a/arch/riscv/disasm/Cargo.toml b/arch/riscv/disasm/Cargo.toml new file mode 100644 index 00000000..a799dc18 --- /dev/null +++ b/arch/riscv/disasm/Cargo.toml @@ -0,0 +1,8 @@ +[package] +name = "riscv-dis" +version = "0.1.0" +authors = ["Ryan Snyder <ryan.snyder.or@gmail.com>"] +edition = "2021" + +[dependencies] +byteorder = "1" diff --git a/arch/riscv/disasm/src/lib.rs b/arch/riscv/disasm/src/lib.rs new file mode 100644 index 00000000..6762bb5d --- /dev/null +++ b/arch/riscv/disasm/src/lib.rs @@ -0,0 +1,3141 @@ +// TODO list +// op-imm shift amounts +// clean up the compressed stuff (esp MISC-ALU) +// finish transition to from_instr32 from 'new' +// make the various component structs smaller (8 bit IntReg/FloatReg etc.) + +extern crate byteorder; + +use std::borrow::Cow; +use std::fmt; +use std::marker::PhantomData; +use std::mem; + +use byteorder::{ByteOrder, LittleEndian}; + +#[derive(Copy, Clone, Debug, Eq, PartialEq)] +pub enum Error { + TooShort, + UnhandledLength, + Unaligned, + InvalidOpcode, + InvalidSubop, + BadRegister, +} + +pub type DisResult<T> = Result<T, Error>; + +#[derive(Copy, Clone, Debug)] +pub enum Op<D: RiscVDisassembler> { + // + // RV32I + // + + // LOAD + Load(LoadTypeInst<D>), + + // MISC-MEM + Fence(ITypeIntInst<D>), + FenceI(ITypeIntInst<D>), + + // OP-IMM + AddI(ITypeIntInst<D>), + SltI(ITypeIntInst<D>), + SltIU(ITypeIntInst<D>), + XorI(ITypeIntInst<D>), + OrI(ITypeIntInst<D>), + AndI(ITypeIntInst<D>), + SllI(ITypeIntInst<D>), + SrlI(ITypeIntInst<D>), + SraI(ITypeIntInst<D>), + + // AUIPC + Auipc(UTypeInst<D>), + + // STORE + Store(StoreTypeInst<D>), + + // OP + Add(RTypeIntInst<D>), + Sll(RTypeIntInst<D>), + Slt(RTypeIntInst<D>), + SltU(RTypeIntInst<D>), + Xor(RTypeIntInst<D>), + Srl(RTypeIntInst<D>), + Or(RTypeIntInst<D>), + And(RTypeIntInst<D>), + Sub(RTypeIntInst<D>), + Sra(RTypeIntInst<D>), + + // LUI + Lui(UTypeInst<D>), + + // BRANCH + Beq(BTypeInst<D>), + Bne(BTypeInst<D>), + Blt(BTypeInst<D>), + Bge(BTypeInst<D>), + BltU(BTypeInst<D>), + BgeU(BTypeInst<D>), + + // JALR + Jalr(ITypeIntInst<D>), + + // JAL + Jal(JTypeInst<D>), + + // SYSTEM + Ecall, + Ebreak, + Csrrw(ITypeIntInst<D>), + Csrrs(ITypeIntInst<D>), + Csrrc(ITypeIntInst<D>), + CsrrwI(CsrITypeInst<D>), + CsrrsI(CsrITypeInst<D>), + CsrrcI(CsrITypeInst<D>), + + Uret, + Sret, + Mret, + Wfi, + SfenceVma(RTypeIntInst<D>), + + // + // RV64I + // + + // OP-IMM-32 + AddIW(ITypeIntInst<D>), + SllIW(ITypeIntInst<D>), + SrlIW(ITypeIntInst<D>), + SraIW(ITypeIntInst<D>), + + // OP-32 + AddW(RTypeIntInst<D>), + SllW(RTypeIntInst<D>), + SrlW(RTypeIntInst<D>), + SubW(RTypeIntInst<D>), + SraW(RTypeIntInst<D>), + + // + // RV32M + // + + // OP + Mul(RTypeIntInst<D>), + MulH(RTypeIntInst<D>), + MulHSU(RTypeIntInst<D>), + MulHU(RTypeIntInst<D>), + Div(RTypeIntInst<D>), + DivU(RTypeIntInst<D>), + Rem(RTypeIntInst<D>), + RemU(RTypeIntInst<D>), + + // + // RV64M + // + + // OP-32 + MulW(RTypeIntInst<D>), + DivW(RTypeIntInst<D>), + DivUW(RTypeIntInst<D>), + RemW(RTypeIntInst<D>), + RemUW(RTypeIntInst<D>), + + // + // RV32A + // + + // AMO + Lr(AtomicInst<D>), + Sc(AtomicInst<D>), + AmoSwap(AtomicInst<D>), + AmoAdd(AtomicInst<D>), + AmoXor(AtomicInst<D>), + AmoAnd(AtomicInst<D>), + AmoOr(AtomicInst<D>), + AmoMin(AtomicInst<D>), + AmoMax(AtomicInst<D>), + AmoMinU(AtomicInst<D>), + AmoMaxU(AtomicInst<D>), + + // + // RV32F + // + LoadFp(FpMemInst<D>), + StoreFp(FpMemInst<D>), + Fmadd(FpMAddInst<D>), + Fmsub(FpMAddInst<D>), + Fnmsub(FpMAddInst<D>), + Fnmadd(FpMAddInst<D>), + Fadd(RTypeFloatRoundInst<D>), + Fsub(RTypeFloatRoundInst<D>), + Fmul(RTypeFloatRoundInst<D>), + Fdiv(RTypeFloatRoundInst<D>), + Fsqrt(RTypeFloatRoundInst<D>), + Fsgnj(RTypeFloatInst<D>), + Fsgnjn(RTypeFloatInst<D>), + Fsgnjx(RTypeFloatInst<D>), + Fmin(RTypeFloatInst<D>), + Fmax(RTypeFloatInst<D>), + Fle(RTypeFloatCmpInst<D>), + Flt(RTypeFloatCmpInst<D>), + Feq(RTypeFloatCmpInst<D>), + Fcvt(FpCvtInst<D>), + FcvtToInt(FpCvtToIntInst<D>), + FcvtFromInt(FpCvtFromIntInst<D>), + FmvToInt(FpMvToIntInst<D>), + FmvFromInt(FpMvFromIntInst<D>), + Fclass(FpClassInst<D>), +} + +pub trait Register { + fn new(id: u32) -> Self; + + fn id(&self) -> u32; + fn valid(&self) -> bool; +} + +#[derive(Copy, Clone, Debug, PartialEq, Eq)] +pub enum RoundMode { + RoundNearestEven, + RoundTowardZero, + RoundDown, + RoundUp, + RoundMaxMagnitude, + Dynamic, +} + +#[derive(Copy, Clone, Debug, Eq, PartialEq)] +pub struct IntReg<D: RiscVDisassembler> { + reg_id: u8, + _dis: PhantomData<D>, +} + +impl<D: RiscVDisassembler> Register for IntReg<D> { + #[inline(always)] + fn new(id: u32) -> Self { + let ret = Self { + reg_id: id as u8, + _dis: PhantomData, + }; + + debug_assert!(ret.valid()); + + ret + } + + #[inline(always)] + fn id(&self) -> u32 { + self.reg_id as u32 + } + + #[inline(always)] + fn valid(&self) -> bool { + (self.reg_id as u32) < <D::RegFile as RegFile>::int_reg_count() + } +} + +#[derive(Copy, Clone, Debug, Eq, PartialEq)] +pub struct FloatReg<D: RiscVDisassembler> { + reg_id: u8, + _dis: PhantomData<D>, +} + +impl<D: RiscVDisassembler> Register for FloatReg<D> { + #[inline(always)] + fn new(id: u32) -> Self { + let ret = Self { + reg_id: id as u8, + _dis: PhantomData, + }; + + debug_assert!(ret.valid()); + + ret + } + + #[inline(always)] + fn id(&self) -> u32 { + self.reg_id as u32 + } + + #[inline(always)] + fn valid(&self) -> bool { + (self.reg_id as u32) < <D::RegFile as RegFile>::int_reg_count() + } +} + +pub trait IntRegType: Sized { + #[inline(always)] + fn width() -> usize { + mem::size_of::<Self>() + } +} + +pub trait FloatRegType: Sized { + #[inline(always)] + fn width() -> usize { + mem::size_of::<Self>() + } + + #[inline(always)] + fn present() -> bool { + mem::size_of::<Self>() != 0 + } +} + +impl IntRegType for u32 {} +impl IntRegType for u64 {} +impl FloatRegType for () {} +impl FloatRegType for f32 {} +impl FloatRegType for f64 {} + +pub trait RegFile: Sized + Copy + Clone { + type Int: IntRegType; + type Float: FloatRegType; + + #[inline(always)] + fn int_reg_count() -> u32 { + 32 + } +} + +#[derive(Copy, Clone, Debug)] +pub struct Rv32IRegs; +impl RegFile for Rv32IRegs { + type Int = u32; + type Float = (); +} + +#[derive(Copy, Clone, Debug)] +pub struct Rv32ERegs; +impl RegFile for Rv32ERegs { + type Int = u32; + type Float = (); + + #[inline(always)] + fn int_reg_count() -> u32 { + 16 + } +} + +#[derive(Copy, Clone, Debug)] +pub struct Rv32GRegs; +impl RegFile for Rv32GRegs { + type Int = u32; + type Float = f64; +} + +#[derive(Copy, Clone, Debug)] +pub struct Rv64GRegs; +impl RegFile for Rv64GRegs { + type Int = u64; + type Float = f64; +} + +pub enum Operand<D: RiscVDisassembler> { + R(IntReg<D>), + F(FloatReg<D>), + I(i32), + M(i32, IntReg<D>), // reg + displacement + RM(RoundMode), +} + +impl<D: RiscVDisassembler> fmt::Display for Operand<D> { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match self { + &Operand::R(ref r) => write!(f, "x{}", r.id()), + &Operand::F(ref r) => write!(f, "f{}", r.id()), + &Operand::I(i) => match i { + -0x80000..=-1 => write!(f, "-{:x}", -i), + _ => write!(f, "{:x}", i), + }, + &Operand::M(i, ref r) => { + if i < 0 { + write!(f, "-{:x}(x{})", -i, r.id()) + } else { + write!(f, "{:x}(x{})", i, r.id()) + } + } + &Operand::RM(ref r) => write!(f, "{}", r.name()), + } + } +} + +#[derive(Copy, Clone, Debug)] +struct Instr32(u32); +impl Instr32 { + #[inline(always)] + fn extract_bits(self, start_bit: u32, width: u32) -> u32 { + self.0.wrapping_shr(start_bit) & 1u32.wrapping_shl(width).wrapping_sub(1) + } + + #[inline(always)] + fn opcode(self) -> u32 { + self.extract_bits(0, 7) + } + + #[inline(always)] + fn rd(self) -> u32 { + self.extract_bits(7, 5) + } + + #[inline(always)] + fn rs1(self) -> u32 { + self.extract_bits(15, 5) + } + + #[inline(always)] + fn rs2(self) -> u32 { + self.extract_bits(20, 5) + } + + #[inline(always)] + fn rs3(self) -> u32 { + self.extract_bits(27, 5) + } + + #[inline(always)] + fn funct3(self) -> u32 { + self.extract_bits(12, 3) + } + + #[inline(always)] + fn funct7(self) -> u32 { + self.extract_bits(25, 7) + } + + #[inline(always)] + fn rm(self) -> u32 { + self.extract_bits(12, 3) + } + + #[inline(always)] + fn fsize(self) -> u32 { + self.extract_bits(25, 2) + } + + #[inline(always)] + fn fop(self) -> u32 { + self.extract_bits(27, 5) + } + + #[inline(always)] + fn i_imm(self) -> i32 { + (self.0 as i32) >> 20 + } + + #[inline(always)] + fn s_imm(self) -> i32 { + (((self.0 as i32) >> 20) & !0x1f) | self.extract_bits(7, 5) as i32 + } + + #[inline(always)] + fn b_imm(self) -> i32 { + let b_imm = self.s_imm(); + (b_imm & !0x801) | ((b_imm & 1) << 11) + } + + #[inline(always)] + fn u_imm(self) -> i32 { + (self.0 & !0xfff) as i32 + } + + #[inline(always)] + fn j_imm(self) -> i32 { + let mut j_imm = (((self.0 as i32) >> 11) as u32) & 0xfff00000; + j_imm |= 0x000ff000 & self.0; + j_imm |= self.extract_bits(20, 11); + ((j_imm & !0x801) | ((j_imm & 1) << 11)) as i32 + } +} + +impl RoundMode { + fn from_bits(bits: u32) -> DisResult<RoundMode> { + match bits { + 0b000 => Ok(RoundMode::RoundNearestEven), + 0b001 => Ok(RoundMode::RoundTowardZero), + 0b010 => Ok(RoundMode::RoundDown), + 0b011 => Ok(RoundMode::RoundUp), + 0b100 => Ok(RoundMode::RoundMaxMagnitude), + 0b111 => Ok(RoundMode::Dynamic), + _ => Err(Error::InvalidSubop), + } + } + + pub fn name(&self) -> &'static str { + match self { + RoundMode::RoundNearestEven => "rne", + RoundMode::RoundTowardZero => "rtz", + RoundMode::RoundDown => "rdn", + RoundMode::RoundUp => "rup", + RoundMode::RoundMaxMagnitude => "rmm", + RoundMode::Dynamic => "dyn", + } + } + + pub fn all() -> &'static [RoundMode] { + &[ + RoundMode::RoundNearestEven, + RoundMode::RoundTowardZero, + RoundMode::RoundDown, + RoundMode::RoundUp, + RoundMode::RoundMaxMagnitude, + RoundMode::Dynamic, + ] + } +} + +#[derive(Copy, Clone, Debug)] +pub struct LoadTypeInst<D: RiscVDisassembler> { + width: u8, + zx: bool, + rd: IntReg<D>, + rs1: IntReg<D>, + imm: i16, + _dis: PhantomData<D>, +} + +impl<D: RiscVDisassembler> LoadTypeInst<D> { + #[inline(always)] + fn new(width: usize, zx: bool, rd: IntReg<D>, rs1: IntReg<D>, imm: i32) -> DisResult<Self> { + if width + zx as usize > <D::RegFile as RegFile>::Int::width() { + return Err(Error::InvalidSubop); + } else if !rd.valid() || !rs1.valid() { + return Err(Error::BadRegister); + } + + Ok(Self { + width: width as u8, + zx, + rd, + rs1, + imm: imm as i16, + _dis: PhantomData, + }) + } + + #[inline(always)] + fn from_instr32(inst: Instr32) -> DisResult<Self> { + let width = 1u32.wrapping_shl(inst.extract_bits(12, 2)) as u8; + let zx = inst.extract_bits(14, 1) == 1; + let rd = IntReg::new(inst.rd()); + let rs1 = IntReg::new(inst.rs1()); + + if width as usize + zx as usize > <D::RegFile as RegFile>::Int::width() { + return Err(Error::InvalidSubop); + } else if !rd.valid() || !rs1.valid() { + return Err(Error::BadRegister); + } + + Ok(Self { + width, + zx, + rd, + rs1, + imm: inst.i_imm() as i16, + _dis: PhantomData, + }) + } + + #[inline(always)] + pub fn width(&self) -> usize { + self.width as usize + } + + #[inline(always)] + pub fn zx(&self) -> bool { + self.zx + } + + #[inline(always)] + pub fn rd(&self) -> IntReg<D> { + self.rd + } + + #[inline(always)] + pub fn rs1(&self) -> IntReg<D> { + self.rs1 + } + + #[inline(always)] + pub fn imm(&self) -> i32 { + self.imm as i32 + } +} + +#[derive(Copy, Clone, Debug)] +pub struct StoreTypeInst<D: RiscVDisassembler> { + width: u8, + rs1: IntReg<D>, + rs2: IntReg<D>, + imm: i16, + _dis: PhantomData<D>, +} + +impl<D: RiscVDisassembler> StoreTypeInst<D> { + #[inline(always)] + fn new(width: usize, rs2: IntReg<D>, rs1: IntReg<D>, imm: i32) -> DisResult<Self> { + if width > <D::RegFile as RegFile>::Int::width() { + return Err(Error::InvalidSubop); + } else if !rs1.valid() || !rs2.valid() { + return Err(Error::BadRegister); + } + + Ok(Self { + width: width as u8, + rs1, + rs2, + imm: imm as i16, + _dis: PhantomData, + }) + } + + #[inline(always)] + fn from_instr32(inst: Instr32) -> DisResult<Self> { + let width = 1u32.wrapping_shl(inst.extract_bits(12, 3)) as u8; + let rs1 = IntReg::new(inst.rs1()); + let rs2 = IntReg::new(inst.rs2()); + + if width as usize > <D::RegFile as RegFile>::Int::width() { + return Err(Error::InvalidSubop); + } else if !rs1.valid() || !rs2.valid() { + return Err(Error::BadRegister); + } + + Ok(Self { + width, + rs1, + rs2, + imm: inst.s_imm() as i16, + _dis: PhantomData, + }) + } + + #[inline(always)] + pub fn width(&self) -> usize { + self.width as usize + } + + #[inline(always)] + pub fn rs1(&self) -> IntReg<D> { + self.rs1 + } + + #[inline(always)] + pub fn rs2(&self) -> IntReg<D> { + self.rs2 + } + + #[inline(always)] + pub fn imm(&self) -> i32 { + self.imm as i32 + } +} + +#[derive(Copy, Clone, Debug)] +pub struct ITypeInst<Rd, Rs1> +where + Rd: Register, + Rs1: Register, +{ + inst: Instr32, + _rd: PhantomData<Rd>, + _rs1: PhantomData<Rs1>, +} + +pub type ITypeIntInst<D> = ITypeInst<IntReg<D>, IntReg<D>>; + +impl<Rd, Rs1> ITypeInst<Rd, Rs1> +where + Rd: Register, + Rs1: Register, +{ + #[inline(always)] + fn new(inst: Instr32) -> DisResult<Self> { + let ret = Self { + inst, + _rd: PhantomData, + _rs1: PhantomData, + }; + + if !ret.rd().valid() || !ret.rs1().valid() { + return Err(Error::BadRegister); + } + + Ok(ret) + } + + #[inline(always)] + fn from_ops(rd: Rd, rs1: Rs1, imm: i32) -> Self { + let imm = imm as u32; + let raw: u32 = (imm << 20) | (rd.id() << 7) | (rs1.id() << 15); + + Self { + inst: Instr32(raw), + _rd: PhantomData, + _rs1: PhantomData, + } + } + + #[inline(always)] + pub fn rd(&self) -> Rd { + Rd::new(self.inst.rd()) + } + + #[inline(always)] + pub fn rs1(&self) -> Rs1 { + Rs1::new(self.inst.rs1()) + } + + #[inline(always)] + pub fn imm(&self) -> i32 { + self.inst.i_imm() + } +} + +#[derive(Copy, Clone, Debug)] +pub struct CsrITypeInst<D: RiscVDisassembler> { + inst: Instr32, + _dis: PhantomData<D>, +} + +impl<D: RiscVDisassembler> CsrITypeInst<D> { + #[inline(always)] + fn new(inst: Instr32) -> DisResult<Self> { + let ret = Self { + inst, + _dis: PhantomData, + }; + + if !ret.rd().valid() { + return Err(Error::BadRegister); + } + + Ok(ret) + } + + #[inline(always)] + pub fn rd(&self) -> IntReg<D> { + IntReg::new(self.inst.rd()) + } + + #[inline(always)] + pub fn imm(&self) -> u32 { + self.inst.rs1() + } + + #[inline(always)] + pub fn csr(&self) -> u32 { + self.inst.i_imm() as u32 & 0xfff + } +} + +#[derive(Copy, Clone, Debug)] +pub struct RTypeInst<Rd, Rs1, Rs2> +where + Rd: Register, + Rs1: Register, + Rs2: Register, +{ + inst: Instr32, + _rd: PhantomData<Rd>, + _rs1: PhantomData<Rs1>, + _rs2: PhantomData<Rs2>, +} + +pub type RTypeIntInst<D> = RTypeInst<IntReg<D>, IntReg<D>, IntReg<D>>; + +impl<Rd, Rs1, Rs2> RTypeInst<Rd, Rs1, Rs2> +where + Rd: Register, + Rs1: Register, + Rs2: Register, +{ + #[inline(always)] + fn new(inst: Instr32) -> DisResult<Self> { + let ret = Self { + inst, + _rd: PhantomData, + _rs1: PhantomData, + _rs2: PhantomData, + }; + + if !ret.rd().valid() || !ret.rs1().valid() || !ret.rs2().valid() { + return Err(Error::BadRegister); + } + + Ok(ret) + } + + #[inline(always)] + fn from_ops(rd: Rd, rs1: Rs1, rs2: Rs2) -> Self { + let raw: u32 = (rd.id() << 7) | (rs1.id() << 15) | (rs2.id() << 20); + + Self { + inst: Instr32(raw), + _rd: PhantomData, + _rs1: PhantomData, + _rs2: PhantomData, + } + } + + #[inline(always)] + pub fn rd(&self) -> Rd { + Rd::new(self.inst.rd()) + } + + #[inline(always)] + pub fn rs1(&self) -> Rs1 { + Rs1::new(self.inst.rs1()) + } + + #[inline(always)] + pub fn rs2(&self) -> Rs2 { + Rs2::new(self.inst.rs2()) + } +} + +#[derive(Copy, Clone, Debug)] +pub struct RTypeFloatInst<D: RiscVDisassembler> { + width: u8, + rd: FloatReg<D>, + rs1: FloatReg<D>, + rs2: FloatReg<D>, +} + +impl<D: RiscVDisassembler> RTypeFloatInst<D> { + #[inline(always)] + fn from_instr32(inst: Instr32) -> DisResult<Self> { + let width = match inst.fsize() { + 0b00 => 4, + 0b01 => 8, + 0b11 => 16, + _ => return Err(Error::InvalidSubop), + }; + + if width > <D::RegFile as RegFile>::Float::width() { + return Err(Error::InvalidSubop); + } + + let rd = FloatReg::new(inst.rd()); + let rs1 = FloatReg::new(inst.rs1()); + let rs2 = FloatReg::new(inst.rs2()); + + if !rd.valid() || !rs1.valid() || !rs2.valid() { + return Err(Error::BadRegister); + } + + Ok(Self { + width: width as u8, + rd, + rs1, + rs2, + }) + } + + #[inline(always)] + pub fn width(&self) -> u8 { + self.width + } + + #[inline(always)] + pub fn rd(&self) -> FloatReg<D> { + self.rd + } + + #[inline(always)] + pub fn rs1(&self) -> FloatReg<D> { + self.rs1 + } + + #[inline(always)] + pub fn rs2(&self) -> FloatReg<D> { + self.rs2 + } +} + +#[derive(Copy, Clone, Debug)] +pub struct RTypeFloatCmpInst<D: RiscVDisassembler> { + width: u8, + rd: IntReg<D>, + rs1: FloatReg<D>, + rs2: FloatReg<D>, +} + +impl<D: RiscVDisassembler> RTypeFloatCmpInst<D> { + #[inline(always)] + fn from_instr32(inst: Instr32) -> DisResult<Self> { + let width = match inst.fsize() { + 0b00 => 4, + 0b01 => 8, + 0b11 => 16, + _ => return Err(Error::InvalidSubop), + }; + + if width > <D::RegFile as RegFile>::Float::width() { + return Err(Error::InvalidSubop); + } + + let rd = IntReg::new(inst.rd()); + let rs1 = FloatReg::new(inst.rs1()); + let rs2 = FloatReg::new(inst.rs2()); + + if !rd.valid() || !rs1.valid() || !rs2.valid() { + return Err(Error::BadRegister); + } + + Ok(Self { + width: width as u8, + rd, + rs1, + rs2, + }) + } + + #[inline(always)] + pub fn width(&self) -> u8 { + self.width + } + + #[inline(always)] + pub fn rd(&self) -> IntReg<D> { + self.rd + } + + #[inline(always)] + pub fn rs1(&self) -> FloatReg<D> { + self.rs1 + } + + #[inline(always)] + pub fn rs2(&self) -> FloatReg<D> { + self.rs2 + } +} + +#[derive(Copy, Clone, Debug)] +pub struct RTypeFloatRoundInst<D: RiscVDisassembler> { + width: u8, + rd: FloatReg<D>, + rs1: FloatReg<D>, + rs2: FloatReg<D>, + rm: RoundMode, +} + +impl<D: RiscVDisassembler> RTypeFloatRoundInst<D> { + #[inline(always)] + fn from_instr32(inst: Instr32) -> DisResult<Self> { + let width = match inst.fsize() { + 0b00 => 4, + 0b01 => 8, + 0b11 => 16, + _ => return Err(Error::InvalidSubop), + }; + + if width > <D::RegFile as RegFile>::Float::width() { + return Err(Error::InvalidSubop); + } + + let rd = FloatReg::new(inst.rd()); + let rs1 = FloatReg::new(inst.rs1()); + let rs2 = FloatReg::new(inst.rs2()); + let rm = RoundMode::from_bits(inst.rm())?; + + if !rd.valid() || !rs1.valid() || !rs2.valid() { + return Err(Error::BadRegister); + } + + Ok(Self { + width: width as u8, + rd, + rs1, + rs2, + rm, + }) + } + + #[inline(always)] + pub fn width(&self) -> u8 { + self.width + } + + #[inline(always)] + pub fn rd(&self) -> FloatReg<D> { + self.rd + } + + #[inline(always)] + pub fn rs1(&self) -> FloatReg<D> { + self.rs1 + } + + #[inline(always)] + pub fn rs2(&self) -> FloatReg<D> { + self.rs2 + } + + #[inline(always)] + pub fn rm(&self) -> RoundMode { + self.rm + } +} + +#[derive(Copy, Clone, Debug)] +pub struct BTypeInst<D: RiscVDisassembler> { + rs1: IntReg<D>, + rs2: IntReg<D>, + imm: i16, + _dis: PhantomData<D>, +} + +impl<D: RiscVDisassembler> BTypeInst<D> { + #[inline(always)] + fn new(rs1: IntReg<D>, rs2: IntReg<D>, imm: i32) -> DisResult<Self> { + if !rs1.valid() || !rs2.valid() { + return Err(Error::BadRegister); + } + + Ok(Self { + rs1, + rs2, + imm: imm as i16, + _dis: PhantomData, + }) + } + + #[inline(always)] + fn from_instr32(inst: Instr32) -> DisResult<Self> { + let rs1 = IntReg::new(inst.rs1()); + let rs2 = IntReg::new(inst.rs2()); + + if !rs1.valid() || !rs2.valid() { + return Err(Error::BadRegister); + } + + Ok(Self { + rs1, + rs2, + imm: inst.b_imm() as i16, + _dis: PhantomData, + }) + } + + #[inline(always)] + pub fn rs1(&self) -> IntReg<D> { + self.rs1 + } + + #[inline(always)] + pub fn rs2(&self) -> IntReg<D> { + self.rs2 + } + + #[inline(always)] + pub fn imm(&self) -> i32 { + self.imm as i32 + } +} + +#[derive(Copy, Clone, Debug)] +pub struct UTypeInst<D: RiscVDisassembler> { + rd: IntReg<D>, + imm: i32, + _dis: PhantomData<D>, +} + +impl<D: RiscVDisassembler> UTypeInst<D> { + #[inline(always)] + fn new(rd: IntReg<D>, imm: i32) -> DisResult<Self> { + if !rd.valid() { + return Err(Error::BadRegister); + } + + Ok(Self { + rd, + imm, + _dis: PhantomData, + }) + } + + #[inline(always)] + fn from_instr32(inst: Instr32) -> DisResult<Self> { + let rd = IntReg::new(inst.rd()); + + if !rd.valid() { + return Err(Error::BadRegister); + } + + Ok(Self { + rd, + imm: inst.u_imm(), + _dis: PhantomData, + }) + } + + #[inline(always)] + pub fn rd(&self) -> IntReg<D> { + self.rd + } + + #[inline(always)] + pub fn imm(&self) -> i32 { + self.imm + } +} + +#[derive(Copy, Clone, Debug)] +pub struct JTypeInst<D: RiscVDisassembler> { + rd: IntReg<D>, + imm: i32, + _dis: PhantomData<D>, +} + +impl<D: RiscVDisassembler> JTypeInst<D> { + #[inline(always)] + fn new(rd: IntReg<D>, imm: i32) -> DisResult<Self> { + if !rd.valid() { + return Err(Error::BadRegister); + } + + Ok(Self { + rd, + imm, + _dis: PhantomData, + }) + } + + #[inline(always)] + fn from_instr32(inst: Instr32) -> DisResult<Self> { + let rd = IntReg::new(inst.rd()); + + if !rd.valid() { + return Err(Error::BadRegister); + } + + Ok(Self { + rd, + imm: inst.j_imm(), + _dis: PhantomData, + }) + } + + #[inline(always)] + pub fn rd(&self) -> IntReg<D> { + self.rd + } + + #[inline(always)] + pub fn imm(&self) -> i32 { + self.imm + } +} + +#[derive(Copy, Clone, Debug)] +pub struct AtomicInst<D: RiscVDisassembler> { + inst: Instr32, + _dis: PhantomData<D>, +} + +impl<D: RiscVDisassembler> AtomicInst<D> { + #[inline(always)] + fn new(inst: Instr32) -> DisResult<Self> { + let ret = Self { + inst, + _dis: PhantomData, + }; + + let width = ret.width(); + + if width < 4 || width > <D::RegFile as RegFile>::Int::width() { + return Err(Error::InvalidSubop); + } else if !ret.rd().valid() || !ret.rs1().valid() || !ret.rs2().valid() { + return Err(Error::BadRegister); + } + + Ok(ret) + } + + #[inline(always)] + pub fn rd(&self) -> IntReg<D> { + IntReg::new(self.inst.rd()) + } + + #[inline(always)] + pub fn rs1(&self) -> IntReg<D> { + IntReg::new(self.inst.rs1()) + } + + #[inline(always)] + pub fn rs2(&self) -> IntReg<D> { + IntReg::new(self.inst.rs2()) + } + + #[inline(always)] + pub fn width(&self) -> usize { + 1usize.wrapping_shl(self.inst.funct3()) + } + + #[inline(always)] + pub fn aq(&self) -> bool { + self.inst.extract_bits(26, 1) != 0 + } + + #[inline(always)] + pub fn rl(&self) -> bool { + self.inst.extract_bits(25, 1) != 0 + } +} + +#[derive(Copy, Clone, Debug)] +pub struct FpMemInst<D: RiscVDisassembler> { + width: u8, + fr: FloatReg<D>, + rs1: IntReg<D>, + imm: i16, + _dis: PhantomData<D>, +} + +impl<D: RiscVDisassembler> FpMemInst<D> { + #[inline(always)] + fn new(width: usize, fr: FloatReg<D>, rs1: IntReg<D>, imm: i32) -> DisResult<Self> { + if width > <D::RegFile as RegFile>::Float::width() { + return Err(Error::InvalidSubop); + } else if !fr.valid() || !rs1.valid() { + return Err(Error::BadRegister); + } + + Ok(Self { + width: width as u8, + fr, + rs1, + imm: imm as i16, + _dis: PhantomData, + }) + } + + #[inline(always)] + pub fn width(&self) -> usize { + self.width as usize + } + + #[inline(always)] + pub fn fr(&self) -> FloatReg<D> { + self.fr + } + + #[inline(always)] + pub fn rs1(&self) -> IntReg<D> { + self.rs1 + } + + #[inline(always)] + pub fn imm(&self) -> i32 { + self.imm as i32 + } +} + +#[derive(Copy, Clone, Debug)] +pub struct FpMAddInst<D: RiscVDisassembler> { + width: u8, + rd: FloatReg<D>, + rs1: FloatReg<D>, + rs2: FloatReg<D>, + rs3: FloatReg<D>, + rm: RoundMode, + _dis: PhantomData<D>, +} + +impl<D: RiscVDisassembler> FpMAddInst<D> { + #[inline(always)] + fn from_instr32(inst: Instr32) -> DisResult<Self> { + let width = match inst.fsize() { + 0b00 => 4, + 0b01 => 8, + 0b11 => 16, + _ => return Err(Error::InvalidSubop), + }; + + if width > <D::RegFile as RegFile>::Float::width() { + return Err(Error::InvalidSubop); + } + + let rd = FloatReg::new(inst.rd()); + let rs1 = FloatReg::new(inst.rs1()); + let rs2 = FloatReg::new(inst.rs2()); + let rs3 = FloatReg::new(inst.rs3()); + let rm = RoundMode::from_bits(inst.rm())?; + + if !rd.valid() || !rs1.valid() || !rs2.valid() || !rs3.valid() { + return Err(Error::BadRegister); + } + + Ok(Self { + width: width as u8, + rd, + rs1, + rs2, + rs3, + rm, + _dis: PhantomData, + }) + } + + #[inline(always)] + pub fn width(&self) -> u8 { + self.width + } + + #[inline(always)] + pub fn rd(&self) -> FloatReg<D> { + self.rd + } + + #[inline(always)] + pub fn rs1(&self) -> FloatReg<D> { + self.rs1 + } + + #[inline(always)] + pub fn rs2(&self) -> FloatReg<D> { + self.rs2 + } + + #[inline(always)] + pub fn rs3(&self) -> FloatReg<D> { + self.rs3 + } + + #[inline(always)] + pub fn rm(&self) -> RoundMode { + self.rm + } +} + +#[derive(Copy, Clone, Debug)] +pub struct FpCvtInst<D: RiscVDisassembler> { + rd_width: u8, + rs1_width: u8, + rd: FloatReg<D>, + rs1: FloatReg<D>, + rm: RoundMode, + _dis: PhantomData<D>, +} + +impl<D: RiscVDisassembler> FpCvtInst<D> { + #[inline(always)] + fn new( + rd: FloatReg<D>, + rd_width: u8, + rs1: FloatReg<D>, + rs1_width: u8, + rm: RoundMode, + ) -> DisResult<Self> { + Ok(Self { + rd_width, + rs1_width, + rd, + rs1, + rm, + _dis: PhantomData, + }) + } + + #[inline(always)] + pub fn rd_width(&self) -> u8 { + self.rd_width + } + + #[inline(always)] + pub fn rs1_width(&self) -> u8 { + self.rs1_width + } + + #[inline(always)] + pub fn rd(&self) -> FloatReg<D> { + self.rd + } + + #[inline(always)] + pub fn rs1(&self) -> FloatReg<D> { + self.rs1 + } + + #[inline(always)] + pub fn rm(&self) -> RoundMode { + self.rm + } +} + +#[derive(Copy, Clone, Debug)] +pub struct FpCvtToIntInst<D: RiscVDisassembler> { + rd_width: u8, + rs1_width: u8, + zx: bool, + rd: IntReg<D>, + rs1: FloatReg<D>, + rm: RoundMode, + _dis: PhantomData<D>, +} + +impl<D: RiscVDisassembler> FpCvtToIntInst<D> { + #[inline(always)] + fn new( + rd: IntReg<D>, + rd_width: u8, + zx: bool, + rs1: FloatReg<D>, + rs1_width: u8, + rm: RoundMode, + ) -> DisResult<Self> { + Ok(Self { + rd_width, + rs1_width, + zx, + rd, + rs1, + rm, + _dis: PhantomData, + }) + } + + #[inline(always)] + pub fn rd_width(&self) -> u8 { + self.rd_width + } + + #[inline(always)] + pub fn rs1_width(&self) -> u8 { + self.rs1_width + } + + #[inline(always)] + pub fn zx(&self) -> bool { + self.zx + } + + #[inline(always)] + pub fn rd(&self) -> IntReg<D> { + self.rd + } + + #[inline(always)] + pub fn rs1(&self) -> FloatReg<D> { + self.rs1 + } + + #[inline(always)] + pub fn rm(&self) -> RoundMode { + self.rm + } +} + +#[derive(Copy, Clone, Debug)] +pub struct FpCvtFromIntInst<D: RiscVDisassembler> { + rd_width: u8, + rs1_width: u8, + zx: bool, + rd: FloatReg<D>, + rs1: IntReg<D>, + rm: RoundMode, + _dis: PhantomData<D>, +} + +impl<D: RiscVDisassembler> FpCvtFromIntInst<D> { + #[inline(always)] + fn new( + rd: FloatReg<D>, + rd_width: u8, + rs1: IntReg<D>, + rs1_width: u8, + zx: bool, + rm: RoundMode, + ) -> DisResult<Self> { + Ok(Self { + rd_width, + rs1_width, + zx, + rd, + rs1, + rm, + _dis: PhantomData, + }) + } + + #[inline(always)] + pub fn rd_width(&self) -> u8 { + self.rd_width + } + + #[inline(always)] + pub fn rs1_width(&self) -> u8 { + self.rs1_width + } + + #[inline(always)] + pub fn zx(&self) -> bool { + self.zx + } + + #[inline(always)] + pub fn rd(&self) -> FloatReg<D> { + self.rd + } + + #[inline(always)] + pub fn rs1(&self) -> IntReg<D> { + self.rs1 + } + + #[inline(always)] + pub fn rm(&self) -> RoundMode { + self.rm + } +} + +#[derive(Copy, Clone, Debug)] +pub struct FpMvToIntInst<D: RiscVDisassembler> { + width: u8, + rd: IntReg<D>, + rs1: FloatReg<D>, + _dis: PhantomData<D>, +} + +impl<D: RiscVDisassembler> FpMvToIntInst<D> { + #[inline(always)] + fn from_instr32(inst: Instr32) -> DisResult<Self> { + let width = match inst.fsize() { + 0b00 => 4, + 0b01 => 8, + 0b11 => 16, + _ => return Err(Error::InvalidSubop), + }; + + if width > <D::RegFile as RegFile>::Float::width() + || width > <D::RegFile as RegFile>::Int::width() + { + return Err(Error::InvalidSubop); + } + + let rd = IntReg::new(inst.rd()); + let rs1 = FloatReg::new(inst.rs1()); + + if !rd.valid() || !rs1.valid() { + return Err(Error::BadRegister); + } + + Ok(Self { + width: width as u8, + rd, + rs1, + _dis: PhantomData, + }) + } + + #[inline(always)] + pub fn width(&self) -> u8 { + self.width + } + + #[inline(always)] + pub fn rd(&self) -> IntReg<D> { + self.rd + } + + #[inline(always)] + pub fn rs1(&self) -> FloatReg<D> { + self.rs1 + } +} + +#[derive(Copy, Clone, Debug)] +pub struct FpMvFromIntInst<D: RiscVDisassembler> { + width: u8, + rd: FloatReg<D>, + rs1: IntReg<D>, + _dis: PhantomData<D>, +} + +impl<D: RiscVDisassembler> FpMvFromIntInst<D> { + #[inline(always)] + fn from_instr32(inst: Instr32) -> DisResult<Self> { + let width = match inst.fsize() { + 0b00 => 4, + 0b01 => 8, + 0b11 => 16, + _ => return Err(Error::InvalidSubop), + }; + + if width > <D::RegFile as RegFile>::Float::width() + || width > <D::RegFile as RegFile>::Int::width() + { + return Err(Error::InvalidSubop); + } + + let rd = FloatReg::new(inst.rd()); + let rs1 = IntReg::new(inst.rs1()); + + if !rd.valid() || !rs1.valid() { + return Err(Error::BadRegister); + } + + Ok(Self { + width: width as u8, + rd, + rs1, + _dis: PhantomData, + }) + } + + #[inline(always)] + pub fn width(&self) -> u8 { + self.width + } + + #[inline(always)] + pub fn rd(&self) -> FloatReg<D> { + self.rd + } + + #[inline(always)] + pub fn rs1(&self) -> IntReg<D> { + self.rs1 + } +} + +#[derive(Copy, Clone, Debug)] +pub struct FpClassInst<D: RiscVDisassembler> { + width: u8, + rd: IntReg<D>, + rs1: FloatReg<D>, + _dis: PhantomData<D>, +} + +impl<D: RiscVDisassembler> FpClassInst<D> { + #[inline(always)] + fn from_instr32(inst: Instr32) -> DisResult<Self> { + let width = match inst.fsize() { + 0b00 => 4, + 0b01 => 8, + 0b11 => 16, + _ => return Err(Error::InvalidSubop), + }; + + if width > <D::RegFile as RegFile>::Float::width() { + return Err(Error::InvalidSubop); + } + + let rd = IntReg::new(inst.rd()); + let rs1 = FloatReg::new(inst.rs1()); + + if !rd.valid() || !rs1.valid() { + return Err(Error::BadRegister); + } + + Ok(Self { + width: width as u8, + rd, + rs1, + _dis: PhantomData, + }) + } + + #[inline(always)] + pub fn width(&self) -> u8 { + self.width + } + + #[inline(always)] + pub fn rd(&self) -> IntReg<D> { + self.rd + } + + #[inline(always)] + pub fn rs1(&self) -> FloatReg<D> { + self.rs1 + } +} + +#[derive(Copy, Clone, Debug)] +pub struct Instr16(u16); +impl Instr16 { + #[inline(always)] + fn extract_bits(self, start_bit: u32, width: u32) -> u16 { + self.0.wrapping_shr(start_bit) & 1u16.wrapping_shl(width).wrapping_sub(1) + } + + #[inline(always)] + fn sp_load_imm(self, size: usize) -> i32 { + let size = size as u32 >> 3; + let start = 4 + size; + + let res = (self.extract_bits(start, 3 - size) << (2 + size)) + | (self.extract_bits(2, 2 + size) << 6); + + (res | (self.extract_bits(12, 1) << 5)) as i32 + } + + #[inline(always)] + fn mem_imm(self, size: usize) -> i32 { + let upper = self.extract_bits(10, 3) << 3; + + let res = match size { + 4 => upper | self.extract_bits(6, 1) << 2 | self.extract_bits(5, 1) << 6, + 8 => upper | self.extract_bits(5, 2) << 6, + _ => unimplemented!(), + }; + + res as i32 + } + + #[inline(always)] + fn sp_store_imm(self, size: usize) -> i32 { + let size = size as u32 >> 3; + let start = 9 + size; + + ((self.extract_bits(start, 4 - size) << (2 + size)) | (self.extract_bits(7, 2 + size) << 6)) + as i32 + } + + #[inline(always)] + fn cb_imm(self) -> i32 { + let mut imm = self.extract_bits(2, 1) << 5; + imm |= self.extract_bits(3, 2) << 1; + imm |= self.extract_bits(5, 2) << 6; + imm |= self.extract_bits(10, 2) << 3; + + if self.extract_bits(12, 1) != 0 { + imm |= !0xff; + } + + imm as i16 as i32 + } + + #[inline(always)] + fn cj_imm(self) -> i32 { + let mut imm = self.extract_bits(2, 1) << 5; + imm |= self.extract_bits(3, 3) << 1; + imm |= self.extract_bits(6, 1) << 7; + imm |= self.extract_bits(7, 1) << 6; + imm |= self.extract_bits(8, 1) << 10; + imm |= self.extract_bits(9, 2) << 8; + imm |= self.extract_bits(11, 1) << 4; + + if self.extract_bits(12, 1) != 0 { + imm |= !0x7ff; + } + + imm as i16 as i32 + } +} + +pub enum Instr<D: RiscVDisassembler> { + Rv16(Op<D>), + Rv32(Op<D>), +} + +impl<D: RiscVDisassembler> Instr<D> { + pub fn mnem(&self) -> Mnem<D> { + Mnem(&self) + } + + pub fn operands(&self) -> Vec<Operand<D>> { + let mut ops = Vec::new(); + + match *self { + //Instr::Rv16(..) => {} + Instr::Rv32(ref op) | Instr::Rv16(ref op) => match *op { + Op::Load(ref l) => { + ops.push(Operand::R(l.rd())); + ops.push(Operand::M(l.imm(), l.rs1())); + } + Op::Store(ref s) => { + ops.push(Operand::R(s.rs2())); + ops.push(Operand::M(s.imm(), s.rs1())); + } + Op::Fence(ref i) + | Op::FenceI(ref i) + | Op::AddI(ref i) + | Op::SltI(ref i) + | Op::SltIU(ref i) + | Op::XorI(ref i) + | Op::OrI(ref i) + | Op::AndI(ref i) + | Op::SllI(ref i) + | Op::SrlI(ref i) + | Op::SraI(ref i) + | Op::AddIW(ref i) + | Op::SllIW(ref i) + | Op::SrlIW(ref i) + | Op::SraIW(ref i) => { + ops.push(Operand::R(i.rd())); + ops.push(Operand::R(i.rs1())); + ops.push(Operand::I(i.imm())); + } + Op::Auipc(ref u) | Op::Lui(ref u) => { + ops.push(Operand::R(u.rd())); + ops.push(Operand::I(u.imm())); + } + Op::Add(ref r) + | Op::Sll(ref r) + | Op::Slt(ref r) + | Op::SltU(ref r) + | Op::Xor(ref r) + | Op::Srl(ref r) + | Op::Or(ref r) + | Op::And(ref r) + | Op::Sub(ref r) + | Op::Sra(ref r) + | Op::AddW(ref r) + | Op::SllW(ref r) + | Op::SrlW(ref r) + | Op::SubW(ref r) + | Op::SraW(ref r) + | Op::Mul(ref r) + | Op::MulH(ref r) + | Op::MulHSU(ref r) + | Op::MulHU(ref r) + | Op::Div(ref r) + | Op::DivU(ref r) + | Op::Rem(ref r) + | Op::RemU(ref r) + | Op::MulW(ref r) + | Op::DivW(ref r) + | Op::DivUW(ref r) + | Op::RemW(ref r) + | Op::RemUW(ref r) => { + ops.push(Operand::R(r.rd())); + ops.push(Operand::R(r.rs1())); + ops.push(Operand::R(r.rs2())); + } + Op::Beq(ref b) + | Op::Bne(ref b) + | Op::Blt(ref b) + | Op::Bge(ref b) + | Op::BltU(ref b) + | Op::BgeU(ref b) => { + ops.push(Operand::R(b.rs1())); + ops.push(Operand::R(b.rs2())); + ops.push(Operand::I(b.imm())); + } + Op::Jalr(ref i) => { + ops.push(Operand::R(i.rd())); + ops.push(Operand::R(i.rs1())); + ops.push(Operand::I(i.imm())); + } + Op::Jal(ref j) => { + ops.push(Operand::R(j.rd())); + ops.push(Operand::I(j.imm())); + } + Op::SfenceVma(ref r) => { + ops.push(Operand::R(r.rs1())); + ops.push(Operand::R(r.rs2())); + } + Op::Csrrw(ref i) | Op::Csrrs(ref i) | Op::Csrrc(ref i) => { + ops.push(Operand::R(i.rd())); + ops.push(Operand::I(i.imm())); + ops.push(Operand::R(i.rs1())); + } + Op::CsrrwI(ref i) | Op::CsrrsI(ref i) | Op::CsrrcI(ref i) => { + ops.push(Operand::R(i.rd())); + ops.push(Operand::I(i.csr() as i32)); + ops.push(Operand::I(i.imm() as i32)); + } + Op::Ecall | Op::Ebreak | Op::Uret | Op::Sret | Op::Mret | Op::Wfi => {} + Op::Lr(ref a) + | Op::Sc(ref a) + | Op::AmoSwap(ref a) + | Op::AmoAdd(ref a) + | Op::AmoXor(ref a) + | Op::AmoAnd(ref a) + | Op::AmoOr(ref a) + | Op::AmoMin(ref a) + | Op::AmoMax(ref a) + | Op::AmoMinU(ref a) + | Op::AmoMaxU(ref a) => { + ops.push(Operand::R(a.rd())); + + if let Op::Lr(..) = *op { + } else { + ops.push(Operand::R(a.rs2())); + } + + ops.push(Operand::M(0, a.rs1())); + } + Op::LoadFp(ref m) | Op::StoreFp(ref m) => { + ops.push(Operand::F(m.fr())); + ops.push(Operand::M(m.imm(), m.rs1())); + } + Op::Fmadd(ref f) | Op::Fmsub(ref f) | Op::Fnmadd(ref f) | Op::Fnmsub(ref f) => { + ops.push(Operand::F(f.rd())); + ops.push(Operand::F(f.rs1())); + ops.push(Operand::F(f.rs2())); + ops.push(Operand::F(f.rs3())); + if f.rm() != RoundMode::Dynamic { + ops.push(Operand::RM(f.rm())); + } + } + Op::Fadd(ref f) | Op::Fsub(ref f) | Op::Fmul(ref f) | Op::Fdiv(ref f) => { + ops.push(Operand::F(f.rd())); + ops.push(Operand::F(f.rs1())); + ops.push(Operand::F(f.rs2())); + if f.rm() != RoundMode::Dynamic { + ops.push(Operand::RM(f.rm())); + } + } + Op::Fsqrt(ref f) => { + ops.push(Operand::F(f.rd())); + ops.push(Operand::F(f.rs1())); + if f.rm() != RoundMode::Dynamic { + ops.push(Operand::RM(f.rm())); + } + } + Op::Fsgnj(ref f) + | Op::Fsgnjn(ref f) + | Op::Fsgnjx(ref f) + | Op::Fmin(ref f) + | Op::Fmax(ref f) => { + ops.push(Operand::F(f.rd())); + ops.push(Operand::F(f.rs1())); + ops.push(Operand::F(f.rs2())); + } + Op::Fle(ref f) | Op::Flt(ref f) | Op::Feq(ref f) => { + ops.push(Operand::R(f.rd())); + ops.push(Operand::F(f.rs1())); + ops.push(Operand::F(f.rs2())); + } + Op::Fcvt(ref f) => { + ops.push(Operand::F(f.rd())); + ops.push(Operand::F(f.rs1())); + if f.rm() != RoundMode::Dynamic { + ops.push(Operand::RM(f.rm())); + } + } + Op::FcvtToInt(ref f) => { + ops.push(Operand::R(f.rd())); + ops.push(Operand::F(f.rs1())); + if f.rm() != RoundMode::Dynamic { + ops.push(Operand::RM(f.rm())); + } + } + Op::FcvtFromInt(ref f) => { + ops.push(Operand::F(f.rd())); + ops.push(Operand::R(f.rs1())); + if f.rm() != RoundMode::Dynamic { + ops.push(Operand::RM(f.rm())); + } + } + Op::FmvToInt(ref f) => { + ops.push(Operand::R(f.rd())); + ops.push(Operand::F(f.rs1())); + } + Op::FmvFromInt(ref f) => { + ops.push(Operand::F(f.rd())); + ops.push(Operand::R(f.rs1())); + } + Op::Fclass(ref f) => { + ops.push(Operand::R(f.rd())); + ops.push(Operand::F(f.rs1())); + } + }, + } + + ops + } +} + +pub struct Mnem<'a, D: RiscVDisassembler + 'a>(&'a Instr<D>); +impl<'a, D: RiscVDisassembler + 'a> Mnem<'a, D> { + fn mnem(&self) -> &str { + match self.0 { + &Instr::Rv32(ref op) | &Instr::Rv16(ref op) => match *op { + Op::Load(..) => "l", + Op::Fence(..) => "fence", + Op::FenceI(..) => "fence.i", + + Op::AddI(..) => "addi", + Op::SltI(..) => "slti", + Op::SltIU(..) => "sltiu", + Op::XorI(..) => "xori", + Op::OrI(..) => "ori", + Op::AndI(..) => "andi", + Op::SllI(..) => "slli", + Op::SrlI(..) => "srli", + Op::SraI(..) => "srai", + + Op::Auipc(..) => "auipc", + + Op::AddIW(..) => "addiw", + Op::SllIW(..) => "slliw", + Op::SrlIW(..) => "srliw", + Op::SraIW(..) => "sraiw", + + Op::Store(..) => "s", + + Op::Lr(..) => "lr", + Op::Sc(..) => "sc", + Op::AmoSwap(..) => "amoswap", + Op::AmoAdd(..) => "amoadd", + Op::AmoXor(..) => "amoxor", + Op::AmoAnd(..) => "amoand", + Op::AmoOr(..) => "amoor", + Op::AmoMin(..) => "amoamin", + Op::AmoMax(..) => "amoamax", + Op::AmoMinU(..) => "amoaminu", + Op::AmoMaxU(..) => "amoamaxu", + + Op::Add(..) => "add", + Op::Sll(..) => "sll", + Op::Slt(..) => "slt", + Op::SltU(..) => "sltu", + Op::Xor(..) => "xor", + Op::Srl(..) => "srl", + Op::Or(..) => "or", + Op::And(..) => "and", + Op::Sub(..) => "sub", + Op::Sra(..) => "sra", + + Op::Mul(..) => "mul", + Op::MulH(..) => "mulh", + Op::MulHSU(..) => "mulhsu", + Op::MulHU(..) => "mulhu", + Op::Div(..) => "div", + Op::DivU(..) => "divu", + Op::Rem(..) => "rem", + Op::RemU(..) => "remu", + + Op::Lui(..) => "lui", + + Op::AddW(..) => "addw", + Op::SllW(..) => "sllw", + Op::SrlW(..) => "srlw", + Op::SubW(..) => "subw", + Op::SraW(..) => "sraw", + + Op::MulW(..) => "mulw", + Op::DivW(..) => "divw", + Op::DivUW(..) => "divuw", + Op::RemW(..) => "remw", + Op::RemUW(..) => "remuw", + + Op::Beq(..) => "beq", + Op::Bne(..) => "bne", + Op::Blt(..) => "blt", + Op::Bge(..) => "bge", + Op::BltU(..) => "bltu", + Op::BgeU(..) => "bgeu", + + Op::Jalr(..) => "jalr", + Op::Jal(..) => "jal", + + Op::Ecall => "ecall", + Op::Ebreak => "ebreak", + + Op::Uret => "uret", + Op::Sret => "sret", + Op::Mret => "mret", + + Op::Wfi => "wfi", + + Op::SfenceVma(..) => "sfence.vma", + + Op::Csrrw(..) => "csrrw", + Op::Csrrs(..) => "csrrs", + Op::Csrrc(..) => "csrrc", + + Op::CsrrwI(..) => "csrrwi", + Op::CsrrsI(..) => "csrrsi", + Op::CsrrcI(..) => "csrrci", + + Op::LoadFp(..) => "fl", + Op::StoreFp(..) => "fs", + + Op::Fmadd(..) => "fmadd", + Op::Fmsub(..) => "fmsub", + Op::Fnmadd(..) => "fnmadd", + Op::Fnmsub(..) => "fnmsub", + Op::Fadd(..) => "fadd", + Op::Fsub(..) => "fsub", + Op::Fmul(..) => "fmul", + Op::Fdiv(..) => "fdiv", + Op::Fsqrt(..) => "fsqrt", + Op::Fsgnj(..) => "fsgnj", + Op::Fsgnjn(..) => "fsgnjn", + Op::Fsgnjx(..) => "fsgnjx", + Op::Fmin(..) => "fmin", + Op::Fmax(..) => "fmax", + Op::Fle(..) => "fle", + Op::Flt(..) => "flt", + Op::Feq(..) => "feq", + Op::Fcvt(..) | Op::FcvtToInt(..) | Op::FcvtFromInt(..) => "fcvt", + Op::FmvToInt(..) | Op::FmvFromInt(..) => "fmv", + Op::Fclass(..) => "fclass", + }, + } + } + + fn suffix(&self) -> Option<Cow<str>> { + match self.0 { + // &Instr::Rv16(_) => None, + &Instr::Rv32(ref op) | &Instr::Rv16(ref op) => match *op { + Op::Load(ref l) => { + let zx = if l.zx() { "u" } else { "" }; + let width = match l.width() { + 1 => "b", + 2 => "h", + 4 => "w", + 8 => "d", + _ => unreachable!(), + }; + + Some(format!("{}{}", width, zx).into()) + } + Op::Store(ref s) => Some( + match s.width() { + 1 => "b", + 2 => "h", + 4 => "w", + 8 => "d", + _ => unreachable!(), + } + .into(), + ), + Op::Lr(ref a) + | Op::Sc(ref a) + | Op::AmoSwap(ref a) + | Op::AmoAdd(ref a) + | Op::AmoXor(ref a) + | Op::AmoAnd(ref a) + | Op::AmoOr(ref a) + | Op::AmoMin(ref a) + | Op::AmoMax(ref a) + | Op::AmoMinU(ref a) + | Op::AmoMaxU(ref a) => { + let width = match a.width() { + 4 => ".w", + 8 => ".d", + _ => unreachable!(), + }; + let aq = if a.aq() { ".aq" } else { "" }; + let rl = if a.rl() { ".rl" } else { "" }; + + Some(format!("{}{}{}", width, aq, rl).into()) + } + Op::LoadFp(ref m) | Op::StoreFp(ref m) => { + let suf = match m.width() { + 4 => "w", + 8 => "d", + 16 => "q", + _ => unreachable!(), + }; + + Some(suf.into()) + } + Op::Fmadd(ref f) | Op::Fmsub(ref f) | Op::Fnmadd(ref f) | Op::Fnmsub(ref f) => { + let suf = match f.width() { + 4 => ".s", + 8 => ".d", + 16 => ".q", + _ => unreachable!(), + }; + + Some(suf.into()) + } + Op::Fadd(ref f) | Op::Fsub(ref f) | Op::Fmul(ref f) | Op::Fdiv(ref f) => { + let suf = match f.width() { + 4 => ".s", + 8 => ".d", + 16 => ".q", + _ => unreachable!(), + }; + + Some(suf.into()) + } + Op::Fsgnj(ref f) + | Op::Fsgnjn(ref f) + | Op::Fsgnjx(ref f) + | Op::Fmin(ref f) + | Op::Fmax(ref f) => { + let suf = match f.width() { + 4 => ".s", + 8 => ".d", + 16 => ".q", + _ => unreachable!(), + }; + + Some(suf.into()) + } + Op::Fle(ref f) | Op::Flt(ref f) | Op::Feq(ref f) => { + let suf = match f.width() { + 4 => ".s", + 8 => ".d", + 16 => ".q", + _ => unreachable!(), + }; + + Some(suf.into()) + } + Op::Fcvt(ref f) => { + let rd_suf = match f.rd_width() { + 4 => ".s", + 8 => ".d", + 16 => ".q", + _ => unreachable!(), + }; + let rs1_suf = match f.rs1_width() { + 4 => ".s", + 8 => ".d", + 16 => ".q", + _ => unreachable!(), + }; + + Some(format!("{}{}", rd_suf, rs1_suf).into()) + } + Op::FcvtToInt(ref f) => { + let rd_suf = match f.rd_width() { + 4 => ".w", + 8 => ".l", + _ => unreachable!(), + }; + let zx_suf = if f.zx() { "u" } else { "" }; + let rs1_suf = match f.rs1_width() { + 4 => ".s", + 8 => ".d", + 16 => ".q", + _ => unreachable!(), + }; + + Some(format!("{}{}{}", rd_suf, zx_suf, rs1_suf).into()) + } + Op::FcvtFromInt(ref f) => { + let rd_suf = match f.rs1_width() { + 4 => ".s", + 8 => ".d", + 16 => ".q", + _ => unreachable!(), + }; + let rs1_suf = match f.rd_width() { + 4 => ".w", + 8 => ".l", + _ => unreachable!(), + }; + let zx_suf = if f.zx() { "u" } else { "" }; + + Some(format!("{}{}{}", rd_suf, rs1_suf, zx_suf).into()) + } + Op::FmvToInt(ref f) => { + let suf = match f.width() { + 4 => ".x.w", + 8 => ".x.d", + _ => unreachable!(), + }; + + Some(suf.into()) + } + Op::FmvFromInt(ref f) => { + let suf = match f.width() { + 4 => ".w.x", + 8 => ".d.x", + _ => unreachable!(), + }; + + Some(suf.into()) + } + Op::Fclass(ref f) => { + let suf = match f.width() { + 4 => ".s", + 8 => ".d", + 16 => ".q", + _ => unreachable!(), + }; + + Some(suf.into()) + } + _ => None, + }, + } + } +} + +impl<'a, D: RiscVDisassembler> fmt::Display for Mnem<'a, D> { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match (self.mnem(), self.suffix()) { + (m, None) => f.pad(m), + (m, Some(s)) => { + let s = format!("{}{}", m, s); + f.pad(&s) + } + } + } +} + +pub trait StandardExtension { + fn supported() -> bool; +} + +pub struct ExtensionNotImplemented; +impl StandardExtension for ExtensionNotImplemented { + #[inline(always)] + fn supported() -> bool { + false + } +} + +pub struct ExtensionSupported; +impl StandardExtension for ExtensionSupported { + #[inline(always)] + fn supported() -> bool { + true + } +} + +pub trait RiscVDisassembler: Sized + Copy + Clone { + type RegFile: RegFile; + type MulDivExtension: StandardExtension; + type AtomicExtension: StandardExtension; + type CompressedExtension: StandardExtension; + + fn decode(addr: u64, bytes: &[u8]) -> DisResult<Instr<Self>> { + use Error::*; + + let required_alignment: u64 = if Self::CompressedExtension::supported() { + 2 + } else { + 4 + }; + + if addr & required_alignment.wrapping_sub(1) != 0 + || bytes.len() < required_alignment as usize + { + return Err(Unaligned); + } + + let parcel = LittleEndian::read_u16(bytes); + + let inst_len = match parcel { + p if (p & 0b11) != 0b11 => 2, + p if (p & 0b11111) != 0b11111 => 4, + _ => { + return Err(UnhandledLength); + } + }; + + match inst_len { + 2 if bytes.len() >= 2 && Self::CompressedExtension::supported() => { + let inst = Instr16(parcel); + + // top 3 bits and bottom 2 bits make up + // the bulk of the opcode map + // see: Table 12.3 RVC Opcode Map RISCV spec 2.2 + let opcode = (parcel >> 11 & !3) | (parcel & 3); + let int_width = <Self::RegFile as RegFile>::Int::width(); + let float_width = <Self::RegFile as RegFile>::Float::width(); + + let decoded = match opcode { + 0b000_00 if parcel != 0 => { + // ADDI4SPN + let rd = 8 + inst.extract_bits(2, 3) as u32; + let mut imm = inst.extract_bits(5, 1) << 3; + imm |= inst.extract_bits(6, 1) << 2; + imm |= inst.extract_bits(7, 4) << 6; + imm |= inst.extract_bits(11, 2) << 4; + + if imm == 0 { + return Err(InvalidSubop); + } + + Op::AddI(ITypeInst::from_ops( + IntReg::new(rd), + IntReg::new(2), + imm as i32, + )) + } + 0b000_01 => { + // ADDI + let rd = inst.extract_bits(7, 5) as u32; + let mut imm = inst.extract_bits(2, 5) as u32; + + // sign extend the 6 bit immediate value + if inst.extract_bits(12, 1) == 1 { + imm |= !0x1f; + } + + Op::AddI(ITypeInst::from_ops( + IntReg::new(rd), + IntReg::new(rd), + imm as i32, + )) + } + // shift amounts >= 32 are prohibited for Rv32 (reserved for NSE) + 0b000_10 if int_width >= 8 || inst.extract_bits(12, 1) == 0 => { + // SLLI + // TODO merge shamt extraction + let shamt = + (inst.extract_bits(12, 1) << 5 | inst.extract_bits(2, 5)) as u32; + let rd = inst.extract_bits(7, 5) as u32; + + // TODO Rv128 shamt of 0 == 64 + + Op::SllI(ITypeInst::from_ops( + IntReg::new(rd), + IntReg::new(rd), + shamt as i32, + )) + } + + //0b001_00 if int_width == 16 => unimplemented!("LQ"), + 0b001_00 if float_width >= 8 => { + // FLD + let rd = FloatReg::new(8 + inst.extract_bits(2, 3) as u32); + let rs1 = IntReg::new(8 + inst.extract_bits(7, 3) as u32); + + let imm = inst.mem_imm(8); + Op::LoadFp(FpMemInst::new(8, rd, rs1, imm)?) + } + 0b001_01 if int_width == 4 => { + // JAL + Op::Jal(JTypeInst::new(IntReg::new(0), inst.cj_imm())?) + } + 0b001_01 => { + // ADDIW + let rd = inst.extract_bits(7, 5) as u32; + let mut imm = inst.extract_bits(2, 5) as u32; + + // TODO rd == zero valid? + // sign extend the 6 bit immediate value + if inst.extract_bits(12, 1) == 1 { + imm |= !0x1f; + } + + Op::AddIW(ITypeInst::from_ops( + IntReg::new(rd), + IntReg::new(rd), + imm as i32, + )) + } + //0b001_10 if int_width == 16 => unimplemented!("LQSP"), + 0b001_10 if float_width >= 8 => { + // FLDSP + let rd = FloatReg::new(inst.extract_bits(7, 5) as u32); + let imm = inst.sp_load_imm(8); + + Op::LoadFp(FpMemInst::new(8, rd, IntReg::new(2), imm)?) + } + + 0b010_00 => { + // LW + let rd = IntReg::new(8 + inst.extract_bits(2, 3) as u32); + let rs1 = IntReg::new(8 + inst.extract_bits(7, 3) as u32); + + let imm = inst.mem_imm(4); + Op::Load(LoadTypeInst::new(4, false, rd, rs1, imm)?) + } + 0b010_01 => { + // LI + let rd = inst.extract_bits(7, 5) as u32; + + // TODO rd == 0 behavior + if rd == 0 { + return Err(InvalidSubop); + } + + // sign extend the 6 bit immediate value + let mut imm = inst.extract_bits(2, 5) as u32; + if inst.extract_bits(12, 1) == 1 { + imm |= !0x1f; + } + + Op::AddI(ITypeInst::from_ops( + IntReg::new(rd), + IntReg::new(0), + imm as i32, + )) + } + 0b010_10 => { + // LWSP + let rd = IntReg::new(inst.extract_bits(7, 5) as u32); + let imm = inst.sp_load_imm(4); + + if rd.id() == 0 { + return Err(InvalidOpcode); + } + + Op::Load(LoadTypeInst::new(4, false, rd, IntReg::new(2), imm)?) + } + + 0b011_00 if int_width >= 8 => { + // LD + let rd = IntReg::new(8 + inst.extract_bits(2, 3) as u32); + let rs1 = IntReg::new(8 + inst.extract_bits(7, 3) as u32); + + let imm = inst.mem_imm(8); + Op::Load(LoadTypeInst::new(8, false, rd, rs1, imm)?) + } + 0b011_00 if float_width >= 4 => { + // FLW + let rd = FloatReg::new(8 + inst.extract_bits(2, 3) as u32); + let rs1 = IntReg::new(8 + inst.extract_bits(7, 3) as u32); + + let imm = inst.mem_imm(4); + Op::LoadFp(FpMemInst::new(4, rd, rs1, imm)?) + } + 0b011_01 => { + // LUI/ADDI16SP + let rd = inst.extract_bits(7, 5) as u32; + + match rd { + 0 => return Err(InvalidSubop), + 2 => { + // ADDI16SP + let mut imm = inst.extract_bits(2, 1) << 5; + imm |= inst.extract_bits(3, 2) << 7; + imm |= inst.extract_bits(5, 1) << 6; + imm |= inst.extract_bits(6, 1) << 4; + + if inst.extract_bits(12, 1) == 1 { + imm |= !0x1ff; + } + + if imm == 0 { + return Err(InvalidSubop); + } + + Op::AddI(ITypeInst::from_ops( + IntReg::new(2), + IntReg::new(2), + imm as i16 as i32, + )) + } + _ => { + // sign extend the 6 bit immediate value + let mut imm = inst.extract_bits(2, 5) as u32; + if inst.extract_bits(12, 1) == 1 { + imm |= !0x1f; + } + + let imm = (imm as i32) << 12; + Op::Lui(UTypeInst::new(IntReg::new(rd), imm)?) + } + } + } + 0b011_10 if int_width >= 8 => { + // LDSP + let rd = IntReg::new(inst.extract_bits(7, 5) as u32); + let imm = inst.sp_load_imm(8); + + if rd.id() == 0 { + return Err(InvalidOpcode); + } + + Op::Load(LoadTypeInst::new(8, false, rd, IntReg::new(2), imm)?) + } + 0b011_10 if float_width >= 4 => { + // FLWSP + let rd = FloatReg::new(inst.extract_bits(7, 5) as u32); + let imm = inst.sp_load_imm(4); + + Op::LoadFp(FpMemInst::new(4, rd, IntReg::new(2), imm)?) + } + + // 0b100_00 RESERVED + 0b100_01 => { + // MISC-ALU + let rd = 8 + inst.extract_bits(7, 3) as u32; + + // TODO merge shamt extraction + let shamt = + (inst.extract_bits(12, 1) << 5 | inst.extract_bits(2, 5)) as u32; + + let mut mask = inst.extract_bits(2, 5) as u32; + if inst.extract_bits(12, 1) == 1 { + mask |= !0x1f; + } + + // TODO is shamt 0 actually prohibited? + // TODO should not always check this... + //if shamt == 0 || (int_width == 4 && shamt >= 32) { + // return Err(InvalidSubop); + //} + + match inst.extract_bits(10, 2) { + 0 => Op::SrlI(ITypeInst::from_ops( + IntReg::new(rd), + IntReg::new(rd), + shamt as i32, + )), // SRLI + 1 => Op::SraI(ITypeInst::from_ops( + IntReg::new(rd), + IntReg::new(rd), + shamt as i32, + )), // SRAI + 2 => Op::AndI(ITypeInst::from_ops( + IntReg::new(rd), + IntReg::new(rd), + mask as i32, + )), // ANDI + 3 => { + let op = inst.extract_bits(5, 2) | (inst.extract_bits(12, 1) << 2); + let rs2 = 8 + inst.extract_bits(2, 3) as u32; + let rtype = RTypeInst::from_ops( + IntReg::new(rd), + IntReg::new(rd), + IntReg::new(rs2), + ); + + match op { + 0 => Op::Sub(rtype), // SUB + 1 => Op::Xor(rtype), // XOR + 2 => Op::Or(rtype), // OR + 3 => Op::And(rtype), // AND + 4 if int_width >= 8 => Op::SubW(rtype), // SUBW + 5 if int_width >= 8 => Op::AddW(rtype), // ADDW + _ => return Err(InvalidSubop), + } + } + _ => unreachable!(), + } + } + 0b100_10 => { + // JALR/MV/ADD + let reg1 = inst.extract_bits(7, 5) as u32; + let reg2 = inst.extract_bits(2, 5) as u32; + let bit = inst.extract_bits(12, 1) as u32; + + match (bit, reg1, reg2) { + (link, rs1, 0) if rs1 != 0 => { + // JR, JALR + Op::Jalr(ITypeInst::from_ops( + IntReg::new(link), + IntReg::new(rs1), + 0, + )) + } + (0, rd, rs2) if rd != 0 && rs2 != 0 => { + // MV + Op::AddI(ITypeInst::from_ops(IntReg::new(rd), IntReg::new(rs2), 0)) + } + (1, 0, 0) => Op::Ebreak, + (1, rd, rs2) if rs2 != 0 => { + // ADD + Op::Add(RTypeInst::from_ops( + IntReg::new(rd), + IntReg::new(rd), + IntReg::new(rs2), + )) + } + _ => return Err(InvalidSubop), + } + } + + //0b101_00 if int_width == 16 => unimplemented!("SQ"), + 0b101_00 if float_width >= 8 => { + // FSD + let rd = FloatReg::new(8 + inst.extract_bits(2, 3) as u32); + let rs1 = IntReg::new(8 + inst.extract_bits(7, 3) as u32); + + let imm = inst.mem_imm(8); + Op::StoreFp(FpMemInst::new(8, rd, rs1, imm)?) + } + 0b101_01 => { + // J + Op::Jal(JTypeInst::new(IntReg::new(0), inst.cj_imm())?) + } + //0b101_10 if int_width == 16 => unimplemented!("SQSP"), + 0b101_10 if float_width >= 8 => { + // FSDSP + let rd = FloatReg::new(inst.extract_bits(7, 5) as u32); + let imm = inst.sp_load_imm(8); + + Op::StoreFp(FpMemInst::new(8, rd, IntReg::new(2), imm)?) + } + + 0b110_00 => { + // SW + let rs2 = IntReg::new(8 + inst.extract_bits(2, 3) as u32); + let rs1 = IntReg::new(8 + inst.extract_bits(7, 3) as u32); + + let imm = inst.mem_imm(4); + Op::Store(StoreTypeInst::new(4, rs2, rs1, imm)?) + } + 0b110_01 => { + // BEQZ + let rs1 = IntReg::new(8 + inst.extract_bits(7, 3) as u32); + Op::Beq(BTypeInst::new(rs1, IntReg::new(0), inst.cb_imm())?) + } + 0b110_10 => { + // SWSP + let rs2 = IntReg::new(inst.extract_bits(2, 5) as u32); + let imm = inst.sp_store_imm(4); + + Op::Store(StoreTypeInst::new(4, rs2, IntReg::new(2), imm)?) + } + + 0b111_00 if int_width >= 8 => { + // SD + let rs2 = IntReg::new(8 + inst.extract_bits(2, 3) as u32); + let rs1 = IntReg::new(8 + inst.extract_bits(7, 3) as u32); + + let imm = inst.mem_imm(8); + Op::Store(StoreTypeInst::new(8, rs2, rs1, imm)?) + } + 0b111_00 if float_width >= 4 => { + // FSW + let rd = FloatReg::new(8 + inst.extract_bits(2, 3) as u32); + let rs1 = IntReg::new(8 + inst.extract_bits(7, 3) as u32); + + let imm = inst.mem_imm(4); + Op::StoreFp(FpMemInst::new(4, rd, rs1, imm)?) + } + 0b111_01 => { + // BNEZ + let rs1 = IntReg::new(8 + inst.extract_bits(7, 3) as u32); + Op::Bne(BTypeInst::new(rs1, IntReg::new(0), inst.cb_imm())?) + } + 0b111_10 if int_width >= 8 => { + // SDSP + let rs2 = IntReg::new(inst.extract_bits(2, 5) as u32); + let imm = inst.sp_store_imm(8); + + Op::Store(StoreTypeInst::new(8, rs2, IntReg::new(2), imm)?) + } + 0b111_10 if float_width >= 4 => { + // FSWSP + let rd = FloatReg::new(inst.extract_bits(7, 5) as u32); + let imm = inst.sp_load_imm(4); + + Op::StoreFp(FpMemInst::new(4, rd, IntReg::new(2), imm)?) + } + + _ => return Err(InvalidOpcode), + }; + + Ok(Instr::Rv16(decoded)) + } + 4 if bytes.len() >= 4 => { + let inst = LittleEndian::read_u32(bytes); + let inst = Instr32(inst); + + let int_width = <Self::RegFile as RegFile>::Int::width(); + let float_width = <Self::RegFile as RegFile>::Float::width(); + + let decoded = match inst.opcode() >> 2 { + 0b00000 => Op::Load(LoadTypeInst::from_instr32(inst)?), // LOAD + 0b00001 if float_width > 0 => { + // LOAD-FP + let width = 1usize.wrapping_shl(inst.funct3()); + let fr = FloatReg::new(inst.rd()); + let rs1 = IntReg::new(inst.rs1()); + let imm = inst.i_imm(); + + if width < 4 || width > float_width { + return Err(InvalidSubop); + } + + Op::LoadFp(FpMemInst::new(width, fr, rs1, imm)?) + } + // TODO CUSTOM_0 + 0b00011 => { + // MISC-MEM + let itype = ITypeInst::new(inst)?; + + match inst.funct3() { + 0b000 => Op::Fence(itype), + 0b001 => Op::FenceI(itype), + _ => return Err(InvalidSubop), + } + } + 0b00100 => { + // OP-IMM + let mut itype = ITypeInst::new(inst)?; + match inst.funct3() { + 0b000 => Op::AddI(itype), + 0b010 => Op::SltI(itype), + 0b011 => Op::SltIU(itype), + 0b100 => Op::XorI(itype), + 0b110 => Op::OrI(itype), + 0b111 => Op::AndI(itype), + 0b001 => Op::SllI(itype), // TODO shamt + 0b101 => { + if inst.0 & 0x40000000 == 0 { + Op::SrlI(itype) + } else { + // pretty terrible hack, whatever + itype.inst.0 &= !0x40000000; + Op::SraI(itype) + } + } + _ => unreachable!(), + } + } + + 0b00101 => Op::Auipc(UTypeInst::from_instr32(inst)?), // AUIPC + + 0b00110 if int_width > 4 => { + // OP-IMM-32 + let mut itype = ITypeInst::new(inst)?; + match inst.funct3() { + 0b000 => Op::AddIW(itype), + 0b001 => Op::SllIW(itype), // TODO shamt + 0b101 => { + if inst.0 & 0x40000000 == 0 { + Op::SrlIW(itype) + } else { + // pretty terrible hack, whatever + itype.inst.0 &= !0x40000000; + Op::SraIW(itype) + } + } + _ => return Err(InvalidSubop), + } + } + + 0b01000 => Op::Store(StoreTypeInst::from_instr32(inst)?), // STORE + 0b01001 if float_width > 0 => { + // STORE-FP + let width = 1usize.wrapping_shl(inst.funct3()); + let fr = FloatReg::new(inst.rs2()); + let rs1 = IntReg::new(inst.rs1()); + let imm = inst.s_imm(); + + if width < 4 || width > float_width { + return Err(InvalidSubop); + } + + Op::StoreFp(FpMemInst::new(width, fr, rs1, imm)?) + } + // TODO CUSTOM_1 + 0b01011 if Self::AtomicExtension::supported() => { + // AMO + let atomic = AtomicInst::new(inst)?; + + // lower two bits represent aq/rl + match inst.funct7() >> 2 { + 0b00010 if inst.rs2() == 0 => Op::Lr(atomic), + 0b00011 => Op::Sc(atomic), + 0b00001 => Op::AmoSwap(atomic), + 0b00000 => Op::AmoAdd(atomic), + 0b00100 => Op::AmoXor(atomic), + 0b01100 => Op::AmoAnd(atomic), + 0b01000 => Op::AmoOr(atomic), + 0b10000 => Op::AmoMin(atomic), + 0b10100 => Op::AmoMax(atomic), + 0b11000 => Op::AmoMinU(atomic), + 0b11100 => Op::AmoMaxU(atomic), + _ => return Err(InvalidSubop), + } + } + 0b01100 => { + // OP + let rtype = RTypeIntInst::new(inst)?; + match inst.funct7() { + 0b0000000 => match inst.funct3() { + 0b000 => Op::Add(rtype), + 0b001 => Op::Sll(rtype), + 0b010 => Op::Slt(rtype), + 0b011 => Op::SltU(rtype), + 0b100 => Op::Xor(rtype), + 0b101 => Op::Srl(rtype), + 0b110 => Op::Or(rtype), + 0b111 => Op::And(rtype), + _ => unreachable!(), + }, + 0b0100000 => match inst.funct3() { + 0b000 => Op::Sub(rtype), + 0b101 => Op::Sra(rtype), + _ => return Err(InvalidSubop), + }, + 0b0000001 if Self::MulDivExtension::supported() => { + match inst.funct3() { + 0b000 => Op::Mul(rtype), + 0b001 => Op::MulH(rtype), + 0b010 => Op::MulHSU(rtype), + 0b011 => Op::MulHU(rtype), + 0b100 => Op::Div(rtype), + 0b101 => Op::DivU(rtype), + 0b110 => Op::Rem(rtype), + 0b111 => Op::RemU(rtype), + _ => unreachable!(), + } + } + _ => return Err(InvalidSubop), + } + } + 0b01101 => Op::Lui(UTypeInst::from_instr32(inst)?), // LUI + 0b01110 if int_width > 4 => { + // OP-32 + let rtype = RTypeIntInst::new(inst)?; + match inst.funct7() { + 0b0000000 => match inst.funct3() { + 0b000 => Op::AddW(rtype), + 0b001 => Op::SllW(rtype), + 0b101 => Op::SrlW(rtype), + _ => return Err(InvalidSubop), + }, + 0b0100000 => match inst.funct3() { + 0b000 => Op::SubW(rtype), + 0b101 => Op::SraW(rtype), + _ => return Err(InvalidSubop), + }, + 0b0000001 if Self::MulDivExtension::supported() => { + match inst.funct3() { + 0b000 => Op::MulW(rtype), + 0b100 => Op::DivW(rtype), + 0b101 => Op::DivUW(rtype), + 0b110 => Op::RemW(rtype), + 0b111 => Op::RemUW(rtype), + _ => return Err(InvalidSubop), + } + } + _ => return Err(InvalidSubop), + } + } + 0b10000 if float_width > 0 => Op::Fmadd(FpMAddInst::from_instr32(inst)?), // MADD + 0b10001 if float_width > 0 => Op::Fmsub(FpMAddInst::from_instr32(inst)?), // MSUB + 0b10010 if float_width > 0 => Op::Fnmsub(FpMAddInst::from_instr32(inst)?), // NMSUB + 0b10011 if float_width > 0 => Op::Fnmadd(FpMAddInst::from_instr32(inst)?), // NMADD + 0b10100 if float_width > 0 => { + // OP-FP + match inst.fop() { + 0b00000 => Op::Fadd(RTypeFloatRoundInst::from_instr32(inst)?), + 0b00001 => Op::Fsub(RTypeFloatRoundInst::from_instr32(inst)?), + 0b00010 => Op::Fmul(RTypeFloatRoundInst::from_instr32(inst)?), + 0b00011 => Op::Fdiv(RTypeFloatRoundInst::from_instr32(inst)?), + 0b00100 => match inst.funct3() { + 0b000 => Op::Fsgnj(RTypeFloatInst::from_instr32(inst)?), + 0b001 => Op::Fsgnjn(RTypeFloatInst::from_instr32(inst)?), + 0b010 => Op::Fsgnjx(RTypeFloatInst::from_instr32(inst)?), + _ => return Err(InvalidSubop), + }, + 0b00101 => match inst.funct3() { + 0b000 => Op::Fmin(RTypeFloatInst::from_instr32(inst)?), + 0b001 => Op::Fmax(RTypeFloatInst::from_instr32(inst)?), + _ => return Err(InvalidSubop), + }, + 0b01000 => { + let rd_width = match inst.fsize() { + 0b00 => 4, + 0b01 => 8, + 0b11 => 16, + _ => return Err(InvalidSubop), + }; + let rs1_width = match inst.rs2() { + 0b00000 => 4, + 0b00001 => 8, + 0b00011 => 16, + _ => return Err(InvalidSubop), + }; + + if rd_width > float_width + || rs1_width > float_width + || rd_width == rs1_width + { + return Err(InvalidSubop); + } + + let rd = FloatReg::new(inst.rd()); + let rs1 = FloatReg::new(inst.rs1()); + let rm = RoundMode::from_bits(inst.rm())?; + + Op::Fcvt(FpCvtInst::new( + rd, + rd_width as u8, + rs1, + rs1_width as u8, + rm, + )?) + } + 0b10100 => match inst.funct3() { + 0b000 => Op::Fle(RTypeFloatCmpInst::from_instr32(inst)?), + 0b001 => Op::Flt(RTypeFloatCmpInst::from_instr32(inst)?), + 0b010 => Op::Feq(RTypeFloatCmpInst::from_instr32(inst)?), + _ => return Err(InvalidSubop), + }, + 0b01011 if inst.rs2() == 0 => { + Op::Fsqrt(RTypeFloatRoundInst::from_instr32(inst)?) + } + 0b11000 => { + let rs1_width = match inst.fsize() { + 0b00 => 4, + 0b01 => 8, + 0b11 => 16, + _ => return Err(InvalidSubop), + }; + + if rs1_width > float_width { + return Err(InvalidSubop); + } + + let rd = IntReg::new(inst.rd()); + let rs1 = FloatReg::new(inst.rs1()); + let rm = RoundMode::from_bits(inst.rm())?; + + match inst.rs2() { + 0b00000 => Op::FcvtToInt(FpCvtToIntInst::new( + rd, + 4, + false, + rs1, + rs1_width as u8, + rm, + )?), + 0b00001 => Op::FcvtToInt(FpCvtToIntInst::new( + rd, + 4, + true, + rs1, + rs1_width as u8, + rm, + )?), + 0b00010 if int_width >= 8 => { + Op::FcvtToInt(FpCvtToIntInst::new( + rd, + 8, + false, + rs1, + rs1_width as u8, + rm, + )?) + } + 0b00011 if int_width >= 8 => Op::FcvtToInt( + FpCvtToIntInst::new(rd, 8, true, rs1, rs1_width as u8, rm)?, + ), + _ => return Err(InvalidSubop), + } + } + 0b11010 => { + let rd_width = match inst.fsize() { + 0b00 => 4, + 0b01 => 8, + 0b11 => 16, + _ => return Err(InvalidSubop), + }; + + if rd_width > float_width { + return Err(InvalidSubop); + } + + let rd = FloatReg::new(inst.rd()); + let rs1 = IntReg::new(inst.rs1()); + let rm = RoundMode::from_bits(inst.rm())?; + + match inst.rs2() { + 0b00000 => Op::FcvtFromInt(FpCvtFromIntInst::new( + rd, + rd_width as u8, + rs1, + 4, + false, + rm, + )?), + 0b00001 => Op::FcvtFromInt(FpCvtFromIntInst::new( + rd, + rd_width as u8, + rs1, + 4, + true, + rm, + )?), + 0b00010 if int_width >= 8 => { + Op::FcvtFromInt(FpCvtFromIntInst::new( + rd, + rd_width as u8, + rs1, + 8, + false, + rm, + )?) + } + 0b00011 if int_width >= 8 => { + Op::FcvtFromInt(FpCvtFromIntInst::new( + rd, + rd_width as u8, + rs1, + 8, + true, + rm, + )?) + } + _ => return Err(InvalidSubop), + } + } + 0b11100 if inst.rs2() == 0 => match inst.funct3() { + 0b000 => Op::FmvToInt(FpMvToIntInst::from_instr32(inst)?), + 0b001 => Op::Fclass(FpClassInst::from_instr32(inst)?), + _ => return Err(InvalidSubop), + }, + 0b11110 if inst.rs2() == 0 && inst.funct3() == 0 => { + Op::FmvFromInt(FpMvFromIntInst::from_instr32(inst)?) + } + _ => return Err(InvalidSubop), + } + } + // TODO CUSTOM_2 + 0b11000 => { + // BRANCH + let btype = BTypeInst::from_instr32(inst)?; + match inst.funct3() { + 0b000 => Op::Beq(btype), + 0b001 => Op::Bne(btype), + 0b100 => Op::Blt(btype), + 0b101 => Op::Bge(btype), + 0b110 => Op::BltU(btype), + 0b111 => Op::BgeU(btype), + _ => return Err(InvalidSubop), + } + } + 0b11001 if inst.funct3() == 0b000 => Op::Jalr(ITypeIntInst::new(inst)?), // JALR + 0b11011 => Op::Jal(JTypeInst::from_instr32(inst)?), // JAL + 0b11100 => { + // SYSTEM + match inst.funct3() { + 0b000 => { + let funct12 = inst.0 >> 20; + + match funct12 { + // Uses the low 5 bits to hold a register, + // everything else treats this as an immediate + // or ignores it + f if (f & 0xfe0) == 0x120 => { + Op::SfenceVma(RTypeIntInst::new(inst)?) + } + + 0x000 => Op::Ecall, + 0x001 => Op::Ebreak, + + 0x002 => Op::Uret, + 0x102 => Op::Sret, + 0x302 => Op::Mret, + + 0x105 => Op::Wfi, + _ => return Err(InvalidSubop), + } + } + 0b001 => Op::Csrrw(ITypeIntInst::new(inst)?), + 0b010 => Op::Csrrs(ITypeIntInst::new(inst)?), + 0b011 => Op::Csrrc(ITypeIntInst::new(inst)?), + 0b101 => Op::CsrrwI(CsrITypeInst::new(inst)?), + 0b110 => Op::CsrrsI(CsrITypeInst::new(inst)?), + 0b111 => Op::CsrrcI(CsrITypeInst::new(inst)?), + _ => return Err(InvalidSubop), + } + } + + _ => return Err(InvalidOpcode), + }; + + Ok(Instr::Rv32(decoded)) + } + _ => return Err(TooShort), + } + } +} + +#[derive(Copy, Clone, Debug)] +pub struct RiscVIMACDisassembler<RF: RegFile>(PhantomData<RF>); +impl<RF: RegFile> RiscVDisassembler for RiscVIMACDisassembler<RF> { + type RegFile = RF; + type MulDivExtension = ExtensionSupported; + type AtomicExtension = ExtensionSupported; + type CompressedExtension = ExtensionSupported; +} diff --git a/arch/riscv/src/lib.rs b/arch/riscv/src/lib.rs new file mode 100644 index 00000000..20136114 --- /dev/null +++ b/arch/riscv/src/lib.rs @@ -0,0 +1,2570 @@ +// Option -> Result +// rework operands/instruction text +// helper func for reading/writing to registers +// do the amo max/min instructions +// Platform api + +use std::borrow::Cow; +use std::fmt; +use std::marker::PhantomData; + +use binaryninja::{ + add_optional_plugin_dependency, architecture, + architecture::{ + llvm_assemble, Architecture, ArchitectureExt, CoreArchitecture, CustomArchitectureHandle, + ImplicitRegisterExtend, InstructionInfo, LlvmServicesCodeModel, LlvmServicesDialect, + LlvmServicesRelocMode, Register as Reg, RegisterInfo, UnusedFlag, UnusedRegisterStack, + UnusedRegisterStackInfo, + }, + binaryview::{BinaryView, BinaryViewExt}, + callingconvention::{register_calling_convention, CallingConventionBase, ConventionBuilder}, + custombinaryview::{BinaryViewType, BinaryViewTypeExt}, + disassembly::{InstructionTextToken, InstructionTextTokenContents}, + function::Function, + functionrecognizer::FunctionRecognizer, + llil, + llil::{ + ExprInfo, InstrInfo, Label, Liftable, LiftableWithSize, LiftedNonSSA, Lifter, Mutable, + NonSSA, + }, + rc::Ref, + relocation::{ + CoreRelocationHandler, CustomRelocationHandlerHandle, RelocationHandler, RelocationInfo, + RelocationType, + }, + string::BnString, + symbol::{Symbol, SymbolType}, + types::{max_confidence, min_confidence, Conf, NameAndType, Type}, +}; + +use riscv_dis::{ + FloatReg, FloatRegType, Instr, IntRegType, Op, RegFile, Register as RiscVRegister, + RiscVDisassembler, RoundMode, +}; + +enum RegType { + Integer(u32), + Float(u32), +} + +#[repr(u32)] +#[derive(Clone, Copy, PartialEq, Eq, Debug)] +enum Intrinsic { + Uret, + Sret, + Mret, + Wfi, + Csrrw, + Csrwr, + Csrrd, + Csrrs, + Csrrc, + Fadd(u8, RoundMode), + Fsub(u8, RoundMode), + Fmul(u8, RoundMode), + Fdiv(u8, RoundMode), + Fsqrt(u8, RoundMode), + Fsgnj(u8), + Fsgnjn(u8), + Fsgnjx(u8), + Fmin(u8), + Fmax(u8), + Fclass(u8), + FcvtFToF(u8, u8, RoundMode), + FcvtIToF(u8, u8, RoundMode), + FcvtFToI(u8, u8, RoundMode), + FcvtUToF(u8, u8, RoundMode), + FcvtFToU(u8, u8, RoundMode), + Fence, +} + +#[derive(Copy, Clone)] +struct Register<D: 'static + RiscVDisassembler> { + id: u32, + _dis: PhantomData<D>, +} + +#[derive(Copy, Clone)] +struct RiscVIntrinsic<D: 'static + RiscVDisassembler> { + id: Intrinsic, + _dis: PhantomData<D>, +} + +impl<D: 'static + RiscVDisassembler> Register<D> { + fn new(id: u32) -> Self { + Self { + id, + _dis: PhantomData, + } + } + + fn reg_type(&self) -> RegType { + let int_reg_count = <D::RegFile as RegFile>::int_reg_count(); + + if self.id < int_reg_count { + RegType::Integer(self.id) + } else { + RegType::Float(self.id - int_reg_count) + } + } +} + +impl<D: 'static + RiscVDisassembler> From<riscv_dis::IntReg<D>> for Register<D> { + fn from(reg: riscv_dis::IntReg<D>) -> Self { + Self { + id: reg.id(), + _dis: PhantomData, + } + } +} + +impl<D: 'static + RiscVDisassembler> From<FloatReg<D>> for Register<D> { + fn from(reg: FloatReg<D>) -> Self { + let int_reg_count = <D::RegFile as RegFile>::int_reg_count(); + + Self { + id: reg.id() + int_reg_count, + _dis: PhantomData, + } + } +} + +impl<D: 'static + RiscVDisassembler> Into<llil::Register<Register<D>>> for Register<D> { + fn into(self) -> llil::Register<Register<D>> { + llil::Register::ArchReg(self) + } +} + +impl<D: 'static + RiscVDisassembler> RegisterInfo for Register<D> { + type RegType = Self; + + fn parent(&self) -> Option<Self> { + None + } + + fn size(&self) -> usize { + match self.reg_type() { + RegType::Integer(_) => <D::RegFile as RegFile>::Int::width(), + RegType::Float(_) => <D::RegFile as RegFile>::Float::width(), + } + } + + fn offset(&self) -> usize { + 0 + } + fn implicit_extend(&self) -> ImplicitRegisterExtend { + ImplicitRegisterExtend::NoExtend + } +} + +impl<D: 'static + RiscVDisassembler> architecture::Register for Register<D> { + type InfoType = Self; + + fn name(&self) -> Cow<str> { + match self.reg_type() { + RegType::Integer(id) => match id { + 0 => "zero".into(), + 1 => "ra".into(), + 2 => "sp".into(), + 3 => "gp".into(), + 4 => "tp".into(), + r @ 5..=7 => format!("t{}", r - 5).into(), + r @ 8..=9 => format!("s{}", r - 8).into(), + r @ 10..=17 => format!("a{}", r - 10).into(), + r @ 18..=27 => format!("s{}", r - 16).into(), + r @ 28..=31 => format!("t{}", r - 25).into(), + _ => unreachable!(), + }, + RegType::Float(id) => match id { + r @ 0..=7 => format!("ft{}", r).into(), + r @ 8..=9 => format!("fs{}", r - 8).into(), + r @ 10..=17 => format!("fa{}", r - 10).into(), + r @ 18..=27 => format!("fs{}", r - 16).into(), + r @ 28..=31 => format!("ft{}", r - 20).into(), + _ => unreachable!(), + }, + } + } + + fn info(&self) -> Self { + *self + } + + fn id(&self) -> u32 { + self.id + } +} + +impl<'a, D: 'static + RiscVDisassembler + Send + Sync> Liftable<'a, RiscVArch<D>> for Register<D> { + type Result = llil::ValueExpr; + + fn lift( + il: &'a llil::Lifter<RiscVArch<D>>, + reg: Self, + ) -> llil::Expression<'a, RiscVArch<D>, Mutable, NonSSA<LiftedNonSSA>, Self::Result> { + match reg.reg_type() { + RegType::Integer(0) => il.const_int(reg.size(), 0), + RegType::Integer(_) => il.reg(reg.size(), reg), + _ => il.unimplemented(), + } + } +} + +impl<'a, D: 'static + RiscVDisassembler + Send + Sync> LiftableWithSize<'a, RiscVArch<D>> + for Register<D> +{ + fn lift_with_size( + il: &'a llil::Lifter<RiscVArch<D>>, + reg: Self, + size: usize, + ) -> llil::Expression<'a, RiscVArch<D>, Mutable, NonSSA<LiftedNonSSA>, llil::ValueExpr> { + #[cfg(debug_assertions)] + { + if reg.size() < size { + log::warn!( + "il @ {:x} attempted to lift {} byte register as {} byte expr", + il.current_address(), + reg.size(), + size + ); + } + } + + match reg.reg_type() { + RegType::Integer(0) => il.const_int(size, 0), + RegType::Integer(_) => { + let expr = il.reg(reg.size(), reg); + + if size < reg.size() { + il.low_part(size, expr).build() + } else { + expr + } + } + _ => il.unimplemented(), + } + } +} + +impl<D: 'static + RiscVDisassembler + Send + Sync> fmt::Debug for Register<D> { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + f.write_str(self.name().as_ref()) + } +} + +impl<D: RiscVDisassembler> RiscVIntrinsic<D> { + fn id_from_parts(id: u32, sz1: Option<u8>, sz2: Option<u8>, rm: Option<RoundMode>) -> u32 { + let sz1 = sz1.unwrap_or(0); + let sz2 = sz2.unwrap_or(0); + let rm = match rm { + None | Some(RoundMode::Dynamic) => 0, + Some(RoundMode::RoundNearestEven) => 1, + Some(RoundMode::RoundTowardZero) => 2, + Some(RoundMode::RoundDown) => 3, + Some(RoundMode::RoundUp) => 4, + Some(RoundMode::RoundMaxMagnitude) => 5, + }; + + let mut id = id << 20; + id |= sz1 as u32; + id |= (sz2 as u32) << 8; + id |= (rm as u32) << 16; + id + } + + fn parts_from_id(id: u32) -> Option<(u32, u8, u8, RoundMode)> { + let sz1 = (id & 0xff) as u8; + let sz2 = ((id >> 8) & 0xff) as u8; + let rm = match (id >> 16) & 0xf { + 0 => RoundMode::Dynamic, + 1 => RoundMode::RoundNearestEven, + 2 => RoundMode::RoundTowardZero, + 3 => RoundMode::RoundDown, + 4 => RoundMode::RoundUp, + 5 => RoundMode::RoundMaxMagnitude, + _ => return None, + }; + Some(((id >> 20) & 0xfff, sz1, sz2, rm)) + } + + fn from_id(id: u32) -> Option<RiscVIntrinsic<D>> { + match Self::parts_from_id(id) { + Some((0, _, _, _)) => Some(Intrinsic::Uret.into()), + Some((1, _, _, _)) => Some(Intrinsic::Sret.into()), + Some((2, _, _, _)) => Some(Intrinsic::Mret.into()), + Some((3, _, _, _)) => Some(Intrinsic::Wfi.into()), + Some((4, _, _, _)) => Some(Intrinsic::Csrrw.into()), + Some((5, _, _, _)) => Some(Intrinsic::Csrwr.into()), + Some((6, _, _, _)) => Some(Intrinsic::Csrrd.into()), + Some((7, _, _, _)) => Some(Intrinsic::Csrrs.into()), + Some((8, _, _, _)) => Some(Intrinsic::Csrrc.into()), + Some((9, size, _, rm)) => Some(Intrinsic::Fadd(size, rm).into()), + Some((10, size, _, rm)) => Some(Intrinsic::Fsub(size, rm).into()), + Some((11, size, _, rm)) => Some(Intrinsic::Fmul(size, rm).into()), + Some((12, size, _, rm)) => Some(Intrinsic::Fdiv(size, rm).into()), + Some((13, size, _, rm)) => Some(Intrinsic::Fsqrt(size, rm).into()), + Some((14, size, _, _)) => Some(Intrinsic::Fsgnj(size).into()), + Some((15, size, _, _)) => Some(Intrinsic::Fsgnjn(size).into()), + Some((16, size, _, _)) => Some(Intrinsic::Fsgnjx(size).into()), + Some((17, size, _, _)) => Some(Intrinsic::Fmin(size).into()), + Some((18, size, _, _)) => Some(Intrinsic::Fmax(size).into()), + Some((19, size, _, _)) => Some(Intrinsic::Fclass(size).into()), + Some((20, ssize, dsize, rm)) => Some(Intrinsic::FcvtFToF(ssize, dsize, rm).into()), + Some((21, isize, fsize, rm)) => Some(Intrinsic::FcvtIToF(isize, fsize, rm).into()), + Some((22, fsize, isize, rm)) => Some(Intrinsic::FcvtFToI(fsize, isize, rm).into()), + Some((23, usize, fsize, rm)) => Some(Intrinsic::FcvtUToF(usize, fsize, rm).into()), + Some((24, fsize, usize, rm)) => Some(Intrinsic::FcvtFToU(fsize, usize, rm).into()), + Some((25, _, _, _)) => Some(Intrinsic::Fence.into()), + _ => None, + } + } + + fn int_size_suffix(size: u8) -> &'static str { + match size { + 4 => "_i32", + 8 => "_i64", + _ => unreachable!(), + } + } + + fn uint_size_suffix(size: u8) -> &'static str { + match size { + 4 => "_u32", + 8 => "_u64", + _ => unreachable!(), + } + } + + fn float_size_suffix(size: u8) -> &'static str { + match size { + 4 => "_s", + 8 => "_d", + 16 => "_q", + _ => unreachable!(), + } + } + + fn round_mode_suffix(rm: RoundMode) -> &'static str { + match rm { + RoundMode::RoundNearestEven => "_rne", + RoundMode::RoundTowardZero => "_rtz", + RoundMode::RoundDown => "_rdn", + RoundMode::RoundUp => "_rup", + RoundMode::RoundMaxMagnitude => "_rmm", + RoundMode::Dynamic => "", + } + } +} + +impl<D: RiscVDisassembler> From<Intrinsic> for RiscVIntrinsic<D> { + fn from(id: Intrinsic) -> Self { + Self { + id, + _dis: PhantomData, + } + } +} + +impl<D: RiscVDisassembler> architecture::Intrinsic for RiscVIntrinsic<D> { + fn name(&self) -> Cow<str> { + match self.id { + Intrinsic::Uret => "_uret".into(), + Intrinsic::Sret => "_sret".into(), + Intrinsic::Mret => "_mret".into(), + Intrinsic::Wfi => "_wfi".into(), + Intrinsic::Csrrw => "_csrrw".into(), + Intrinsic::Csrwr => "_csrwr".into(), + Intrinsic::Csrrd => "_csrrd".into(), + Intrinsic::Csrrs => "_csrrs".into(), + Intrinsic::Csrrc => "_csrrc".into(), + Intrinsic::Fadd(size, rm) => format!( + "_fadd{}{}", + Self::float_size_suffix(size), + Self::round_mode_suffix(rm) + ) + .into(), + Intrinsic::Fsub(size, rm) => format!( + "_fsub{}{}", + Self::float_size_suffix(size), + Self::round_mode_suffix(rm) + ) + .into(), + Intrinsic::Fmul(size, rm) => format!( + "_fmul{}{}", + Self::float_size_suffix(size), + Self::round_mode_suffix(rm) + ) + .into(), + Intrinsic::Fdiv(size, rm) => format!( + "_fdiv{}{}", + Self::float_size_suffix(size), + Self::round_mode_suffix(rm) + ) + .into(), + Intrinsic::Fsqrt(size, rm) => format!( + "_fsqrt{}{}", + Self::float_size_suffix(size), + Self::round_mode_suffix(rm) + ) + .into(), + Intrinsic::Fsgnj(size) => format!("_fsgnj{}", Self::float_size_suffix(size)).into(), + Intrinsic::Fsgnjn(size) => format!("_fsgnjn{}", Self::float_size_suffix(size)).into(), + Intrinsic::Fsgnjx(size) => format!("_fsgnjx{}", Self::float_size_suffix(size)).into(), + Intrinsic::Fmin(size) => format!("_fmin{}", Self::float_size_suffix(size)).into(), + Intrinsic::Fmax(size) => format!("_fmax{}", Self::float_size_suffix(size)).into(), + Intrinsic::Fclass(size) => format!("_fclass{}", Self::float_size_suffix(size)).into(), + Intrinsic::FcvtFToF(usize, fsize, rm) => format!( + "_fcvt{}_to{}{}", + Self::float_size_suffix(usize), + Self::float_size_suffix(fsize), + Self::round_mode_suffix(rm) + ) + .into(), + Intrinsic::FcvtIToF(isize, fsize, rm) => format!( + "_fcvt{}_to{}{}", + Self::int_size_suffix(isize), + Self::float_size_suffix(fsize), + Self::round_mode_suffix(rm) + ) + .into(), + Intrinsic::FcvtFToI(fsize, isize, rm) => format!( + "_fcvt{}_to{}{}", + Self::float_size_suffix(fsize), + Self::int_size_suffix(isize), + Self::round_mode_suffix(rm) + ) + .into(), + Intrinsic::FcvtUToF(usize, fsize, rm) => format!( + "_fcvt{}_to{}{}", + Self::uint_size_suffix(usize), + Self::float_size_suffix(fsize), + Self::round_mode_suffix(rm) + ) + .into(), + Intrinsic::FcvtFToU(fsize, usize, rm) => format!( + "_fcvt{}_to{}{}", + Self::float_size_suffix(fsize), + Self::uint_size_suffix(usize), + Self::round_mode_suffix(rm) + ) + .into(), + Intrinsic::Fence => "_fence".into(), + } + } + + fn id(&self) -> u32 { + match self.id { + Intrinsic::Uret => Self::id_from_parts(0, None, None, None), + Intrinsic::Sret => Self::id_from_parts(1, None, None, None), + Intrinsic::Mret => Self::id_from_parts(2, None, None, None), + Intrinsic::Wfi => Self::id_from_parts(3, None, None, None), + Intrinsic::Csrrw => Self::id_from_parts(4, None, None, None), + Intrinsic::Csrwr => Self::id_from_parts(5, None, None, None), + Intrinsic::Csrrd => Self::id_from_parts(6, None, None, None), + Intrinsic::Csrrs => Self::id_from_parts(7, None, None, None), + Intrinsic::Csrrc => Self::id_from_parts(8, None, None, None), + Intrinsic::Fadd(size, rm) => Self::id_from_parts(9, Some(size), None, Some(rm)), + Intrinsic::Fsub(size, rm) => Self::id_from_parts(10, Some(size), None, Some(rm)), + Intrinsic::Fmul(size, rm) => Self::id_from_parts(11, Some(size), None, Some(rm)), + Intrinsic::Fdiv(size, rm) => Self::id_from_parts(12, Some(size), None, Some(rm)), + Intrinsic::Fsqrt(size, rm) => Self::id_from_parts(13, Some(size), None, Some(rm)), + Intrinsic::Fsgnj(size) => Self::id_from_parts(14, Some(size), None, None), + Intrinsic::Fsgnjn(size) => Self::id_from_parts(15, Some(size), None, None), + Intrinsic::Fsgnjx(size) => Self::id_from_parts(16, Some(size), None, None), + Intrinsic::Fmin(size) => Self::id_from_parts(17, Some(size), None, None), + Intrinsic::Fmax(size) => Self::id_from_parts(18, Some(size), None, None), + Intrinsic::Fclass(size) => Self::id_from_parts(19, Some(size), None, None), + Intrinsic::FcvtFToF(ssize, dsize, rm) => { + Self::id_from_parts(20, Some(ssize), Some(dsize), Some(rm)) + } + Intrinsic::FcvtIToF(isize, fsize, rm) => { + Self::id_from_parts(21, Some(isize), Some(fsize), Some(rm)) + } + Intrinsic::FcvtFToI(fsize, isize, rm) => { + Self::id_from_parts(22, Some(isize), Some(fsize), Some(rm)) + } + Intrinsic::FcvtUToF(usize, fsize, rm) => { + Self::id_from_parts(23, Some(usize), Some(fsize), Some(rm)) + } + Intrinsic::FcvtFToU(fsize, usize, rm) => { + Self::id_from_parts(24, Some(usize), Some(fsize), Some(rm)) + } + Intrinsic::Fence => Self::id_from_parts(25, None, None, None), + } + } + + fn inputs(&self) -> Vec<NameAndType<String>> { + match self.id { + Intrinsic::Uret | Intrinsic::Sret | Intrinsic::Mret | Intrinsic::Wfi => { + vec![] + } + Intrinsic::Csrrd => { + vec![NameAndType::new( + "csr".into(), + &Type::int(4, false), + max_confidence(), + )] + } + Intrinsic::Csrrw | Intrinsic::Csrwr | Intrinsic::Csrrs | Intrinsic::Csrrc => { + vec![ + NameAndType::new("csr".into(), &Type::int(4, false), max_confidence()), + NameAndType::new( + "value".into(), + &Type::int(<D::RegFile as RegFile>::Int::width(), false), + min_confidence(), + ), + ] + } + Intrinsic::Fadd(size, _) + | Intrinsic::Fsub(size, _) + | Intrinsic::Fmul(size, _) + | Intrinsic::Fdiv(size, _) + | Intrinsic::Fsgnj(size) + | Intrinsic::Fsgnjn(size) + | Intrinsic::Fsgnjx(size) + | Intrinsic::Fmin(size) + | Intrinsic::Fmax(size) => { + vec![ + NameAndType::new("".into(), &Type::float(size as usize), max_confidence()), + NameAndType::new("".into(), &Type::float(size as usize), max_confidence()), + ] + } + Intrinsic::Fsqrt(size, _) + | Intrinsic::Fclass(size) + | Intrinsic::FcvtFToF(size, _, _) + | Intrinsic::FcvtFToI(size, _, _) + | Intrinsic::FcvtFToU(size, _, _) => { + vec![NameAndType::new( + "".into(), + &Type::float(size as usize), + max_confidence(), + )] + } + Intrinsic::FcvtIToF(size, _, _) => { + vec![NameAndType::new( + "".into(), + &Type::int(size as usize, true), + max_confidence(), + )] + } + Intrinsic::FcvtUToF(size, _, _) => { + vec![NameAndType::new( + "".into(), + &Type::int(size as usize, false), + max_confidence(), + )] + } + Intrinsic::Fence => { + vec![NameAndType::new( + "".into(), + &Type::int(4, false), + min_confidence(), + )] + } + } + } + + fn outputs(&self) -> Vec<Conf<Ref<Type>>> { + match self.id { + Intrinsic::Uret + | Intrinsic::Sret + | Intrinsic::Mret + | Intrinsic::Wfi + | Intrinsic::Csrwr + | Intrinsic::Fence => { + vec![] + } + Intrinsic::Csrrw | Intrinsic::Csrrd | Intrinsic::Csrrs | Intrinsic::Csrrc => { + vec![Conf::new( + Type::int(<D::RegFile as RegFile>::Int::width(), false), + min_confidence(), + )] + } + Intrinsic::Fadd(size, _) + | Intrinsic::Fsub(size, _) + | Intrinsic::Fmul(size, _) + | Intrinsic::Fdiv(size, _) + | Intrinsic::Fsqrt(size, _) + | Intrinsic::Fsgnj(size) + | Intrinsic::Fsgnjn(size) + | Intrinsic::Fsgnjx(size) + | Intrinsic::Fmin(size) + | Intrinsic::Fmax(size) + | Intrinsic::FcvtFToF(_, size, _) + | Intrinsic::FcvtIToF(_, size, _) + | Intrinsic::FcvtUToF(_, size, _) => { + vec![Conf::new(Type::float(size as usize), max_confidence())] + } + Intrinsic::Fclass(_) => { + vec![Conf::new(Type::int(4, false), min_confidence())] + } + Intrinsic::FcvtFToI(_, size, _) => { + vec![Conf::new(Type::int(size as usize, true), max_confidence())] + } + Intrinsic::FcvtFToU(_, size, _) => { + vec![Conf::new(Type::int(size as usize, false), max_confidence())] + } + } + } +} + +struct RiscVArch<D: 'static + RiscVDisassembler + Send + Sync> { + handle: CoreArchitecture, + custom_handle: CustomArchitectureHandle<RiscVArch<D>>, + _dis: PhantomData<D>, +} + +impl<D: 'static + RiscVDisassembler + Send + Sync> architecture::Architecture for RiscVArch<D> { + type Handle = CustomArchitectureHandle<Self>; + + type RegisterInfo = Register<D>; + type Register = Register<D>; + type RegisterStackInfo = UnusedRegisterStackInfo<Self::Register>; + type RegisterStack = UnusedRegisterStack<Self::Register>; + + type Flag = UnusedFlag; + type FlagWrite = UnusedFlag; + type FlagClass = UnusedFlag; + type FlagGroup = UnusedFlag; + + type Intrinsic = RiscVIntrinsic<D>; + + fn endianness(&self) -> binaryninja::Endianness { + binaryninja::Endianness::LittleEndian + } + + fn address_size(&self) -> usize { + <D::RegFile as RegFile>::Int::width() + } + + fn default_integer_size(&self) -> usize { + <D::RegFile as RegFile>::Int::width() + } + + fn instruction_alignment(&self) -> usize { + use riscv_dis::StandardExtension; + + if D::CompressedExtension::supported() { + 2 + } else { + 4 + } + } + + fn max_instr_len(&self) -> usize { + 4 + } + + fn opcode_display_len(&self) -> usize { + self.max_instr_len() + } + + fn associated_arch_by_addr(&self, _addr: &mut u64) -> CoreArchitecture { + self.handle + } + + fn instruction_info(&self, data: &[u8], addr: u64) -> Option<InstructionInfo> { + use architecture::BranchInfo; + + let (inst_len, op) = match D::decode(addr, data) { + Ok(Instr::Rv16(op)) => (2, op), + Ok(Instr::Rv32(op)) => (4, op), + _ => return None, + }; + + let mut res = InstructionInfo::new(inst_len, false); + + match op { + Op::Jal(ref j) => { + let target = addr.wrapping_add(j.imm() as i64 as u64); + + let branch = if j.rd().id() == 0 { + BranchInfo::Unconditional(target) + } else { + BranchInfo::Call(target) + }; + + res.add_branch(branch, None); + } + Op::Jalr(ref i) => { + // TODO handle the calls with rs1 == 0? + if i.rd().id() == 0 { + let branch_type = if i.rs1().id() == 1 { + BranchInfo::FunctionReturn + } else { + BranchInfo::Unresolved + }; + + res.add_branch(branch_type, None); + } + } + Op::Beq(ref b) + | Op::Bne(ref b) + | Op::Blt(ref b) + | Op::Bge(ref b) + | Op::BltU(ref b) + | Op::BgeU(ref b) => { + res.add_branch(BranchInfo::False(addr.wrapping_add(inst_len as u64)), None); + res.add_branch( + BranchInfo::True(addr.wrapping_add(b.imm() as i64 as u64)), + None, + ); + } + Op::Ecall => { + res.add_branch(BranchInfo::SystemCall, None); + } + Op::Ebreak => { + // TODO is this valid, or should lifting handle this? + res.add_branch(BranchInfo::Unresolved, None); + } + Op::Uret | Op::Sret | Op::Mret => { + res.add_branch(BranchInfo::FunctionReturn, None); + } + _ => {} + } + + Some(res) + } + + fn instruction_text( + &self, + data: &[u8], + addr: u64, + ) -> Option<(usize, Vec<InstructionTextToken>)> { + use riscv_dis::Operand; + use InstructionTextTokenContents::*; + + let inst = match D::decode(addr, data) { + Ok(i) => i, + _ => return None, + }; + + let (inst_len, op) = match inst { + Instr::Rv16(op) => (2, op), + Instr::Rv32(op) => (4, op), + }; + + let mut res = Vec::new(); + let mut mnem = format!("{}", inst.mnem()); + let mut pad_len = 8usize.saturating_sub(mnem.len()); + let mut operands = inst.operands(); + + // Handle pseudo-instructions. Only single instruction pseudo-instructions are handled. + match op { + Op::AddI(i) => { + // addi zero, zero, 0 => nop + if i.rd().id() == 0 && i.rs1().id() == 0 && i.imm() == 0 { + mnem = "nop".into(); + pad_len = 8usize.saturating_sub(mnem.len()); + operands.clear(); + } + // addi rd, zero, imm => li rd, imm + else if i.rs1().id() == 0 { + mnem = "li".into(); + pad_len = 8usize.saturating_sub(mnem.len()); + operands.remove(1); + } + // addi rd, rs, 0 => mv rd, rs + else if i.imm() == 0 { + mnem = "mv".into(); + pad_len = 8usize.saturating_sub(mnem.len()); + operands.remove(2); + } + } + Op::AddIW(i) => { + // addiw rd, rs, 0 => sext.w rd, rs + if i.imm() == 0 { + mnem = "sext.w".into(); + pad_len = 8usize.saturating_sub(mnem.len()); + operands.remove(2); + } + } + Op::Beq(i) => { + // beq rs, zero, offset => beqz rs, offset + if i.rs2().id() == 0 { + mnem = "beqz".into(); + pad_len = 8usize.saturating_sub(mnem.len()); + operands.remove(1); + } + } + Op::Bne(i) => { + // bne rs, zero, offset => bnez rs, offset + if i.rs2().id() == 0 { + mnem = "bnez".into(); + pad_len = 8usize.saturating_sub(mnem.len()); + operands.remove(1); + } + } + Op::Bge(i) => { + // bge zero, rs, offset => blez rs, offset + if i.rs1().id() == 0 { + mnem = "blez".into(); + pad_len = 8usize.saturating_sub(mnem.len()); + operands.remove(0); + } + // bge rs, zero, offset => bgez rs, offset + else if i.rs2().id() == 0 { + mnem = "bgez".into(); + pad_len = 8usize.saturating_sub(mnem.len()); + operands.remove(1); + } + } + Op::Blt(i) => { + // blt zero, rs, offset => bgtz rs, offset + if i.rs1().id() == 0 { + mnem = "bgtz".into(); + pad_len = 8usize.saturating_sub(mnem.len()); + operands.remove(0); + } + // blt rs, zero, offset => bltz rs, offset + else if i.rs2().id() == 0 { + mnem = "bltz".into(); + pad_len = 8usize.saturating_sub(mnem.len()); + operands.remove(1); + } + } + Op::Jal(i) => { + // jal zero, offset => j offset + if i.rd().id() == 0 { + mnem = "j".into(); + pad_len = 8usize.saturating_sub(mnem.len()); + operands.remove(0); + } + // jal ra, offset => jal offset + else if i.rd().id() == 0 { + operands.remove(0); + } + } + Op::Jalr(i) => { + // jalr zero, ra, 0 => ret + if i.rd().id() == 0 && i.rs1().id() == 1 && i.imm() == 0 { + mnem = "ret".into(); + pad_len = 8usize.saturating_sub(mnem.len()); + operands.clear(); + } + // jalr zero, rs, 0 => jr rs + else if i.rd().id() == 0 && i.imm() == 0 { + mnem = "jr".into(); + pad_len = 8usize.saturating_sub(mnem.len()); + operands.remove(2); + operands.remove(0); + } + // jalr ra, rs, 0 => jalr rs + else if i.rd().id() == 1 && i.imm() == 0 { + mnem = "jalr".into(); + pad_len = 8usize.saturating_sub(mnem.len()); + operands.remove(2); + operands.remove(0); + } + } + Op::Slt(i) => { + // slt rd, rs, zero => sltz rd, rs + if i.rs2().id() == 0 { + mnem = "sltz".into(); + pad_len = 8usize.saturating_sub(mnem.len()); + operands.remove(2); + } + // slt rd, zero, rs => sgtz rd, rs + else if i.rs1().id() == 0 { + mnem = "sgtz".into(); + pad_len = 8usize.saturating_sub(mnem.len()); + operands.remove(1); + } + } + Op::SltU(i) => { + // sltu rd, zero, rs => snez rd, rs + if i.rs1().id() == 0 { + mnem = "snez".into(); + pad_len = 8usize.saturating_sub(mnem.len()); + operands.remove(1); + } + } + Op::SltIU(i) => { + // sltiu rd, rs, 1 => seqz rd, rs + if i.imm() == 1 { + mnem = "seqz".into(); + pad_len = 8usize.saturating_sub(mnem.len()); + operands.remove(2); + } + } + Op::Sub(i) => { + // sub rd, zero, rs => neg rd, rs + if i.rs1().id() == 0 { + mnem = "neg".into(); + pad_len = 8usize.saturating_sub(mnem.len()); + operands.remove(1); + } + } + Op::SubW(i) => { + // subw rd, zero, rs => negw rd, rs + if i.rs1().id() == 0 { + mnem = "negw".into(); + pad_len = 8usize.saturating_sub(mnem.len()); + operands.remove(1); + } + } + Op::XorI(i) => { + // xori rd, rs, -1 => not rd, rs + if i.imm() == -1 { + mnem = "not".into(); + pad_len = 8usize.saturating_sub(mnem.len()); + operands.remove(2); + } + } + Op::Fsgnj(i) => { + // fsgnj rd, rs, rs => fmv rd, rs + if i.rs1().id() == i.rs2().id() { + mnem = match i.width() { + 4 => "fmv.s".into(), + 8 => "fmv.d".into(), + 16 => "fmv.q".into(), + _ => unreachable!(), + }; + pad_len = 8usize.saturating_sub(mnem.len()); + operands.remove(2); + } + } + Op::Fsgnjn(i) => { + // fsgnjn rd, rs, rs => fneg rd, rs + if i.rs1().id() == i.rs2().id() { + mnem = match i.width() { + 4 => "fneg.s".into(), + 8 => "fneg.d".into(), + 16 => "fneg.q".into(), + _ => unreachable!(), + }; + pad_len = 8usize.saturating_sub(mnem.len()); + operands.remove(2); + } + } + Op::Fsgnjx(i) => { + // fsgnjx rd, rs, rs => fabs rd, rs + if i.rs1().id() == i.rs2().id() { + mnem = match i.width() { + 4 => "fabs.s".into(), + 8 => "fabs.d".into(), + 16 => "fabs.q".into(), + _ => unreachable!(), + }; + pad_len = 8usize.saturating_sub(mnem.len()); + operands.remove(2); + } + } + _ => (), + } + + res.push(InstructionTextToken::new(BnString::new(mnem), Instruction)); + + for (i, oper) in operands.iter().enumerate() { + if i == 0 { + res.push(InstructionTextToken::new( + BnString::new(format!("{:1$}", " ", pad_len)), + Text, + )); + } else { + res.push(InstructionTextToken::new( + BnString::new(","), + OperandSeparator, + )); + res.push(InstructionTextToken::new(BnString::new(" "), Text)); + } + + match *oper { + Operand::R(r) => { + let reg = self::Register::from(r); + + res.push(InstructionTextToken::new( + BnString::new(®.name()), + Register, + )); + } + Operand::F(r) => { + let reg = self::Register::from(r); + + res.push(InstructionTextToken::new( + BnString::new(®.name()), + Register, + )); + } + Operand::I(i) => { + match op { + Op::Beq(..) + | Op::Bne(..) + | Op::Blt(..) + | Op::Bge(..) + | Op::BltU(..) + | Op::BgeU(..) + | Op::Jal(..) => { + // BRANCH or JAL + let target = addr.wrapping_add(i as i64 as u64); + + res.push(InstructionTextToken::new( + BnString::new(format!("0x{:x}", target)), + CodeRelativeAddress(target), + )); + } + _ => { + res.push(InstructionTextToken::new( + BnString::new(match i { + -0x8_0000..=-1 => format!("-0x{:x}", -i), + _ => format!("0x{:x}", i), + }), + Integer(i as u64), + )); + } + } + } + Operand::M(i, b) => { + let reg = self::Register::from(b); + + res.push(InstructionTextToken::new( + BnString::new(""), + BeginMemoryOperand, + )); + res.push(InstructionTextToken::new( + BnString::new(if i < 0 { + format!("-0x{:x}", -i) + } else { + format!("0x{:x}", i) + }), + Integer(i as u64), + )); + + res.push(InstructionTextToken::new(BnString::new("("), Text)); + res.push(InstructionTextToken::new( + BnString::new(®.name()), + Register, + )); + res.push(InstructionTextToken::new(BnString::new(")"), Text)); + res.push(InstructionTextToken::new( + BnString::new(""), + EndMemoryOperand, + )); + } + Operand::RM(r) => { + res.push(InstructionTextToken::new(BnString::new(r.name()), Register)); + } + } + } + + Some((inst_len, res)) + } + + fn instruction_llil( + &self, + data: &[u8], + addr: u64, + il: &mut llil::Lifter<Self>, + ) -> Option<(usize, bool)> { + let max_width = self.default_integer_size(); + + let (inst_len, op) = match D::decode(addr, data) { + Ok(Instr::Rv16(op)) => (2, op), + Ok(Instr::Rv32(op)) => (4, op), + _ => return None, + }; + + macro_rules! set_reg_or_append_fallback { + ($op:ident, $t:expr, $f:expr) => {{ + let rd = Register::from($op.rd()); + match rd.id { + 0 => $f.append(), + _ => il.set_reg(rd.size(), rd, $t).append(), + } + }}; + } + + macro_rules! simple_op { + ($op:ident, no_discard $f:expr) => {{ + let expr = $f; + set_reg_or_append_fallback!($op, expr, expr) + }}; + ($op:ident, $f:expr) => { + set_reg_or_append_fallback!($op, $f, il.nop()) + }; + } + + macro_rules! simple_i { + ($i:ident, $f:expr ) => {{ + let rs1 = Register::from($i.rs1()); + simple_op!($i, $f(rs1, $i.imm())) + }}; + } + + macro_rules! simple_r { + ($r:ident, $f:expr ) => {{ + let rs1 = Register::from($r.rs1()); + let rs2 = Register::from($r.rs2()); + simple_op!($r, $f(rs1, rs2)) + }}; + } + + match op { + Op::Load(l) => simple_op!(l, no_discard { + let size = l.width(); + let rs1 = Register::from(l.rs1()); + + let src_expr = il.add(max_width, rs1, l.imm()); + let load_expr = il.load(size, src_expr) + .with_source_operand(1); + + match (size < max_width, l.zx()) { + (false, _) => load_expr, + (true, true) => il.zx(max_width, load_expr).build(), + (true, false) => il.sx(max_width, load_expr).build(), + } + }), + Op::Store(s) => { + let size = s.width(); + let dest = il.add(max_width, Register::from(s.rs1()), s.imm()); + let mut src = il + .expression(Register::from(s.rs2())) + .with_source_operand(0); + + if size < max_width { + src = il.low_part(size, src).build(); + } + + il.store(size, dest, src).with_source_operand(1).append(); + } + + Op::AddI(i) => simple_i!(i, |rs1, imm| il.add(max_width, rs1, imm)), + Op::SltI(i) => simple_i!(i, |rs1, imm| il + .bool_to_int(max_width, il.cmp_slt(max_width, rs1, imm))), + Op::SltIU(i) => simple_i!(i, |rs1, imm| il + .bool_to_int(max_width, il.cmp_ult(max_width, rs1, imm))), + Op::XorI(i) => simple_i!(i, |rs1, imm| il.xor(max_width, rs1, imm)), + Op::OrI(i) => simple_i!(i, |rs1, imm| il.or(max_width, rs1, imm)), + Op::AndI(i) => simple_i!(i, |rs1, imm| il.and(max_width, rs1, imm)), + Op::SllI(i) => simple_i!(i, |rs1, imm| il.lsl(max_width, rs1, imm)), + Op::SrlI(i) => simple_i!(i, |rs1, imm| il.lsr(max_width, rs1, imm)), + Op::SraI(i) => simple_i!(i, |rs1, imm| il.asr(max_width, rs1, imm)), + + // r-type + Op::Add(r) => simple_r!(r, |rs1, rs2| il.add(max_width, rs1, rs2)), + Op::Sll(r) => simple_r!(r, |rs1, rs2| il.lsl(max_width, rs1, rs2)), + Op::Slt(r) => simple_r!(r, |rs1, rs2| il + .bool_to_int(max_width, il.cmp_slt(max_width, rs1, rs2))), + Op::SltU(r) => simple_r!(r, |rs1, rs2| il + .bool_to_int(max_width, il.cmp_ult(max_width, rs1, rs2))), + Op::Xor(r) => simple_r!(r, |rs1, rs2| il.xor(max_width, rs1, rs2)), + Op::Srl(r) => simple_r!(r, |rs1, rs2| il.lsr(max_width, rs1, rs2)), + Op::Or(r) => simple_r!(r, |rs1, rs2| il.or(max_width, rs1, rs2)), + Op::And(r) => simple_r!(r, |rs1, rs2| il.and(max_width, rs1, rs2)), + Op::Sub(r) => simple_r!(r, |rs1, rs2| il.sub(max_width, rs1, rs2)), + Op::Sra(r) => simple_r!(r, |rs1, rs2| il.asr(max_width, rs1, rs2)), + + // i-type 32-bit + Op::AddIW(i) => simple_i!(i, |rs1, imm| il.sx(max_width, il.add(4, rs1, imm))), + Op::SllIW(i) => simple_i!(i, |rs1, imm| il.sx(max_width, il.lsl(4, rs1, imm))), + Op::SrlIW(i) => simple_i!(i, |rs1, imm| il.sx(max_width, il.lsr(4, rs1, imm))), + Op::SraIW(i) => simple_i!(i, |rs1, imm| il.sx(max_width, il.asr(4, rs1, imm))), + + // r-type 32-bit + Op::AddW(r) => simple_r!(r, |rs1, rs2| il.sx(max_width, il.add(4, rs1, rs2))), + Op::SllW(r) => simple_r!(r, |rs1, rs2| il.sx(max_width, il.lsl(4, rs1, rs2))), + Op::SrlW(r) => simple_r!(r, |rs1, rs2| il.sx(max_width, il.lsr(4, rs1, rs2))), + Op::SubW(r) => simple_r!(r, |rs1, rs2| il.sx(max_width, il.sub(4, rs1, rs2))), + Op::SraW(r) => simple_r!(r, |rs1, rs2| il.sx(max_width, il.asr(4, rs1, rs2))), + + Op::Mul(r) => simple_r!(r, |rs1, rs2| il.mul(max_width, rs1, rs2)), + /* + Op::MulH(r) => + Op::MulHU(r) => + Op::MulHSU(r) => + */ + Op::Div(r) => simple_r!(r, |rs1, rs2| il.divs(max_width, rs1, rs2)), + Op::DivU(r) => simple_r!(r, |rs1, rs2| il.divu(max_width, rs1, rs2)), + Op::Rem(r) => simple_r!(r, |rs1, rs2| il.mods(max_width, rs1, rs2)), + Op::RemU(r) => simple_r!(r, |rs1, rs2| il.modu(max_width, rs1, rs2)), + + Op::MulW(r) => simple_r!(r, |rs1, rs2| il.sx(max_width, il.mul(4, rs1, rs2))), + Op::DivW(r) => simple_r!(r, |rs1, rs2| il.sx(max_width, il.divs(4, rs1, rs2))), + Op::DivUW(r) => simple_r!(r, |rs1, rs2| il.sx(max_width, il.divu(4, rs1, rs2))), + Op::RemW(r) => simple_r!(r, |rs1, rs2| il.sx(max_width, il.mods(4, rs1, rs2))), + Op::RemUW(r) => simple_r!(r, |rs1, rs2| il.sx(max_width, il.modu(4, rs1, rs2))), + + Op::Lui(u) => simple_op!(u, il.const_int(max_width, u.imm() as i64 as u64)), + Op::Auipc(u) => simple_op!(u, il.const_ptr(addr.wrapping_add(u.imm() as i64 as u64))), + + Op::Jal(j) => { + let target = addr.wrapping_add(j.imm() as i64 as u64); + + match (j.rd().id(), il.label_for_address(target)) { + (0, Some(l)) => il.goto(l), + (0, None) => il.jump(il.const_ptr(target)), + (_, _) => il.call(il.const_ptr(target)), + } + .append(); + } + Op::Jalr(i) => { + let rd = i.rd(); + let rs1 = i.rs1(); + let imm = i.imm(); + + let target = il.add(max_width, Register::from(rs1), imm).build(); + + match (rd.id(), rs1.id(), imm) { + (0, 1, 0) => il.ret(target).append(), // jalr zero, ra, 0 + (1, _, _) => il.call(target).append(), // indirect call + (0, _, _) => il.jump(target).append(), // indirect jump + (_, _, _) => { + // indirect jump with storage of next address to non-`ra` register + il.set_reg( + max_width, + Register::from(rd), + il.const_ptr(addr.wrapping_add(inst_len)), + ) + .append(); + il.jump(target).append(); + } + } + } + + Op::Beq(b) | Op::Bne(b) | Op::Blt(b) | Op::Bge(b) | Op::BltU(b) | Op::BgeU(b) => { + let left = Register::from(b.rs1()); + let right = Register::from(b.rs2()); + + let cond_expr = match op { + Op::Beq(..) => il.cmp_e(max_width, left, right), + Op::Bne(..) => il.cmp_ne(max_width, left, right), + Op::Blt(..) => il.cmp_slt(max_width, left, right), + Op::Bge(..) => il.cmp_sge(max_width, left, right), + Op::BltU(..) => il.cmp_ult(max_width, left, right), + Op::BgeU(..) => il.cmp_uge(max_width, left, right), + _ => unreachable!(), + }; + + let mut new_false: Option<Label> = None; + let mut new_true: Option<Label> = None; + + let ft = addr.wrapping_add(inst_len); + let tt = addr.wrapping_add(b.imm() as i64 as u64); + + { + let f = il.label_for_address(ft).unwrap_or_else(|| { + new_false = Some(Label::new()); + new_false.as_ref().unwrap() + }); + + let t = il.label_for_address(tt).unwrap_or_else(|| { + new_true = Some(Label::new()); + new_true.as_ref().unwrap() + }); + + il.if_expr(cond_expr, t, f).append(); + } + + if let Some(t) = new_true.as_mut() { + il.mark_label(t); + + il.jump(il.const_ptr(tt)).append(); + } + + if let Some(f) = new_false.as_mut() { + il.mark_label(f); + } + } + + Op::Ecall => il.syscall().append(), + Op::Ebreak => il.bp().append(), + Op::Uret => { + il.intrinsic( + Lifter::<Self>::NO_OUTPUTS, + Intrinsic::Uret, + Lifter::<Self>::NO_INPUTS, + ) + .append(); + il.no_ret().append(); + } + Op::Sret => { + il.intrinsic( + Lifter::<Self>::NO_OUTPUTS, + Intrinsic::Sret, + Lifter::<Self>::NO_INPUTS, + ) + .append(); + il.no_ret().append(); + } + Op::Mret => { + il.intrinsic( + Lifter::<Self>::NO_OUTPUTS, + Intrinsic::Mret, + Lifter::<Self>::NO_INPUTS, + ) + .append(); + il.no_ret().append(); + } + Op::Wfi => il + .intrinsic( + Lifter::<Self>::NO_OUTPUTS, + Intrinsic::Wfi, + Lifter::<Self>::NO_INPUTS, + ) + .append(), + Op::Fence(i) => il + .intrinsic( + Lifter::<Self>::NO_OUTPUTS, + Intrinsic::Fence, + [il.const_int(4, i.imm() as u32 as u64)], + ) + .append(), + + Op::Csrrw(i) => { + let rd = Register::from(i.rd()); + let rs1 = Liftable::lift(il, Register::from(i.rs1())); + let csr = il.const_int(4, i.imm() as u64); + + if i.rd().id() == 0 { + il.intrinsic(Lifter::<Self>::NO_OUTPUTS, Intrinsic::Csrwr, [csr, rs1]) + .append(); + } else { + il.intrinsic([rd], Intrinsic::Csrrw, [rs1]).append(); + } + } + Op::Csrrs(i) => { + let rd = Register::from(i.rd()); + let rs1 = Liftable::lift(il, Register::from(i.rs1())); + let csr = il.const_int(4, i.imm() as u64); + + if i.rs1().id() == 0 { + il.intrinsic([rd], Intrinsic::Csrrd, [csr]).append(); + } else { + il.intrinsic([rd], Intrinsic::Csrrs, [csr, rs1]).append(); + } + } + Op::Csrrc(i) => { + let rd = Register::from(i.rd()); + let rs1 = Liftable::lift(il, Register::from(i.rs1())); + let csr = il.const_int(4, i.imm() as u64); + + if i.rs1().id() == 0 { + il.intrinsic([rd], Intrinsic::Csrrd, [csr]).append(); + } else { + il.intrinsic([rd], Intrinsic::Csrrc, [csr, rs1]).append(); + } + } + Op::CsrrwI(i) => { + let rd = Register::from(i.rd()); + let csr = il.const_int(4, i.csr() as u64); + let imm = il.const_int(max_width, i.imm() as u64); + + if i.rd().id() == 0 { + il.intrinsic(Lifter::<Self>::NO_OUTPUTS, Intrinsic::Csrwr, [csr, imm]) + .append(); + } else { + il.intrinsic([rd], Intrinsic::Csrrw, [csr, imm]).append(); + } + } + Op::CsrrsI(i) => { + let rd = Register::from(i.rd()); + let csr = il.const_int(4, i.csr() as u64); + let imm = il.const_int(max_width, i.imm() as u64); + + if i.imm() == 0 { + il.intrinsic([rd], Intrinsic::Csrrd, [csr]).append(); + } else { + il.intrinsic([rd], Intrinsic::Csrrs, [csr, imm]).append(); + } + } + Op::CsrrcI(i) => { + let rd = Register::from(i.rd()); + let csr = il.const_int(4, i.csr() as u64); + let imm = il.const_int(max_width, i.imm() as u64); + + if i.imm() == 0 { + il.intrinsic([rd], Intrinsic::Csrrd, [csr]).append(); + } else { + il.intrinsic([rd], Intrinsic::Csrrc, [csr, imm]).append(); + } + } + + Op::Lr(a) => simple_op!(a, no_discard { + let size = a.width(); + let load_expr = il.load(size, Register::from(a.rs1())) + .with_source_operand(1); + + match size == max_width { + true => load_expr, + false => il.sx(max_width, load_expr).build(), + } + }), + Op::Sc(a) => { + let size = a.width(); + let rd = a.rd(); + + let dest_reg = match rd.id() { + 0 => llil::Register::Temp(0), + _ => Register::from(rd).into(), + }; + + // set rd (or a temp register) to an indeterminate value, + // which signals to the application whether the conditional + // store was successful. by clobbering first, we can then + // emit conditionals based on its value to lift the conditional + // nature of the store -- dataflow will give up + il.set_reg(max_width, dest_reg, il.unimplemented()).append(); + + let mut new_false: Option<Label> = None; + let mut t = Label::new(); + + { + let cond_expr = il.cmp_e(max_width, dest_reg, 0u64); + + let ft = addr.wrapping_add(inst_len); + let f = il.label_for_address(ft).unwrap_or_else(|| { + new_false = Some(Label::new()); + new_false.as_ref().unwrap() + }); + + il.if_expr(cond_expr, &t, f).append(); + } + + il.mark_label(&mut t); + + il.store(size, Register::from(a.rs1()), Register::from(a.rs2())) + .with_source_operand(2) + .append(); + + if let Some(f) = new_false.as_mut() { + il.mark_label(f); + } + } + Op::AmoSwap(a) + | Op::AmoAdd(a) + | Op::AmoXor(a) + | Op::AmoAnd(a) + | Op::AmoOr(a) + | Op::AmoMin(a) + | Op::AmoMax(a) + | Op::AmoMinU(a) + | Op::AmoMaxU(a) => { + let size = a.width(); + let rd = a.rd(); + let rs1 = a.rs1(); + let rs2 = a.rs2(); + + let dest_reg = match rd.id() { + 0 => llil::Register::Temp(0), + _ => Register::from(rd).into(), + }; + + let mut next_temp_reg = 1; + let mut alloc_reg = |rs: riscv_dis::IntReg<D>| match (rs.id(), rd.id()) { + (id, r) if id != 0 && id == r => { + let reg = llil::Register::Temp(next_temp_reg); + next_temp_reg += 1; + + il.set_reg(max_width, reg, Register::from(rs)).append(); + + reg + } + _ => Register::from(rs).into(), + }; + + let reg_with_address = alloc_reg(rs1); + let reg_with_val = alloc_reg(rs2); + + let mut load_expr = il.load(size, Register::from(rs1)).with_source_operand(2); + + if size < max_width { + load_expr = il.sx(max_width, load_expr).build(); + } + + il.set_reg(max_width, dest_reg, load_expr).append(); + + let val_expr = LiftableWithSize::lift_with_size(il, reg_with_val, size); + let dest_reg_val = LiftableWithSize::lift_with_size(il, dest_reg, size); + + let val_to_store = match op { + Op::AmoSwap(..) => val_expr, + Op::AmoAdd(..) => il.add(size, dest_reg_val, val_expr).build(), + Op::AmoXor(..) => il.xor(size, dest_reg_val, val_expr).build(), + Op::AmoAnd(..) => il.and(size, dest_reg_val, val_expr).build(), + Op::AmoOr(..) => il.or(size, dest_reg_val, val_expr).build(), + Op::AmoMin(..) | Op::AmoMax(..) | Op::AmoMinU(..) | Op::AmoMaxU(..) => { + il.unimplemented() + } + _ => unreachable!(), + }; + + il.store(size, reg_with_address, val_to_store).append() + } + + Op::LoadFp(m) => { + let rd = Register::from(m.fr()); + let rs1 = Register::from(m.rs1()); + + let load_expr = il + .load(m.width(), il.add(max_width, rs1, m.imm())) + .with_source_operand(1); + + il.set_reg(m.width(), rd, load_expr).append(); + } + Op::StoreFp(m) => { + let rs1 = Register::from(m.rs1()); + let rs2 = Register::from(m.fr()); + + let dest_expr = il.add(max_width, rs1, m.imm()); + + il.store(m.width(), dest_expr, il.reg(m.width(), rs2)) + .with_source_operand(1) + .append(); + } + Op::Fmadd(f) | Op::Fmsub(f) | Op::Fnmadd(f) | Op::Fnmsub(f) => { + let rd = Register::from(f.rd()); + let rs1 = Register::from(f.rs1()); + let rs2 = Register::from(f.rs2()); + let rs3 = Register::from(f.rs2()); + let width = f.width() as usize; + if f.rm() == RoundMode::Dynamic { + let product = il.fmul(width, il.reg(width, rs1), il.reg(width, rs2)); + let result = match op { + Op::Fmadd(..) => il.fadd(width, product, il.reg(width, rs3)), + Op::Fmsub(..) => il.fsub(width, product, il.reg(width, rs3)), + Op::Fnmadd(..) => { + il.fsub(width, il.fneg(width, product), il.reg(width, rs3)) + } + Op::Fnmsub(..) => { + il.fadd(width, il.fneg(width, product), il.reg(width, rs3)) + } + _ => unreachable!(), + }; + il.set_reg(width, rd, result).append(); + } else { + let product = llil::Register::Temp(0); + il.intrinsic( + [product], + Intrinsic::Fmul(f.width(), f.rm()), + [il.reg(width, rs1), il.reg(width, rs2)], + ) + .append(); + match op { + Op::Fmadd(..) => il + .intrinsic( + [rd], + Intrinsic::Fmul(f.width(), f.rm()), + [il.reg(width, product), il.reg(width, rs3)], + ) + .append(), + Op::Fmsub(..) => il + .intrinsic( + [rd], + Intrinsic::Fsub(f.width(), f.rm()), + [il.reg(width, product), il.reg(width, rs3)], + ) + .append(), + Op::Fnmadd(..) => il + .intrinsic( + [rd], + Intrinsic::Fsub(f.width(), f.rm()), + [ + il.fneg(width, il.reg(width, product)).build(), + il.reg(width, rs3), + ], + ) + .append(), + Op::Fnmsub(..) => il + .intrinsic( + [rd], + Intrinsic::Fadd(f.width(), f.rm()), + [ + il.fneg(width, il.reg(width, product)).build(), + il.reg(width, rs3), + ], + ) + .append(), + _ => unreachable!(), + }; + } + } + Op::Fadd(f) | Op::Fsub(f) | Op::Fmul(f) | Op::Fdiv(f) => { + let rd = Register::from(f.rd()); + let rs1 = Register::from(f.rs1()); + let rs2 = Register::from(f.rs2()); + let width = f.width() as usize; + if f.rm() == RoundMode::Dynamic { + let result = match op { + Op::Fadd(..) => il.fadd(width, il.reg(width, rs1), il.reg(width, rs2)), + Op::Fsub(..) => il.fsub(width, il.reg(width, rs1), il.reg(width, rs2)), + Op::Fmul(..) => il.fmul(width, il.reg(width, rs1), il.reg(width, rs2)), + Op::Fdiv(..) => il.fdiv(width, il.reg(width, rs1), il.reg(width, rs2)), + _ => unreachable!(), + }; + il.set_reg(width, rd, result).append(); + } else { + let intrinsic = match op { + Op::Fadd(..) => Intrinsic::Fadd(f.width(), f.rm()), + Op::Fsub(..) => Intrinsic::Fsub(f.width(), f.rm()), + Op::Fmul(..) => Intrinsic::Fmul(f.width(), f.rm()), + Op::Fdiv(..) => Intrinsic::Fdiv(f.width(), f.rm()), + _ => unreachable!(), + }; + il.intrinsic([rd], intrinsic, [il.reg(width, rs1), il.reg(width, rs2)]) + .append(); + } + } + Op::Fsgnj(f) => { + let rd = Register::from(f.rd()); + let rs1 = Register::from(f.rs1()); + let rs2 = Register::from(f.rs2()); + let width = f.width() as usize; + if f.rs1().id() == f.rs2().id() { + il.set_reg(width, rd, il.reg(width, rs1)).append(); + } else { + il.intrinsic( + [rd], + Intrinsic::Fsgnj(f.width()), + [il.reg(width, rs1), il.reg(width, rs2)], + ) + .append(); + } + } + Op::Fsgnjn(f) => { + let rd = Register::from(f.rd()); + let rs1 = Register::from(f.rs1()); + let rs2 = Register::from(f.rs2()); + let width = f.width() as usize; + if f.rs1().id() == f.rs2().id() { + il.set_reg(width, rd, il.fneg(width, il.reg(width, rs1))) + .append(); + } else { + il.intrinsic( + [rd], + Intrinsic::Fsgnjn(f.width()), + [il.reg(width, rs1), il.reg(width, rs2)], + ) + .append(); + } + } + Op::Fsgnjx(f) => { + let rd = Register::from(f.rd()); + let rs1 = Register::from(f.rs1()); + let rs2 = Register::from(f.rs2()); + let width = f.width() as usize; + if f.rs1().id() == f.rs2().id() { + il.set_reg(width, rd, il.fabs(width, il.reg(width, rs1))) + .append(); + } else { + il.intrinsic( + [rd], + Intrinsic::Fsgnjx(f.width()), + [il.reg(width, rs1), il.reg(width, rs2)], + ) + .append(); + } + } + Op::Fsqrt(f) => { + let rd = Register::from(f.rd()); + let rs1 = Register::from(f.rs1()); + let width = f.width() as usize; + let result = il.fsqrt(width, il.reg(width, rs1)); + il.set_reg(width, rd, result).append(); + } + Op::Fmin(f) => { + let rd = Register::from(f.rd()); + let rs1 = Register::from(f.rs1()); + let rs2 = Register::from(f.rs2()); + let width = f.width() as usize; + il.intrinsic( + [rd], + Intrinsic::Fmin(f.width()), + [il.reg(width, rs1), il.reg(width, rs2)], + ) + .append(); + } + Op::Fmax(f) => { + let rd = Register::from(f.rd()); + let rs1 = Register::from(f.rs1()); + let rs2 = Register::from(f.rs2()); + let width = f.width() as usize; + il.intrinsic( + [rd], + Intrinsic::Fmax(f.width()), + [il.reg(width, rs1), il.reg(width, rs2)], + ) + .append(); + } + Op::Fle(f) | Op::Flt(f) | Op::Feq(f) => { + let rd = match f.rd().id() { + 0 => llil::Register::Temp(0), + _ => Register::from(f.rd()).into(), + }; + let left = Register::from(f.rs1()); + let right = Register::from(f.rs2()); + let width = f.width() as usize; + let cond_expr = match op { + Op::Fle(..) => il.fcmp_le(width, il.reg(width, left), il.reg(width, right)), + Op::Flt(..) => il.fcmp_lt(width, il.reg(width, left), il.reg(width, right)), + Op::Feq(..) => il.fcmp_e(width, il.reg(width, left), il.reg(width, right)), + _ => unreachable!(), + }; + let result = il.bool_to_int(max_width, cond_expr); + il.set_reg(max_width, rd, result).append(); + } + Op::Fcvt(f) => { + let rd = Register::from(f.rd()); + let rs1 = Register::from(f.rs1()); + let rd_width = f.rd_width() as usize; + let rs1_width = f.rs1_width() as usize; + if f.rm() == RoundMode::Dynamic { + let src = il.float_conv(rd_width, il.reg(rs1_width, rs1)); + il.set_reg(rd_width, rd, src).append(); + } else { + il.intrinsic( + [rd], + Intrinsic::FcvtFToF(f.rs1_width(), f.rd_width(), f.rm()), + [il.reg(rs1_width, rs1)], + ) + .append(); + } + } + Op::FcvtToInt(f) => { + let rd = match f.rd().id() { + 0 => llil::Register::Temp(0), + _ => Register::from(f.rd()).into(), + }; + let rs1 = Register::from(f.rs1()); + let rd_width = f.rd_width() as usize; + let rs1_width = f.rs1_width() as usize; + if f.zx() { + il.intrinsic( + [rd], + Intrinsic::FcvtFToU(f.rs1_width(), f.rd_width(), f.rm()), + [il.reg(rs1_width, rs1)], + ) + .append(); + } else if f.rm() != RoundMode::Dynamic { + il.intrinsic( + [rd], + Intrinsic::FcvtFToI(f.rs1_width(), f.rd_width(), f.rm()), + [il.reg(rs1_width, rs1)], + ) + .append(); + } else { + let conv = il.float_to_int(rd_width, il.reg(rs1_width, rs1)); + let src = if rd_width < max_width { + il.sx(max_width, conv) + } else { + conv + }; + il.set_reg(max_width, rd, src).append(); + } + } + Op::FcvtFromInt(f) => { + let rd = Register::from(f.rd()); + let rs1 = Register::from(f.rs1()); + let rd_width = f.rd_width() as usize; + let rs1_width = f.rs1_width() as usize; + let rs1 = LiftableWithSize::lift_with_size(il, rs1, rs1_width); + if f.zx() { + il.intrinsic( + [rd], + Intrinsic::FcvtUToF(f.rs1_width(), f.rd_width(), f.rm()), + [rs1], + ) + .append(); + } else if f.rm() != RoundMode::Dynamic { + il.intrinsic( + [rd], + Intrinsic::FcvtIToF(f.rs1_width(), f.rd_width(), f.rm()), + [rs1], + ) + .append(); + } else { + il.set_reg(rd_width, rd, il.int_to_float(rd_width, rs1)) + .append(); + } + } + Op::FmvToInt(f) => { + let rd = match f.rd().id() { + 0 => llil::Register::Temp(0), + _ => Register::from(f.rd()).into(), + }; + let rs1 = Register::from(f.rs1()); + let width = f.width() as usize; + let src = if width < max_width { + il.sx(max_width, il.reg(width, rs1)).build() + } else { + il.reg(width, rs1) + }; + il.set_reg(max_width, rd, src).append(); + } + Op::FmvFromInt(f) => { + let rd = Register::from(f.rd()); + let rs1 = Register::from(f.rs1()); + let width = f.width() as usize; + let rs1 = LiftableWithSize::lift_with_size(il, rs1, width); + il.set_reg(width, rd, rs1).append(); + } + Op::Fclass(f) => { + let rd = Register::from(f.rd()); + let rs1 = Register::from(f.rs1()); + let width = f.width() as usize; + il.intrinsic([rd], Intrinsic::Fclass(f.width()), [il.reg(width, rs1)]) + .append(); + } + + _ => il.unimplemented().append(), + }; + + Some((inst_len as usize, true)) + } + + fn registers_all(&self) -> Vec<Self::Register> { + let mut reg_count = <D::RegFile as RegFile>::int_reg_count(); + + if <D::RegFile as RegFile>::Float::present() { + reg_count += 32; + } + + let mut res = Vec::with_capacity(reg_count as usize); + + for i in 0..reg_count { + res.push(Register::new(i)); + } + + res + } + + fn registers_full_width(&self) -> Vec<Self::Register> { + self.registers_all() + } + + fn registers_global(&self) -> Vec<Self::Register> { + let mut regs = Vec::with_capacity(2); + + for i in &[3, 4] { + regs.push(Register::new(*i)); + } + + regs + } + + fn stack_pointer_reg(&self) -> Option<Self::Register> { + Some(Register::new(2)) + } + + fn link_reg(&self) -> Option<Self::Register> { + Some(Register::new(1)) + } + + fn register_from_id(&self, id: u32) -> Option<Self::Register> { + let mut reg_count = <D::RegFile as RegFile>::int_reg_count(); + + if <D::RegFile as RegFile>::Float::present() { + reg_count += 32; + } + + if id > reg_count { + None + } else { + Some(Register::new(id)) + } + } + + fn intrinsics(&self) -> Vec<Self::Intrinsic> { + let mut res = Vec::new(); + + res.extend_from_slice(&[ + Intrinsic::Uret, + Intrinsic::Sret, + Intrinsic::Mret, + Intrinsic::Wfi, + Intrinsic::Csrrw, + Intrinsic::Csrwr, + Intrinsic::Csrrd, + Intrinsic::Csrrs, + Intrinsic::Csrrc, + ]); + + if <D::RegFile as RegFile>::Float::present() { + let mut float_sizes = vec![4]; + if <D::RegFile as RegFile>::Float::width() >= 8 { + float_sizes.push(8); + } + if <D::RegFile as RegFile>::Float::width() >= 16 { + float_sizes.push(16); + } + let mut int_sizes = vec![4]; + if <D::RegFile as RegFile>::Int::width() >= 8 { + int_sizes.push(8); + } + + for fsize in &float_sizes { + res.extend_from_slice(&[ + Intrinsic::Fsgnj(*fsize), + Intrinsic::Fsgnjn(*fsize), + Intrinsic::Fsgnjx(*fsize), + Intrinsic::Fmin(*fsize), + Intrinsic::Fmax(*fsize), + Intrinsic::Fclass(*fsize), + ]); + for rm in RoundMode::all() { + if *rm != RoundMode::Dynamic { + res.extend_from_slice(&[ + Intrinsic::Fadd(*fsize, *rm), + Intrinsic::Fsub(*fsize, *rm), + Intrinsic::Fmul(*fsize, *rm), + Intrinsic::Fdiv(*fsize, *rm), + Intrinsic::Fsqrt(*fsize, *rm), + ]); + } + } + for dsize in &float_sizes { + if fsize != dsize { + for rm in RoundMode::all() { + if *rm != RoundMode::Dynamic { + res.push(Intrinsic::FcvtFToF(*fsize, *dsize, *rm)); + } + } + } + } + for isize in &int_sizes { + for rm in RoundMode::all() { + res.push(Intrinsic::FcvtFToU(*fsize, *isize, *rm)); + res.push(Intrinsic::FcvtUToF(*isize, *fsize, *rm)); + if *rm != RoundMode::Dynamic { + res.push(Intrinsic::FcvtFToI(*fsize, *isize, *rm)); + res.push(Intrinsic::FcvtIToF(*isize, *fsize, *rm)); + } + } + } + } + } + + res.iter().map(|i| (*i).into()).collect() + } + + fn intrinsic_from_id(&self, id: u32) -> Option<Self::Intrinsic> { + RiscVIntrinsic::from_id(id) + } + + fn can_assemble(&self) -> bool { + true + } + + fn assemble(&self, code: &str, _addr: u64) -> Result<Vec<u8>, String> { + // FIXME: This does not support any instructions outside the very basic RV32I/RV64I instruction set. + // It is completely undocumented how to tell LLVM to accept the additional extensions, and may + // require core changes to enable. + let arch_triple = if <D::RegFile as RegFile>::Int::width() == 4 { + "riscv32-none-none" + } else { + "riscv64-none-none" + }; + + llvm_assemble( + code, + LlvmServicesDialect::Unspecified, + arch_triple, + LlvmServicesCodeModel::Default, + LlvmServicesRelocMode::Static, + ) + } + + fn is_never_branch_patch_available(&self, data: &[u8], addr: u64) -> bool { + let op = match D::decode(addr, data) { + Ok(Instr::Rv16(op)) => op, + Ok(Instr::Rv32(op)) => op, + _ => return false, + }; + + match op { + Op::Beq(_) | Op::Bne(_) | Op::Blt(_) | Op::Bge(_) | Op::BltU(_) | Op::BgeU(_) => true, + _ => false, + } + } + + fn is_always_branch_patch_available(&self, data: &[u8], addr: u64) -> bool { + self.is_never_branch_patch_available(data, addr) + } + + fn is_invert_branch_patch_available(&self, data: &[u8], addr: u64) -> bool { + self.is_never_branch_patch_available(data, addr) + } + + fn is_skip_and_return_zero_patch_available(&self, data: &[u8], addr: u64) -> bool { + let op = match D::decode(addr, data) { + Ok(Instr::Rv16(op)) => op, + Ok(Instr::Rv32(op)) => op, + _ => return false, + }; + + match op { + Op::Jal(ref j) if j.rd().id() != 0 => true, + Op::Jalr(ref j) if j.rd().id() != 0 => true, + _ => false, + } + } + + fn is_skip_and_return_value_patch_available(&self, data: &[u8], addr: u64) -> bool { + self.is_skip_and_return_zero_patch_available(data, addr) + } + + fn convert_to_nop(&self, mut data: &mut [u8], _addr: u64) -> bool { + if data.len() & 1 != 0 { + // If not aligned on 16 bit boundary, can't convert to nop + return false; + } + + while data.len() > 0 { + if data.len() >= 4 { + // If more than 4 bytes left, use uncompressed nop + data[0..4].copy_from_slice(&[0x13, 0x00, 0x00, 0x00]); + data = data[4..].as_mut(); + } else { + // If only 2 bytes left, use compressed nop + data[0..2].copy_from_slice(&[0x01, 0x00]); + data = data[2..].as_mut(); + } + } + + true + } + + fn always_branch(&self, data: &mut [u8], addr: u64) -> bool { + let op = match D::decode(addr, data) { + Ok(Instr::Rv16(_)) => return false, + Ok(Instr::Rv32(op)) => op, + _ => return false, + }; + + if data.len() < 4 { + return false; + } + + match op { + Op::Beq(ref b) + | Op::Bne(ref b) + | Op::Blt(ref b) + | Op::Bge(ref b) + | Op::BltU(ref b) + | Op::BgeU(ref b) => { + let offset = b.imm() as u32; + let opcode = ((offset >> 20) & 1) << 31 + | ((offset >> 1) & 0x3ff) << 21 + | ((offset >> 11) & 1) << 20 + | ((offset >> 12) & 0xff) << 12 + | 0b1101111; + data[0..4].copy_from_slice(&opcode.to_le_bytes()); + true + } + _ => false, + } + } + + fn invert_branch(&self, data: &mut [u8], addr: u64) -> bool { + let op = match D::decode(addr, data) { + Ok(Instr::Rv16(_)) => return false, + Ok(Instr::Rv32(op)) => op, + _ => return false, + }; + + if data.len() < 4 { + return false; + } + + match op { + Op::Beq(_) | Op::Bne(_) | Op::Blt(_) | Op::Bge(_) | Op::BltU(_) | Op::BgeU(_) => { + data[1] ^= 0x10; + true + } + _ => false, + } + } + + fn skip_and_return_value(&self, data: &mut [u8], addr: u64, value: u64) -> bool { + let (instr_len, op) = match D::decode(addr, data) { + Ok(Instr::Rv16(op)) => (2, op), + Ok(Instr::Rv32(op)) => (4, op), + _ => return false, + }; + + if data.len() < instr_len { + return false; + } + + let valid = match op { + Op::Jal(ref j) if j.rd().id() != 0 => true, + Op::Jalr(ref j) if j.rd().id() != 0 => true, + _ => false, + }; + if !valid { + return false; + } + + let signed_value = if <D::RegFile as RegFile>::Int::width() == 4 { + value as i32 as i64 + } else { + value as i64 + }; + + match instr_len { + 2 => { + if signed_value < -0x20 || signed_value > 0x1f { + return false; + } + let opcode = (0b010 << 13) + | (signed_value as u16 & 0x10) << 7 + | (0b01010 << 7) + | (signed_value as u16 & 0xf) << 2 + | 0b01; + data[0..2].copy_from_slice(&opcode.to_le_bytes()); + } + 4 => { + if signed_value < -0x800 || signed_value > 0x7ff { + return false; + } + let opcode = (signed_value as u32 & 0xfff) << 20 | 0b00000_000_01010_0010011; + data[0..4].copy_from_slice(&opcode.to_le_bytes()); + } + _ => unreachable!(), + } + + true + } + + fn handle(&self) -> CustomArchitectureHandle<Self> { + self.custom_handle + } +} + +impl<D: 'static + RiscVDisassembler + Send + Sync> AsRef<CoreArchitecture> for RiscVArch<D> { + fn as_ref(&self) -> &CoreArchitecture { + &self.handle + } +} + +struct RiscVELFRelocationHandler<D: 'static + RiscVDisassembler + Send + Sync> { + handle: CoreRelocationHandler, + custom_handle: CustomRelocationHandlerHandle<Self>, + _dis: PhantomData<D>, +} + +impl<D: 'static + RiscVDisassembler + Send + Sync> RiscVELFRelocationHandler<D> { + const R_RISCV_NONE: u64 = 0; + const R_RISCV_32: u64 = 1; + const R_RISCV_64: u64 = 2; + const R_RISCV_RELATIVE: u64 = 3; + const R_RISCV_COPY: u64 = 4; + const R_RISCV_JUMP_SLOT: u64 = 5; +} + +impl<D: 'static + RiscVDisassembler + Send + Sync> RelocationHandler + for RiscVELFRelocationHandler<D> +{ + type Handle = CustomRelocationHandlerHandle<Self>; + + fn get_relocation_info( + &self, + _bv: &BinaryView, + _arch: &CoreArchitecture, + info: &mut [RelocationInfo], + ) -> bool { + for reloc in info.iter_mut() { + reloc.type_ = RelocationType::StandardRelocationType; + match reloc.native_type { + Self::R_RISCV_NONE => reloc.type_ = RelocationType::IgnoredRelocation, + Self::R_RISCV_32 => { + reloc.pc_relative = false; + reloc.base_relative = false; + reloc.has_sign = false; + reloc.size = 4; + reloc.truncate_size = 4; + } + Self::R_RISCV_64 => { + reloc.pc_relative = false; + reloc.base_relative = false; + reloc.has_sign = false; + reloc.size = 8; + reloc.truncate_size = 8; + } + Self::R_RISCV_RELATIVE => { + reloc.pc_relative = false; + reloc.base_relative = true; + reloc.has_sign = false; + reloc.size = <D::RegFile as RegFile>::Int::width(); + reloc.truncate_size = <D::RegFile as RegFile>::Int::width(); + } + Self::R_RISCV_COPY => { + reloc.type_ = RelocationType::ELFCopyRelocationType; + reloc.size = <D::RegFile as RegFile>::Int::width(); + } + Self::R_RISCV_JUMP_SLOT => { + reloc.type_ = RelocationType::ELFJumpSlotRelocationType; + reloc.size = <D::RegFile as RegFile>::Int::width(); + } + _ => { + reloc.type_ = RelocationType::UnhandledRelocation; + log::warn!( + "Unknown relocation type {:?} at {:x?}", + reloc.native_type, + reloc.address + ) + } + } + } + true + } + + fn handle(&self) -> Self::Handle { + self.custom_handle + } +} + +impl<D: 'static + RiscVDisassembler + Send + Sync> AsRef<CoreRelocationHandler> + for RiscVELFRelocationHandler<D> +{ + fn as_ref(&self) -> &CoreRelocationHandler { + &self.handle + } +} + +struct RiscVCC<D: 'static + RiscVDisassembler + Send + Sync> { + _dis: PhantomData<D>, +} + +impl<D: 'static + RiscVDisassembler + Send + Sync> RiscVCC<D> { + fn new() -> Self { + RiscVCC { _dis: PhantomData } + } +} + +impl<D: 'static + RiscVDisassembler + Send + Sync> CallingConventionBase for RiscVCC<D> { + type Arch = RiscVArch<D>; + + fn caller_saved_registers(&self) -> Vec<Register<D>> { + let mut regs = Vec::with_capacity(36); + let int_reg_count = <D::RegFile as RegFile>::int_reg_count(); + + for i in &[ + 1u32, 5, 6, 7, 10, 11, 12, 13, 14, 15, 16, 17, 28, 29, 30, 31, + ] { + if i < &int_reg_count { + regs.push(Register::new(*i)); + } + } + + if <D::RegFile as RegFile>::Float::present() { + for i in &[ + 0u32, 1, 2, 3, 4, 5, 6, 7, 10, 11, 12, 13, 14, 15, 16, 17, 28, 29, 30, 31, + ] { + regs.push(Register::new(*i + int_reg_count)); + } + } + + regs + } + + fn callee_saved_registers(&self) -> Vec<Register<D>> { + let mut regs = Vec::with_capacity(24); + let int_reg_count = <D::RegFile as RegFile>::int_reg_count(); + + for i in &[8u32, 9, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27] { + if i < &int_reg_count { + regs.push(Register::new(*i)); + } + } + + if <D::RegFile as RegFile>::Float::present() { + for i in &[8u32, 9, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27] { + regs.push(Register::new(*i + int_reg_count)); + } + } + + regs + } + + fn int_arg_registers(&self) -> Vec<Register<D>> { + let mut regs = Vec::with_capacity(8); + let int_reg_count = <D::RegFile as RegFile>::int_reg_count(); + + for i in &[10, 11, 12, 13, 14, 15, 16, 17] { + if i < &int_reg_count { + regs.push(Register::new(*i)); + } + } + + regs + } + + fn float_arg_registers(&self) -> Vec<Register<D>> { + let mut regs = Vec::with_capacity(8); + + if <D::RegFile as RegFile>::Float::present() { + let int_reg_count = <D::RegFile as RegFile>::int_reg_count(); + for i in &[10, 11, 12, 13, 14, 15, 16, 17] { + regs.push(Register::new(*i + int_reg_count)); + } + } + + regs + } + + fn arg_registers_shared_index(&self) -> bool { + false + } + + fn reserved_stack_space_for_arg_registers(&self) -> bool { + false + } + fn stack_adjusted_on_return(&self) -> bool { + false + } + + fn is_eligible_for_heuristics(&self) -> bool { + true + } + + // a0 == x10 + fn return_int_reg(&self) -> Option<Register<D>> { + Some(Register::new(10)) + } + // a1 == x11 + fn return_hi_int_reg(&self) -> Option<Register<D>> { + Some(Register::new(11)) + } + + fn return_float_reg(&self) -> Option<Register<D>> { + if <D::RegFile as RegFile>::Float::present() { + let int_reg_count = <D::RegFile as RegFile>::int_reg_count(); + Some(Register::new(10 + int_reg_count)) + } else { + None + } + } + + // gp == x3 + fn global_pointer_reg(&self) -> Option<Register<D>> { + Some(Register::new(3)) + } + + fn implicitly_defined_registers(&self) -> Vec<Register<D>> { + Vec::new() + } + fn are_argument_registers_used_for_var_args(&self) -> bool { + true + } +} + +struct RiscVELFPLTRecognizer; + +impl FunctionRecognizer for RiscVELFPLTRecognizer { + fn recognize_low_level_il( + &self, + bv: &BinaryView, + func: &Function, + llil: &llil::RegularFunction<CoreArchitecture>, + ) -> bool { + // Look for the following code pattern: + // t3 = plt + // t3 = [t3 + pltoffset] || t3 = [t3] + // t1 = next instruction + // jump(t3) + + if llil.instruction_count() < 4 { + return false; + } + + // Match instruction that fetches PC-relative PLT address range + let auipc = llil.instruction_from_idx(0).info(); + let (auipc_dest, plt_base) = match auipc { + InstrInfo::SetReg(r) => { + let value = match r.source_expr().info() { + ExprInfo::Const(v) | ExprInfo::ConstPtr(v) => v.value(), + _ => return false, + }; + (r.dest_reg(), value) + } + _ => return false, + }; + + // Match load instruction that loads the imported address + let load = llil.instruction_from_idx(1).info(); + let (mut entry, target_reg) = match load { + InstrInfo::SetReg(r) => match r.source_expr().info() { + ExprInfo::Load(l) => { + let target_reg = r.dest_reg(); + let entry = match l.source_mem_expr().info() { + ExprInfo::Reg(lr) if lr.source_reg() == auipc_dest => plt_base, + ExprInfo::Add(a) => match (a.left().info(), a.right().info()) { + (ExprInfo::Reg(a), ExprInfo::Const(b) | ExprInfo::ConstPtr(b)) + if a.source_reg() == auipc_dest => + { + plt_base.wrapping_add(b.value()) + } + (ExprInfo::Const(b) | ExprInfo::ConstPtr(b), ExprInfo::Reg(a)) + if a.source_reg() == auipc_dest => + { + plt_base.wrapping_add(b.value()) + } + _ => return false, + }, + ExprInfo::Sub(a) => match (a.left().info(), a.right().info()) { + (ExprInfo::Reg(a), ExprInfo::Const(b) | ExprInfo::ConstPtr(b)) + if a.source_reg() == auipc_dest => + { + plt_base.wrapping_sub(b.value()) + } + _ => return false, + }, + _ => return false, + }; + (entry, target_reg) + } + _ => return false, + }, + _ => return false, + }; + if func.arch().address_size() == 4 { + entry = entry as u32 as u64; + } + + // Ensure that load is pointing at an import address + let sym = match bv.symbol_by_address(entry) { + Ok(sym) => sym, + Err(_) => return false, + }; + if sym.sym_type() != SymbolType::ImportAddress { + return false; + } + + // Match instruction that stores the next instruction address into a register + let next_pc_inst = llil.instruction_from_idx(2).info(); + let (next_pc_dest, next_pc, cur_pc) = match next_pc_inst { + InstrInfo::SetReg(r) => { + let value = match r.source_expr().info() { + ExprInfo::Const(v) | ExprInfo::ConstPtr(v) => v.value(), + _ => return false, + }; + (r.dest_reg(), value, r.address()) + } + _ => return false, + }; + if next_pc != cur_pc + 4 || next_pc_dest == target_reg { + return false; + } + + // Match tail call at the end and make sure it is going to the import + let jump = llil.instruction_from_idx(3).info(); + match jump { + InstrInfo::TailCall(j) => { + match j.target().info() { + ExprInfo::Reg(r) if r.source_reg() == target_reg => (), + _ => return false, + }; + } + InstrInfo::Jump(j) => { + match j.target().info() { + ExprInfo::Reg(r) if r.source_reg() == target_reg => (), + _ => return false, + }; + } + _ => return false, + } + + let func_sym = + Symbol::imported_function_from_import_address_symbol(sym.as_ref(), func.start()); + bv.define_auto_symbol(func_sym.as_ref()); + func.apply_imported_types(func_sym.as_ref(), None); + true + } +} + +#[no_mangle] +#[allow(non_snake_case)] +pub extern "C" fn CorePluginInit() -> bool { + binaryninja::logger::init(log::LevelFilter::Trace).expect("Failed to set up logging"); + + use riscv_dis::{RiscVIMACDisassembler, Rv32GRegs, Rv64GRegs}; + let arch32 = + architecture::register_architecture("rv32gc", |custom_handle, core_arch| RiscVArch::< + RiscVIMACDisassembler<Rv32GRegs>, + > { + handle: core_arch, + custom_handle, + _dis: PhantomData, + }); + let arch64 = + architecture::register_architecture("rv64gc", |custom_handle, core_arch| RiscVArch::< + RiscVIMACDisassembler<Rv64GRegs>, + > { + handle: core_arch, + custom_handle, + _dis: PhantomData, + }); + + arch32.register_relocation_handler("ELF", |custom_handle, core_handler| { + RiscVELFRelocationHandler::<RiscVIMACDisassembler<Rv32GRegs>> { + handle: core_handler, + custom_handle, + _dis: PhantomData, + } + }); + arch64.register_relocation_handler("ELF", |custom_handle, core_handler| { + RiscVELFRelocationHandler::<RiscVIMACDisassembler<Rv64GRegs>> { + handle: core_handler, + custom_handle, + _dis: PhantomData, + } + }); + + arch32.register_function_recognizer(RiscVELFPLTRecognizer); + arch64.register_function_recognizer(RiscVELFPLTRecognizer); + + let cc32 = register_calling_convention(arch32, "default", RiscVCC::new()); + arch32.set_default_calling_convention(&cc32); + let cc64 = register_calling_convention(arch64, "default", RiscVCC::new()); + arch64.set_default_calling_convention(&cc64); + + if let Ok(bvt) = BinaryViewType::by_name("ELF") { + bvt.register_arch( + (1 << 16) | 243, + binaryninja::Endianness::LittleEndian, + arch32, + ); + bvt.register_arch( + (2 << 16) | 243, + binaryninja::Endianness::LittleEndian, + arch64, + ); + } + + let plat32 = arch32.standalone_platform().unwrap(); + let plat64 = arch64.standalone_platform().unwrap(); + + let syscall_cc32 = ConventionBuilder::new(arch32) + .caller_saved_registers(&[ + "ra", "t0", "t1", "t2", "t3", "t4", "t5", "t6", "a0", "a1", "a2", "a3", "a4", "a5", + "a6", "a7", + ]) + .int_arg_registers(&["a7", "a0", "a1", "a2", "a3", "a4", "a5", "a6"]) + .return_int_reg("a0") + .return_hi_int_reg("a1") + .global_pointer_reg("gp") + .implicitly_defined_registers(&["gp", "tp"]) + .register("syscall"); + let syscall_cc64 = ConventionBuilder::new(arch64) + .caller_saved_registers(&[ + "ra", "t0", "t1", "t2", "t3", "t4", "t5", "t6", "a0", "a1", "a2", "a3", "a4", "a5", + "a6", "a7", + ]) + .int_arg_registers(&["a7", "a0", "a1", "a2", "a3", "a4", "a5", "a6"]) + .return_int_reg("a0") + .return_hi_int_reg("a1") + .global_pointer_reg("gp") + .implicitly_defined_registers(&["gp", "tp"]) + .register("syscall"); + + plat32.set_syscall_convention(&syscall_cc32); + plat64.set_syscall_convention(&syscall_cc64); + + true +} + +#[no_mangle] +#[allow(non_snake_case)] +pub extern "C" fn CorePluginDependencies() { + add_optional_plugin_dependency("view_elf"); +} diff --git a/arch/riscv/src/liftcheck.rs b/arch/riscv/src/liftcheck.rs new file mode 100644 index 00000000..f59b25e5 --- /dev/null +++ b/arch/riscv/src/liftcheck.rs @@ -0,0 +1,427 @@ +use binaryninja::architecture::{Architecture, CoreArchitecture, Flag, Register, RegisterInfo}; +use binaryninja::binaryview::{BinaryView, BinaryViewExt}; +use binaryninja::command; +use binaryninja::function::Function; +use binaryninja::llil; +use binaryninja::llil::{ + Expression, Finalized, Instruction, LiftedNonSSA, NonSSA, ValueExpr, VisitorAction, +}; + +fn check_expression(expr: &Expression<CoreArchitecture, Finalized, NonSSA<LiftedNonSSA>, ValueExpr>, + required_size: Option<usize>) +{ + use llil::ExprInfo::*; + + match expr.info() { + Reg(ref op) => { + let size = op.size(); + + if let Some(required_size) = required_size { + if required_size != size { + error!("LLIL_REG op gives a {} byte value where {} bytes are expected! (addr: {:x} {:?})", + size, required_size, op.address(), expr); + } + } + + if let llil::Register::ArchReg(r) = op.source_reg() { + let reg_size = r.info().size(); + + if reg_size != size { + error!("LLIL_REG attempting to load {} bytes out of a {} byte register! (addr: {:x} {:?})", + size, reg_size, op.address(), expr); + } + } + } + + Flag(ref op) => { + if let Some(required_size) = required_size { + if required_size != 0 { + error!("LLIL_FLAG op gives a boolean value where {} bytes are expected! (addr: {:x} {:?})", + required_size, op.address(), expr); + } + } + } + + FlagBit(ref op) => { + // TODO + } + + Load(ref op) => { + if let Some(required_size) = required_size { + if required_size != op.size() { + error!("LLIL_LOAD op gives a {} byte value where {} bytes are expected! (addr: {:x} {:?})", + op.size(), required_size, op.address(), expr); + } + } + } + + Pop(ref op) => { + if let Some(required_size) = required_size { + if required_size != op.size() { + error!("LLIL_POP op gives a {} byte value where {} bytes are expected! (addr: {:x} {:?})", + op.size(), required_size, op.address(), expr); + } + } + } + + Const(ref op) | ConstPtr(ref op) => { + if let Some(required_size) = required_size { + if required_size != op.size() { + error!("LLIL_CONST op gives a {} byte value where {} bytes are expected! (addr: {:x} {:?})", + op.size(), required_size, op.address(), expr); + } + } + } + + FlagCond(ref op) => { + if let Some(required_size) = required_size { + if required_size != 0 { + error!("LLIL_FLAG_COND op gives boolean value where {} bytes are expected! (addr: {:x} {:?})", + required_size, op.address(), expr); + } + } + } + + 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) => { + if let Some(required_size) = required_size { + if required_size != 0 { + error!("LLIL_CMP ops produce a boolean value, and a {} byte value is expected here! (addr: {:x} {:?})", + required_size, op.address(), expr); + } + } + + let cmp_size = op.size(); + if cmp_size == 0 { + error!("compare of zero width detected! {:?}", expr); + } + + check_expression(&op.left(), Some(cmp_size)); + check_expression(&op.right(), Some(cmp_size)); + } + + Adc(ref op) | + Sbb(ref op) => { + let op_size = op.size(); + + if let Some(required_size) = required_size { + if required_size != op_size { + error!("LLIL_ADC/SBB producing {} byte value {} byte value is expected! (addr: {:x} {:?})", + op_size, required_size, op.address(), expr); + } + } + + check_expression(&op.left(), Some(op_size)); + check_expression(&op.right(), Some(op_size)); + check_expression(&op.carry(), Some(0)); + } + + Rlc(ref op) | + Rrc(ref op) => { + let op_size = op.size(); + + if let Some(required_size) = required_size { + if required_size != op_size { + error!("LLIL_RLC/RRC producing {} byte value {} byte value is expected! (addr: {:x} {:?})", + op_size, required_size, op.address(), expr); + } + } + + // rotate amounts just need to be >= 1 byte + if let Some(0) = op.right().info().size() { + error!("LLIL_RLC/RRC can't rotate by a 0 byte expression! (addr: {:x} {:?})", + op.address(), expr); + } + + check_expression(&op.left(), Some(op_size)); + check_expression(&op.right(), None); + check_expression(&op.carry(), Some(0)); + } + + Add(ref op) | + Sub(ref op) | + And(ref op) | + Or (ref op) | + Xor(ref op) | + Mul(ref op) | + Divu(ref op) | + Divs(ref op) | + Modu(ref op) | + Mods(ref op) => { + let op_size = op.size(); + + if let Some(required_size) = required_size { + if required_size != op_size { + error!("LLIL binary op producing {} byte value where {} byte value is expected! (addr: {:x} {:?})", + op_size, required_size, op.address(), expr); + } + } + + check_expression(&op.left(), Some(op_size)); + check_expression(&op.right(), Some(op_size)); + } + + Lsl(ref op) | + Lsr(ref op) | + Asr(ref op) | + Rol(ref op) | + Ror(ref op) => { + let op_size = op.size(); + + if let Some(required_size) = required_size { + if required_size != op_size { + error!("LLIL rotate/shift binary op producing {} byte value where {} byte value is expected! (addr: {:x} {:?})", + op_size, required_size, op.address(), expr); + } + } + + // rotate amounts just need to be >= 1 byte + if let Some(0) = op.right().info().size() { + error!("LLIL shift/rotate ops can't rotate by a 0 byte expression! (addr: {:x} {:?})", + op.address(), expr); + } + + check_expression(&op.left(), Some(op_size)); + check_expression(&op.right(), None); + } + + MulsDp(ref op) | + MuluDp(ref op) => { + let op_size = op.size(); + + if let Some(required_size) = required_size { + if required_size != op_size * 2 { + error!("LLIL double precision mul op producing {} byte value where {} byte value is expected! (addr: {:x} {:?})", + op_size, required_size, op.address(), expr); + } + } + + check_expression(&op.left(), Some(op_size)); + check_expression(&op.right(), Some(op_size)); + } + + DivuDp(ref op) | + DivsDp(ref op) | + ModuDp(ref op) | + ModsDp(ref op) => { + let op_size = op.size(); + + if let Some(required_size) = required_size { + if required_size != op_size { + error!("LLIL double precision div op producing {} byte value where {} byte value is expected! (addr: {:x} {:?})", + op_size, required_size, op.address(), expr); + } + } + + // TODO what's the actual right size here? + check_expression(&op.high(), Some(op_size)); + check_expression(&op.low(), Some(op_size)); + check_expression(&op.right(), Some(op_size)); + } + + Neg(ref op) | + Not(ref op) => { + let op_size = op.size(); + + if let Some(required_size) = required_size { + if required_size != op_size { + error!("LLIL unary op producing {} byte value where {} byte value is expected! (addr: {:x} {:?})", + op_size, required_size, op.address(), expr); + } + } + + check_expression(&op.operand(), Some(op_size)); + } + + Sx(ref op) | + Zx(ref op) => { + let op_size = op.size(); + + if let Some(required_size) = required_size { + if required_size != op_size { + error!("LLIL extending op producing {} byte value where {} byte value is expected! (addr: {:x} {:?})", + op_size, required_size, op.address(), expr); + } + } + + let operand = op.operand(); + + if let Some(actual_size) = operand.info().size() { + if actual_size >= op_size { + error!("LLIL extending op to {} bytes is invalid; source is already {} bytes (addr: {:x} {:?})", + op_size, actual_size, op.address(), expr); + } + } + + check_expression(&operand, None); + } + + LowPart(ref op) => { + let op_size = op.size(); + + if let Some(required_size) = required_size { + if required_size != op_size { + error!("LLIL_LOW_PART truncating to {} byte value where {} byte value is expected! (addr: {:x} {:?})", + op_size, required_size, op.address(), expr); + } + } + + let operand = op.operand(); + + if let Some(actual_size) = operand.info().size() { + if actual_size <= op_size { + error!("LLIL truncating op to {} bytes is invalid; source is already {} bytes (addr: {:x} {:?})", + op_size, actual_size, op.address(), expr); + } + } + + check_expression(&operand, None); + } + + BoolToInt(ref op) => { + let op_size = op.size(); + + if let Some(required_size) = required_size { + if required_size != op_size { + error!("LLIL_BOOL_TO_INT extending to {} byte value where {} byte value is expected! (addr: {:x} {:?})", + op_size, required_size, op.address(), expr); + } + } + + check_expression(&op.operand(), Some(0)); + } + + UnimplMem(ref op) => { + let op_size = op.size(); + + if let Some(required_size) = required_size { + if required_size != op_size { + error!("LLIL_UNIMPL_MEM producing {} byte value where {} byte value is expected! (addr: {:x} {:?})", + op_size, required_size, op.address(), expr); + } + } + + check_expression(&op.mem_expr(), None); + } + + Unimpl(_) => {} + + op => { + info!(" unhandled expr @ {:x} ... {:?}", op.address(), expr); + } + } +} + +fn check_instruction(inst: &Instruction<CoreArchitecture, Finalized, NonSSA<LiftedNonSSA>>) { + use llil::InstrInfo::*; + + match inst.info() { + SetReg(op) => { + let required_expr_size = op.size(); + + // TODO how to do sanity checking for temp registers? + if let llil::Register::ArchReg(r) = op.dest_reg() { + let reg_size = r.info().size(); + + if reg_size != required_expr_size { + error!("LLIL_SET_REG can't set {} byte register to {} byte value! (addr: {:x})", + reg_size, required_expr_size, op.address()); + } + } + + check_expression(&op.source_expr(), Some(required_expr_size)); + } + SetRegSplit(op) => { + let required_reg_size = op.size(); + + if let llil::Register::ArchReg(hi) = op.dest_reg_high() { + if hi.info().size() != required_reg_size { + error!("LLIL_SET_REG_SPLIT received a register with wrong size (wanted {} bytes)! (addr: {:x})", + required_reg_size, op.address()); + } + } + + if let llil::Register::ArchReg(lo) = op.dest_reg_low() { + if lo.info().size() != required_reg_size { + error!("LLIL_SET_REG_SPLIT received a register with wrong size (wanted {} bytes)! (addr: {:x})", + required_reg_size, op.address()); + } + } + + check_expression(&op.source_expr(), Some(required_reg_size * 2)); + } + SetFlag(op) => { + check_expression(&op.source_expr(), Some(0)); + } + Store(op) => { + let required_expr_size = op.size(); + + if required_expr_size == 0 { + error!("LLIL_STORE storing a 0 byte value! non-sensical! (addr {:x})", op.address()); + } + + // TODO make sure size matches arch default addr len? + check_expression(&op.dest_mem_expr(), None); + check_expression(&op.source_expr(), Some(required_expr_size)); + } + Push(op) => { + let required_expr_size = op.size(); + + if required_expr_size == 0 { + error!("LLIL_PUSH pushing a 0 byte value! non-sensical! (addr {:x})", op.address()); + } + + check_expression(&op.operand(), Some(required_expr_size)); + } + Jump(op) => { + check_expression(&op.target(), None); + } + JumpTo(op) => { + check_expression(&op.target(), None); + } + Call(op) => { + check_expression(&op.target(), None); + } + Ret(op) => { + check_expression(&op.target(), None); + } + If(op) => { + check_expression(&op.condition(), Some(0)); + } + Value(e, _) => { + check_expression(&e, None); + } + _ => {}, + } +} + +pub fn check_function(func: &Function) { + if let Ok(llil) = func.lifted_il() { + for block in &llil.basic_blocks() { + for inst in &*block { + check_instruction(&inst); + } + } + } +} + +use rayon::prelude::*; +use std::thread; +use std::time; + +pub fn check_all_functions_parallel(bv: &BinaryView) { + let bv = bv.to_owned(); + + thread::spawn(move || { + let start = time::Instant::now(); + let functions = bv.functions(); + + functions.par_iter().for_each(|f| check_function(&f)); + + let elapsed = time::Instant::now().duration_since(start); + info!("LiftCheck parallel: checked {} functions in {:?} seconds", functions.len(), elapsed); + }); +} diff --git a/platform/linux/platform_linux.cpp b/platform/linux/platform_linux.cpp index 1a4c9d93..107ce892 100644 --- a/platform/linux/platform_linux.cpp +++ b/platform/linux/platform_linux.cpp @@ -162,6 +162,29 @@ public: }; +class LinuxRiscVPlatform : public Platform +{ +public: + LinuxRiscVPlatform(Architecture* arch, const std::string& name) : Platform(arch, name) + { + Ref<CallingConvention> cc; + + cc = arch->GetCallingConventionByName("default"); + if (cc) + { + RegisterDefaultCallingConvention(cc); + RegisterCdeclCallingConvention(cc); + RegisterFastcallCallingConvention(cc); + RegisterStdcallCallingConvention(cc); + } + + cc = arch->GetCallingConventionByName("syscall"); + if (cc) + SetSystemCallConvention(cc); + } +}; + + extern "C" { BN_DECLARE_CORE_ABI_VERSION @@ -174,6 +197,7 @@ extern "C" AddOptionalPluginDependency("arch_arm64"); AddOptionalPluginDependency("arch_mips"); AddOptionalPluginDependency("arch_ppc"); + AddOptionalPluginDependency("arch_riscv"); AddOptionalPluginDependency("view_elf"); } #endif @@ -300,6 +324,30 @@ extern "C" BinaryViewType::RegisterPlatform("ELF", 3, mipseb, platformBE); } + Ref<Architecture> rv32 = Architecture::GetByName("rv32gc"); + if (rv32) + { + Ref<Platform> platform; + + platform = new LinuxRiscVPlatform(rv32, "linux-rv32gc"); + Platform::Register("linux", platform); + // Linux binaries sometimes have an OS identifier of zero, even though 3 is the correct one + BinaryViewType::RegisterPlatform("ELF", 0, rv32, platform); + BinaryViewType::RegisterPlatform("ELF", 3, rv32, platform); + } + + Ref<Architecture> rv64 = Architecture::GetByName("rv64gc"); + if (rv64) + { + Ref<Platform> platform; + + platform = new LinuxRiscVPlatform(rv64, "linux-rv64gc"); + Platform::Register("linux", platform); + // Linux binaries sometimes have an OS identifier of zero, even though 3 is the correct one + BinaryViewType::RegisterPlatform("ELF", 0, rv64, platform); + BinaryViewType::RegisterPlatform("ELF", 3, rv64, platform); + } + return true; } } |
