summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMason Reed <mason@vector35.com>2025-01-31 12:59:42 -0500
committerMason Reed <mason@vector35.com>2025-07-02 01:58:31 -0400
commit110c06851bbbd09f78a3e87979d529d6e09df851 (patch)
tree7849015b26a14cd2b7be2d87fc1e0d5c101ef457
parent7b1e8bbdb971aed21b6d889aa4a46f9ef54829c1 (diff)
WARP 1.0
- Added FFI - Added a sidebar to the UI - Added project, directory and archive processing - Added generic `Container` interface for extensible stores of WARP data - Fixed type references being constructed and pulled incorrectly - Added HTML, Markdown and JSON report generation - Made the WARP information added as an analysis activity - Flattened the signatures directory, the target information is stored in the file now - Matched function information is stored as function metadata in the database to reliably persist, alongside the function GUID - Split the matching out from the application, allowing you to match on a given function without applying it - Added more/better tests - Added support for binaries with multiple architectures, the functions are now also queried based off the Target, see WARP spec for more details - Greatly improved support for RISC architectures, see WARP spec for more details - Greatly improved UX when loading files after the fact, will now sanely rerun the matcher - Omitted the function type if not a user type, this greatly reduces file size - Improved support for functions that reference a page aligned base pointer, see WARP spec for more details - Removed some extra cache structures that were causing erroneous behavior - Fixed edge-case in LLIL traversal missing some constant pointers, this was a bug in the Rust bindings - Added support for function comments - Made long running tasks, such as generating, matching and loading signatures, cancellable where possible - Made function constraints more versatile, allowing for easy extensions in the future, see WARP spec for details - Added options to signature generation, such as what data to store, and whether to compress the data or not - Made all long running tasks prompt the user for required information before the task starts, allowing users to "set it and forget it" and not have to baby sit the finalization of the task - Myriad of other changes to the actual WARP format that impact performance, file size and general feature set, see https://github.com/Vector35/warp for more details
-rw-r--r--Cargo.lock8
-rw-r--r--plugins/warp/CMakeLists.txt16
-rw-r--r--plugins/warp/Cargo.toml37
-rw-r--r--plugins/warp/api/CMakeLists.txt49
-rw-r--r--plugins/warp/api/python/CMakeLists.txt50
-rw-r--r--plugins/warp/api/python/__init__.py7
-rw-r--r--plugins/warp/api/python/_warpcore.py1081
-rw-r--r--plugins/warp/api/python/_warpcore_template.py58
-rw-r--r--plugins/warp/api/python/generator.cpp652
-rw-r--r--plugins/warp/api/python/warp.py384
-rw-r--r--plugins/warp/api/python/warp_enums.py1
-rw-r--r--plugins/warp/api/warp.cpp336
-rw-r--r--plugins/warp/api/warp.h378
-rw-r--r--plugins/warp/api/warpcore.h143
-rw-r--r--plugins/warp/benches/convert.rs49
-rw-r--r--plugins/warp/benches/function.rs62
-rw-r--r--plugins/warp/benches/guid.rs31
-rw-r--r--plugins/warp/build.rs4
-rw-r--r--plugins/warp/fixtures/bin/random.warpbin0 -> 14248 bytes
-rw-r--r--plugins/warp/src/bin/sigem.rs269
-rw-r--r--plugins/warp/src/cache.rs413
-rw-r--r--plugins/warp/src/cache/container.rs28
-rw-r--r--plugins/warp/src/cache/function.rs28
-rw-r--r--plugins/warp/src/cache/guid.rs193
-rw-r--r--plugins/warp/src/cache/type_reference.rs105
-rw-r--r--plugins/warp/src/container.rs266
-rw-r--r--plugins/warp/src/container/disk.rs402
-rw-r--r--plugins/warp/src/container/memory.rs307
-rw-r--r--plugins/warp/src/container/network.rs13
-rw-r--r--plugins/warp/src/convert.rs702
-rw-r--r--plugins/warp/src/convert/symbol.rs91
-rw-r--r--plugins/warp/src/convert/types.rs516
-rw-r--r--plugins/warp/src/lib.rs266
-rw-r--r--plugins/warp/src/matcher.rs557
-rw-r--r--plugins/warp/src/plugin.rs279
-rw-r--r--plugins/warp/src/plugin/add.rs72
-rw-r--r--plugins/warp/src/plugin/copy.rs29
-rw-r--r--plugins/warp/src/plugin/create.rs284
-rw-r--r--plugins/warp/src/plugin/debug.rs48
-rw-r--r--plugins/warp/src/plugin/ffi.rs205
-rw-r--r--plugins/warp/src/plugin/ffi/container.rs412
-rw-r--r--plugins/warp/src/plugin/ffi/function.rs228
-rw-r--r--plugins/warp/src/plugin/file.rs41
-rw-r--r--plugins/warp/src/plugin/find.rs57
-rw-r--r--plugins/warp/src/plugin/function.rs133
-rw-r--r--plugins/warp/src/plugin/load.rs182
-rw-r--r--plugins/warp/src/plugin/project.rs132
-rw-r--r--plugins/warp/src/plugin/render_layer.rs100
-rw-r--r--plugins/warp/src/plugin/settings.rs129
-rw-r--r--plugins/warp/src/plugin/types.rs57
-rw-r--r--plugins/warp/src/plugin/workflow.rs188
-rw-r--r--plugins/warp/src/processor.rs829
-rw-r--r--plugins/warp/src/report.rs185
-rw-r--r--plugins/warp/src/templates/file.html37
-rw-r--r--plugins/warp/src/templates/file.json21
-rw-r--r--plugins/warp/src/templates/file.md15
-rw-r--r--plugins/warp/tests/container.rs280
-rw-r--r--plugins/warp/tests/determinism.rs40
-rw-r--r--plugins/warp/tests/matcher.rs182
-rw-r--r--plugins/warp/tests/processor.rs62
-rw-r--r--plugins/warp/tests/snapshots/determinism___ctype.obj.snap (renamed from plugins/warp/src/snapshots/warp_ninja__tests__snapshot__ctype.snap)160
-rw-r--r--plugins/warp/tests/snapshots/determinism___fptostr.obj.snap (renamed from plugins/warp/src/snapshots/warp_ninja__tests__snapshot__fptostr.snap)8
-rw-r--r--plugins/warp/tests/snapshots/determinism___mbslen.obj.snap (renamed from plugins/warp/src/snapshots/warp_ninja__tests__snapshot__mbslen.snap)46
-rw-r--r--plugins/warp/tests/snapshots/determinism___memicmp.obj.snap (renamed from plugins/warp/src/snapshots/warp_ninja__tests__snapshot__memicmp.snap)8
-rw-r--r--plugins/warp/tests/snapshots/determinism___strnicm.obj.snap (renamed from plugins/warp/src/snapshots/warp_ninja__tests__snapshot__strnicm.snap)8
-rw-r--r--plugins/warp/tests/snapshots/determinism___wctype.obj.snap (renamed from plugins/warp/src/snapshots/warp_ninja__tests__snapshot__wctype.snap)148
-rw-r--r--plugins/warp/tests/snapshots/determinism__atof.obj.snap (renamed from plugins/warp/src/snapshots/warp_ninja__tests__snapshot_atof.snap)510
-rw-r--r--plugins/warp/tests/snapshots/determinism__atoldbl.obj.snap (renamed from plugins/warp/src/snapshots/warp_ninja__tests__snapshot_atoldbl.snap)248
-rw-r--r--plugins/warp/tests/snapshots/determinism__atox.obj.snap (renamed from plugins/warp/src/snapshots/warp_ninja__tests__snapshot_atox.snap)238
-rw-r--r--plugins/warp/ui/CMakeLists.txt51
-rw-r--r--plugins/warp/ui/matched.cpp83
-rw-r--r--plugins/warp/ui/matched.h27
-rw-r--r--plugins/warp/ui/matches.cpp164
-rw-r--r--plugins/warp/ui/matches.h29
-rw-r--r--plugins/warp/ui/plugin.cpp198
-rw-r--r--plugins/warp/ui/plugin.h53
-rw-r--r--plugins/warp/ui/shared/constraint.cpp118
-rw-r--r--plugins/warp/ui/shared/constraint.h78
-rw-r--r--plugins/warp/ui/shared/function.cpp365
-rw-r--r--plugins/warp/ui/shared/function.h168
-rw-r--r--plugins/warp/ui/shared/misc.cpp81
-rw-r--r--plugins/warp/ui/shared/misc.h84
82 files changed, 11492 insertions, 3110 deletions
diff --git a/Cargo.lock b/Cargo.lock
index 0dc5b15f..60964008 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -261,9 +261,9 @@ dependencies = [
[[package]]
name = "bumpalo"
-version = "3.18.1"
+version = "3.19.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "793db76d6187cd04dff33004d8e6c9cc4e05cd330500379d2394209271b4aeee"
+checksum = "46c5e41b57b8bba42a04676d81cb89e9ee8e859a1a66f80a5a72e1cb76b34d43"
[[package]]
name = "bytemuck"
@@ -498,9 +498,9 @@ checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28"
[[package]]
name = "crunchy"
-version = "0.2.3"
+version = "0.2.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "43da5946c66ffcc7745f48db692ffbb10a83bfe0afd96235c5c2a4fb23994929"
+checksum = "460fbee9c2c2f33933d720630a6a0bac33ba7053db5344fac858d4b8952d77d5"
[[package]]
name = "darling"
diff --git a/plugins/warp/CMakeLists.txt b/plugins/warp/CMakeLists.txt
index 37b86165..4b680b40 100644
--- a/plugins/warp/CMakeLists.txt
+++ b/plugins/warp/CMakeLists.txt
@@ -21,6 +21,13 @@ file(GLOB_RECURSE PLUGIN_SOURCES CONFIGURE_DEPENDS
${PROJECT_SOURCE_DIR}/Cargo.toml
${PROJECT_SOURCE_DIR}/src/*.rs)
+# Add API library.
+add_subdirectory(api)
+# Add UI plugin.
+if(NOT HEADLESS)
+ add_subdirectory(ui)
+endif()
+
if(CMAKE_BUILD_TYPE MATCHES Debug)
set(TARGET_DIR ${PROJECT_BINARY_DIR}/target/debug)
set(CARGO_OPTS --target-dir=${PROJECT_BINARY_DIR}/target)
@@ -38,8 +45,14 @@ endif()
set(CARGO_FEATURES "--no-default-features")
set(OUTPUT_FILE_NAME ${CMAKE_SHARED_LIBRARY_PREFIX}${PROJECT_NAME}${CMAKE_SHARED_LIBRARY_SUFFIX})
set(OUTPUT_PDB_NAME ${CMAKE_SHARED_LIBRARY_PREFIX}${PROJECT_NAME}.pdb)
+set(OUTPUT_LIB_NAME ${CMAKE_SHARED_LIBRARY_PREFIX}${PROJECT_NAME}.dll.lib)
set(OUTPUT_FILE_PATH ${BN_CORE_PLUGIN_DIR}/${CMAKE_SHARED_LIBRARY_PREFIX}${PROJECT_NAME}${CMAKE_SHARED_LIBRARY_SUFFIX})
set(OUTPUT_PDB_PATH ${BN_CORE_PLUGIN_DIR}/${CMAKE_SHARED_LIBRARY_PREFIX}${PROJECT_NAME}.pdb)
+set(OUTPUT_LIB_PATH ${BN_CORE_PLUGIN_DIR}/${PROJECT_NAME}.lib)
+
+# warp_ninja_interface is the target hack im going with for now to get warp_api to build on windows
+add_library(${PROJECT_NAME}_interface INTERFACE)
+add_dependencies(${PROJECT_NAME}_interface ${PROJECT_NAME})
add_custom_target(${PROJECT_NAME} ALL DEPENDS ${OUTPUT_FILE_PATH})
add_dependencies(${PROJECT_NAME} binaryninjaapi)
@@ -109,11 +122,12 @@ if(APPLE)
endif()
elseif(WIN32)
add_custom_command(
- OUTPUT ${OUTPUT_FILE_PATH}
+ OUTPUT ${OUTPUT_FILE_PATH} ${OUTPUT_PDB_PATH} ${OUTPUT_LIB_PATH}
COMMAND ${CMAKE_COMMAND} -E env BINARYNINJADIR=${BINJA_LIB_DIR} ${RUSTUP_COMMAND} clean ${CARGO_OPTS} --package binaryninjacore-sys
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}
+ COMMAND ${CMAKE_COMMAND} -E copy ${TARGET_DIR}/${OUTPUT_LIB_NAME} ${OUTPUT_LIB_PATH}
WORKING_DIRECTORY ${PROJECT_SOURCE_DIR}
DEPENDS ${PLUGIN_SOURCES} ${API_SOURCES}
)
diff --git a/plugins/warp/Cargo.toml b/plugins/warp/Cargo.toml
index 07e0a4de..afebf577 100644
--- a/plugins/warp/Cargo.toml
+++ b/plugins/warp/Cargo.toml
@@ -10,32 +10,31 @@ crate-type = ["lib", "cdylib"]
[dependencies]
binaryninja = { workspace = true, features = ["rayon"] }
binaryninjacore-sys.workspace = true
-warp = { git = "https://github.com/Vector35/warp/", rev = "0ee5a6f" }
+warp = { git = "https://github.com/Vector35/warp/", tag = "1.0.0" }
log = "0.4"
-arboard = "3.4"
+itertools = "0.14.0"
+dashmap = { version = "6.1", features = ["rayon"]}
rayon = "1.10"
-dashmap = "6.1"
+arboard = "3.4"
walkdir = "2.5"
-rfd = "0.15"
+serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
+uuid = { version = "1.12.0", features = ["v4"] }
+thiserror = "2.0"
+ar = { git = "https://github.com/mdsteele/rust-ar" }
+tempdir = "0.3.7"
+regex = "1.11"
-# For sigem
-env_logger = { version = "0.11", optional = true }
-clap = { version = "4.5", features = ["derive"], optional = true }
-ar = { git = "https://github.com/mdsteele/rust-ar", optional = true }
-tempdir = { version = "0.3.7", optional = true }
+# For reports
+minijinja = "2.10.2"
+minijinja-embed = "2.10.2"
-[dev-dependencies]
-criterion = "0.5.1"
-insta = { version = "1.38.0", features = ["yaml"] }
+[build-dependencies]
+minijinja-embed = "2.10.2"
-[features]
-default = ["sigem"]
-sigem = ["env_logger", "clap", "ar", "tempdir"]
-
-[[bin]]
-name = "sigem"
-required-features = ["sigem"]
+[dev-dependencies]
+criterion = "0.6"
+insta = { version = "1.42", features = ["yaml"] }
[[bench]]
name = "guid"
diff --git a/plugins/warp/api/CMakeLists.txt b/plugins/warp/api/CMakeLists.txt
new file mode 100644
index 00000000..4aded248
--- /dev/null
+++ b/plugins/warp/api/CMakeLists.txt
@@ -0,0 +1,49 @@
+cmake_minimum_required(VERSION 3.13 FATAL_ERROR)
+
+project(warp_api CXX C)
+
+add_library(warp_api STATIC warp.cpp warp.h warpcore.h)
+target_include_directories(warp_api PUBLIC ${PROJECT_SOURCE_DIR})
+
+if (NOT BN_API_BUILD_EXAMPLES AND NOT BN_INTERNAL_BUILD)
+ # Out-of-tree build
+ find_path(
+ BN_API_PATH
+ NAMES binaryninjaapi.h
+ HINTS ../.. binaryninjaapi $ENV{BN_API_PATH}
+ REQUIRED
+ )
+ add_subdirectory(${BN_API_PATH} api)
+endif()
+
+# Make sure the core rust dylib is built before warp_api.
+add_dependencies(warp_api warp_ninja)
+
+target_link_libraries(warp_api binaryninjaui)
+
+if (NOT DEMO)
+ add_subdirectory(python)
+endif()
+
+# Link to the warp plugin.
+# TODO: Need to make this less scuffed, but this will do for now.
+if (WIN32)
+ # By linking to warp_ninja_interface we insure that we get built _after_ the lib has been generated.
+ target_link_libraries(${PROJECT_NAME} warp_ninja_interface "${BN_CORE_PLUGIN_DIR}/warp_ninja.lib")
+else ()
+ target_link_libraries(${PROJECT_NAME} warp_ninja_interface ${BN_CORE_PLUGIN_DIR}/libwarp_ninja${CMAKE_SHARED_LIBRARY_SUFFIX})
+ # Set RPATH so the library can be found at runtime
+ if(APPLE)
+ set_target_properties(${PROJECT_NAME} PROPERTIES BUILD_WITH_INSTALL_RPATH TRUE INSTALL_RPATH "@loader_path")
+ else()
+ set_target_properties(${PROJECT_NAME} PROPERTIES BUILD_WITH_INSTALL_RPATH TRUE INSTALL_RPATH "$ORIGIN")
+ endif()
+endif()
+
+set_target_properties(warp_api PROPERTIES
+ CXX_STANDARD 17
+ CXX_VISIBILITY_PRESET hidden
+ CXX_STANDARD_REQUIRED ON
+ VISIBILITY_INLINES_HIDDEN ON
+ POSITION_INDEPENDENT_CODE ON
+ ARCHIVE_OUTPUT_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/out) \ No newline at end of file
diff --git a/plugins/warp/api/python/CMakeLists.txt b/plugins/warp/api/python/CMakeLists.txt
new file mode 100644
index 00000000..8e6003c0
--- /dev/null
+++ b/plugins/warp/api/python/CMakeLists.txt
@@ -0,0 +1,50 @@
+cmake_minimum_required(VERSION 3.9...3.15 FATAL_ERROR)
+
+project(warp-python-api)
+
+file(GLOB PYTHON_SOURCES ${PROJECT_SOURCE_DIR}/*.py)
+list(REMOVE_ITEM PYTHON_SOURCES ${PROJECT_SOURCE_DIR}/_warpcore.py)
+list(REMOVE_ITEM PYTHON_SOURCES ${PROJECT_SOURCE_DIR}/enums.py)
+
+add_executable(warp_generator
+ ${PROJECT_SOURCE_DIR}/generator.cpp)
+target_link_libraries(warp_generator binaryninjaapi)
+target_include_directories(warp_generator PUBLIC {PROJECT_SOURCE_DIR}/../../api)
+
+set_target_properties(warp_generator PROPERTIES
+ CXX_STANDARD 17
+ CXX_STANDARD_REQUIRED ON
+ BUILD_WITH_INSTALL_RPATH OFF
+ RUNTIME_OUTPUT_DIRECTORY ${PROJECT_BINARY_DIR})
+
+if(BN_INTERNAL_BUILD)
+ set(PYTHON_OUTPUT_DIRECTORY ${BN_RESOURCE_DIR}/python/binaryninja/warp/)
+else()
+ set(PYTHON_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/out/plugins/warp/)
+endif()
+
+if(WIN32)
+ if (BN_INTERNAL_BUILD)
+ add_custom_command(TARGET warp_generator PRE_BUILD
+ COMMAND ${CMAKE_COMMAND} -E copy ${BN_CORE_OUTPUT_DIR}/binaryninjacore.dll ${PROJECT_BINARY_DIR}/)
+ else()
+ add_custom_command(TARGET warp_generator PRE_BUILD
+ COMMAND ${CMAKE_COMMAND} -E copy ${BN_INSTALL_DIR}/binaryninjacore.dll ${PROJECT_BINARY_DIR}/)
+ endif()
+endif()
+
+add_custom_target(warp_generator_copy ALL
+ BYPRODUCTS ${PROJECT_SOURCE_DIR}/_warpcore.py ${PROJECT_SOURCE_DIR}/enums.py
+ DEPENDS ${PYTHON_SOURCES} ${PROJECT_SOURCE_DIR}/../warpcore.h $<TARGET_FILE:warp_generator>
+ COMMAND ${CMAKE_COMMAND} -E echo "Copying WARP Python Sources"
+ COMMAND ${CMAKE_COMMAND} -E make_directory ${PYTHON_OUTPUT_DIRECTORY}
+ COMMAND ${CMAKE_COMMAND} -E env ASAN_OPTIONS=detect_leaks=0 $<TARGET_FILE:warp_generator>
+ ${PROJECT_SOURCE_DIR}/../warpcore.h
+ ${PROJECT_SOURCE_DIR}/_warpcore.py
+ ${PROJECT_SOURCE_DIR}/_warpcore_template.py
+ ${PROJECT_SOURCE_DIR}/warp_enums.py
+
+ COMMAND ${CMAKE_COMMAND} -E copy ${PYTHON_SOURCES} ${PYTHON_OUTPUT_DIRECTORY}
+ COMMAND ${CMAKE_COMMAND} -E copy ${PROJECT_SOURCE_DIR}/_warpcore.py ${PYTHON_OUTPUT_DIRECTORY}
+ COMMAND ${CMAKE_COMMAND} -E copy ${PROJECT_SOURCE_DIR}/warp_enums.py ${PYTHON_OUTPUT_DIRECTORY})
+
diff --git a/plugins/warp/api/python/__init__.py b/plugins/warp/api/python/__init__.py
new file mode 100644
index 00000000..f4bc8e83
--- /dev/null
+++ b/plugins/warp/api/python/__init__.py
@@ -0,0 +1,7 @@
+import os
+
+from binaryninja._binaryninjacore import BNGetUserPluginDirectory
+user_plugin_dir = os.path.realpath(BNGetUserPluginDirectory())
+current_path = os.path.realpath(__file__)
+
+from .warp import * \ No newline at end of file
diff --git a/plugins/warp/api/python/_warpcore.py b/plugins/warp/api/python/_warpcore.py
new file mode 100644
index 00000000..c53feefa
--- /dev/null
+++ b/plugins/warp/api/python/_warpcore.py
@@ -0,0 +1,1081 @@
+import binaryninja
+import ctypes, os
+
+from typing import Optional
+from . import warp_enums
+# Load core module
+import platform
+core = None
+core_platform = platform.system()
+
+from binaryninja import Settings
+if Settings().get_bool("corePlugins.warp"):
+ from binaryninja._binaryninjacore import BNGetBundledPluginDirectory
+ if core_platform == "Darwin":
+ _base_path = BNGetBundledPluginDirectory()
+ core = ctypes.CDLL(os.path.join(_base_path, "libwarp_ninja.dylib"))
+
+ elif core_platform == "Linux":
+ _base_path = BNGetBundledPluginDirectory()
+ core = ctypes.CDLL(os.path.join(_base_path, "libwarp_ninja.so"))
+
+ elif (core_platform == "Windows") or (core_platform.find("CYGWIN_NT") == 0):
+ _base_path = BNGetBundledPluginDirectory()
+ core = ctypes.CDLL(os.path.join(_base_path, "warp_ninja.dll"))
+ else:
+ raise Exception("OS not supported")
+else:
+ from binaryninja._binaryninjacore import BNGetUserPluginDirectory
+ if core_platform == "Darwin":
+ _base_path = BNGetUserPluginDirectory()
+ core = ctypes.CDLL(os.path.join(_base_path, "libwarp_ninja.dylib"))
+
+ elif core_platform == "Linux":
+ _base_path = BNGetUserPluginDirectory()
+ core = ctypes.CDLL(os.path.join(_base_path, "libwarp_ninja.so"))
+
+ elif (core_platform == "Windows") or (core_platform.find("CYGWIN_NT") == 0):
+ _base_path = BNGetUserPluginDirectory()
+ core = ctypes.CDLL(os.path.join(_base_path, "warp_ninja.dll"))
+ else:
+ raise Exception("OS not supported")
+
+def cstr(var) -> Optional[ctypes.c_char_p]:
+ if var is None:
+ return None
+ if isinstance(var, bytes):
+ return var
+ return var.encode("utf-8")
+
+def pyNativeStr(arg):
+ if isinstance(arg, str):
+ return arg
+ else:
+ return arg.decode('utf8')
+
+def free_string(value:ctypes.c_char_p) -> None:
+ BNFreeString(ctypes.cast(value, ctypes.POINTER(ctypes.c_byte)))
+
+from binaryninja._binaryninjacore import BNFreeString
+# Type definitions
+from binaryninja._binaryninjacore import BNArchitecture, BNArchitectureHandle
+from binaryninja._binaryninjacore import BNBasicBlock, BNBasicBlockHandle
+from binaryninja._binaryninjacore import BNBinaryView, BNBinaryViewHandle
+from binaryninja._binaryninjacore import BNFunction, BNFunctionHandle
+from binaryninja._binaryninjacore import BNLowLevelILFunction, BNLowLevelILFunctionHandle
+from binaryninja._binaryninjacore import BNPlatform, BNPlatformHandle
+from binaryninja._binaryninjacore import BNSymbol, BNSymbolHandle
+from binaryninja._binaryninjacore import BNType, BNTypeHandle
+class BNWARPConstraint(ctypes.Structure):
+ pass
+BNWARPConstraintHandle = ctypes.POINTER(BNWARPConstraint)
+class BNWARPContainer(ctypes.Structure):
+ pass
+BNWARPContainerHandle = ctypes.POINTER(BNWARPContainer)
+class BNWARPFunction(ctypes.Structure):
+ pass
+BNWARPFunctionHandle = ctypes.POINTER(BNWARPFunction)
+class BNWARPFunctionComment(ctypes.Structure):
+ @property
+ def text(self):
+ return pyNativeStr(self._text)
+ @text.setter
+ def text(self, value):
+ self._text = cstr(value)
+BNWARPFunctionCommentHandle = ctypes.POINTER(BNWARPFunctionComment)
+class BNWARPTarget(ctypes.Structure):
+ pass
+BNWARPTargetHandle = ctypes.POINTER(BNWARPTarget)
+class BNWARPUUID(ctypes.Structure):
+ pass
+BNWARPUUIDHandle = ctypes.POINTER(BNWARPUUID)
+
+# Structure definitions
+BNWARPBasicBlockGUID = BNWARPUUID
+BNWARPBasicBlockGUIDHandle = BNWARPUUIDHandle
+BNWARPConstraintGUID = BNWARPUUID
+BNWARPConstraintGUIDHandle = BNWARPUUIDHandle
+BNWARPFunctionComment._fields_ = [
+ ("_text", ctypes.c_char_p),
+ ("offset", ctypes.c_longlong),
+ ]
+BNWARPFunctionGUID = BNWARPUUID
+BNWARPFunctionGUIDHandle = BNWARPUUIDHandle
+BNWARPSource = BNWARPUUID
+BNWARPSourceHandle = BNWARPUUIDHandle
+BNWARPTypeGUID = BNWARPUUID
+BNWARPTypeGUIDHandle = BNWARPUUIDHandle
+BNWARPUUID._fields_ = [
+ ("uuid", ctypes.c_ubyte * 16),
+ ]
+BNWARPConstraint._fields_ = [
+ ("guid", BNWARPConstraintGUID),
+ ("offset", ctypes.c_longlong),
+ ]
+
+# Function definitions
+# -------------------------------------------------------
+# _BNWARPContainerAddFunctions
+
+_BNWARPContainerAddFunctions = core.BNWARPContainerAddFunctions
+_BNWARPContainerAddFunctions.restype = ctypes.c_bool
+_BNWARPContainerAddFunctions.argtypes = [
+ ctypes.POINTER(BNWARPContainer),
+ ctypes.POINTER(BNWARPTarget),
+ ctypes.POINTER(BNWARPSource),
+ ctypes.POINTER(ctypes.POINTER(BNWARPFunction)),
+ ctypes.c_ulonglong,
+ ]
+
+
+# noinspection PyPep8Naming
+def BNWARPContainerAddFunctions(
+ container: ctypes.POINTER(BNWARPContainer),
+ target: ctypes.POINTER(BNWARPTarget),
+ source: ctypes.POINTER(BNWARPSource),
+ functions: ctypes.POINTER(ctypes.POINTER(BNWARPFunction)),
+ count: int
+ ) -> bool:
+ return _BNWARPContainerAddFunctions(container, target, source, functions, count)
+
+
+# -------------------------------------------------------
+# _BNWARPContainerAddSource
+
+_BNWARPContainerAddSource = core.BNWARPContainerAddSource
+_BNWARPContainerAddSource.restype = ctypes.c_bool
+_BNWARPContainerAddSource.argtypes = [
+ ctypes.POINTER(BNWARPContainer),
+ ctypes.c_char_p,
+ ctypes.POINTER(BNWARPSource),
+ ]
+
+
+# noinspection PyPep8Naming
+def BNWARPContainerAddSource(
+ container: ctypes.POINTER(BNWARPContainer),
+ sourcePath: Optional[str],
+ result: ctypes.POINTER(BNWARPSource)
+ ) -> bool:
+ return _BNWARPContainerAddSource(container, cstr(sourcePath), result)
+
+
+# -------------------------------------------------------
+# _BNWARPContainerAddTypes
+
+_BNWARPContainerAddTypes = core.BNWARPContainerAddTypes
+_BNWARPContainerAddTypes.restype = ctypes.c_bool
+_BNWARPContainerAddTypes.argtypes = [
+ ctypes.POINTER(BNBinaryView),
+ ctypes.POINTER(BNWARPContainer),
+ ctypes.POINTER(BNWARPSource),
+ ctypes.POINTER(ctypes.POINTER(BNType)),
+ ctypes.c_ulonglong,
+ ]
+
+
+# noinspection PyPep8Naming
+def BNWARPContainerAddTypes(
+ view: ctypes.POINTER(BNBinaryView),
+ container: ctypes.POINTER(BNWARPContainer),
+ source: ctypes.POINTER(BNWARPSource),
+ types: ctypes.POINTER(ctypes.POINTER(BNType)),
+ count: int
+ ) -> bool:
+ return _BNWARPContainerAddTypes(view, container, source, types, count)
+
+
+# -------------------------------------------------------
+# _BNWARPContainerCommitSource
+
+_BNWARPContainerCommitSource = core.BNWARPContainerCommitSource
+_BNWARPContainerCommitSource.restype = ctypes.c_bool
+_BNWARPContainerCommitSource.argtypes = [
+ ctypes.POINTER(BNWARPContainer),
+ ctypes.POINTER(BNWARPSource),
+ ]
+
+
+# noinspection PyPep8Naming
+def BNWARPContainerCommitSource(
+ container: ctypes.POINTER(BNWARPContainer),
+ source: ctypes.POINTER(BNWARPSource)
+ ) -> bool:
+ return _BNWARPContainerCommitSource(container, source)
+
+
+# -------------------------------------------------------
+# _BNWARPContainerGetFunctionsWithGUID
+
+_BNWARPContainerGetFunctionsWithGUID = core.BNWARPContainerGetFunctionsWithGUID
+_BNWARPContainerGetFunctionsWithGUID.restype = ctypes.POINTER(ctypes.POINTER(BNWARPFunction))
+_BNWARPContainerGetFunctionsWithGUID.argtypes = [
+ ctypes.POINTER(BNWARPContainer),
+ ctypes.POINTER(BNWARPTarget),
+ ctypes.POINTER(BNWARPSource),
+ ctypes.POINTER(BNWARPFunctionGUID),
+ ctypes.POINTER(ctypes.c_ulonglong),
+ ]
+
+
+# noinspection PyPep8Naming
+def BNWARPContainerGetFunctionsWithGUID(
+ container: ctypes.POINTER(BNWARPContainer),
+ target: ctypes.POINTER(BNWARPTarget),
+ source: ctypes.POINTER(BNWARPSource),
+ guid: ctypes.POINTER(BNWARPFunctionGUID),
+ count: ctypes.POINTER(ctypes.c_ulonglong)
+ ) -> Optional[ctypes.POINTER(ctypes.POINTER(BNWARPFunction))]:
+ result = _BNWARPContainerGetFunctionsWithGUID(container, target, source, guid, count)
+ if not result:
+ return None
+ return result
+
+
+# -------------------------------------------------------
+# _BNWARPContainerGetName
+
+_BNWARPContainerGetName = core.BNWARPContainerGetName
+_BNWARPContainerGetName.restype = ctypes.POINTER(ctypes.c_byte)
+_BNWARPContainerGetName.argtypes = [
+ ctypes.POINTER(BNWARPContainer),
+ ]
+
+
+# noinspection PyPep8Naming
+def BNWARPContainerGetName(
+ container: ctypes.POINTER(BNWARPContainer)
+ ) -> Optional[Optional[str]]:
+ result = _BNWARPContainerGetName(container)
+ if not result:
+ return None
+ string = str(pyNativeStr(ctypes.cast(result, ctypes.c_char_p).value))
+ BNFreeString(result)
+ return string
+
+
+# -------------------------------------------------------
+# _BNWARPContainerGetSourcePath
+
+_BNWARPContainerGetSourcePath = core.BNWARPContainerGetSourcePath
+_BNWARPContainerGetSourcePath.restype = ctypes.POINTER(ctypes.c_byte)
+_BNWARPContainerGetSourcePath.argtypes = [
+ ctypes.POINTER(BNWARPContainer),
+ ctypes.POINTER(BNWARPSource),
+ ]
+
+
+# noinspection PyPep8Naming
+def BNWARPContainerGetSourcePath(
+ container: ctypes.POINTER(BNWARPContainer),
+ source: ctypes.POINTER(BNWARPSource)
+ ) -> Optional[Optional[str]]:
+ result = _BNWARPContainerGetSourcePath(container, source)
+ if not result:
+ return None
+ string = str(pyNativeStr(ctypes.cast(result, ctypes.c_char_p).value))
+ BNFreeString(result)
+ return string
+
+
+# -------------------------------------------------------
+# _BNWARPContainerGetSources
+
+_BNWARPContainerGetSources = core.BNWARPContainerGetSources
+_BNWARPContainerGetSources.restype = ctypes.POINTER(BNWARPSource)
+_BNWARPContainerGetSources.argtypes = [
+ ctypes.POINTER(BNWARPContainer),
+ ctypes.POINTER(ctypes.c_ulonglong),
+ ]
+
+
+# noinspection PyPep8Naming
+def BNWARPContainerGetSources(
+ container: ctypes.POINTER(BNWARPContainer),
+ count: ctypes.POINTER(ctypes.c_ulonglong)
+ ) -> Optional[ctypes.POINTER(BNWARPSource)]:
+ result = _BNWARPContainerGetSources(container, count)
+ if not result:
+ return None
+ return result
+
+
+# -------------------------------------------------------
+# _BNWARPContainerGetSourcesWithFunctionGUID
+
+_BNWARPContainerGetSourcesWithFunctionGUID = core.BNWARPContainerGetSourcesWithFunctionGUID
+_BNWARPContainerGetSourcesWithFunctionGUID.restype = ctypes.POINTER(BNWARPSource)
+_BNWARPContainerGetSourcesWithFunctionGUID.argtypes = [
+ ctypes.POINTER(BNWARPContainer),
+ ctypes.POINTER(BNWARPTarget),
+ ctypes.POINTER(BNWARPFunctionGUID),
+ ctypes.POINTER(ctypes.c_ulonglong),
+ ]
+
+
+# noinspection PyPep8Naming
+def BNWARPContainerGetSourcesWithFunctionGUID(
+ container: ctypes.POINTER(BNWARPContainer),
+ target: ctypes.POINTER(BNWARPTarget),
+ guid: ctypes.POINTER(BNWARPFunctionGUID),
+ count: ctypes.POINTER(ctypes.c_ulonglong)
+ ) -> Optional[ctypes.POINTER(BNWARPSource)]:
+ result = _BNWARPContainerGetSourcesWithFunctionGUID(container, target, guid, count)
+ if not result:
+ return None
+ return result
+
+
+# -------------------------------------------------------
+# _BNWARPContainerGetSourcesWithTypeGUID
+
+_BNWARPContainerGetSourcesWithTypeGUID = core.BNWARPContainerGetSourcesWithTypeGUID
+_BNWARPContainerGetSourcesWithTypeGUID.restype = ctypes.POINTER(BNWARPSource)
+_BNWARPContainerGetSourcesWithTypeGUID.argtypes = [
+ ctypes.POINTER(BNWARPContainer),
+ ctypes.POINTER(BNWARPTypeGUID),
+ ctypes.POINTER(ctypes.c_ulonglong),
+ ]
+
+
+# noinspection PyPep8Naming
+def BNWARPContainerGetSourcesWithTypeGUID(
+ container: ctypes.POINTER(BNWARPContainer),
+ guid: ctypes.POINTER(BNWARPTypeGUID),
+ count: ctypes.POINTER(ctypes.c_ulonglong)
+ ) -> Optional[ctypes.POINTER(BNWARPSource)]:
+ result = _BNWARPContainerGetSourcesWithTypeGUID(container, guid, count)
+ if not result:
+ return None
+ return result
+
+
+# -------------------------------------------------------
+# _BNWARPContainerGetTypeGUIDsWithName
+
+_BNWARPContainerGetTypeGUIDsWithName = core.BNWARPContainerGetTypeGUIDsWithName
+_BNWARPContainerGetTypeGUIDsWithName.restype = ctypes.POINTER(BNWARPTypeGUID)
+_BNWARPContainerGetTypeGUIDsWithName.argtypes = [
+ ctypes.POINTER(BNWARPContainer),
+ ctypes.POINTER(BNWARPSource),
+ ctypes.c_char_p,
+ ctypes.POINTER(ctypes.c_ulonglong),
+ ]
+
+
+# noinspection PyPep8Naming
+def BNWARPContainerGetTypeGUIDsWithName(
+ container: ctypes.POINTER(BNWARPContainer),
+ source: ctypes.POINTER(BNWARPSource),
+ name: Optional[str],
+ count: ctypes.POINTER(ctypes.c_ulonglong)
+ ) -> Optional[ctypes.POINTER(BNWARPTypeGUID)]:
+ result = _BNWARPContainerGetTypeGUIDsWithName(container, source, cstr(name), count)
+ if not result:
+ return None
+ return result
+
+
+# -------------------------------------------------------
+# _BNWARPContainerGetTypeWithGUID
+
+_BNWARPContainerGetTypeWithGUID = core.BNWARPContainerGetTypeWithGUID
+_BNWARPContainerGetTypeWithGUID.restype = ctypes.POINTER(BNType)
+_BNWARPContainerGetTypeWithGUID.argtypes = [
+ ctypes.POINTER(BNArchitecture),
+ ctypes.POINTER(BNWARPContainer),
+ ctypes.POINTER(BNWARPSource),
+ ctypes.POINTER(BNWARPTypeGUID),
+ ]
+
+
+# noinspection PyPep8Naming
+def BNWARPContainerGetTypeWithGUID(
+ arch: ctypes.POINTER(BNArchitecture),
+ container: ctypes.POINTER(BNWARPContainer),
+ source: ctypes.POINTER(BNWARPSource),
+ guid: ctypes.POINTER(BNWARPTypeGUID)
+ ) -> Optional[ctypes.POINTER(BNType)]:
+ result = _BNWARPContainerGetTypeWithGUID(arch, container, source, guid)
+ if not result:
+ return None
+ return result
+
+
+# -------------------------------------------------------
+# _BNWARPContainerIsSourceUncommitted
+
+_BNWARPContainerIsSourceUncommitted = core.BNWARPContainerIsSourceUncommitted
+_BNWARPContainerIsSourceUncommitted.restype = ctypes.c_bool
+_BNWARPContainerIsSourceUncommitted.argtypes = [
+ ctypes.POINTER(BNWARPContainer),
+ ctypes.POINTER(BNWARPSource),
+ ]
+
+
+# noinspection PyPep8Naming
+def BNWARPContainerIsSourceUncommitted(
+ container: ctypes.POINTER(BNWARPContainer),
+ source: ctypes.POINTER(BNWARPSource)
+ ) -> bool:
+ return _BNWARPContainerIsSourceUncommitted(container, source)
+
+
+# -------------------------------------------------------
+# _BNWARPContainerIsSourceWritable
+
+_BNWARPContainerIsSourceWritable = core.BNWARPContainerIsSourceWritable
+_BNWARPContainerIsSourceWritable.restype = ctypes.c_bool
+_BNWARPContainerIsSourceWritable.argtypes = [
+ ctypes.POINTER(BNWARPContainer),
+ ctypes.POINTER(BNWARPSource),
+ ]
+
+
+# noinspection PyPep8Naming
+def BNWARPContainerIsSourceWritable(
+ container: ctypes.POINTER(BNWARPContainer),
+ source: ctypes.POINTER(BNWARPSource)
+ ) -> bool:
+ return _BNWARPContainerIsSourceWritable(container, source)
+
+
+# -------------------------------------------------------
+# _BNWARPContainerRemoveFunctions
+
+_BNWARPContainerRemoveFunctions = core.BNWARPContainerRemoveFunctions
+_BNWARPContainerRemoveFunctions.restype = ctypes.c_bool
+_BNWARPContainerRemoveFunctions.argtypes = [
+ ctypes.POINTER(BNWARPContainer),
+ ctypes.POINTER(BNWARPTarget),
+ ctypes.POINTER(BNWARPSource),
+ ctypes.POINTER(ctypes.POINTER(BNWARPFunction)),
+ ctypes.c_ulonglong,
+ ]
+
+
+# noinspection PyPep8Naming
+def BNWARPContainerRemoveFunctions(
+ container: ctypes.POINTER(BNWARPContainer),
+ target: ctypes.POINTER(BNWARPTarget),
+ source: ctypes.POINTER(BNWARPSource),
+ functions: ctypes.POINTER(ctypes.POINTER(BNWARPFunction)),
+ count: int
+ ) -> bool:
+ return _BNWARPContainerRemoveFunctions(container, target, source, functions, count)
+
+
+# -------------------------------------------------------
+# _BNWARPContainerRemoveTypes
+
+_BNWARPContainerRemoveTypes = core.BNWARPContainerRemoveTypes
+_BNWARPContainerRemoveTypes.restype = ctypes.c_bool
+_BNWARPContainerRemoveTypes.argtypes = [
+ ctypes.POINTER(BNWARPContainer),
+ ctypes.POINTER(BNWARPSource),
+ ctypes.POINTER(BNWARPTypeGUID),
+ ctypes.c_ulonglong,
+ ]
+
+
+# noinspection PyPep8Naming
+def BNWARPContainerRemoveTypes(
+ container: ctypes.POINTER(BNWARPContainer),
+ source: ctypes.POINTER(BNWARPSource),
+ types: ctypes.POINTER(BNWARPTypeGUID),
+ count: int
+ ) -> bool:
+ return _BNWARPContainerRemoveTypes(container, source, types, count)
+
+
+# -------------------------------------------------------
+# _BNWARPFreeConstraintList
+
+_BNWARPFreeConstraintList = core.BNWARPFreeConstraintList
+_BNWARPFreeConstraintList.restype = None
+_BNWARPFreeConstraintList.argtypes = [
+ ctypes.POINTER(BNWARPConstraint),
+ ctypes.c_ulonglong,
+ ]
+
+
+# noinspection PyPep8Naming
+def BNWARPFreeConstraintList(
+ constraints: ctypes.POINTER(BNWARPConstraint),
+ count: int
+ ) -> None:
+ return _BNWARPFreeConstraintList(constraints, count)
+
+
+# -------------------------------------------------------
+# _BNWARPFreeContainerList
+
+_BNWARPFreeContainerList = core.BNWARPFreeContainerList
+_BNWARPFreeContainerList.restype = None
+_BNWARPFreeContainerList.argtypes = [
+ ctypes.POINTER(ctypes.POINTER(BNWARPContainer)),
+ ctypes.c_ulonglong,
+ ]
+
+
+# noinspection PyPep8Naming
+def BNWARPFreeContainerList(
+ containers: ctypes.POINTER(ctypes.POINTER(BNWARPContainer)),
+ count: int
+ ) -> None:
+ return _BNWARPFreeContainerList(containers, count)
+
+
+# -------------------------------------------------------
+# _BNWARPFreeContainerReference
+
+_BNWARPFreeContainerReference = core.BNWARPFreeContainerReference
+_BNWARPFreeContainerReference.restype = None
+_BNWARPFreeContainerReference.argtypes = [
+ ctypes.POINTER(BNWARPContainer),
+ ]
+
+
+# noinspection PyPep8Naming
+def BNWARPFreeContainerReference(
+ container: ctypes.POINTER(BNWARPContainer)
+ ) -> None:
+ return _BNWARPFreeContainerReference(container)
+
+
+# -------------------------------------------------------
+# _BNWARPFreeFunctionCommentList
+
+_BNWARPFreeFunctionCommentList = core.BNWARPFreeFunctionCommentList
+_BNWARPFreeFunctionCommentList.restype = None
+_BNWARPFreeFunctionCommentList.argtypes = [
+ ctypes.POINTER(BNWARPFunctionComment),
+ ctypes.c_ulonglong,
+ ]
+
+
+# noinspection PyPep8Naming
+def BNWARPFreeFunctionCommentList(
+ comments: ctypes.POINTER(BNWARPFunctionComment),
+ count: int
+ ) -> None:
+ return _BNWARPFreeFunctionCommentList(comments, count)
+
+
+# -------------------------------------------------------
+# _BNWARPFreeFunctionList
+
+_BNWARPFreeFunctionList = core.BNWARPFreeFunctionList
+_BNWARPFreeFunctionList.restype = None
+_BNWARPFreeFunctionList.argtypes = [
+ ctypes.POINTER(ctypes.POINTER(BNWARPFunction)),
+ ctypes.c_ulonglong,
+ ]
+
+
+# noinspection PyPep8Naming
+def BNWARPFreeFunctionList(
+ functions: ctypes.POINTER(ctypes.POINTER(BNWARPFunction)),
+ count: int
+ ) -> None:
+ return _BNWARPFreeFunctionList(functions, count)
+
+
+# -------------------------------------------------------
+# _BNWARPFreeFunctionReference
+
+_BNWARPFreeFunctionReference = core.BNWARPFreeFunctionReference
+_BNWARPFreeFunctionReference.restype = None
+_BNWARPFreeFunctionReference.argtypes = [
+ ctypes.POINTER(BNWARPFunction),
+ ]
+
+
+# noinspection PyPep8Naming
+def BNWARPFreeFunctionReference(
+ function: ctypes.POINTER(BNWARPFunction)
+ ) -> None:
+ return _BNWARPFreeFunctionReference(function)
+
+
+# -------------------------------------------------------
+# _BNWARPFreeTargetReference
+
+_BNWARPFreeTargetReference = core.BNWARPFreeTargetReference
+_BNWARPFreeTargetReference.restype = None
+_BNWARPFreeTargetReference.argtypes = [
+ ctypes.POINTER(BNWARPTarget),
+ ]
+
+
+# noinspection PyPep8Naming
+def BNWARPFreeTargetReference(
+ target: ctypes.POINTER(BNWARPTarget)
+ ) -> None:
+ return _BNWARPFreeTargetReference(target)
+
+
+# -------------------------------------------------------
+# _BNWARPFreeUUIDList
+
+_BNWARPFreeUUIDList = core.BNWARPFreeUUIDList
+_BNWARPFreeUUIDList.restype = None
+_BNWARPFreeUUIDList.argtypes = [
+ ctypes.POINTER(BNWARPUUID),
+ ctypes.c_ulonglong,
+ ]
+
+
+# noinspection PyPep8Naming
+def BNWARPFreeUUIDList(
+ uuids: ctypes.POINTER(BNWARPUUID),
+ count: int
+ ) -> None:
+ return _BNWARPFreeUUIDList(uuids, count)
+
+
+# -------------------------------------------------------
+# _BNWARPFunctionApply
+
+_BNWARPFunctionApply = core.BNWARPFunctionApply
+_BNWARPFunctionApply.restype = None
+_BNWARPFunctionApply.argtypes = [
+ ctypes.POINTER(BNWARPFunction),
+ ctypes.POINTER(BNFunction),
+ ]
+
+
+# noinspection PyPep8Naming
+def BNWARPFunctionApply(
+ function: ctypes.POINTER(BNWARPFunction),
+ analysisFunction: ctypes.POINTER(BNFunction)
+ ) -> None:
+ return _BNWARPFunctionApply(function, analysisFunction)
+
+
+# -------------------------------------------------------
+# _BNWARPFunctionGetComments
+
+_BNWARPFunctionGetComments = core.BNWARPFunctionGetComments
+_BNWARPFunctionGetComments.restype = ctypes.POINTER(BNWARPFunctionComment)
+_BNWARPFunctionGetComments.argtypes = [
+ ctypes.POINTER(BNWARPFunction),
+ ctypes.POINTER(ctypes.c_ulonglong),
+ ]
+
+
+# noinspection PyPep8Naming
+def BNWARPFunctionGetComments(
+ function: ctypes.POINTER(BNWARPFunction),
+ count: ctypes.POINTER(ctypes.c_ulonglong)
+ ) -> Optional[ctypes.POINTER(BNWARPFunctionComment)]:
+ result = _BNWARPFunctionGetComments(function, count)
+ if not result:
+ return None
+ return result
+
+
+# -------------------------------------------------------
+# _BNWARPFunctionGetConstraints
+
+_BNWARPFunctionGetConstraints = core.BNWARPFunctionGetConstraints
+_BNWARPFunctionGetConstraints.restype = ctypes.POINTER(BNWARPConstraint)
+_BNWARPFunctionGetConstraints.argtypes = [
+ ctypes.POINTER(BNWARPFunction),
+ ctypes.POINTER(ctypes.c_ulonglong),
+ ]
+
+
+# noinspection PyPep8Naming
+def BNWARPFunctionGetConstraints(
+ function: ctypes.POINTER(BNWARPFunction),
+ count: ctypes.POINTER(ctypes.c_ulonglong)
+ ) -> Optional[ctypes.POINTER(BNWARPConstraint)]:
+ result = _BNWARPFunctionGetConstraints(function, count)
+ if not result:
+ return None
+ return result
+
+
+# -------------------------------------------------------
+# _BNWARPFunctionGetGUID
+
+_BNWARPFunctionGetGUID = core.BNWARPFunctionGetGUID
+_BNWARPFunctionGetGUID.restype = BNWARPFunctionGUID
+_BNWARPFunctionGetGUID.argtypes = [
+ ctypes.POINTER(BNWARPFunction),
+ ]
+
+
+# noinspection PyPep8Naming
+def BNWARPFunctionGetGUID(
+ function: ctypes.POINTER(BNWARPFunction)
+ ) -> BNWARPFunctionGUID:
+ return _BNWARPFunctionGetGUID(function)
+
+
+# -------------------------------------------------------
+# _BNWARPFunctionGetSymbol
+
+_BNWARPFunctionGetSymbol = core.BNWARPFunctionGetSymbol
+_BNWARPFunctionGetSymbol.restype = ctypes.POINTER(BNSymbol)
+_BNWARPFunctionGetSymbol.argtypes = [
+ ctypes.POINTER(BNWARPFunction),
+ ctypes.POINTER(BNFunction),
+ ]
+
+
+# noinspection PyPep8Naming
+def BNWARPFunctionGetSymbol(
+ function: ctypes.POINTER(BNWARPFunction),
+ analysisFunction: ctypes.POINTER(BNFunction)
+ ) -> Optional[ctypes.POINTER(BNSymbol)]:
+ result = _BNWARPFunctionGetSymbol(function, analysisFunction)
+ if not result:
+ return None
+ return result
+
+
+# -------------------------------------------------------
+# _BNWARPFunctionGetSymbolName
+
+_BNWARPFunctionGetSymbolName = core.BNWARPFunctionGetSymbolName
+_BNWARPFunctionGetSymbolName.restype = ctypes.POINTER(ctypes.c_byte)
+_BNWARPFunctionGetSymbolName.argtypes = [
+ ctypes.POINTER(BNWARPFunction),
+ ]
+
+
+# noinspection PyPep8Naming
+def BNWARPFunctionGetSymbolName(
+ function: ctypes.POINTER(BNWARPFunction)
+ ) -> Optional[Optional[str]]:
+ result = _BNWARPFunctionGetSymbolName(function)
+ if not result:
+ return None
+ string = str(pyNativeStr(ctypes.cast(result, ctypes.c_char_p).value))
+ BNFreeString(result)
+ return string
+
+
+# -------------------------------------------------------
+# _BNWARPFunctionGetType
+
+_BNWARPFunctionGetType = core.BNWARPFunctionGetType
+_BNWARPFunctionGetType.restype = ctypes.POINTER(BNType)
+_BNWARPFunctionGetType.argtypes = [
+ ctypes.POINTER(BNWARPFunction),
+ ctypes.POINTER(BNFunction),
+ ]
+
+
+# noinspection PyPep8Naming
+def BNWARPFunctionGetType(
+ function: ctypes.POINTER(BNWARPFunction),
+ analysisFunction: ctypes.POINTER(BNFunction)
+ ) -> Optional[ctypes.POINTER(BNType)]:
+ result = _BNWARPFunctionGetType(function, analysisFunction)
+ if not result:
+ return None
+ return result
+
+
+# -------------------------------------------------------
+# _BNWARPFunctionsEqual
+
+_BNWARPFunctionsEqual = core.BNWARPFunctionsEqual
+_BNWARPFunctionsEqual.restype = ctypes.c_bool
+_BNWARPFunctionsEqual.argtypes = [
+ ctypes.POINTER(BNWARPFunction),
+ ctypes.POINTER(BNWARPFunction),
+ ]
+
+
+# noinspection PyPep8Naming
+def BNWARPFunctionsEqual(
+ functionA: ctypes.POINTER(BNWARPFunction),
+ functionB: ctypes.POINTER(BNWARPFunction)
+ ) -> bool:
+ return _BNWARPFunctionsEqual(functionA, functionB)
+
+
+# -------------------------------------------------------
+# _BNWARPGetAnalysisFunctionGUID
+
+_BNWARPGetAnalysisFunctionGUID = core.BNWARPGetAnalysisFunctionGUID
+_BNWARPGetAnalysisFunctionGUID.restype = ctypes.c_bool
+_BNWARPGetAnalysisFunctionGUID.argtypes = [
+ ctypes.POINTER(BNFunction),
+ ctypes.POINTER(BNWARPFunctionGUID),
+ ]
+
+
+# noinspection PyPep8Naming
+def BNWARPGetAnalysisFunctionGUID(
+ analysisFunction: ctypes.POINTER(BNFunction),
+ result: ctypes.POINTER(BNWARPFunctionGUID)
+ ) -> bool:
+ return _BNWARPGetAnalysisFunctionGUID(analysisFunction, result)
+
+
+# -------------------------------------------------------
+# _BNWARPGetBasicBlockGUID
+
+_BNWARPGetBasicBlockGUID = core.BNWARPGetBasicBlockGUID
+_BNWARPGetBasicBlockGUID.restype = ctypes.c_bool
+_BNWARPGetBasicBlockGUID.argtypes = [
+ ctypes.POINTER(BNBasicBlock),
+ ctypes.POINTER(BNWARPBasicBlockGUID),
+ ]
+
+
+# noinspection PyPep8Naming
+def BNWARPGetBasicBlockGUID(
+ basicBlock: ctypes.POINTER(BNBasicBlock),
+ result: ctypes.POINTER(BNWARPBasicBlockGUID)
+ ) -> bool:
+ return _BNWARPGetBasicBlockGUID(basicBlock, result)
+
+
+# -------------------------------------------------------
+# _BNWARPGetContainers
+
+_BNWARPGetContainers = core.BNWARPGetContainers
+_BNWARPGetContainers.restype = ctypes.POINTER(ctypes.POINTER(BNWARPContainer))
+_BNWARPGetContainers.argtypes = [
+ ctypes.POINTER(ctypes.c_ulonglong),
+ ]
+
+
+# noinspection PyPep8Naming
+def BNWARPGetContainers(
+ count: ctypes.POINTER(ctypes.c_ulonglong)
+ ) -> Optional[ctypes.POINTER(ctypes.POINTER(BNWARPContainer))]:
+ result = _BNWARPGetContainers(count)
+ if not result:
+ return None
+ return result
+
+
+# -------------------------------------------------------
+# _BNWARPGetFunction
+
+_BNWARPGetFunction = core.BNWARPGetFunction
+_BNWARPGetFunction.restype = ctypes.POINTER(BNWARPFunction)
+_BNWARPGetFunction.argtypes = [
+ ctypes.POINTER(BNFunction),
+ ]
+
+
+# noinspection PyPep8Naming
+def BNWARPGetFunction(
+ analysisFunction: ctypes.POINTER(BNFunction)
+ ) -> Optional[ctypes.POINTER(BNWARPFunction)]:
+ result = _BNWARPGetFunction(analysisFunction)
+ if not result:
+ return None
+ return result
+
+
+# -------------------------------------------------------
+# _BNWARPGetMatchedFunction
+
+_BNWARPGetMatchedFunction = core.BNWARPGetMatchedFunction
+_BNWARPGetMatchedFunction.restype = ctypes.POINTER(BNWARPFunction)
+_BNWARPGetMatchedFunction.argtypes = [
+ ctypes.POINTER(BNFunction),
+ ]
+
+
+# noinspection PyPep8Naming
+def BNWARPGetMatchedFunction(
+ analysisFunction: ctypes.POINTER(BNFunction)
+ ) -> Optional[ctypes.POINTER(BNWARPFunction)]:
+ result = _BNWARPGetMatchedFunction(analysisFunction)
+ if not result:
+ return None
+ return result
+
+
+# -------------------------------------------------------
+# _BNWARPGetTarget
+
+_BNWARPGetTarget = core.BNWARPGetTarget
+_BNWARPGetTarget.restype = ctypes.POINTER(BNWARPTarget)
+_BNWARPGetTarget.argtypes = [
+ ctypes.POINTER(BNPlatform),
+ ]
+
+
+# noinspection PyPep8Naming
+def BNWARPGetTarget(
+ platform: ctypes.POINTER(BNPlatform)
+ ) -> Optional[ctypes.POINTER(BNWARPTarget)]:
+ result = _BNWARPGetTarget(platform)
+ if not result:
+ return None
+ return result
+
+
+# -------------------------------------------------------
+# _BNWARPIsLiftedInstructionBlacklisted
+
+_BNWARPIsLiftedInstructionBlacklisted = core.BNWARPIsLiftedInstructionBlacklisted
+_BNWARPIsLiftedInstructionBlacklisted.restype = ctypes.c_bool
+_BNWARPIsLiftedInstructionBlacklisted.argtypes = [
+ ctypes.POINTER(BNLowLevelILFunction),
+ ctypes.c_ulonglong,
+ ]
+
+
+# noinspection PyPep8Naming
+def BNWARPIsLiftedInstructionBlacklisted(
+ liftedFunction: ctypes.POINTER(BNLowLevelILFunction),
+ idx: int
+ ) -> bool:
+ return _BNWARPIsLiftedInstructionBlacklisted(liftedFunction, idx)
+
+
+# -------------------------------------------------------
+# _BNWARPIsLiftedInstructionVariant
+
+_BNWARPIsLiftedInstructionVariant = core.BNWARPIsLiftedInstructionVariant
+_BNWARPIsLiftedInstructionVariant.restype = ctypes.c_bool
+_BNWARPIsLiftedInstructionVariant.argtypes = [
+ ctypes.POINTER(BNLowLevelILFunction),
+ ctypes.c_ulonglong,
+ ]
+
+
+# noinspection PyPep8Naming
+def BNWARPIsLiftedInstructionVariant(
+ liftedFunction: ctypes.POINTER(BNLowLevelILFunction),
+ idx: int
+ ) -> bool:
+ return _BNWARPIsLiftedInstructionVariant(liftedFunction, idx)
+
+
+# -------------------------------------------------------
+# _BNWARPNewContainerReference
+
+_BNWARPNewContainerReference = core.BNWARPNewContainerReference
+_BNWARPNewContainerReference.restype = ctypes.POINTER(BNWARPContainer)
+_BNWARPNewContainerReference.argtypes = [
+ ctypes.POINTER(BNWARPContainer),
+ ]
+
+
+# noinspection PyPep8Naming
+def BNWARPNewContainerReference(
+ container: ctypes.POINTER(BNWARPContainer)
+ ) -> Optional[ctypes.POINTER(BNWARPContainer)]:
+ result = _BNWARPNewContainerReference(container)
+ if not result:
+ return None
+ return result
+
+
+# -------------------------------------------------------
+# _BNWARPNewFunctionReference
+
+_BNWARPNewFunctionReference = core.BNWARPNewFunctionReference
+_BNWARPNewFunctionReference.restype = ctypes.POINTER(BNWARPFunction)
+_BNWARPNewFunctionReference.argtypes = [
+ ctypes.POINTER(BNWARPFunction),
+ ]
+
+
+# noinspection PyPep8Naming
+def BNWARPNewFunctionReference(
+ function: ctypes.POINTER(BNWARPFunction)
+ ) -> Optional[ctypes.POINTER(BNWARPFunction)]:
+ result = _BNWARPNewFunctionReference(function)
+ if not result:
+ return None
+ return result
+
+
+# -------------------------------------------------------
+# _BNWARPNewTargetReference
+
+_BNWARPNewTargetReference = core.BNWARPNewTargetReference
+_BNWARPNewTargetReference.restype = ctypes.POINTER(BNWARPTarget)
+_BNWARPNewTargetReference.argtypes = [
+ ctypes.POINTER(BNWARPTarget),
+ ]
+
+
+# noinspection PyPep8Naming
+def BNWARPNewTargetReference(
+ target: ctypes.POINTER(BNWARPTarget)
+ ) -> Optional[ctypes.POINTER(BNWARPTarget)]:
+ result = _BNWARPNewTargetReference(target)
+ if not result:
+ return None
+ return result
+
+
+# -------------------------------------------------------
+# _BNWARPRunMatcher
+
+_BNWARPRunMatcher = core.BNWARPRunMatcher
+_BNWARPRunMatcher.restype = None
+_BNWARPRunMatcher.argtypes = [
+ ctypes.POINTER(BNBinaryView),
+ ]
+
+
+# noinspection PyPep8Naming
+def BNWARPRunMatcher(
+ view: ctypes.POINTER(BNBinaryView)
+ ) -> None:
+ return _BNWARPRunMatcher(view)
+
+
+# -------------------------------------------------------
+# _BNWARPUUIDEqual
+
+_BNWARPUUIDEqual = core.BNWARPUUIDEqual
+_BNWARPUUIDEqual.restype = ctypes.c_bool
+_BNWARPUUIDEqual.argtypes = [
+ ctypes.POINTER(BNWARPUUID),
+ ctypes.POINTER(BNWARPUUID),
+ ]
+
+
+# noinspection PyPep8Naming
+def BNWARPUUIDEqual(
+ a: ctypes.POINTER(BNWARPUUID),
+ b: ctypes.POINTER(BNWARPUUID)
+ ) -> bool:
+ return _BNWARPUUIDEqual(a, b)
+
+
+# -------------------------------------------------------
+# _BNWARPUUIDGetString
+
+_BNWARPUUIDGetString = core.BNWARPUUIDGetString
+_BNWARPUUIDGetString.restype = ctypes.POINTER(ctypes.c_byte)
+_BNWARPUUIDGetString.argtypes = [
+ ctypes.POINTER(BNWARPUUID),
+ ]
+
+
+# noinspection PyPep8Naming
+def BNWARPUUIDGetString(
+ uuid: ctypes.POINTER(BNWARPUUID)
+ ) -> Optional[Optional[str]]:
+ result = _BNWARPUUIDGetString(uuid)
+ if not result:
+ return None
+ string = str(pyNativeStr(ctypes.cast(result, ctypes.c_char_p).value))
+ BNFreeString(result)
+ return string
+
+
+
+# Helper functions
+def handle_of_type(value, handle_type):
+ if isinstance(value, ctypes.POINTER(handle_type)) or isinstance(value, ctypes.c_void_p):
+ return ctypes.cast(value, ctypes.POINTER(handle_type))
+ raise ValueError('expected pointer to %s' % str(handle_type))
diff --git a/plugins/warp/api/python/_warpcore_template.py b/plugins/warp/api/python/_warpcore_template.py
new file mode 100644
index 00000000..fd923450
--- /dev/null
+++ b/plugins/warp/api/python/_warpcore_template.py
@@ -0,0 +1,58 @@
+import binaryninja
+import ctypes, os
+
+from typing import Optional
+from . import warp_enums
+# Load core module
+import platform
+core = None
+core_platform = platform.system()
+
+from binaryninja import Settings
+if Settings().get_bool("corePlugins.warp"):
+ from binaryninja._binaryninjacore import BNGetBundledPluginDirectory
+ if core_platform == "Darwin":
+ _base_path = BNGetBundledPluginDirectory()
+ core = ctypes.CDLL(os.path.join(_base_path, "libwarp_ninja.dylib"))
+
+ elif core_platform == "Linux":
+ _base_path = BNGetBundledPluginDirectory()
+ core = ctypes.CDLL(os.path.join(_base_path, "libwarp_ninja.so"))
+
+ elif (core_platform == "Windows") or (core_platform.find("CYGWIN_NT") == 0):
+ _base_path = BNGetBundledPluginDirectory()
+ core = ctypes.CDLL(os.path.join(_base_path, "warp_ninja.dll"))
+ else:
+ raise Exception("OS not supported")
+else:
+ from binaryninja._binaryninjacore import BNGetUserPluginDirectory
+ if core_platform == "Darwin":
+ _base_path = BNGetUserPluginDirectory()
+ core = ctypes.CDLL(os.path.join(_base_path, "libwarp_ninja.dylib"))
+
+ elif core_platform == "Linux":
+ _base_path = BNGetUserPluginDirectory()
+ core = ctypes.CDLL(os.path.join(_base_path, "libwarp_ninja.so"))
+
+ elif (core_platform == "Windows") or (core_platform.find("CYGWIN_NT") == 0):
+ _base_path = BNGetUserPluginDirectory()
+ core = ctypes.CDLL(os.path.join(_base_path, "warp_ninja.dll"))
+ else:
+ raise Exception("OS not supported")
+
+def cstr(var) -> Optional[ctypes.c_char_p]:
+ if var is None:
+ return None
+ if isinstance(var, bytes):
+ return var
+ return var.encode("utf-8")
+
+def pyNativeStr(arg):
+ if isinstance(arg, str):
+ return arg
+ else:
+ return arg.decode('utf8')
+
+def free_string(value:ctypes.c_char_p) -> None:
+ BNFreeString(ctypes.cast(value, ctypes.POINTER(ctypes.c_byte)))
+
diff --git a/plugins/warp/api/python/generator.cpp b/plugins/warp/api/python/generator.cpp
new file mode 100644
index 00000000..f40fc01a
--- /dev/null
+++ b/plugins/warp/api/python/generator.cpp
@@ -0,0 +1,652 @@
+/*
+Copyright 2020-2025 Vector 35 Inc.
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+
+
+#include <stdio.h>
+#include <inttypes.h>
+#include "binaryninjaapi.h"
+
+using namespace BinaryNinja;
+using namespace std;
+
+
+map<string, string> g_pythonKeywordReplacements = {
+ {"False", "False_"},
+ {"True", "True_"},
+ {"None", "None_"},
+ {"and", "and_"},
+ {"as", "as_"},
+ {"assert", "assert_"},
+ {"async", "async_"},
+ {"await", "await_"},
+ {"break", "break_"},
+ {"class", "class_"},
+ {"continue", "continue_"},
+ {"def", "def_"},
+ {"del", "del_"},
+ {"elif", "elif_"},
+ {"else", "else_"},
+ {"except", "except_"},
+ {"finally", "finally_"},
+ {"for", "for_"},
+ {"from", "from_"},
+ {"global", "global_"},
+ {"if", "if_"},
+ {"import", "import_"},
+ {"in", "in_"},
+ {"is", "is_"},
+ {"lambda", "lambda_"},
+ {"nonlocal", "nonlocal_"},
+ {"not", "not_"},
+ {"or", "or_"},
+ {"pass", "pass_"},
+ {"raise", "raise_"},
+ {"return", "return_"},
+ {"try", "try_"},
+ {"while", "while_"},
+ {"with", "with_"},
+ {"yield", "yield_"},
+};
+
+
+void OutputType(FILE* out, Type* type, bool isReturnType = false, bool isCallback = false)
+{
+ switch (type->GetClass())
+ {
+ case BoolTypeClass:
+ fprintf(out, "ctypes.c_bool");
+ break;
+ case IntegerTypeClass:
+ switch (type->GetWidth())
+ {
+ case 1:
+ if (type->IsSigned())
+ fprintf(out, "ctypes.c_byte");
+ else
+ fprintf(out, "ctypes.c_ubyte");
+ break;
+ case 2:
+ if (type->IsSigned())
+ fprintf(out, "ctypes.c_short");
+ else
+ fprintf(out, "ctypes.c_ushort");
+ break;
+ case 4:
+ if (type->IsSigned())
+ fprintf(out, "ctypes.c_int");
+ else
+ fprintf(out, "ctypes.c_uint");
+ break;
+ default:
+ if (type->IsSigned())
+ fprintf(out, "ctypes.c_longlong");
+ else
+ fprintf(out, "ctypes.c_ulonglong");
+ break;
+ }
+ break;
+ case FloatTypeClass:
+ if (type->GetWidth() == 4)
+ fprintf(out, "ctypes.c_float");
+ else
+ fprintf(out, "ctypes.c_double");
+ break;
+ case NamedTypeReferenceClass:
+ if (type->GetNamedTypeReference()->GetTypeReferenceClass() == EnumNamedTypeClass)
+ {
+ string name = type->GetNamedTypeReference()->GetName().GetString();
+ if (name.size() > 16 && name.substr(0, 11) == "_BNDebugger")
+ name = name.substr(3);
+ else if (name.size() > 15 && name.substr(0, 10) == "BNDebugger")
+ name = name.substr(2);
+ else if (name.size() > 15 && name.substr(0, 7) == "BNDebug")
+ name = name.substr(2);
+ else if (name.size() > 2 && name.substr(0, 2) == "BN")
+ name = name.substr(2);
+ fprintf(out, "%sEnum", name.c_str());
+ }
+ else
+ {
+ fprintf(out, "%s", type->GetNamedTypeReference()->GetName().GetString().c_str());
+ }
+ break;
+ case PointerTypeClass:
+ if (isCallback || (type->GetChildType()->GetClass() == VoidTypeClass))
+ {
+ fprintf(out, "ctypes.c_void_p");
+ break;
+ }
+ else if ((type->GetChildType()->GetClass() == IntegerTypeClass) &&
+ (type->GetChildType()->GetWidth() == 1) && (type->GetChildType()->IsSigned()))
+ {
+ if (isReturnType)
+ fprintf(out, "ctypes.POINTER(ctypes.c_byte)");
+ else
+ fprintf(out, "ctypes.c_char_p");
+ break;
+ }
+ else if (type->GetChildType()->GetClass() == FunctionTypeClass)
+ {
+ fprintf(out, "ctypes.CFUNCTYPE(");
+ OutputType(out, type->GetChildType()->GetChildType().GetValue(), true, true);
+ for (auto& i : type->GetChildType()->GetParameters())
+ {
+ fprintf(out, ", ");
+ OutputType(out, i.type.GetValue());
+ }
+ fprintf(out, ")");
+ break;
+ }
+ fprintf(out, "ctypes.POINTER(");
+ OutputType(out, type->GetChildType().GetValue());
+ fprintf(out, ")");
+ break;
+ case ArrayTypeClass:
+ OutputType(out, type->GetChildType().GetValue());
+ fprintf(out, " * %" PRId64, type->GetElementCount());
+ break;
+ default:
+ fprintf(out, "None");
+ break;
+ }
+}
+
+
+void OutputSwizzledType(FILE* out, Type* type)
+{
+ switch (type->GetClass())
+ {
+ case BoolTypeClass:
+ fprintf(out, "bool");
+ break;
+ case IntegerTypeClass:
+ fprintf(out, "int");
+ break;
+ case FloatTypeClass:
+ fprintf(out, "float");
+ break;
+ case NamedTypeReferenceClass:
+ if (type->GetNamedTypeReference()->GetTypeReferenceClass() == EnumNamedTypeClass)
+ {
+ string name = type->GetNamedTypeReference()->GetName().GetString();
+ if (name.size() > 16 && name.substr(0, 11) == "_BNDebugger")
+ name = name.substr(3);
+ else if (name.size() > 15 && name.substr(0, 10) == "BNDebugger")
+ name = name.substr(2);
+ else if (name.size() > 15 && name.substr(0, 7) == "BNDebug")
+ name = name.substr(2);
+ else if (name.size() > 2 && name.substr(0, 2) == "BN")
+ name = name.substr(2);
+ fprintf(out, "%sEnum", name.c_str());
+ }
+ else
+ {
+ fprintf(out, "%s", type->GetNamedTypeReference()->GetName().GetString().c_str());
+ }
+ break;
+ case PointerTypeClass:
+ if (type->GetChildType()->GetClass() == VoidTypeClass)
+ {
+ fprintf(out, "Optional[ctypes.c_void_p]");
+ break;
+ }
+ else if ((type->GetChildType()->GetClass() == IntegerTypeClass) &&
+ (type->GetChildType()->GetWidth() == 1) && (type->GetChildType()->IsSigned()))
+ {
+ fprintf(out, "Optional[str]");
+ break;
+ }
+ else if (type->GetChildType()->GetClass() == FunctionTypeClass)
+ {
+ fprintf(out, "ctypes.CFUNCTYPE(");
+ OutputType(out, type->GetChildType()->GetChildType().GetValue(), true, true);
+ for (auto& i : type->GetChildType()->GetParameters())
+ {
+ fprintf(out, ", ");
+ OutputType(out, i.type.GetValue());
+ }
+ fprintf(out, ")");
+ break;
+ }
+ fprintf(out, "ctypes.POINTER(");
+ OutputType(out, type->GetChildType().GetValue());
+ fprintf(out, ")");
+ break;
+ case ArrayTypeClass:
+ OutputType(out, type->GetChildType().GetValue());
+ fprintf(out, " * %" PRId64, type->GetElementCount());
+ break;
+ default:
+ fprintf(out, "None");
+ break;
+ }
+}
+
+
+int main(int argc, char* argv[])
+{
+ if (argc < 5)
+ {
+ fprintf(stderr, "Usage: generator <header> <output> <output_template> <output_enum>\n");
+ return 1;
+ }
+
+ // Parse API header to get type and function information
+ map<QualifiedName, Ref<Type>> types, vars, funcs;
+ string errors;
+ auto arch = new CoreArchitecture(BNGetNativeTypeParserArchitecture());
+
+ // Enable ephemeral settings
+ Settings::Instance()->LoadSettingsFile("");
+ Settings::Instance()->Set("analysis.types.parserName", "ClangTypeParser");
+ bool ok = arch->GetStandalonePlatform()->ParseTypesFromSourceFile(argv[1], types, vars, funcs, errors);
+
+ if (!ok)
+ {
+ fprintf(stderr, "Errors: %s\n", errors.c_str());
+ return 1;
+ }
+
+ FILE* out = fopen(argv[2], "w");
+ FILE* out_template = fopen(argv[3], "r");
+ FILE* enums = fopen(argv[4], "w");
+
+ fprintf(enums, "import enum\n");
+
+ // Copy the content of the template to the output file
+ int c;
+ while((c = fgetc(out_template)) != EOF)
+ fputc(c, out);
+
+ // Create type objects
+ fprintf(out, "from binaryninja._binaryninjacore import BNFreeString\n");
+ fprintf(out, "# Type definitions\n");
+ for (auto& i : types)
+ {
+ string name;
+ if (i.first.size() != 1)
+ continue;
+ name = i.first[0];
+ if (name == "BNBinaryView")
+ {
+ fprintf(out, "from binaryninja._binaryninjacore import BNBinaryView, BNBinaryViewHandle\n");
+ continue;
+ }
+ if (name == "BNArchitecture")
+ {
+ fprintf(out, "from binaryninja._binaryninjacore import BNArchitecture, BNArchitectureHandle\n");
+ continue;
+ }
+ if (name == "BNBasicBlock")
+ {
+ fprintf(out, "from binaryninja._binaryninjacore import BNBasicBlock, BNBasicBlockHandle\n");
+ continue;
+ }
+ if (name == "BNFunction")
+ {
+ fprintf(out, "from binaryninja._binaryninjacore import BNFunction, BNFunctionHandle\n");
+ continue;
+ }
+ if (name == "BNLowLevelILFunction")
+ {
+ fprintf(out, "from binaryninja._binaryninjacore import BNLowLevelILFunction, BNLowLevelILFunctionHandle\n");
+ continue;
+ }
+ if (name == "BNPlatform")
+ {
+ fprintf(out, "from binaryninja._binaryninjacore import BNPlatform, BNPlatformHandle\n");
+ continue;
+ }
+ if (name == "BNSymbol")
+ {
+ fprintf(out, "from binaryninja._binaryninjacore import BNSymbol, BNSymbolHandle\n");
+ continue;
+ }
+ if (name == "BNType")
+ {
+ fprintf(out, "from binaryninja._binaryninjacore import BNType, BNTypeHandle\n");
+ continue;
+ }
+ if (i.second->GetClass() == StructureTypeClass)
+ {
+ fprintf(out, "class %s(ctypes.Structure):\n", name.c_str());
+
+ // python uses str's, C uses byte-arrays
+ bool stringField = false;
+ for (auto& arg : i.second->GetStructure()->GetMembers())
+ {
+ if ((arg.type->GetClass() == PointerTypeClass) &&
+ (arg.type->GetChildType()->GetWidth() == 1) &&
+ (arg.type->GetChildType()->IsSigned()))
+ {
+ fprintf(out, "\t@property\n\tdef %s(self):\n\t\treturn pyNativeStr(self._%s)\n", arg.name.c_str(), arg.name.c_str());
+ fprintf(out, "\t@%s.setter\n\tdef %s(self, value):\n\t\tself._%s = cstr(value)\n", arg.name.c_str(), arg.name.c_str(), arg.name.c_str());
+ stringField = true;
+ }
+ }
+
+ if (!stringField)
+ fprintf(out, "\tpass\n");
+
+ fprintf(out, "%sHandle = ctypes.POINTER(%s)\n", name.c_str(), name.c_str());
+ }
+ else if (i.second->GetClass() == EnumerationTypeClass)
+ {
+ bool isBNAPIEnum = false;
+ if (name.size() > 16 && name.substr(0, 11) == "_BNDebugger")
+ name = name.substr(3);
+ else if (name.size() > 15 && name.substr(0, 10) == "BNDebugger")
+ name = name.substr(2);
+ else if (name.size() > 15 && name.substr(0, 7) == "BNDebug")
+ name = name.substr(2);
+ else if (name.size() > 2 && name.substr(0, 2) == "BN")
+ {
+ name = name.substr(2);
+ isBNAPIEnum = false;
+ }
+ else
+ continue;
+
+ if (isBNAPIEnum)
+ {
+ fprintf(out, "from binaryninja._binaryninjacore import %sEnum\n", name.c_str());
+ continue;
+ }
+
+ fprintf(out, "%sEnum = ctypes.c_int\n", name.c_str());
+
+ fprintf(enums, "\n\nclass %s(enum.IntEnum):\n", name.c_str());
+ for (auto& j : i.second->GetEnumeration()->GetMembers())
+ {
+ fprintf(enums, "\t%s = %" PRId64 "\n", j.name.c_str(), j.value);
+ }
+ }
+ else if ((i.second->GetClass() == BoolTypeClass) || (i.second->GetClass() == IntegerTypeClass) ||
+ (i.second->GetClass() == FloatTypeClass) || (i.second->GetClass() == ArrayTypeClass))
+ {
+ fprintf(out, "%s = ", name.c_str());
+ OutputType(out, i.second);
+ fprintf(out, "\n");
+ }
+ }
+
+
+ fprintf(out, "\n# Structure definitions\n");
+ set<QualifiedName> structsToProcess;
+ set<QualifiedName> finishedStructs;
+ for (auto& i : types)
+ structsToProcess.insert(i.first);
+ while (structsToProcess.size() != 0)
+ {
+ set<QualifiedName> currentStructList = structsToProcess;
+ structsToProcess.clear();
+ bool processedSome = false;
+ for (auto& i : currentStructList)
+ {
+ string name;
+ if (i.size() != 1)
+ continue;
+ Ref<Type> type = types[i];
+ name = i[0];
+ if ((type->GetClass() == StructureTypeClass) && (type->GetStructure()->GetMembers().size() != 0))
+ {
+ bool requiresDependency = false;
+ for (auto& j : type->GetStructure()->GetMembers())
+ {
+ if ((j.type->GetClass() == NamedTypeReferenceClass) &&
+ (finishedStructs.count(j.type->GetNamedTypeReference()->GetName()) == 0))
+ {
+ // This structure needs another structure that isn't fully defined yet, need to wait
+ // for the dependencies to be defined
+ structsToProcess.insert(i);
+ requiresDependency = true;
+ break;
+ }
+ }
+
+ if (requiresDependency)
+ continue;
+ fprintf(out, "%s._fields_ = [\n", name.c_str());
+ for (auto& j : type->GetStructure()->GetMembers())
+ {
+ // To help the python->C wrappers
+ if ((j.type->GetClass() == PointerTypeClass) &&
+ (j.type->GetChildType()->GetWidth() == 1) &&
+ (j.type->GetChildType()->IsSigned()))
+ {
+ fprintf(out, "\t\t(\"_%s\", ", j.name.c_str());
+ }
+ else
+ fprintf(out, "\t\t(\"%s\", ", j.name.c_str());
+ OutputType(out, j.type.GetValue());
+ fprintf(out, "),\n");
+ }
+ fprintf(out, "\t]\n");
+ finishedStructs.insert(i);
+ processedSome = true;
+ }
+ else if (type->GetClass() == NamedTypeReferenceClass)
+ {
+ if (type->GetNamedTypeReference()->GetTypeReferenceClass() == StructNamedTypeClass)
+ {
+ fprintf(out, "%s = %s\n", name.c_str(), type->GetNamedTypeReference()->GetName().GetString().c_str());
+ fprintf(out, "%sHandle = %sHandle\n", name.c_str(), type->GetNamedTypeReference()->GetName().GetString().c_str());
+ }
+ else if (type->GetNamedTypeReference()->GetTypeReferenceClass() == EnumNamedTypeClass)
+ {
+ fprintf(out, "%s = ctypes.c_int\n", name.c_str());
+ }
+ finishedStructs.insert(i);
+ processedSome = true;
+ }
+ }
+
+ if (!processedSome && structsToProcess.size() != 0)
+ {
+ fprintf(stderr, "Detected dependency cycle in structures\n");
+ for (auto& i : structsToProcess)
+ fprintf(stderr, "%s\n", i.GetString().c_str());
+ return 1;
+ }
+ }
+
+ fprintf(out, "\n# Function definitions\n");
+ for (auto& i : funcs)
+ {
+ string name;
+ if (i.first.size() != 1)
+ continue;
+ name = i.first[0];
+
+ // Check for a string result, these will be automatically wrapped to free the string
+ // memory and return a Python string
+ bool stringResult = (i.second->GetChildType()->GetClass() == PointerTypeClass) &&
+ (i.second->GetChildType()->GetChildType()->GetWidth() == 1) &&
+ (i.second->GetChildType()->GetChildType()->IsSigned());
+ // Pointer returns will be automatically wrapped to return None on null pointer
+ bool pointerResult = (i.second->GetChildType()->GetClass() == PointerTypeClass);
+
+ // From python -> C python3 requires str -> str.encode('charmap')
+ bool swizzleArgs = true;
+ if (name == "BNFreeString")
+ swizzleArgs = false;
+
+ bool callbackConvention = false;
+ if (name == "BNAllocString")
+ {
+ // Don't perform automatic wrapping of string allocation, and return a void
+ // pointer so that callback functions (which is the only valid use of BNDebuggerAllocString)
+ // can properly return the result
+ stringResult = false;
+ callbackConvention = true;
+ swizzleArgs = false;
+ }
+
+ string funcName = string("_") + name;
+
+ fprintf(out, "# -------------------------------------------------------\n");
+ fprintf(out, "# %s\n\n", funcName.c_str());
+ fprintf(out, "%s = core.%s\n", funcName.c_str(), name.c_str());
+ fprintf(out, "%s.restype = ", funcName.c_str());
+ OutputType(out, i.second->GetChildType().GetValue(), true, callbackConvention);
+ fprintf(out, "\n");
+ if (!i.second->HasVariableArguments())
+ {
+ fprintf(out, "%s.argtypes = [\n", funcName.c_str());
+ for (auto& j : i.second->GetParameters())
+ {
+ fprintf(out, "\t\t");
+ if (name == "BNFreeString")
+ {
+ // BNDebuggerFreeString expects a pointer to a string allocated by the core, so do not use
+ // a c_char_p here, as that would be allocated by the Python runtime. This can
+ // be enforced by outputting like a return value.
+ OutputType(out, j.type.GetValue(), true);
+ }
+ else
+ {
+ OutputType(out, j.type.GetValue());
+ }
+ fprintf(out, ",\n");
+ }
+ fprintf(out, "\t]");
+ }
+ else
+ {
+ // As of writing this, only BNLog's have variable instruction lengths, but in an attempt not to break in the future:
+ if (funcName.compare(0, 6, "_BNLog") == 0)
+ {
+ if (funcName != "_BNLog")
+ {
+ fprintf(out, "def %s(*args):\n", name.c_str());
+ fprintf(out, "\treturn %s(*[cstr(arg) for arg in args])\n\n", funcName.c_str());
+ continue;
+ }
+ else
+ {
+ fprintf(out, "def %s(level, *args):\n", name.c_str());
+ fprintf(out, "\treturn %s(level, *[cstr(arg) for arg in args])\n\n", funcName.c_str());
+ continue;
+ }
+ }
+ }
+ fprintf(out, "\n\n\n# noinspection PyPep8Naming\n");
+ fprintf(out, "def %s(", name.c_str());
+ if (!i.second->HasVariableArguments())
+ {
+ size_t argN = 0;
+ for (auto& arg: i.second->GetParameters())
+ {
+ string argName = arg.name;
+ if (g_pythonKeywordReplacements.find(argName) != g_pythonKeywordReplacements.end())
+ argName = g_pythonKeywordReplacements[argName];
+
+ if (argName.empty())
+ argName = "arg" + to_string(argN);
+
+ if (argN > 0)
+ fprintf(out, ", ");
+ fprintf(out, "\n\t\t");
+ fprintf(out, "%s: ", argName.c_str());
+ if (swizzleArgs)
+ OutputSwizzledType(out, arg.type.GetValue());
+ else
+ OutputType(out, arg.type.GetValue());
+ argN ++;
+ }
+ }
+ fprintf(out, "\n\t\t) -> ");
+ if (swizzleArgs)
+ {
+ if (stringResult || pointerResult)
+ fprintf(out, "Optional[");
+ OutputSwizzledType(out, i.second->GetChildType().GetValue());
+ if (stringResult || pointerResult)
+ fprintf(out, "]");
+ }
+ else
+ {
+ OutputType(out, i.second->GetChildType().GetValue());
+ }
+ fprintf(out, ":\n");
+
+ string stringArgFuncCall = funcName + "(";
+ size_t argN = 0;
+ for (auto& arg : i.second->GetParameters())
+ {
+ string argName = arg.name;
+ if (g_pythonKeywordReplacements.find(argName) != g_pythonKeywordReplacements.end())
+ argName = g_pythonKeywordReplacements[argName];
+
+ if (argName.empty())
+ argName = "arg" + to_string(argN);
+
+ if (swizzleArgs && (arg.type->GetClass() == PointerTypeClass) &&
+ (arg.type->GetChildType()->GetClass() == IntegerTypeClass) &&
+ (arg.type->GetChildType()->GetWidth() == 1) &&
+ (arg.type->GetChildType()->IsSigned()))
+ {
+ stringArgFuncCall += string("cstr(") + argName + "), ";
+ }
+ else
+ {
+ stringArgFuncCall += argName + ", ";
+ }
+ argN++;
+ }
+ if (argN > 0)
+ stringArgFuncCall = stringArgFuncCall.substr(0, stringArgFuncCall.size()-2);
+ stringArgFuncCall += ")";
+
+ if (stringResult)
+ {
+ // Emit wrapper to get Python string and free native memory
+ fprintf(out, "\tresult = ");
+ fprintf(out, "%s\n", stringArgFuncCall.c_str());
+ fprintf(out, "\tif not result:\n");
+ fprintf(out, "\t\treturn None\n");
+ fprintf(out, "\tstring = str(pyNativeStr(ctypes.cast(result, ctypes.c_char_p).value))\n");
+ fprintf(out, "\tBNFreeString(result)\n");
+ fprintf(out, "\treturn string\n");
+ }
+ else if (pointerResult)
+ {
+ // Emit wrapper to return None on null pointer
+ fprintf(out, "\tresult = ");
+ fprintf(out, "%s\n", stringArgFuncCall.c_str());
+ fprintf(out, "\tif not result:\n");
+ fprintf(out, "\t\treturn None\n");
+ fprintf(out, "\treturn result\n");
+ }
+ else
+ {
+ fprintf(out, "\treturn ");
+ fprintf(out, "%s\n", stringArgFuncCall.c_str());
+ }
+ fprintf(out, "\n\n");
+ }
+
+ fprintf(out, "\n# Helper functions\n");
+ fprintf(out, "def handle_of_type(value, handle_type):\n");
+ fprintf(out, "\tif isinstance(value, ctypes.POINTER(handle_type)) or isinstance(value, ctypes.c_void_p):\n");
+ fprintf(out, "\t\treturn ctypes.cast(value, ctypes.POINTER(handle_type))\n");
+ fprintf(out, "\traise ValueError('expected pointer to %%s' %% str(handle_type))\n");
+
+ fclose(out);
+ fclose(enums);
+ return 0;
+}
diff --git a/plugins/warp/api/python/warp.py b/plugins/warp/api/python/warp.py
new file mode 100644
index 00000000..e0500215
--- /dev/null
+++ b/plugins/warp/api/python/warp.py
@@ -0,0 +1,384 @@
+import ctypes
+import dataclasses
+import uuid
+from typing import List, Optional, Union
+
+import binaryninja
+from binaryninja import BinaryView, Function, BasicBlock, Architecture, Platform, Type, Symbol, LowLevelILInstruction, LowLevelILFunction
+from binaryninja._binaryninjacore import BNFreeString, BNAllocString, BNType
+
+from . import _warpcore as warpcore
+
+
+class WarpUUID:
+ def __init__(self, _uuid: Union[warpcore.BNWARPUUID, str, uuid.UUID]):
+ if isinstance(_uuid, str):
+ _uuid = uuid.UUID(_uuid)
+ if isinstance(_uuid, uuid.UUID):
+ uuid_bytes = _uuid.bytes
+ _uuid = warpcore.BNWARPUUID()
+ _uuid.uuid = (ctypes.c_ubyte * 16).from_buffer_copy(uuid_bytes)
+ elif isinstance(_uuid, warpcore.BNWARPUUID):
+ # We must create a copy!
+ new_uuid = warpcore.BNWARPUUID()
+ new_uuid.uuid = (ctypes.c_ubyte * 16).from_buffer_copy(_uuid.uuid)
+ _uuid = new_uuid
+ self._uuid = _uuid
+
+ def to_string(self) -> str:
+ return warpcore.BNWARPUUIDGetString(self._uuid)
+
+ def __str__(self):
+ return self.to_string()
+
+ def __repr__(self):
+ return f"<WarpUUID '{str(self)}'>"
+
+ def __hash__(self):
+ # Hash based on the UUID bytes
+ return hash(bytes(self._uuid.uuid))
+
+ def __eq__(self, other):
+ if not isinstance(other, WarpUUID):
+ return False
+ return warpcore.BNWARPUUIDEqual(self._uuid, other._uuid)
+
+ @property
+ def uuid(self):
+ return self._uuid
+
+
+class Source(WarpUUID):
+ def __repr__(self):
+ return f"<Source '{str(self)}'>"
+
+
+class BasicBlockGUID(WarpUUID):
+ def __repr__(self):
+ return f"<BasicBlockGUID '{str(self)}'>"
+
+
+class FunctionGUID(WarpUUID):
+ def __repr__(self):
+ return f"<FunctionGUID '{str(self)}'>"
+
+
+class ConstraintGUID(WarpUUID):
+ def __repr__(self):
+ return f"<ConstraintGUID '{str(self)}'>"
+
+
+class TypeGUID(WarpUUID):
+ def __repr__(self):
+ return f"<TypeGUID '{str(self)}'>"
+
+
+@dataclasses.dataclass
+class WarpFunctionComment:
+ text: str
+ offset: int
+
+ def __str__(self):
+ return repr(self)
+
+ def __repr__(self):
+ return f"<WarpFunctionComment '{self.text}': {self.offset:#x}>"
+
+ @staticmethod
+ def from_api(comment: warpcore.BNWARPFunctionComment) -> 'WarpFunctionComment':
+ return WarpFunctionComment(
+ text=comment.text.decode('utf-8'),
+ offset=comment.offset
+ )
+
+@dataclasses.dataclass
+class WarpConstraint:
+ guid: ConstraintGUID
+ offset: Optional[int]
+
+ def __str__(self):
+ return repr(self)
+
+ def __repr__(self):
+ if self.offset is None:
+ return f"<WarpConstraint '{self.guid}'>"
+ return f"<WarpConstraint '{self.guid}': {self.offset:#x}>"
+
+ @staticmethod
+ def from_api(constraint: warpcore.BNWARPConstraint) -> 'WarpConstraint':
+ if constraint.offset == -1:
+ return WarpConstraint(guid=ConstraintGUID(constraint.guid), offset=None)
+ return WarpConstraint(guid=ConstraintGUID(constraint.guid), offset=constraint.offset)
+
+class WarpTarget:
+ def __init__(self, handle: Union[warpcore.BNWARPTarget, Platform]):
+ if isinstance(handle, Platform):
+ self.handle = warpcore.BNWARPGetTarget(handle.handle)
+ else:
+ self.handle = handle
+
+ def __del__(self):
+ if self.handle is not None:
+ warpcore.BNWARPFreeTargetReference(self.handle)
+
+ @staticmethod
+ def from_platform(platform: Platform) -> Optional['WarpTarget']:
+ handle = warpcore.BNWARPGetTarget(platform.handle)
+ if not handle:
+ return None
+ return WarpTarget(handle)
+
+
+class WarpFunction:
+ def __init__(self, handle: Union[warpcore.BNWARPFunction, Function]):
+ if isinstance(handle, Function):
+ self.handle = warpcore.BNWARPGetFunction(handle.handle)
+ else:
+ self.handle = handle
+ def __del__(self):
+ if self.handle is not None:
+ warpcore.BNWARPFreeFunctionReference(self.handle)
+
+ def __repr__(self):
+ return f"<WarpFunction '{self.name}': '{self.guid}'>"
+
+ @property
+ def guid(self) -> FunctionGUID:
+ return FunctionGUID(warpcore.BNWARPFunctionGetGUID(self.handle))
+
+ @property
+ def name(self) -> str:
+ return warpcore.BNWARPFunctionGetSymbolName(self.handle)
+
+ def get_symbol(self, function: Function) -> Symbol:
+ symbol_handle = warpcore.BNWARPFunctionGetSymbol(self.handle, function.handle)
+ return Symbol(symbol_handle)
+
+ def get_type(self, function: Function) -> Optional[Type]:
+ type_handle = warpcore.BNWARPFunctionGetType(self.handle, function.handle)
+ if not type_handle:
+ return None
+ return Type(type_handle)
+
+ @property
+ def constraints(self) -> List[WarpConstraint]:
+ count = ctypes.c_size_t()
+ constraints = warpcore.BNWARPFunctionGetConstraints(self.handle, count)
+ if not constraints:
+ return []
+ result = []
+ for i in range(count.value):
+ result.append(WarpConstraint.from_api(constraints[i]))
+ warpcore.BNWARPFreeConstraintList(constraints, count.value)
+ return result
+
+ @property
+ def comments(self) -> List[WarpFunctionComment]:
+ count = ctypes.c_size_t()
+ comments = warpcore.BNWARPFunctionGetComments(self.handle, count)
+ if not comments:
+ return []
+ result = []
+ for i in range(count.value):
+ result.append(WarpFunctionComment.from_api(comments[i]))
+ warpcore.BNWARPFreeFunctionCommentList(comments, count.value)
+ return result
+
+ @staticmethod
+ def get_matched(function: Function) -> Optional['WarpFunction']:
+ handle = warpcore.BNWARPGetMatchedFunction(function.handle)
+ if not handle:
+ return None
+ return WarpFunction(handle)
+
+ def apply(self, function: Function):
+ warpcore.BNWARPFunctionApply(self.handle, function.handle)
+
+
+class _WarpContainerMetaclass(type):
+ def __iter__(self):
+ binaryninja._init_plugins()
+ count = ctypes.c_ulonglong()
+ containers = warpcore.BNWARPGetContainers(count)
+ try:
+ for i in range(0, count.value):
+ yield WarpContainer(warpcore.BNWARPNewContainerReference(containers[i]))
+ finally:
+ warpcore.BNWARPFreeContainerList(containers, count.value)
+
+ def __getitem__(self, value):
+ binaryninja._init_plugins()
+ count = ctypes.c_ulonglong()
+ containers = warpcore.BNWARPGetContainers(count)
+ try:
+ for i in range(0, count.value):
+ container = WarpContainer(warpcore.BNWARPNewContainerReference(containers[i]))
+ if container.name == str(value):
+ return container
+ raise KeyError(f"'{value}' is not a valid container name")
+ finally:
+ warpcore.BNWARPFreeContainerList(containers, count.value)
+
+
+class WarpContainer(metaclass=_WarpContainerMetaclass):
+ def __init__(self, handle: warpcore.BNWARPContainer):
+ self.handle = handle
+
+ def __del__(self):
+ if self.handle is not None:
+ warpcore.BNWARPFreeContainerReference(self.handle)
+
+ def __repr__(self):
+ return f"<WarpContainer '{self.name}'>"
+
+ @staticmethod
+ def all() -> List['WarpContainer']:
+ count = ctypes.c_size_t()
+ containers = warpcore.BNWARPGetContainers(count)
+ if not containers:
+ return []
+ result = []
+ for i in range(count.value):
+ result.append(WarpContainer(warpcore.BNWARPNewContainerReference(containers[i])))
+ warpcore.BNWARPFreeContainerList(containers, count.value)
+ return result
+
+ @property
+ def name(self) -> str:
+ return warpcore.BNWARPContainerGetName(self.handle)
+
+ @property
+ def sources(self) -> List[Source]:
+ count = ctypes.c_size_t()
+ sources = warpcore.BNWARPContainerGetSources(self.handle, count)
+ if not sources:
+ return []
+ result = []
+ for i in range(count.value):
+ result.append(Source(sources[i]))
+ warpcore.BNWARPFreeUUIDList(sources, count.value)
+ return result
+
+ def add_source(self, source_path: str) -> Optional[Source]:
+ source = warpcore.BNWARPUUID()
+ if not warpcore.BNWARPContainerAddSource(self.handle, source_path, source):
+ return None
+ return Source(source)
+
+ def commit_source(self, source: Source) -> bool:
+ return warpcore.BNWARPContainerCommitSource(self.handle, source.uuid)
+
+ def is_source_uncommitted(self, source: Source) -> bool:
+ return warpcore.BNWARPContainerIsSourceWritable(self.handle, source.uuid)
+
+ def is_source_writable(self, source: Source) -> bool:
+ return warpcore.BNWARPContainerIsSourceWritable(self.handle, source.uuid)
+
+ def get_source_path(self, source: Source) -> Optional[str]:
+ return warpcore.BNWARPContainerGetSourcePath(self.handle, source.uuid)
+
+ def add_functions(self, target: WarpTarget, source: Source, functions: List[Function]) -> bool:
+ count = len(functions)
+ core_funcs = (ctypes.POINTER(warpcore.BNWARPFunction) * count)()
+ for i in range(count):
+ core_funcs[i] = functions[i].handle
+ return warpcore.BNWARPContainerAddFunctions(self.handle, target.handle, source.uuid, core_funcs, count)
+
+ def add_types(self, view: BinaryView, source: Source, types: List[Type]) -> bool:
+ count = len(types)
+ core_types = (ctypes.POINTER(BNType) * count)()
+ for i in range(count):
+ core_types[i] = types[i].handle
+ return warpcore.BNWARPContainerAddTypes(view.handle, self.handle, source.uuid, core_types, count)
+
+ def remove_functions(self, target: WarpTarget, source: Source, functions: List[Function]) -> bool:
+ count = len(functions)
+ core_funcs = (ctypes.POINTER(warpcore.BNWARPFunction) * count)()
+ for i in range(count):
+ core_funcs[i] = functions[i].handle
+ return warpcore.BNWARPContainerRemoveFunctions(self.handle, target.handle, source.uuid, core_funcs, count)
+
+ def remove_types(self, source: Source, guids: List[TypeGUID]) -> bool:
+ count = len(guids)
+ core_guids = (ctypes.POINTER(warpcore.BNWARPTypeGUID) * count)()
+ for i in range(count):
+ core_guids[i] = guids[i].uuid
+ return warpcore.BNWARPContainerRemoveTypes(self.handle, source.uuid, core_guids, count)
+
+ def get_sources_with_function_guid(self, target: WarpTarget, guid: FunctionGUID) -> List[Source]:
+ count = ctypes.c_size_t()
+ sources = warpcore.BNWARPContainerGetSourcesWithFunctionGUID(self.handle, target.handle, guid.uuid, count)
+ if not sources:
+ return []
+ result = []
+ for i in range(count.value):
+ result.append(Source(sources[i]))
+ warpcore.BNWARPFreeUUIDList(sources, count.value)
+ return result
+
+ def get_sources_with_type_guid(self, guid: TypeGUID) -> List[Source]:
+ count = ctypes.c_size_t()
+ sources = warpcore.BNWARPContainerGetSourcesWithTypeGUID(self.handle, guid.uuid, count)
+ if not sources:
+ return []
+ result = []
+ for i in range(count.value):
+ result.append(Source(sources[i]))
+ warpcore.BNWARPFreeUUIDList(sources, count.value)
+ return result
+
+ def get_functions_with_guid(self, target: WarpTarget, source: Source, guid: FunctionGUID) -> List[Function]:
+ count = ctypes.c_size_t()
+ funcs = warpcore.BNWARPContainerGetFunctionsWithGUID(self.handle, target.handle, source.uuid, guid.uuid, count)
+ if not funcs:
+ return []
+ result = []
+ for i in range(count.value):
+ result.append(WarpFunction(warpcore.BNWARPNewFunctionReference(funcs[i])))
+ warpcore.BNWARPFreeFunctionList(funcs, count.value)
+ return result
+
+ def get_type_with_guid(self, arch: Architecture, source: Source, guid: TypeGUID) -> Optional[Type]:
+ ty = warpcore.BNWARPContainerGetTypeWithGUID(self.handle, arch.handle, source.uuid, guid.uuid)
+ if not ty:
+ return None
+ return Type(ty)
+
+ def get_type_guids_with_name(self, source: Source, name: str) -> List[TypeGUID]:
+ count = ctypes.c_size_t()
+ guids = warpcore.BNWARPContainerGetTypeGUIDsWithName(self.handle, source.uuid, name, count)
+ if not guids:
+ return []
+ result = []
+ for i in range(count.value):
+ result.append(TypeGUID(guids[i]))
+ warpcore.BNWARPFreeUUIDList(guids, count.value)
+ return result
+
+
+def run_matcher(view: BinaryView):
+ warpcore.BNWARPRunMatcher(view.handle)
+
+def is_instruction_variant(function: LowLevelILFunction, variant: LowLevelILInstruction) -> bool:
+ return warpcore.BNWARPIsLiftedInstructionVariant(function.handle, variant.instr_index)
+
+def is_instruction_blacklisted(function: LowLevelILFunction, variant: LowLevelILInstruction) -> bool:
+ return warpcore.BNWARPIsLiftedInstructionBlacklisted(function.handle, variant.instr_index)
+
+def get_function_guid(function: Function) -> Optional[FunctionGUID]:
+ guid = warpcore.BNWARPUUID()
+ if not warpcore.BNWARPGetAnalysisFunctionGUID(function.handle, guid):
+ return None
+ return FunctionGUID(guid)
+
+
+def get_basic_block_guid(basic_block: BasicBlock) -> Optional[BasicBlockGUID]:
+ # TODO: I believe this won't work for HLIL: https://github.com/Vector35/binaryninja-api/issues/6998
+ if basic_block.is_il:
+ basic_block = basic_block.source_block
+ guid = warpcore.BNWARPUUID()
+ if not warpcore.BNWARPGetBasicBlockGUID(basic_block.handle, guid):
+ return None
+ return BasicBlockGUID(guid)
+
+# TODO: Magic matched_function, possible_functions \ No newline at end of file
diff --git a/plugins/warp/api/python/warp_enums.py b/plugins/warp/api/python/warp_enums.py
new file mode 100644
index 00000000..e47ff1c3
--- /dev/null
+++ b/plugins/warp/api/python/warp_enums.py
@@ -0,0 +1 @@
+import enum
diff --git a/plugins/warp/api/warp.cpp b/plugins/warp/api/warp.cpp
new file mode 100644
index 00000000..7d91c6e2
--- /dev/null
+++ b/plugins/warp/api/warp.cpp
@@ -0,0 +1,336 @@
+#include "warpcore.h"
+#include "warp.h"
+
+#include <utility>
+
+using namespace Warp;
+
+std::string WarpUUID::ToString() const
+{
+ char *str = BNWARPUUIDGetString(&uuid);
+ std::string result = str;
+ BNFreeString(str);
+ return result;
+}
+
+Target::Target(BNWARPTarget *target)
+{
+ m_object = target;
+}
+
+Ref<Target> Target::FromPlatform(const BinaryNinja::Platform &platform)
+{
+ BNWARPTarget *result = BNWARPGetTarget(platform.m_object);
+ if (!result)
+ return nullptr;
+ return new Target(result);
+}
+
+Constraint::Constraint(ConstraintGUID guid, std::optional<int64_t> offset)
+{
+ this->guid = guid;
+ this->offset = offset;
+}
+
+Constraint Constraint::FromAPIObject(BNWARPConstraint *constraint)
+{
+ auto offset = constraint->offset == INT64_MAX ? std::nullopt : std::optional(constraint->offset);
+ return {constraint->guid, offset};
+}
+
+FunctionComment::FunctionComment(std::string text, int64_t offset)
+{
+ this->text = std::move(text);
+ this->offset = offset;
+}
+
+FunctionComment FunctionComment::FromAPIObject(BNWARPFunctionComment *comment)
+{
+ return {comment->text, comment->offset};
+}
+
+Function::Function(BNWARPFunction *function)
+{
+ m_object = function;
+}
+
+FunctionGUID Function::GetGUID() const
+{
+ return BNWARPFunctionGetGUID(m_object);
+}
+
+std::string Function::GetSymbolName() const
+{
+ char *name = BNWARPFunctionGetSymbolName(m_object);
+ std::string result = name;
+ BNFreeString(name);
+ return result;
+}
+
+BinaryNinja::Ref<BinaryNinja::Symbol> Function::GetSymbol(const BinaryNinja::Function &function) const
+{
+ BNSymbol *symbol = BNWARPFunctionGetSymbol(m_object, function.m_object);
+ if (!symbol)
+ return nullptr;
+ return new BinaryNinja::Symbol(symbol);
+}
+
+BinaryNinja::Ref<BinaryNinja::Type> Function::GetType(const BinaryNinja::Function &function) const
+{
+ BNType *type = BNWARPFunctionGetType(m_object, function.m_object);
+ if (!type)
+ return nullptr;
+ return new BinaryNinja::Type(type);
+}
+
+std::vector<Constraint> Function::GetConstraints() const
+{
+ size_t count;
+ BNWARPConstraint *constraints = BNWARPFunctionGetConstraints(m_object, &count);
+ std::vector<Constraint> result;
+ result.reserve(count);
+ for (int i = 0; i < count; i++)
+ result.push_back(Constraint::FromAPIObject(&constraints[i]));
+ BNWARPFreeConstraintList(constraints, count);
+ return result;
+}
+
+std::vector<FunctionComment> Function::GetComments() const
+{
+ size_t count;
+ BNWARPFunctionComment *comments = BNWARPFunctionGetComments(m_object, &count);
+ std::vector<FunctionComment> result;
+ result.reserve(count);
+ for (int i = 0; i < count; i++)
+ result.push_back(FunctionComment::FromAPIObject(&comments[i]));
+ BNWARPFreeFunctionCommentList(comments, count);
+ return result;
+}
+
+Ref<Function> Function::Get(const BinaryNinja::Function &function)
+{
+ BNWARPFunction *result = BNWARPGetFunction(function.m_object);
+ if (!result)
+ return nullptr;
+ return new Function(result);
+}
+
+Ref<Function> Function::GetMatched(const BinaryNinja::Function &function)
+{
+ BNWARPFunction *result = BNWARPGetMatchedFunction(function.m_object);
+ if (!result)
+ return nullptr;
+ return new Function(result);
+}
+
+void Function::Apply(const BinaryNinja::Function &function) const
+{
+ BNWARPFunctionApply(m_object, function.m_object);
+}
+
+void Function::RemoveMatch(const BinaryNinja::Function &function)
+{
+ BNWARPFunctionApply(nullptr, function.m_object);
+}
+
+Container::Container(BNWARPContainer *container)
+{
+ m_object = container;
+}
+
+std::vector<Ref<Container> > Container::All()
+{
+ size_t count;
+ BNWARPContainer **containers = BNWARPGetContainers(&count);
+ std::vector<Ref<Container> > result;
+ result.reserve(count);
+ for (int i = 0; i < count; i++)
+ result.push_back(new Container(BNWARPNewContainerReference(containers[i])));
+ BNWARPFreeContainerList(containers, count);
+ return result;
+}
+
+std::string Container::GetName() const
+{
+ char *rawName = BNWARPContainerGetName(m_object);
+ std::string name = rawName;
+ BNFreeString(rawName);
+ return name;
+}
+
+std::vector<Source> Container::GetSources() const
+{
+ size_t count;
+ BNWARPSource *sources = BNWARPContainerGetSources(m_object, &count);
+ std::vector<Source> result;
+ result.reserve(count);
+ for (int i = 0; i < count; i++)
+ result.emplace_back(sources[i]);
+ BNWARPFreeUUIDList(sources, count);
+ return result;
+}
+
+std::optional<Source> Container::AddSource(const std::string &sourcePath) const
+{
+ Source source;
+ if (!BNWARPContainerAddSource(m_object, sourcePath.c_str(), source.RawMut()))
+ return std::nullopt;
+ return source;
+}
+
+bool Container::CommitSource(const Source &source) const
+{
+ return BNWARPContainerCommitSource(m_object, source.Raw());
+}
+
+bool Container::IsSourceUncommitted(const Source &source) const
+{
+ return BNWARPContainerIsSourceUncommitted(m_object, source.Raw());
+}
+
+bool Container::IsSourceWritable(const Source &source) const
+{
+ return BNWARPContainerIsSourceWritable(m_object, source.Raw());
+}
+
+std::optional<std::string> Container::SourcePath(const Source &source) const
+{
+ char *rawPath = BNWARPContainerGetSourcePath(m_object, source.Raw());
+ if (!rawPath)
+ return std::nullopt;
+ std::string path = rawPath;
+ BNFreeString(rawPath);
+ return path;
+}
+
+bool Container::AddFunctions(const Target &target, const Source &source, const std::vector<Ref<Function> > &functions) const
+{
+ size_t count = functions.size();
+ BNWARPFunction **apiFunctions = new BNWARPFunction *[count];
+ for (size_t i = 0; i < count; i++)
+ apiFunctions[i] = functions[i]->m_object;
+ const bool result = BNWARPContainerAddFunctions(m_object, target.m_object, source.Raw(), apiFunctions, count);
+ delete[] apiFunctions;
+ return result;
+}
+
+bool Container::AddTypes(const BinaryNinja::BinaryView &view, const Source &source,
+ const std::vector<BinaryNinja::Ref<BinaryNinja::Type> > &types) const
+{
+ size_t count = types.size();
+ BNType **apiTypes = new BNType *[count];
+ for (size_t i = 0; i < count; i++)
+ apiTypes[i] = types[i]->m_object;
+ const bool result = BNWARPContainerAddTypes(view.m_object, m_object, source.Raw(), apiTypes, count);
+ delete[] apiTypes;
+ return result;
+}
+
+bool Container::RemoveFunctions(const Target &target, const Source &source,
+ const std::vector<Ref<Function>> &functions) const
+{
+ size_t count = functions.size();
+ BNWARPFunction **apiFunctions = new BNWARPFunction *[count];
+ for (size_t i = 0; i < count; i++)
+ apiFunctions[i] = functions[i]->m_object;
+ const bool result = BNWARPContainerRemoveFunctions(m_object, target.m_object, source.Raw(), apiFunctions, count);
+ delete[] apiFunctions;
+ return result;
+}
+
+bool Container::RemoveTypes(const Source &source, const std::vector<TypeGUID> &guids) const
+{
+ size_t count = guids.size();
+ BNWARPTypeGUID* apiGuids = new BNWARPTypeGUID[count];
+ for (size_t i = 0; i < count; i++)
+ apiGuids[i] = *guids[i].Raw();
+ const bool result = BNWARPContainerRemoveTypes(m_object, source.Raw(), apiGuids, count);
+ delete[] apiGuids;
+ return result;
+}
+
+std::vector<Source> Container::GetSourcesWithFunctionGUID(const Target& target, const FunctionGUID &guid) const
+{
+ size_t count;
+ BNWARPSource *sources = BNWARPContainerGetSourcesWithFunctionGUID(m_object, target.m_object, guid.Raw(), &count);
+ std::vector<Source> result;
+ result.reserve(count);
+ for (int i = 0; i < count; i++)
+ result.emplace_back(sources[i]);
+ BNWARPFreeUUIDList(sources, count);
+ return result;
+}
+
+std::vector<Source> Container::GetSourcesWithTypeGUID(const TypeGUID &guid) const
+{
+ size_t count;
+ BNWARPSource *sources = BNWARPContainerGetSourcesWithTypeGUID(m_object, guid.Raw(), &count);
+ std::vector<Source> result;
+ result.reserve(count);
+ for (int i = 0; i < count; i++)
+ result.emplace_back(sources[i]);
+ BNWARPFreeUUIDList(sources, count);
+ return result;
+}
+
+std::vector<Ref<Function> > Container::GetFunctionsWithGUID(const Target& target, const Source &source, const FunctionGUID &guid) const
+{
+ size_t count;
+ BNWARPFunction **functions = BNWARPContainerGetFunctionsWithGUID(m_object, target.m_object, source.Raw(), guid.Raw(), &count);
+ std::vector<Ref<Function> > result;
+ result.reserve(count);
+ for (int i = 0; i < count; i++)
+ result.push_back(new Function(BNWARPNewFunctionReference(functions[i])));
+ BNWARPFreeFunctionList(functions, count);
+ return result;
+}
+
+BinaryNinja::Ref<BinaryNinja::Type> Container::GetTypeWithGUID(const BinaryNinja::Architecture &arch,
+ const Source &source, const TypeGUID &guid) const
+{
+ BNType *type = BNWARPContainerGetTypeWithGUID(arch.m_object, m_object, source.Raw(), guid.Raw());
+ return new BinaryNinja::Type(type);
+}
+
+std::vector<TypeGUID> Container::GetTypeGUIDsWithName(const Source &source, const std::string &name) const
+{
+ size_t count;
+ BNWARPTypeGUID *guids = BNWARPContainerGetTypeGUIDsWithName(m_object, source.Raw(), name.c_str(), &count);
+ std::vector<TypeGUID> result;
+ result.reserve(count);
+ for (int i = 0; i < count; i++)
+ result.emplace_back(guids[i]);
+ BNWARPFreeUUIDList(guids, count);
+ return result;
+}
+
+void Warp::RunMatcher(const BinaryNinja::BinaryView &view)
+{
+ BNWARPRunMatcher(view.m_object);
+}
+
+bool IsInstructionVariant(const BinaryNinja::LowLevelILFunction &function, BinaryNinja::ExprId idx)
+{
+ return BNWARPIsLiftedInstructionVariant(function.m_object, idx);
+}
+
+bool IsInstructionBlacklisted(const BinaryNinja::LowLevelILFunction &function, BinaryNinja::ExprId idx)
+{
+ return BNWARPIsLiftedInstructionBlacklisted(function.m_object, idx);
+}
+
+std::optional<FunctionGUID> Warp::GetAnalysisFunctionGUID(const BinaryNinja::Function &function)
+{
+ FunctionGUID guid;
+ if (!BNWARPGetAnalysisFunctionGUID(function.m_object, guid.RawMut()))
+ return std::nullopt;
+ return guid;
+}
+
+std::optional<BasicBlockGUID> Warp::GetBasicBlockGUID(const BinaryNinja::BasicBlock &basicBlock)
+{
+ BasicBlockGUID guid;
+ if (!BNWARPGetBasicBlockGUID(basicBlock.m_object, guid.RawMut()))
+ return std::nullopt;
+ return guid;
+}
diff --git a/plugins/warp/api/warp.h b/plugins/warp/api/warp.h
new file mode 100644
index 00000000..b57a0c7b
--- /dev/null
+++ b/plugins/warp/api/warp.h
@@ -0,0 +1,378 @@
+#pragma once
+
+#include <binaryninjaapi.h>
+#include "warpcore.h"
+
+template<class T, T *(*AddObjectReference)(T *), void (*FreeObjectReference)(T *)>
+class WarpRefCountObject
+{
+ void AddRefInternal() { m_refs.fetch_add(1); }
+
+ void ReleaseInternal()
+ {
+ if (m_refs.fetch_sub(1) == 1)
+ {
+ if (!m_registeredRef)
+ delete this;
+ }
+ }
+
+public:
+ std::atomic<int> m_refs;
+ bool m_registeredRef = false;
+ T *m_object;
+
+ WarpRefCountObject() : m_refs(0), m_object(nullptr)
+ {
+ }
+
+ virtual ~WarpRefCountObject() = default;
+
+ T *GetObject() const { return m_object; }
+
+ static T *GetObject(WarpRefCountObject *obj)
+ {
+ if (!obj)
+ return nullptr;
+ return obj->GetObject();
+ }
+
+ void AddRef()
+ {
+ if (m_object && (m_refs != 0))
+ AddObjectReference(m_object);
+ AddRefInternal();
+ }
+
+ void Release()
+ {
+ if (m_object)
+ FreeObjectReference(m_object);
+ ReleaseInternal();
+ }
+
+ void AddRefForRegistration() { m_registeredRef = true; }
+
+ void ReleaseForRegistration()
+ {
+ m_object = nullptr;
+ m_registeredRef = false;
+ if (m_refs == 0)
+ delete this;
+ }
+};
+
+namespace Warp {
+ template<class T>
+ class Ref
+ {
+ T *m_obj;
+#ifdef BN_REF_COUNT_DEBUG
+ void* m_assignmentTrace = nullptr;
+#endif
+
+ public:
+ Ref() : m_obj(NULL)
+ {
+ }
+
+ Ref(T *obj) : m_obj(obj)
+ {
+ if (m_obj)
+ {
+ m_obj->AddRef();
+#ifdef BN_REF_COUNT_DEBUG
+ m_assignmentTrace = BNRegisterObjectRefDebugTrace(typeid(T).name());
+#endif
+ }
+ }
+
+ Ref(const Ref &obj) : m_obj(obj.m_obj)
+ {
+ if (m_obj)
+ {
+ m_obj->AddRef();
+#ifdef BN_REF_COUNT_DEBUG
+ m_assignmentTrace = BNRegisterObjectRefDebugTrace(typeid(T).name());
+#endif
+ }
+ }
+
+ Ref(Ref &&other) : m_obj(other.m_obj)
+ {
+ other.m_obj = 0;
+#ifdef BN_REF_COUNT_DEBUG
+ m_assignmentTrace = other.m_assignmentTrace;
+#endif
+ }
+
+ ~Ref()
+ {
+ if (m_obj)
+ {
+ m_obj->Release();
+#ifdef BN_REF_COUNT_DEBUG
+ BNUnregisterObjectRefDebugTrace(typeid(T).name(), m_assignmentTrace);
+#endif
+ }
+ }
+
+ Ref<T> &operator=(const Ref<T> &obj)
+ {
+#ifdef BN_REF_COUNT_DEBUG
+ if (m_obj)
+ BNUnregisterObjectRefDebugTrace(typeid(T).name(), m_assignmentTrace);
+ if (obj.m_obj)
+ m_assignmentTrace = BNRegisterObjectRefDebugTrace(typeid(T).name());
+#endif
+ T *oldObj = m_obj;
+ m_obj = obj.m_obj;
+ if (m_obj)
+ m_obj->AddRef();
+ if (oldObj)
+ oldObj->Release();
+ return *this;
+ }
+
+ Ref<T> &operator=(Ref<T> &&other)
+ {
+ if (m_obj)
+ {
+#ifdef BN_REF_COUNT_DEBUG
+ BNUnregisterObjectRefDebugTrace(typeid(T).name(), m_assignmentTrace);
+#endif
+ m_obj->Release();
+ }
+ m_obj = other.m_obj;
+ other.m_obj = 0;
+#ifdef BN_REF_COUNT_DEBUG
+ m_assignmentTrace = other.m_assignmentTrace;
+#endif
+ return *this;
+ }
+
+ Ref<T> &operator=(T *obj)
+ {
+#ifdef BN_REF_COUNT_DEBUG
+ if (m_obj)
+ BNUnregisterObjectRefDebugTrace(typeid(T).name(), m_assignmentTrace);
+ if (obj)
+ m_assignmentTrace = BNRegisterObjectRefDebugTrace(typeid(T).name());
+#endif
+ T *oldObj = m_obj;
+ m_obj = obj;
+ if (m_obj)
+ m_obj->AddRef();
+ if (oldObj)
+ oldObj->Release();
+ return *this;
+ }
+
+ operator T *() const
+ {
+ return m_obj;
+ }
+
+ T *operator->() const
+ {
+ return m_obj;
+ }
+
+ T &operator*() const
+ {
+ return *m_obj;
+ }
+
+ bool operator!() const
+ {
+ return m_obj == NULL;
+ }
+
+ bool operator==(const T *obj) const
+ {
+ return T::GetObject(m_obj) == T::GetObject(obj);
+ }
+
+ bool operator==(const Ref<T> &obj) const
+ {
+ return T::GetObject(m_obj) == T::GetObject(obj.m_obj);
+ }
+
+ bool operator!=(const T *obj) const
+ {
+ return T::GetObject(m_obj) != T::GetObject(obj);
+ }
+
+ bool operator!=(const Ref<T> &obj) const
+ {
+ return T::GetObject(m_obj) != T::GetObject(obj.m_obj);
+ }
+
+ bool operator<(const T *obj) const
+ {
+ return T::GetObject(m_obj) < T::GetObject(obj);
+ }
+
+ bool operator<(const Ref<T> &obj) const
+ {
+ return T::GetObject(m_obj) < T::GetObject(obj.m_obj);
+ }
+
+ T *GetPtr() const
+ {
+ return m_obj;
+ }
+ };
+
+ class WarpUUID
+ {
+ BNWARPUUID uuid;
+
+ public:
+ WarpUUID() = default;
+
+ WarpUUID(BNWARPUUID uuid) : uuid(uuid) {}
+
+ std::string ToString() const;
+
+ bool operator==(const WarpUUID &other) const
+ {
+ return BNWARPUUIDEqual(&uuid, &other.uuid);
+ }
+
+ bool operator!=(const WarpUUID &other) const
+ {
+ return !(*this == other);
+ }
+
+ BNWARPUUID* RawMut()
+ {
+ return &uuid;
+ }
+
+ const BNWARPUUID* Raw() const
+ {
+ return &uuid;
+ }
+ };
+
+ typedef WarpUUID Source;
+ typedef WarpUUID BasicBlockGUID;
+ typedef WarpUUID FunctionGUID;
+ typedef WarpUUID ConstraintGUID;
+ typedef WarpUUID TypeGUID;
+
+ class Target : public WarpRefCountObject<BNWARPTarget, BNWARPNewTargetReference,
+ BNWARPFreeTargetReference>
+ {
+ public:
+ explicit Target(BNWARPTarget *target);
+
+ static Ref<Target> FromPlatform(const BinaryNinja::Platform& platform);
+ };
+
+ struct Constraint
+ {
+ ConstraintGUID guid;
+ std::optional<int64_t> offset;
+
+ Constraint(ConstraintGUID guid, std::optional<int64_t> offset);
+
+ static Constraint FromAPIObject(BNWARPConstraint* constraint);
+ };
+
+ struct FunctionComment
+ {
+ std::string text;
+ int64_t offset;
+
+ FunctionComment(std::string text, int64_t offset);
+
+ static FunctionComment FromAPIObject(BNWARPFunctionComment* comment);
+ };
+
+ class Function : public WarpRefCountObject<BNWARPFunction, BNWARPNewFunctionReference, BNWARPFreeFunctionReference>
+ {
+ public:
+ explicit Function(BNWARPFunction *function);
+
+ bool operator==(const Function &other) const
+ {
+ return BNWARPFunctionsEqual(m_object, other.m_object);
+ }
+
+ FunctionGUID GetGUID() const;
+
+ std::string GetSymbolName() const;
+
+ BinaryNinja::Ref<BinaryNinja::Symbol> GetSymbol(const BinaryNinja::Function &function) const;
+
+ BinaryNinja::Ref<BinaryNinja::Type> GetType(const BinaryNinja::Function &function) const;
+
+ std::vector<Constraint> GetConstraints() const;
+
+ std::vector<FunctionComment> GetComments() const;
+
+ static Ref<Function> Get(const BinaryNinja::Function &function);
+
+ static Ref<Function> GetMatched(const BinaryNinja::Function &function);
+
+ void Apply(const BinaryNinja::Function &function) const;
+
+ static void RemoveMatch(const BinaryNinja::Function &function);
+ };
+
+ class Container : public WarpRefCountObject<BNWARPContainer, BNWARPNewContainerReference,
+ BNWARPFreeContainerReference>
+ {
+ public:
+ explicit Container(BNWARPContainer *container);
+
+ /// Retrieve all available containers.
+ static std::vector<Ref<Container> > All();
+
+ std::string GetName() const;
+
+ std::vector<Source> GetSources() const;
+
+ std::optional<Source> AddSource(const std::string &sourcePath) const;
+
+ bool CommitSource(const Source &source) const;
+
+ bool IsSourceUncommitted(const Source &source) const;
+
+ bool IsSourceWritable(const Source &source) const;
+
+ std::optional<std::string> SourcePath(const Source &source) const;
+
+ bool AddFunctions(const Target &target, const Source &source, const std::vector<Ref<Function> > &functions) const;
+
+ bool AddTypes(const BinaryNinja::BinaryView &view, const Source &source,
+ const std::vector<BinaryNinja::Ref<BinaryNinja::Type> > &types) const;
+
+ bool RemoveFunctions(const Target &target, const Source &source, const std::vector<Ref<Function> > &functions) const;
+
+ bool RemoveTypes(const Source &source, const std::vector<TypeGUID> &guids) const;
+
+ std::vector<Source> GetSourcesWithFunctionGUID(const Target& target, const FunctionGUID &guid) const;
+
+ std::vector<Source> GetSourcesWithTypeGUID(const TypeGUID &guid) const;
+
+ std::vector<Ref<Function> > GetFunctionsWithGUID(const Target& target, const Source &source, const FunctionGUID &guid) const;
+
+ BinaryNinja::Ref<BinaryNinja::Type> GetTypeWithGUID(const BinaryNinja::Architecture &arch, const Source &source,
+ const TypeGUID &guid) const;
+
+ std::vector<TypeGUID> GetTypeGUIDsWithName(const Source &source, const std::string &name) const;
+ };
+
+ void RunMatcher(const BinaryNinja::BinaryView& view);
+
+ bool IsInstructionVariant(const BinaryNinja::LowLevelILFunction &function, BinaryNinja::ExprId idx);
+
+ bool IsInstructionBlacklisted(const BinaryNinja::LowLevelILFunction &function, BinaryNinja::ExprId idx);
+
+ std::optional<FunctionGUID> GetAnalysisFunctionGUID(const BinaryNinja::Function &function);
+
+ std::optional<BasicBlockGUID> GetBasicBlockGUID(const BinaryNinja::BasicBlock &basicBlock);
+}
diff --git a/plugins/warp/api/warpcore.h b/plugins/warp/api/warpcore.h
new file mode 100644
index 00000000..d12c6b98
--- /dev/null
+++ b/plugins/warp/api/warpcore.h
@@ -0,0 +1,143 @@
+#pragma once
+
+#ifndef BN_TYPE_PARSER
+#ifdef __cplusplus
+#include <cstdint>
+#include <cstddef>
+#include <cstdlib>
+#else
+#include <stdbool.h>
+#include <stdint.h>
+#include <stddef.h>
+#include <stdlib.h>
+#endif
+#endif
+
+#ifdef __GNUC__
+ #ifdef WARP_LIBRARY
+ #define WARP_FFI_API __attribute__((visibility("default")))
+ #else // WARP_LIBRARY
+ #define WARP_FFI_API
+ #endif // WARP_LIBRARY
+#else // __GNUC__
+ #ifdef _MSC_VER
+ #ifndef DEMO_VERSION
+ #ifdef WARP_LIBRARY
+ #define WARP_FFI_API __declspec(dllexport)
+ #else // WARP_LIBRARY
+ #define WARP_FFI_API __declspec(dllimport)
+ #endif // WARP_LIBRARY
+ #else
+ #define WARP_FFI_API
+ #endif
+ #else // _MSC_VER
+ #define WARP_FFI_API
+ #endif // _MSC_VER
+#endif // __GNUC__C
+
+#ifdef __cplusplus
+extern "C"
+{
+#endif
+
+ typedef struct BNArchitecture BNArchitecture;
+ typedef struct BNBinaryView BNBinaryView;
+ typedef struct BNPlatform BNPlatform;
+ typedef struct BNBasicBlock BNBasicBlock;
+ typedef struct BNLowLevelILFunction BNLowLevelILFunction;
+ typedef struct BNFunction BNFunction;
+ typedef struct BNSymbol BNSymbol;
+ typedef struct BNType BNType;
+
+ struct BNWARPUUID
+ {
+ uint8_t uuid[16];
+ };
+
+ struct BNWARPFunctionComment
+ {
+ char* text;
+ int64_t offset;
+ };
+
+ char* BNWARPUUIDGetString(const BNWARPUUID* uuid);
+ bool BNWARPUUIDEqual(const BNWARPUUID* a, const BNWARPUUID* b);
+ void BNWARPFreeUUIDList(BNWARPUUID* uuids, size_t count);
+
+ typedef BNWARPUUID BNWARPSource;
+ typedef BNWARPUUID BNWARPBasicBlockGUID;
+ typedef BNWARPUUID BNWARPConstraintGUID;
+ typedef BNWARPUUID BNWARPFunctionGUID;
+ typedef BNWARPUUID BNWARPTypeGUID;
+
+ typedef struct BNWARPTarget BNWARPTarget;
+ typedef struct BNWARPContainer BNWARPContainer;
+ typedef struct BNWARPFunction BNWARPFunction;
+ typedef struct BNWARPConstraint BNWARPConstraint;
+
+ struct BNWARPConstraint
+ {
+ BNWARPConstraintGUID guid;
+ int64_t offset;
+ };
+
+ WARP_FFI_API void BNWARPRunMatcher(BNBinaryView* view);
+
+ WARP_FFI_API bool BNWARPGetBasicBlockGUID(BNBasicBlock* basicBlock, BNWARPBasicBlockGUID* result);
+ WARP_FFI_API bool BNWARPGetAnalysisFunctionGUID(BNFunction* analysisFunction, BNWARPFunctionGUID* result);
+ WARP_FFI_API bool BNWARPIsLiftedInstructionVariant(BNLowLevelILFunction* liftedFunction, size_t idx);
+ WARP_FFI_API bool BNWARPIsLiftedInstructionBlacklisted(BNLowLevelILFunction* liftedFunction, size_t idx);
+
+ WARP_FFI_API BNWARPFunction* BNWARPGetFunction(BNFunction* analysisFunction);
+ WARP_FFI_API BNWARPFunction* BNWARPGetMatchedFunction(BNFunction* analysisFunction);
+ WARP_FFI_API BNWARPContainer** BNWARPGetContainers(size_t* count);
+
+ WARP_FFI_API char* BNWARPContainerGetName(BNWARPContainer* container);
+
+ WARP_FFI_API BNWARPSource* BNWARPContainerGetSources(BNWARPContainer* container, size_t* count);
+ WARP_FFI_API bool BNWARPContainerAddSource(BNWARPContainer* container, const char* sourcePath, BNWARPSource* result);
+ WARP_FFI_API bool BNWARPContainerCommitSource(BNWARPContainer* container, const BNWARPSource* source);
+ WARP_FFI_API bool BNWARPContainerIsSourceUncommitted(BNWARPContainer* container, const BNWARPSource* source);
+ WARP_FFI_API bool BNWARPContainerIsSourceWritable(BNWARPContainer* container, const BNWARPSource* source);
+ WARP_FFI_API char* BNWARPContainerGetSourcePath(BNWARPContainer* container, const BNWARPSource* source);
+
+ WARP_FFI_API bool BNWARPContainerAddFunctions(BNWARPContainer* container, const BNWARPTarget* target, const BNWARPSource* source, BNWARPFunction** functions, size_t count);
+ WARP_FFI_API bool BNWARPContainerAddTypes(BNBinaryView* view, BNWARPContainer* container, const BNWARPSource* source, BNType** types, size_t count);
+
+ WARP_FFI_API bool BNWARPContainerRemoveFunctions(BNWARPContainer* container, const BNWARPTarget* target, const BNWARPSource* source, BNWARPFunction** functions, size_t count);
+ WARP_FFI_API bool BNWARPContainerRemoveTypes(BNWARPContainer* container, const BNWARPSource* source, BNWARPTypeGUID* types, size_t count);
+
+ WARP_FFI_API BNWARPSource* BNWARPContainerGetSourcesWithFunctionGUID(BNWARPContainer* container, const BNWARPTarget* target, const BNWARPFunctionGUID* guid, size_t* count);
+ WARP_FFI_API BNWARPSource* BNWARPContainerGetSourcesWithTypeGUID(BNWARPContainer* container, const BNWARPTypeGUID* guid, size_t* count);
+ WARP_FFI_API BNWARPFunction** BNWARPContainerGetFunctionsWithGUID(BNWARPContainer* container, const BNWARPTarget* target, const BNWARPSource* source, const BNWARPFunctionGUID* guid, size_t* count);
+ WARP_FFI_API BNType* BNWARPContainerGetTypeWithGUID(BNArchitecture* arch, BNWARPContainer* container, const BNWARPSource* source, const BNWARPTypeGUID* guid);
+ WARP_FFI_API BNWARPTypeGUID* BNWARPContainerGetTypeGUIDsWithName(BNWARPContainer* container, const BNWARPSource* source, const char* name, size_t* count);
+
+ WARP_FFI_API BNWARPContainer* BNWARPNewContainerReference(BNWARPContainer* container);
+ WARP_FFI_API void BNWARPFreeContainerReference(BNWARPContainer* container);
+ WARP_FFI_API void BNWARPFreeContainerList(BNWARPContainer** containers, size_t count);
+
+ WARP_FFI_API void BNWARPFunctionApply(BNWARPFunction* function, BNFunction* analysisFunction);
+ WARP_FFI_API BNWARPFunctionGUID BNWARPFunctionGetGUID(BNWARPFunction* function);
+ WARP_FFI_API BNSymbol* BNWARPFunctionGetSymbol(BNWARPFunction* function, BNFunction* analysisFunction);
+ WARP_FFI_API char* BNWARPFunctionGetSymbolName(BNWARPFunction* function);
+ WARP_FFI_API BNType* BNWARPFunctionGetType(BNWARPFunction* function, BNFunction* analysisFunction);
+ WARP_FFI_API BNWARPConstraint* BNWARPFunctionGetConstraints(BNWARPFunction* function, size_t* count);
+ WARP_FFI_API BNWARPFunctionComment* BNWARPFunctionGetComments(BNWARPFunction* function, size_t* count);
+ WARP_FFI_API bool BNWARPFunctionsEqual(BNWARPFunction* functionA, BNWARPFunction* functionB);
+
+ WARP_FFI_API void BNWARPFreeFunctionCommentList(BNWARPFunctionComment* comments, size_t count);
+ WARP_FFI_API void BNWARPFreeConstraintList(BNWARPConstraint* constraints, size_t count);
+
+ WARP_FFI_API BNWARPFunction* BNWARPNewFunctionReference(BNWARPFunction* function);
+ WARP_FFI_API void BNWARPFreeFunctionReference(BNWARPFunction* function);
+ WARP_FFI_API void BNWARPFreeFunctionList(BNWARPFunction** functions, size_t count);
+
+ WARP_FFI_API BNWARPTarget* BNWARPGetTarget(BNPlatform* platform);
+
+ WARP_FFI_API BNWARPTarget* BNWARPNewTargetReference(BNWARPTarget* target);
+ WARP_FFI_API void BNWARPFreeTargetReference(BNWARPTarget* target);
+
+#ifdef __cplusplus
+}
+#endif
diff --git a/plugins/warp/benches/convert.rs b/plugins/warp/benches/convert.rs
index 7d7f40b3..e0acdc15 100644
--- a/plugins/warp/benches/convert.rs
+++ b/plugins/warp/benches/convert.rs
@@ -4,33 +4,36 @@ use criterion::{criterion_group, criterion_main, Criterion};
use std::path::PathBuf;
use warp_ninja::convert::from_bn_type;
+// These are the target files present in OUT_DIR
+// Add the files to fixtures/bin
+static TARGET_FILES: [&str; 1] = ["atox.obj"];
+
pub fn type_conversion_benchmark(c: &mut Criterion) {
let session = Session::new().expect("Failed to initialize session");
let out_dir = env!("OUT_DIR").parse::<PathBuf>().unwrap();
- for entry in std::fs::read_dir(out_dir).expect("Failed to read OUT_DIR") {
- let entry = entry.expect("Failed to read directory entry");
- let path = entry.path();
- if path.is_file() {
- if let Some(bv) = session.load(path.to_str().unwrap()) {
- let functions = bv.functions();
- c.bench_function("type conversion all functions", |b| {
- b.iter(|| {
- for func in &functions {
- from_bn_type(&bv, &func.function_type(), u8::MAX);
- }
- })
- });
+ for file_name in TARGET_FILES {
+ let path = out_dir.join(file_name);
+ let view = session.load(&path).expect("Failed to load view");
+
+ // Bench function types.
+ let functions: Vec<_> = view.functions().iter().map(|f| f.to_owned()).collect();
+ c.bench_function("type conversion all functions", |b| {
+ b.iter(|| {
+ for func in &functions {
+ from_bn_type(&view, &func.function_type(), u8::MAX);
+ }
+ })
+ });
- let types = bv.types();
- c.bench_function("type conversion all types", |b| {
- b.iter(|| {
- for ty in &types {
- from_bn_type(&bv, &ty.ty, u8::MAX);
- }
- })
- });
- }
- }
+ // Bench view types.
+ let types = view.types().to_vec();
+ c.bench_function("type conversion all types", |b| {
+ b.iter(|| {
+ for ty in &types {
+ from_bn_type(&view, &ty.ty, u8::MAX);
+ }
+ })
+ });
}
}
diff --git a/plugins/warp/benches/function.rs b/plugins/warp/benches/function.rs
index 0ddf9ce2..dc5016be 100644
--- a/plugins/warp/benches/function.rs
+++ b/plugins/warp/benches/function.rs
@@ -2,43 +2,43 @@ use binaryninja::binary_view::BinaryViewExt;
use binaryninja::headless::Session;
use criterion::{criterion_group, criterion_main, Criterion};
use rayon::prelude::*;
+use std::path::PathBuf;
use warp_ninja::build_function;
-use warp_ninja::cache::FunctionCache;
+
+// These are the target files present in OUT_DIR
+// Add the files to fixtures/bin
+static TARGET_FILES: [&str; 1] = ["atox.obj"];
pub fn function_benchmark(c: &mut Criterion) {
let session = Session::new().expect("Failed to initialize session");
- let bv = session.load(env!("TEST_BIN_LIBRARY_OBJ")).unwrap();
- let functions = bv.functions();
- assert_eq!(functions.len(), 6);
- let mut function_iter = functions.into_iter();
- let first_function = function_iter.next().unwrap();
-
- c.bench_function("signature first function", |b| {
- b.iter(|| {
- let _ = build_function(&first_function, &first_function.low_level_il().unwrap());
- })
- });
+ let out_dir = env!("OUT_DIR").parse::<PathBuf>().unwrap();
+ for file_name in TARGET_FILES {
+ let path = out_dir.join(file_name);
+ let view = session.load(&path).expect("Failed to load view");
- c.bench_function("signature all functions", |b| {
- b.iter(|| {
- for func in &functions {
- let _ = build_function(&func, &func.low_level_il().unwrap());
- }
- })
- });
+ let functions: Vec<_> = view.functions().iter().map(|f| f.to_owned()).collect();
+ // Bench all functions sequentially
+ c.bench_function("signature all functions", |b| {
+ b.iter(|| {
+ for func in &functions {
+ let _ = build_function(&func, &func.lifted_il().unwrap());
+ }
+ })
+ });
- let cache = FunctionCache::default();
- c.bench_function("signature all functions rayon", |b| {
- b.iter(|| {
- functions
- .par_iter()
- .map_with(cache.clone(), |par_cache, func| {
- let llil = func.low_level_il().ok()?;
- Some(par_cache.function(func.as_ref(), llil.as_ref()))
- })
- .collect::<Vec<_>>()
- })
- });
+ // Bench all functions in parallel
+ c.bench_function("signature all functions rayon", |b| {
+ b.iter(|| {
+ functions
+ .par_iter()
+ .filter_map(|func| {
+ let llil = func.lifted_il().ok()?;
+ Some(build_function(func.as_ref(), llil.as_ref()))
+ })
+ .collect::<Vec<_>>()
+ })
+ });
+ }
}
criterion_group!(benches, function_benchmark);
diff --git a/plugins/warp/benches/guid.rs b/plugins/warp/benches/guid.rs
index 046886b5..eddf2f06 100644
--- a/plugins/warp/benches/guid.rs
+++ b/plugins/warp/benches/guid.rs
@@ -1,22 +1,31 @@
use binaryninja::binary_view::BinaryViewExt;
use binaryninja::headless::Session;
use criterion::{criterion_group, criterion_main, Criterion};
+use std::path::PathBuf;
use warp_ninja::function_guid;
+// These are the target files present in OUT_DIR
+// Add the files to fixtures/bin
+static TARGET_FILES: [&str; 1] = ["atox.obj"];
+
pub fn guid_benchmark(c: &mut Criterion) {
let session = Session::new().expect("Failed to initialize session");
- let bv = session.load(env!("TEST_BIN_LIBRARY_OBJ")).unwrap();
- let functions = bv.functions();
- assert_eq!(functions.len(), 6);
- let mut function_iter = functions.into_iter();
- let first_function = function_iter.next().unwrap();
+ let out_dir = env!("OUT_DIR").parse::<PathBuf>().unwrap();
+ for file_name in TARGET_FILES {
+ let path = out_dir.join(file_name);
+ let view = session.load(&path).expect("Failed to load view");
- c.bench_function("function guid", |b| {
- b.iter(|| {
- let llil = first_function.low_level_il().unwrap();
- function_guid(&first_function, &llil);
- })
- });
+ let functions: Vec<_> = view.functions().iter().map(|f| f.to_owned()).collect();
+ // Bench all functions sequentially
+ c.bench_function("guid all functions", |b| {
+ b.iter(|| {
+ for func in &functions {
+ let llil = func.lifted_il().unwrap();
+ function_guid(&func, &llil);
+ }
+ })
+ });
+ }
}
criterion_group!(benches, guid_benchmark);
diff --git a/plugins/warp/build.rs b/plugins/warp/build.rs
index 39b3238b..53b31182 100644
--- a/plugins/warp/build.rs
+++ b/plugins/warp/build.rs
@@ -31,4 +31,8 @@ fn main() {
}
}
}
+
+ println!("cargo::rerun-if-changed=src/templates");
+ // Templates used for rendering reports.
+ minijinja_embed::embed_templates!("src/templates");
}
diff --git a/plugins/warp/fixtures/bin/random.warp b/plugins/warp/fixtures/bin/random.warp
new file mode 100644
index 00000000..47c7367d
--- /dev/null
+++ b/plugins/warp/fixtures/bin/random.warp
Binary files differ
diff --git a/plugins/warp/src/bin/sigem.rs b/plugins/warp/src/bin/sigem.rs
deleted file mode 100644
index a50b4ae2..00000000
--- a/plugins/warp/src/bin/sigem.rs
+++ /dev/null
@@ -1,269 +0,0 @@
-use std::collections::HashSet;
-use std::fs::File;
-use std::io::Read;
-use std::path::{Path, PathBuf};
-
-use ar::Archive;
-use clap::{arg, Parser};
-use rayon::prelude::*;
-
-use binaryninja::binary_view::{BinaryView, BinaryViewExt};
-use binaryninja::function::Function as BNFunction;
-use binaryninja::rc::Guard as BNGuard;
-use binaryninja::settings::Settings;
-use serde_json::{json, Value};
-use walkdir::WalkDir;
-use warp::signature::Data;
-use warp_ninja::cache::{cached_type_references, register_cache_destructor};
-
-#[derive(Parser, Debug)]
-#[command(about, long_about)]
-/// A simple CLI utility to generate WARP signature files headlessly using Binary Ninja.
-///
-/// NOTE: This requires a headless compatible Binary Ninja, make sure it's in your path.
-struct Args {
- /// Path to create signatures from, this can be:
- /// - A binary (that can be opened with Binary Ninja)
- /// - A directory (all files will be merged)
- /// - An archive (with ext: a, lib, rlib)
- /// - A BNDB
- /// - A Signature file (sbin)
- #[arg(index = 1, verbatim_doc_comment)]
- path: PathBuf,
-
- /// The signature output file
- ///
- /// NOTE: If not specified the output will be the input path with the sbin extension
- /// as an example `mylib.a` will output `mylib.sbin`.
- #[arg(index = 2)]
- output: Option<PathBuf>,
-
- /// Should we overwrite output file
- ///
- /// NOTE: If the file exists we will exit early to prevent wasted effort.
- #[arg(short, long)]
- overwrite: Option<bool>,
-
- /// The external debug information file to use
- #[arg(short, long)]
- debug_info: Option<PathBuf>,
- // TODO: Add a file filter and default to filter out files starting with "."
-}
-
-fn default_settings(bn_settings: &Settings) -> Value {
- // TODO: Make these settings configurable through the CLI
- let mut settings = json!({
- "analysis.linearSweep.autorun": false,
- "analysis.signatureMatcher.autorun": false,
- "analysis.mode": "full",
- // The reason we need to do this is a little unfortunate.
- // Basically some of the COFF's have really low image bases that confuses
- // Analysis and also our basic block GUID when a constant value points to a low address section.
- // TODO: This might not exist, we should set this based on the view.
- "loader.imageBase": 0x1000000,
- });
-
- // If WARP is enabled we must turn it off to prevent matching on other stuff.
- if bn_settings.contains("analysis.warp.matcher") {
- settings["analysis.warp.matcher"] = json!(false);
- settings["analysis.warp.guid"] = json!(false);
- }
-
- settings
-}
-
-fn main() {
- let args = Args::parse();
- env_logger::Builder::from_env(env_logger::Env::default().default_filter_or("info")).init();
-
- // TODO: After analysis finishes for a file we should save off the bndb to another directory called the bndb cache
- // TODO: This cache should be used before opening a file for first analysis.
-
- // TODO: We should resolve the path to something sensible in cases where user is passing CWD.
- // If no output file was given, just prepend binary with extension sbin
- let output_file = args
- .output
- .unwrap_or(args.path.to_owned())
- .with_extension("sbin");
-
- if output_file.exists() && !args.overwrite.unwrap_or(false) {
- log::info!("Output file already exists, skipping... {:?}", output_file);
- return;
- }
-
- log::debug!("Starting Binary Ninja session...");
- let _headless_session =
- binaryninja::headless::Session::new().expect("Failed to initialize session");
-
- // Adjust the amount of worker threads so that we can actually free BinaryViews.
- let worker_count = rayon::current_num_threads() * 4;
- log::debug!("Adjusting Binary Ninja worker count to {}...", worker_count);
- binaryninja::worker_thread::set_worker_thread_count(worker_count);
-
- // Make sure caches are flushed when the views get destructed.
- register_cache_destructor();
-
- let bn_settings = Settings::new();
- let settings = default_settings(&bn_settings);
-
- log::info!("Creating functions for {:?}...", args.path);
- let start = std::time::Instant::now();
- let data = data_from_file(&settings, &args.path)
- .expect("Failed to read data, check your license and Binary Ninja version!");
- log::info!("Functions created in {:?}", start.elapsed());
-
- // TODO: Add a way to override the symbol type to make it a different function symbol.
- // TODO: Right now the consumers must dictate that.
- // TODO: The binja_warp consumer sets this to library function fwiw
-
- if !data.functions.is_empty() {
- std::fs::write(&output_file, data.to_bytes()).expect("Failed to write functions to file");
- log::info!(
- "{} functions written to {:?}...",
- data.functions.len(),
- output_file
- );
- } else {
- log::warn!("No functions found for binary {:?}...", args.path);
- }
-}
-
-fn data_from_view(view: &BinaryView) -> Data {
- let mut data = Data::default();
- let is_function_named = |f: &BNGuard<BNFunction>| {
- !f.symbol().short_name().to_string_lossy().contains("sub_") || f.has_user_annotations()
- };
-
- data.functions = view
- .functions()
- .iter()
- .filter(is_function_named)
- .filter_map(|f| {
- let llil = f.low_level_il().ok()?;
- Some(warp_ninja::cache::cached_function(&f, &llil))
- })
- .collect::<Vec<_>>();
-
- if let Some(ref_ty_cache) = cached_type_references(view) {
- let referenced_types = ref_ty_cache
- .cache
- .iter()
- .filter_map(|t| t.to_owned())
- .collect::<Vec<_>>();
-
- data.types.extend(referenced_types);
- }
-
- data
-}
-
-fn data_from_archive<R: Read>(settings: &Value, mut archive: Archive<R>) -> Option<Data> {
- // TODO: I feel like this is a hack...
- let temp_dir = tempdir::TempDir::new("tmp_archive").ok()?;
- // Iterate through the entries in the ar file and make a temp dir with them
- let mut entry_files: HashSet<PathBuf> = HashSet::new();
- while let Some(entry) = archive.next_entry() {
- match entry {
- Ok(mut entry) => {
- let name = String::from_utf8_lossy(entry.header().identifier()).to_string();
- // Write entry data to a temp directory
- let output_path = temp_dir.path().join(&name);
- if !entry_files.contains(&output_path) {
- let mut output_file =
- File::create(&output_path).expect("Failed to create entry file");
- std::io::copy(&mut entry, &mut output_file).expect("Failed to read entry data");
- entry_files.insert(output_path);
- } else {
- log::debug!("Skipping already inserted entry: {}", name);
- }
- }
- Err(e) => {
- log::error!("Failed to read archive entry: {}", e);
- }
- }
- }
-
- // Create the data.
- let entry_data = entry_files
- .into_par_iter()
- .filter_map(|path| {
- log::debug!("Creating data for ENTRY {:?}...", path);
- data_from_file(settings, &path)
- })
- .collect::<Vec<_>>();
-
- Some(Data::merge(entry_data))
-}
-
-fn data_from_directory(settings: &Value, dir: PathBuf) -> Option<Data> {
- let files = WalkDir::new(dir)
- .into_iter()
- .filter_map(|e| {
- let path = e.ok()?.into_path();
- if path.is_file() {
- Some(path)
- } else {
- None
- }
- })
- .collect::<Vec<_>>();
-
- let unmerged_data = files
- .into_par_iter()
- .filter_map(|path| {
- log::info!("Creating data for FILE {:?}...", path);
- data_from_file(settings, &path)
- })
- .collect::<Vec<_>>();
-
- if !unmerged_data.is_empty() {
- Some(Data::merge(unmerged_data))
- } else {
- None
- }
-}
-
-fn data_from_file(settings: &Value, path: &Path) -> Option<Data> {
- match path.extension() {
- Some(ext) if ext == "a" || ext == "lib" || ext == "rlib" => {
- let archive_file = File::open(path).expect("Failed to open archive file");
- let archive = Archive::new(archive_file);
- data_from_archive(settings, archive)
- }
- Some(ext) if ext == "sbin" => {
- let contents = std::fs::read(path).ok()?;
- Data::from_bytes(&contents)
- }
- _ if path.is_dir() => data_from_directory(settings, path.into()),
- _ => {
- let path_str = path.to_str().unwrap();
- let view = binaryninja::load_with_options(path_str, true, Some(settings.to_string()))?;
- let data = data_from_view(&view);
- view.file().close();
- Some(data)
- }
- }
-}
-
-#[cfg(test)]
-mod tests {
- use super::*;
- #[test]
- fn test_data_from_file() {
- env_logger::init();
- // TODO: Store oracles here to get more out of this test.
- let out_dir = env!("OUT_DIR").parse::<PathBuf>().unwrap();
- let _headless_session =
- binaryninja::headless::Session::new().expect("Failed to initialize session");
- let bn_settings = Settings::new();
- let settings = default_settings(&bn_settings);
- for entry in std::fs::read_dir(out_dir).expect("Failed to read OUT_DIR") {
- let entry = entry.expect("Failed to read directory entry");
- let path = entry.path();
- if path.is_file() {
- let result = data_from_file(&settings, &path);
- assert!(result.is_some());
- }
- }
- }
-}
diff --git a/plugins/warp/src/cache.rs b/plugins/warp/src/cache.rs
index cc8dfded..09ad1444 100644
--- a/plugins/warp/src/cache.rs
+++ b/plugins/warp/src/cache.rs
@@ -1,29 +1,18 @@
-use crate::convert::{from_bn_symbol, from_bn_type_internal};
-use crate::{build_function, function_guid};
+pub mod container;
+pub mod function;
+pub mod guid;
+pub mod type_reference;
+
+pub use function::*;
+pub use guid::*;
+pub use type_reference::*;
+
use binaryninja::binary_view::{BinaryView, BinaryViewExt};
-use binaryninja::confidence::MAX_CONFIDENCE;
use binaryninja::function::Function as BNFunction;
-use binaryninja::low_level_il::function::{FunctionMutability, LowLevelILFunction, NonSSA};
-use binaryninja::low_level_il::LowLevelILRegularFunction;
use binaryninja::rc::Guard;
use binaryninja::rc::Ref as BNRef;
-use binaryninja::symbol::Symbol as BNSymbol;
-use binaryninja::types::NamedTypeReference as BNNamedTypeReference;
use binaryninja::ObjectDestructor;
-use dashmap::mapref::one::Ref;
-use dashmap::DashMap;
-use std::collections::HashSet;
use std::hash::{DefaultHasher, Hash, Hasher};
-use std::sync::OnceLock;
-use warp::r#type::ComputedType;
-use warp::signature::function::constraints::FunctionConstraint;
-use warp::signature::function::{Function, FunctionGUID};
-
-pub static MATCHED_FUNCTION_CACHE: OnceLock<DashMap<ViewID, MatchedFunctionCache>> =
- OnceLock::new();
-pub static FUNCTION_CACHE: OnceLock<DashMap<ViewID, FunctionCache>> = OnceLock::new();
-pub static GUID_CACHE: OnceLock<DashMap<ViewID, GUIDCache>> = OnceLock::new();
-pub static TYPE_REF_CACHE: OnceLock<DashMap<ViewID, TypeRefCache>> = OnceLock::new();
pub fn register_cache_destructor() {
pub static mut CACHE_DESTRUCTOR: CacheDestructor = CacheDestructor;
@@ -34,351 +23,6 @@ pub fn register_cache_destructor() {
};
}
-pub fn cached_function_match<F>(function: &BNFunction, f: F) -> Option<Function>
-where
- F: Fn() -> Option<Function>,
-{
- let view = function.view();
- let view_id = ViewID::from(view.as_ref());
- let function_id = FunctionID::from(function);
- let function_cache = MATCHED_FUNCTION_CACHE.get_or_init(Default::default);
- match function_cache.get(&view_id) {
- Some(cache) => cache.get_or_insert(&function_id, f).to_owned(),
- None => {
- let cache = MatchedFunctionCache::default();
- let matched = cache.get_or_insert(&function_id, f).to_owned();
- function_cache.insert(view_id, cache);
- matched
- }
- }
-}
-
-pub fn try_cached_function_match(function: &BNFunction) -> Option<Function> {
- let view = function.view();
- let view_id = ViewID::from(view);
- let function_id = FunctionID::from(function);
- let function_cache = MATCHED_FUNCTION_CACHE.get_or_init(Default::default);
- function_cache
- .get(&view_id)?
- .get(&function_id)?
- .value()
- .to_owned()
-}
-
-pub fn cached_function(function: &BNFunction, llil: &LowLevelILRegularFunction) -> Function {
- let view = function.view();
- let view_id = ViewID::from(view.as_ref());
- let function_cache = FUNCTION_CACHE.get_or_init(Default::default);
- match function_cache.get(&view_id) {
- Some(cache) => cache.function(function, llil),
- None => {
- let cache = FunctionCache::default();
- let function = cache.function(function, llil);
- function_cache.insert(view_id, cache);
- function
- }
- }
-}
-
-pub fn cached_call_site_constraints(function: &BNFunction) -> HashSet<FunctionConstraint> {
- let view = function.view();
- let view_id = ViewID::from(view);
- let guid_cache = GUID_CACHE.get_or_init(Default::default);
- match guid_cache.get(&view_id) {
- Some(cache) => cache.call_site_constraints(function),
- None => {
- let cache = GUIDCache::default();
- let constraints = cache.call_site_constraints(function);
- guid_cache.insert(view_id, cache);
- constraints
- }
- }
-}
-
-pub fn cached_adjacency_constraints<F>(
- function: &BNFunction,
- filter: F,
-) -> HashSet<FunctionConstraint>
-where
- F: Fn(&BNFunction) -> bool,
-{
- let view = function.view();
- let view_id = ViewID::from(view);
- let guid_cache = GUID_CACHE.get_or_init(Default::default);
- match guid_cache.get(&view_id) {
- Some(cache) => cache.adjacency_constraints(function, filter),
- None => {
- let cache = GUIDCache::default();
- let constraints = cache.adjacency_constraints(function, filter);
- guid_cache.insert(view_id, cache);
- constraints
- }
- }
-}
-
-pub fn cached_function_guid<M: FunctionMutability>(
- function: &BNFunction,
- llil: &LowLevelILFunction<M, NonSSA>,
-) -> FunctionGUID {
- let view = function.view();
- let view_id = ViewID::from(view);
- let guid_cache = GUID_CACHE.get_or_init(Default::default);
- match guid_cache.get(&view_id) {
- Some(cache) => cache.function_guid(function, llil),
- None => {
- let cache = GUIDCache::default();
- let guid = cache.function_guid(function, llil);
- guid_cache.insert(view_id, cache);
- guid
- }
- }
-}
-
-pub fn try_cached_function_guid(function: &BNFunction) -> Option<FunctionGUID> {
- let view = function.view();
- let view_id = ViewID::from(view);
- let guid_cache = GUID_CACHE.get_or_init(Default::default);
- guid_cache.get(&view_id)?.try_function_guid(function)
-}
-
-pub fn cached_type_reference(
- view: &BinaryView,
- visited_refs: &mut HashSet<TypeRefID>,
- type_ref: &BNNamedTypeReference,
-) -> Option<ComputedType> {
- let view_id = ViewID::from(view);
- let type_ref_cache = TYPE_REF_CACHE.get_or_init(Default::default);
- match type_ref_cache.get(&view_id) {
- Some(cache) => cache.cached_type_reference(view, visited_refs, type_ref),
- None => {
- let cache = TypeRefCache::default();
- let ntr = cache.cached_type_reference(view, visited_refs, type_ref);
- type_ref_cache.insert(view_id, cache);
- ntr
- }
- }
-}
-
-pub fn cached_type_references(view: &BinaryView) -> Option<Ref<ViewID, TypeRefCache>> {
- let view_id = ViewID::from(view);
- let type_ref_cache = TYPE_REF_CACHE.get_or_init(Default::default);
- type_ref_cache.get(&view_id)
-}
-
-#[derive(Clone, Debug, Default)]
-pub struct MatchedFunctionCache {
- pub cache: DashMap<FunctionID, Option<Function>>,
-}
-
-impl MatchedFunctionCache {
- pub fn get_or_insert<F>(
- &self,
- function_id: &FunctionID,
- f: F,
- ) -> Ref<'_, FunctionID, Option<Function>>
- where
- F: FnOnce() -> Option<Function>,
- {
- self.cache.get(function_id).unwrap_or_else(|| {
- self.cache.insert(*function_id, f());
- self.cache.get(function_id).unwrap()
- })
- }
-
- pub fn get(&self, function_id: &FunctionID) -> Option<Ref<'_, FunctionID, Option<Function>>> {
- self.cache.get(function_id)
- }
-}
-
-#[derive(Clone, Debug, Default)]
-pub struct FunctionCache {
- pub cache: DashMap<FunctionID, Function>,
-}
-
-impl FunctionCache {
- pub fn function(&self, function: &BNFunction, llil: &LowLevelILRegularFunction) -> Function {
- let function_id = FunctionID::from(function);
- match self.cache.get(&function_id) {
- Some(function) => function.value().to_owned(),
- None => {
- let function = build_function(function, llil);
- self.cache.insert(function_id, function.clone());
- function
- }
- }
- }
-}
-
-#[derive(Clone, Debug, Default)]
-pub struct GUIDCache {
- pub cache: DashMap<FunctionID, FunctionGUID>,
-}
-
-impl GUIDCache {
- pub fn call_site_constraints(&self, function: &BNFunction) -> HashSet<FunctionConstraint> {
- let view = function.view();
- let func_id = FunctionID::from(function);
- let func_start = function.start();
- let func_platform = function.platform();
- let mut constraints = HashSet::new();
- for call_site in &function.call_sites() {
- for cs_ref_addr in view.code_refs_from_addr(call_site.address, Some(function)) {
- match view.function_at(&func_platform, cs_ref_addr) {
- Some(cs_ref_func) => {
- // Call site is a function, constrain on it.
- let cs_ref_func_id = FunctionID::from(cs_ref_func.as_ref());
- if cs_ref_func_id != func_id {
- let call_site_offset: i64 =
- call_site.address.wrapping_sub(func_start) as i64;
- // TODO: If the function is thunk we should also insert the called function.
- constraints
- .insert(self.function_constraint(&cs_ref_func, call_site_offset));
- }
- }
- None => {
- // We could be dealing with an extern symbol, get the symbol as a constraint.
- let call_site_offset: i64 =
- call_site.address.wrapping_sub(func_start) as i64;
- if let Some(call_site_sym) = view.symbol_by_address(cs_ref_addr) {
- constraints.insert(
- self.function_constraint_from_symbol(
- &call_site_sym,
- call_site_offset,
- ),
- );
- }
- }
- }
- }
- }
- constraints
- }
-
- pub fn adjacency_constraints<F>(
- &self,
- function: &BNFunction,
- filter: F,
- ) -> HashSet<FunctionConstraint>
- where
- F: Fn(&BNFunction) -> bool,
- {
- let view = function.view();
- let func_id = FunctionID::from(function);
- let func_start = function.start();
- let mut constraints = HashSet::new();
-
- let mut func_addr_constraint = |func_start_addr| {
- // NOTE: We could potentially have dozens of functions all at the same start address.
- for curr_func in &view.functions_at(func_start_addr) {
- let curr_func_id = FunctionID::from(curr_func.as_ref());
- if curr_func_id != func_id && filter(curr_func.as_ref()) {
- // NOTE: For this to work the GUID has to have already been cached. If not it will just be the symbol.
- // Function adjacent to another function, constrain on the pattern.
- let curr_addr_offset = (func_start_addr as i64) - func_start as i64;
- constraints.insert(self.function_constraint(&curr_func, curr_addr_offset));
- }
- }
- };
-
- let mut before_func_start = func_start;
- for _ in 0..2 {
- before_func_start = view.function_start_before(before_func_start);
- func_addr_constraint(before_func_start);
- }
-
- let mut after_func_start = func_start;
- for _ in 0..2 {
- after_func_start = view.function_start_after(after_func_start);
- func_addr_constraint(after_func_start);
- }
-
- constraints
- }
-
- /// Construct a function constraint, must pass the offset at which it is located.
- pub fn function_constraint(&self, function: &BNFunction, offset: i64) -> FunctionConstraint {
- let guid = self.try_function_guid(function);
- let symbol = from_bn_symbol(&function.symbol());
- FunctionConstraint {
- guid,
- symbol: Some(symbol),
- offset,
- }
- }
-
- /// Construct a function constraint from a symbol, typically used for extern function call sites, must pass the offset at which it is located.
- pub fn function_constraint_from_symbol(
- &self,
- symbol: &BNSymbol,
- offset: i64,
- ) -> FunctionConstraint {
- let symbol = from_bn_symbol(symbol);
- FunctionConstraint {
- guid: None,
- symbol: Some(symbol),
- offset,
- }
- }
-
- pub fn function_guid<M: FunctionMutability>(
- &self,
- function: &BNFunction,
- llil: &LowLevelILFunction<M, NonSSA>,
- ) -> FunctionGUID {
- let function_id = FunctionID::from(function);
- match self.cache.get(&function_id) {
- Some(function_guid) => function_guid.value().to_owned(),
- None => {
- let function_guid = function_guid(function, llil);
- self.cache.insert(function_id, function_guid);
- function_guid
- }
- }
- }
-
- pub fn try_function_guid(&self, function: &BNFunction) -> Option<FunctionGUID> {
- let function_id = FunctionID::from(function);
- self.cache
- .get(&function_id)
- .map(|function_guid| function_guid.value().to_owned())
- }
-}
-
-#[derive(Clone, Debug, Default)]
-pub struct TypeRefCache {
- pub cache: DashMap<TypeRefID, Option<ComputedType>>,
-}
-
-impl TypeRefCache {
- /// NOTE: No self-referential type must be used on this function.
- pub fn cached_type_reference(
- &self,
- view: &BinaryView,
- visited_refs: &mut HashSet<TypeRefID>,
- type_ref: &BNNamedTypeReference,
- ) -> Option<ComputedType> {
- let ntr_id = TypeRefID::from(type_ref);
- match self.cache.get(&ntr_id) {
- Some(cache) => cache.to_owned(),
- None => match type_ref.target(view) {
- Some(raw_ty) => {
- let computed_ty = ComputedType::new(from_bn_type_internal(
- view,
- visited_refs,
- &raw_ty,
- MAX_CONFIDENCE,
- ));
- self.cache
- .entry(ntr_id)
- .insert(Some(computed_ty))
- .to_owned()
- }
- None => self.cache.entry(ntr_id).insert(None).to_owned(),
- },
- }
- }
-}
-
/// A unique view ID, used for caching.
#[derive(Copy, Clone, Debug, Hash, PartialEq, Eq, PartialOrd, Ord)]
pub struct ViewID(u64);
@@ -430,48 +74,11 @@ impl From<Guard<'_, BNFunction>> for FunctionID {
}
}
-/// A unique named type reference ID, used for caching.
-#[derive(Copy, Clone, Debug, Hash, PartialEq, Eq, PartialOrd, Ord)]
-pub struct TypeRefID(u64);
-
-impl From<&BNNamedTypeReference> for TypeRefID {
- fn from(value: &BNNamedTypeReference) -> Self {
- let mut hasher = DefaultHasher::new();
- hasher.write(value.id().as_bytes());
- Self(hasher.finish())
- }
-}
-
-impl From<BNRef<BNNamedTypeReference>> for TypeRefID {
- fn from(value: BNRef<BNNamedTypeReference>) -> Self {
- Self::from(value.as_ref())
- }
-}
-
-impl From<Guard<'_, BNNamedTypeReference>> for TypeRefID {
- fn from(value: Guard<'_, BNNamedTypeReference>) -> Self {
- Self::from(value.as_ref())
- }
-}
-
pub struct CacheDestructor;
impl ObjectDestructor for CacheDestructor {
fn destruct_view(&self, view: &BinaryView) {
- // Clear caches as the view is no longer alive.
- let view_id = ViewID::from(view);
- if let Some(cache) = MATCHED_FUNCTION_CACHE.get() {
- cache.remove(&view_id);
- }
- if let Some(cache) = FUNCTION_CACHE.get() {
- cache.remove(&view_id);
- }
- if let Some(cache) = GUID_CACHE.get() {
- cache.remove(&view_id);
- }
- if let Some(cache) = TYPE_REF_CACHE.get() {
- cache.remove(&view_id);
- }
+ clear_type_ref_cache(view);
log::debug!("Removed WARP caches for {:?}", view.file().filename());
}
}
diff --git a/plugins/warp/src/cache/container.rs b/plugins/warp/src/cache/container.rs
new file mode 100644
index 00000000..59b31bad
--- /dev/null
+++ b/plugins/warp/src/cache/container.rs
@@ -0,0 +1,28 @@
+use crate::container::Container;
+use dashmap::DashMap;
+use std::ops::Deref;
+use std::sync::{Arc, OnceLock, RwLock};
+
+pub static CONTAINER_CACHE: OnceLock<DashMap<String, Arc<RwLock<Box<dyn Container>>>>> =
+ OnceLock::new();
+
+pub fn for_cached_containers(f: impl Fn(&dyn Container)) {
+ let containers_cache = CONTAINER_CACHE.get_or_init(Default::default);
+ for container in containers_cache.iter() {
+ if let Ok(guarded_container) = container.read() {
+ f(guarded_container.deref().as_ref());
+ }
+ }
+}
+
+// TODO: The static lifetime here is a little wierd... (we need it to Box)
+pub fn add_cached_container(container: impl Container + 'static) {
+ let containers_cache = CONTAINER_CACHE.get_or_init(Default::default);
+ let container_name = container.to_string();
+ containers_cache.insert(container_name, Arc::new(RwLock::new(Box::new(container))));
+}
+
+pub fn cached_containers() -> Vec<Arc<RwLock<Box<dyn Container>>>> {
+ let containers_cache = CONTAINER_CACHE.get_or_init(Default::default);
+ containers_cache.iter().map(|c| c.clone()).collect()
+}
diff --git a/plugins/warp/src/cache/function.rs b/plugins/warp/src/cache/function.rs
new file mode 100644
index 00000000..2fef05fd
--- /dev/null
+++ b/plugins/warp/src/cache/function.rs
@@ -0,0 +1,28 @@
+use binaryninja::function::{Function as BNFunction, FunctionUpdateType};
+use warp::signature::function::Function;
+
+/// Inserts a function match into the cache.
+///
+/// IMPORTANT: This will mark the function as needing updates, if you intend to fill in functions with
+/// no match (i.e. `None`), then you must change this function to prevent marking that as needing updates.
+/// However, it's perfectly valid to remove a match and need to update the function still, so be careful.
+pub fn insert_cached_function_match(function: &BNFunction, matched_function: Option<Function>) {
+ // NOTE: If we expect to run match_function multiple times on a function, we should move this elsewhere.
+ // Mark the function as needing updates so that reanalysis occurs on the function, and we apply the match.
+ function.mark_updates_required(FunctionUpdateType::FullAutoFunctionUpdate);
+ match matched_function {
+ Some(matched_function) => {
+ function.store_metadata("warp_matched_function", &matched_function.to_bytes(), false);
+ }
+ None => {
+ function.remove_metadata("warp_matched_function");
+ }
+ }
+}
+
+// TODO: This does allocations, and for every reanalysis.
+pub fn try_cached_function_match(function: &BNFunction) -> Option<Function> {
+ let metadata = function.query_metadata("warp_matched_function")?;
+ let raw_metadata = metadata.get_raw()?;
+ Function::from_bytes(&raw_metadata)
+}
diff --git a/plugins/warp/src/cache/guid.rs b/plugins/warp/src/cache/guid.rs
new file mode 100644
index 00000000..f1788a33
--- /dev/null
+++ b/plugins/warp/src/cache/guid.rs
@@ -0,0 +1,193 @@
+use crate::cache::FunctionID;
+use crate::convert::from_bn_symbol;
+use crate::function_guid;
+use binaryninja::binary_view::BinaryViewExt;
+use binaryninja::function::Function as BNFunction;
+use binaryninja::low_level_il::function::{FunctionMutability, LowLevelILFunction, NonSSA};
+use binaryninja::symbol::Symbol as BNSymbol;
+use std::collections::HashSet;
+use uuid::Uuid;
+use warp::signature::constraint::Constraint;
+use warp::signature::function::FunctionGUID;
+
+pub fn cached_function_guid<M: FunctionMutability>(
+ function: &BNFunction,
+ lifted_il: &LowLevelILFunction<M, NonSSA>,
+) -> FunctionGUID {
+ let cached_guid = try_cached_function_guid(function);
+ if let Some(cached_guid) = cached_guid {
+ return cached_guid;
+ }
+
+ let function_guid = function_guid(function, lifted_il);
+ function.store_metadata(
+ "warp_function_guid",
+ &function_guid.as_bytes().to_vec(),
+ false,
+ );
+ function_guid
+}
+
+pub fn try_cached_function_guid(function: &BNFunction) -> Option<FunctionGUID> {
+ let metadata = function.query_metadata("warp_function_guid")?;
+ let raw_metadata = metadata.get_raw()?;
+ let uuid = Uuid::from_slice(raw_metadata.as_slice()).ok()?;
+ Some(FunctionGUID::from(uuid))
+}
+
+pub fn cached_constraints<F>(function: &BNFunction, filter: F) -> HashSet<Constraint>
+where
+ F: Fn(&BNFunction) -> bool,
+{
+ // TODO: Implied constraints, symbol name, image offset
+ let cs_constraints = cached_call_site_constraints(function);
+ let adj_constraints = cached_adjacency_constraints(function, filter);
+ cs_constraints.union(&adj_constraints).cloned().collect()
+}
+
+pub fn cached_call_site_constraints(function: &BNFunction) -> HashSet<Constraint> {
+ let cache = ConstraintBuilder;
+ cache.call_site_constraints(function)
+}
+
+pub fn cached_adjacency_constraints<F>(function: &BNFunction, filter: F) -> HashSet<Constraint>
+where
+ F: Fn(&BNFunction) -> bool,
+{
+ let cache = ConstraintBuilder;
+ cache.adjacency_constraints(function, filter)
+}
+
+#[derive(Clone, Debug, Default)]
+pub struct ConstraintBuilder;
+
+impl ConstraintBuilder {
+ pub fn call_site_constraints(&self, function: &BNFunction) -> HashSet<Constraint> {
+ let view = function.view();
+ let func_id = FunctionID::from(function);
+ let func_start = function.start();
+ let func_platform = function.platform();
+ let mut constraints = HashSet::new();
+ for call_site in &function.call_sites() {
+ for cs_ref_addr in view.code_refs_from_addr(call_site.address, Some(function)) {
+ match view.function_at(&func_platform, cs_ref_addr) {
+ Some(cs_ref_func) => {
+ // Call site is a function, constrain on it.
+ let cs_ref_func_id = FunctionID::from(cs_ref_func.as_ref());
+ if cs_ref_func_id != func_id {
+ let call_site_offset: i64 =
+ call_site.address.wrapping_sub(func_start) as i64;
+ // TODO: If the function is thunk we should also insert the called function.
+ constraints.extend(
+ self.related_function_constraint(&cs_ref_func, call_site_offset),
+ );
+ }
+ }
+ None => {
+ // We could be dealing with an extern symbol, get the symbol as a constraint.
+ let call_site_offset: i64 =
+ call_site.address.wrapping_sub(func_start) as i64;
+ if let Some(call_site_sym) = view.symbol_by_address(cs_ref_addr) {
+ constraints.insert(
+ self.related_symbol_constraint(&call_site_sym, call_site_offset),
+ );
+ }
+ }
+ }
+ }
+ }
+ constraints
+ }
+
+ pub fn adjacency_constraints<F>(&self, function: &BNFunction, filter: F) -> HashSet<Constraint>
+ where
+ F: Fn(&BNFunction) -> bool,
+ {
+ let view = function.view();
+ let func_id = FunctionID::from(function);
+ let func_start = function.start();
+ let mut constraints = HashSet::new();
+
+ let mut func_addr_constraint = |func_start_addr| {
+ // NOTE: We could potentially have dozens of functions all at the same start address.
+ for curr_func in &view.functions_at(func_start_addr) {
+ let curr_func_id = FunctionID::from(curr_func.as_ref());
+ if curr_func_id != func_id && filter(curr_func.as_ref()) {
+ // NOTE: For this to work the GUID has to have already been cached. If not it will just be the symbol.
+ // Function adjacent to another function, constrain on the pattern.
+ let curr_addr_offset = (func_start_addr as i64) - func_start as i64;
+ constraints
+ .extend(self.related_function_constraint(&curr_func, curr_addr_offset));
+ }
+ }
+ };
+
+ let mut before_func_start = func_start;
+ for _ in 0..2 {
+ before_func_start = view.function_start_before(before_func_start);
+ func_addr_constraint(before_func_start);
+ }
+
+ let mut after_func_start = func_start;
+ for _ in 0..2 {
+ after_func_start = view.function_start_after(after_func_start);
+ func_addr_constraint(after_func_start);
+ }
+
+ constraints
+ }
+
+ /// Construct a function constraint, must pass the offset at which it is located.
+ pub fn related_function_constraint(
+ &self,
+ function: &BNFunction,
+ offset: i64,
+ ) -> Vec<Constraint> {
+ let mut constraints = vec![];
+ if let Some(guid) = try_cached_function_guid(function) {
+ let guid_constraint = Constraint::from_function(&guid, Some(offset));
+ constraints.push(guid_constraint);
+ }
+ let symbol_constraint = self.related_symbol_constraint(&function.symbol(), offset);
+ constraints.push(symbol_constraint);
+ constraints
+ }
+
+ /// Construct a symbol constraint, must pass the offset at which it is located.
+ pub fn related_symbol_constraint(&self, symbol: &BNSymbol, offset: i64) -> Constraint {
+ let mut symbol = from_bn_symbol(symbol);
+ symbol.name = clean_symbol_name(&symbol.name);
+ Constraint::from_symbol(&symbol, Some(offset))
+ }
+}
+
+/// Cleans various internal symbol prefixes and suffixes for consistency.
+///
+/// This is very important for getting matching symbol constraints.
+///
+/// Examples:
+/// - "__imp__RemoveDirectoryW@4" -> "RemoveDirectoryW"
+/// - "__free_base" -> "free_base"
+/// - "__impl__free_base" -> "free_base"
+/// - "j___free_base" -> "free_base"
+/// - "j_free_base" -> "free_base"
+/// - "_free_base" -> "free_base"
+pub fn clean_symbol_name(symbol_name: &str) -> String {
+ // Handle MSVC-style imported symbols
+ let without_imp = symbol_name.strip_prefix("__imp__").unwrap_or(symbol_name);
+
+ // Handle jump thunk prefix
+ let without_jump = without_imp.strip_prefix("j_").unwrap_or(without_imp);
+
+ // Strip all remaining leading underscores
+ let mut result = without_jump;
+ while result.starts_with('_') {
+ result = &result[1..];
+ }
+
+ // Remove stdcall decoration (@N suffix)
+ match result.find('@') {
+ Some(pos) => result[..pos].to_string(),
+ None => result.to_string(),
+ }
+}
diff --git a/plugins/warp/src/cache/type_reference.rs b/plugins/warp/src/cache/type_reference.rs
new file mode 100644
index 00000000..0b07c139
--- /dev/null
+++ b/plugins/warp/src/cache/type_reference.rs
@@ -0,0 +1,105 @@
+use crate::cache::ViewID;
+use crate::convert::from_bn_type_internal;
+use binaryninja::binary_view::BinaryView;
+use binaryninja::confidence::MAX_CONFIDENCE;
+use binaryninja::rc::Guard;
+use binaryninja::rc::Ref as BNRef;
+use binaryninja::types::NamedTypeReference as BNNamedTypeReference;
+use dashmap::mapref::one::Ref;
+use dashmap::DashMap;
+use std::collections::HashSet;
+use std::hash::{DefaultHasher, Hasher};
+use std::sync::OnceLock;
+use warp::r#type::ComputedType;
+
+pub static TYPE_REF_CACHE: OnceLock<DashMap<ViewID, TypeRefCache>> = OnceLock::new();
+
+pub fn clear_type_ref_cache(view: &BinaryView) {
+ let view_id = ViewID::from(view);
+ if let Some(cache) = TYPE_REF_CACHE.get() {
+ cache.remove(&view_id);
+ }
+}
+
+pub fn cached_type_reference(
+ view: &BinaryView,
+ visited_refs: &mut HashSet<TypeRefID>,
+ type_ref: &BNNamedTypeReference,
+) -> Option<ComputedType> {
+ let view_id = ViewID::from(view);
+ let type_ref_cache = TYPE_REF_CACHE.get_or_init(Default::default);
+ match type_ref_cache.get(&view_id) {
+ Some(cache) => cache.cached_type_reference(view, visited_refs, type_ref),
+ None => {
+ let cache = TypeRefCache::default();
+ let ntr = cache.cached_type_reference(view, visited_refs, type_ref);
+ type_ref_cache.insert(view_id, cache);
+ ntr
+ }
+ }
+}
+
+pub fn cached_type_references(view: &BinaryView) -> Option<Ref<ViewID, TypeRefCache>> {
+ let view_id = ViewID::from(view);
+ let type_ref_cache = TYPE_REF_CACHE.get_or_init(Default::default);
+ type_ref_cache.get(&view_id)
+}
+
+#[derive(Clone, Debug, Default)]
+pub struct TypeRefCache {
+ pub cache: DashMap<TypeRefID, Option<ComputedType>>,
+}
+
+impl TypeRefCache {
+ /// NOTE: No self-referential type must be used on this function.
+ pub fn cached_type_reference(
+ &self,
+ view: &BinaryView,
+ visited_refs: &mut HashSet<TypeRefID>,
+ type_ref: &BNNamedTypeReference,
+ ) -> Option<ComputedType> {
+ let ntr_id = TypeRefID::from(type_ref);
+ match self.cache.get(&ntr_id) {
+ Some(cache) => cache.to_owned(),
+ None => match type_ref.target(view) {
+ Some(raw_ty) => {
+ let computed_ty = ComputedType::new(from_bn_type_internal(
+ view,
+ visited_refs,
+ &raw_ty,
+ MAX_CONFIDENCE,
+ ));
+ self.cache
+ .entry(ntr_id)
+ .insert(Some(computed_ty))
+ .to_owned()
+ }
+ None => self.cache.entry(ntr_id).insert(None).to_owned(),
+ },
+ }
+ }
+}
+
+/// A unique named type reference ID, used for caching.
+#[derive(Copy, Clone, Debug, Hash, PartialEq, Eq, PartialOrd, Ord)]
+pub struct TypeRefID(u64);
+
+impl From<&BNNamedTypeReference> for TypeRefID {
+ fn from(value: &BNNamedTypeReference) -> Self {
+ let mut hasher = DefaultHasher::new();
+ hasher.write(value.id().as_bytes());
+ Self(hasher.finish())
+ }
+}
+
+impl From<BNRef<BNNamedTypeReference>> for TypeRefID {
+ fn from(value: BNRef<BNNamedTypeReference>) -> Self {
+ Self::from(value.as_ref())
+ }
+}
+
+impl From<Guard<'_, BNNamedTypeReference>> for TypeRefID {
+ fn from(value: Guard<'_, BNNamedTypeReference>) -> Self {
+ Self::from(value.as_ref())
+ }
+}
diff --git a/plugins/warp/src/container.rs b/plugins/warp/src/container.rs
new file mode 100644
index 00000000..8d720021
--- /dev/null
+++ b/plugins/warp/src/container.rs
@@ -0,0 +1,266 @@
+use crate::container::disk::NAMESPACE_DISK_SOURCE;
+use std::collections::HashMap;
+use std::fmt::{Debug, Display};
+use std::hash::Hash;
+use std::io;
+use std::path::{Path, PathBuf};
+use std::str::FromStr;
+use thiserror::Error;
+use uuid::Uuid;
+use warp::r#type::guid::TypeGUID;
+use warp::r#type::{ComputedType, Type};
+use warp::signature::function::{Function, FunctionGUID};
+use warp::target::Target;
+
+pub mod disk;
+pub mod memory;
+pub mod network;
+
+pub type ContainerResult<T> = Result<T, ContainerError>;
+
+#[derive(Debug, Error, PartialEq, Eq, Hash)]
+pub enum ContainerError {
+ #[error("source {0} was not found")]
+ SourceNotFound(SourceId),
+ #[error("source {0} is not writable")]
+ SourceNotWritable(SourceId),
+ #[error("source with path {0} already exists")]
+ SourceAlreadyExists(SourcePath),
+ #[error("source with path {0} cannot be created in container")]
+ CannotCreateSource(SourcePath),
+ #[error("operation failed due to corrupted data: {0}")]
+ CorruptedData(&'static str),
+ #[error("failed io operation: {0}")]
+ FailedIO(io::ErrorKind),
+ #[error("source {0} does not have an available path")]
+ SourcePathUnavailable(SourceId),
+}
+
+/// Represents the ID for a single container source.
+///
+/// A [`SourceId`] can be used in multiple separate containers, but **must** be unique in a container.
+///
+/// A source is used to relate types and functions separate from the container. This allows
+/// type name lookups and for containers which are bandwidth sensitive to exist.
+///
+/// An example of a bandwidth-sensitive container would be a container that pulls functions over
+/// the network instead of from memory.
+///
+/// This type is marked `repr(transparent)` to the underlying `[u8; 16]` type, so it is safe to use in FFI.
+#[repr(transparent)]
+#[derive(Clone, Debug, Eq, PartialEq, Hash, Copy)]
+pub struct SourceId(Uuid);
+
+impl SourceId {
+ pub fn new() -> Self {
+ Self(Uuid::new_v4())
+ }
+}
+
+impl From<Uuid> for SourceId {
+ fn from(value: Uuid) -> Self {
+ Self(value)
+ }
+}
+
+impl FromStr for SourceId {
+ type Err = uuid::Error;
+
+ fn from_str(s: &str) -> Result<Self, Self::Err> {
+ Uuid::parse_str(s).map(Into::into)
+ }
+}
+
+impl Display for SourceId {
+ fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+ write!(f, "{}", self.0)
+ }
+}
+
+/// Represents a unique path to a source.
+///
+/// This is used when first creating a source for a container, the path is given to the container
+/// as otherwise the user has no control over source creation and where the source is ultimately located.
+///
+/// While the underlying type is a [`PathBuf`], a source path can be really anything, the [`PathBuf`]
+/// just provides an easier way to join segments for nested source locations.
+#[derive(Clone, Debug, Eq, PartialEq, Hash)]
+pub struct SourcePath(PathBuf);
+
+impl SourcePath {
+ pub fn new(path: PathBuf) -> Self {
+ Self(path)
+ }
+
+ pub fn new_with_str(value: &str) -> Self {
+ Self(PathBuf::from(value))
+ }
+
+ pub fn to_source_id(&self) -> SourceId {
+ // TODO: This path is not relative to the disk container is it?
+ // TODO: The path here should be relative to the container I think?
+ // TODO: The above is important so that the id is the same across users.
+ let value: Vec<u8> = self.to_string().into_bytes();
+ SourceId(Uuid::new_v5(&NAMESPACE_DISK_SOURCE, &value))
+ }
+}
+
+impl AsRef<PathBuf> for SourcePath {
+ fn as_ref(&self) -> &PathBuf {
+ &self.0
+ }
+}
+
+impl AsRef<Path> for SourcePath {
+ fn as_ref(&self) -> &Path {
+ &self.0
+ }
+}
+
+impl Display for SourcePath {
+ fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+ write!(f, "{}", self.0.display())
+ }
+}
+
+/// Storage for WARP information.
+///
+/// Containers are made up of sources, see [`SourceId`] for more details.
+pub trait Container: Send + Sync + Display + Debug {
+ /// Available container sources.
+ ///
+ /// NOTE: Due to the nature of some containers, this list of sources may be incomplete. Do not
+ /// rely on this list to retrieve data, instead prefer:
+ /// - [Container::sources_with_type_guid]
+ /// - [Container::sources_with_type_guids]
+ /// - [Container::sources_with_function_guid]
+ /// - [Container::sources_with_function_guids]
+ fn sources(&self) -> ContainerResult<Vec<SourceId>>;
+
+ /// Create a new source in the container or add the existing source at the given path to known sources.
+ ///
+ /// The returned [`SourceId`] can be used to add, query and remove information from the source.
+ ///
+ /// NOTE: Adding a source does **NOT** mean that it, and the data associated with it, has been
+ /// persisted, you **MUST** call [`Container::commit_source`] to persist the created source.
+ ///
+ /// NOTE: Adding a source does **NOT** mean that you can write to it, use [`Container::is_source_writable`]
+ /// to verify the permissions of the source.
+ fn add_source(&mut self, path: SourcePath) -> ContainerResult<SourceId>;
+
+ /// Flush changes made to a source.
+ ///
+ /// Because writing to a source can require file or network operations, we let the container
+ /// offer the ability to hold off performing that operation until the data needs to be committed.
+ fn commit_source(&mut self, source: &SourceId) -> ContainerResult<bool>;
+
+ /// Whether the source can be written to.
+ ///
+ /// The source must be mutable to perform the following:
+ /// - [Container::add_types]
+ /// - [Container::add_computed_types]
+ /// - [Container::remove_types]
+ /// - [Container::add_functions]
+ /// - [Container::remove_functions]
+ fn is_source_writable(&self, source: &SourceId) -> ContainerResult<bool>;
+
+ /// Whether the source has uncommitted changes or not.
+ ///
+ /// NOTE: This is **NOT** whether the source has been committed at all, rather a flag to indicate
+ /// that a source has uncommitted changes.
+ fn is_source_uncommitted(&self, source: &SourceId) -> ContainerResult<bool>;
+
+ /// Retrieve the [`SourcePath`] for the given source.
+ ///
+ /// NOTE: This does not have to be a filesystem path, its representation is dictated
+ /// by the implementation.
+ fn source_path(&self, source: &SourceId) -> ContainerResult<SourcePath>;
+
+ // TODO: Note about commit_source
+ fn add_types(&mut self, source: &SourceId, types: &[Type]) -> ContainerResult<()> {
+ let computed_types: Vec<_> = types.iter().cloned().map(ComputedType::new).collect();
+ self.add_computed_types(source, &computed_types)
+ }
+
+ // TODO: Note about commit_source
+ fn add_computed_types(
+ &mut self,
+ source: &SourceId,
+ types: &[ComputedType],
+ ) -> ContainerResult<()>;
+
+ // TODO: Note about commit_source
+ fn remove_types(&mut self, source: &SourceId, guids: &[TypeGUID]) -> ContainerResult<()>;
+
+ // TODO: Note about commit_source
+ fn add_functions(
+ &mut self,
+ target: &Target,
+ source: &SourceId,
+ functions: &[Function],
+ ) -> ContainerResult<()>;
+
+ // TODO: Note about commit_source
+ fn remove_functions(
+ &mut self,
+ target: &Target,
+ source: &SourceId,
+ functions: &[Function],
+ ) -> ContainerResult<()>;
+
+ /// Get the sources that contain a type with the given [`TypeGUID`].
+ fn sources_with_type_guid(&self, guid: &TypeGUID) -> ContainerResult<Vec<SourceId>>;
+
+ /// Plural version of [`Container::sources_with_type_guid`].
+ ///
+ /// Each source will have a list of the containing GUID's so that when looking up a source, you give
+ /// it only the GUID's that it knows about, for networking this means cutting down traffic significantly.
+ fn sources_with_type_guids(
+ &self,
+ guids: &[TypeGUID],
+ ) -> ContainerResult<HashMap<TypeGUID, Vec<SourceId>>>;
+
+ /// Retrieve all [`TypeGUID`]'s with the given name.
+ fn type_guids_with_name(&self, source: &SourceId, name: &str)
+ -> ContainerResult<Vec<TypeGUID>>;
+
+ fn type_with_guid(&self, source: &SourceId, guid: &TypeGUID) -> ContainerResult<Option<Type>>;
+
+ fn has_type_with_guid(&self, source: &SourceId, guid: &TypeGUID) -> ContainerResult<bool> {
+ Ok(self.type_with_guid(source, guid)?.is_some())
+ }
+
+ /// Get the sources that contain functions with the given [`FunctionGUID`].
+ fn sources_with_function_guid(
+ &self,
+ target: &Target,
+ guid: &FunctionGUID,
+ ) -> ContainerResult<Vec<SourceId>>;
+
+ // TODO: Allocating with Vec is not good.
+ /// Plural version of [`Container::sources_with_function_guid`].
+ ///
+ /// Each source will have a list of the containing GUID's so that when looking up a source you give
+ /// it only the GUID's that it knows about, for networking this means cutting down traffic significantly.
+ fn sources_with_function_guids(
+ &self,
+ target: &Target,
+ guids: &[FunctionGUID],
+ ) -> ContainerResult<HashMap<FunctionGUID, Vec<SourceId>>>;
+
+ fn functions_with_guid(
+ &self,
+ target: &Target,
+ source: &SourceId,
+ guid: &FunctionGUID,
+ ) -> ContainerResult<Vec<Function>>;
+
+ fn has_function_with_guid(
+ &self,
+ target: &Target,
+ source: &SourceId,
+ guid: &FunctionGUID,
+ ) -> ContainerResult<bool> {
+ Ok(!self.functions_with_guid(target, source, guid)?.is_empty())
+ }
+}
diff --git a/plugins/warp/src/container/disk.rs b/plugins/warp/src/container/disk.rs
new file mode 100644
index 00000000..ca685a5f
--- /dev/null
+++ b/plugins/warp/src/container/disk.rs
@@ -0,0 +1,402 @@
+use crate::container::{Container, ContainerError, ContainerResult, SourceId, SourcePath};
+use std::collections::HashMap;
+use std::fmt::{Debug, Display, Formatter};
+use std::hash::{Hash, Hasher};
+use std::path::PathBuf;
+use uuid::{uuid, Uuid};
+use walkdir::{DirEntry, WalkDir};
+use warp::chunk::{Chunk, ChunkKind, CompressionType};
+use warp::r#type::chunk::TypeChunk;
+use warp::r#type::guid::TypeGUID;
+use warp::r#type::{ComputedType, Type};
+use warp::signature::chunk::SignatureChunk;
+use warp::signature::function::{Function, FunctionGUID};
+use warp::target::Target;
+use warp::{WarpFile, WarpFileHeader};
+
+pub const NAMESPACE_DISK_SOURCE: Uuid = uuid!("ea89e8ab-a27a-432b-8fbd-77b026cd5f41");
+
+// TODO: How to support remote projects? I.e. collaboration?
+pub struct DiskContainer {
+ pub name: String,
+ pub sources: HashMap<SourceId, DiskContainerSource>,
+}
+
+impl DiskContainer {
+ pub fn new(name: String, sources: HashMap<SourceId, DiskContainerSource>) -> Self {
+ Self { name, sources }
+ }
+
+ pub fn new_from_dir(dir_path: PathBuf) -> Self {
+ let source_from_entry = |entry: DirEntry| {
+ let path = SourcePath(entry.into_path());
+ let source_id = path.to_source_id();
+ let path_ext = path.0.extension().unwrap_or_default().to_str();
+ match (DiskContainerSource::new_from_path(path.clone()), path_ext) {
+ (Ok(source), _) => Some((source_id, source)),
+ (Err(err), Some("warp")) => {
+ log::error!("Failed to load source '{}' from disk: {}", path, err);
+ None
+ }
+ // We don't care to show errors loading for non-warp files.
+ (Err(_), _) => None,
+ }
+ };
+
+ // TODO: For now, any file that does not have the "warp" extension will be filtered out.
+ // TODO: cont. in the future we might want to remove this for convenience.
+ let name = dir_path.to_string_lossy().to_string();
+ let sources = WalkDir::new(dir_path)
+ .into_iter()
+ .filter_map(|e| e.ok())
+ .filter(|e| e.file_type().is_file())
+ .filter(|e| e.path().extension().is_some_and(|e| e == "warp"))
+ .filter_map(source_from_entry)
+ .collect();
+
+ Self::new(name, sources)
+ }
+}
+
+impl Container for DiskContainer {
+ fn sources(&self) -> ContainerResult<Vec<SourceId>> {
+ Ok(self.sources.keys().copied().collect())
+ }
+
+ fn add_source(&mut self, path: SourcePath) -> ContainerResult<SourceId> {
+ // Disk sources have there source id computed from the path.
+ let source_id = path.to_source_id();
+ if self.sources.contains_key(&source_id) {
+ return Err(ContainerError::SourceAlreadyExists(path));
+ }
+ // NOTE: We let anyone add a file from anywhere on the file system because of this.
+ match path.0.exists() {
+ true => {
+ let disk_source = DiskContainerSource::new_from_path(path.clone())?;
+ self.sources.insert(source_id, disk_source);
+ Ok(source_id)
+ }
+ false => {
+ let file = WarpFile::new(WarpFileHeader::new(), vec![]);
+ let disk_source = DiskContainerSource::new(path, file);
+ self.sources.insert(source_id, disk_source);
+ Ok(source_id)
+ }
+ }
+ }
+
+ fn commit_source(&mut self, source: &SourceId) -> ContainerResult<bool> {
+ let disk_source = self
+ .sources
+ .get_mut(source)
+ .ok_or(ContainerError::SourceNotFound(*source))?;
+
+ disk_source.commit_to_disk()
+ }
+
+ fn is_source_writable(&self, source: &SourceId) -> ContainerResult<bool> {
+ let _disk_source = self
+ .sources
+ .get(source)
+ .ok_or(ContainerError::SourceNotFound(*source))?;
+ // TODO: I think this should be up to the container. (cant write to bundled files)
+ Ok(true)
+ }
+
+ fn is_source_uncommitted(&self, source: &SourceId) -> ContainerResult<bool> {
+ let disk_source = self
+ .sources
+ .get(source)
+ .ok_or(ContainerError::SourceNotFound(*source))?;
+ Ok(disk_source.uncommitted)
+ }
+
+ fn source_path(&self, source: &SourceId) -> ContainerResult<SourcePath> {
+ let disk_source = self
+ .sources
+ .get(source)
+ .ok_or(ContainerError::SourceNotFound(*source))?;
+ Ok(disk_source.path.clone())
+ }
+
+ fn add_computed_types(
+ &mut self,
+ source: &SourceId,
+ types: &[ComputedType],
+ ) -> ContainerResult<()> {
+ let disk_source = self
+ .sources
+ .get_mut(source)
+ .ok_or(ContainerError::SourceNotFound(*source))?;
+
+ disk_source.add_computed_types(types)
+ }
+
+ // TODO: I believe any remove has to happen immediately, i.e. we cant add an uncommitted for this?
+ fn remove_types(&mut self, source: &SourceId, _guids: &[TypeGUID]) -> ContainerResult<()> {
+ let _disk_source = self
+ .sources
+ .get(source)
+ .ok_or(ContainerError::SourceNotFound(*source))?;
+
+ // TODO: Do this.
+ Err(ContainerError::SourceNotWritable(*source))
+ }
+
+ fn add_functions(
+ &mut self,
+ target: &Target,
+ source: &SourceId,
+ functions: &[Function],
+ ) -> ContainerResult<()> {
+ let disk_source = self
+ .sources
+ .get_mut(source)
+ .ok_or(ContainerError::SourceNotFound(*source))?;
+
+ disk_source.add_functions(target.clone(), functions)
+ }
+
+ // TODO: I believe any remove has to happen immediately, i.e. we cant add an uncommitted for this?
+ fn remove_functions(
+ &mut self,
+ _target: &Target,
+ source: &SourceId,
+ _functions: &[Function],
+ ) -> ContainerResult<()> {
+ let _disk_source = self
+ .sources
+ .get(source)
+ .ok_or(ContainerError::SourceNotFound(*source))?;
+
+ // TODO: Do this.
+ Err(ContainerError::SourceNotWritable(*source))
+ }
+
+ fn sources_with_type_guid(&self, guid: &TypeGUID) -> ContainerResult<Vec<SourceId>> {
+ let sources = self
+ .sources
+ .iter()
+ .filter(|(_, source)| source.has_type_with_guid(guid))
+ .map(|(id, _)| *id)
+ .collect();
+ Ok(sources)
+ }
+
+ fn sources_with_type_guids<'a>(
+ &'a self,
+ guids: &'a [TypeGUID],
+ ) -> ContainerResult<HashMap<TypeGUID, Vec<SourceId>>> {
+ let mut result: HashMap<TypeGUID, Vec<SourceId>> = HashMap::new();
+ for (source_id, source) in &self.sources {
+ guids
+ .iter()
+ .filter(|guid| source.has_type_with_guid(guid))
+ .for_each(|guid| result.entry(*guid).or_default().push(*source_id));
+ }
+ Ok(result)
+ }
+
+ fn type_guids_with_name(
+ &self,
+ source: &SourceId,
+ name: &str,
+ ) -> ContainerResult<Vec<TypeGUID>> {
+ let disk_source = self
+ .sources
+ .get(source)
+ .ok_or(ContainerError::SourceNotFound(*source))?;
+ Ok(disk_source.type_guids_with_name(name))
+ }
+
+ fn type_with_guid(&self, source: &SourceId, guid: &TypeGUID) -> ContainerResult<Option<Type>> {
+ let disk_source = self
+ .sources
+ .get(source)
+ .ok_or(ContainerError::SourceNotFound(*source))?;
+ Ok(disk_source.type_with_guid(guid))
+ }
+
+ fn sources_with_function_guid(
+ &self,
+ target: &Target,
+ guid: &FunctionGUID,
+ ) -> ContainerResult<Vec<SourceId>> {
+ let sources = self
+ .sources
+ .iter()
+ .filter(|(_, source)| source.has_function_with_guid(target, guid))
+ .map(|(id, _)| *id)
+ .collect();
+ Ok(sources)
+ }
+
+ fn sources_with_function_guids<'a>(
+ &self,
+ target: &Target,
+ guids: &[FunctionGUID],
+ ) -> ContainerResult<HashMap<FunctionGUID, Vec<SourceId>>> {
+ let mut result: HashMap<FunctionGUID, Vec<SourceId>> = HashMap::new();
+ for (source_id, source) in &self.sources {
+ guids
+ .iter()
+ .filter(|guid| source.has_function_with_guid(target, guid))
+ .for_each(|guid| result.entry(*guid).or_default().push(*source_id));
+ }
+ Ok(result)
+ }
+
+ fn functions_with_guid(
+ &self,
+ target: &Target,
+ source: &SourceId,
+ guid: &FunctionGUID,
+ ) -> ContainerResult<Vec<Function>> {
+ let disk_source = self
+ .sources
+ .get(source)
+ .ok_or(ContainerError::SourceNotFound(*source))?;
+ Ok(disk_source.functions_with_guid(target, guid))
+ }
+}
+
+impl Display for DiskContainer {
+ fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
+ write!(f, "{}", self.name)
+ }
+}
+
+impl Debug for DiskContainer {
+ fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
+ f.debug_struct("DiskContainer")
+ .field("name", &self.name)
+ .field("sources", &self.sources)
+ .finish()
+ }
+}
+
+pub struct DiskContainerSource {
+ pub path: SourcePath,
+ file: WarpFile<'static>,
+ uncommitted: bool,
+}
+
+impl DiskContainerSource {
+ pub fn new(path: SourcePath, file: WarpFile<'static>) -> Self {
+ Self {
+ path,
+ file,
+ uncommitted: false,
+ }
+ }
+
+ pub fn new_from_path(path: SourcePath) -> ContainerResult<Self> {
+ // TODO: To keep the lifetime out of DiskContainerSource we do not allow mapping file to memory.
+ let contents = std::fs::read(&path).map_err(|e| ContainerError::FailedIO(e.kind()))?;
+ let file = WarpFile::from_owned_bytes(contents).ok_or(ContainerError::CorruptedData(
+ "file data failed to validate",
+ ))?;
+ Ok(Self::new(path, file))
+ }
+
+ fn add_computed_types(&mut self, types: &[ComputedType]) -> ContainerResult<()> {
+ let type_chunk = TypeChunk::new_with_computed(types).ok_or(
+ ContainerError::CorruptedData("type chunk failed to validate"),
+ )?;
+ let chunk = Chunk::new(ChunkKind::Type(type_chunk), CompressionType::None);
+ self.file.chunks.push(chunk);
+ self.uncommitted = true;
+ Ok(())
+ }
+
+ fn add_functions(&mut self, target: Target, functions: &[Function]) -> ContainerResult<()> {
+ let signature_chunk = SignatureChunk::new(functions).ok_or(
+ ContainerError::CorruptedData("signature chunk failed to validate"),
+ )?;
+ let chunk = Chunk::new_with_target(
+ ChunkKind::Signature(signature_chunk),
+ CompressionType::None,
+ target,
+ );
+ self.file.chunks.push(chunk);
+ self.uncommitted = true;
+ Ok(())
+ }
+
+ fn commit_to_disk(&mut self) -> ContainerResult<bool> {
+ let file = self.file.to_bytes();
+ std::fs::write(&self.path, file).map_err(|e| ContainerError::FailedIO(e.kind()))?;
+ self.uncommitted = false;
+ Ok(true)
+ }
+
+ fn type_guids_with_name(&self, name: &str) -> Vec<TypeGUID> {
+ let mut found: Vec<TypeGUID> = Vec::new();
+ for chunk in &self.file.chunks {
+ if let ChunkKind::Type(tc) = &chunk.kind {
+ found.extend(
+ tc.raw_type_with_name(name)
+ .into_iter()
+ .map(|t| TypeGUID::from(t.guid())),
+ );
+ }
+ }
+ found
+ }
+
+ fn type_with_guid(&self, guid: &TypeGUID) -> Option<Type> {
+ self.file.chunks.iter().find_map(|chunk| {
+ if let ChunkKind::Type(tc) = &chunk.kind {
+ tc.type_with_guid(guid)
+ } else {
+ None
+ }
+ })
+ }
+
+ // TODO: When we support reading lazily instead of all in memory.
+ fn has_type_with_guid(&self, guid: &TypeGUID) -> bool {
+ self.type_with_guid(guid).is_some()
+ }
+
+ fn functions_with_guid(&self, target: &Target, guid: &FunctionGUID) -> Vec<Function> {
+ let mut found: Vec<Function> = Vec::new();
+ for chunk in &self.file.chunks {
+ if chunk.header.target != *target {
+ continue;
+ }
+ if let ChunkKind::Signature(sc) = &chunk.kind {
+ found.extend(sc.functions_with_guid(guid));
+ }
+ }
+ found
+ }
+
+ // TODO: When we support reading lazily instead of all in memory.
+ fn has_function_with_guid(&self, target: &Target, guid: &FunctionGUID) -> bool {
+ // TODO: How about we dont clone.
+ !self.functions_with_guid(target, guid).is_empty()
+ }
+}
+
+impl Hash for DiskContainerSource {
+ fn hash<H: Hasher>(&self, state: &mut H) {
+ self.path.hash(state);
+ }
+}
+
+impl Display for DiskContainerSource {
+ fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
+ write!(f, "{}", self.path)
+ }
+}
+
+impl Debug for DiskContainerSource {
+ fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
+ f.debug_struct("DiskContainerSource")
+ .field("path", &self.path)
+ .field("file_header", &self.file.header)
+ .field("file_chunks", &self.file.chunks.len())
+ .finish()
+ }
+}
diff --git a/plugins/warp/src/container/memory.rs b/plugins/warp/src/container/memory.rs
new file mode 100644
index 00000000..cf53390e
--- /dev/null
+++ b/plugins/warp/src/container/memory.rs
@@ -0,0 +1,307 @@
+use crate::container::{Container, ContainerError, ContainerResult, SourceId, SourcePath};
+use std::collections::HashMap;
+use std::fmt::Display;
+use warp::r#type::guid::TypeGUID;
+use warp::r#type::{ComputedType, Type};
+use warp::signature::function::{Function, FunctionGUID};
+use warp::target::Target;
+
+#[derive(Debug, Clone, PartialEq, Eq, Default)]
+pub struct MemoryContainer {
+ sources: HashMap<SourceId, MemorySource>,
+}
+
+impl MemoryContainer {
+ pub fn new() -> Self {
+ MemoryContainer::default()
+ }
+
+ pub fn with_source(mut self, id: SourceId, source: MemorySource) -> Self {
+ self.sources.insert(id, source);
+ self
+ }
+
+ pub fn with_source_function(
+ mut self,
+ id: SourceId,
+ guid: FunctionGUID,
+ func: Function,
+ ) -> Self {
+ self.sources
+ .entry(id)
+ .or_default()
+ .functions
+ .entry(guid)
+ .or_default()
+ .push(func);
+ self
+ }
+
+ pub fn with_source_type(mut self, id: SourceId, guid: TypeGUID, ty: Type) -> Self {
+ self.sources.entry(id).or_default().types.insert(guid, ty);
+ self
+ }
+}
+
+impl Container for MemoryContainer {
+ fn sources(&self) -> ContainerResult<Vec<SourceId>> {
+ todo!()
+ }
+
+ fn add_source(&mut self, path: SourcePath) -> ContainerResult<SourceId> {
+ Err(ContainerError::CannotCreateSource(path))
+ }
+
+ fn commit_source(&mut self, _source: &SourceId) -> ContainerResult<bool> {
+ Ok(false)
+ }
+
+ fn is_source_writable(&self, source: &SourceId) -> ContainerResult<bool> {
+ let memory_source = self
+ .sources
+ .get(source)
+ .ok_or(ContainerError::SourceNotFound(*source))?;
+ Ok(memory_source.writable)
+ }
+
+ fn is_source_uncommitted(&self, source: &SourceId) -> ContainerResult<bool> {
+ let _memory_source = self
+ .sources
+ .get(source)
+ .ok_or(ContainerError::SourceNotFound(*source))?;
+ // NOTE: Memory containers do not have a notion of uncommitted data.
+ Ok(false)
+ }
+
+ fn source_path(&self, source: &SourceId) -> ContainerResult<SourcePath> {
+ Err(ContainerError::SourcePathUnavailable(*source))
+ }
+
+ fn add_computed_types(
+ &mut self,
+ source: &SourceId,
+ types: &[ComputedType],
+ ) -> ContainerResult<()> {
+ let memory_source = self
+ .sources
+ .get_mut(source)
+ .ok_or(ContainerError::SourceNotFound(*source))?;
+ match memory_source.writable {
+ true => {
+ for ty in types {
+ memory_source.types.insert(ty.guid, ty.ty.clone());
+ }
+ Ok(())
+ }
+ false => Err(ContainerError::SourceNotWritable(*source)),
+ }
+ }
+
+ fn remove_types(&mut self, source: &SourceId, guids: &[TypeGUID]) -> ContainerResult<()> {
+ let memory_source = self
+ .sources
+ .get_mut(source)
+ .ok_or(ContainerError::SourceNotFound(*source))?;
+ match memory_source.writable {
+ true => {
+ for guid in guids {
+ memory_source.types.remove(guid);
+ }
+ Ok(())
+ }
+ false => Err(ContainerError::SourceNotWritable(*source)),
+ }
+ }
+
+ fn add_functions(
+ &mut self,
+ _target: &Target,
+ source: &SourceId,
+ functions: &[Function],
+ ) -> ContainerResult<()> {
+ let memory_source = self
+ .sources
+ .get_mut(source)
+ .ok_or(ContainerError::SourceNotFound(*source))?;
+ match memory_source.writable {
+ true => {
+ for function in functions {
+ memory_source
+ .functions
+ .entry(function.guid)
+ .or_default()
+ .push(function.clone());
+ }
+ Ok(())
+ }
+ false => Err(ContainerError::SourceNotWritable(*source)),
+ }
+ }
+
+ fn remove_functions(
+ &mut self,
+ _target: &Target,
+ source: &SourceId,
+ functions: &[Function],
+ ) -> ContainerResult<()> {
+ let memory_source = self
+ .sources
+ .get_mut(source)
+ .ok_or(ContainerError::SourceNotFound(*source))?;
+ match memory_source.writable {
+ true => {
+ for function in functions {
+ if let Some(src_funcs) = memory_source.functions.get_mut(&function.guid) {
+ src_funcs.retain(|f| f != function);
+ if src_funcs.is_empty() {
+ memory_source.functions.remove(&function.guid);
+ }
+ }
+ }
+ Ok(())
+ }
+ false => Err(ContainerError::SourceNotWritable(*source)),
+ }
+ }
+
+ fn sources_with_type_guid(&self, guid: &TypeGUID) -> ContainerResult<Vec<SourceId>> {
+ let sources = self
+ .sources
+ .iter()
+ .filter(|(_, source)| source.has_type_with_guid(guid))
+ .map(|(id, _)| *id)
+ .collect();
+ Ok(sources)
+ }
+
+ fn sources_with_type_guids(
+ &self,
+ guids: &[TypeGUID],
+ ) -> ContainerResult<HashMap<TypeGUID, Vec<SourceId>>> {
+ let mut result: HashMap<TypeGUID, Vec<SourceId>> = HashMap::new();
+ for (source_id, source) in &self.sources {
+ guids
+ .iter()
+ .filter(|guid| source.has_type_with_guid(guid))
+ .for_each(|guid| result.entry(*guid).or_default().push(*source_id));
+ }
+ Ok(result)
+ }
+
+ fn type_guids_with_name(
+ &self,
+ source: &SourceId,
+ name: &str,
+ ) -> ContainerResult<Vec<TypeGUID>> {
+ let memory_source = self
+ .sources
+ .get(source)
+ .ok_or(ContainerError::SourceNotFound(*source))?;
+ Ok(memory_source.type_guids_with_name(name))
+ }
+
+ fn type_with_guid(&self, source: &SourceId, guid: &TypeGUID) -> ContainerResult<Option<Type>> {
+ let memory_source = self
+ .sources
+ .get(source)
+ .ok_or(ContainerError::SourceNotFound(*source))?;
+ Ok(memory_source.type_with_guid(guid))
+ }
+
+ fn sources_with_function_guid(
+ &self,
+ _target: &Target,
+ guid: &FunctionGUID,
+ ) -> ContainerResult<Vec<SourceId>> {
+ let sources = self
+ .sources
+ .iter()
+ .filter(|(_, source)| source.has_function_with_guid(guid))
+ .map(|(id, _)| *id)
+ .collect();
+ Ok(sources)
+ }
+
+ fn sources_with_function_guids(
+ &self,
+ _target: &Target,
+ guids: &[FunctionGUID],
+ ) -> ContainerResult<HashMap<FunctionGUID, Vec<SourceId>>> {
+ let mut result: HashMap<FunctionGUID, Vec<SourceId>> = HashMap::new();
+ for (source_id, source) in &self.sources {
+ guids
+ .iter()
+ .filter(|guid| source.has_function_with_guid(guid))
+ .for_each(|guid| result.entry(*guid).or_default().push(*source_id));
+ }
+ Ok(result)
+ }
+
+ fn functions_with_guid(
+ &self,
+ _target: &Target,
+ source: &SourceId,
+ guid: &FunctionGUID,
+ ) -> ContainerResult<Vec<Function>> {
+ let memory_source = self
+ .sources
+ .get(source)
+ .ok_or(ContainerError::SourceNotFound(*source))?;
+ Ok(memory_source.functions_with_guid(guid))
+ }
+}
+
+impl Display for MemoryContainer {
+ fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+ f.write_str("MemoryContainer")
+ }
+}
+
+/// An in-memory store of functions.
+///
+/// This is typically an overlay on top of a container source.
+#[derive(Eq, PartialEq, Debug, Clone)]
+pub struct MemorySource {
+ pub writable: bool,
+ pub functions: HashMap<FunctionGUID, Vec<Function>>,
+ pub types: HashMap<TypeGUID, Type>,
+ pub named_types: HashMap<String, Vec<TypeGUID>>,
+}
+
+impl MemorySource {
+ pub fn type_guids_with_name(&self, name: &str) -> Vec<TypeGUID> {
+ // TODO: The function here is a little goofy.
+ // TODO: This is cloned.
+ self.named_types.get(name).cloned().unwrap_or_default()
+ }
+
+ pub fn type_with_guid(&self, guid: &TypeGUID) -> Option<Type> {
+ // TODO: This is cloned.
+ self.types.get(guid).cloned()
+ }
+
+ pub fn functions_with_guid(&self, guid: &FunctionGUID) -> Vec<Function> {
+ // TODO: The function here is a little goofy.
+ // TODO: This is cloned.
+ self.functions.get(guid).cloned().unwrap_or_default()
+ }
+
+ pub fn has_type_with_guid(&self, guid: &TypeGUID) -> bool {
+ self.type_with_guid(guid).is_some()
+ }
+
+ pub fn has_function_with_guid(&self, guid: &FunctionGUID) -> bool {
+ !self.functions_with_guid(guid).is_empty()
+ }
+}
+
+impl Default for MemorySource {
+ fn default() -> Self {
+ Self {
+ writable: true,
+ functions: HashMap::new(),
+ types: HashMap::new(),
+ named_types: HashMap::new(),
+ }
+ }
+}
diff --git a/plugins/warp/src/container/network.rs b/plugins/warp/src/container/network.rs
new file mode 100644
index 00000000..ffbe6108
--- /dev/null
+++ b/plugins/warp/src/container/network.rs
@@ -0,0 +1,13 @@
+pub struct NetworkContainer {}
+
+// TODO: The container is populated as the user is navigating a binary
+// TODO: We need to have a few helper functions here to post and pull
+// TODO: Then in the interface we operate off the network cache
+// TODO: The network cache could just be a disk container? Or disk container sources?
+// TODO: We should also store the cache on the filesystem for a certain time, will need to timestamp
+// TODO: When we commit we need to actually POST i believe.
+// TODO: There needs to be a setting that adjusts the sweep size of functions at the cursor.
+// TODO: Probably need a callback or something to tell the network containers to refresh from the network.
+// TODO: The network container should never instantiate itself, unless its gurenteed to not have any data in it?
+
+// TODO: Need to PUSH chunks and PULL chunks
diff --git a/plugins/warp/src/convert.rs b/plugins/warp/src/convert.rs
index b6c2f7b4..8272768b 100644
--- a/plugins/warp/src/convert.rs
+++ b/plugins/warp/src/convert.rs
@@ -1,678 +1,54 @@
-use std::collections::HashSet;
+pub mod symbol;
+pub mod types;
-use binaryninja::architecture::Architecture as BNArchitecture;
-use binaryninja::architecture::ArchitectureExt;
-use binaryninja::binary_view::{BinaryView, BinaryViewExt};
-use binaryninja::calling_convention::CoreCallingConvention as BNCallingConvention;
-use binaryninja::confidence::{Conf as BNConf, MAX_CONFIDENCE};
-use binaryninja::rc::Ref as BNRef;
-use binaryninja::symbol::{Symbol as BNSymbol, SymbolType as BNSymbolType};
-use binaryninja::types::{
- BaseStructure as BNBaseStructure, EnumerationBuilder as BNEnumerationBuilder,
- FunctionParameter as BNFunctionParameter, MemberAccess as BNMemberAccess, MemberAccess,
- MemberScope as BNMemberScope, NamedTypeReference, NamedTypeReference as BNNamedTypeReference,
- NamedTypeReferenceClass, StructureBuilder as BNStructureBuilder,
- StructureMember as BNStructureMember,
-};
-use binaryninja::types::{
- StructureType as BNStructureType, Type as BNType, TypeClass as BNTypeClass,
-};
+use binaryninja::function::Comment as BNComment;
+use binaryninja::function::Function as BNFunction;
+use binaryninja::platform::Platform;
+use binaryninja::rc::Ref;
+pub use symbol::*;
+pub use types::*;
+use warp::signature::comment::FunctionComment;
+use warp::target::Target;
-use crate::cache::{cached_type_reference, TypeRefID};
-use warp::r#type::class::array::ArrayModifiers;
-use warp::r#type::class::function::{Location, RegisterLocation};
-use warp::r#type::class::pointer::PointerAddressing;
-use warp::r#type::class::structure::StructureMemberModifiers;
-use warp::r#type::class::{
- ArrayClass, BooleanClass, CallingConvention, CharacterClass, EnumerationClass,
- EnumerationMember, FloatClass, FunctionClass, FunctionMember, IntegerClass, PointerClass,
- ReferrerClass, StructureClass, StructureMember, TypeClass,
-};
-use warp::r#type::Type;
-use warp::symbol::class::SymbolClass;
-use warp::symbol::{Symbol, SymbolModifiers};
-
-pub fn from_bn_symbol(raw_symbol: &BNSymbol) -> Symbol {
- // TODO: Use this?
- let _is_export = raw_symbol.external();
- let symbol_name = raw_symbol.raw_name().to_string_lossy().to_string();
- match raw_symbol.sym_type() {
- BNSymbolType::ImportAddress => {
- Symbol::new(
- symbol_name,
- SymbolClass::Function,
- // TODO: External = symbolic i guess
- SymbolModifiers::External,
- )
- }
- BNSymbolType::Data => {
- Symbol::new(
- symbol_name,
- // TODO: Data?
- SymbolClass::Data,
- SymbolModifiers::default(),
- )
- }
- BNSymbolType::Symbolic => {
- Symbol::new(
- symbol_name,
- SymbolClass::Function,
- // TODO: External = symbolic i guess
- SymbolModifiers::External,
- )
- }
- BNSymbolType::LocalLabel => {
- // TODO: This is a placeholder for another symbol.
- Symbol::new(symbol_name, SymbolClass::Data, SymbolModifiers::External)
- }
- BNSymbolType::External => Symbol::new(
- symbol_name,
- // TODO: External data?
- SymbolClass::Function,
- SymbolModifiers::External,
- ),
- BNSymbolType::ImportedData => {
- Symbol::new(symbol_name, SymbolClass::Data, SymbolModifiers::External)
- }
- BNSymbolType::LibraryFunction | BNSymbolType::Function => Symbol::new(
- symbol_name,
- SymbolClass::Function,
- SymbolModifiers::default(),
- ),
- BNSymbolType::ImportedFunction => Symbol::new(
- symbol_name,
- SymbolClass::Function,
- // TODO: Exported?
- SymbolModifiers::External,
- ),
- }
-}
-
-pub fn to_bn_symbol_at_address(view: &BinaryView, symbol: &Symbol, addr: u64) -> BNRef<BNSymbol> {
- let is_external = symbol.modifiers.contains(SymbolModifiers::External);
- let _is_exported = symbol.modifiers.contains(SymbolModifiers::Exported);
- let symbol_type = match symbol.class {
- SymbolClass::Function if is_external => BNSymbolType::ImportedFunction,
- // TODO: We should instead make it a Function, however due to the nature of the imports we are setting them to library for now.
- SymbolClass::Function => BNSymbolType::LibraryFunction,
- SymbolClass::Data if is_external => BNSymbolType::ImportedData,
- SymbolClass::Data => BNSymbolType::Data,
- };
- let raw_name = symbol.name.as_str();
- let mut symbol_builder = BNSymbol::builder(symbol_type, &symbol.name, addr);
- // Demangle symbol name (short is with simplifications).
- if let Some(arch) = view.default_arch() {
- if let Some((full_name, _)) =
- binaryninja::demangle::demangle_generic(&arch, raw_name, Some(view), false)
- {
- symbol_builder = symbol_builder.full_name(full_name);
- }
- if let Some((short_name, _)) =
- binaryninja::demangle::demangle_generic(&arch, raw_name, Some(view), false)
- {
- symbol_builder = symbol_builder.short_name(short_name);
- }
- }
- symbol_builder.create()
-}
-
-pub fn from_bn_type(view: &BinaryView, raw_ty: &BNType, confidence: u8) -> Type {
- from_bn_type_internal(view, &mut HashSet::new(), raw_ty, confidence)
-}
-
-pub fn from_bn_type_internal(
- view: &BinaryView,
- visited_refs: &mut HashSet<TypeRefID>,
- raw_ty: &BNType,
- confidence: u8,
-) -> Type {
- let bytes_to_bits = |val| val * 8;
- let raw_ty_bit_width = bytes_to_bits(raw_ty.width());
- let type_class = match raw_ty.type_class() {
- BNTypeClass::VoidTypeClass => TypeClass::Void,
- BNTypeClass::BoolTypeClass => {
- let bool_class = BooleanClass { width: None };
- TypeClass::Boolean(bool_class)
- }
- BNTypeClass::IntegerTypeClass => {
- let signed = raw_ty.is_signed().contents;
- let width = Some(raw_ty_bit_width as u16);
- if signed && width == Some(8) {
- // NOTE: if its an i8, its a char.
- let char_class = CharacterClass { width: None };
- TypeClass::Character(char_class)
- } else {
- let int_class = IntegerClass { width, signed };
- TypeClass::Integer(int_class)
- }
- }
- BNTypeClass::FloatTypeClass => {
- let float_class = FloatClass {
- width: Some(raw_ty_bit_width as u16),
- };
- TypeClass::Float(float_class)
- }
- // TODO: Union?????
- BNTypeClass::StructureTypeClass => {
- let raw_struct = raw_ty.get_structure().unwrap();
-
- let mut members = raw_struct
- .members()
- .into_iter()
- .map(|raw_member| {
- let bit_offset = bytes_to_bits(raw_member.offset);
- let mut modifiers = StructureMemberModifiers::empty();
- // If this member is not public mark it as internal.
- modifiers.set(
- StructureMemberModifiers::Internal,
- !matches!(raw_member.access, MemberAccess::PublicAccess),
- );
- StructureMember {
- name: Some(raw_member.name),
- offset: bit_offset,
- ty: from_bn_type_internal(
- view,
- visited_refs,
- &raw_member.ty.contents,
- raw_member.ty.confidence,
- ),
- modifiers,
- }
- })
- .collect::<Vec<_>>();
-
- // Add base structures as flattened members
- let base_to_member_iter = raw_struct.base_structures().into_iter().map(|base_struct| {
- let bit_offset = bytes_to_bits(base_struct.offset);
- let mut modifiers = StructureMemberModifiers::empty();
- modifiers.set(StructureMemberModifiers::Flattened, true);
- let base_struct_ty = from_bn_type_internal(
- view,
- visited_refs,
- &BNType::named_type(&base_struct.ty),
- MAX_CONFIDENCE,
- );
- StructureMember {
- name: base_struct_ty.name.to_owned(),
- offset: bit_offset,
- ty: base_struct_ty,
- modifiers,
- }
- });
- members.extend(base_to_member_iter);
-
- // TODO: Check if union
- let struct_class = StructureClass::new(members);
- TypeClass::Structure(struct_class)
- }
- BNTypeClass::EnumerationTypeClass => {
- let raw_enum = raw_ty.get_enumeration().unwrap();
-
- let enum_ty_signed = raw_ty.is_signed().contents;
- let enum_ty = Type::builder::<String, _>()
- .class(TypeClass::Integer(IntegerClass {
- width: Some(raw_ty_bit_width as u16),
- signed: enum_ty_signed,
- }))
- .build();
-
- let members = raw_enum
- .members()
- .into_iter()
- .map(|raw_member| EnumerationMember {
- name: Some(raw_member.name),
- constant: raw_member.value,
- })
- .collect();
-
- let enum_class = EnumerationClass::new(enum_ty, members);
- TypeClass::Enumeration(enum_class)
- }
- BNTypeClass::PointerTypeClass => {
- let raw_child_ty = raw_ty.target().unwrap();
- let ptr_class = PointerClass {
- width: Some(raw_ty_bit_width as u16),
- child_type: from_bn_type_internal(
- view,
- visited_refs,
- &raw_child_ty.contents,
- raw_child_ty.confidence,
- ),
- // TODO: Handle addressing.
- addressing: PointerAddressing::Absolute,
- };
- TypeClass::Pointer(ptr_class)
- }
- BNTypeClass::ArrayTypeClass => {
- let length = raw_ty.count();
- let raw_member_ty = raw_ty.element_type().unwrap();
- let array_class = ArrayClass {
- length: Some(length),
- member_type: from_bn_type_internal(
- view,
- visited_refs,
- &raw_member_ty.contents,
- raw_member_ty.confidence,
- ),
- modifiers: ArrayModifiers::empty(),
- };
- TypeClass::Array(array_class)
- }
- BNTypeClass::FunctionTypeClass => {
- let in_members = raw_ty
- .parameters()
- .unwrap()
- .into_iter()
- .map(|raw_member| {
- // TODO: Location...
- let _location = Location::Register(RegisterLocation);
- FunctionMember {
- name: Some(raw_member.name),
- ty: from_bn_type_internal(
- view,
- visited_refs,
- &raw_member.ty.contents,
- raw_member.ty.confidence,
- ),
- // TODO: Just omit location for now?
- // TODO: Location should be optional...
- locations: vec![],
- }
- })
- .collect();
-
- let mut out_members = Vec::new();
- if let Some(return_ty) = raw_ty.return_value() {
- out_members.push(FunctionMember {
- name: None,
- ty: from_bn_type_internal(
- view,
- visited_refs,
- &return_ty.contents,
- return_ty.confidence,
- ),
- locations: vec![],
- });
- }
-
- let calling_convention = raw_ty
- .calling_convention()
- .map(|bn_cc| from_bn_calling_convention(bn_cc.contents));
-
- let func_class = FunctionClass {
- calling_convention,
- in_members,
- out_members,
- };
- TypeClass::Function(func_class)
- }
- BNTypeClass::VarArgsTypeClass => TypeClass::Void,
- BNTypeClass::ValueTypeClass => {
- // What the is this.
- TypeClass::Void
- }
- BNTypeClass::NamedTypeReferenceClass => {
- let raw_ntr = raw_ty.get_named_type_reference().unwrap();
- let ref_id = TypeRefID::from(raw_ntr.as_ref());
- let mut ref_class = ReferrerClass::new(None, Some(raw_ntr.name().to_string()));
- if visited_refs.insert(ref_id) {
- // This ntr is NOT self-referential, meaning we can deduce a type GUID.
- if let Some(computed_ty) = cached_type_reference(view, visited_refs, &raw_ntr) {
- // NOTE: The GUID here must always equal the same for any given type for this to work effectively.
- ref_class.guid = Some(computed_ty.guid);
- }
- visited_refs.remove(&ref_id);
- }
- TypeClass::Referrer(ref_class)
- }
- BNTypeClass::WideCharTypeClass => {
- let char_class = CharacterClass {
- width: Some(raw_ty_bit_width as u16),
- };
- TypeClass::Character(char_class)
- }
- };
-
- let name = raw_ty.registered_name().map(|n| n.name().to_string());
-
- Type {
- name,
- class: Box::new(type_class),
- confidence,
- // TODO: Fill these out...
- modifiers: vec![],
- alignment: Default::default(),
- // TODO: Filling this out is... weird.
- // TODO: we _do_ want this for networked types (this is the only way we can update type is if we fill this out)
- ancestors: vec![],
+pub fn bn_comment_to_comment(func: &BNFunction, bn_comment: BNComment) -> FunctionComment {
+ let offset = (bn_comment.addr as i64) - (func.start() as i64);
+ FunctionComment {
+ offset,
+ text: bn_comment.comment,
}
}
-pub fn from_bn_calling_convention(raw_cc: BNRef<BNCallingConvention>) -> CallingConvention {
- // NOTE: Currently calling convention just stores the name.
- CallingConvention::new(raw_cc.name().as_str())
-}
-
-pub fn to_bn_calling_convention<A: BNArchitecture>(
- arch: &A,
- calling_convention: &CallingConvention,
-) -> BNRef<BNCallingConvention> {
- for cc in &arch.calling_conventions() {
- if cc.name().as_str() == calling_convention.name {
- return cc.clone();
- }
+pub fn comment_to_bn_comment(func: &BNFunction, comment: FunctionComment) -> BNComment {
+ BNComment {
+ addr: comment
+ .offset
+ .checked_add_unsigned(func.start())
+ .unwrap_or_default() as u64,
+ comment: comment.text,
}
- arch.get_default_calling_convention().unwrap()
}
-pub fn to_bn_type<A: BNArchitecture>(arch: &A, ty: &Type) -> BNRef<BNType> {
- let bits_to_bytes = |val: u64| (val / 8);
- let addr_size = arch.address_size() as u64;
- match ty.class.as_ref() {
- TypeClass::Void => BNType::void(),
- TypeClass::Boolean(_) => BNType::bool(),
- TypeClass::Integer(c) => {
- let width = c.width.map(|w| bits_to_bytes(w as _)).unwrap_or(4);
- BNType::int(width as usize, c.signed)
- }
- TypeClass::Character(c) => match c.width {
- Some(w) => BNType::wide_char(bits_to_bytes(w as _) as usize),
- None => BNType::char(),
- },
- TypeClass::Float(c) => {
- let width = c.width.map(|w| bits_to_bytes(w as _)).unwrap_or(4);
- BNType::float(width as usize)
+pub fn platform_to_target(platform: &Platform) -> Target {
+ let arch_name = platform.arch().name();
+ let platform_name = platform.name();
+ // We do not want to populate the platform if we are actually only the architecture.
+ if arch_name == platform_name {
+ Target {
+ architecture: Some(arch_name),
+ platform: None,
}
- TypeClass::Pointer(ref c) => {
- let child_type = to_bn_type(arch, &c.child_type);
- let ptr_width = c.width.map(|w| bits_to_bytes(w as _)).unwrap_or(addr_size);
- // TODO: Child type confidence
- let constant = ty.is_const();
- let volatile = ty.is_volatile();
- // TODO: If the pointer is to a null terminated array of chars, make it a pointer to char
- // TODO: Addressing mode
- BNType::pointer_of_width(&child_type, ptr_width as usize, constant, volatile, None)
- }
- TypeClass::Array(c) => {
- let member_type = to_bn_type(arch, &c.member_type);
- // TODO: How to handle DST array (length is None)
- BNType::array(&member_type, c.length.unwrap_or(0))
- }
- TypeClass::Structure(c) => {
- let mut builder = BNStructureBuilder::new();
- // TODO: Structure type class?
- // TODO: Alignment
- // TODO: Other modifiers?
- let mut base_structs: Vec<BNBaseStructure> = Vec::new();
- for member in &c.members {
- let member_type = BNConf::new(to_bn_type(arch, &member.ty), u8::MAX);
- let member_name = member.name.to_owned().unwrap_or("field_OFFSET".into());
- let member_offset = bits_to_bytes(member.offset);
- let member_access = if member
- .modifiers
- .contains(StructureMemberModifiers::Internal)
- {
- BNMemberAccess::PrivateAccess
- } else {
- BNMemberAccess::PublicAccess
- };
- // TODO: Member scope
- let member_scope = BNMemberScope::NoScope;
- if member
- .modifiers
- .contains(StructureMemberModifiers::Flattened)
- {
- // Add member as a base structure to inherit its fields.
- match member.ty.class.as_ref() {
- TypeClass::Referrer(c) => {
- // We only support base structures with a referrer right now.
- let base_struct_ntr_name =
- c.name.to_owned().unwrap_or("base_UNKNOWN".into());
- let base_struct_ntr = match c.guid {
- Some(guid) => BNNamedTypeReference::new_with_id(
- NamedTypeReferenceClass::UnknownNamedTypeClass,
- &guid.to_string(),
- base_struct_ntr_name,
- ),
- None => BNNamedTypeReference::new(
- NamedTypeReferenceClass::UnknownNamedTypeClass,
- base_struct_ntr_name,
- ),
- };
- base_structs.push(BNBaseStructure::new(
- base_struct_ntr,
- member_offset,
- member.ty.size().unwrap_or(0),
- ))
- }
- _ => {
- log::error!(
- "Adding base {:?} with invalid ty: {:?}",
- ty.name,
- member.ty
- );
- }
- }
- } else {
- builder.insert_member(
- BNStructureMember::new(
- member_type,
- member_name,
- member_offset,
- member_access,
- member_scope,
- ),
- false,
- );
- }
- }
- builder.base_structures(&base_structs);
- BNType::structure(&builder.finalize())
- }
- TypeClass::Enumeration(c) => {
- let mut builder = BNEnumerationBuilder::new();
- for member in &c.members {
- // TODO: Add default name?
- let member_name = member.name.to_owned().unwrap_or("enum_VAL".into());
- let member_value = member.constant;
- builder.insert(&member_name, member_value);
- }
- // TODO: Warn if enumeration has no size.
- let width = bits_to_bytes(c.member_type.size().unwrap()) as usize;
- let signed = matches!(*c.member_type.class, TypeClass::Integer(c) if c.signed);
- // TODO: Passing width like this is weird.
- BNType::enumeration(&builder.finalize(), width.try_into().unwrap(), signed)
- }
- TypeClass::Union(c) => {
- let mut builder = BNStructureBuilder::new();
- builder.structure_type(BNStructureType::UnionStructureType);
- for member in &c.members {
- let member_type = BNConf::new(to_bn_type(arch, &member.ty), u8::MAX);
- let member_name = member.name.to_owned();
- // TODO: Member access
- let member_access = BNMemberAccess::PublicAccess;
- // TODO: Member scope
- let member_scope = BNMemberScope::NoScope;
- let structure_member = BNStructureMember::new(
- member_type,
- member_name,
- 0, // Union members all exist at 0 right?
- member_access,
- member_scope,
- );
- builder.insert_member(structure_member, false);
- }
- BNType::structure(&builder.finalize())
- }
- TypeClass::Function(c) => {
- let return_type = if !c.out_members.is_empty() {
- // TODO: WTF
- to_bn_type(arch, &c.out_members[0].ty)
- } else {
- BNType::void()
- };
- let params: Vec<_> = c
- .in_members
- .iter()
- .map(|member| {
- let member_type = to_bn_type(arch, &member.ty);
- let name = member.name.clone();
- // TODO: Location AND fix default param name
- BNFunctionParameter::new(member_type, name.unwrap_or("param_IDK".into()), None)
- })
- .collect();
- // TODO: Variable arguments
- let variable_args = false;
- // If we have a calling convention we run the extended function type creation.
- match c.calling_convention.as_ref() {
- Some(cc) => {
- let calling_convention = to_bn_calling_convention(arch, cc);
- BNType::function_with_opts(
- &return_type,
- &params,
- variable_args,
- BNConf::new(calling_convention, u8::MAX),
- BNConf::new(0, 0),
- )
- }
- None => BNType::function(&return_type, params, variable_args),
- }
- }
- TypeClass::Referrer(c) => {
- let ntr = match c.guid {
- Some(guid) => {
- let guid_str = guid.to_string();
- let ntr_name = c.name.to_owned().unwrap_or(guid_str.clone());
- NamedTypeReference::new_with_id(
- NamedTypeReferenceClass::UnknownNamedTypeClass,
- &guid_str,
- ntr_name,
- )
- }
- None => match c.name.as_ref() {
- Some(ntr_name) => NamedTypeReference::new(
- NamedTypeReferenceClass::UnknownNamedTypeClass,
- ntr_name,
- ),
- None => {
- log::error!("Referrer with no reference! {:?}", c);
- NamedTypeReference::new(
- NamedTypeReferenceClass::UnknownNamedTypeClass,
- "AHHHHHH",
- )
- }
- },
- };
- BNType::named_type(&ntr)
+ } else {
+ Target {
+ architecture: Some(arch_name),
+ platform: Some(platform_name),
}
}
}
-#[cfg(test)]
-mod tests {
- use super::*;
- use binaryninja::binary_view::BinaryViewExt;
- use binaryninja::headless::Session;
- use std::path::PathBuf;
- use std::sync::OnceLock;
- use warp::r#type::guid::TypeGUID;
-
- static INIT: OnceLock<Session> = OnceLock::new();
-
- fn get_session<'a>() -> &'a Session {
- INIT.get_or_init(|| Session::new().expect("Failed to initialize session"))
- }
-
- #[test]
- fn type_conversion() {
- let session = get_session();
- let out_dir = env!("OUT_DIR").parse::<PathBuf>().unwrap();
- for entry in std::fs::read_dir(out_dir).expect("Failed to read OUT_DIR") {
- let entry = entry.expect("Failed to read directory entry");
- let path = entry.path();
- if path.is_file() {
- if let Some(bv) = session.load(path.to_str().unwrap()) {
- let types_len = bv.types().len();
- let converted_types: Vec<_> = bv
- .types()
- .iter()
- .map(|qualified_name_and_type| {
- let ty = from_bn_type(&bv, &qualified_name_and_type.ty, u8::MAX);
- (TypeGUID::from(&ty), ty)
- })
- .collect();
- assert_eq!(types_len, converted_types.len());
- }
- }
- }
- }
-
- #[ignore]
- #[test]
- fn check_for_leaks() {
- let session = get_session();
- let out_dir = env!("OUT_DIR").parse::<PathBuf>().unwrap();
- for entry in std::fs::read_dir(out_dir).expect("Failed to read OUT_DIR") {
- let entry = entry.expect("Failed to read directory entry");
- let path = entry.path();
- if path.is_file() {
- if let Some(inital_bv) = session.load(path.to_str().unwrap()) {
- let types_len = inital_bv.types().len();
- let converted_types: Vec<_> = inital_bv
- .types()
- .iter()
- .map(|t| {
- let ty = from_bn_type(&inital_bv, &t.ty, u8::MAX);
- (TypeGUID::from(&ty), ty)
- })
- .collect();
- assert_eq!(types_len, converted_types.len());
- // Hold on to a reference to the core to prevent view getting dropped in worker thread.
- let _core_ref = inital_bv
- .functions()
- .iter()
- .next()
- .map(|f| f.unresolved_stack_adjustment_graph());
- // Drop the file and view.
- inital_bv.file().close();
- std::mem::drop(inital_bv);
- let initial_memory_info = binaryninja::memory_info();
- if let Some(second_bv) = session.load(path.to_str().unwrap()) {
- let types_len = second_bv.types().len();
- let converted_types: Vec<_> = second_bv
- .types()
- .iter()
- .map(|t| {
- let ty = from_bn_type(&second_bv, &t.ty, u8::MAX);
- (TypeGUID::from(&ty), ty)
- })
- .collect();
- assert_eq!(types_len, converted_types.len());
- // Hold on to a reference to the core to prevent view getting dropped in worker thread.
- let _core_ref = second_bv
- .functions()
- .iter()
- .next()
- .map(|f| f.unresolved_stack_adjustment_graph());
- // Drop the file and view.
- second_bv.file().close();
- std::mem::drop(second_bv);
- let final_memory_info = binaryninja::memory_info();
- for info in initial_memory_info {
- let initial_count = info.1;
- if let Some(&final_count) = final_memory_info.get(&info.0) {
- assert!(
- final_count <= initial_count,
- "{}: final objects {} vs initial objects {}",
- info.0,
- final_count,
- initial_count
- );
- }
- }
- }
- }
- }
- }
+pub fn target_to_platform(target: Target) -> Option<Ref<Platform>> {
+ // First try using the platform, then try using the arch.
+ match Platform::by_name(&target.platform.unwrap()) {
+ None => Platform::by_name(&target.architecture.unwrap()).map(|platform| platform),
+ Some(platform) => Some(platform),
}
}
diff --git a/plugins/warp/src/convert/symbol.rs b/plugins/warp/src/convert/symbol.rs
new file mode 100644
index 00000000..0107cff5
--- /dev/null
+++ b/plugins/warp/src/convert/symbol.rs
@@ -0,0 +1,91 @@
+use binaryninja::binary_view::{BinaryView, BinaryViewExt};
+use binaryninja::rc::Ref as BNRef;
+use binaryninja::symbol::Symbol as BNSymbol;
+use binaryninja::symbol::SymbolType as BNSymbolType;
+use warp::symbol::{Symbol, SymbolClass, SymbolModifiers};
+
+pub fn from_bn_symbol(raw_symbol: &BNSymbol) -> Symbol {
+ // TODO: Use this?
+ let _is_export = raw_symbol.external();
+ let raw_symbol_name = raw_symbol.raw_name();
+ let symbol_name = raw_symbol_name.to_string_lossy();
+ match raw_symbol.sym_type() {
+ BNSymbolType::ImportAddress => {
+ Symbol::new(
+ symbol_name,
+ SymbolClass::Function,
+ // TODO: External = symbolic i guess
+ SymbolModifiers::External,
+ )
+ }
+ BNSymbolType::Data => {
+ Symbol::new(
+ symbol_name,
+ // TODO: Data?
+ SymbolClass::Data,
+ SymbolModifiers::default(),
+ )
+ }
+ BNSymbolType::Symbolic => {
+ Symbol::new(
+ symbol_name,
+ SymbolClass::Function,
+ // TODO: External = symbolic i guess
+ SymbolModifiers::External,
+ )
+ }
+ BNSymbolType::LocalLabel => {
+ // TODO: This is a placeholder for another symbol.
+ Symbol::new(symbol_name, SymbolClass::Data, SymbolModifiers::External)
+ }
+ BNSymbolType::External => Symbol::new(
+ symbol_name,
+ // TODO: External data?
+ SymbolClass::Function,
+ SymbolModifiers::External,
+ ),
+ BNSymbolType::ImportedData => {
+ Symbol::new(symbol_name, SymbolClass::Data, SymbolModifiers::External)
+ }
+ BNSymbolType::LibraryFunction | BNSymbolType::Function => Symbol::new(
+ symbol_name,
+ SymbolClass::Function,
+ SymbolModifiers::default(),
+ ),
+ BNSymbolType::ImportedFunction => Symbol::new(
+ symbol_name,
+ SymbolClass::Function,
+ // TODO: Exported?
+ SymbolModifiers::External,
+ ),
+ }
+}
+
+pub fn to_bn_symbol_at_address(view: &BinaryView, symbol: &Symbol, addr: u64) -> BNRef<BNSymbol> {
+ let is_external = symbol.modifiers.contains(SymbolModifiers::External);
+ let _is_exported = symbol.modifiers.contains(SymbolModifiers::Exported);
+ let symbol_type = match symbol.class {
+ SymbolClass::Function if is_external => BNSymbolType::ImportedFunction,
+ // TODO: We should instead make it a Function, however due to the nature of the imports we are setting them to library for now.
+ SymbolClass::Function => BNSymbolType::LibraryFunction,
+ SymbolClass::Data if is_external => BNSymbolType::ImportedData,
+ SymbolClass::Data => BNSymbolType::Data,
+ _ => BNSymbolType::Data,
+ };
+ let raw_name = symbol.name.as_str();
+ let mut symbol_builder = BNSymbol::builder(symbol_type, &symbol.name, addr);
+ // Demangle symbol name (short is with simplifications).
+ if let Some(arch) = view.default_arch() {
+ if let Some((full_name, _)) =
+ binaryninja::demangle::demangle_generic(&arch, raw_name, Some(view), false)
+ {
+ symbol_builder = symbol_builder.full_name(full_name);
+ }
+ if let Some((short_name, _)) =
+ binaryninja::demangle::demangle_generic(&arch, raw_name, Some(view), false)
+ {
+ symbol_builder = symbol_builder.short_name(short_name);
+ }
+ }
+ symbol_builder.create()
+}
diff --git a/plugins/warp/src/convert/types.rs b/plugins/warp/src/convert/types.rs
new file mode 100644
index 00000000..1dfe5fa9
--- /dev/null
+++ b/plugins/warp/src/convert/types.rs
@@ -0,0 +1,516 @@
+use crate::cache::{cached_type_reference, TypeRefID};
+use binaryninja::architecture::Architecture as BNArchitecture;
+use binaryninja::architecture::ArchitectureExt;
+use binaryninja::binary_view::BinaryView;
+use binaryninja::calling_convention::CoreCallingConvention as BNCallingConvention;
+use binaryninja::confidence::Conf as BNConf;
+use binaryninja::confidence::MAX_CONFIDENCE;
+use binaryninja::rc::Ref as BNRef;
+use binaryninja::types::BaseStructure as BNBaseStructure;
+use binaryninja::types::EnumerationBuilder as BNEnumerationBuilder;
+use binaryninja::types::FunctionParameter as BNFunctionParameter;
+use binaryninja::types::MemberAccess as BNMemberAccess;
+use binaryninja::types::MemberScope as BNMemberScope;
+use binaryninja::types::NamedTypeReference as BNNamedTypeReference;
+use binaryninja::types::StructureBuilder as BNStructureBuilder;
+use binaryninja::types::StructureMember as BNStructureMember;
+use binaryninja::types::StructureType as BNStructureType;
+use binaryninja::types::Type as BNType;
+use binaryninja::types::TypeClass as BNTypeClass;
+use binaryninja::types::{NamedTypeReference, NamedTypeReferenceClass};
+use std::collections::HashSet;
+use warp::r#type::class::array::ArrayModifiers;
+use warp::r#type::class::pointer::PointerAddressing;
+use warp::r#type::class::structure::StructureMemberModifiers;
+use warp::r#type::class::{
+ ArrayClass, BooleanClass, CallingConvention, CharacterClass, EnumerationClass,
+ EnumerationMember, FloatClass, FunctionClass, FunctionMember, IntegerClass, PointerClass,
+ ReferrerClass, StructureClass, StructureMember, TypeClass,
+};
+use warp::r#type::{Type, TypeModifiers};
+
+pub fn from_bn_type(view: &BinaryView, raw_ty: &BNType, confidence: u8) -> Type {
+ from_bn_type_internal(view, &mut HashSet::new(), raw_ty, confidence)
+}
+
+pub fn from_bn_type_internal(
+ view: &BinaryView,
+ visited_refs: &mut HashSet<TypeRefID>,
+ raw_ty: &BNType,
+ confidence: u8,
+) -> Type {
+ let bytes_to_bits = |val| val * 8;
+ let raw_ty_bit_width = bytes_to_bits(raw_ty.width());
+ let type_class = match raw_ty.type_class() {
+ BNTypeClass::VoidTypeClass => TypeClass::Void,
+ BNTypeClass::BoolTypeClass => {
+ let bool_class = BooleanClass { width: None };
+ TypeClass::Boolean(bool_class)
+ }
+ BNTypeClass::IntegerTypeClass => {
+ let signed = raw_ty.is_signed().contents;
+ let width = Some(raw_ty_bit_width as u16);
+ if signed && width == Some(8) {
+ // NOTE: if its an i8, its a char.
+ let char_class = CharacterClass { width: None };
+ TypeClass::Character(char_class)
+ } else {
+ let int_class = IntegerClass { width, signed };
+ TypeClass::Integer(int_class)
+ }
+ }
+ BNTypeClass::FloatTypeClass => {
+ let float_class = FloatClass {
+ width: Some(raw_ty_bit_width as u16),
+ };
+ TypeClass::Float(float_class)
+ }
+ // TODO: Union?????
+ BNTypeClass::StructureTypeClass => {
+ let raw_struct = raw_ty.get_structure().unwrap();
+
+ let mut members = raw_struct
+ .members()
+ .into_iter()
+ .map(|raw_member| {
+ let bit_offset = bytes_to_bits(raw_member.offset);
+ let mut modifiers = StructureMemberModifiers::empty();
+ // If this member is not public mark it as internal.
+ modifiers.set(
+ StructureMemberModifiers::Internal,
+ !matches!(raw_member.access, BNMemberAccess::PublicAccess),
+ );
+ StructureMember {
+ name: Some(raw_member.name),
+ offset: bit_offset,
+ ty: Box::new(from_bn_type_internal(
+ view,
+ visited_refs,
+ &raw_member.ty.contents,
+ raw_member.ty.confidence,
+ )),
+ modifiers,
+ }
+ })
+ .collect::<Vec<_>>();
+
+ // Add base structures as flattened members
+ let base_to_member_iter = raw_struct.base_structures().into_iter().map(|base_struct| {
+ let bit_offset = bytes_to_bits(base_struct.offset);
+ let mut modifiers = StructureMemberModifiers::empty();
+ modifiers.set(StructureMemberModifiers::Flattened, true);
+ let base_struct_ty = from_bn_type_internal(
+ view,
+ visited_refs,
+ &BNType::named_type(&base_struct.ty),
+ MAX_CONFIDENCE,
+ );
+ StructureMember {
+ name: base_struct_ty.name.to_owned(),
+ offset: bit_offset,
+ ty: Box::new(base_struct_ty),
+ modifiers,
+ }
+ });
+ members.extend(base_to_member_iter);
+
+ // TODO: Check if union
+ let struct_class = StructureClass::new(members);
+ TypeClass::Structure(struct_class)
+ }
+ BNTypeClass::EnumerationTypeClass => {
+ let raw_enum = raw_ty.get_enumeration().unwrap();
+
+ let enum_ty_signed = raw_ty.is_signed().contents;
+ let enum_ty = Type::builder::<String, _>()
+ .class(TypeClass::Integer(IntegerClass {
+ width: Some(raw_ty_bit_width as u16),
+ signed: enum_ty_signed,
+ }))
+ .build();
+
+ let members = raw_enum
+ .members()
+ .into_iter()
+ .map(|raw_member| EnumerationMember {
+ name: Some(raw_member.name),
+ constant: raw_member.value,
+ })
+ .collect();
+
+ let enum_class = EnumerationClass::new(enum_ty, members);
+ TypeClass::Enumeration(enum_class)
+ }
+ BNTypeClass::PointerTypeClass => {
+ let raw_child_ty = raw_ty.target().unwrap();
+ let ptr_class = PointerClass {
+ width: Some(raw_ty_bit_width as u16),
+ child_type: Box::new(from_bn_type_internal(
+ view,
+ visited_refs,
+ &raw_child_ty.contents,
+ raw_child_ty.confidence,
+ )),
+ // TODO: Handle addressing.
+ addressing: PointerAddressing::Absolute,
+ };
+ TypeClass::Pointer(ptr_class)
+ }
+ BNTypeClass::ArrayTypeClass => {
+ let length = raw_ty.count();
+ let raw_member_ty = raw_ty.element_type().unwrap();
+ let array_class = ArrayClass {
+ length: Some(length),
+ member_type: Box::new(from_bn_type_internal(
+ view,
+ visited_refs,
+ &raw_member_ty.contents,
+ raw_member_ty.confidence,
+ )),
+ modifiers: ArrayModifiers::empty(),
+ };
+ TypeClass::Array(array_class)
+ }
+ BNTypeClass::FunctionTypeClass => {
+ let in_members = raw_ty
+ .parameters()
+ .unwrap()
+ .into_iter()
+ .map(|raw_member| {
+ // TODO: Location...
+ // let _location = Location::Register(RegisterLocation);
+ FunctionMember {
+ name: Some(raw_member.name),
+ ty: Box::new(from_bn_type_internal(
+ view,
+ visited_refs,
+ &raw_member.ty.contents,
+ raw_member.ty.confidence,
+ )),
+ // TODO: Just omit location for now?
+ // TODO: Location should be optional...
+ location: None,
+ }
+ })
+ .collect();
+
+ let mut out_members = Vec::new();
+ if let Some(return_ty) = raw_ty.return_value() {
+ out_members.push(FunctionMember {
+ name: None,
+ ty: Box::new(from_bn_type_internal(
+ view,
+ visited_refs,
+ &return_ty.contents,
+ return_ty.confidence,
+ )),
+ location: None,
+ });
+ }
+
+ let calling_convention = raw_ty
+ .calling_convention()
+ .map(|bn_cc| from_bn_calling_convention(bn_cc.contents));
+
+ let func_class = FunctionClass {
+ calling_convention,
+ in_members,
+ out_members,
+ };
+ TypeClass::Function(func_class)
+ }
+ BNTypeClass::VarArgsTypeClass => TypeClass::Void,
+ BNTypeClass::ValueTypeClass => {
+ // What the is this.
+ TypeClass::Void
+ }
+ BNTypeClass::NamedTypeReferenceClass => {
+ let raw_ntr = raw_ty.get_named_type_reference().unwrap();
+ let ref_id = TypeRefID::from(raw_ntr.as_ref());
+ let mut ref_class = ReferrerClass::new(None, Some(raw_ntr.name().to_string()));
+ if visited_refs.insert(ref_id) {
+ // This ntr is NOT self-referential, meaning we can deduce a type GUID.
+ if let Some(computed_ty) = cached_type_reference(view, visited_refs, &raw_ntr) {
+ // NOTE: The GUID here must always equal the same for any given type for this to work effectively.
+ ref_class.guid = Some(computed_ty.guid);
+ }
+ visited_refs.remove(&ref_id);
+ }
+ TypeClass::Referrer(ref_class)
+ }
+ BNTypeClass::WideCharTypeClass => {
+ let char_class = CharacterClass {
+ width: Some(raw_ty_bit_width as u16),
+ };
+ TypeClass::Character(char_class)
+ }
+ };
+
+ let name = raw_ty.registered_name().map(|n| n.name().to_string());
+
+ Type {
+ name,
+ class: type_class,
+ confidence,
+ // TODO: Fill these out...
+ modifiers: TypeModifiers::empty(),
+ metadata: vec![],
+ alignment: Default::default(),
+ // TODO: Filling this out is... weird.
+ // TODO: we _do_ want this for networked types (this is the only way we can update type is if we fill this out)
+ ancestors: vec![],
+ }
+}
+
+pub fn from_bn_calling_convention(raw_cc: BNRef<BNCallingConvention>) -> CallingConvention {
+ // NOTE: Currently calling convention just stores the name.
+ CallingConvention::new(raw_cc.name().as_str())
+}
+
+pub fn to_bn_calling_convention<A: BNArchitecture>(
+ arch: &A,
+ calling_convention: &CallingConvention,
+) -> BNRef<BNCallingConvention> {
+ for cc in &arch.calling_conventions() {
+ if cc.name().as_str() == calling_convention.name {
+ return cc.clone();
+ }
+ }
+ arch.get_default_calling_convention().unwrap()
+}
+
+pub fn to_bn_type<A: BNArchitecture>(arch: &A, ty: &Type) -> BNRef<BNType> {
+ let bits_to_bytes = |val: u64| (val / 8);
+ let addr_size = arch.address_size() as u64;
+ match &ty.class {
+ TypeClass::Void => BNType::void(),
+ TypeClass::Boolean(_) => BNType::bool(),
+ TypeClass::Integer(c) => {
+ let width = c.width.map(|w| bits_to_bytes(w as _)).unwrap_or(4);
+ BNType::int(width as usize, c.signed)
+ }
+ TypeClass::Character(c) => match c.width {
+ Some(w) => BNType::wide_char(bits_to_bytes(w as _) as usize),
+ None => BNType::char(),
+ },
+ TypeClass::Float(c) => {
+ let width = c.width.map(|w| bits_to_bytes(w as _)).unwrap_or(4);
+ BNType::float(width as usize)
+ }
+ TypeClass::Pointer(ref c) => {
+ let child_type = to_bn_type(arch, &c.child_type);
+ let ptr_width = c.width.map(|w| bits_to_bytes(w as _)).unwrap_or(addr_size);
+ // TODO: Child type confidence
+ let constant = ty.is_const();
+ let volatile = ty.is_volatile();
+ // TODO: If the pointer is to a null terminated array of chars, make it a pointer to char
+ // TODO: Addressing mode
+ BNType::pointer_of_width(&child_type, ptr_width as usize, constant, volatile, None)
+ }
+ TypeClass::Array(c) => {
+ let member_type = to_bn_type(arch, &c.member_type);
+ // TODO: How to handle DST array (length is None)
+ BNType::array(&member_type, c.length.unwrap_or(0))
+ }
+ TypeClass::Structure(c) => {
+ let mut builder = BNStructureBuilder::new();
+ // TODO: Structure type class?
+ // TODO: Alignment
+ // TODO: Other modifiers?
+ let mut base_structs: Vec<BNBaseStructure> = Vec::new();
+ for member in &c.members {
+ let member_type = BNConf::new(to_bn_type(arch, &member.ty), u8::MAX);
+ let member_name = member.name.to_owned().unwrap_or("field_OFFSET".into());
+ let member_offset = bits_to_bytes(member.offset);
+ let member_access = if member
+ .modifiers
+ .contains(StructureMemberModifiers::Internal)
+ {
+ BNMemberAccess::PrivateAccess
+ } else {
+ BNMemberAccess::PublicAccess
+ };
+ // TODO: Member scope
+ let member_scope = BNMemberScope::NoScope;
+ if member
+ .modifiers
+ .contains(StructureMemberModifiers::Flattened)
+ {
+ // Add member as a base structure to inherit its fields.
+ match &member.ty.class {
+ TypeClass::Referrer(c) => {
+ // We only support base structures with a referrer right now.
+ let base_struct_ntr_name =
+ c.name.to_owned().unwrap_or("base_UNKNOWN".into());
+ let base_struct_ntr = match c.guid {
+ Some(guid) => BNNamedTypeReference::new_with_id(
+ NamedTypeReferenceClass::UnknownNamedTypeClass,
+ &guid.to_string(),
+ base_struct_ntr_name,
+ ),
+ None => BNNamedTypeReference::new(
+ NamedTypeReferenceClass::UnknownNamedTypeClass,
+ base_struct_ntr_name,
+ ),
+ };
+ base_structs.push(BNBaseStructure::new(
+ base_struct_ntr,
+ member_offset,
+ member.ty.size().unwrap_or(0),
+ ))
+ }
+ _ => {
+ log::error!(
+ "Adding base {:?} with invalid ty: {:?}",
+ ty.name,
+ member.ty
+ );
+ }
+ }
+ } else {
+ builder.insert_member(
+ BNStructureMember::new(
+ member_type,
+ member_name,
+ member_offset,
+ member_access,
+ member_scope,
+ ),
+ false,
+ );
+ }
+ }
+ builder.base_structures(&base_structs);
+ BNType::structure(&builder.finalize())
+ }
+ TypeClass::Enumeration(c) => {
+ let mut builder = BNEnumerationBuilder::new();
+ for member in &c.members {
+ // TODO: Add default name?
+ let member_name = member.name.to_owned().unwrap_or("enum_VAL".into());
+ let member_value = member.constant;
+ builder.insert(&member_name, member_value);
+ }
+ // TODO: Warn if enumeration has no size.
+ let width = bits_to_bytes(c.member_type.size().unwrap()) as usize;
+ let signed = matches!(c.member_type.class, TypeClass::Integer(c) if c.signed);
+ // TODO: Passing width like this is weird.
+ BNType::enumeration(&builder.finalize(), width.try_into().unwrap(), signed)
+ }
+ TypeClass::Union(c) => {
+ let mut builder = BNStructureBuilder::new();
+ builder.structure_type(BNStructureType::UnionStructureType);
+ for member in &c.members {
+ let member_type = BNConf::new(to_bn_type(arch, &member.ty), u8::MAX);
+ let member_name = member.name.to_owned();
+ // TODO: Member access
+ let member_access = BNMemberAccess::PublicAccess;
+ // TODO: Member scope
+ let member_scope = BNMemberScope::NoScope;
+ let structure_member = BNStructureMember::new(
+ member_type,
+ member_name,
+ 0, // Union members all exist at 0 right?
+ member_access,
+ member_scope,
+ );
+ builder.insert_member(structure_member, false);
+ }
+ BNType::structure(&builder.finalize())
+ }
+ TypeClass::Function(c) => {
+ let return_type = if !c.out_members.is_empty() {
+ // TODO: WTF
+ to_bn_type(arch, &c.out_members[0].ty)
+ } else {
+ BNType::void()
+ };
+ let params: Vec<_> = c
+ .in_members
+ .iter()
+ .map(|member| {
+ let member_type = to_bn_type(arch, &member.ty);
+ let name = member.name.clone();
+ // TODO: Location AND fix default param name
+ BNFunctionParameter::new(member_type, name.unwrap_or("param_IDK".into()), None)
+ })
+ .collect();
+ // TODO: Variable arguments
+ let variable_args = false;
+ // If we have a calling convention we run the extended function type creation.
+ match c.calling_convention.as_ref() {
+ Some(cc) => {
+ let calling_convention = to_bn_calling_convention(arch, cc);
+ BNType::function_with_opts(
+ &return_type,
+ &params,
+ variable_args,
+ BNConf::new(calling_convention, u8::MAX),
+ BNConf::new(0, 0),
+ )
+ }
+ None => BNType::function(&return_type, params, variable_args),
+ }
+ }
+ TypeClass::Referrer(c) => {
+ let ntr = match c.guid {
+ Some(guid) => {
+ let guid_str = guid.to_string();
+ let ntr_name = c.name.to_owned().unwrap_or(guid_str.clone());
+ NamedTypeReference::new_with_id(
+ NamedTypeReferenceClass::TypedefNamedTypeClass,
+ &guid_str,
+ ntr_name,
+ )
+ }
+ None => match c.name.as_ref() {
+ Some(ntr_name) => NamedTypeReference::new(
+ NamedTypeReferenceClass::UnknownNamedTypeClass,
+ ntr_name,
+ ),
+ None => {
+ log::error!("Referrer with no reference! {:?}", c);
+ NamedTypeReference::new(
+ NamedTypeReferenceClass::UnknownNamedTypeClass,
+ "AHHHHHH",
+ )
+ }
+ },
+ };
+ BNType::named_type(&ntr)
+ }
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+ use binaryninja::binary_view::BinaryViewExt;
+ use binaryninja::headless::Session;
+ use std::path::PathBuf;
+ use warp::r#type::guid::TypeGUID;
+
+ #[test]
+ fn type_conversion() {
+ let session = Session::new().expect("Failed to initialize session");
+ let out_dir = env!("OUT_DIR").parse::<PathBuf>().unwrap();
+ for entry in std::fs::read_dir(out_dir).expect("Failed to read OUT_DIR") {
+ let entry = entry.expect("Failed to read directory entry");
+ let path = entry.path();
+ if path.is_file() {
+ if let Some(bv) = session.load(path) {
+ let types_len = bv.types().len();
+ let converted_types: Vec<_> = bv
+ .types()
+ .iter()
+ .map(|qualified_name_and_type| {
+ let ty = from_bn_type(&bv, &qualified_name_and_type.ty, u8::MAX);
+ (TypeGUID::from(&ty), ty)
+ })
+ .collect();
+ assert_eq!(types_len, converted_types.len());
+ }
+ }
+ }
+ }
+}
diff --git a/plugins/warp/src/lib.rs b/plugins/warp/src/lib.rs
index 6d147bdc..aab26932 100644
--- a/plugins/warp/src/lib.rs
+++ b/plugins/warp/src/lib.rs
@@ -1,7 +1,5 @@
-use crate::cache::{
- cached_adjacency_constraints, cached_call_site_constraints, cached_function_guid,
-};
-use crate::convert::{from_bn_symbol, from_bn_type};
+use crate::cache::{cached_constraints, cached_function_guid};
+use crate::convert::{bn_comment_to_comment, from_bn_symbol, from_bn_type};
use binaryninja::architecture::{
Architecture, ImplicitRegisterExtend, Register as BNRegister, RegisterInfo,
};
@@ -9,25 +7,55 @@ use binaryninja::basic_block::BasicBlock as BNBasicBlock;
use binaryninja::binary_view::{BinaryView, BinaryViewExt};
use binaryninja::confidence::MAX_CONFIDENCE;
use binaryninja::function::{Function as BNFunction, NativeBlock};
-use binaryninja::low_level_il::expression::{ExpressionHandler, LowLevelILExpressionKind};
+use binaryninja::low_level_il::expression::{
+ ExpressionHandler, LowLevelILExpression, LowLevelILExpressionKind, ValueExpr,
+};
use binaryninja::low_level_il::function::{FunctionMutability, LowLevelILFunction, NonSSA};
use binaryninja::low_level_il::instruction::{
InstructionHandler, LowLevelILInstruction, LowLevelILInstructionKind,
};
use binaryninja::low_level_il::{LowLevelILRegisterKind, VisitorAction};
-use binaryninja::rc::Ref as BNRef;
+use binaryninja::rc::{Ref as BNRef, Ref};
use std::ops::Range;
use std::path::PathBuf;
use warp::signature::basic_block::BasicBlockGUID;
-use warp::signature::function::constraints::FunctionConstraints;
use warp::signature::function::{Function, FunctionGUID};
+use binaryninja::tags::TagType;
+use binaryninja::variable::RegisterValueType;
+/// Re-export the warp crate that is used, this is useful for consumers of this crate.
+pub use warp;
+
pub mod cache;
+pub mod container;
pub mod convert;
-mod matcher;
+pub mod matcher;
+pub mod processor;
+pub mod report;
+
/// Only used when compiled for cdylib target.
mod plugin;
+// TODO: Make this 4kb
+/// If the address is within this range before or after a relocatable region, we will assume the address to be relocatable.
+const ADDRESS_RELOCATION_THRESHOLD: u64 = 0x10000;
+
+const TAG_ICON: &str = "🌐";
+const TAG_NAME: &str = "WARP";
+
+fn get_warp_tag_type(view: &BinaryView) -> Ref<TagType> {
+ view.tag_type_by_name(TAG_NAME)
+ .unwrap_or_else(|| view.create_tag_type(TAG_NAME, TAG_ICON))
+}
+
+const INCLUDE_TAG_ICON: &str = "🚀";
+const INCLUDE_TAG_NAME: &str = "WARP: Selected Function";
+
+fn get_warp_include_tag_type(view: &BinaryView) -> Ref<TagType> {
+ view.tag_type_by_name(INCLUDE_TAG_NAME)
+ .unwrap_or_else(|| view.create_tag_type(INCLUDE_TAG_NAME, INCLUDE_TAG_ICON))
+}
+
pub fn core_signature_dir() -> PathBuf {
// Get core signatures for the given platform
let install_dir = binaryninja::install_directory();
@@ -45,22 +73,34 @@ pub fn user_signature_dir() -> PathBuf {
pub fn build_function<M: FunctionMutability>(
func: &BNFunction,
- llil: &LowLevelILFunction<M, NonSSA>,
+ lifted_il: &LowLevelILFunction<M, NonSSA>,
) -> Function {
- let bn_fn_ty = func.function_type();
+ let comments = func
+ .comments()
+ .iter()
+ .map(|c| bn_comment_to_comment(func, c))
+ .collect();
Function {
- guid: cached_function_guid(func, llil),
+ guid: cached_function_guid(func, lifted_il),
symbol: from_bn_symbol(&func.symbol()),
- ty: from_bn_type(&func.view(), &bn_fn_ty, MAX_CONFIDENCE),
- constraints: FunctionConstraints {
- // NOTE: Adding adjacent only works if analysis is complete.
- // NOTE: We do not filter out adjacent functions here.
- adjacent: cached_adjacency_constraints(func, |_| true),
- call_sites: cached_call_site_constraints(func),
- // TODO: Add caller sites (when adjacent and call sites are minimal)
- // NOTE: Adding caller sites only works if analysis is complete.
- caller_sites: Default::default(),
+ // Currently we only store the type if its a user type.
+ // TODO: In the future we might want to make this configurable.
+ ty: match func.has_user_type() || func.has_explicitly_defined_type() {
+ true => Some(from_bn_type(
+ &func.view(),
+ &func.function_type(),
+ MAX_CONFIDENCE,
+ )),
+ false => None,
},
+ // NOTE: Adding adjacent only works if analysis is complete.
+ // NOTE: We do not filter out adjacent functions here.
+ constraints: cached_constraints(func, |_| true),
+ comments,
+ // TODO: Gather relevant variables (only user?).
+ // TODO: Will need MLIL SSA for this to locate def sites.
+ // TODO: Add this info in a second pass?
+ variables: vec![],
}
}
@@ -71,20 +111,21 @@ pub fn sorted_basic_blocks(func: &BNFunction) -> Vec<BNRef<BNBasicBlock<NativeBl
.iter()
.map(|bb| bb.clone())
.collect::<Vec<_>>();
+ // NOTE: start_index is actually the address with [`NativeBlock`].
basic_blocks.sort_by_key(|f| f.start_index());
basic_blocks
}
pub fn function_guid<M: FunctionMutability>(
func: &BNFunction,
- llil: &LowLevelILFunction<M, NonSSA>,
+ lifted_il: &LowLevelILFunction<M, NonSSA>,
) -> FunctionGUID {
// TODO: We might want to make this configurable, or otherwise _not_ retrieve from the view here.
let relocatable_regions = relocatable_regions(&func.view());
let basic_blocks = sorted_basic_blocks(func);
let basic_block_guids = basic_blocks
.iter()
- .map(|bb| basic_block_guid(&relocatable_regions, bb, llil))
+ .map(|bb| basic_block_guid(&relocatable_regions, bb, lifted_il))
.collect::<Vec<_>>();
FunctionGUID::from_basic_blocks(&basic_block_guids)
}
@@ -92,30 +133,60 @@ pub fn function_guid<M: FunctionMutability>(
pub fn basic_block_guid<M: FunctionMutability>(
relocatable_regions: &[Range<u64>],
basic_block: &BNBasicBlock<NativeBlock>,
- llil: &LowLevelILFunction<M, NonSSA>,
+ lifted_il: &LowLevelILFunction<M, NonSSA>,
) -> BasicBlockGUID {
let func = basic_block.function();
+ // TODO: We really should never consult another IL, no guarantee that it exists.
+ let low_level_il = func.low_level_il();
let view = func.view();
let arch = func.arch();
let max_instr_len = arch.max_instr_len();
+ // NOTE: Whenever you make a change here, prefer being "additive", that is, make a smaller change that
+ // only increases the masked contents, instead of making a larger change that could *remove* masked
+ // contents. The reason is that we assume any change that is purely additive to increase the ability
+ // to match previously "unmatchable" functions, whereas the latter would take away. This is not always
+ // the case, but it is generally a good rule to follow.
let basic_block_range = basic_block.start_index()..basic_block.end_index();
let mut basic_block_bytes = Vec::with_capacity(basic_block_range.count());
for instr_addr in basic_block.into_iter() {
let mut instr_bytes = view.read_vec(instr_addr, max_instr_len);
if let Some(instr_info) = arch.instruction_info(&instr_bytes, instr_addr) {
instr_bytes.truncate(instr_info.length);
- if let Some(instr_llil) = llil.instruction_at(instr_addr) {
- // If instruction is blacklisted don't include the bytes.
- if !is_blacklisted_instruction(&instr_llil) {
- if is_variant_instruction(relocatable_regions, &instr_llil) {
- // Found a variant instruction, mask off entire instruction.
+
+ // Find variant and blacklisted instructions using lifted il.
+ if let Some(lifted_il_instr) = lifted_il.instruction_at(instr_addr) {
+ // If instruction is blacklisted, don't include the bytes.
+ if is_blacklisted_instruction(&lifted_il_instr) {
+ continue;
+ }
+
+ if is_variant_instruction(relocatable_regions, &lifted_il_instr) {
+ // Found a variant instruction, mask off the entire instruction.
+ instr_bytes.fill(0);
+ }
+ }
+
+ // TODO: We cannot access the values of expression in lifted IL, we have to go and consult low level IL.
+ // TODO: But because of some extremely annoying simplifications that are happening at LLIL, namely
+ // TODO: Folding of expressions into other instructions, we cannot use only LLIL. Therefor
+ // TODO: We only put the checks that require the expr value here.
+ // TODO: This still has the issue of, some (if (rax + 44) => 28) expression being masked,
+ // TODO: But the only way to remove that is to not consult LLIL at all and have the values
+ // TODO: Available at lifted IL, I have not found a good way to do this without making
+ // TODO: A "mapped llil" or having some simple data flow, the simple data flow is the most attractive
+ // TODO: "solution", but it would require
+ if let Ok(llil) = &low_level_il {
+ if let Some(low_level_instr) = llil.instruction_at(instr_addr) {
+ if is_computed_variant_instruction(relocatable_regions, &low_level_instr) {
+ // Found a computed variant instruction, mask off the entire instruction.
instr_bytes.fill(0);
}
- // Add the instructions bytes to the basic blocks bytes
- basic_block_bytes.extend(instr_bytes);
}
}
+
+ // Add the instruction bytes to the basic blocks bytes
+ basic_block_bytes.extend(instr_bytes);
}
}
@@ -127,8 +198,8 @@ pub fn basic_block_guid<M: FunctionMutability>(
/// Blacklisted instructions will make an otherwise identical function GUID fail to match.
///
/// Example: NOPs and useless moves are blacklisted to allow for hot-patchable functions.
-pub fn is_blacklisted_instruction<A: Architecture, M: FunctionMutability>(
- instr: &LowLevelILInstruction<A, M, NonSSA<RegularNonSSA>>,
+pub fn is_blacklisted_instruction<M: FunctionMutability>(
+ instr: &LowLevelILInstruction<M, NonSSA>,
) -> bool {
match instr.kind() {
LowLevelILInstructionKind::Nop(_) => true,
@@ -138,13 +209,13 @@ pub fn is_blacklisted_instruction<A: Architecture, M: FunctionMutability>(
if op.dest_reg() == source_op.source_reg() =>
{
match op.dest_reg() {
- LowLevelILRegister::ArchReg(r) => {
- // If this register has no implicit extend then we can safely assume it's a NOP.
+ LowLevelILRegisterKind::Arch(r) => {
+ // If this register has no implicit extend, we can safely assume it's a NOP.
// Ex. on x86_64 we don't want to remove `mov edi, edi` as it will zero the upper 32 bits.
// Ex. on x86 we do want to remove `mov edi, edi` as it will not have a side effect like above.
matches!(r.info().implicit_extend(), ImplicitRegisterExtend::NoExtend)
}
- LowLevelILRegister::Temp(_) => false,
+ LowLevelILRegisterKind::Temp(_) => false,
}
}
_ => false,
@@ -154,24 +225,22 @@ pub fn is_blacklisted_instruction<A: Architecture, M: FunctionMutability>(
}
}
-pub fn is_variant_instruction<A: Architecture, M: FunctionMutability>(
+pub fn is_variant_instruction<M: FunctionMutability>(
relocatable_regions: &[Range<u64>],
- instr: &LowLevelILInstruction<A, M, NonSSA<RegularNonSSA>>,
+ instr: &LowLevelILInstruction<M, NonSSA>,
) -> bool {
- let is_variant_expr = |expr: &LowLevelILExpressionKind<A, M, NonSSA<RegularNonSSA>>| {
- match expr {
+ let is_variant_expr = |expr: &LowLevelILExpression<M, NonSSA, ValueExpr>| {
+ match expr.kind() {
LowLevelILExpressionKind::ConstPtr(op)
if is_address_relocatable(relocatable_regions, op.value()) =>
{
// Constant Pointer must be in a section for it to be relocatable.
- // NOTE: We cannot utilize segments here as there will be a zero based segment.
true
}
LowLevelILExpressionKind::Const(op)
if is_address_relocatable(relocatable_regions, op.value()) =>
{
// Constant value must be in a section for it to be relocatable.
- // NOTE: We cannot utilize segments here as there will be a zero based segment.
true
}
LowLevelILExpressionKind::ExternPtr(_) => true,
@@ -181,7 +250,7 @@ pub fn is_variant_instruction<A: Architecture, M: FunctionMutability>(
// Visit instruction expressions looking for variant expression, [VisitorAction::Halt] means variant.
instr.visit_tree(&mut |expr| {
- if is_variant_expr(&expr.kind()) {
+ if is_variant_expr(expr) {
// Found a variant expression.
VisitorAction::Halt
} else {
@@ -191,18 +260,87 @@ pub fn is_variant_instruction<A: Architecture, M: FunctionMutability>(
}) == VisitorAction::Halt
}
-/// If the address is inside any of the given ranges we will assume the address to be relocatable.
+/// NOTE: This will only work at LLIL, **NOT** lifted IL. You must do this in a second pass.
+///
+/// This was previously done inside `is_variant_instruction` but had to be moved to access expr value.
+pub fn is_computed_variant_instruction<M: FunctionMutability>(
+ relocatable_regions: &[Range<u64>],
+ instr: &LowLevelILInstruction<M, NonSSA>,
+) -> bool {
+ let is_expr_constant = |expr: &LowLevelILExpression<M, NonSSA, ValueExpr>| match expr.kind() {
+ LowLevelILExpressionKind::Const(_) => true,
+ _ => false,
+ };
+
+ let is_variant_observed_expr = |expr: &LowLevelILExpression<M, NonSSA, ValueExpr>| {
+ match expr.kind() {
+ // TODO: Skip problematic expressions like IF?
+ LowLevelILExpressionKind::Add(op) | LowLevelILExpressionKind::Sub(op) => {
+ // For now, we limit to only expressions that contain some constant; this keeps add expressions
+ // with two registers with known values from being marked variant.
+ let constant_expressed =
+ is_expr_constant(&op.left()) || is_expr_constant(&op.right());
+ // NOTE: Lifted IL does not have the value ever, we must consult Low Level IL.
+ // If the expression value is known, we check to see if it's a relocatable address.
+ let expr_value = expr.value();
+ match expr_value.state {
+ RegisterValueType::EntryValue
+ | RegisterValueType::ConstantValue
+ | RegisterValueType::ConstantPointerValue
+ | RegisterValueType::ExternalPointerValue
+ | RegisterValueType::StackFrameOffset
+ | RegisterValueType::ReturnAddressValue
+ | RegisterValueType::ImportedAddressValue
+ if constant_expressed
+ && is_address_relocatable(
+ relocatable_regions,
+ expr_value.value as u64,
+ ) =>
+ {
+ // Concrete arithmetic operation with a relocatable result.
+ true
+ }
+ _ => false,
+ }
+ }
+ _ => false,
+ }
+ };
+
+ // Visit instruction expressions looking for an observed variant expression, [VisitorAction::Halt] means variant.
+ instr.visit_tree(&mut |expr| {
+ if is_variant_observed_expr(expr) {
+ // Found a variant expression.
+ VisitorAction::Halt
+ } else {
+ // Keep looking for an observed variant expression.
+ VisitorAction::Descend
+ }
+ }) == VisitorAction::Halt
+}
+
+/// If the address is inside any of the given ranges, we will assume the address to be relocatable.
pub fn is_address_relocatable(relocatable_regions: &[Range<u64>], address: u64) -> bool {
relocatable_regions
.iter()
- .any(|range| range.contains(&address))
+ .any(|range| {
+ // Check if the address is within the range itself
+ (range.contains(&address))
+ // Check if the address is within the threshold **AFTER** the range
+ // NOTE: The address must at least be larger than the threshold itself, for lower image-based binaries.
+ || (address > range.end && address > ADDRESS_RELOCATION_THRESHOLD && address <= range.end + ADDRESS_RELOCATION_THRESHOLD)
+ // Check if the address is within the threshold **BEFORE** the range
+ // NOTE: The address must at least be larger than the threshold itself, for lower image-based binaries.
+ || (address < range.start && address > ADDRESS_RELOCATION_THRESHOLD && address >= range.start.saturating_sub(ADDRESS_RELOCATION_THRESHOLD))
+ })
}
// TODO: This might need to be configurable, in that case we better remove this function.
/// Get the relocatable regions of the view.
///
-/// Currently, this is all the sections, however this might be refined later.
+/// Currently, this is all the sections, however, this might be refined later.
pub fn relocatable_regions(view: &BinaryView) -> Vec<Range<u64>> {
+ // NOTE: We cannot use segments here as there will be a zero-based segment.
view.sections()
.iter()
.map(|s| Range {
@@ -211,41 +349,3 @@ pub fn relocatable_regions(view: &BinaryView) -> Vec<Range<u64>> {
})
.collect()
}
-
-#[cfg(test)]
-mod tests {
- use crate::cache::cached_function_guid;
- use binaryninja::binary_view::BinaryViewExt;
- use binaryninja::headless::Session;
- use std::path::PathBuf;
- use std::sync::OnceLock;
-
- static INIT: OnceLock<Session> = OnceLock::new();
-
- fn get_session<'a>() -> &'a Session {
- // TODO: This is not shared between other test modules, should still be fine (mutex in core now).
- INIT.get_or_init(|| Session::new().expect("Failed to initialize session"))
- }
-
- #[test]
- fn insta_signatures() {
- let session = get_session();
- let out_dir = env!("OUT_DIR").parse::<PathBuf>().unwrap();
- for entry in std::fs::read_dir(out_dir).expect("Failed to read OUT_DIR") {
- let entry = entry.expect("Failed to read directory entry");
- let path = entry.path();
- if path.is_file() {
- let view = session.load(&path).expect("Failed to load view");
- let mut functions = view
- .functions()
- .iter()
- .map(|f| cached_function_guid(&f, &f.low_level_il().unwrap()))
- .collect::<Vec<_>>();
- functions.sort_by_key(|guid| guid.guid);
- let snapshot_name =
- format!("snapshot_{}", path.file_stem().unwrap().to_string_lossy());
- insta::assert_debug_snapshot!(snapshot_name, functions);
- }
- }
- }
-}
diff --git a/plugins/warp/src/matcher.rs b/plugins/warp/src/matcher.rs
index 0ce258ad..c6ab7f9f 100644
--- a/plugins/warp/src/matcher.rs
+++ b/plugins/warp/src/matcher.rs
@@ -1,363 +1,282 @@
+use crate::cache::cached_constraints;
+use crate::container::{Container, SourceId};
+use crate::convert::to_bn_type;
use binaryninja::architecture::Architecture as BNArchitecture;
use binaryninja::binary_view::{BinaryView, BinaryViewExt};
use binaryninja::function::Function as BNFunction;
-use binaryninja::platform::Platform;
-use binaryninja::rc::Guard;
-use binaryninja::rc::Ref as BNRef;
-use dashmap::DashMap;
+use binaryninja::settings::{QueryOptions, Settings as BNSettings};
use serde_json::json;
use std::cmp::Ordering;
-use std::collections::{HashMap, HashSet};
-use std::hash::{DefaultHasher, Hash, Hasher};
-use std::path::PathBuf;
-use std::sync::OnceLock;
-use walkdir::{DirEntry, WalkDir};
+use std::collections::HashSet;
+use std::hash::Hash;
use warp::r#type::class::TypeClass;
-use warp::r#type::guid::TypeGUID;
use warp::r#type::Type;
-use warp::signature::function::{Function, FunctionGUID};
-use warp::signature::Data;
-
-use crate::cache::{
- cached_adjacency_constraints, cached_call_site_constraints, cached_function_match,
- try_cached_function_guid,
-};
-use crate::convert::to_bn_type;
-use crate::plugin::on_matched_function;
-use crate::{core_signature_dir, user_signature_dir};
-
-pub static PLAT_MATCHER_CACHE: OnceLock<DashMap<PlatformID, Matcher>> = OnceLock::new();
-
-pub fn cached_function_matcher(function: &BNFunction) {
- let platform = function.platform();
- let platform_id = PlatformID::from(platform.as_ref());
- let matcher_cache = PLAT_MATCHER_CACHE.get_or_init(Default::default);
- match matcher_cache.get(&platform_id) {
- Some(matcher) => matcher.match_function(function),
- None => {
- let matcher = Matcher::from_platform(platform);
- matcher.match_function(function);
- matcher_cache.insert(platform_id, matcher);
- }
- }
-}
-
-// TODO: Maybe just clear individual platforms? This works well enough either way.
-pub fn invalidate_function_matcher_cache() {
- let matcher_cache = PLAT_MATCHER_CACHE.get_or_init(Default::default);
- matcher_cache.clear();
-}
+use warp::signature::function::Function;
-#[derive(Debug, Default, Clone)]
+/// A matcher represents a specific configuration for identify functions using WARP. A matcher
+/// does not store/own any WARP information directly, instead the matcher is given a [`Container`]
+/// that holds all of that information.
+///
+/// The separation of the WARP information from the [`Matcher`] allows a greater degree of control and
+/// provides a clean interface for further logic to be built on top of. A matcher instance, unlike
+/// a typical [`Container`] implementation, is cheap to create.
+#[derive(Debug, Clone, Copy)]
pub struct Matcher {
- // TODO: Storing the settings here means that they are effectively global.
- // TODO: If we want scoped or view settings they must be moved out.
pub settings: MatcherSettings,
- pub functions: DashMap<FunctionGUID, Vec<Function>>,
- pub types: DashMap<TypeGUID, Type>,
- pub named_types: DashMap<String, Type>,
}
impl Matcher {
- /// Create a matcher from the platforms signature subdirectory.
- pub fn from_platform(platform: BNRef<Platform>) -> Self {
- let platform_name = platform.name().to_string();
+ pub fn new(settings: MatcherSettings) -> Self {
+ Matcher { settings }
+ }
- // Get core and user signatures.
- // TODO: Separate each file into own bucket for filtering?
- let plat_core_sig_dir = core_signature_dir().join(&platform_name);
- let mut data = get_data_from_dir(&plat_core_sig_dir);
- let plat_user_sig_dir = user_signature_dir().join(&platform_name);
- let user_data = get_data_from_dir(&plat_user_sig_dir);
+ pub fn match_function_from_constraints<'a>(
+ &self,
+ function: &BNFunction,
+ matched_functions: &'a [Function],
+ ) -> Option<&'a Function> {
+ let function_len = function.highest_address() - function.lowest_address();
+ let is_function_trivial = { function_len < self.settings.trivial_function_len };
+ let is_function_allowed = {
+ function_len >= self.settings.minimum_function_len
+ && function_len < self.settings.maximum_function_len.unwrap_or(u64::MAX)
+ };
- data.extend(user_data);
- let merged_data = Data::merge(data.values().cloned().collect::<Vec<_>>());
- log::debug!("Loaded signatures: {:?}", data.keys());
- Matcher::from_data(merged_data)
- }
+ // Function isn't allowed, or no matches so stop early.
+ if !is_function_allowed || matched_functions.is_empty() {
+ return None;
+ }
- pub fn from_data(data: Data) -> Self {
- let functions = data.functions.into_iter().fold(
- DashMap::new(),
- |map: DashMap<FunctionGUID, Vec<_>>, func| {
- map.entry(func.guid).or_default().push(func);
- map
- },
- );
- let types = data
- .types
- .iter()
- .map(|ty| (ty.guid, ty.ty.clone()))
- .collect();
- let named_types = data
- .types
- .into_iter()
- .filter_map(|ty| ty.ty.name.to_owned().map(|name| (name, ty.ty)))
- .collect();
+ // If we have a single possible match than that must be our function.
+ // We must also not be a trivial function, as those will likely be artifacts of an incomplete dataset
+ if matched_functions.len() == 1 && !is_function_trivial {
+ return matched_functions.first();
+ }
+ // Filter out adjacent functions which are trivial, this helps avoid false positives.
+ // NOTE: If the user sets `trivial_function_adjacent_allowed` to true we will always match.
+ // TODO: Expand on this more later. We might want to match on adjacent functions smaller than this.
+ let adjacent_function_filter = |adj_func: &BNFunction| {
+ let adj_func_len = adj_func.highest_address() - adj_func.lowest_address();
+ adj_func_len >= self.settings.trivial_function_len
+ || self.settings.trivial_function_adjacent_allowed
+ };
- Self {
- // NOTE: Settings will be retrieved from global state every time this is called.
- settings: MatcherSettings::global(),
- functions,
- types,
- named_types,
+ // TODO: When the highest count has two matches we return None. Need to alert the user.
+ // "common" being the intersection between the observed and matched.
+ let constraints = cached_constraints(function, adjacent_function_filter);
+ let mut highest_count = 0;
+ let mut matched_func = None;
+ for matched in matched_functions {
+ let common_count = constraints.intersection(&matched.constraints).count();
+ match common_count.cmp(&highest_count) {
+ Ordering::Equal => matched_func = None,
+ Ordering::Greater => {
+ highest_count = common_count;
+ matched_func = Some(matched);
+ }
+ Ordering::Less => {}
+ }
}
- }
- pub fn extend_with_matcher(&mut self, matcher: Matcher) {
- self.functions.extend(matcher.functions);
- self.types.extend(matcher.types);
- self.named_types.extend(matcher.named_types);
+ // If we have a match below the minimum threshold, ignore.
+ match highest_count.cmp(&self.settings.minimum_matched_constraints) {
+ Ordering::Equal => matched_func,
+ Ordering::Greater => matched_func,
+ Ordering::Less => None,
+ }
}
- pub fn add_type_to_view<A: BNArchitecture>(&self, view: &BinaryView, arch: &A, ty: &Type) {
+ // TODO: I would really like for WARP types to be added in a seperate type container, so that we don't
+ // TODO: just add them as system or user types.
+ pub fn add_type_to_view<A: BNArchitecture>(
+ &self,
+ container: &dyn Container,
+ source: &SourceId,
+ view: &BinaryView,
+ arch: &A,
+ ty: &Type,
+ ) where
+ Self: Sized,
+ {
fn inner_add_type_to_view<A: BNArchitecture>(
- matcher: &Matcher,
+ container: &dyn Container,
+ source: &SourceId,
view: &BinaryView,
arch: &A,
visited_refs: &mut HashSet<String>,
ty: &Type,
) {
- let ty_id_str = TypeGUID::from(ty).to_string();
- if view.type_by_id(&ty_id_str).is_some() {
- // Type already added.
- return;
- }
// Type not already added to the view.
// Verify all nested types are added before adding type.
- match ty.class.as_ref() {
- TypeClass::Pointer(c) => {
- inner_add_type_to_view(matcher, view, arch, visited_refs, &c.child_type)
- }
- TypeClass::Array(c) => {
- inner_add_type_to_view(matcher, view, arch, visited_refs, &c.member_type)
- }
+ match &ty.class {
+ TypeClass::Pointer(c) => inner_add_type_to_view(
+ container,
+ source,
+ view,
+ arch,
+ visited_refs,
+ &c.child_type,
+ ),
+ TypeClass::Array(c) => inner_add_type_to_view(
+ container,
+ source,
+ view,
+ arch,
+ visited_refs,
+ &c.member_type,
+ ),
TypeClass::Structure(c) => {
for member in &c.members {
- inner_add_type_to_view(matcher, view, arch, visited_refs, &member.ty)
+ inner_add_type_to_view(
+ container,
+ source,
+ view,
+ arch,
+ visited_refs,
+ &member.ty,
+ )
}
}
- TypeClass::Enumeration(c) => {
- inner_add_type_to_view(matcher, view, arch, visited_refs, &c.member_type)
- }
+ TypeClass::Enumeration(c) => inner_add_type_to_view(
+ container,
+ source,
+ view,
+ arch,
+ visited_refs,
+ &c.member_type,
+ ),
TypeClass::Union(c) => {
for member in &c.members {
- inner_add_type_to_view(matcher, view, arch, visited_refs, &member.ty)
+ inner_add_type_to_view(
+ container,
+ source,
+ view,
+ arch,
+ visited_refs,
+ &member.ty,
+ )
}
}
TypeClass::Function(c) => {
for out_member in &c.out_members {
- inner_add_type_to_view(matcher, view, arch, visited_refs, &out_member.ty)
+ inner_add_type_to_view(
+ container,
+ source,
+ view,
+ arch,
+ visited_refs,
+ &out_member.ty,
+ )
}
for in_member in &c.in_members {
- inner_add_type_to_view(matcher, view, arch, visited_refs, &in_member.ty)
+ inner_add_type_to_view(
+ container,
+ source,
+ view,
+ arch,
+ visited_refs,
+ &in_member.ty,
+ )
}
}
TypeClass::Referrer(c) => {
// Check to see if the referrer has been added to the view.
- let mut resolved = false;
+ let mut resolved_ty = None;
if let Some(ref_guid) = c.guid {
// NOTE: We do not need to check for cyclic reference here because
// NOTE: GUID references are unable to be referenced by themselves.
if view.type_by_id(&ref_guid.to_string()).is_none() {
// Add the referrer to the view if it is in the Matcher types
- if let Some(ref_ty) = matcher.types.get(&ref_guid) {
- inner_add_type_to_view(matcher, view, arch, visited_refs, &ref_ty);
- resolved = true;
+ if let Ok(Some(ref_ty)) = container.type_with_guid(source, &ref_guid) {
+ inner_add_type_to_view(
+ container,
+ source,
+ view,
+ arch,
+ visited_refs,
+ &ref_ty,
+ );
+ resolved_ty = Some(ref_ty);
}
}
}
if let Some(ref_name) = &c.name {
// Only try and resolve by name if not already visiting.
- if !resolved
+ if resolved_ty.is_none()
&& visited_refs.insert(ref_name.to_string())
&& view.type_by_name(ref_name).is_none()
{
// Add the ref to the view if it is in the Matcher types
- if let Some(ref_ty) = matcher.named_types.get(ref_name) {
- inner_add_type_to_view(matcher, view, arch, visited_refs, &ref_ty);
+ let type_guids = container
+ .type_guids_with_name(source, ref_name)
+ .unwrap_or_default();
+ // TODO: What happens if we have more than one?
+ if type_guids.len() == 1 {
+ // TODO: What happens if we cant get the guid?
+ if let Ok(Some(ref_ty)) =
+ container.type_with_guid(source, &type_guids[0])
+ {
+ inner_add_type_to_view(
+ container,
+ source,
+ view,
+ arch,
+ visited_refs,
+ &ref_ty,
+ );
+ resolved_ty = Some(ref_ty);
+ }
}
// No longer visiting type.
visited_refs.remove(ref_name);
}
}
- // All nested types _should_ be added now, we can add this type.
- // TODO: Do we want to make unnamed types visible? I think we should, but some people might be opposed.
- let ty_name = ty.name.to_owned().unwrap_or_else(|| ty_id_str.clone());
- view.define_auto_type_with_id(ty_name, &ty_id_str, &to_bn_type(arch, ty));
- }
- _ => {}
- }
- }
- inner_add_type_to_view(self, view, arch, &mut HashSet::new(), ty)
- }
-
- pub fn match_function(&self, function: &BNFunction) {
- // Call this the first time you matched on the function.
- let resolve_new_types = |matched: &Function| {
- // We also want to resolve the types here.
- if let TypeClass::Function(c) = matched.ty.class.as_ref() {
- // Recursively go through the function type and resolve referrers
- let view = function.view();
- let arch = function.arch();
- for out_member in &c.out_members {
- self.add_type_to_view(&view, &arch, &out_member.ty);
- }
- for in_member in &c.in_members {
- self.add_type_to_view(&view, &arch, &in_member.ty);
- }
- }
- };
-
- if let Some(matched_function) = cached_function_match(function, || {
- // We have yet to match on this function.
- let function_len = function.highest_address() - function.lowest_address();
- let is_function_trivial = { function_len < self.settings.trivial_function_len };
- let is_function_allowed = {
- function_len > self.settings.minimum_function_len
- && function_len < self.settings.maximum_function_len.unwrap_or(u64::MAX)
- };
- let warp_func_guid = try_cached_function_guid(function)?;
- match self.functions.get(&warp_func_guid) {
- _ if !is_function_allowed => None,
- Some(matched) if matched.len() == 1 && !is_function_trivial => {
- resolve_new_types(&matched[0]);
- Some(matched[0].to_owned())
- }
- Some(matched) => {
- let matched_on = self.match_function_from_constraints(function, &matched)?;
- resolve_new_types(matched_on);
- Some(matched_on.to_owned())
- }
- None => None,
- }
- }) {
- on_matched_function(function, &matched_function);
- }
- }
-
- pub fn match_function_from_constraints<'a>(
- &self,
- function: &BNFunction,
- matched_functions: &'a [Function],
- ) -> Option<&'a Function> {
- // Filter out adjacent functions which are trivial, this helps avoid false positives.
- // NOTE: If the user sets `trivial_function_adjacent_allowed` to true we will always match.
- // TODO: Expand on this more later. We might want to match on adjacent functions smaller than this.
- let adjacent_function_filter = |adj_func: &BNFunction| {
- let adj_func_len = adj_func.highest_address() - adj_func.lowest_address();
- adj_func_len > self.settings.trivial_function_len
- || self.settings.trivial_function_adjacent_allowed
- };
-
- let call_sites = cached_call_site_constraints(function);
- let adjacent = cached_adjacency_constraints(function, adjacent_function_filter);
-
- // "common" being the intersection between the observed and matched.
- fn find_highest_common_count<'a, F, T>(
- observed_items: &HashSet<T>,
- matched_functions: &'a [Function],
- extract_items: F,
- ) -> (usize, Option<&'a Function>)
- where
- F: Fn(&Function) -> HashSet<T>,
- T: Hash + Eq,
- {
- let mut highest_count = 0;
- let mut matched_func = None;
- for matched in matched_functions {
- let matched_items = extract_items(matched);
- let common_count = observed_items.intersection(&matched_items).count();
- match common_count.cmp(&highest_count) {
- Ordering::Equal => matched_func = None,
- Ordering::Greater => {
- highest_count = common_count;
- matched_func = Some(matched);
+ // Adds the ref'd type to the view.
+ match (c.guid, &c.name, resolved_ty) {
+ (Some(guid), Some(name), Some(ref_ty)) => {
+ view.define_auto_type_with_id(
+ name,
+ &guid.to_string(),
+ &to_bn_type(arch, &ref_ty),
+ );
+ }
+ (Some(_guid), Some(_name), None) => {
+ // TODO: Got name and guid but no type? Do we add a bare NTR?
+ }
+ (Some(_guid), None, _) => {
+ // TODO: How would we reference this type without a name???
+ }
+ (None, Some(_name), _) => {
+ // TODO: Cyclic type reference if no guid, so... dont define?
+ }
+ (None, None, _) => {
+ // TODO: What?!?!?
+ }
}
- Ordering::Less => {}
}
+ TypeClass::Void
+ | TypeClass::Boolean(_)
+ | TypeClass::Integer(_)
+ | TypeClass::Character(_)
+ | TypeClass::Float(_) => {}
}
- (highest_count, matched_func)
- }
-
- let call_site_guids: HashSet<_> = call_sites.iter().filter_map(|c| c.guid).collect();
- let call_site_symbol_names: HashSet<_> = call_sites
- .into_iter()
- .filter_map(|c| c.symbol.map(|s| s.name))
- .collect();
- let adjacent_guids: HashSet<_> = adjacent.iter().filter_map(|c| c.guid).collect();
- let adjacent_symbol_names: HashSet<_> = adjacent
- .into_iter()
- .filter_map(|c| c.symbol.map(|s| s.name))
- .collect();
-
- // Ordered from the lowest confidence to the highest confidence constraint.
- let checked_constraints = [
- find_highest_common_count(&adjacent_symbol_names, matched_functions, |matched| {
- matched
- .constraints
- .adjacent
- .iter()
- .filter_map(|c| c.symbol.to_owned().map(|s| s.name))
- .collect()
- }),
- find_highest_common_count(&adjacent_guids, matched_functions, |matched| {
- matched
- .constraints
- .adjacent
- .iter()
- .filter_map(|c| c.guid)
- .collect()
- }),
- find_highest_common_count(&call_site_symbol_names, matched_functions, |matched| {
- matched
- .constraints
- .call_sites
- .iter()
- .filter_map(|c| c.symbol.to_owned().map(|s| s.name))
- .collect()
- }),
- find_highest_common_count(&call_site_guids, matched_functions, |matched| {
- matched
- .constraints
- .call_sites
- .iter()
- .filter_map(|c| c.guid)
- .collect()
- }),
- ];
- // If there is a tie, the last one wins, which should be call_site guid.
- checked_constraints
- .into_iter()
- .max_by_key(|&(count, _)| count)
- .filter(|&(count, _)| count >= self.settings.minimum_matched_constraints)
- .and_then(|(_, func)| func)
+ // TODO: Some refs likely need to ommitted because they are just that, refs to another type.
+ // let guid = TypeGUID::from(ty);
+ // let name = ty.name.clone().unwrap_or(guid.to_string());
+ // view.define_auto_type_with_id(name, &guid.to_string(), &to_bn_type(arch, ty));
+ }
+ inner_add_type_to_view(container, source, view, arch, &mut HashSet::new(), ty)
}
}
-fn get_data_from_dir(dir: &PathBuf) -> HashMap<PathBuf, Data> {
- let data_from_entry = |entry: DirEntry| {
- let path = entry.path();
- let contents = std::fs::read(path).ok()?;
- Data::from_bytes(&contents)
- };
-
- WalkDir::new(dir)
- .into_iter()
- .filter_map(|e| e.ok())
- .filter(|e| e.file_type().is_file())
- .filter_map(|e| Some((e.clone().into_path(), data_from_entry(e)?)))
- .collect()
-}
-
-#[derive(Debug, Clone)]
+#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct MatcherSettings {
/// Any function under this length will be required to constrain.
///
- /// This is set to [MatcherSettings::DEFAULT_TRIVIAL_FUNCTION_LEN] by default.
+ /// This is set to [MatcherSettings::TRIVIAL_FUNCTION_LEN_DEFAULT] by default.
pub trivial_function_len: u64,
/// Any function under this length will not match.
///
@@ -367,13 +286,15 @@ pub struct MatcherSettings {
///
/// This is set to [MatcherSettings::MAXIMUM_FUNCTION_LEN_DEFAULT] by default.
pub maximum_function_len: Option<u64>,
- /// For a successful constrained function match the number of matches must be above this.
+ /// For a successful constrained function match, the number of matches must be above this.
///
- /// This is set to [MatcherSettings::DEFAULT_TRIVIAL_FUNCTION_LEN] by default.
+ /// This is set to [MatcherSettings::MINIMUM_MATCHED_CONSTRAINTS_DEFAULT] by default.
pub minimum_matched_constraints: usize,
- /// For a successful constrained function match the number of matches must be above this.
+ /// When function constraints are checked, if this is enabled, functions can match based off trivial adjacent functions.
+ ///
+ /// Any function under `trivial_function_len` will be considered trivial.
///
- /// This is set to [MatcherSettings::DEFAULT_TRIVIAL_FUNCTION_LEN] by default.
+ /// This is set to [MatcherSettings::TRIVIAL_FUNCTION_ADJACENT_ALLOWED_DEFAULT] by default.
pub trivial_function_adjacent_allowed: bool,
}
@@ -395,17 +316,15 @@ impl MatcherSettings {
///
/// Call this once when you initialize so that the settings exist.
///
- /// NOTE: If you are using this as a library then just modify the MatcherSettings directly
+ /// NOTE: If you are using this as a library, then modify the [`MatcherSettings`] directly
/// in the matcher instance, that way you don't need to round-trip through Binary Ninja.
- pub fn register() {
- let bn_settings = binaryninja::settings::Settings::new();
-
+ pub fn register(bn_settings: &mut BNSettings) {
let trivial_function_len_props = json!({
"title" : "Trivial Function Length",
"type" : "number",
"default" : Self::TRIVIAL_FUNCTION_LEN_DEFAULT,
"description" : "Functions below this length in bytes will be required to match on constraints.",
- "ignore" : ["SettingsProjectScope", "SettingsResourceScope"]
+ "ignore" : []
});
bn_settings.register_setting_json(
Self::TRIVIAL_FUNCTION_LEN_SETTING,
@@ -417,7 +336,7 @@ impl MatcherSettings {
"type" : "number",
"default" : Self::MINIMUM_FUNCTION_LEN_DEFAULT,
"description" : "Functions below this length will not be matched.",
- "ignore" : ["SettingsProjectScope", "SettingsResourceScope"]
+ "ignore" : []
});
bn_settings.register_setting_json(
Self::MINIMUM_FUNCTION_LEN_SETTING,
@@ -429,7 +348,7 @@ impl MatcherSettings {
"type" : "number",
"default" : Self::MAXIMUM_FUNCTION_LEN_DEFAULT,
"description" : "Functions above this length will not be matched. A value of 0 will disable this check.",
- "ignore" : ["SettingsProjectScope", "SettingsResourceScope"]
+ "ignore" : []
});
bn_settings.register_setting_json(
Self::MAXIMUM_FUNCTION_LEN_SETTING,
@@ -441,7 +360,7 @@ impl MatcherSettings {
"type" : "number",
"default" : Self::MINIMUM_MATCHED_CONSTRAINTS_DEFAULT,
"description" : "When function constraints are checked the amount of constraints matched must be at-least this.",
- "ignore" : ["SettingsProjectScope", "SettingsResourceScope"]
+ "ignore" : []
});
bn_settings.register_setting_json(
Self::MINIMUM_MATCHED_CONSTRAINTS_SETTING,
@@ -453,7 +372,7 @@ impl MatcherSettings {
"type" : "boolean",
"default" : Self::TRIVIAL_FUNCTION_ADJACENT_ALLOWED_DEFAULT,
"description" : "When function constraints are checked if this is enabled functions can match based off trivial adjacent functions.",
- "ignore" : ["SettingsProjectScope", "SettingsResourceScope"]
+ "ignore" : []
});
bn_settings.register_setting_json(
Self::TRIVIAL_FUNCTION_ADJACENT_ALLOWED_SETTING,
@@ -461,26 +380,32 @@ impl MatcherSettings {
);
}
- pub fn global() -> Self {
+ /// Retrieve matcher settings from [`BNSettings`].
+ pub fn from_settings(bn_settings: &BNSettings, query_opts: &mut QueryOptions) -> Self {
let mut settings = MatcherSettings::default();
- let bn_settings = binaryninja::settings::Settings::new();
if bn_settings.contains(Self::TRIVIAL_FUNCTION_LEN_SETTING) {
settings.trivial_function_len =
- bn_settings.get_integer(Self::TRIVIAL_FUNCTION_LEN_SETTING);
+ bn_settings.get_integer_with_opts(Self::TRIVIAL_FUNCTION_LEN_SETTING, query_opts);
}
if bn_settings.contains(Self::MINIMUM_FUNCTION_LEN_SETTING) {
settings.minimum_function_len =
- bn_settings.get_integer(Self::MINIMUM_FUNCTION_LEN_SETTING);
+ bn_settings.get_integer_with_opts(Self::MINIMUM_FUNCTION_LEN_SETTING, query_opts);
}
if bn_settings.contains(Self::MAXIMUM_FUNCTION_LEN_SETTING) {
- match bn_settings.get_integer(Self::MAXIMUM_FUNCTION_LEN_SETTING) {
+ match bn_settings.get_integer_with_opts(Self::MAXIMUM_FUNCTION_LEN_SETTING, query_opts)
+ {
0 => settings.maximum_function_len = None,
len => settings.maximum_function_len = Some(len),
}
}
if bn_settings.contains(Self::MINIMUM_MATCHED_CONSTRAINTS_SETTING) {
- settings.minimum_matched_constraints =
- bn_settings.get_integer(Self::MINIMUM_MATCHED_CONSTRAINTS_SETTING) as usize;
+ settings.minimum_matched_constraints = bn_settings
+ .get_integer_with_opts(Self::MINIMUM_MATCHED_CONSTRAINTS_SETTING, query_opts)
+ as usize;
+ }
+ if bn_settings.contains(Self::TRIVIAL_FUNCTION_ADJACENT_ALLOWED_SETTING) {
+ settings.trivial_function_adjacent_allowed = bn_settings
+ .get_bool_with_opts(Self::TRIVIAL_FUNCTION_ADJACENT_ALLOWED_SETTING, query_opts);
}
settings
}
@@ -498,27 +423,3 @@ impl Default for MatcherSettings {
}
}
}
-
-/// A unique platform ID, used for caching.
-#[derive(Copy, Clone, Debug, Hash, PartialEq, Eq, PartialOrd, Ord)]
-pub struct PlatformID(u64);
-
-impl From<&Platform> for PlatformID {
- fn from(value: &Platform) -> Self {
- let mut hasher = DefaultHasher::new();
- hasher.write(value.name().as_bytes());
- Self(hasher.finish())
- }
-}
-
-impl From<BNRef<Platform>> for PlatformID {
- fn from(value: BNRef<Platform>) -> Self {
- Self::from(value.as_ref())
- }
-}
-
-impl From<Guard<'_, Platform>> for PlatformID {
- fn from(value: Guard<'_, Platform>) -> Self {
- Self::from(value.as_ref())
- }
-}
diff --git a/plugins/warp/src/plugin.rs b/plugins/warp/src/plugin.rs
index 4adab293..f8c4c0c5 100644
--- a/plugins/warp/src/plugin.rs
+++ b/plugins/warp/src/plugin.rs
@@ -1,186 +1,40 @@
use crate::cache::register_cache_destructor;
+use std::time::Instant;
+use crate::cache::container::add_cached_container;
+use crate::container::disk::DiskContainer;
use crate::matcher::MatcherSettings;
use crate::plugin::render_layer::HighlightRenderLayer;
-use binaryninja::binary_view::{BinaryView, BinaryViewExt};
-use binaryninja::command::{Command, FunctionCommand};
-use binaryninja::function::{Function, FunctionUpdateType};
+use crate::plugin::settings::PluginSettings;
+use crate::{core_signature_dir, user_signature_dir};
+use binaryninja::background_task::BackgroundTask;
+use binaryninja::command::{
+ register_command, register_command_for_function, register_command_for_project,
+};
use binaryninja::logger::Logger;
-use binaryninja::rc::Ref;
-use binaryninja::tags::TagType;
-use binaryninja::ObjectDestructor;
+use binaryninja::settings::Settings;
use log::LevelFilter;
-use warp::signature::function::constraints::FunctionConstraint;
-use warp::signature::function::Function as WarpFunction;
-mod add;
-mod copy;
mod create;
-mod find;
+mod debug;
+mod ffi;
+mod file;
+mod function;
mod load;
+mod project;
mod render_layer;
-mod types;
+mod settings;
mod workflow;
-// TODO: This icon is a little much
-const TAG_ICON: &str = "🌏";
-const TAG_NAME: &str = "WARP";
-
-fn get_warp_tag_type(view: &BinaryView) -> Ref<TagType> {
- view.tag_type_by_name(TAG_NAME)
- .unwrap_or_else(|| view.create_tag_type(TAG_NAME, TAG_ICON))
-}
-
-// What happens to the function when it is matched.
-// TODO: add user: bool
-// TODO: Rename to markup_function or something.
-pub fn on_matched_function(function: &Function, matched: &WarpFunction) {
- let view = function.view();
- // TODO: Using user symbols here is problematic
- // TODO: For one they queue up a bunch of main thread actions
- // TODO: Secondly by queueing up those main thread actions if you attempt to save the file
- // TODO: Before the undo actions are done completing
- view.define_user_symbol(&to_bn_symbol_at_address(
- &view,
- &matched.symbol,
- function.symbol().address(),
- ));
- function.set_user_type(&to_bn_type(&function.arch(), &matched.ty));
- // TODO: Add metadata. (both binja metadata and warp metadata)
- function.add_tag(
- &get_warp_tag_type(&view),
- &matched.guid.to_string(),
- None,
- true,
- None,
- );
- // Seems to be the only way to get the analysis update to work correctly.
- function.mark_updates_required(FunctionUpdateType::FullAutoFunctionUpdate);
-}
-
-struct DebugFunction;
-
-impl FunctionCommand for DebugFunction {
- fn action(&self, _view: &BinaryView, func: &Function) {
- if let Ok(llil) = func.low_level_il() {
- log::info!("{:#?}", build_function(func, &llil));
- }
- }
-
- fn valid(&self, _view: &BinaryView, _func: &Function) -> bool {
- true
- }
-}
-
-struct DebugMatcher;
-
-impl FunctionCommand for DebugMatcher {
- fn action(&self, _view: &BinaryView, function: &Function) {
- let Ok(llil) = function.low_level_il() else {
- log::error!("No LLIL for function 0x{:x}", function.start());
- return;
- };
- let platform = function.platform();
- // Build the matcher every time this is called to make sure we aren't in a bad state.
- let matcher = Matcher::from_platform(platform);
- let func = build_function(function, &llil);
- // TODO: Clean this up.
- if let Some(possible_matches) = matcher.functions.get(&func.guid) {
- let print_constraint = |prefix: &str, constraint: &FunctionConstraint| {
- log::info!(
- " {} {} ({})",
- prefix,
- constraint
- .to_owned()
- .symbol
- .map(|s| s.name)
- .unwrap_or("*".to_string()),
- constraint
- .guid
- .map(|g| g.to_string())
- .unwrap_or("*".to_string())
- );
- };
- for possible_match in possible_matches.value() {
- log::info!("{} ({})", possible_match.symbol.name, possible_match.guid);
- for constraint in &possible_match.constraints.call_sites {
- print_constraint("CS", constraint);
- }
- for constraint in &possible_match.constraints.call_sites {
- print_constraint("ADJ", constraint);
- }
- }
- } else {
- log::error!(
- "No possible matches found for the function 0x{:x}",
- function.start()
- );
- };
- }
-
- fn valid(&self, _view: &BinaryView, _function: &Function) -> bool {
- true
- }
-}
-
-struct DebugCache;
-
-impl Command for DebugCache {
- fn action(&self, view: &BinaryView) {
- let view_id = ViewID::from(view);
- let function_cache = FUNCTION_CACHE.get_or_init(Default::default);
- if let Some(cache) = function_cache.get(&view_id) {
- log::info!("View functions: {}", cache.cache.len());
- }
-
- let matched_function_cache = MATCHED_FUNCTION_CACHE.get_or_init(Default::default);
- if let Some(cache) = matched_function_cache.get(&view_id) {
- log::info!("View matched functions: {}", cache.cache.len());
- }
-
- let function_guid_cache = GUID_CACHE.get_or_init(Default::default);
- if let Some(cache) = function_guid_cache.get(&view_id) {
- log::info!("View function guids: {}", cache.cache.len());
- }
-
- let plat_cache = PLAT_MATCHER_CACHE.get_or_init(Default::default);
- if let Some(plat) = view.default_platform() {
- let platform_id = PlatformID::from(plat);
- if let Some(cache) = plat_cache.get(&platform_id) {
- log::info!("Platform functions: {}", cache.functions.len());
- log::info!("Platform types: {}", cache.types.len());
- log::info!("Platform settings: {:?}", cache.settings);
- }
- }
- }
-
- fn valid(&self, _view: &BinaryView) -> bool {
- true
- }
-}
-
-struct DebugInvalidateCache;
-
-impl Command for DebugInvalidateCache {
- fn action(&self, view: &BinaryView) {
- invalidate_function_matcher_cache();
- let destructor = cache::CacheDestructor {};
- destructor.destruct_view(view);
- log::info!("Invalidated all WARP caches...");
- }
-
- fn valid(&self, _view: &BinaryView) -> bool {
- true
- }
-}
-
#[no_mangle]
#[allow(non_snake_case)]
pub extern "C" fn CorePluginInit() -> bool {
Logger::new("WARP").with_level(LevelFilter::Debug).init();
- // Register our matcher settings.
- MatcherSettings::register();
+ // Register our matcher and plugin settings globally.
+ let mut global_bn_settings = Settings::new();
+ MatcherSettings::register(&mut global_bn_settings);
+ PluginSettings::register(&mut global_bn_settings);
// Make sure caches are flushed when the views get destructed.
register_cache_destructor();
@@ -190,70 +44,93 @@ pub extern "C" fn CorePluginInit() -> bool {
workflow::insert_workflow();
- binaryninja::command::register_command(
+ let plugin_settings = PluginSettings::from_settings(&global_bn_settings);
+ // We want to load all the bundled directories into the container cache.
+ let background_task = BackgroundTask::new("Loading WARP files...", false);
+ let start = Instant::now();
+ if plugin_settings.load_bundled_files {
+ let core_disk_container = DiskContainer::new_from_dir(core_signature_dir());
+ log::debug!("{:#?}", core_disk_container);
+ add_cached_container(core_disk_container);
+ }
+ if plugin_settings.load_user_files {
+ let user_disk_container = DiskContainer::new_from_dir(user_signature_dir());
+ log::debug!("{:#?}", user_disk_container);
+ add_cached_container(user_disk_container);
+ }
+ log::info!("Loading bundled files took {:?}", start.elapsed());
+ background_task.finish();
+
+ register_command(
"WARP\\Run Matcher",
"Run the matcher manually",
workflow::RunMatcher {},
);
- binaryninja::command::register_command(
+ register_command(
"WARP\\Debug\\Cache",
"Debug cache sizes... because...",
- DebugCache {},
+ debug::DebugCache {},
);
- binaryninja::command::register_command(
+ register_command(
"WARP\\Debug\\Invalidate Caches",
"Invalidate all WARP caches",
- DebugInvalidateCache {},
+ debug::DebugInvalidateCache {},
);
- binaryninja::command::register_command_for_function(
+ register_command_for_function(
"WARP\\Debug\\Function Signature",
"Print the entire signature for the function",
- DebugFunction {},
- );
-
- binaryninja::command::register_command_for_function(
- "WARP\\Debug\\Function Matcher",
- "Print all possible matches for the function",
- DebugMatcher {},
+ debug::DebugFunction {},
);
- binaryninja::command::register_command(
- "WARP\\Debug\\Apply Signature File Types",
- "Load all types from a signature file and ignore functions",
- types::LoadTypes {},
- );
-
- binaryninja::command::register_command(
- "WARP\\Load Signature File",
+ register_command(
+ "WARP\\Load File",
"Load file into the matcher, this does NOT kick off matcher analysis",
load::LoadSignatureFile {},
);
- binaryninja::command::register_command_for_function(
- "WARP\\Copy Function GUID",
+ register_command_for_function(
+ "WARP\\Function\\Include",
+ "Add current function to the list of functions to add to the signature file",
+ function::IncludeFunction {},
+ );
+
+ register_command_for_function(
+ "WARP\\Function\\Copy GUID",
"Copy the computed GUID for the function",
- copy::CopyFunctionGUID {},
+ function::CopyFunctionGUID {},
);
- binaryninja::command::register_command(
- "WARP\\Find Function From GUID",
+ register_command(
+ "WARP\\Function\\Find GUID",
"Locate the function in the view using a GUID",
- find::FindFunctionFromGUID {},
+ function::FindFunctionFromGUID {},
+ );
+
+ register_command(
+ "WARP\\Create\\From Current View",
+ "Creates a signature file containing all selected functions",
+ create::CreateFromCurrentView {},
+ );
+
+ register_command(
+ "WARP\\Create\\From File(s)",
+ "Creates a signature file containing all selected functions",
+ create::CreateFromFiles {},
);
- binaryninja::command::register_command(
- "WARP\\Generate Signature File",
- "Generates a signature file containing all binary view functions",
- create::CreateSignatureFile {},
+ register_command(
+ "WARP\\Show Report",
+ "Creates a report for the selected file, displaying info on functions and types",
+ file::ShowFileReport {},
);
- binaryninja::command::register_command_for_function(
- "WARP\\Add Function Signature to File",
- "Stores the signature for the function in the signature file",
- add::AddFunctionSignature {},
+ register_command_for_project(
+ "WARP\\Create\\From Project",
+ "Create signature files from select project files",
+ project::CreateSignatures {},
);
true
diff --git a/plugins/warp/src/plugin/add.rs b/plugins/warp/src/plugin/add.rs
deleted file mode 100644
index 2f76c8b5..00000000
--- a/plugins/warp/src/plugin/add.rs
+++ /dev/null
@@ -1,72 +0,0 @@
-use crate::cache::{cached_function, cached_type_references};
-use crate::matcher::invalidate_function_matcher_cache;
-use crate::user_signature_dir;
-use binaryninja::binary_view::BinaryView;
-use binaryninja::command::FunctionCommand;
-use binaryninja::function::Function;
-use std::thread;
-
-pub struct AddFunctionSignature;
-
-impl FunctionCommand for AddFunctionSignature {
- fn action(&self, view: &BinaryView, func: &Function) {
- let func_plat_name = func.platform().name().to_string();
- let signature_dir = user_signature_dir().join(func_plat_name);
- let view = view.to_owned();
- let func = func.to_owned();
- thread::spawn(move || {
- let Ok(llil) = func.low_level_il() else {
- log::error!("Could not get low level IL for function.");
- return;
- };
-
- // NOTE: Because we only can consume signatures from a specific directory, we don't need to use the interaction API.
- // If we did need to save signature files to a project than this would need to change.
- let Some(save_file) = rfd::FileDialog::new()
- .add_filter("Signature Files", &["sbin"])
- .set_file_name("user.sbin")
- .set_directory(signature_dir)
- .save_file()
- else {
- return;
- };
-
- let mut data = warp::signature::Data::default();
- if let Ok(file_bytes) = std::fs::read(&save_file) {
- // If the file we are adding the function to already has data we should preserve it!
- log::info!("Signature file already exists, preserving data...");
- let Some(file_data) = warp::signature::Data::from_bytes(&file_bytes) else {
- log::error!("Could not get data from signature file: {:?}", save_file);
- return;
- };
- data = file_data;
- };
-
- // Now add our function to the data.
- data.functions.push(cached_function(&func, &llil));
-
- if let Some(ref_ty_cache) = cached_type_references(&view) {
- let referenced_types = ref_ty_cache
- .cache
- .iter()
- .filter_map(|t| t.to_owned())
- .collect::<Vec<_>>();
-
- data.types.extend(referenced_types);
- }
-
- match std::fs::write(&save_file, data.to_bytes()) {
- Ok(_) => {
- log::info!("Signature file saved successfully.");
- // Force rebuild platform matcher.
- invalidate_function_matcher_cache();
- }
- Err(e) => log::error!("Failed to write data to signature file: {:?}", e),
- }
- });
- }
-
- fn valid(&self, _view: &BinaryView, _func: &Function) -> bool {
- true
- }
-}
diff --git a/plugins/warp/src/plugin/copy.rs b/plugins/warp/src/plugin/copy.rs
deleted file mode 100644
index b9b985fc..00000000
--- a/plugins/warp/src/plugin/copy.rs
+++ /dev/null
@@ -1,29 +0,0 @@
-use binaryninja::binary_view::BinaryView;
-use binaryninja::command::FunctionCommand;
-use binaryninja::function::Function;
-
-use crate::cache::cached_function_guid;
-
-pub struct CopyFunctionGUID;
-
-impl FunctionCommand for CopyFunctionGUID {
- fn action(&self, _view: &BinaryView, func: &Function) {
- let Ok(llil) = func.low_level_il() else {
- log::error!("Could not get low level il for copied function");
- return;
- };
- let guid = cached_function_guid(func, &llil);
- log::info!(
- "Function GUID for {:?}... {}",
- func.symbol().short_name(),
- guid
- );
- if let Ok(mut clipboard) = arboard::Clipboard::new() {
- let _ = clipboard.set_text(guid.to_string());
- }
- }
-
- fn valid(&self, _view: &BinaryView, _func: &Function) -> bool {
- true
- }
-}
diff --git a/plugins/warp/src/plugin/create.rs b/plugins/warp/src/plugin/create.rs
index 1dd83e6e..9ebcba2d 100644
--- a/plugins/warp/src/plugin/create.rs
+++ b/plugins/warp/src/plugin/create.rs
@@ -1,94 +1,232 @@
-use crate::cache::{cached_function, cached_type_references};
-use crate::matcher::invalidate_function_matcher_cache;
-use crate::user_signature_dir;
+use crate::processor::{
+ new_processing_state_background_thread, CompressionTypeField, FileDataKindField,
+ IncludedFunctionsField, SaveReportToDiskField, WarpFileProcessor,
+};
+use crate::report::{ReportGenerator, ReportKindField};
+use crate::{user_signature_dir, INCLUDE_TAG_NAME};
+use binaryninja::background_task::BackgroundTask;
use binaryninja::binary_view::{BinaryView, BinaryViewExt};
use binaryninja::command::Command;
-use binaryninja::function::Function;
-use binaryninja::rc::Guard;
-use rayon::prelude::*;
-use std::sync::atomic::AtomicUsize;
-use std::sync::atomic::Ordering::Relaxed;
+use binaryninja::interaction::form::{Form, FormInputField};
+use binaryninja::interaction::{MessageBoxButtonResult, MessageBoxButtonSet, MessageBoxIcon};
+use binaryninja::rc::Ref;
+use std::path::PathBuf;
use std::thread;
-use std::time::Instant;
+use warp::chunk::Chunk;
+use warp::WarpFile;
-pub struct CreateSignatureFile;
+pub struct SaveFileField;
-// TODO: Prompt the user to add the newly created signature file to the signature blacklist (so that it doesn't keep getting applied)
+impl SaveFileField {
+ pub fn field(view: &BinaryView) -> FormInputField {
+ let default_name = view
+ .file()
+ .filename()
+ .split('/')
+ .last()
+ .unwrap_or("file")
+ .to_string();
+ let signature_dir = user_signature_dir();
+ let default_file_path = signature_dir.join(&default_name).with_extension("warp");
+ FormInputField::SaveFileName {
+ prompt: "File Path".to_string(),
+ // TODO: This is called extension but is really a filter.
+ extension: Some("*.warp".to_string()),
+ default_name: Some(default_name),
+ default: Some(default_file_path.to_string_lossy().to_string()),
+ value: None,
+ }
+ }
-impl Command for CreateSignatureFile {
- fn action(&self, view: &BinaryView) {
- let is_function_named = |f: &Guard<Function>| {
- !f.symbol().short_name().to_string_lossy().contains("sub_") || f.has_user_annotations()
- };
- let mut signature_dir = user_signature_dir();
- if let Some(default_plat) = view.default_platform() {
- // If there is a default platform, put the signature in there.
- // TODO: We should instead use the platform of the function.
- signature_dir.push(default_plat.name().to_string());
+ pub fn from_form(form: &Form) -> Option<PathBuf> {
+ let field = form.get_field_with_name("File Path")?;
+ let field_value = field.try_value_string()?;
+ Some(PathBuf::from(field_value))
+ }
+}
+
+pub struct OpenFileField;
+
+impl OpenFileField {
+ pub fn field() -> FormInputField {
+ FormInputField::OpenFileName {
+ prompt: "Input File Path".to_string(),
+ extension: None,
+ default: None,
+ value: None,
}
- let view = view.to_owned();
- thread::spawn(move || {
- let total_functions = view.functions().len();
- let done_functions = AtomicUsize::default();
- let background_task = binaryninja::background_task::BackgroundTask::new(
- &format!("Generating signatures... ({}/{})", 0, total_functions),
- true,
- );
+ }
+
+ pub fn from_form(form: &Form) -> Option<PathBuf> {
+ let field = form.get_field_with_name("Input File Path")?;
+ let field_value = field.try_value_string()?;
+ Some(PathBuf::from(field_value))
+ }
+}
+
+pub struct CreateFromCurrentView;
- let start = Instant::now();
+impl CreateFromCurrentView {
+ pub fn execute(view: Ref<BinaryView>, external_file: bool) -> Option<()> {
+ // Prompt the user first so that they can go do other things and not worry about a popup.
+ let mut form = Form::new("Create From View");
- let mut data = warp::signature::Data::default();
- data.functions.par_extend(
- view.functions()
- .par_iter()
- .inspect(|_| {
- done_functions.fetch_add(1, Relaxed);
- background_task.set_progress_text(&format!(
- "Generating signatures... ({}/{})",
- done_functions.load(Relaxed),
- total_functions
- ))
- })
- .filter(is_function_named)
- .filter(|f| !f.analysis_skipped())
- .filter_map(|func| {
- let llil = func.low_level_il().ok()?;
- Some(cached_function(&func, &llil))
- }),
+ if external_file {
+ form.add_field(OpenFileField::field());
+ }
+
+ form.add_field(SaveFileField::field(&view));
+
+ let fd_field = FileDataKindField::default();
+ form.add_field(fd_field.to_field());
+
+ let compression_field = CompressionTypeField::default();
+ form.add_field(compression_field.to_field());
+
+ let mut included_field = IncludedFunctionsField::default();
+ // If the view has the include tag, we better set the default to the selected functions.
+ if view.tag_type_by_name(INCLUDE_TAG_NAME).is_some() {
+ included_field = IncludedFunctionsField::Selected;
+ }
+ form.add_field(included_field.to_field());
+
+ let report_field = ReportKindField::default();
+ form.add_field(report_field.to_field());
+ let report_to_disk_field = SaveReportToDiskField::default();
+ form.add_field(report_to_disk_field.to_field());
+
+ if !form.prompt() {
+ return None;
+ }
+ let compression_type = CompressionTypeField::from_form(&form).unwrap_or_default();
+ let file_path = SaveFileField::from_form(&form)?;
+ let file_data_kind = FileDataKindField::from_form(&form).unwrap_or_default();
+ let file_included_functions = IncludedFunctionsField::from_form(&form).unwrap_or_default();
+ let report_kind = ReportKindField::from_form(&form).unwrap_or_default();
+ let save_report_to_disk = SaveReportToDiskField::from_form(&form).unwrap_or_default();
+ let open_file_path = OpenFileField::from_form(&form);
+
+ // If we already have a file, prompt the user if they want to add the data.
+ let mut existing_chunks = Vec::new();
+ if file_path.exists() {
+ let prompt_result = binaryninja::interaction::show_message_box(
+ "Keep existing file data?",
+ "The file already exists. Do you want to keep the existing data?",
+ MessageBoxButtonSet::YesNoCancelButtonSet,
+ MessageBoxIcon::QuestionIcon,
);
- if let Some(ref_ty_cache) = cached_type_references(&view) {
- let referenced_types = ref_ty_cache
- .cache
- .iter()
- .filter_map(|t| t.to_owned())
- .collect::<Vec<_>>();
+ match prompt_result {
+ MessageBoxButtonResult::NoButton => {
+ // User wants to overwrite the file.
+ }
+ MessageBoxButtonResult::YesButton | MessageBoxButtonResult::OKButton => {
+ // User wants to keep the existing data.
+ let data = std::fs::read(&file_path).ok()?;
+ let existing_file = WarpFile::from_owned_bytes(data)?;
+ existing_chunks.extend(existing_file.chunks);
+ }
+ MessageBoxButtonResult::CancelButton => {
+ log::info!(
+ "User cancelled signature file creation, no operations were performed."
+ );
+ return None;
+ }
+ }
+ }
+
+ let processor = WarpFileProcessor::new()
+ .with_compression_type(compression_type)
+ .with_file_data(file_data_kind)
+ .with_included_functions(file_included_functions);
- data.types.extend(referenced_types);
+ let file = match open_file_path {
+ None => {
+ // We are processing the current view. NOT an external file.
+ // Reference path is just used for the state tracking. Does not need to be readable.
+ let reference_path = file_path.clone();
+ processor.process_view(reference_path, &view)
+ }
+ Some(open_file_path) => {
+ // This thread will show the state in a background task.
+ let background_task = BackgroundTask::new("Processing started...", true);
+ new_processing_state_background_thread(background_task.clone(), processor.state());
+ let file = processor.process(open_file_path);
+ background_task.finish();
+ file
}
+ };
+
+ if let Err(err) = file {
+ binaryninja::interaction::show_message_box(
+ "Error",
+ &format!("Failed to create signature file: {}", err),
+ MessageBoxButtonSet::OKButtonSet,
+ MessageBoxIcon::ErrorIcon,
+ );
+ log::error!("Failed to create signature file: {}", err);
+ return None;
+ }
- log::info!("Signature generation took {:?}", start.elapsed());
- background_task.finish();
+ let mut file = file.unwrap();
+ // Add back the existing chunks if the user selected to keep them.
+ file.chunks.extend(existing_chunks);
+ // TODO: Make merging optional?
+ file.chunks = Chunk::merge(&file.chunks, compression_type.into());
+
+ if std::fs::write(&file_path, file.to_bytes()).is_err() {
+ log::error!("Failed to write data to signature file!");
+ }
- // NOTE: Because we only can consume signatures from a specific directory, we don't need to use the interaction API.
- // If we did need to save signature files to a project than this would need to change.
- let Some(save_file) = rfd::FileDialog::new()
- .add_filter("Signature Files", &["sbin"])
- .set_file_name(format!("{}.sbin", view.file().filename()))
- .set_directory(signature_dir)
- .save_file()
- else {
- return;
- };
+ // Show a report of the generate signatures, if desired.
+ let report_generator = ReportGenerator::new();
+ if let Some(report_string) = report_generator.report(&report_kind, &file) {
+ if save_report_to_disk == SaveReportToDiskField::Yes {
+ let report_ext = report_generator
+ .report_extension(&report_kind)
+ .unwrap_or_default();
+ let report_path = file_path.with_extension(report_ext);
+ let _ = std::fs::write(report_path, &report_string);
+ }
- match std::fs::write(&save_file, data.to_bytes()) {
- Ok(_) => {
- log::info!("Signature file saved successfully.");
- // Force rebuild platform matcher.
- invalidate_function_matcher_cache();
+ match report_kind {
+ ReportKindField::None => {}
+ ReportKindField::Html => {
+ view.show_html_report("Generated WARP File", report_string.as_str(), "");
+ }
+ ReportKindField::Markdown => {
+ view.show_markdown_report("Generated WARP File", report_string.as_str(), "");
+ }
+ ReportKindField::Json => {
+ view.show_plaintext_report("Generated WARP File", report_string.as_str());
}
- Err(e) => log::error!("Failed to write data to signature file: {:?}", e),
}
+ }
+
+ Some(())
+ }
+}
+
+impl Command for CreateFromCurrentView {
+ fn action(&self, view: &BinaryView) {
+ let view = view.to_owned();
+ thread::spawn(move || {
+ CreateFromCurrentView::execute(view, false);
+ });
+ }
+
+ fn valid(&self, _view: &BinaryView) -> bool {
+ true
+ }
+}
+
+pub struct CreateFromFiles;
+
+impl Command for CreateFromFiles {
+ fn action(&self, view: &BinaryView) {
+ let view = view.to_owned();
+ thread::spawn(move || {
+ CreateFromCurrentView::execute(view, true);
});
}
diff --git a/plugins/warp/src/plugin/debug.rs b/plugins/warp/src/plugin/debug.rs
new file mode 100644
index 00000000..fc4e5817
--- /dev/null
+++ b/plugins/warp/src/plugin/debug.rs
@@ -0,0 +1,48 @@
+use crate::cache::container::for_cached_containers;
+use crate::{build_function, cache};
+use binaryninja::binary_view::BinaryView;
+use binaryninja::command::{Command, FunctionCommand};
+use binaryninja::function::Function;
+use binaryninja::ObjectDestructor;
+
+pub struct DebugFunction;
+
+impl FunctionCommand for DebugFunction {
+ fn action(&self, _view: &BinaryView, func: &Function) {
+ if let Ok(lifted_il) = func.lifted_il() {
+ log::info!("{:#?}", build_function(func, &lifted_il));
+ }
+ }
+
+ fn valid(&self, _view: &BinaryView, _func: &Function) -> bool {
+ true
+ }
+}
+
+pub struct DebugCache;
+
+impl Command for DebugCache {
+ fn action(&self, _view: &BinaryView) {
+ for_cached_containers(|c| {
+ log::info!("Container: {:#?}", c);
+ });
+ }
+
+ fn valid(&self, _view: &BinaryView) -> bool {
+ true
+ }
+}
+
+pub struct DebugInvalidateCache;
+
+impl Command for DebugInvalidateCache {
+ fn action(&self, view: &BinaryView) {
+ let destructor = cache::CacheDestructor {};
+ destructor.destruct_view(view);
+ log::info!("Invalidated all WARP caches...");
+ }
+
+ fn valid(&self, _view: &BinaryView) -> bool {
+ true
+ }
+}
diff --git a/plugins/warp/src/plugin/ffi.rs b/plugins/warp/src/plugin/ffi.rs
new file mode 100644
index 00000000..d78ca3c5
--- /dev/null
+++ b/plugins/warp/src/plugin/ffi.rs
@@ -0,0 +1,205 @@
+mod container;
+mod function;
+
+use binaryninjacore_sys::{
+ BNBasicBlock, BNBinaryView, BNFunction, BNLowLevelILFunction, BNPlatform,
+};
+use std::ffi::c_char;
+use std::sync::{Arc, RwLock};
+use uuid::Uuid;
+
+use binaryninja::basic_block::{BasicBlock, BasicBlockType};
+use binaryninja::function::{Function, NativeBlock};
+
+use crate::cache::cached_function_guid;
+use crate::container::{Container, SourceId};
+use crate::convert::platform_to_target;
+use crate::plugin::workflow::run_matcher;
+use crate::{
+ basic_block_guid, is_blacklisted_instruction, is_computed_variant_instruction,
+ is_variant_instruction, relocatable_regions,
+};
+use binaryninja::binary_view::BinaryView;
+use binaryninja::low_level_il::function::{LowLevelILFunction, Mutable, NonSSA};
+use binaryninja::low_level_il::instruction::LowLevelInstructionIndex;
+use binaryninja::platform::Platform;
+use binaryninja::string::BnString;
+use warp::r#type::guid::TypeGUID;
+use warp::signature::basic_block::BasicBlockGUID;
+use warp::signature::constraint::{Constraint, ConstraintGUID, UNRELATED_OFFSET};
+use warp::signature::function::FunctionGUID;
+
+/// [`SourceId`] is marked transparent to the underlying `[u8; 16]`, safe to use directly in FFI.
+pub type BNWARPSource = SourceId;
+
+/// [`BasicBlockGUID`] is marked transparent to the underlying `[u8; 16]`, safe to use directly in FFI.
+pub type BNWARPBasicBlockGUID = BasicBlockGUID;
+
+/// [`ConstraintGUID`] is marked transparent to the underlying `[u8; 16]`, safe to use directly in FFI.
+pub type BNWARPConstraintGUID = ConstraintGUID;
+
+/// [`FunctionGUID`] is marked transparent to the underlying `[u8; 16]`, safe to use directly in FFI.
+pub type BNWARPFunctionGUID = FunctionGUID;
+
+/// [`TypeGUID`] is marked transparent to the underlying `[u8; 16]`, safe to use directly in FFI.
+pub type BNWARPTypeGUID = TypeGUID;
+
+pub type BNWARPTarget = warp::target::Target;
+pub type BNWARPFunction = warp::signature::function::Function;
+pub type BNWARPContainer = RwLock<Box<dyn Container>>;
+
+// TODO: Some sort of callback for loading functions
+// TODO: Be able to run matcher for a specific file
+// TODO: Generate signatures for a file, return what?
+
+#[repr(C)]
+#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
+pub struct BNWARPConstraint {
+ guid: BNWARPConstraintGUID,
+ offset: i64,
+}
+
+impl From<BNWARPConstraint> for Constraint {
+ fn from(constraint: BNWARPConstraint) -> Self {
+ Constraint {
+ guid: constraint.guid,
+ offset: match constraint.offset {
+ UNRELATED_OFFSET => None,
+ _ => Some(constraint.offset),
+ },
+ }
+ }
+}
+
+impl From<Constraint> for BNWARPConstraint {
+ fn from(constraint: Constraint) -> Self {
+ BNWARPConstraint {
+ guid: constraint.guid,
+ offset: constraint.offset.unwrap_or(UNRELATED_OFFSET),
+ }
+ }
+}
+
+#[no_mangle]
+pub unsafe extern "C" fn BNWARPUUIDGetString(uuid: *const Uuid) -> *mut c_char {
+ let uuid_str = (*uuid).to_string();
+ // NOTE: Leak the uuid string to be freed by BNFreeString
+ BnString::into_raw(uuid_str.into())
+}
+
+#[no_mangle]
+pub unsafe extern "C" fn BNWARPUUIDEqual(a: *const Uuid, b: *const Uuid) -> bool {
+ (*a) == (*b)
+}
+
+#[no_mangle]
+pub unsafe extern "C" fn BNWARPRunMatcher(view: *mut BNBinaryView) {
+ let view = BinaryView::from_raw(view);
+ run_matcher(&view)
+}
+
+#[no_mangle]
+pub unsafe extern "C" fn BNWARPGetBasicBlockGUID(
+ basic_block: *mut BNBasicBlock,
+ result: *mut BNWARPBasicBlockGUID,
+) -> bool {
+ let basic_block = unsafe { BasicBlock::from_raw(basic_block, NativeBlock::new()) };
+ if basic_block.block_type() != BasicBlockType::Native {
+ return false;
+ }
+ let function = basic_block.function();
+ match function.lifted_il() {
+ Ok(lifted_il) => {
+ let relocatable_regions = relocatable_regions(&function.view());
+ *result = basic_block_guid(&relocatable_regions, &basic_block, &lifted_il);
+ true
+ }
+ Err(_) => false,
+ }
+}
+
+#[no_mangle]
+pub unsafe extern "C" fn BNWARPGetAnalysisFunctionGUID(
+ analysis_function: *mut BNFunction,
+ result: *mut BNWARPFunctionGUID,
+) -> bool {
+ let function = unsafe { Function::from_raw(analysis_function) };
+ match function.lifted_il() {
+ Ok(lifted_il) => {
+ *result = cached_function_guid(&function, &lifted_il);
+ true
+ }
+ Err(_) => false,
+ }
+}
+
+#[no_mangle]
+pub unsafe extern "C" fn BNWARPIsLiftedInstructionVariant(
+ analysis_function: *mut BNLowLevelILFunction,
+ index: LowLevelInstructionIndex,
+) -> bool {
+ let lifted_il: LowLevelILFunction<Mutable, NonSSA> =
+ unsafe { LowLevelILFunction::from_raw(analysis_function) };
+ match lifted_il.instruction_from_index(index) {
+ Some(instr) => {
+ let relocatable_regions = relocatable_regions(&lifted_il.function().view());
+ is_variant_instruction(&relocatable_regions, &instr)
+ }
+ None => false,
+ }
+}
+
+#[no_mangle]
+pub unsafe extern "C" fn BNWARPIsLowLevelInstructionComputedVariant(
+ analysis_function: *mut BNLowLevelILFunction,
+ index: LowLevelInstructionIndex,
+) -> bool {
+ let llil: LowLevelILFunction<Mutable, NonSSA> =
+ unsafe { LowLevelILFunction::from_raw(analysis_function) };
+ match llil.instruction_from_index(index) {
+ Some(instr) => {
+ let relocatable_regions = relocatable_regions(&llil.function().view());
+ is_computed_variant_instruction(&relocatable_regions, &instr)
+ }
+ None => false,
+ }
+}
+
+#[no_mangle]
+pub unsafe extern "C" fn BNWARPIsLiftedInstructionBlacklisted(
+ analysis_function: *mut BNLowLevelILFunction,
+ index: LowLevelInstructionIndex,
+) -> bool {
+ let lifted_il: LowLevelILFunction<Mutable, NonSSA> =
+ unsafe { LowLevelILFunction::from_raw(analysis_function) };
+ match lifted_il.instruction_from_index(index) {
+ Some(instr) => is_blacklisted_instruction(&instr),
+ None => false,
+ }
+}
+
+#[no_mangle]
+pub unsafe extern "C" fn BNWARPFreeUUIDList(uuids: *mut Uuid, count: usize) {
+ let sources_ptr = std::ptr::slice_from_raw_parts_mut(uuids, count);
+ let _ = unsafe { Box::from_raw(sources_ptr) };
+}
+
+#[no_mangle]
+pub unsafe extern "C" fn BNWARPGetTarget(platform: *mut BNPlatform) -> *mut BNWARPTarget {
+ let platform = Platform::from_raw(platform);
+ Arc::into_raw(Arc::new(platform_to_target(&platform))) as *mut BNWARPTarget
+}
+
+#[no_mangle]
+pub unsafe extern "C" fn BNWARPNewTargetReference(target: *mut BNWARPTarget) -> *mut BNWARPTarget {
+ Arc::increment_strong_count(target);
+ target
+}
+
+#[no_mangle]
+pub unsafe extern "C" fn BNWARPFreeTargetReference(target: *mut BNWARPTarget) {
+ if target.is_null() {
+ return;
+ }
+ Arc::decrement_strong_count(target);
+}
diff --git a/plugins/warp/src/plugin/ffi/container.rs b/plugins/warp/src/plugin/ffi/container.rs
new file mode 100644
index 00000000..21ad4dc0
--- /dev/null
+++ b/plugins/warp/src/plugin/ffi/container.rs
@@ -0,0 +1,412 @@
+use crate::cache::container::cached_containers;
+use crate::container::SourcePath;
+use crate::convert::{from_bn_type, to_bn_type};
+use crate::plugin::ffi::{
+ BNWARPContainer, BNWARPFunction, BNWARPFunctionGUID, BNWARPSource, BNWARPTarget, BNWARPTypeGUID,
+};
+use binaryninja::architecture::CoreArchitecture;
+use binaryninja::binary_view::BinaryView;
+use binaryninja::rc::Ref;
+use binaryninja::string::BnString;
+use binaryninja::types::Type;
+use binaryninjacore_sys::{BNArchitecture, BNBinaryView, BNType};
+use std::ffi::{c_char, CStr};
+use std::mem::ManuallyDrop;
+use std::sync::Arc;
+
+#[no_mangle]
+pub unsafe extern "C" fn BNWARPGetContainers(count: *mut usize) -> *mut *mut BNWARPContainer {
+ // NOTE: Leak the arc pointers to be freed by BNWARPFreeContainerList
+ let boxed_raw_containers: Box<[_]> =
+ cached_containers().into_iter().map(Arc::into_raw).collect();
+ *count = boxed_raw_containers.len();
+ let leaked_raw_containers = Box::into_raw(boxed_raw_containers);
+ leaked_raw_containers as *mut *mut BNWARPContainer
+}
+
+#[no_mangle]
+pub unsafe extern "C" fn BNWARPContainerGetName(container: *mut BNWARPContainer) -> *const c_char {
+ let arc_container = ManuallyDrop::new(Arc::from_raw(container));
+ let Ok(container) = arc_container.read() else {
+ return std::ptr::null();
+ };
+ let name = container.to_string();
+ // NOTE: Leak the container name to be freed by BNFreeString
+ BnString::into_raw(name.into())
+}
+
+#[no_mangle]
+pub unsafe extern "C" fn BNWARPContainerGetSources(
+ container: *mut BNWARPContainer,
+ count: *mut usize,
+) -> *mut BNWARPSource {
+ let arc_container = ManuallyDrop::new(Arc::from_raw(container));
+ let Ok(container) = arc_container.write() else {
+ return std::ptr::null_mut();
+ };
+
+ // NOTE: Leak the sources to be freed by BNWARPFreeSourceList
+ let boxed_sources: Box<[_]> = container.sources().unwrap_or_default().into_boxed_slice();
+ *count = boxed_sources.len();
+ Box::into_raw(boxed_sources) as *mut BNWARPSource
+}
+
+#[no_mangle]
+pub unsafe extern "C" fn BNWARPContainerAddSource(
+ container: *mut BNWARPContainer,
+ source_path: *const c_char,
+ result: *mut BNWARPSource,
+) -> bool {
+ let arc_container = ManuallyDrop::new(Arc::from_raw(container));
+ let Ok(mut container) = arc_container.write() else {
+ return false;
+ };
+
+ let source_path_cstr = unsafe { CStr::from_ptr(source_path) };
+ let source_path_str = source_path_cstr.to_str().unwrap();
+ let source_path = SourcePath::new_with_str(source_path_str);
+
+ match container.add_source(source_path) {
+ Ok(source) => {
+ // NOTE: Leak the source to be freed by BNFreeString
+ *result = source;
+ true
+ }
+ Err(_) => false,
+ }
+}
+
+#[no_mangle]
+pub unsafe extern "C" fn BNWARPContainerCommitSource(
+ container: *mut BNWARPContainer,
+ source: *const BNWARPSource,
+) -> bool {
+ let arc_container = ManuallyDrop::new(Arc::from_raw(container));
+ let Ok(mut container) = arc_container.write() else {
+ return false;
+ };
+
+ let source = unsafe { *source };
+
+ container
+ .commit_source(&source)
+ .is_ok_and(|committed| committed)
+}
+
+#[no_mangle]
+pub unsafe extern "C" fn BNWARPContainerIsSourceUncommitted(
+ container: *mut BNWARPContainer,
+ source: *const BNWARPSource,
+) -> bool {
+ let arc_container = ManuallyDrop::new(Arc::from_raw(container));
+ let Ok(container) = arc_container.read() else {
+ return false;
+ };
+
+ let source = unsafe { *source };
+
+ container
+ .is_source_uncommitted(&source)
+ .is_ok_and(|uncommitted| uncommitted)
+}
+
+#[no_mangle]
+pub unsafe extern "C" fn BNWARPContainerIsSourceWritable(
+ container: *mut BNWARPContainer,
+ source: *const BNWARPSource,
+) -> bool {
+ let arc_container = ManuallyDrop::new(Arc::from_raw(container));
+ let Ok(container) = arc_container.read() else {
+ return false;
+ };
+
+ let source = unsafe { *source };
+
+ container
+ .is_source_writable(&source)
+ .is_ok_and(|writable| writable)
+}
+
+#[no_mangle]
+pub unsafe extern "C" fn BNWARPContainerGetSourcePath(
+ container: *mut BNWARPContainer,
+ source: *const BNWARPSource,
+) -> *const c_char {
+ let arc_container = ManuallyDrop::new(Arc::from_raw(container));
+ let Ok(container) = arc_container.read() else {
+ return std::ptr::null();
+ };
+
+ let source = unsafe { *source };
+
+ match container.source_path(&source) {
+ Ok(path) => {
+ let path = path.to_string();
+ // NOTE: Leak the source path to be freed by BNFreeString
+ BnString::into_raw(path.into())
+ }
+ Err(_) => std::ptr::null(),
+ }
+}
+
+#[no_mangle]
+pub unsafe extern "C" fn BNWARPContainerAddFunctions(
+ container: *mut BNWARPContainer,
+ target: *mut BNWARPTarget,
+ source: *const BNWARPSource,
+ functions: *mut *mut BNWARPFunction,
+ count: usize,
+) -> bool {
+ let arc_container = ManuallyDrop::new(Arc::from_raw(container));
+ let Ok(mut container) = arc_container.write() else {
+ return false;
+ };
+
+ let target = unsafe { ManuallyDrop::new(Arc::from_raw(target)) };
+
+ let source = unsafe { *source };
+
+ let functions_ptr = std::slice::from_raw_parts(functions, count);
+ // TODO: We have to clone the objects here to make the type checker happy.
+ // TODO: See about avoiding this later.
+ let functions: Vec<_> = functions_ptr
+ .iter()
+ .map(|&f| unsafe { ManuallyDrop::new(Arc::from_raw(f)).as_ref().clone() })
+ .collect();
+ container
+ .add_functions(&target, &source, &functions)
+ .is_ok()
+}
+
+#[no_mangle]
+pub unsafe extern "C" fn BNWARPContainerAddTypes(
+ view: *mut BNBinaryView,
+ container: *mut BNWARPContainer,
+ source: *const BNWARPSource,
+ types: *mut *mut BNType,
+ count: usize,
+) -> bool {
+ let view = unsafe { BinaryView::from_raw(view) };
+
+ let arc_container = ManuallyDrop::new(Arc::from_raw(container));
+ let Ok(mut container) = arc_container.write() else {
+ return false;
+ };
+
+ let source = unsafe { *source };
+
+ let types_ptr = std::slice::from_raw_parts(types, count);
+ let types: Vec<_> = types_ptr
+ .iter()
+ .map(|&t| Type::from_raw(t))
+ .map(|ty| from_bn_type(&view, &ty, 255))
+ .collect();
+ container.add_types(&source, &types).is_ok()
+}
+
+#[no_mangle]
+pub unsafe extern "C" fn BNWARPContainerRemoveFunctions(
+ container: *mut BNWARPContainer,
+ target: *mut BNWARPTarget,
+ source: *const BNWARPSource,
+ functions: *mut *mut BNWARPFunction,
+ count: usize,
+) -> bool {
+ let arc_container = ManuallyDrop::new(Arc::from_raw(container));
+ let Ok(mut container) = arc_container.write() else {
+ return false;
+ };
+
+ let target = unsafe { ManuallyDrop::new(Arc::from_raw(target)) };
+
+ let source = unsafe { *source };
+
+ let functions_ptr = std::slice::from_raw_parts(functions, count);
+ // TODO: We have to clone the objects here to make the type checker happy.
+ // TODO: See about avoiding this later.
+ let functions: Vec<_> = functions_ptr
+ .iter()
+ .map(|&f| unsafe { ManuallyDrop::new(Arc::from_raw(f)).as_ref().clone() })
+ .collect();
+ container
+ .remove_functions(&target, &source, &functions)
+ .is_ok()
+}
+
+#[no_mangle]
+pub unsafe extern "C" fn BNWARPContainerRemoveTypes(
+ container: *mut BNWARPContainer,
+ source: *const BNWARPSource,
+ guids: *mut BNWARPTypeGUID,
+ count: usize,
+) -> bool {
+ let arc_container = ManuallyDrop::new(Arc::from_raw(container));
+ let Ok(mut container) = arc_container.write() else {
+ return false;
+ };
+
+ let source = unsafe { *source };
+
+ let guids = std::slice::from_raw_parts(guids, count);
+ container.remove_types(&source, &guids).is_ok()
+}
+
+#[no_mangle]
+pub unsafe extern "C" fn BNWARPContainerGetSourcesWithFunctionGUID(
+ container: *mut BNWARPContainer,
+ target: *mut BNWARPTarget,
+ guid: *const BNWARPFunctionGUID,
+ count: *mut usize,
+) -> *mut BNWARPSource {
+ let arc_container = ManuallyDrop::new(Arc::from_raw(container));
+ let Ok(container) = arc_container.read() else {
+ return std::ptr::null_mut();
+ };
+
+ let target = unsafe { ManuallyDrop::new(Arc::from_raw(target)) };
+
+ let guid = unsafe { *guid };
+
+ // NOTE: Leak the sources to be freed by BNWARPFreeSourceList
+ let boxed_sources: Box<[_]> = container
+ .sources_with_function_guid(&target, &guid)
+ .unwrap_or_default()
+ .into_boxed_slice();
+ *count = boxed_sources.len();
+ Box::into_raw(boxed_sources) as *mut BNWARPSource
+}
+
+#[no_mangle]
+pub unsafe extern "C" fn BNWARPContainerGetSourcesWithTypeGUID(
+ container: *mut BNWARPContainer,
+ guid: *const BNWARPTypeGUID,
+ count: *mut usize,
+) -> *mut BNWARPSource {
+ let arc_container = ManuallyDrop::new(Arc::from_raw(container));
+ let Ok(container) = arc_container.read() else {
+ return std::ptr::null_mut();
+ };
+
+ let guid = unsafe { *guid };
+
+ // NOTE: Leak the sources to be freed by BNWARPFreeSourceList
+ let boxed_sources: Box<[_]> = container
+ .sources_with_type_guid(&guid)
+ .unwrap_or_default()
+ .into_boxed_slice();
+ *count = boxed_sources.len();
+ Box::into_raw(boxed_sources) as *mut BNWARPSource
+}
+
+#[no_mangle]
+pub unsafe extern "C" fn BNWARPContainerGetFunctionsWithGUID(
+ container: *mut BNWARPContainer,
+ target: *mut BNWARPTarget,
+ source: *const BNWARPSource,
+ guid: *const BNWARPFunctionGUID,
+ count: *mut usize,
+) -> *mut *mut BNWARPFunction {
+ let arc_container = ManuallyDrop::new(Arc::from_raw(container));
+ let Ok(container) = arc_container.read() else {
+ return std::ptr::null_mut();
+ };
+
+ let source = unsafe { *source };
+
+ let target = unsafe { ManuallyDrop::new(Arc::from_raw(target)) };
+
+ let guid = unsafe { *guid };
+
+ // NOTE: Leak the functions to be freed by BNWARPFreeFunctionList
+ let raw_boxed_functions: Box<[_]> = container
+ .functions_with_guid(&target, &source, &guid)
+ .unwrap_or_default()
+ .into_iter()
+ .map(Arc::new)
+ .map(Arc::into_raw)
+ .collect();
+ *count = raw_boxed_functions.len();
+ Box::into_raw(raw_boxed_functions) as *mut *mut BNWARPFunction
+}
+
+// TODO: Swap arch to Target?
+#[no_mangle]
+pub unsafe extern "C" fn BNWARPContainerGetTypeWithGUID(
+ arch: *mut BNArchitecture,
+ container: *mut BNWARPContainer,
+ source: *const BNWARPSource,
+ guid: *const BNWARPTypeGUID,
+) -> *mut BNType {
+ let arc_container = ManuallyDrop::new(Arc::from_raw(container));
+ let Ok(container) = arc_container.read() else {
+ return std::ptr::null_mut();
+ };
+
+ // NOTE: to convert the type, we must have an architecture.
+ let arch = CoreArchitecture::from_raw(arch);
+
+ let source = unsafe { *source };
+
+ let guid = unsafe { *guid };
+
+ let Some(ty) = container.type_with_guid(&source, &guid).unwrap_or_default() else {
+ return std::ptr::null_mut();
+ };
+ let function_type = to_bn_type(&arch, &ty);
+ // NOTE: The type ref has been pre-incremented for the caller.
+ unsafe { Ref::into_raw(function_type) }.handle
+}
+
+#[no_mangle]
+pub unsafe extern "C" fn BNWARPContainerGetTypeGUIDsWithName(
+ container: *mut BNWARPContainer,
+ source: *const BNWARPSource,
+ name: *const c_char,
+ count: *mut usize,
+) -> *mut BNWARPTypeGUID {
+ let arc_container = ManuallyDrop::new(Arc::from_raw(container));
+ let Ok(container) = arc_container.read() else {
+ return std::ptr::null_mut();
+ };
+
+ let source = unsafe { *source };
+
+ let name_cstr = unsafe { CStr::from_ptr(name) };
+ let name = name_cstr.to_str().unwrap();
+
+ // NOTE: Leak the guids to be freed by BNWARPFreeTypeGUIDList
+ let boxed_guids = container
+ .type_guids_with_name(&source, name)
+ .unwrap_or_default()
+ .into_boxed_slice();
+ *count = boxed_guids.len();
+ Box::into_raw(boxed_guids) as *mut BNWARPTypeGUID
+}
+
+#[no_mangle]
+pub unsafe extern "C" fn BNWARPNewContainerReference(
+ container: *mut BNWARPContainer,
+) -> *mut BNWARPContainer {
+ Arc::increment_strong_count(container);
+ container
+}
+
+#[no_mangle]
+pub unsafe extern "C" fn BNWARPFreeContainerReference(container: *mut BNWARPContainer) {
+ if container.is_null() {
+ return;
+ }
+ Arc::decrement_strong_count(container);
+}
+
+#[no_mangle]
+pub unsafe extern "C" fn BNWARPFreeContainerList(
+ containers: *mut *mut BNWARPContainer,
+ count: usize,
+) {
+ let containers_ptr = std::ptr::slice_from_raw_parts_mut(containers, count);
+ let containers = unsafe { Box::from_raw(containers_ptr) };
+ for container in containers {
+ BNWARPFreeContainerReference(container);
+ }
+}
diff --git a/plugins/warp/src/plugin/ffi/function.rs b/plugins/warp/src/plugin/ffi/function.rs
new file mode 100644
index 00000000..3db8f307
--- /dev/null
+++ b/plugins/warp/src/plugin/ffi/function.rs
@@ -0,0 +1,228 @@
+use crate::build_function;
+use crate::cache::{insert_cached_function_match, try_cached_function_match};
+use crate::convert::{to_bn_symbol_at_address, to_bn_type};
+use crate::plugin::ffi::{BNWARPConstraint, BNWARPFunction, BNWARPFunctionGUID};
+use binaryninja::function::Function;
+use binaryninja::rc::Ref;
+use binaryninja::string::BnString;
+use binaryninjacore_sys::{BNFunction, BNSymbol, BNType};
+use std::ffi::c_char;
+use std::mem::ManuallyDrop;
+use std::sync::Arc;
+use warp::signature::comment::FunctionComment;
+
+#[repr(C)]
+pub struct BNWarpFunctionComment {
+ pub text: *mut c_char,
+ pub offset: i64,
+}
+
+impl BNWarpFunctionComment {
+ /// Leaks the text string to be freed with BNWARPFreeFunctionComment
+ pub fn from_owned(value: &FunctionComment) -> Self {
+ let text = BnString::into_raw(BnString::new(&value.text));
+ Self {
+ text,
+ offset: value.offset,
+ }
+ }
+
+ pub unsafe fn free_raw(value: &Self) {
+ unsafe { BnString::free_raw(value.text) }
+ }
+}
+
+#[no_mangle]
+pub unsafe extern "C" fn BNWARPGetFunction(
+ analysis_function: *mut BNFunction,
+) -> *mut BNWARPFunction {
+ let function = Function::from_raw(analysis_function);
+ let Ok(lifted_il) = function.lifted_il() else {
+ return std::ptr::null_mut();
+ };
+ let function = build_function(&function, &lifted_il);
+ Arc::into_raw(Arc::new(function)) as *mut BNWARPFunction
+}
+
+#[no_mangle]
+pub unsafe extern "C" fn BNWARPGetMatchedFunction(
+ analysis_function: *mut BNFunction,
+) -> *mut BNWARPFunction {
+ let function = Function::from_raw(analysis_function);
+ match try_cached_function_match(&function) {
+ Some(matched_function) => {
+ let arc_matched_function = Arc::new(matched_function);
+ // NOTE: Freed by BNWARPFreeFunctionReference
+ Arc::into_raw(arc_matched_function) as *mut BNWARPFunction
+ }
+ None => std::ptr::null_mut(),
+ }
+}
+
+#[no_mangle]
+pub unsafe extern "C" fn BNWARPFunctionApply(
+ function: *mut BNWARPFunction,
+ analysis_function: *mut BNFunction,
+) {
+ let analysis_function = Function::from_raw(analysis_function);
+ match function.is_null() {
+ false => {
+ // Set the matched function to `function` and return previous.
+ let matched_function = ManuallyDrop::new(Arc::from_raw(function));
+ insert_cached_function_match(
+ &analysis_function,
+ Some(matched_function.as_ref().clone()),
+ )
+ }
+ true => {
+ // We are removing the previous match and returning it.
+ insert_cached_function_match(&analysis_function, None)
+ }
+ };
+}
+
+#[no_mangle]
+pub unsafe extern "C" fn BNWARPFunctionGetGUID(
+ function: *mut BNWARPFunction,
+) -> BNWARPFunctionGUID {
+ // We do not own function so we should not drop.
+ let function = ManuallyDrop::new(Arc::from_raw(function));
+ function.guid
+}
+
+#[no_mangle]
+pub unsafe extern "C" fn BNWARPFunctionGetSymbol(
+ function: *mut BNWARPFunction,
+ analysis_function: *mut BNFunction,
+) -> *mut BNSymbol {
+ let analysis_function = Function::from_raw(analysis_function);
+ // We do not own function so we should not drop.
+ let function = ManuallyDrop::new(Arc::from_raw(function));
+ let view = analysis_function.view();
+ let address = analysis_function.symbol().address();
+ let function_symbol = to_bn_symbol_at_address(&view, &function.symbol, address);
+ // NOTE: The symbol ref has been pre-incremented for the caller.
+ Ref::into_raw(function_symbol).handle
+}
+
+#[no_mangle]
+pub unsafe extern "C" fn BNWARPFunctionGetSymbolName(function: *mut BNWARPFunction) -> *mut c_char {
+ // We do not own function so we should not drop.
+ let function = ManuallyDrop::new(Arc::from_raw(function));
+ let bn_name = BnString::new(&function.symbol.name);
+ // NOTE: The symbol name string to be freed by BNFreeString
+ BnString::into_raw(bn_name)
+}
+
+#[no_mangle]
+pub unsafe extern "C" fn BNWARPFunctionGetType(
+ function: *mut BNWARPFunction,
+ analysis_function: *mut BNFunction,
+) -> *mut BNType {
+ let analysis_function = Function::from_raw(analysis_function);
+ // We do not own function so we should not drop.
+ let function = ManuallyDrop::new(Arc::from_raw(function));
+ match &function.ty {
+ Some(func_ty) => {
+ let arch = analysis_function.arch();
+ let function_type = to_bn_type(&arch, func_ty);
+ // NOTE: The type ref has been pre-incremented for the caller.
+ unsafe { Ref::into_raw(function_type) }.handle
+ }
+ None => std::ptr::null_mut(),
+ }
+}
+
+#[no_mangle]
+pub unsafe extern "C" fn BNWARPFunctionGetConstraints(
+ function: *mut BNWARPFunction,
+ count: *mut usize,
+) -> *mut BNWARPConstraint {
+ // We do not own function so we should not drop.
+ let function = ManuallyDrop::new(Arc::from_raw(function));
+ let raw_constraints: Box<[BNWARPConstraint]> = function
+ .constraints
+ .clone()
+ .into_iter()
+ .map(Into::into)
+ .collect();
+ *count = raw_constraints.len();
+ let raw_constraints_ptr = Box::into_raw(raw_constraints);
+ raw_constraints_ptr as *mut BNWARPConstraint
+}
+
+#[no_mangle]
+pub unsafe extern "C" fn BNWARPFunctionGetComments(
+ function: *mut BNWARPFunction,
+ count: *mut usize,
+) -> *mut BNWarpFunctionComment {
+ // We do not own function so we should not drop.
+ let function = ManuallyDrop::new(Arc::from_raw(function));
+ let raw_comments: Box<[_]> = function
+ .comments
+ .iter()
+ .map(BNWarpFunctionComment::from_owned)
+ .collect();
+ *count = raw_comments.len();
+ let raw_comments_ptr = Box::into_raw(raw_comments);
+ raw_comments_ptr as *mut BNWarpFunctionComment
+}
+
+#[no_mangle]
+pub unsafe extern "C" fn BNWARPFunctionsEqual(
+ function_a: *mut BNWARPFunction,
+ function_b: *mut BNWARPFunction,
+) -> bool {
+ // We do not own function so we should not drop.
+ let function_a = ManuallyDrop::new(Arc::from_raw(function_a));
+ // We do not own function so we should not drop.
+ let function_b = ManuallyDrop::new(Arc::from_raw(function_b));
+ function_a.eq(&function_b)
+}
+
+#[no_mangle]
+pub unsafe extern "C" fn BNWARPNewFunctionReference(
+ function: *mut BNWARPFunction,
+) -> *mut BNWARPFunction {
+ Arc::increment_strong_count(function);
+ function
+}
+
+#[no_mangle]
+pub unsafe extern "C" fn BNWARPFreeFunctionReference(function: *mut BNWARPFunction) {
+ if function.is_null() {
+ return;
+ }
+ Arc::decrement_strong_count(function);
+}
+
+#[no_mangle]
+pub unsafe extern "C" fn BNWARPFreeFunctionList(functions: *mut *mut BNWARPFunction, count: usize) {
+ let functions_ptr = std::ptr::slice_from_raw_parts_mut(functions, count);
+ let functions = Box::from_raw(functions_ptr);
+ for function in functions {
+ // NOTE: The functions themselves should also be arc.
+ BNWARPFreeFunctionReference(function);
+ }
+}
+
+#[no_mangle]
+pub unsafe extern "C" fn BNWARPFreeFunctionCommentList(
+ comments: *mut BNWarpFunctionComment,
+ count: usize,
+) {
+ let comments_ptr = std::ptr::slice_from_raw_parts_mut(comments, count);
+ let comments = Box::from_raw(comments_ptr);
+ for comment in &comments {
+ BNWarpFunctionComment::free_raw(comment)
+ }
+}
+
+#[no_mangle]
+pub unsafe extern "C" fn BNWARPFreeConstraintList(
+ constraints: *mut BNWARPConstraint,
+ count: usize,
+) {
+ let constraints_ptr = std::ptr::slice_from_raw_parts_mut(constraints, count);
+ let _constraints = unsafe { Box::from_raw(constraints_ptr) };
+}
diff --git a/plugins/warp/src/plugin/file.rs b/plugins/warp/src/plugin/file.rs
new file mode 100644
index 00000000..ec61bd78
--- /dev/null
+++ b/plugins/warp/src/plugin/file.rs
@@ -0,0 +1,41 @@
+use crate::report::ReportGenerator;
+use binaryninja::binary_view::{BinaryView, BinaryViewExt};
+use binaryninja::command::Command;
+
+pub struct ShowFileReport;
+
+impl Command for ShowFileReport {
+ fn action(&self, view: &BinaryView) {
+ let view = view.to_owned();
+ std::thread::spawn(move || {
+ let Some(path) =
+ binaryninja::interaction::get_open_filename_input("Select file to show", "*.warp")
+ else {
+ return;
+ };
+
+ let Ok(bytes) = std::fs::read(&path) else {
+ log::error!("Failed to read file: {:?}", path);
+ return;
+ };
+
+ let Some(file) = warp::WarpFile::from_bytes(&bytes) else {
+ log::error!("Failed to parse file: {:?}", path);
+ return;
+ };
+
+ let report_generator = ReportGenerator::new();
+ if let Some(html_string) = report_generator.html_report(&file) {
+ view.show_html_report(
+ &format!("WARP File: {}", path.to_string_lossy()),
+ html_string.as_str(),
+ "",
+ );
+ }
+ });
+ }
+
+ fn valid(&self, _view: &BinaryView) -> bool {
+ true
+ }
+}
diff --git a/plugins/warp/src/plugin/find.rs b/plugins/warp/src/plugin/find.rs
deleted file mode 100644
index accc015d..00000000
--- a/plugins/warp/src/plugin/find.rs
+++ /dev/null
@@ -1,57 +0,0 @@
-use crate::cache::try_cached_function_guid;
-use binaryninja::binary_view::{BinaryView, BinaryViewExt};
-use binaryninja::command::Command;
-use binaryninja::function::Function as BNFunction;
-use binaryninja::rc::Guard as BNGuard;
-use rayon::prelude::*;
-use std::thread;
-use warp::signature::function::FunctionGUID;
-
-pub struct FindFunctionFromGUID;
-
-impl Command for FindFunctionFromGUID {
- fn action(&self, view: &BinaryView) {
- let Some(guid_str) = binaryninja::interaction::get_text_line_input(
- "Function GUID",
- "Find Function from GUID",
- ) else {
- return;
- };
-
- let Ok(searched_guid) = guid_str.parse::<FunctionGUID>() else {
- log::error!("Failed to parse function guid... {}", guid_str);
- return;
- };
-
- log::info!("Searching functions for GUID... {}", searched_guid);
- let funcs = view.functions();
- thread::spawn(move || {
- let background_task = binaryninja::background_task::BackgroundTask::new(
- &format!("Searching functions for GUID... {}", searched_guid),
- false,
- );
-
- // Only run this for functions which have already generated a GUID.
- let matched = funcs
- .par_iter()
- .filter(|func| {
- try_cached_function_guid(func).is_some_and(|guid| guid == searched_guid)
- })
- .collect::<Vec<BNGuard<BNFunction>>>();
-
- if matched.is_empty() {
- log::info!("No matches found for GUID... {}", searched_guid);
- } else {
- for func in matched {
- log::info!("Match found at function... 0x{:0x}", func.start());
- }
- }
-
- background_task.finish();
- });
- }
-
- fn valid(&self, _view: &BinaryView) -> bool {
- true
- }
-}
diff --git a/plugins/warp/src/plugin/function.rs b/plugins/warp/src/plugin/function.rs
new file mode 100644
index 00000000..130e8582
--- /dev/null
+++ b/plugins/warp/src/plugin/function.rs
@@ -0,0 +1,133 @@
+use crate::cache::{cached_function_guid, try_cached_function_guid};
+use crate::{get_warp_include_tag_type, INCLUDE_TAG_NAME};
+use binaryninja::background_task::BackgroundTask;
+use binaryninja::binary_view::{BinaryView, BinaryViewExt};
+use binaryninja::command::{Command, FunctionCommand};
+use binaryninja::function::Function;
+use binaryninja::rc::Guard;
+use rayon::iter::ParallelIterator;
+use std::thread;
+use warp::signature::function::FunctionGUID;
+
+pub struct IncludeFunction;
+
+impl FunctionCommand for IncludeFunction {
+ fn action(&self, view: &BinaryView, func: &Function) {
+ let sym_name = func.symbol().short_name();
+ let sym_name_str = sym_name.to_string_lossy();
+ let should_add_tag = func.function_tags(None, Some(INCLUDE_TAG_NAME)).is_empty();
+ let insert_tag_type = get_warp_include_tag_type(view);
+ match should_add_tag {
+ true => {
+ log::info!(
+ "Including selected function '{}' at 0x{:x}",
+ sym_name_str,
+ func.start()
+ );
+ func.add_tag(&insert_tag_type, "", None, false, None);
+ }
+ false => {
+ log::info!(
+ "Removing included function '{}' at 0x{:x}",
+ sym_name_str,
+ func.start()
+ );
+ func.remove_tags_of_type(&insert_tag_type, None, false, None);
+ }
+ }
+ }
+
+ fn valid(&self, _view: &BinaryView, _func: &Function) -> bool {
+ // TODO: Only allow if the function is named?
+ true
+ }
+}
+
+pub struct CopyFunctionGUID;
+
+impl FunctionCommand for CopyFunctionGUID {
+ fn action(&self, _view: &BinaryView, func: &Function) {
+ let Ok(lifted_il) = func.lifted_il() else {
+ log::error!("Could not get lifted il for copied function");
+ return;
+ };
+ let guid = cached_function_guid(func, &lifted_il);
+ log::info!(
+ "Function GUID for {:?}... {}",
+ func.symbol().short_name(),
+ guid
+ );
+ if let Ok(mut clipboard) = arboard::Clipboard::new() {
+ let _ = clipboard.set_text(guid.to_string());
+ }
+ }
+
+ fn valid(&self, _view: &BinaryView, _func: &Function) -> bool {
+ true
+ }
+}
+
+pub struct FindFunctionFromGUID;
+
+impl Command for FindFunctionFromGUID {
+ fn action(&self, view: &BinaryView) {
+ let Some(guid_str) = binaryninja::interaction::get_text_line_input(
+ "Function GUID",
+ "Find Function from GUID",
+ ) else {
+ return;
+ };
+
+ let Ok(searched_guid) = guid_str.parse::<FunctionGUID>() else {
+ log::error!("Failed to parse function guid... {}", guid_str);
+ return;
+ };
+
+ log::info!("Searching functions for GUID... {}", searched_guid);
+ let funcs = view.functions();
+ let view = view.to_owned();
+ thread::spawn(move || {
+ let background_task = BackgroundTask::new(
+ &format!("Searching functions for GUID... {}", searched_guid),
+ false,
+ );
+
+ // Only run this for functions which have already generated a GUID.
+ let matched: Vec<Guard<Function>> = funcs
+ .par_iter()
+ .filter(|func| {
+ try_cached_function_guid(func).is_some_and(|guid| guid == searched_guid)
+ })
+ .collect();
+
+ if matched.is_empty() {
+ log::info!("No matches found for GUID... {}", searched_guid);
+ } else {
+ for func in &matched {
+ // Also navigate the user, as that is probably what they want.
+ if matched.len() == 1 {
+ let current_view = view.file().current_view();
+ if view
+ .file()
+ .navigate_to(&current_view, func.start())
+ .is_err()
+ {
+ log::error!(
+ "Failed to navigate to found function 0x{:0x} in view {}",
+ func.start(),
+ current_view
+ );
+ }
+ }
+ log::info!("Match found at function... 0x{:0x}", func.start());
+ }
+ }
+
+ background_task.finish();
+ });
+ }
+
+ fn valid(&self, _view: &BinaryView) -> bool {
+ true
+ }
+}
diff --git a/plugins/warp/src/plugin/load.rs b/plugins/warp/src/plugin/load.rs
index c86a16cb..c0319ea5 100644
--- a/plugins/warp/src/plugin/load.rs
+++ b/plugins/warp/src/plugin/load.rs
@@ -1,54 +1,162 @@
-use crate::matcher::{Matcher, PlatformID, PLAT_MATCHER_CACHE};
+use crate::cache::container::add_cached_container;
+use crate::container::disk::{DiskContainer, DiskContainerSource};
+use crate::container::{ContainerError, SourcePath};
+use crate::convert::platform_to_target;
+use crate::plugin::workflow::run_matcher;
use binaryninja::binary_view::{BinaryView, BinaryViewExt};
use binaryninja::command::Command;
+use binaryninja::interaction::{
+ show_message_box, Form, FormInputField, MessageBoxButtonResult, MessageBoxButtonSet,
+ MessageBoxIcon,
+};
+use binaryninja::rc::Ref;
+use std::collections::HashMap;
+use std::path::PathBuf;
+use std::thread;
+use warp::WarpFile;
+
+pub struct LoadFileField;
+
+impl LoadFileField {
+ pub fn field() -> FormInputField {
+ FormInputField::OpenFileName {
+ prompt: "File Path".to_string(),
+ // TODO: This is called extension but is really a filter.
+ extension: Some("*.warp".to_string()),
+ default: None,
+ value: None,
+ }
+ }
+
+ pub fn from_form(form: &Form) -> Option<PathBuf> {
+ let field = form.get_field_with_name("File Path")?;
+ let field_value = field.try_value_string()?;
+ Some(PathBuf::from(field_value))
+ }
+}
+
+pub struct RunMatcherField;
+
+impl RunMatcherField {
+ pub fn field() -> FormInputField {
+ FormInputField::Choice {
+ prompt: "Rerun Initial Matcher".to_string(),
+ choices: vec!["No".to_string(), "Yes".to_string()],
+ default: Some(1),
+ value: 0,
+ }
+ }
+
+ pub fn from_form(form: &Form) -> Option<bool> {
+ let field = form.get_field_with_name("Rerun Initial Matcher")?;
+ let field_value = field.try_value_index()?;
+ match field_value {
+ 1 => Some(true),
+ _ => Some(false),
+ }
+ }
+}
+
pub struct LoadSignatureFile;
-impl Command for LoadSignatureFile {
- fn action(&self, view: &BinaryView) {
- let Some(platform) = view.default_platform() else {
- log::error!("Default platform must be set to load signature!");
- return;
- };
+impl LoadSignatureFile {
+ pub fn read_file(
+ view: &BinaryView,
+ path: SourcePath,
+ ) -> Result<WarpFile<'static>, ContainerError> {
+ let contents = std::fs::read(&path).map_err(|e| ContainerError::FailedIO(e.kind()))?;
+ let mut file = WarpFile::from_owned_bytes(contents).ok_or(
+ ContainerError::CorruptedData("file data failed to validate"),
+ )?;
- // NOTE: Because we only can consume signatures from a specific directory, we don't need to use the interaction API.
- // If we did need to load signature files from a project than this would need to change.
- let Some(file) = rfd::FileDialog::new()
- .add_filter("Signature Files", &["sbin"])
- .set_file_name(format!("{}.sbin", view.file().filename()))
- .pick_file()
- else {
- return;
- };
+ let view_target = view
+ .default_platform()
+ .map(|p| platform_to_target(&p))
+ .unwrap_or_default();
+ let file_has_target = file
+ .chunks
+ .iter()
+ .find(|c| c.header.target == view_target)
+ .is_some();
+
+ if !file_has_target {
+ // File does not contain a view target, alert user if they would like to override the file chunks to the view target.
+ let text = format!(
+ "Attempting to load WARP file with no target `{:?}`, continue loading anyways?",
+ &view_target
+ );
+ let res = show_message_box(
+ "Override file target?",
+ &text,
+ MessageBoxButtonSet::YesNoButtonSet,
+ MessageBoxIcon::WarningIcon,
+ );
+ if res != MessageBoxButtonResult::YesButton {
+ return Err(ContainerError::CorruptedData(
+ "User does not want to load file",
+ ));
+ }
+
+ // Take all the chunks and convert them to the target, so we load them.
+ // If we do not do this, the user will be surprised when they get no new matches.
+ for chunk in &mut file.chunks {
+ chunk.header.target = view_target.clone();
+ }
+ }
- let Ok(data) = std::fs::read(&file) else {
- log::error!("Could not read signature file: {:?}", file);
+ Ok(file)
+ }
+
+ pub fn execute(view: Ref<BinaryView>) {
+ let mut form = Form::new("Load Signature File");
+ form.add_field(LoadFileField::field());
+ // let fd_field = FileDataKindField::default();
+ // form.add_field(fd_field.to_field());
+ form.add_field(RunMatcherField::field());
+ if !form.prompt() {
return;
- };
+ }
- let Some(data) = warp::signature::Data::from_bytes(&data) else {
- log::error!("Could not get data from signature file: {:?}", file);
+ let Some(file_path) = LoadFileField::from_form(&form) else {
return;
};
+ // TODO: Decide what to pull using the file data kind.
+ // let _file_data_kind = FileDataKindField::from_form(&form).unwrap_or_default();
+ let rerun_matcher = RunMatcherField::from_form(&form).unwrap_or(false);
+
+ let source_file_path = SourcePath::new(file_path.clone());
- let new_matcher = Matcher::from_data(data);
- log::info!(
- "Loading signature file with {} functions and {} types...",
- new_matcher.functions.len(),
- new_matcher.types.len()
- );
- let platform_id = PlatformID::from(platform.as_ref());
- let matcher_cache = PLAT_MATCHER_CACHE.get_or_init(Default::default);
- match matcher_cache.get_mut(&platform_id) {
- Some(mut matcher) => matcher.extend_with_matcher(new_matcher),
- None => {
- // We still must uphold `from_platform` in case we are running this before the matcher workflow
- // is kicked off. Other-wise we only will have the `new_matcher` data.
- let mut matcher = Matcher::from_platform(platform);
- matcher.extend_with_matcher(new_matcher);
- matcher_cache.insert(platform_id, matcher);
+ let file = match LoadSignatureFile::read_file(&view, source_file_path.clone()) {
+ Ok(file) => file,
+ Err(e) => {
+ log::error!("Failed to read signature file: {}", e);
+ return;
}
+ };
+
+ let container_source = DiskContainerSource::new(source_file_path.clone(), file);
+ log::info!("Loading container source: '{}'", container_source.path);
+ let mut map = HashMap::new();
+ map.insert(source_file_path.to_source_id(), container_source);
+ let container = DiskContainer::new("Loaded signatures".to_string(), map);
+ // TODO: See notes in the matcher about doing this, we really need to load it into an existing container.
+ add_cached_container(container);
+
+ if rerun_matcher {
+ thread::spawn(move || {
+ run_matcher(&view);
+ });
}
}
+}
+
+impl Command for LoadSignatureFile {
+ fn action(&self, view: &BinaryView) {
+ let view = view.to_owned();
+ thread::spawn(move || {
+ LoadSignatureFile::execute(view);
+ });
+ }
fn valid(&self, _view: &BinaryView) -> bool {
true
diff --git a/plugins/warp/src/plugin/project.rs b/plugins/warp/src/plugin/project.rs
new file mode 100644
index 00000000..ea5a5600
--- /dev/null
+++ b/plugins/warp/src/plugin/project.rs
@@ -0,0 +1,132 @@
+use binaryninja::background_task::BackgroundTask;
+use binaryninja::command::ProjectCommand;
+use binaryninja::interaction::{Form, FormInputField};
+use binaryninja::project::Project;
+use binaryninja::rc::Ref;
+use regex::Regex;
+use std::thread;
+use std::time::Instant;
+
+use crate::processor::{
+ new_processing_state_background_thread, FileDataKindField, FileFilterField, WarpFileProcessor,
+};
+use crate::report::{ReportGenerator, ReportKindField};
+
+pub struct CreateSignaturesForm {
+ form: Form,
+}
+
+impl CreateSignaturesForm {
+ pub fn new(_project: &Project) -> CreateSignaturesForm {
+ let mut form = Form::new("Create Signature File");
+ form.add_field(Self::file_data_field());
+ form.add_field(Self::file_filter_field());
+ form.add_field(Self::generated_report_field());
+ // TODO: Threads (we run the analysis in the background)
+ Self { form }
+ }
+
+ pub fn file_data_field() -> FormInputField {
+ FileDataKindField::default().to_field()
+ }
+
+ pub fn file_data_kind(&self) -> FileDataKindField {
+ FileDataKindField::from_form(&self.form).unwrap_or_default()
+ }
+
+ pub fn file_filter_field() -> FormInputField {
+ FileFilterField::to_field()
+ }
+
+ pub fn file_filter(&self) -> Option<Regex> {
+ FileFilterField::from_form(&self.form)
+ }
+
+ pub fn generated_report_field() -> FormInputField {
+ ReportKindField::default().to_field()
+ }
+
+ pub fn generated_report_kind(&self) -> ReportKindField {
+ ReportKindField::from_form(&self.form).unwrap_or_default()
+ }
+
+ pub fn prompt(&mut self) -> bool {
+ self.form.prompt()
+ }
+}
+
+pub struct CreateSignatures;
+
+impl CreateSignatures {
+ pub fn execute(project: Ref<Project>) {
+ let mut form = CreateSignaturesForm::new(&project);
+ if !form.prompt() {
+ return;
+ }
+ let file_data_kind = form.file_data_kind();
+ let report_kind = form.generated_report_kind();
+
+ let mut processor = WarpFileProcessor::new().with_file_data(file_data_kind);
+
+ // This thread will show the state in a background task.
+ let background_task = BackgroundTask::new("Processing started...", true);
+ new_processing_state_background_thread(background_task.clone(), processor.state());
+
+ if let Some(filter) = form.file_filter() {
+ processor = processor.with_file_filter(filter);
+ }
+
+ let start = Instant::now();
+ match processor.process_project(&project) {
+ Ok(warp_file) => {
+ // Print the processor string into the description of the file, so we know how it was generated.
+ let processor_str = format!("{:#?}", &processor);
+
+ // TODO: File name needs to be configurable.
+ if project
+ .create_file(
+ &warp_file.to_bytes(),
+ None,
+ "generated.warp",
+ &processor_str,
+ )
+ .is_err()
+ {
+ log::error!("Failed to create project file!");
+ }
+
+ let report = ReportGenerator::new();
+ if let Some(generated) = report.report(&report_kind, &warp_file) {
+ let ext = report.report_extension(&report_kind).unwrap_or_default();
+ let file_name = format!("report.{}", ext);
+ if project
+ .create_file(&generated.into_bytes(), None, &file_name, "Warp file")
+ .is_err()
+ {
+ log::error!("Failed to create project file!");
+ }
+ }
+ }
+ Err(e) => {
+ log::error!("Failed to process project: {}", e);
+ }
+ }
+ log::info!("Processing project files took: {:?}", start.elapsed());
+
+ // Tells the processing state thread to finish.
+ background_task.finish();
+ }
+}
+
+impl ProjectCommand for CreateSignatures {
+ fn action(&self, project: &Project) {
+ let project = project.to_owned();
+ thread::spawn(move || {
+ CreateSignatures::execute(project);
+ });
+ }
+
+ fn valid(&self, _view: &Project) -> bool {
+ true
+ }
+}
diff --git a/plugins/warp/src/plugin/render_layer.rs b/plugins/warp/src/plugin/render_layer.rs
index cdac71f2..d7984303 100644
--- a/plugins/warp/src/plugin/render_layer.rs
+++ b/plugins/warp/src/plugin/render_layer.rs
@@ -1,56 +1,94 @@
-use crate::{is_blacklisted_instruction, is_variant_instruction, relocatable_regions};
+use crate::{
+ is_blacklisted_instruction, is_computed_variant_instruction, is_variant_instruction,
+ relocatable_regions,
+};
use binaryninja::basic_block::BasicBlock;
use binaryninja::disassembly::DisassemblyTextLine;
+use binaryninja::flowgraph::FlowGraph;
use binaryninja::function::{HighlightColor, HighlightStandardColor, NativeBlock};
-use binaryninja::low_level_il::instruction::LowLevelInstructionIndex;
+use binaryninja::low_level_il::LowLevelILRegularFunction;
use binaryninja::render_layer::{register_render_layer, RenderLayer};
-pub struct HighlightRenderLayer {}
+// TODO: Add a render layer to show basic block GUID's?
+// TODO: Add a render layer to show constraints for current function?
+
+pub struct HighlightRenderLayer {
+ blacklist: HighlightColor,
+ variant: HighlightColor,
+ computed_variant: HighlightColor,
+}
impl HighlightRenderLayer {
pub fn register() {
register_render_layer(
"WARP Highlight Layer",
- HighlightRenderLayer {},
+ // TODO: Make the highlight colors configurable.
+ HighlightRenderLayer {
+ blacklist: HighlightColor::StandardHighlightColor {
+ color: HighlightStandardColor::BlackHighlightColor,
+ alpha: 155,
+ },
+ variant: HighlightColor::StandardHighlightColor {
+ color: HighlightStandardColor::RedHighlightColor,
+ alpha: 155,
+ },
+ computed_variant: HighlightColor::StandardHighlightColor {
+ color: HighlightStandardColor::OrangeHighlightColor,
+ alpha: 155,
+ },
+ },
Default::default(),
);
}
+
+ /// Highlights the lines that are variant or blacklisted.
+ pub fn highlight_lines(
+ &self,
+ lifted_il: &LowLevelILRegularFunction,
+ llil: &LowLevelILRegularFunction,
+ lines: &mut [DisassemblyTextLine],
+ ) {
+ let relocatable_regions = relocatable_regions(&lifted_il.function().view());
+ for line in lines {
+ // We use address here instead of index since it's more reliable for other IL's.
+ if let Some(lifted_il_instr) = lifted_il.instruction_at(line.address) {
+ if is_blacklisted_instruction(&lifted_il_instr) {
+ line.highlight = self.blacklist;
+ } else if is_variant_instruction(&relocatable_regions, &lifted_il_instr) {
+ line.highlight = self.variant;
+ }
+ }
+
+ if let Some(llil_instr) = llil.instruction_at(line.address) {
+ if is_computed_variant_instruction(&relocatable_regions, &llil_instr) {
+ line.highlight = self.computed_variant;
+ }
+ }
+ }
+ }
}
impl RenderLayer for HighlightRenderLayer {
- fn apply_to_llil_block(
+ fn apply_to_flow_graph(&self, graph: &mut FlowGraph) {
+ if let (Some(lifted_il), Some(llil)) = (graph.lifted_il(), graph.low_level_il()) {
+ for node in &graph.nodes() {
+ let mut new_lines = node.lines().to_vec();
+ self.highlight_lines(&lifted_il, &llil, &mut new_lines);
+ node.set_lines(new_lines);
+ }
+ }
+ }
+
+ fn apply_to_block(
&self,
block: &BasicBlock<NativeBlock>,
mut lines: Vec<DisassemblyTextLine>,
) -> Vec<DisassemblyTextLine> {
- // Highlight any LLIL instruction that will be masked by WARP.
+ // Highlight any instruction that WARP will mask.
let function = block.function();
- // TODO: We might need to make relocatable regions configurable.
- let relocatable_regions = relocatable_regions(&function.view());
- let Ok(llil) = function.low_level_il() else {
- // Don't even think this is possible but _shrug_.
- return lines;
- };
-
- for line in &mut lines {
- let llil_instr_idx = LowLevelInstructionIndex(line.instruction_index);
- if let Some(llil_instr) = llil.instruction_from_index(llil_instr_idx) {
- if is_blacklisted_instruction(&llil_instr) {
- // We have a blacklisted instruction, highlight it as orange!
- line.highlight = HighlightColor::StandardHighlightColor {
- color: HighlightStandardColor::OrangeHighlightColor,
- alpha: 155,
- };
- } else if is_variant_instruction(&relocatable_regions, &llil_instr) {
- // We have a variant instruction, highlight it as red!
- line.highlight = HighlightColor::StandardHighlightColor {
- color: HighlightStandardColor::RedHighlightColor,
- alpha: 155,
- };
- }
- }
+ if let (Ok(lifted_il), Ok(llil)) = (function.lifted_il(), function.low_level_il()) {
+ self.highlight_lines(&lifted_il, &llil, &mut lines);
}
-
lines
}
}
diff --git a/plugins/warp/src/plugin/settings.rs b/plugins/warp/src/plugin/settings.rs
new file mode 100644
index 00000000..6cd2e0cc
--- /dev/null
+++ b/plugins/warp/src/plugin/settings.rs
@@ -0,0 +1,129 @@
+use binaryninja::settings::Settings as BNSettings;
+use serde_json::json;
+use std::string::ToString;
+
+#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
+pub struct PluginSettings {
+ /// Whether to load bundled WARP files on startup. Turn this off if you want to manually load them.
+ ///
+ /// This is set to [PluginSettings::LOAD_BUNDLED_FILES_DEFAULT] by default.
+ pub load_bundled_files: bool,
+ /// Whether to load user WARP files on startup. Turn this off if you want to manually load them.
+ ///
+ /// This is set to [PluginSettings::LOAD_USER_FILES_DEFAULT] by default.
+ pub load_user_files: bool,
+ /// The WARP server to use.
+ ///
+ /// This is set to [PluginSettings::SERVER_URL_DEFAULT] by default.
+ pub server_url: String,
+ /// The API key to use for the selected WARP server, if not specified, you will be unable to push data and may be rate-limited.
+ ///
+ /// This is set to [PluginSettings::SERVER_API_KEY_DEFAULT] by default.
+ pub server_api_key: String,
+ /// Whether to allow networked WARP requests. Turning this off will not disable local WARP functionality.
+ ///
+ /// This is set to [PluginSettings::ENABLE_SERVER_DEFAULT] by default.
+ pub enable_server: bool,
+}
+
+impl PluginSettings {
+ pub const LOAD_BUNDLED_FILES_DEFAULT: bool = true;
+ pub const LOAD_BUNDLED_FILES_SETTING: &'static str = "analysis.warp.loadBundledFiles";
+ pub const LOAD_USER_FILES_DEFAULT: bool = true;
+ pub const LOAD_USER_FILES_SETTING: &'static str = "analysis.warp.loadUserFiles";
+ pub const SERVER_URL_DEFAULT: &'static str = "https://warp.binary.ninja";
+ pub const SERVER_URL_SETTING: &'static str = "analysis.warp.serverUrl";
+ pub const SERVER_API_KEY_DEFAULT: &'static str = "";
+ pub const SERVER_API_KEY_SETTING: &'static str = "analysis.warp.serverApiKey";
+ pub const ENABLE_SERVER_DEFAULT: bool = true;
+ pub const ENABLE_SERVER_SETTING: &'static str = "network.enableWARP";
+
+ pub fn register(bn_settings: &mut BNSettings) {
+ let load_bundled_files_prop = json!({
+ "title" : "Load Bundled Files",
+ "type" : "boolean",
+ "default" : Self::LOAD_BUNDLED_FILES_DEFAULT,
+ "description" : "Whether to load bundled WARP files on startup. Turn this off if you want to manually load them.",
+ "ignore" : ["SettingsProjectScope", "SettingsResourceScope"]
+ });
+ bn_settings.register_setting_json(
+ Self::LOAD_BUNDLED_FILES_SETTING,
+ &load_bundled_files_prop.to_string(),
+ );
+ let load_user_files_prop = json!({
+ "title" : "Load User Files",
+ "type" : "boolean",
+ "default" : Self::LOAD_USER_FILES_DEFAULT,
+ "description" : "Whether to load user WARP files on startup. Turn this off if you want to manually load them.",
+ "ignore" : ["SettingsProjectScope", "SettingsResourceScope"]
+ });
+ bn_settings.register_setting_json(
+ Self::LOAD_USER_FILES_SETTING,
+ &load_user_files_prop.to_string(),
+ );
+ let server_url_prop = json!({
+ "title" : "Server URL",
+ "type" : "string",
+ "default" : Self::SERVER_URL_DEFAULT,
+ "description" : "The WARP server to use.",
+ "ignore" : ["SettingsProjectScope", "SettingsResourceScope"]
+ });
+ bn_settings.register_setting_json(Self::SERVER_URL_SETTING, &server_url_prop.to_string());
+ let server_api_key_prop = json!({
+ "title" : "Server API Key",
+ "type" : "string",
+ "default" : Self::SERVER_API_KEY_DEFAULT,
+ "description" : "The API key to use for the selected WARP server, if not specified you will be unable to push data, and may be rate limited.",
+ "ignore" : ["SettingsProjectScope", "SettingsResourceScope"],
+ "hidden": true
+ });
+ bn_settings.register_setting_json(
+ Self::SERVER_API_KEY_SETTING,
+ &server_api_key_prop.to_string(),
+ );
+ let server_enabled_prop = json!({
+ "title" : "Enable WARP",
+ "type" : "boolean",
+ "default" : Self::ENABLE_SERVER_DEFAULT,
+ "description" : "Whether or not to allow networked WARP requests. Turning this off will not disable local WARP functionality.",
+ "ignore" : ["SettingsProjectScope", "SettingsResourceScope"]
+ });
+ bn_settings.register_setting_json(
+ Self::ENABLE_SERVER_SETTING,
+ &server_enabled_prop.to_string(),
+ );
+ }
+
+ /// Retrieve plugin settings from [`BNSettings`].
+ pub fn from_settings(bn_settings: &BNSettings) -> Self {
+ let mut settings = PluginSettings::default();
+ if bn_settings.contains(Self::LOAD_BUNDLED_FILES_SETTING) {
+ settings.load_bundled_files = bn_settings.get_bool(Self::LOAD_BUNDLED_FILES_SETTING);
+ }
+ if bn_settings.contains(Self::LOAD_USER_FILES_SETTING) {
+ settings.load_user_files = bn_settings.get_bool(Self::LOAD_USER_FILES_SETTING);
+ }
+ if bn_settings.contains(Self::SERVER_URL_SETTING) {
+ settings.server_url = bn_settings.get_string(Self::SERVER_URL_SETTING);
+ }
+ if bn_settings.contains(Self::SERVER_API_KEY_SETTING) {
+ settings.server_url = bn_settings.get_string(Self::SERVER_API_KEY_SETTING);
+ }
+ if bn_settings.contains(Self::ENABLE_SERVER_SETTING) {
+ settings.enable_server = bn_settings.get_bool(Self::ENABLE_SERVER_SETTING);
+ }
+ settings
+ }
+}
+
+impl Default for PluginSettings {
+ fn default() -> Self {
+ Self {
+ load_bundled_files: PluginSettings::LOAD_BUNDLED_FILES_DEFAULT,
+ load_user_files: PluginSettings::LOAD_USER_FILES_DEFAULT,
+ server_url: PluginSettings::SERVER_URL_DEFAULT.to_string(),
+ server_api_key: PluginSettings::SERVER_API_KEY_DEFAULT.to_string(),
+ enable_server: PluginSettings::ENABLE_SERVER_DEFAULT,
+ }
+ }
+}
diff --git a/plugins/warp/src/plugin/types.rs b/plugins/warp/src/plugin/types.rs
deleted file mode 100644
index 41e03cd3..00000000
--- a/plugins/warp/src/plugin/types.rs
+++ /dev/null
@@ -1,57 +0,0 @@
-use crate::convert::to_bn_type;
-use binaryninja::binary_view::{BinaryView, BinaryViewExt};
-use binaryninja::command::Command;
-use std::time::Instant;
-
-pub struct LoadTypes;
-
-impl Command for LoadTypes {
- fn action(&self, view: &BinaryView) {
- // NOTE: Because we only can consume signatures from a specific directory, we don't need to use the interaction API.
- // If we did need to load signature files from a project than this would need to change.
- let Some(file) = rfd::FileDialog::new()
- .add_filter("Signature Files", &["sbin"])
- .set_file_name(format!("{}.sbin", view.file().filename()))
- .pick_file()
- else {
- return;
- };
-
- let Ok(data) = std::fs::read(&file) else {
- log::error!("Could not read signature file: {:?}", file);
- return;
- };
-
- let Some(data) = warp::signature::Data::from_bytes(&data) else {
- log::error!("Could not get data from signature file: {:?}", file);
- return;
- };
-
- let Some(arch) = view.default_arch() else {
- log::error!("Could not get default architecture");
- return;
- };
-
- let view = view.to_owned();
- std::thread::spawn(move || {
- let background_task = binaryninja::background_task::BackgroundTask::new(
- &format!("Applying {} types...", data.types.len()),
- true,
- );
-
- let start = Instant::now();
- for comp_ty in data.types {
- let ty_id = comp_ty.guid.to_string();
- let ty_name = comp_ty.ty.name.to_owned().unwrap_or_else(|| ty_id.clone());
- view.define_auto_type_with_id(ty_name, &ty_id, &to_bn_type(&arch, &comp_ty.ty));
- }
-
- log::info!("Type application took {:?}", start.elapsed());
- background_task.finish();
- });
- }
-
- fn valid(&self, _view: &BinaryView) -> bool {
- true
- }
-}
diff --git a/plugins/warp/src/plugin/workflow.rs b/plugins/warp/src/plugin/workflow.rs
index f5763116..487fccfe 100644
--- a/plugins/warp/src/plugin/workflow.rs
+++ b/plugins/warp/src/plugin/workflow.rs
@@ -1,16 +1,40 @@
-use crate::cache::cached_function_guid;
-use crate::matcher::cached_function_matcher;
+use crate::cache::container::for_cached_containers;
+use crate::cache::{
+ cached_function_guid, insert_cached_function_match, try_cached_function_guid,
+ try_cached_function_match,
+};
+use crate::convert::{
+ comment_to_bn_comment, platform_to_target, to_bn_symbol_at_address, to_bn_type,
+};
+use crate::matcher::{Matcher, MatcherSettings};
+use crate::{get_warp_tag_type, relocatable_regions};
use binaryninja::background_task::BackgroundTask;
use binaryninja::binary_view::{BinaryView, BinaryViewExt};
use binaryninja::command::Command;
+use binaryninja::settings::{QueryOptions, Settings};
use binaryninja::workflow::{Activity, AnalysisContext, Workflow};
+use itertools::Itertools;
+use std::collections::HashMap;
use std::time::Instant;
+use warp::signature::function::{Function, FunctionGUID};
+use warp::target::Target;
+
+pub const APPLY_ACTIVITY_NAME: &str = "analysis.warp.apply";
+const APPLY_ACTIVITY_CONFIG: &str = r#"{
+ "name": "analysis.warp.apply",
+ "title" : "WARP Apply Matched",
+ "description": "This analysis step applies WARP info to matched functions...",
+ "eligibility": {
+ "auto": {},
+ "runOnce": false
+ }
+}"#;
pub const MATCHER_ACTIVITY_NAME: &str = "analysis.warp.matcher";
const MATCHER_ACTIVITY_CONFIG: &str = r#"{
"name": "analysis.warp.matcher",
"title" : "WARP Matcher",
- "description": "This analysis step applies WARP info to matched functions...",
+ "description": "This analysis step attempts to find matching WARP functions after the initial analysis is complete...",
"eligibility": {
"auto": {},
"runOnce": true
@@ -24,7 +48,7 @@ const GUID_ACTIVITY_CONFIG: &str = r#"{
"description": "This analysis step generates the GUID for all analyzed functions...",
"eligibility": {
"auto": {},
- "runOnce": true
+ "runOnce": false
}
}"#;
@@ -33,19 +57,8 @@ pub struct RunMatcher;
impl Command for RunMatcher {
fn action(&self, view: &BinaryView) {
let view = view.to_owned();
- // TODO: Check to see if the GUID cache is empty and ask the user if they want to regenerate the guids.
std::thread::spawn(move || {
- let undo_id = view.file().begin_undo_actions(true);
- let background_task = BackgroundTask::new("Matching on functions...", false);
- let start = Instant::now();
- view.functions()
- .iter()
- .for_each(|function| cached_function_matcher(&function));
- log::info!("Function matching took {:?}", start.elapsed());
- background_task.finish();
- view.file().commit_undo_actions(&undo_id);
- // Now we want to trigger re-analysis.
- view.update_analysis();
+ run_matcher(&view);
});
}
@@ -54,44 +67,157 @@ impl Command for RunMatcher {
}
}
+pub fn run_matcher(view: &BinaryView) {
+ // Alert the user if we have no actual regions (one comes from the synthetic section).
+ let regions = relocatable_regions(view);
+ if regions.len() <= 1 {
+ log::warn!(
+ "No relocatable regions found, for best results please define sections for the binary!"
+ );
+ }
+
+ // Then we want to actually find matching functions.
+ let background_task = BackgroundTask::new("Matching on WARP functions...", true);
+ let start = Instant::now();
+
+ // Build matcher
+ let view_settings = Settings::new();
+ let mut query_opts = QueryOptions::new_with_view(view);
+ let matcher_settings = MatcherSettings::from_settings(&view_settings, &mut query_opts);
+ let matcher = Matcher::new(matcher_settings);
+
+ // TODO: Par iter this? Using dashmap
+ let functions_by_target_and_guid: HashMap<(FunctionGUID, Target), Vec<_>> = view
+ .functions()
+ .iter()
+ .filter_map(|f| {
+ let guid = try_cached_function_guid(&f)?;
+ let target = platform_to_target(&f.platform());
+ Some(((guid, target), f.to_owned()))
+ })
+ .into_group_map();
+
+ // TODO: Par iter this? Using dashmap
+ let guids_by_target: HashMap<Target, Vec<FunctionGUID>> = functions_by_target_and_guid
+ .keys()
+ .map(|(guid, target)| (target.clone(), *guid))
+ .into_group_map();
+
+ // TODO: Target gets cloned a lot.
+ // TODO: Containers might both match on the same function. What should we do?
+ for_cached_containers(|container| {
+ if background_task.is_cancelled() {
+ return;
+ }
+
+ for (target, guids) in &guids_by_target {
+ let function_guid_with_sources = container
+ .sources_with_function_guids(target, guids)
+ .unwrap_or_default();
+
+ for (guid, sources) in &function_guid_with_sources {
+ let matched_functions: Vec<Function> = sources
+ .iter()
+ .flat_map(|source| {
+ container
+ .functions_with_guid(target, source, guid)
+ .unwrap_or_default()
+ })
+ .collect();
+
+ let functions = functions_by_target_and_guid
+ .get(&(*guid, target.clone()))
+ .expect("Function guid not found");
+
+ for function in functions {
+ // Match on all the possible functions
+ if let Some(matched_function) =
+ matcher.match_function_from_constraints(function, &matched_functions)
+ {
+ // We were able to find a match, add it to the match cache and then mark the function
+ // as requiring updates; this is so that we know about it in the applier activity.
+ insert_cached_function_match(function, Some(matched_function.clone()));
+ }
+ }
+ }
+ }
+ });
+
+ if background_task.is_cancelled() {
+ log::info!("Matcher was cancelled by user, you may run it again by running the 'Run Matcher' command.");
+ }
+
+ log::info!("Function matching took {:?}", start.elapsed());
+ background_task.finish();
+
+ // Now we want to trigger re-analysis.
+ view.update_analysis();
+}
+
pub fn insert_workflow() {
+ // "Hey look, it's a plier" ~ Josh 2025
+ let apply_activity = |ctx: &AnalysisContext| {
+ let view = ctx.view();
+ let function = ctx.function();
+ if let Some(matched_function) = try_cached_function_match(&function) {
+ view.define_auto_symbol(&to_bn_symbol_at_address(
+ &view,
+ &matched_function.symbol,
+ function.symbol().address(),
+ ));
+ if let Some(func_ty) = &matched_function.ty {
+ function.set_auto_type(&to_bn_type(&function.arch(), func_ty));
+ }
+ // TODO: How to clear the comments? They are just persisted.
+ // TODO: Also they generate an undo action, i hate implicit undo actions so much.
+ for comment in matched_function.comments {
+ let bn_comment = comment_to_bn_comment(&function, comment);
+ function.set_comment_at(bn_comment.addr, &bn_comment.comment);
+ }
+ function.add_tag(
+ &get_warp_tag_type(&view),
+ &matched_function.guid.to_string(),
+ None,
+ false,
+ None,
+ );
+ }
+ };
+
let matcher_activity = |ctx: &AnalysisContext| {
let view = ctx.view();
- let undo_id = view.file().begin_undo_actions(true);
- let background_task = BackgroundTask::new("Matching on functions...", false);
- let start = Instant::now();
- view.functions()
- .iter()
- .for_each(|function| cached_function_matcher(&function));
- log::info!("Function matching took {:?}", start.elapsed());
- background_task.finish();
- view.file().commit_undo_actions(&undo_id);
- // Now we want to trigger re-analysis.
- view.update_analysis();
+ run_matcher(&view);
};
let guid_activity = |ctx: &AnalysisContext| {
let function = ctx.function();
- // TODO: Returning RegularNonSSA means we cant modify the il (the lifting code was written just for lifted il, that needs to be fixed)
- if let Some(llil) = unsafe { ctx.llil_function() } {
- cached_function_guid(&function, &llil);
+ if let Some(lifted_il) = unsafe { ctx.lifted_il_function() } {
+ cached_function_guid(&function, &lifted_il);
}
};
let old_function_meta_workflow = Workflow::instance("core.function.metaAnalysis");
let function_meta_workflow = old_function_meta_workflow.clone_to("core.function.metaAnalysis");
let guid_activity = Activity::new_with_action(GUID_ACTIVITY_CONFIG, guid_activity);
+ let apply_activity = Activity::new_with_action(APPLY_ACTIVITY_CONFIG, apply_activity);
function_meta_workflow
.register_activity(&guid_activity)
.unwrap();
+ // Because we are going to impact analysis with application we must make sure the function update is triggered to continue to update analysis.
+ // TODO: need to ask why i cant do core.function.update like in the rtti plugin.
+ function_meta_workflow
+ .register_activity_with_subactivities::<Vec<String>>(&apply_activity, vec![])
+ .unwrap();
function_meta_workflow.insert("core.function.runFunctionRecognizers", [GUID_ACTIVITY_NAME]);
+ function_meta_workflow.insert("core.function.generateMediumLevelIL", [APPLY_ACTIVITY_NAME]);
function_meta_workflow.register().unwrap();
let old_module_meta_workflow = Workflow::instance("core.module.metaAnalysis");
let module_meta_workflow = old_module_meta_workflow.clone_to("core.module.metaAnalysis");
let matcher_activity = Activity::new_with_action(MATCHER_ACTIVITY_CONFIG, matcher_activity);
+ // Matcher activity must have core.module.update as subactivity otherwise analysis will sometimes never retrigger.
module_meta_workflow
- .register_activity(&matcher_activity)
+ .register_activity_with_subactivities(&matcher_activity, vec!["core.module.update"])
.unwrap();
module_meta_workflow.insert(
"core.module.deleteUnusedAutoFunctions",
diff --git a/plugins/warp/src/processor.rs b/plugins/warp/src/processor.rs
new file mode 100644
index 00000000..4b973753
--- /dev/null
+++ b/plugins/warp/src/processor.rs
@@ -0,0 +1,829 @@
+use std::collections::{HashMap, HashSet};
+use std::fs::File;
+use std::path::{Path, PathBuf};
+use std::sync::atomic::Ordering::Relaxed;
+use std::sync::atomic::{AtomicBool, AtomicUsize};
+use std::sync::Arc;
+use std::time::{Duration, Instant};
+
+use ar::Archive;
+use dashmap::DashMap;
+use rayon::iter::IntoParallelIterator;
+use rayon::iter::{IntoParallelRefIterator, ParallelIterator};
+use regex::Regex;
+use serde_json::{json, Value};
+use tempdir::TempDir;
+use thiserror::Error;
+use walkdir::WalkDir;
+
+use binaryninja::background_task::BackgroundTask;
+use binaryninja::binary_view::{BinaryView, BinaryViewExt};
+use binaryninja::function::Function as BNFunction;
+use binaryninja::interaction::{Form, FormInputField};
+use binaryninja::project::file::ProjectFile;
+use binaryninja::project::Project;
+use binaryninja::rc::{Guard, Ref};
+
+use warp::chunk::{Chunk, ChunkKind, CompressionType};
+use warp::r#type::chunk::TypeChunk;
+use warp::signature::chunk::SignatureChunk;
+use warp::signature::function::Function;
+use warp::target::Target;
+use warp::{WarpFile, WarpFileHeader};
+
+use crate::cache::cached_type_references;
+use crate::convert::platform_to_target;
+use crate::{build_function, INCLUDE_TAG_ICON, INCLUDE_TAG_NAME};
+
+#[derive(Error, Debug)]
+pub enum ProcessingError {
+ #[error("Failed to open archive: {0}")]
+ ArchiveOpen(std::io::Error),
+
+ #[error("Failed to read archive entry: {0}")]
+ ArchiveRead(std::io::Error),
+
+ #[error("Binary view load error: {0}")]
+ BinaryViewLoad(PathBuf),
+
+ #[error("Existing data load error: {0}")]
+ ExistingDataLoad(PathBuf),
+
+ #[error("Temporary directory creation failed: {0}")]
+ TempDirCreation(std::io::Error),
+
+ #[error("Failed to read file: {0}")]
+ FileRead(std::io::Error),
+
+ #[error("Failed to create chunk, possibly too large")]
+ ChunkCreationFailed,
+
+ #[error("Failed to retrieve path to project file: {0:?}")]
+ NoPathToProjectFile(Ref<ProjectFile>),
+
+ #[error("Processing state has been poisoned")]
+ StatePoisoned,
+
+ #[error("Processing has been cancelled")]
+ Cancelled,
+}
+
+#[derive(Debug, Clone, Default)]
+pub struct FileFilterField;
+
+impl FileFilterField {
+ pub fn to_field() -> FormInputField {
+ FormInputField::TextLine {
+ prompt: "File Filter".to_string(),
+ default: None,
+ value: None,
+ }
+ }
+
+ pub fn from_form(form: &Form) -> Option<Regex> {
+ let field = form.get_field_with_name("File Filter")?;
+ let field_value = field.try_value_string()?;
+
+ // TODO: This is pretty absurd but whatever.
+ let pattern = if field_value.contains(['*', '.', '[', '(']) {
+ // Assume it's a regex if it contains meta-characters.
+ field_value
+ } else {
+ // Treat it as a substring
+ format!(".*{}.*", regex::escape(&field_value))
+ };
+
+ Regex::new(&pattern).ok()
+ }
+}
+
+#[derive(Debug, Clone, Copy, PartialEq, Default)]
+pub enum FileDataKindField {
+ Symbols,
+ Signatures,
+ Types,
+ #[default]
+ All,
+}
+
+impl FileDataKindField {
+ pub fn to_field(&self) -> FormInputField {
+ FormInputField::Choice {
+ prompt: "File Data".to_string(),
+ choices: vec![
+ "Symbols".to_string(),
+ "Signatures".to_string(),
+ "Types".to_string(),
+ "All".to_string(),
+ ],
+ default: Some(match self {
+ Self::Symbols => 0,
+ Self::Signatures => 1,
+ Self::Types => 2,
+ Self::All => 3,
+ }),
+ value: 0,
+ }
+ }
+
+ pub fn from_form(form: &Form) -> Option<Self> {
+ let field = form.get_field_with_name("File Data")?;
+ let field_value = field.try_value_index()?;
+ match field_value {
+ 3 => Some(Self::All),
+ 2 => Some(Self::Types),
+ 1 => Some(Self::Signatures),
+ 0 => Some(Self::Symbols),
+ _ => None,
+ }
+ }
+}
+
+#[derive(Debug, Clone, Copy, PartialEq, Default)]
+pub enum IncludedFunctionsField {
+ Selected,
+ #[default]
+ Annotated,
+ All,
+}
+
+impl IncludedFunctionsField {
+ pub fn to_field(&self) -> FormInputField {
+ // If the user has selected any functions, change the default value of the included functions field.
+ FormInputField::Choice {
+ prompt: "Included Functions".to_string(),
+ choices: vec![
+ format!("Selected {}", INCLUDE_TAG_ICON),
+ "Annotated".to_string(),
+ "All".to_string(),
+ ],
+ default: Some(match self {
+ Self::Selected => 0,
+ Self::Annotated => 1,
+ Self::All => 2,
+ }),
+ value: 0,
+ }
+ }
+
+ pub fn from_form(form: &Form) -> Option<Self> {
+ let field = form.get_field_with_name("Included Functions")?;
+ let field_value = field.try_value_index()?;
+ match field_value {
+ 2 => Some(Self::All),
+ 1 => Some(Self::Annotated),
+ 0 => Some(Self::Selected),
+ _ => None,
+ }
+ }
+}
+
+#[derive(Debug, Clone, Copy, PartialEq, Default)]
+pub enum SaveReportToDiskField {
+ No,
+ #[default]
+ Yes,
+}
+
+impl SaveReportToDiskField {
+ pub fn to_field(&self) -> FormInputField {
+ FormInputField::Checkbox {
+ prompt: "Save Report to Disk".to_string(),
+ default: Some(true),
+ value: false,
+ }
+ }
+
+ pub fn from_form(form: &Form) -> Option<Self> {
+ let field = form.get_field_with_name("Save Report to Disk")?;
+ let field_value = field.try_value_int()?;
+ match field_value {
+ 1 => Some(Self::Yes),
+ _ => Some(Self::No),
+ }
+ }
+}
+
+#[derive(Debug, Clone, Copy, PartialEq, Default)]
+pub enum CompressionTypeField {
+ None,
+ #[default]
+ Zstd,
+}
+
+impl CompressionTypeField {
+ pub fn to_field(&self) -> FormInputField {
+ FormInputField::Choice {
+ prompt: "Compression Type".to_string(),
+ choices: vec!["None".to_string(), "Zstd".to_string()],
+ default: Some(match self {
+ Self::None => 0,
+ Self::Zstd => 1,
+ }),
+ value: 0,
+ }
+ }
+
+ pub fn from_form(form: &Form) -> Option<Self> {
+ let field = form.get_field_with_name("Compression Type")?;
+ let field_value = field.try_value_index()?;
+ match field_value {
+ 1 => Some(Self::Zstd),
+ _ => Some(Self::None),
+ }
+ }
+}
+
+impl From<CompressionTypeField> for CompressionType {
+ fn from(field: CompressionTypeField) -> Self {
+ match field {
+ CompressionTypeField::None => CompressionType::None,
+ CompressionTypeField::Zstd => CompressionType::Zstd,
+ }
+ }
+}
+
+pub fn new_processing_state_background_thread(
+ task: Ref<BackgroundTask>,
+ state: Arc<ProcessingState>,
+) {
+ std::thread::spawn(move || {
+ let start = Instant::now();
+ while !task.is_finished() {
+ std::thread::sleep(Duration::from_millis(100));
+ // Check if the user wants to cancel the processing.
+ if task.is_cancelled() {
+ state.cancel();
+ }
+
+ let total = state.total_files();
+ let processed = state.files_with_state(ProcessingFileState::Processed);
+ let unprocessed = state.files_with_state(ProcessingFileState::Unprocessed);
+ let analyzing = state.files_with_state(ProcessingFileState::Analyzing);
+ let processing = state.files_with_state(ProcessingFileState::Processing);
+ let completion = (processed as f64 / total as f64) * 100.0;
+ let elapsed = start.elapsed().as_secs_f32();
+ let text = format!(
+ "Processing {} files... {{{}|{}|{}|{}}} ({:.2}%) [{:.2}s]",
+ total, unprocessed, analyzing, processing, processed, completion, elapsed
+ );
+ task.set_progress_text(&text);
+ }
+ });
+}
+
+#[derive(Debug, Clone, PartialEq, Eq, Hash)]
+pub enum ProcessingFileState {
+ /// File is yet to be processed.
+ Unprocessed,
+ /// File is being analyzed by Binary Ninja.
+ Analyzing,
+ /// File is currently generating WARP data.
+ /// TODO: (AtomicUsize) for the total and done functions, we can then write to it with Relaxed when processing.
+ Processing,
+ /// File is done being processed.
+ Processed,
+}
+
+#[derive(Debug, Default)]
+pub struct ProcessingState {
+ pub cancelled: AtomicBool,
+ pub files: DashMap<PathBuf, ProcessingFileState>,
+ pub total_functions: AtomicUsize,
+}
+
+impl ProcessingState {
+ pub fn is_cancelled(&self) -> bool {
+ self.cancelled.load(Relaxed)
+ }
+
+ pub fn cancel(&self) {
+ self.cancelled.store(true, Relaxed)
+ }
+
+ pub fn increment_functions(&self) {
+ self.total_functions.fetch_add(1, Relaxed);
+ }
+
+ pub fn total_files(&self) -> usize {
+ self.files.len()
+ }
+
+ pub fn files_with_state(&self, state: ProcessingFileState) -> usize {
+ self.files.iter().filter(|f| *f.value() == state).count()
+ }
+
+ pub fn set_file_state(&self, path: PathBuf, state: ProcessingFileState) {
+ self.files.insert(path, state);
+ }
+}
+
+/// Create a new [`WarpFile`] from files, projects, and directories.
+#[derive(Debug, Clone)]
+pub struct WarpFileProcessor {
+ /// The Binary Ninja settings to use when analyzing the binaries.
+ analysis_settings: Value,
+ /// For any function without an LLIL, request analysis to be run, waiting for analysis to
+ /// complete to include in the analysis.
+ request_analysis: bool,
+ // TODO: Project cache path, so we save to a project instead of some temp path.
+ // TODO: Databases will require regenerating LLIL in some cases, so we must support generating the LLIL.
+ /// The path to a folder to intake and output analysis artifacts.
+ cache_path: Option<PathBuf>,
+ file_data: FileDataKindField,
+ included_functions: IncludedFunctionsField,
+ compression_type: CompressionTypeField,
+ /// Regex pattern used to filter out files.
+ file_filter: Option<Regex>,
+ /// Processor state, this is shareable between threads, so the processor and the consumer can
+ /// read / write to the state, use this if you want to show a progress indicator.
+ state: Arc<ProcessingState>,
+}
+
+impl WarpFileProcessor {
+ pub fn new() -> Self {
+ Self {
+ analysis_settings: json!({
+ "analysis.linearSweep.autorun": false,
+ "analysis.signatureMatcher.autorun": false,
+ "analysis.mode": "full",
+ // Disable warp when opening views.
+ "analysis.warp.guid": false,
+ "analysis.warp.matcher": false,
+ "analysis.warp.apply": false,
+ }),
+ request_analysis: true,
+ cache_path: None,
+ file_data: Default::default(),
+ included_functions: Default::default(),
+ compression_type: Default::default(),
+ file_filter: None,
+ state: Arc::new(ProcessingState::default()),
+ }
+ }
+
+ /// Retrieve a thread-safe shared reference to the [`ProcessingState`].
+ pub fn state(&self) -> Arc<ProcessingState> {
+ self.state.clone()
+ }
+
+ pub fn with_analysis_settings(mut self, analysis_settings: Value) -> Self {
+ self.analysis_settings = analysis_settings;
+ self
+ }
+
+ pub fn with_request_analysis(mut self, request_analysis: bool) -> Self {
+ self.request_analysis = request_analysis;
+ self
+ }
+
+ pub fn with_cache_path(mut self, cache_path: PathBuf) -> Self {
+ self.cache_path = Some(cache_path);
+ self
+ }
+
+ pub fn with_file_data(mut self, file_data: FileDataKindField) -> Self {
+ self.file_data = file_data;
+ self
+ }
+
+ pub fn with_included_functions(mut self, included_functions: IncludedFunctionsField) -> Self {
+ self.included_functions = included_functions;
+ self
+ }
+
+ pub fn with_compression_type(mut self, compression_type: CompressionTypeField) -> Self {
+ self.compression_type = compression_type;
+ self
+ }
+
+ pub fn with_file_filter(mut self, file_filter: Regex) -> Self {
+ self.file_filter = Some(file_filter);
+ self
+ }
+
+ pub fn file_filter(&self, path: &Path) -> bool {
+ match (&self.file_filter, path.to_str()) {
+ (Some(filter), Some(path)) => filter.is_match(path),
+ _ => true,
+ }
+ }
+
+ /// Place a call to this in places to interrupt when canceled.
+ fn check_cancelled(&self) -> Result<(), ProcessingError> {
+ match self.state.is_cancelled() {
+ true => Err(ProcessingError::Cancelled),
+ false => Ok(()),
+ }
+ }
+
+ pub fn process(&self, path: PathBuf) -> Result<WarpFile<'static>, ProcessingError> {
+ match path.extension() {
+ Some(ext) if ext == "a" || ext == "lib" || ext == "rlib" => self.process_archive(path),
+ Some(ext) if ext == "warp" => self.process_warp_file(path),
+ _ if path.is_dir() => self.process_directory(&path),
+ // TODO: process_database?
+ _ => self.process_file(path),
+ }
+ }
+
+ pub fn process_project(&self, project: &Project) -> Result<WarpFile<'static>, ProcessingError> {
+ let filter_project_file = |file: &Guard<ProjectFile>| {
+ let path = project_file_path(file);
+ self.file_filter(&path)
+ };
+
+ let files: Vec<_> = project
+ .files()
+ .iter()
+ .filter(filter_project_file)
+ .map(|f| f.to_owned())
+ .collect();
+
+ // Inform the state of the new unprocessed project files.
+ for project_file in &files {
+ // NOTE: We use the on disk path here because the downstream file state uses that.
+ if let Some(path) = project_file.path_on_disk() {
+ self.state
+ .set_file_state(path, ProcessingFileState::Unprocessed);
+ }
+ }
+
+ let unmerged_files: Result<Vec<_>, _> = files
+ .par_iter()
+ .map(|file| {
+ self.check_cancelled()?;
+ self.process_project_file(file)
+ })
+ .filter_map(|res| match res {
+ Ok(result) => Some(Ok(result)),
+ Err(ProcessingError::Cancelled) => Some(Err(ProcessingError::Cancelled)),
+ Err(e) => {
+ log::error!("Project file processing error: {:?}", e);
+ None
+ }
+ })
+ .collect();
+
+ let unmerged_chunks: Vec<_> = unmerged_files?
+ .iter()
+ .flat_map(|f| f.chunks.clone())
+ .collect();
+ let merged_chunks = Chunk::merge(&unmerged_chunks, self.compression_type.into());
+ Ok(WarpFile::new(WarpFileHeader::new(), merged_chunks))
+ }
+
+ pub fn process_project_file(
+ &self,
+ project_file: &ProjectFile,
+ ) -> Result<WarpFile<'static>, ProcessingError> {
+ let file_name = project_file.name();
+ let extension = file_name.split('.').last();
+ let path = project_file
+ .path_on_disk()
+ .ok_or_else(|| ProcessingError::NoPathToProjectFile(project_file.to_owned()))?;
+ match extension {
+ Some(ext) if ext == "a" || ext == "lib" || ext == "rlib" => self.process_archive(path),
+ Some("warp") => self.process_warp_file(path),
+ _ => self.process_file(path),
+ }
+ }
+
+ pub fn process_warp_file(&self, path: PathBuf) -> Result<WarpFile<'static>, ProcessingError> {
+ let contents = std::fs::read(&path).map_err(ProcessingError::FileRead)?;
+ let file = WarpFile::from_owned_bytes(contents)
+ .ok_or(ProcessingError::ExistingDataLoad(path.clone()));
+
+ // Inform the state of the new processed warp file.
+ self.state
+ .set_file_state(path, ProcessingFileState::Processed);
+
+ file
+ }
+
+ pub fn process_file(&self, path: PathBuf) -> Result<WarpFile<'static>, ProcessingError> {
+ // Inform the state of the new analyzing file.
+ self.state
+ .set_file_state(path.clone(), ProcessingFileState::Analyzing);
+
+ // Load the view, either from the cache or from the given path.
+ // Using the cache can speed up the processing, especially for larger binaries.
+ let settings_str = self.analysis_settings.to_string();
+ let view = match &self.cache_path {
+ Some(cache_path) => {
+ // Processor is caching analysis, try and find our file in the cache.
+ let file_cache_path = cache_path
+ .join(path.file_name().unwrap())
+ .with_extension("bndb");
+ if file_cache_path.exists() {
+ // TODO: Update analysis and wait option
+ log::debug!("Analysis database found in cache: {:?}", file_cache_path);
+ binaryninja::load_with_options(&file_cache_path, true, Some(settings_str))
+ } else {
+ log::debug!("No database found in cache: {:?}", file_cache_path);
+ binaryninja::load_with_options(&path, true, Some(settings_str))
+ }
+ }
+ None => {
+ // Processor is not caching analysis
+ binaryninja::load_with_options(&path, true, Some(settings_str))
+ }
+ }
+ .ok_or(ProcessingError::BinaryViewLoad(path.clone()))?;
+
+ // Analysis is complete, if needed, save the database to cache.
+ if let Some(cache_path) = &self.cache_path {
+ // Before we process the view we should cache the analysis database.
+ // Only cache the analysis database if there has been a change.
+ // TODO: What if there is multiple paths with the same name?
+ // TODO: We need more context than just the path, likely we need a processing path stack.
+ let file_cache_path = cache_path
+ .join(path.file_name().unwrap())
+ .with_extension("bndb");
+ // TODO: We should also update the cache if analysis has changed!
+ if !view.file().is_database_backed() {
+ // Update the cache.
+ log::debug!("Saving analysis database to {:?}", file_cache_path);
+ if !view.file().create_database(&file_cache_path) {
+ // TODO: We might want to error here...
+ log::warn!("Failed to save analysis database to {:?}", file_cache_path);
+ }
+ } else {
+ log::debug!(
+ "Analysis database unchanged, skipping save to {:?}",
+ file_cache_path
+ );
+ }
+ }
+
+ // Process the view
+ let warp_file = self.process_view(path, &view);
+ // Close the view manually, see comment in [`BinaryView`].
+ view.file().close();
+ warp_file
+ }
+
+ pub fn process_directory(&self, path: &Path) -> Result<WarpFile<'static>, ProcessingError> {
+ // Collect all files in the directory
+ let files = WalkDir::new(path)
+ .into_iter()
+ .filter_map(|e| {
+ let path = e.ok()?.into_path();
+ if path.is_file() && self.file_filter(&path) {
+ Some(path)
+ } else {
+ None
+ }
+ })
+ .collect::<Vec<_>>();
+
+ // Inform the state of the new unprocessed files.
+ for entry_file in &files {
+ self.state
+ .set_file_state(entry_file.clone(), ProcessingFileState::Unprocessed);
+ }
+
+ // Process all the files.
+ let unmerged_files: Result<Vec<_>, _> = files
+ .into_par_iter()
+ .inspect(|path| log::debug!("Processing file: {:?}", path))
+ .map(|path| {
+ self.check_cancelled()?;
+ self.process(path)
+ })
+ .filter_map(|res| match res {
+ Ok(result) => Some(Ok(result)),
+ Err(ProcessingError::Cancelled) => Some(Err(ProcessingError::Cancelled)),
+ Err(e) => {
+ log::error!("Directory file processing error: {:?}", e);
+ None
+ }
+ })
+ .collect();
+
+ let unmerged_chunks: Vec<_> = unmerged_files?
+ .iter()
+ .flat_map(|f| f.chunks.clone())
+ .collect();
+ let merged_chunks = Chunk::merge(&unmerged_chunks, self.compression_type.into());
+ Ok(WarpFile::new(WarpFileHeader::new(), merged_chunks))
+ }
+
+ pub fn process_archive(&self, path: PathBuf) -> Result<WarpFile<'static>, ProcessingError> {
+ // Open the archive.
+ let archive_file = File::open(&path).map_err(ProcessingError::ArchiveOpen)?;
+ let mut archive = Archive::new(archive_file);
+
+ // Create a temp directory to store the archive entries.
+ let temp_dir = TempDir::new("tmp_archive").map_err(ProcessingError::TempDirCreation)?;
+
+ // TODO: Use the file_filter? We would need to normalize the path then.
+ // Iterate through the entries in the ar file and make a temp dir with them
+ let mut entry_files: HashSet<PathBuf> = HashSet::new();
+ while let Some(entry) = archive.next_entry() {
+ let mut entry = entry.map_err(ProcessingError::ArchiveRead)?;
+ // NOTE: The entry name here may resemble a full path, on unix this is fine, but
+ // on Windows this will prevent a file from being created, so we "normalize" the file name.
+ let name = String::from_utf8_lossy(entry.header().identifier()).to_string();
+ // Normalize file name for Windows compatibility
+ let normalized_name = name
+ .replace(':', "_")
+ .replace('/', "_")
+ .replace('\\', "_")
+ .split_whitespace()
+ .collect::<Vec<_>>()
+ .join("_");
+ let output_path = temp_dir.path().join(&normalized_name);
+ if !entry_files.contains(&output_path) {
+ let mut output_file =
+ File::create(&output_path).map_err(ProcessingError::TempDirCreation)?;
+ std::io::copy(&mut entry, &mut output_file).map_err(ProcessingError::FileRead)?;
+ entry_files.insert(output_path);
+ } else {
+ log::debug!("Skipping already inserted entry: {}", normalized_name);
+ }
+ }
+
+ // Inform the state of the new unprocessed files.
+ for entry_file in &entry_files {
+ self.state
+ .set_file_state(entry_file.clone(), ProcessingFileState::Unprocessed);
+ }
+
+ // TODO: Par iter?
+ // Process all the entries.
+ let unmerged_files: Result<Vec<_>, _> = entry_files
+ .into_par_iter()
+ .inspect(|path| log::debug!("Processing entry: {:?}", path))
+ .map(|path| {
+ self.check_cancelled()?;
+ self.process_file(path)
+ })
+ .filter_map(|res| match res {
+ Ok(result) => Some(Ok(result)),
+ Err(ProcessingError::Cancelled) => Some(Err(ProcessingError::Cancelled)),
+ Err(e) => {
+ log::error!("Archive file processing error: {:?}", e);
+ None
+ }
+ })
+ .collect();
+
+ let unmerged_chunks: Vec<_> = unmerged_files?
+ .iter()
+ .flat_map(|f| f.chunks.clone())
+ .collect();
+ let merged_chunks = Chunk::merge(&unmerged_chunks, self.compression_type.into());
+ Ok(WarpFile::new(WarpFileHeader::new(), merged_chunks))
+ }
+
+ pub fn process_view(
+ &self,
+ path: PathBuf,
+ view: &BinaryView,
+ ) -> Result<WarpFile<'static>, ProcessingError> {
+ self.state
+ .set_file_state(path.clone(), ProcessingFileState::Processing);
+
+ let mut chunks = Vec::new();
+ if self.file_data != FileDataKindField::Types {
+ let mut signature_chunks = self.create_signature_chunks(view)?;
+ for (target, signature_chunk) in signature_chunks.drain() {
+ let chunk = Chunk::new_with_target(
+ ChunkKind::Signature(signature_chunk),
+ self.compression_type.into(),
+ target,
+ );
+ chunks.push(chunk)
+ }
+ }
+
+ if self.file_data != FileDataKindField::Signatures {
+ chunks.push(Chunk::new(
+ ChunkKind::Type(self.create_type_chunk(view)?),
+ self.compression_type.into(),
+ ));
+ }
+
+ self.state
+ .set_file_state(path, ProcessingFileState::Processed);
+
+ Ok(WarpFile::new(WarpFileHeader::new(), chunks))
+ }
+
+ /// Create signature chunks for each unique [`Target`].
+ ///
+ /// A [`Target`] in Binary Ninja is a [`Platform`], so we just fill in that information.
+ pub fn create_signature_chunks(
+ &self,
+ view: &BinaryView,
+ ) -> Result<HashMap<Target, SignatureChunk<'static>>, ProcessingError> {
+ let is_function_named = |f: &Guard<BNFunction>| {
+ self.included_functions == IncludedFunctionsField::All
+ || view.symbol_by_address(f.start()).is_some()
+ || f.has_user_annotations()
+ };
+ let is_function_tagged = |f: &Guard<BNFunction>| {
+ self.included_functions != IncludedFunctionsField::Selected
+ || !f.function_tags(None, Some(INCLUDE_TAG_NAME)).is_empty()
+ };
+ // TODO: is_function_blacklisted (use tag)
+
+ // TODO: Move this background task to use the ProcessingState.
+ let view_functions = view.functions();
+ let total_functions = view_functions.len();
+ let done_functions = AtomicUsize::default();
+ let background_task = BackgroundTask::new(
+ &format!("Generating signatures... ({}/{})", 0, total_functions),
+ true,
+ );
+
+ // Create all of the "built" functions, for the chunk.
+ // NOTE: This does a bit of filtering to remove undesired functions, look at this if
+ // a desired function is not in the created chunk.
+ // TODO: Make this interruptable. with background_task.is_cancelled.
+ let start = Instant::now();
+ let built_functions: DashMap<Target, Vec<Function>> = view_functions
+ .par_iter()
+ .inspect(|_| {
+ done_functions.fetch_add(1, Relaxed);
+ background_task.set_progress_text(&format!(
+ "Generating signatures... ({}/{}) [{}s]",
+ done_functions.load(Relaxed),
+ total_functions,
+ start.elapsed().as_secs_f32()
+ ))
+ })
+ .filter(is_function_tagged)
+ .filter(is_function_named)
+ .filter(|f| !f.analysis_skipped())
+ .filter_map(|func| {
+ let lifted_il = func.lifted_il().ok()?;
+ let target = platform_to_target(&func.platform());
+ let mut built_function = build_function(&func, &lifted_il);
+ // User asked to only save symbols, so we will remove the function type.
+ if self.file_data == FileDataKindField::Symbols {
+ built_function.ty = None;
+ }
+ Some((target, built_function))
+ })
+ .fold(
+ DashMap::new,
+ |acc: DashMap<Target, Vec<Function>>, (target, function)| {
+ acc.entry(target).or_default().push(function);
+ acc
+ },
+ )
+ .reduce(DashMap::new, |acc, other| {
+ other.into_iter().for_each(|(key, value)| {
+ acc.entry(key).or_default().extend(value);
+ });
+ acc
+ });
+
+ let chunks: Result<HashMap<Target, SignatureChunk<'static>>, ProcessingError> =
+ built_functions
+ .into_iter()
+ .map(|(target, functions)| {
+ Ok((
+ target,
+ SignatureChunk::new(&functions)
+ .ok_or(ProcessingError::ChunkCreationFailed)?,
+ ))
+ })
+ .collect();
+
+ background_task.finish();
+ chunks
+ }
+
+ // TODO: Add a background task here.
+ pub fn create_type_chunk(
+ &self,
+ view: &BinaryView,
+ ) -> Result<TypeChunk<'static>, ProcessingError> {
+ let mut referenced_types = Vec::new();
+ if let Some(ref_ty_cache) = cached_type_references(view) {
+ referenced_types = ref_ty_cache
+ .cache
+ .iter()
+ .filter_map(|t| t.to_owned())
+ .collect::<Vec<_>>();
+ }
+ TypeChunk::new_with_computed(&referenced_types).ok_or(ProcessingError::ChunkCreationFailed)
+ }
+}
+
+fn project_file_path(file: &ProjectFile) -> PathBuf {
+ // Recurse up the folders to build a string like /foldera/folderb/myfile
+ let mut path = PathBuf::new();
+ // Add file name
+ path.push(file.name());
+ // Recursively add parent folder names
+ let mut current = file.folder();
+ while let Some(folder) = current {
+ path = PathBuf::from(folder.name()).join(path);
+ current = folder.parent();
+ }
+ path
+}
diff --git a/plugins/warp/src/report.rs b/plugins/warp/src/report.rs
new file mode 100644
index 00000000..8ba9cc8b
--- /dev/null
+++ b/plugins/warp/src/report.rs
@@ -0,0 +1,185 @@
+use binaryninja::interaction::{Form, FormInputField};
+use minijinja::Environment;
+use serde::Serialize;
+use warp::chunk::{Chunk, ChunkKind};
+use warp::r#type::guid::TypeGUID;
+use warp::WarpFile;
+
+#[derive(Debug, Clone, Copy, PartialEq, Default)]
+pub enum ReportKindField {
+ None,
+ #[default]
+ Html,
+ Markdown,
+ Json,
+}
+
+impl ReportKindField {
+ pub fn to_field(&self) -> FormInputField {
+ FormInputField::Choice {
+ prompt: "Generated Report".to_string(),
+ choices: vec![
+ "None".to_string(),
+ "HTML".to_string(),
+ "Markdown".to_string(),
+ "JSON".to_string(),
+ ],
+ default: Some(match self {
+ Self::None => 0,
+ Self::Html => 1,
+ Self::Markdown => 2,
+ Self::Json => 3,
+ }),
+ value: 0,
+ }
+ }
+
+ pub fn from_form(form: &Form) -> Option<Self> {
+ let field = form.get_field_with_name("Generated Report")?;
+ let field_value = field.try_value_index()?;
+ match field_value {
+ 3 => Some(Self::Json),
+ 2 => Some(Self::Markdown),
+ 1 => Some(Self::Html),
+ _ => Some(Self::None),
+ }
+ }
+}
+
+#[derive(Debug, Clone)]
+pub struct ReportGenerator {
+ environment: Environment<'static>,
+}
+
+impl ReportGenerator {
+ pub fn new() -> Self {
+ let mut environment = Environment::new();
+ // Remove trailing lines for blocks, this is required for Markdown tables.
+ environment.set_trim_blocks(true);
+ minijinja_embed::load_templates!(&mut environment);
+ Self { environment }
+ }
+
+ pub fn report(&self, kind: &ReportKindField, file: &WarpFile) -> Option<String> {
+ match kind {
+ ReportKindField::None => None,
+ ReportKindField::Html => self.html_report(file),
+ ReportKindField::Markdown => self.markdown_report(file),
+ ReportKindField::Json => self.json_report(file),
+ }
+ }
+
+ pub fn report_extension(&self, kind: &ReportKindField) -> Option<&'static str> {
+ match kind {
+ ReportKindField::None => None,
+ ReportKindField::Html => Some("html"),
+ ReportKindField::Markdown => Some("md"),
+ ReportKindField::Json => Some("json"),
+ }
+ }
+
+ pub fn html_report(&self, file: &WarpFile) -> Option<String> {
+ let data = FileReportData::new(file);
+ let tmpl = self.environment.get_template("file.html").ok()?;
+ tmpl.render(data).ok()
+ }
+
+ pub fn markdown_report(&self, file: &WarpFile) -> Option<String> {
+ let data = FileReportData::new(file);
+ let tmpl = self.environment.get_template("file.md").ok()?;
+ tmpl.render(data).ok()
+ }
+
+ pub fn json_report(&self, file: &WarpFile) -> Option<String> {
+ let data = FileReportData::new(file);
+ let tmpl = self.environment.get_template("file.json").ok()?;
+ tmpl.render(data).ok()
+ }
+}
+
+#[derive(Debug, Clone, Serialize)]
+pub struct FileReportData {
+ pub title: String,
+ // pub header: WarpFileHeader,
+ pub chunks: Vec<ChunkReportData>,
+}
+
+impl FileReportData {
+ pub fn new(file: &WarpFile) -> Self {
+ Self {
+ title: "Warp File Report".to_string(),
+ // header: file.header.clone(),
+ chunks: file
+ .chunks
+ .iter()
+ .map(|chunk| ChunkReportData::new(chunk))
+ .collect(),
+ }
+ }
+}
+
+#[derive(Debug, Clone, Serialize)]
+pub struct ChunkReportData {
+ pub title: String,
+ // pub header: ChunkHeader,
+ pub target: String,
+ pub total_item_count: usize,
+ /// View into a (possible subset) of chunk items.
+ pub item_view: Vec<ItemReportData>,
+}
+
+impl ChunkReportData {
+ pub fn new(chunk: &Chunk) -> Self {
+ // TODO: Set a limit for the number of items so we dont construct 10000000 items in the report.
+ let items: Vec<_> = match &chunk.kind {
+ ChunkKind::Signature(sc) => sc
+ .raw_functions()
+ .map(|f| ItemReportData {
+ name: f.symbol().and_then(|s| s.name().map(|n| n.to_string())),
+ guid: f.guid().to_string(),
+ note: None,
+ })
+ .collect(),
+ ChunkKind::Type(tc) => tc
+ .raw_types()
+ .map(|t| ItemReportData {
+ name: t.type_().and_then(|s| s.name().map(|n| n.to_string())),
+ guid: TypeGUID::from(t.guid()).to_string(),
+ note: None,
+ })
+ .collect(),
+ };
+
+ let chunk_type = match &chunk.kind {
+ ChunkKind::Signature(_) => "Signature".to_string(),
+ ChunkKind::Type(_) => "Type".to_string(),
+ };
+
+ let size_in_kb = chunk.header.size as f64 / 1024.0;
+ let formatted_size = format!("{:.1}kb", size_in_kb);
+
+ // For the target show the platform, or the architecture if available.
+ let target = chunk
+ .header
+ .target
+ .platform
+ .clone()
+ .or_else(|| chunk.header.target.architecture.clone())
+ .unwrap_or_else(|| "None".to_string());
+
+ Self {
+ title: format!("{} Chunk ({})", chunk_type, formatted_size),
+ target,
+ // header: chunk.header.clone(),
+ total_item_count: items.len(),
+ item_view: items,
+ }
+ }
+}
+
+#[derive(Debug, Clone, Serialize)]
+pub struct ItemReportData {
+ pub guid: String,
+ pub name: Option<String>,
+ pub note: Option<String>,
+}
diff --git a/plugins/warp/src/templates/file.html b/plugins/warp/src/templates/file.html
new file mode 100644
index 00000000..7ea465ef
--- /dev/null
+++ b/plugins/warp/src/templates/file.html
@@ -0,0 +1,37 @@
+<html lang="en">
+<head>
+ <meta charset="UTF-8">
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
+ <title>{{ title }}</title>
+</head>
+<body>
+<h1>{{ title }}</h1>
+
+{% for chunk in chunks %}
+<section>
+ <h2>{{ chunk.title }}</h2>
+ <p>Target: {{ chunk.target }}</p>
+ <p>Total items: {{ chunk.total_item_count }}</p>
+
+ <table>
+ <thead>
+ <tr>
+ <th>GUID</th>
+ <th>Name</th>
+ <th>Note</th>
+ </tr>
+ </thead>
+ <tbody>
+ {% for item in chunk.item_view %}
+ <tr>
+ <td>{{ item.guid }}</td>
+ <td>{{ item.name or 'N/A' }}</td>
+ <td>{{ item.note or 'N/A' }}</td>
+ </tr>
+ {% endfor %}
+ </tbody>
+ </table>
+</section>
+{% endfor %}
+</body>
+</html> \ No newline at end of file
diff --git a/plugins/warp/src/templates/file.json b/plugins/warp/src/templates/file.json
new file mode 100644
index 00000000..764d35cd
--- /dev/null
+++ b/plugins/warp/src/templates/file.json
@@ -0,0 +1,21 @@
+{
+ "title": "{{ title }}",
+ "chunks": [
+ {% for chunk in chunks %}
+ {
+ "title": "{{ chunk.title }}",
+ "target": "{{ chunk.target }}",
+ "total_item_count": {{ chunk.total_item_count }},
+ "item_view": [
+ {% for item in chunk.item_view %}
+ {
+ "guid": "{{ item.guid }}",
+ "name": "{{ item.name or 'N/A' }}",
+ "note": "{{ item.note or 'N/A' }}"
+ }{% if not loop.last %},{% endif %}
+ {% endfor %}
+ ]
+ }{% if not loop.last %},{% endif %}
+ {% endfor %}
+ ]
+} \ No newline at end of file
diff --git a/plugins/warp/src/templates/file.md b/plugins/warp/src/templates/file.md
new file mode 100644
index 00000000..433cb8e9
--- /dev/null
+++ b/plugins/warp/src/templates/file.md
@@ -0,0 +1,15 @@
+# {{ title }}
+
+{% for chunk in chunks %}
+## {{ chunk.title }}
+
+Target: {{ chunk.target }}
+
+Total items: {{ chunk.total_item_count }}
+
+| GUID | Name | Note |
+|--------------|--------------|--------------|
+{% for item in chunk.item_view -%}
+| {{ item.guid }} | {{ item.name or 'N/A' }} | {{ item.note or 'N/A' }} |
+{% endfor %}
+{% endfor %}
diff --git a/plugins/warp/tests/container.rs b/plugins/warp/tests/container.rs
new file mode 100644
index 00000000..31b47377
--- /dev/null
+++ b/plugins/warp/tests/container.rs
@@ -0,0 +1,280 @@
+use std::collections::HashSet;
+use std::path::PathBuf;
+use std::str::FromStr;
+use uuid::Uuid;
+use warp::r#type::class::BooleanClass;
+use warp::r#type::class::TypeClass::Void;
+use warp::r#type::guid::TypeGUID;
+use warp::r#type::{ComputedType, Type};
+use warp::signature::function::{Function, FunctionGUID};
+use warp::symbol::{Symbol, SymbolClass};
+use warp::target::Target;
+use warp_ninja::container::disk::DiskContainer;
+use warp_ninja::container::memory::{MemoryContainer, MemorySource};
+use warp_ninja::container::{Container, ContainerError, SourceId, SourcePath};
+
+fn type_0() -> (TypeGUID, Type) {
+ let ty = Type::builder()
+ .name("type_0")
+ .class(BooleanClass::builder().width(1).build())
+ .build();
+ (TypeGUID::from(&ty), ty)
+}
+
+fn type_1() -> (TypeGUID, Type) {
+ let ty = Type::builder()
+ .name("type_1")
+ .class(BooleanClass::builder().width(4).build())
+ .build();
+ (TypeGUID::from(&ty), ty)
+}
+
+fn type_2() -> (TypeGUID, Type) {
+ let ty = Type::builder()
+ .name("type_2")
+ .class(BooleanClass::builder().width(8).build())
+ .build();
+ (TypeGUID::from(&ty), ty)
+}
+
+fn func_0() -> (FunctionGUID, Function) {
+ let guid = FunctionGUID::from(Uuid::from_str("d4e56ec8-1f2b-4a87-9f2c-bc3f10c4d8e9").unwrap());
+ let function = Function {
+ guid,
+ symbol: Symbol {
+ name: "func_0".to_string(),
+ modifiers: Default::default(),
+ class: SymbolClass::Function,
+ },
+ // TODO: We might want to give this an actual function type.
+ ty: Some(Type::builder::<String, _>().class(Void).build()),
+ constraints: Default::default(),
+ comments: vec![],
+ variables: vec![],
+ };
+ (guid, function)
+}
+
+fn func_1() -> (FunctionGUID, Function) {
+ let guid = FunctionGUID::from(Uuid::from_str("b713c293-463a-5baa-b31b-ed010510d5c0").unwrap());
+ let function = Function {
+ guid,
+ symbol: Symbol {
+ name: "func_1".to_string(),
+ modifiers: Default::default(),
+ class: SymbolClass::Function,
+ },
+ // TODO: We might want to give this an actual function type.
+ ty: Some(Type::builder::<String, _>().class(Void).build()),
+ constraints: Default::default(),
+ comments: vec![],
+ variables: vec![],
+ };
+ (guid, function)
+}
+
+#[test]
+fn test_sources_with_type_guid() {
+ let source1 = SourceId::new();
+ let source2 = SourceId::new();
+ let (guid_1, ty_1) = type_0();
+ let (guid_2, ty_2) = type_1();
+
+ let container = MemoryContainer::new()
+ .with_source_type(source1, guid_1, ty_1)
+ .with_source_type(source1, guid_2, ty_2.clone())
+ .with_source_type(source2, guid_2, ty_2);
+
+ let result = container
+ .sources_with_type_guid(&guid_2)
+ .expect("Failed to get sources");
+ // HashSet used for unordered comparison
+ let result_set: HashSet<_> = result.into_iter().collect();
+ let expected_set: HashSet<_> = vec![source1, source2].into_iter().collect();
+ assert_eq!(result_set, expected_set);
+
+ let result = container
+ .sources_with_type_guid(&guid_1)
+ .expect("Failed to get sources");
+ // HashSet used for unordered comparison
+ let result_set: HashSet<_> = result.into_iter().collect();
+ let expected_set: HashSet<_> = vec![source1].into_iter().collect();
+ assert_eq!(result_set, expected_set);
+}
+
+#[test]
+fn test_sources_with_function_guid() {
+ let target = Target::default();
+
+ let source1 = SourceId::new();
+ let source2 = SourceId::new();
+ let (guid_1, fn_1) = func_0();
+ let (guid_2, fn_2) = func_1();
+
+ let container = MemoryContainer::new()
+ .with_source_function(source1, guid_1, fn_1)
+ .with_source_function(source1, guid_2, fn_2.clone())
+ .with_source_function(source2, guid_2, fn_2);
+
+ let result = container
+ .sources_with_function_guid(&target, &guid_2)
+ .expect("Failed to get sources");
+ // HashSet used for unordered comparison
+ let result_set: HashSet<_> = result.into_iter().collect();
+ let expected_set: HashSet<_> = vec![source1, source2].into_iter().collect();
+ assert_eq!(result_set, expected_set);
+
+ let result = container
+ .sources_with_function_guid(&target, &guid_1)
+ .expect("Failed to get sources");
+ assert_eq!(result, vec![source1]);
+}
+
+#[test]
+fn test_sources_with_type_guids() {
+ let source1 = SourceId::new();
+ let source2 = SourceId::new();
+ let (guid_1, ty_1) = type_0();
+ let (guid_2, ty_2) = type_1();
+ let (guid_3, ty_3) = type_2();
+
+ let container = MemoryContainer::new()
+ .with_source_type(source1, guid_1, ty_1)
+ .with_source_type(source1, guid_2, ty_2.clone())
+ .with_source_type(source2, guid_2, ty_2)
+ .with_source_type(source2, guid_3, ty_3);
+
+ let result = container
+ .sources_with_type_guids(&[guid_2.clone(), guid_3.clone()])
+ .expect("Failed to get sources");
+ assert_eq!(result.len(), 2);
+ assert!(result.get(&guid_2).unwrap().contains(&source1));
+ assert!(result.get(&guid_2).unwrap().contains(&source2));
+ assert!(result.get(&guid_3).unwrap().contains(&source2));
+}
+
+#[test]
+fn test_add_types() {
+ let source1 = SourceId::new();
+ let source2 = SourceId::new();
+ let (guid_1, ty_1) = type_0();
+ let (guid_2, ty_2) = type_1();
+ let mut container = MemoryContainer::new()
+ .with_source(
+ source1,
+ MemorySource {
+ writable: false,
+ ..Default::default()
+ },
+ )
+ .with_source_type(source1, guid_1, ty_1.clone())
+ .with_source_type(source2, guid_2, ty_2.clone());
+
+ assert_eq!(
+ container.add_types(&source1, &[ty_1.clone()]),
+ Err(ContainerError::SourceNotWritable(source1)),
+ "Source should not be writable"
+ );
+ assert_eq!(
+ container.add_types(&source2, &[ty_1.clone()]),
+ Ok(()),
+ "Source should be writable"
+ );
+ container
+ .type_with_guid(&source1, &guid_1)
+ .expect("Failed to get existing type");
+ container
+ .type_with_guid(&source2, &guid_1)
+ .expect("Failed to get added type");
+ container
+ .type_with_guid(&source2, &guid_2)
+ .expect("Failed to get existing type");
+}
+
+#[test]
+fn test_disk_container() {
+ // We are going to use the OUT_DIR as the disk container path, this might have other artifacts in it
+ // so it's a good test to make sure that we handle bad files gracefully.
+ let out_dir = std::env::var("OUT_DIR").expect("OUT_DIR environment variable is not set");
+ let out_dir_path: PathBuf = out_dir.parse().expect("Failed to parse OUT_DIR as path");
+
+ // TODO: Use a temp file for this instead lol.
+ let added_type_path = out_dir_path.join("added_type.warp");
+ // Make sure that we have deleted the previous runs artifacts.
+ if std::fs::exists(&added_type_path).unwrap() {
+ std::fs::remove_file(&added_type_path).expect("Failed to remove existing file");
+ }
+
+ // Write a simple test to open a file with DiskContainer
+ let mut container = DiskContainer::new_from_dir(out_dir_path.clone());
+
+ // Just pass in the default target, there is no target specified in the file.
+ let target = Target::default();
+
+ // Test type retrieval.
+ // Type -> type_10 : 2f6d2876-ec42-5ca1-bee3-f12fe91d7e13
+ let type_0 = TypeGUID::from(Uuid::from_str("2f6d2876-ec42-5ca1-bee3-f12fe91d7e13").unwrap());
+ let sources: Vec<SourceId> = container
+ .sources_with_type_guid(&type_0)
+ .expect("Failed to get sources")
+ .into_iter()
+ .collect();
+ assert_eq!(sources.len(), 1);
+ let result_type_0 = container
+ .type_with_guid(&sources[0], &type_0)
+ .expect("Failed to get type")
+ .expect("Type not found");
+ assert_eq!(result_type_0.name, Some("type_10".to_string()));
+ let result_type_guids_0 = container
+ .type_guids_with_name(&sources[0], "type_10")
+ .expect("Failed to get type guids");
+ assert_eq!(result_type_guids_0.len(), 1);
+ assert_eq!(result_type_guids_0[0], type_0);
+
+ // Test function retrieval.
+ // Function -> function_95 : d5da0413-a020-5db8-b838-4d0ea8bd3dcb
+ let func_0 =
+ FunctionGUID::from(Uuid::from_str("d5da0413-a020-5db8-b838-4d0ea8bd3dcb").unwrap());
+ let func_sources = container
+ .sources_with_function_guid(&target, &func_0)
+ .expect("Failed to get sources");
+ assert_eq!(func_sources.len(), 1);
+ let result_func_0 = container
+ .functions_with_guid(&target, &func_sources[0], &func_0)
+ .expect("Failed to get functions");
+ assert_eq!(result_func_0.len(), 1);
+ assert_eq!(result_func_0[0].symbol.name, "function_95".to_string());
+
+ // Test adding a type to an existing disk source.
+ let mut result_type_0_mut = result_type_0.clone();
+ result_type_0_mut.name = Some("added_type".to_string());
+ let computed_result_type_0 = ComputedType::new(result_type_0_mut);
+ container
+ .add_types(&sources[0], &[computed_result_type_0.ty])
+ .expect("Failed to add type");
+ let result_type_1 = container
+ .type_guids_with_name(&sources[0], "added_type")
+ .expect("Failed to get added type");
+ assert_eq!(
+ result_type_1,
+ vec![computed_result_type_0.guid],
+ "Added type was not found in the existing source"
+ );
+
+ // Test commiting the updated source.
+ // NOTE: Because we don't want to modify the file, we are going to change the source path
+ // before trying to commit it, this is a bit hacky and should not really be done in real code.
+ let source = container
+ .sources
+ .get_mut(&sources[0])
+ .expect("Container does not contain the source");
+ source.path = SourcePath::new(out_dir_path.join("added_type.warp"));
+ container
+ .commit_source(&sources[0])
+ .expect("Failed to commit source");
+ assert_eq!(
+ std::fs::exists(&added_type_path).unwrap(),
+ true,
+ "File was not created"
+ );
+}
diff --git a/plugins/warp/tests/determinism.rs b/plugins/warp/tests/determinism.rs
new file mode 100644
index 00000000..403fff1d
--- /dev/null
+++ b/plugins/warp/tests/determinism.rs
@@ -0,0 +1,40 @@
+//! This tests to make sure the function GUIDs are stable.
+use binaryninja::binary_view::BinaryViewExt;
+use binaryninja::headless::Session;
+use std::collections::BTreeMap;
+use std::path::PathBuf;
+use warp::signature::function::FunctionGUID;
+use warp_ninja::cache::cached_function_guid;
+
+// These are the target files present in OUT_DIR
+// Add the files to fixtures/bin
+static TARGET_FILES: [&str; 9] = [
+ "_ctype.obj",
+ "_fptostr.obj",
+ "_mbslen.obj",
+ "_memicmp.obj",
+ "_strnicm.obj",
+ "_wctype.obj",
+ "atof.obj",
+ "atoldbl.obj",
+ "atox.obj",
+];
+
+#[test]
+fn insta_signatures() {
+ let session = Session::new().expect("Failed to initialize session");
+ let out_dir = env!("OUT_DIR").parse::<PathBuf>().unwrap();
+ for file_name in TARGET_FILES {
+ let path = out_dir.join(file_name);
+ let view = session.load(&path).expect("Failed to load view");
+ let functions: BTreeMap<u64, FunctionGUID> = view
+ .functions()
+ .iter()
+ .map(|f| {
+ let guid = cached_function_guid(&f, &f.lifted_il().unwrap());
+ (f.start(), guid)
+ })
+ .collect();
+ insta::assert_debug_snapshot!(file_name, functions);
+ }
+}
diff --git a/plugins/warp/tests/matcher.rs b/plugins/warp/tests/matcher.rs
new file mode 100644
index 00000000..13e2ce01
--- /dev/null
+++ b/plugins/warp/tests/matcher.rs
@@ -0,0 +1,182 @@
+use binaryninja::architecture::CoreArchitecture;
+use binaryninja::binary_view::{BinaryView, BinaryViewExt};
+use binaryninja::file_metadata::FileMetadata;
+use binaryninja::function::Function as BNFunction;
+use binaryninja::headless::Session;
+use binaryninja::platform::Platform;
+use binaryninja::rc::Ref;
+use binaryninja::symbol::{Symbol as BNSymbol, SymbolType};
+use binaryninja::types::TypeClass as BNTypeClass;
+use std::str::FromStr;
+use warp::mock::{mock_constraint, mock_function};
+use warp::r#type::class::{IntegerClass, ReferrerClass, StructureClass, StructureMember};
+use warp::r#type::guid::TypeGUID;
+use warp::r#type::Type;
+use warp::signature::function::FunctionGUID;
+use warp::target::Target;
+use warp_ninja::container::memory::MemoryContainer;
+use warp_ninja::container::{Container, SourceId};
+use warp_ninja::function_guid;
+use warp_ninja::matcher::{Matcher, MatcherSettings};
+
+const MOCK_FUNCTION_GUID: &'static str = "02e8690a-bd1e-54df-9a75-5e7bca594c30";
+const MOCK_FUNCTION_BYTES: &[u8] = &[
+ // first_function
+ 0xA1, 0xFA, 0xF8, 0xF0, 0x99, // ; mov eax, [0x99f0f8fa]
+ 0x83, 0xC0, 0x37, // ; add eax, 55
+ 0xC3, // ; ret
+ // second_function
+ 0xA1, 0xFA, 0xF8, 0xF0, 0x91, // ; mov eax, [0x91f0f8fa]
+ 0x83, 0xC0, 0x37, // ; add eax, 55
+ 0xC3, // ; ret
+];
+
+fn create_mock_bn_function(_session: &Session) -> Ref<BNFunction> {
+ let file = FileMetadata::new();
+ let view =
+ BinaryView::from_data(&file, MOCK_FUNCTION_BYTES).expect("Failed to create mock view");
+ let platform = Platform::by_name("x86").unwrap();
+ // Add the constraint symbol so that the matcher picks it up, so we can test constraint matching.
+ let constraint_symbol =
+ BNSymbol::builder(SymbolType::Function, "second_function", 0x9).create();
+ view.define_user_symbol(&constraint_symbol);
+ let function_symbol = BNSymbol::builder(SymbolType::Function, "first_function", 0x0).create();
+ view.define_user_symbol(&function_symbol);
+ // Define the constraint function.
+ view.add_user_function_with_platform(0x9, &platform)
+ .expect("Failed to create constraint function");
+ // Actually define the function and return it.
+ view.add_user_function_with_platform(0x0, &platform)
+ .expect("Failed to create mock function")
+}
+
+#[test]
+fn test_match_function() {
+ let mut matcher_settings = MatcherSettings::default();
+ matcher_settings.trivial_function_len = 0;
+ let matcher = Matcher::new(matcher_settings);
+
+ // The memory container does not currently reference a target.
+ let target = Target::default();
+
+ let func = mock_function(MOCK_FUNCTION_GUID);
+ let func_guid = func.guid.clone();
+
+ let source = SourceId::new();
+ let memory_container =
+ MemoryContainer::new().with_source_function(source, func_guid, func.clone());
+
+ // Create mock binary ninja function.
+ let session = Session::new().expect("Failed to create session");
+ let bn_function = create_mock_bn_function(&session);
+
+ let possible_funcs = memory_container
+ .functions_with_guid(&target, &source, &func_guid)
+ .expect("Failed to get functions");
+
+ // Because there is only a single possible function this should just return that function.
+ let matched_function = matcher
+ .match_function_from_constraints(&bn_function, &possible_funcs)
+ .expect("Failed to match function");
+ assert_eq!(matched_function, &func);
+}
+
+#[test]
+fn test_match_function_from_constraints() {
+ let mut matcher_settings = MatcherSettings::default();
+ // TODO: This is needed to make the test pass, the functions are "trivial" in length.
+ matcher_settings.trivial_function_adjacent_allowed = true;
+ let matcher = Matcher::new(matcher_settings);
+ let mut function = mock_function("first_function");
+ function.guid = FunctionGUID::from_str(MOCK_FUNCTION_GUID).expect("Failed to parse guid");
+ let func_guid = function.guid.clone();
+ // Add constraint
+ function
+ .constraints
+ .insert(mock_constraint("second_function", Some(0x9)));
+
+ let matched_func_1 = function.clone();
+ let mut matched_func_2 = function.clone();
+ // Remove the constraint from 2, this means that the first matched_func should match.
+ matched_func_2.constraints.clear();
+ let mut matched_functions = vec![matched_func_1.clone(), matched_func_2];
+
+ // Create a mock binary ninja function.
+ let session = Session::new().expect("Failed to create session");
+ let bn_function = create_mock_bn_function(&session);
+ let bn_function_guid = function_guid(&bn_function, &bn_function.lifted_il().unwrap());
+ assert_eq!(bn_function_guid, func_guid);
+
+ // We should match on the first as it has the adjacent constraint still.
+ let matched_0 = matcher
+ .match_function_from_constraints(&bn_function, &matched_functions)
+ .expect("Failed to match function");
+ assert_eq!(matched_0, &function);
+
+ // Now we want to verify we do not match when the matched function is duplicated.
+ // NOTE: That in the case of identical functions in a set, we would prune them eagerly, so this is
+ // NOTE: not really indicative of a real scenario.
+ matched_functions.push(matched_func_1);
+
+ let matched_1 = matcher.match_function_from_constraints(&bn_function, &matched_functions);
+ assert_eq!(matched_1, None);
+}
+
+fn create_mock_type() -> Type {
+ // Build the type, this is quite annoying.
+ let int_class = IntegerClass::builder().width(64).signed(true).build();
+ let int_type = Type::builder()
+ .name("my_int".to_owned())
+ .class(int_class)
+ .build();
+
+ let struct_member_0 = StructureMember::builder()
+ .name("field_0")
+ .ty(int_type)
+ .offset(0)
+ .build();
+ let struct_class = StructureClass::builder()
+ .members(vec![struct_member_0])
+ .build();
+
+ Type::builder()
+ .name("my_struct")
+ .class(struct_class)
+ .build()
+}
+
+#[test]
+fn test_add_type_to_view() {
+ let matcher_settings = MatcherSettings::default();
+ let matcher = Matcher::new(matcher_settings);
+
+ // Add a source type that we can reference and pull in from the container.
+ let struct_type = create_mock_type();
+ let struct_type_guid = TypeGUID::from(&struct_type);
+ let struct_type_name = struct_type.name.clone().expect("Type should have name");
+
+ let source = SourceId::new();
+ let container = MemoryContainer::new().with_source_type(source, struct_type_guid, struct_type);
+
+ let _session = Session::new().expect("Failed to create session");
+ let file = FileMetadata::new();
+ let view = BinaryView::from_data(&file, &[]).expect("Failed to create view");
+ let arch = CoreArchitecture::by_name("x86").expect("Failed to get architecture");
+
+ // Try and add a NTR to the view, this should also add the referenced struct type.
+ let ref_class = ReferrerClass::builder()
+ .name(struct_type_name)
+ .guid(struct_type_guid)
+ .build();
+ let ref_type = Type::builder().name("my_ref").class(ref_class).build();
+ matcher.add_type_to_view(&container, &source, &view, &arch, &ref_type);
+
+ println!("{:#?}", view.types().to_vec());
+
+ // Verify the type was added to the view.
+ let found_type = view
+ .type_by_name("my_struct")
+ .expect("Failed to find added type");
+ // Make sure we actually added it as a structure type.
+ assert_eq!(found_type.type_class(), BNTypeClass::StructureTypeClass);
+}
diff --git a/plugins/warp/tests/processor.rs b/plugins/warp/tests/processor.rs
new file mode 100644
index 00000000..12749779
--- /dev/null
+++ b/plugins/warp/tests/processor.rs
@@ -0,0 +1,62 @@
+use std::path::PathBuf;
+use tempdir::TempDir;
+use warp_ninja::processor::WarpFileProcessor;
+
+// These are the target files present in OUT_DIR
+// Add the files to fixtures/bin
+static BIN_TARGET_FILES: [&str; 9] = [
+ "_ctype.obj",
+ "_fptostr.obj",
+ "_mbslen.obj",
+ "_memicmp.obj",
+ "_strnicm.obj",
+ "_wctype.obj",
+ "atof.obj",
+ "atoldbl.obj",
+ "atox.obj",
+];
+
+#[test]
+fn test_processor() {
+ let out_dir = env!("OUT_DIR").parse::<PathBuf>().unwrap();
+ let _headless_session =
+ binaryninja::headless::Session::new().expect("Failed to initialize session");
+
+ let processor = WarpFileProcessor::new();
+
+ // All files should process and not error.
+ for file_name in BIN_TARGET_FILES {
+ let path = out_dir.join(file_name);
+ processor.process(path).unwrap();
+ }
+
+ // We should be able to process a warp file.
+ let warp_path = out_dir.join("random.warp");
+ processor.process(warp_path).unwrap();
+}
+
+#[test]
+fn test_caching() {
+ let out_dir = env!("OUT_DIR").parse::<PathBuf>().unwrap();
+ let cache_dir = TempDir::new("tmp_cache").unwrap();
+ let _headless_session =
+ binaryninja::headless::Session::new().expect("Failed to initialize session");
+
+ let processor = WarpFileProcessor::new().with_cache_path(cache_dir.path().to_path_buf());
+
+ // Go through files, this should cache the databases.
+ for file_name in BIN_TARGET_FILES {
+ let path = out_dir.join(file_name);
+ processor.process(path).unwrap();
+ }
+
+ // Verify the databases were saved to the cache.
+ let mut cached_paths = Vec::new();
+ for entry in std::fs::read_dir(cache_dir.path()).expect("Failed to read cache dir") {
+ let entry = entry.expect("Failed to read cache dir entry");
+ let path = entry.path();
+ assert!(path.is_file());
+ cached_paths.push(path);
+ }
+ assert_eq!(BIN_TARGET_FILES.len(), cached_paths.len());
+}
diff --git a/plugins/warp/src/snapshots/warp_ninja__tests__snapshot__ctype.snap b/plugins/warp/tests/snapshots/determinism___ctype.obj.snap
index 0cda5d3a..a0ef5ea0 100644
--- a/plugins/warp/src/snapshots/warp_ninja__tests__snapshot__ctype.snap
+++ b/plugins/warp/tests/snapshots/determinism___ctype.obj.snap
@@ -1,126 +1,126 @@
---
-source: plugins/warp/src/lib.rs
+source: plugins/warp/tests/determinism.rs
expression: functions
---
-[
- FunctionGUID {
- guid: 00c71b63-3039-5aa3-94f7-1737530239d9,
- },
- FunctionGUID {
- guid: 06ff982d-b167-5dce-9689-71a631820da6,
+{
+ 181712: FunctionGUID {
+ guid: 29690354-fa27-54d1-a8be-18535b19b1c3,
},
- FunctionGUID {
- guid: 0d6d3685-7a8a-5d1c-8b7a-fe14871218ff,
+ 182032: FunctionGUID {
+ guid: 1e794537-6289-59e7-bef9-0c72f3989db8,
},
- FunctionGUID {
- guid: 0df54a49-5267-52f7-9665-70a8e66ad0dd,
+ 182720: FunctionGUID {
+ guid: 6abe3fc2-6d29-5fde-a31e-bb8249db6bf9,
},
- FunctionGUID {
+ 183072: FunctionGUID {
guid: 13b16f81-0c6f-5aee-ad9f-0d142658cf18,
},
- FunctionGUID {
- guid: 15d686e8-3e80-5632-9ce7-17c3510f8238,
- },
- FunctionGUID {
- guid: 18cfce71-47bc-5595-89cb-06e0563b211d,
+ 183376: FunctionGUID {
+ guid: 8a2b7bda-5fdb-5ba3-888b-20d5b8d7b2cd,
},
- FunctionGUID {
- guid: 1b6aa5a3-ac7f-542d-a0ab-bae8f142d8d8,
+ 183712: FunctionGUID {
+ guid: 53b484f3-751a-505d-beae-acc9d69c261d,
},
- FunctionGUID {
- guid: 1e794537-6289-59e7-bef9-0c72f3989db8,
+ 184032: FunctionGUID {
+ guid: 88d9b26b-3884-58d6-b164-435d8d888e5c,
},
- FunctionGUID {
- guid: 29690354-fa27-54d1-a8be-18535b19b1c3,
+ 184400: FunctionGUID {
+ guid: 3cc2e827-b707-5c9d-82e9-d76ac3abc904,
},
- FunctionGUID {
- guid: 33e6bc5f-eeb6-5a07-810e-f04a2dba36cf,
+ 184736: FunctionGUID {
+ guid: 7f0e055b-1e83-5f7b-a1fd-6a0a3d0b74f5,
},
- FunctionGUID {
- guid: 3b992cd7-1721-5f30-b59c-84ea682be808,
+ 185040: FunctionGUID {
+ guid: c7bd1444-8f5c-5bc6-87c3-48f010b00455,
},
- FunctionGUID {
- guid: 3ba4d0cb-9c07-5904-aafe-34e051b89be5,
+ 185376: FunctionGUID {
+ guid: 18cfce71-47bc-5595-89cb-06e0563b211d,
},
- FunctionGUID {
- guid: 3cc2e827-b707-5c9d-82e9-d76ac3abc904,
+ 185712: FunctionGUID {
+ guid: 8ff1fc25-6912-5772-8fa3-19e1b996aea7,
},
- FunctionGUID {
- guid: 43a8c54b-dd4f-5334-ae00-218cb758ad4c,
+ 186000: FunctionGUID {
+ guid: 5b7f41e9-de1e-558c-ac7d-888acca3a76c,
},
- FunctionGUID {
- guid: 53b484f3-751a-505d-beae-acc9d69c261d,
+ 186352: FunctionGUID {
+ guid: cdb3f650-974f-5686-b81f-10b6544680f4,
},
- FunctionGUID {
- guid: 5b7cc78f-3b2f-5b82-9726-37c26ad087b4,
+ 186784: FunctionGUID {
+ guid: 9e30f7d4-0a31-50cf-93f8-490a9e8c300b,
},
- FunctionGUID {
- guid: 5b7f41e9-de1e-558c-ac7d-888acca3a76c,
+ 187216: FunctionGUID {
+ guid: 0df54a49-5267-52f7-9665-70a8e66ad0dd,
},
- FunctionGUID {
- guid: 6abe3fc2-6d29-5fde-a31e-bb8249db6bf9,
+ 187664: FunctionGUID {
+ guid: 43a8c54b-dd4f-5334-ae00-218cb758ad4c,
},
- FunctionGUID {
- guid: 6db530d1-8ea4-568e-a089-c61164851f04,
+ 188096: FunctionGUID {
+ guid: 5b7cc78f-3b2f-5b82-9726-37c26ad087b4,
},
- FunctionGUID {
+ 188512: FunctionGUID {
guid: 7cae2466-6b19-5fff-869e-9a9717043df4,
},
- FunctionGUID {
+ 188864: FunctionGUID {
guid: 7cae2466-6b19-5fff-869e-9a9717043df4,
},
- FunctionGUID {
- guid: 7f0e055b-1e83-5f7b-a1fd-6a0a3d0b74f5,
+ 189216: FunctionGUID {
+ guid: 1b6aa5a3-ac7f-542d-a0ab-bae8f142d8d8,
},
- FunctionGUID {
- guid: 88d9b26b-3884-58d6-b164-435d8d888e5c,
+ 189632: FunctionGUID {
+ guid: 951d5c60-d457-5b62-98e7-ade4a37d7cbe,
},
- FunctionGUID {
- guid: 8a2b7bda-5fdb-5ba3-888b-20d5b8d7b2cd,
+ 190064: FunctionGUID {
+ guid: a7acb567-eb98-53e4-a843-5c60a8c59f19,
},
- FunctionGUID {
- guid: 8ff1fc25-6912-5772-8fa3-19e1b996aea7,
+ 190480: FunctionGUID {
+ guid: d5456209-db22-53a1-906d-1ecb3254ab2d,
},
- FunctionGUID {
- guid: 951d5c60-d457-5b62-98e7-ade4a37d7cbe,
+ 190912: FunctionGUID {
+ guid: bc4d18a4-96d5-5322-ba99-fcee83648701,
},
- FunctionGUID {
- guid: 95bf9d56-3b11-515f-960f-07abf69aed95,
+ 191328: FunctionGUID {
+ guid: ec40932b-8293-5ffb-9d31-04c282b6530d,
},
- FunctionGUID {
- guid: 9e30f7d4-0a31-50cf-93f8-490a9e8c300b,
+ 191744: FunctionGUID {
+ guid: 15d686e8-3e80-5632-9ce7-17c3510f8238,
},
- FunctionGUID {
- guid: a7acb567-eb98-53e4-a843-5c60a8c59f19,
+ 192160: FunctionGUID {
+ guid: faedbe75-8451-525b-83da-7a57ec96166e,
},
- FunctionGUID {
- guid: ae6f9d26-872c-5b6e-925e-40c0ec95c702,
+ 192592: FunctionGUID {
+ guid: 06ff982d-b167-5dce-9689-71a631820da6,
},
- FunctionGUID {
- guid: b8b2746b-20aa-5ad2-bcfd-ab5448d952eb,
+ 192944: FunctionGUID {
+ guid: 3b992cd7-1721-5f30-b59c-84ea682be808,
},
- FunctionGUID {
- guid: bc4d18a4-96d5-5322-ba99-fcee83648701,
+ 193296: FunctionGUID {
+ guid: 6db530d1-8ea4-568e-a089-c61164851f04,
},
- FunctionGUID {
- guid: c7bd1444-8f5c-5bc6-87c3-48f010b00455,
+ 193680: FunctionGUID {
+ guid: 95bf9d56-3b11-515f-960f-07abf69aed95,
},
- FunctionGUID {
+ 194032: FunctionGUID {
guid: cacf9fe4-7518-5b22-ae3a-ab775dea4c9a,
},
- FunctionGUID {
- guid: cdb3f650-974f-5686-b81f-10b6544680f4,
+ 194384: FunctionGUID {
+ guid: 00c71b63-3039-5aa3-94f7-1737530239d9,
+ },
+ 194736: FunctionGUID {
+ guid: 33e6bc5f-eeb6-5a07-810e-f04a2dba36cf,
},
- FunctionGUID {
+ 195088: FunctionGUID {
guid: d344f254-4903-581d-ad93-713c9ff2be2e,
},
- FunctionGUID {
- guid: d5456209-db22-53a1-906d-1ecb3254ab2d,
+ 195440: FunctionGUID {
+ guid: 3ba4d0cb-9c07-5904-aafe-34e051b89be5,
},
- FunctionGUID {
- guid: ec40932b-8293-5ffb-9d31-04c282b6530d,
+ 195792: FunctionGUID {
+ guid: b8b2746b-20aa-5ad2-bcfd-ab5448d952eb,
},
- FunctionGUID {
- guid: faedbe75-8451-525b-83da-7a57ec96166e,
+ 196144: FunctionGUID {
+ guid: ae6f9d26-872c-5b6e-925e-40c0ec95c702,
+ },
+ 196496: FunctionGUID {
+ guid: 0d6d3685-7a8a-5d1c-8b7a-fe14871218ff,
},
-]
+}
diff --git a/plugins/warp/src/snapshots/warp_ninja__tests__snapshot__fptostr.snap b/plugins/warp/tests/snapshots/determinism___fptostr.obj.snap
index 5628853f..717dc070 100644
--- a/plugins/warp/src/snapshots/warp_ninja__tests__snapshot__fptostr.snap
+++ b/plugins/warp/tests/snapshots/determinism___fptostr.obj.snap
@@ -1,9 +1,9 @@
---
-source: plugins/warp/src/lib.rs
+source: plugins/warp/tests/determinism.rs
expression: functions
---
-[
- FunctionGUID {
+{
+ 180832: FunctionGUID {
guid: 9d4dba66-7106-5215-8e64-a47279f67dfe,
},
-]
+}
diff --git a/plugins/warp/src/snapshots/warp_ninja__tests__snapshot__mbslen.snap b/plugins/warp/tests/snapshots/determinism___mbslen.obj.snap
index 01ca9ec5..848666d9 100644
--- a/plugins/warp/src/snapshots/warp_ninja__tests__snapshot__mbslen.snap
+++ b/plugins/warp/tests/snapshots/determinism___mbslen.obj.snap
@@ -1,39 +1,39 @@
---
-source: plugins/warp/src/lib.rs
+source: plugins/warp/tests/determinism.rs
expression: functions
---
-[
- FunctionGUID {
- guid: 13b16f81-0c6f-5aee-ad9f-0d142658cf18,
- },
- FunctionGUID {
- guid: 1e794537-6289-59e7-bef9-0c72f3989db8,
- },
- FunctionGUID {
+{
+ 178448: FunctionGUID {
guid: 29690354-fa27-54d1-a8be-18535b19b1c3,
},
- FunctionGUID {
- guid: 3cc2e827-b707-5c9d-82e9-d76ac3abc904,
- },
- FunctionGUID {
- guid: 6a7eb6dd-be3e-50dd-ae91-cea8f3eb4f06,
+ 178768: FunctionGUID {
+ guid: 1e794537-6289-59e7-bef9-0c72f3989db8,
},
- FunctionGUID {
+ 179456: FunctionGUID {
guid: 6abe3fc2-6d29-5fde-a31e-bb8249db6bf9,
},
- FunctionGUID {
- guid: 6b0d368e-83d9-55fc-9683-5812e651a49b,
+ 179808: FunctionGUID {
+ guid: 13b16f81-0c6f-5aee-ad9f-0d142658cf18,
},
- FunctionGUID {
+ 180112: FunctionGUID {
guid: 8a2b7bda-5fdb-5ba3-888b-20d5b8d7b2cd,
},
- FunctionGUID {
- guid: bfaf2f0a-d92d-5ef7-879d-86eb0ca01257,
+ 180448: FunctionGUID {
+ guid: 6b0d368e-83d9-55fc-9683-5812e651a49b,
+ },
+ 181632: FunctionGUID {
+ guid: 3cc2e827-b707-5c9d-82e9-d76ac3abc904,
},
- FunctionGUID {
+ 181968: FunctionGUID {
guid: f034945d-32e2-5d86-8a86-3761cc54f419,
},
- FunctionGUID {
+ 182352: FunctionGUID {
+ guid: 6a7eb6dd-be3e-50dd-ae91-cea8f3eb4f06,
+ },
+ 182688: FunctionGUID {
+ guid: bfaf2f0a-d92d-5ef7-879d-86eb0ca01257,
+ },
+ 183024: FunctionGUID {
guid: f552d8ee-3064-50ed-af18-4e60f095c9ea,
},
-]
+}
diff --git a/plugins/warp/src/snapshots/warp_ninja__tests__snapshot__memicmp.snap b/plugins/warp/tests/snapshots/determinism___memicmp.obj.snap
index 8f83530e..3c9b0358 100644
--- a/plugins/warp/src/snapshots/warp_ninja__tests__snapshot__memicmp.snap
+++ b/plugins/warp/tests/snapshots/determinism___memicmp.obj.snap
@@ -1,9 +1,9 @@
---
-source: plugins/warp/src/lib.rs
+source: plugins/warp/tests/determinism.rs
expression: functions
---
-[
- FunctionGUID {
+{
+ 65728: FunctionGUID {
guid: cf6e80f2-69aa-5757-a06f-fb2e4866ab14,
},
-]
+}
diff --git a/plugins/warp/src/snapshots/warp_ninja__tests__snapshot__strnicm.snap b/plugins/warp/tests/snapshots/determinism___strnicm.obj.snap
index 9d661016..4a118098 100644
--- a/plugins/warp/src/snapshots/warp_ninja__tests__snapshot__strnicm.snap
+++ b/plugins/warp/tests/snapshots/determinism___strnicm.obj.snap
@@ -1,9 +1,9 @@
---
-source: plugins/warp/src/lib.rs
+source: plugins/warp/tests/determinism.rs
expression: functions
---
-[
- FunctionGUID {
+{
+ 65728: FunctionGUID {
guid: 7c350a2d-2282-5625-943e-009f317b8d2c,
},
-]
+}
diff --git a/plugins/warp/src/snapshots/warp_ninja__tests__snapshot__wctype.snap b/plugins/warp/tests/snapshots/determinism___wctype.obj.snap
index 2d9f4e73..3da522a5 100644
--- a/plugins/warp/src/snapshots/warp_ninja__tests__snapshot__wctype.snap
+++ b/plugins/warp/tests/snapshots/determinism___wctype.obj.snap
@@ -1,117 +1,117 @@
---
-source: plugins/warp/src/lib.rs
+source: plugins/warp/tests/determinism.rs
expression: functions
---
-[
- FunctionGUID {
- guid: 0a9027e0-fc87-5f83-a9da-ea7b1eef0761,
+{
+ 181536: FunctionGUID {
+ guid: 29690354-fa27-54d1-a8be-18535b19b1c3,
},
- FunctionGUID {
- guid: 13b16f81-0c6f-5aee-ad9f-0d142658cf18,
+ 181856: FunctionGUID {
+ guid: 1e794537-6289-59e7-bef9-0c72f3989db8,
},
- FunctionGUID {
- guid: 1b434f0e-26b8-539b-9679-33d96a683190,
+ 182544: FunctionGUID {
+ guid: 6abe3fc2-6d29-5fde-a31e-bb8249db6bf9,
},
- FunctionGUID {
- guid: 1b434f0e-26b8-539b-9679-33d96a683190,
+ 182896: FunctionGUID {
+ guid: 13b16f81-0c6f-5aee-ad9f-0d142658cf18,
},
- FunctionGUID {
- guid: 1e34d278-fd31-5cdf-98dc-395cb29419a7,
+ 183200: FunctionGUID {
+ guid: 8a2b7bda-5fdb-5ba3-888b-20d5b8d7b2cd,
},
- FunctionGUID {
- guid: 1e34d278-fd31-5cdf-98dc-395cb29419a7,
+ 183536: FunctionGUID {
+ guid: 3cc2e827-b707-5c9d-82e9-d76ac3abc904,
},
- FunctionGUID {
- guid: 1e794537-6289-59e7-bef9-0c72f3989db8,
+ 183872: FunctionGUID {
+ guid: 1b434f0e-26b8-539b-9679-33d96a683190,
},
- FunctionGUID {
- guid: 23ba7382-bd48-5d47-a1f0-79ad5db87730,
+ 184208: FunctionGUID {
+ guid: b3723a76-a709-510c-9d93-97dc42b163a3,
},
- FunctionGUID {
- guid: 23ba7382-bd48-5d47-a1f0-79ad5db87730,
+ 184544: FunctionGUID {
+ guid: 576f40fd-30a0-53af-8102-7e1b5298d38a,
},
- FunctionGUID {
- guid: 29690354-fa27-54d1-a8be-18535b19b1c3,
- },
- FunctionGUID {
+ 184960: FunctionGUID {
guid: 33ce8a60-e3fa-5941-9ca6-09707bb1f579,
},
- FunctionGUID {
- guid: 33ce8a60-e3fa-5941-9ca6-09707bb1f579,
+ 185280: FunctionGUID {
+ guid: b2339a04-7dcf-5791-b74e-806220d37f56,
},
- FunctionGUID {
- guid: 3cc2e827-b707-5c9d-82e9-d76ac3abc904,
+ 185600: FunctionGUID {
+ guid: b08dcc9b-b766-56e0-a9af-1d24cc6c6574,
},
- FunctionGUID {
- guid: 607a10c5-8943-5dec-b888-f5210f08b311,
+ 185952: FunctionGUID {
+ guid: 1e34d278-fd31-5cdf-98dc-395cb29419a7,
},
- FunctionGUID {
- guid: 607a10c5-8943-5dec-b888-f5210f08b311,
+ 186272: FunctionGUID {
+ guid: 1b434f0e-26b8-539b-9679-33d96a683190,
},
- FunctionGUID {
- guid: 6abe3fc2-6d29-5fde-a31e-bb8249db6bf9,
+ 186624: FunctionGUID {
+ guid: b3723a76-a709-510c-9d93-97dc42b163a3,
},
- FunctionGUID {
- guid: 6cebba56-9929-5f3d-9dd3-fc56f8e84593,
+ 186976: FunctionGUID {
+ guid: eed08c42-3616-5c27-82ad-719db5cce36e,
},
- FunctionGUID {
- guid: 75b0aba8-582a-5085-abbb-cebf55d2498d,
+ 187296: FunctionGUID {
+ guid: 23ba7382-bd48-5d47-a1f0-79ad5db87730,
+ },
+ 187616: FunctionGUID {
+ guid: 90cb6aa0-f402-5874-ae2d-e10752ad6462,
},
- FunctionGUID {
+ 187936: FunctionGUID {
guid: 75b0aba8-582a-5085-abbb-cebf55d2498d,
},
- FunctionGUID {
- guid: 8a2b7bda-5fdb-5ba3-888b-20d5b8d7b2cd,
+ 188256: FunctionGUID {
+ guid: cc259c7e-0ada-5680-8ad7-b077d60bd2a1,
},
- FunctionGUID {
- guid: 90cb6aa0-f402-5874-ae2d-e10752ad6462,
+ 188576: FunctionGUID {
+ guid: 607a10c5-8943-5dec-b888-f5210f08b311,
},
- FunctionGUID {
- guid: 90cb6aa0-f402-5874-ae2d-e10752ad6462,
+ 188896: FunctionGUID {
+ guid: f6535880-21d4-5272-b2c0-5e63cbc208e7,
},
- FunctionGUID {
+ 189216: FunctionGUID {
guid: 9dd8ec46-e680-5fbe-b28b-30505b505fa3,
},
- FunctionGUID {
- guid: 9dd8ec46-e680-5fbe-b28b-30505b505fa3,
+ 189552: FunctionGUID {
+ guid: b098f8a1-ecdd-5938-8da3-bb37935c6b7e,
},
- FunctionGUID {
- guid: b08dcc9b-b766-56e0-a9af-1d24cc6c6574,
+ 189856: FunctionGUID {
+ guid: 33ce8a60-e3fa-5941-9ca6-09707bb1f579,
},
- FunctionGUID {
- guid: b08dcc9b-b766-56e0-a9af-1d24cc6c6574,
+ 190160: FunctionGUID {
+ guid: b2339a04-7dcf-5791-b74e-806220d37f56,
},
- FunctionGUID {
- guid: b098f8a1-ecdd-5938-8da3-bb37935c6b7e,
+ 190464: FunctionGUID {
+ guid: 6cebba56-9929-5f3d-9dd3-fc56f8e84593,
},
- FunctionGUID {
- guid: b2339a04-7dcf-5791-b74e-806220d37f56,
+ 190768: FunctionGUID {
+ guid: b08dcc9b-b766-56e0-a9af-1d24cc6c6574,
},
- FunctionGUID {
- guid: b2339a04-7dcf-5791-b74e-806220d37f56,
+ 191088: FunctionGUID {
+ guid: 1e34d278-fd31-5cdf-98dc-395cb29419a7,
},
- FunctionGUID {
- guid: b3723a76-a709-510c-9d93-97dc42b163a3,
+ 191392: FunctionGUID {
+ guid: eed08c42-3616-5c27-82ad-719db5cce36e,
},
- FunctionGUID {
- guid: b3723a76-a709-510c-9d93-97dc42b163a3,
+ 191696: FunctionGUID {
+ guid: 23ba7382-bd48-5d47-a1f0-79ad5db87730,
},
- FunctionGUID {
- guid: cc259c7e-0ada-5680-8ad7-b077d60bd2a1,
+ 192000: FunctionGUID {
+ guid: 90cb6aa0-f402-5874-ae2d-e10752ad6462,
},
- FunctionGUID {
- guid: cc259c7e-0ada-5680-8ad7-b077d60bd2a1,
+ 192304: FunctionGUID {
+ guid: 75b0aba8-582a-5085-abbb-cebf55d2498d,
},
- FunctionGUID {
- guid: eed08c42-3616-5c27-82ad-719db5cce36e,
+ 192608: FunctionGUID {
+ guid: cc259c7e-0ada-5680-8ad7-b077d60bd2a1,
},
- FunctionGUID {
- guid: eed08c42-3616-5c27-82ad-719db5cce36e,
+ 192912: FunctionGUID {
+ guid: 607a10c5-8943-5dec-b888-f5210f08b311,
},
- FunctionGUID {
+ 193216: FunctionGUID {
guid: f6535880-21d4-5272-b2c0-5e63cbc208e7,
},
- FunctionGUID {
- guid: f6535880-21d4-5272-b2c0-5e63cbc208e7,
+ 193520: FunctionGUID {
+ guid: 9dd8ec46-e680-5fbe-b28b-30505b505fa3,
},
-]
+}
diff --git a/plugins/warp/src/snapshots/warp_ninja__tests__snapshot_atof.snap b/plugins/warp/tests/snapshots/determinism__atof.obj.snap
index 4ff8251c..a4841378 100644
--- a/plugins/warp/src/snapshots/warp_ninja__tests__snapshot_atof.snap
+++ b/plugins/warp/tests/snapshots/determinism__atof.obj.snap
@@ -1,390 +1,390 @@
---
-source: plugins/warp/src/lib.rs
+source: plugins/warp/tests/determinism.rs
expression: functions
---
-[
- FunctionGUID {
- guid: 02d864ef-80bc-5265-b78f-0e42815a4d1d,
+{
+ 234752: FunctionGUID {
+ guid: 29690354-fa27-54d1-a8be-18535b19b1c3,
},
- FunctionGUID {
- guid: 02d864ef-80bc-5265-b78f-0e42815a4d1d,
+ 235072: FunctionGUID {
+ guid: d8ba616a-1747-587c-9265-cd4e26ae35c8,
},
- FunctionGUID {
- guid: 06a9339a-7249-50f3-9c3d-131d3248d560,
+ 235536: FunctionGUID {
+ guid: 1b1669c2-4a81-5300-99f7-14cba1cc7b24,
},
- FunctionGUID {
- guid: 06a9339a-7249-50f3-9c3d-131d3248d560,
+ 236016: FunctionGUID {
+ guid: 72452dc5-da9c-54fc-82de-032aab1780a4,
},
- FunctionGUID {
- guid: 06a9339a-7249-50f3-9c3d-131d3248d560,
+ 236528: FunctionGUID {
+ guid: 87371a48-38c9-5e0e-81db-5e2d6da49202,
},
- FunctionGUID {
- guid: 06a9339a-7249-50f3-9c3d-131d3248d560,
+ 237088: FunctionGUID {
+ guid: c74ed160-24fe-50d7-ba1c-9751c4599420,
},
- FunctionGUID {
- guid: 07c4bac9-cf7d-5e00-833d-d23ba44c07d9,
+ 237600: FunctionGUID {
+ guid: 1eee5e05-95ef-5f99-8713-504c2003183c,
},
- FunctionGUID {
- guid: 0916139e-a5db-51c8-be24-2c6fb20a9e3a,
+ 238160: FunctionGUID {
+ guid: 65be15d9-ef02-58a4-af63-b40ddb3e56b7,
},
- FunctionGUID {
- guid: 0c7b078d-dafb-5be7-8b10-a25b1e691b33,
+ 238672: FunctionGUID {
+ guid: 3efd6327-6038-53ed-904c-a800bee86d2e,
+ },
+ 239232: FunctionGUID {
+ guid: 82e98c80-859c-5ab3-83ab-b597fc520a3f,
+ },
+ 239808: FunctionGUID {
+ guid: c44bdef1-d78a-57ec-bff5-4e7d32ff4337,
+ },
+ 240448: FunctionGUID {
+ guid: 7cbd1cef-0134-5a29-a8c6-82697f5717fe,
},
- FunctionGUID {
+ 240960: FunctionGUID {
+ guid: e4ba95bb-0221-5dbd-9884-13a39c4f9d55,
+ },
+ 241520: FunctionGUID {
guid: 0e6feb85-ff6f-5b86-bd2c-26fb9829241c,
},
- FunctionGUID {
+ 242464: FunctionGUID {
guid: 0e6feb85-ff6f-5b86-bd2c-26fb9829241c,
},
- FunctionGUID {
- guid: 11592e15-a2a6-51b3-bda2-534cc0434df0,
+ 243408: FunctionGUID {
+ guid: a0c07bfa-a027-5c19-9da4-d2d35d7beb40,
},
- FunctionGUID {
- guid: 13b16f81-0c6f-5aee-ad9f-0d142658cf18,
+ 244208: FunctionGUID {
+ guid: a0c07bfa-a027-5c19-9da4-d2d35d7beb40,
},
- FunctionGUID {
- guid: 1b1669c2-4a81-5300-99f7-14cba1cc7b24,
+ 245008: FunctionGUID {
+ guid: 06a9339a-7249-50f3-9c3d-131d3248d560,
},
- FunctionGUID {
- guid: 1b969932-96ce-5a6f-b91c-82e5f7b7f33d,
+ 245408: FunctionGUID {
+ guid: 06a9339a-7249-50f3-9c3d-131d3248d560,
},
- FunctionGUID {
- guid: 1c037c1e-9af1-59af-b19e-122fdf885148,
+ 245808: FunctionGUID {
+ guid: 06a9339a-7249-50f3-9c3d-131d3248d560,
},
- FunctionGUID {
- guid: 1de88f85-a19c-5454-9122-ddc80f7509b4,
+ 246208: FunctionGUID {
+ guid: 06a9339a-7249-50f3-9c3d-131d3248d560,
},
- FunctionGUID {
+ 246608: FunctionGUID {
guid: 1de88f85-a19c-5454-9122-ddc80f7509b4,
},
- FunctionGUID {
- guid: 1e794537-6289-59e7-bef9-0c72f3989db8,
- },
- FunctionGUID {
- guid: 1eee5e05-95ef-5f99-8713-504c2003183c,
+ 246992: FunctionGUID {
+ guid: 1de88f85-a19c-5454-9122-ddc80f7509b4,
},
- FunctionGUID {
- guid: 2833f544-9acb-552d-aa4b-df7d51dbab64,
+ 247376: FunctionGUID {
+ guid: ce28e080-0609-5bcd-abe1-2a6e57c91f83,
},
- FunctionGUID {
- guid: 29690354-fa27-54d1-a8be-18535b19b1c3,
+ 248672: FunctionGUID {
+ guid: ce28e080-0609-5bcd-abe1-2a6e57c91f83,
},
- FunctionGUID {
- guid: 31a8e73e-74fe-5a33-b0ab-aa1b136e021d,
+ 249968: FunctionGUID {
+ guid: ce28e080-0609-5bcd-abe1-2a6e57c91f83,
},
- FunctionGUID {
- guid: 364c5864-0cd1-5527-8ca0-2378d5b4c0a5,
+ 251264: FunctionGUID {
+ guid: 41fd6d24-0f8b-5848-9873-65b98bd370ab,
},
- FunctionGUID {
- guid: 3c38a678-9c4e-527a-b543-3075b345d532,
+ 255600: FunctionGUID {
+ guid: 89f0e45b-c1f6-5aeb-94a8-26e72bb66e6c,
},
- FunctionGUID {
- guid: 3c38a678-9c4e-527a-b543-3075b345d532,
+ 259968: FunctionGUID {
+ guid: 4f645f3c-3e89-5b5d-a185-9f1a25e94ed2,
},
- FunctionGUID {
- guid: 3cc2e827-b707-5c9d-82e9-d76ac3abc904,
+ 261152: FunctionGUID {
+ guid: 4b60a419-4ad1-5808-b41f-d341868c9234,
},
- FunctionGUID {
- guid: 3eb2a4c5-84f5-5687-92a0-35ef66c8b746,
+ 262352: FunctionGUID {
+ guid: 7c43c697-22ef-57a9-a43a-2214e627749d,
},
- FunctionGUID {
- guid: 3efd6327-6038-53ed-904c-a800bee86d2e,
+ 263792: FunctionGUID {
+ guid: cfa4374b-0fc7-575d-9ba1-4a37d2de0406,
},
- FunctionGUID {
- guid: 40358a63-7e16-52da-9270-434c7d444b10,
+ 265248: FunctionGUID {
+ guid: 45770948-1c56-5b24-bd38-f5a559ae8d22,
},
- FunctionGUID {
- guid: 40358a63-7e16-52da-9270-434c7d444b10,
+ 265792: FunctionGUID {
+ guid: 45770948-1c56-5b24-bd38-f5a559ae8d22,
},
- FunctionGUID {
- guid: 40358a63-7e16-52da-9270-434c7d444b10,
+ 266336: FunctionGUID {
+ guid: cf4808fe-2fea-517d-8f85-f88ddc96cc78,
},
- FunctionGUID {
- guid: 410b1cab-55ff-5931-a894-54842e56bee2,
+ 266880: FunctionGUID {
+ guid: cf4808fe-2fea-517d-8f85-f88ddc96cc78,
},
- FunctionGUID {
- guid: 41fd6d24-0f8b-5848-9873-65b98bd370ab,
+ 267424: FunctionGUID {
+ guid: 86ab6ab4-45e5-5c08-9a47-05344ea503ab,
},
- FunctionGUID {
- guid: 4345609a-e619-5f33-84ca-7cc1ccdfa2d0,
+ 268912: FunctionGUID {
+ guid: 86ab6ab4-45e5-5c08-9a47-05344ea503ab,
},
- FunctionGUID {
- guid: 43a8c54b-dd4f-5334-ae00-218cb758ad4c,
+ 270400: FunctionGUID {
+ guid: 364c5864-0cd1-5527-8ca0-2378d5b4c0a5,
},
- FunctionGUID {
- guid: 45770948-1c56-5b24-bd38-f5a559ae8d22,
+ 271040: FunctionGUID {
+ guid: a5b947d4-a7e0-5072-86b3-240474c16595,
},
- FunctionGUID {
- guid: 45770948-1c56-5b24-bd38-f5a559ae8d22,
+ 271696: FunctionGUID {
+ guid: a707a2d3-c7d2-5b93-97c9-1db77d8ccfe5,
},
- FunctionGUID {
- guid: 479b6feb-0dff-5c33-8f30-fd0c563b9abc,
+ 272128: FunctionGUID {
+ guid: a707a2d3-c7d2-5b93-97c9-1db77d8ccfe5,
},
- FunctionGUID {
- guid: 4b60a419-4ad1-5808-b41f-d341868c9234,
+ 272560: FunctionGUID {
+ guid: a707a2d3-c7d2-5b93-97c9-1db77d8ccfe5,
},
- FunctionGUID {
- guid: 4ba8a311-60f3-57d4-a17a-c0cc93f5c2a4,
+ 272992: FunctionGUID {
+ guid: a707a2d3-c7d2-5b93-97c9-1db77d8ccfe5,
},
- FunctionGUID {
- guid: 4f0bdeb9-691c-52f8-8541-6c834f3fbe64,
+ 273424: FunctionGUID {
+ guid: a707a2d3-c7d2-5b93-97c9-1db77d8ccfe5,
},
- FunctionGUID {
- guid: 4f645f3c-3e89-5b5d-a185-9f1a25e94ed2,
+ 273856: FunctionGUID {
+ guid: a707a2d3-c7d2-5b93-97c9-1db77d8ccfe5,
},
- FunctionGUID {
- guid: 52b2a610-5304-5e4f-8b79-9b975e9efb86,
+ 274288: FunctionGUID {
+ guid: a482559e-6a69-505e-b692-147deeced692,
},
- FunctionGUID {
- guid: 53b484f3-751a-505d-beae-acc9d69c261d,
+ 274720: FunctionGUID {
+ guid: a482559e-6a69-505e-b692-147deeced692,
},
- FunctionGUID {
- guid: 53b484f3-751a-505d-beae-acc9d69c261d,
+ 275152: FunctionGUID {
+ guid: 1e794537-6289-59e7-bef9-0c72f3989db8,
},
- FunctionGUID {
- guid: 56c82b1f-380c-52fe-ae70-a39962e5ffdc,
+ 275840: FunctionGUID {
+ guid: bbb9b018-a0cd-5f98-b941-367f8707434d,
},
- FunctionGUID {
- guid: 5779439c-a22f-5d33-8868-4e1832ad49e9,
+ 276224: FunctionGUID {
+ guid: 879b1d59-7301-5cb7-911e-db05292bee8f,
},
- FunctionGUID {
- guid: 5b55a8c8-1d3a-5825-a0d9-917f434e07af,
+ 276592: FunctionGUID {
+ guid: b9111cf4-dcff-52a5-8755-2dedea6d5c72,
},
- FunctionGUID {
- guid: 5b7f41e9-de1e-558c-ac7d-888acca3a76c,
+ 277040: FunctionGUID {
+ guid: 2833f544-9acb-552d-aa4b-df7d51dbab64,
},
- FunctionGUID {
- guid: 65be15d9-ef02-58a4-af63-b40ddb3e56b7,
+ 277488: FunctionGUID {
+ guid: 7995ca73-04fe-5b30-9e88-1e69197007c1,
},
- FunctionGUID {
- guid: 669a32f2-f1e9-5b38-98aa-9eb8431ebc4e,
+ 277872: FunctionGUID {
+ guid: 7995ca73-04fe-5b30-9e88-1e69197007c1,
},
- FunctionGUID {
+ 278256: FunctionGUID {
guid: 6abe3fc2-6d29-5fde-a31e-bb8249db6bf9,
},
- FunctionGUID {
- guid: 6c01cd47-89a2-59f3-ac6a-cd7806fb8f79,
+ 278608: FunctionGUID {
+ guid: bbb9b018-a0cd-5f98-b941-367f8707434d,
},
- FunctionGUID {
- guid: 72452dc5-da9c-54fc-82de-032aab1780a4,
+ 279008: FunctionGUID {
+ guid: 0c7b078d-dafb-5be7-8b10-a25b1e691b33,
},
- FunctionGUID {
- guid: 7995ca73-04fe-5b30-9e88-1e69197007c1,
+ 279536: FunctionGUID {
+ guid: 40358a63-7e16-52da-9270-434c7d444b10,
},
- FunctionGUID {
- guid: 7995ca73-04fe-5b30-9e88-1e69197007c1,
+ 279952: FunctionGUID {
+ guid: 40358a63-7e16-52da-9270-434c7d444b10,
},
- FunctionGUID {
- guid: 7c43c697-22ef-57a9-a43a-2214e627749d,
+ 280368: FunctionGUID {
+ guid: 8c736b66-b63f-5000-a060-2e60a1b8952a,
},
- FunctionGUID {
- guid: 7cbd1cef-0134-5a29-a8c6-82697f5717fe,
+ 280784: FunctionGUID {
+ guid: 8c736b66-b63f-5000-a060-2e60a1b8952a,
},
- FunctionGUID {
- guid: 7d4d1429-5fef-5c8b-9074-93e9d97ab57e,
+ 281200: FunctionGUID {
+ guid: 8c736b66-b63f-5000-a060-2e60a1b8952a,
},
- FunctionGUID {
- guid: 82e98c80-859c-5ab3-83ab-b597fc520a3f,
+ 281616: FunctionGUID {
+ guid: 40358a63-7e16-52da-9270-434c7d444b10,
},
- FunctionGUID {
- guid: 84429ba2-9f72-585f-8cda-31e8ef4983fd,
+ 282032: FunctionGUID {
+ guid: 13b16f81-0c6f-5aee-ad9f-0d142658cf18,
},
- FunctionGUID {
- guid: 86ab6ab4-45e5-5c08-9a47-05344ea503ab,
+ 282336: FunctionGUID {
+ guid: 8a2b7bda-5fdb-5ba3-888b-20d5b8d7b2cd,
},
- FunctionGUID {
- guid: 86ab6ab4-45e5-5c08-9a47-05344ea503ab,
+ 282672: FunctionGUID {
+ guid: 3eb2a4c5-84f5-5687-92a0-35ef66c8b746,
},
- FunctionGUID {
- guid: 87371a48-38c9-5e0e-81db-5e2d6da49202,
+ 283472: FunctionGUID {
+ guid: 5b55a8c8-1d3a-5825-a0d9-917f434e07af,
},
- FunctionGUID {
- guid: 879b1d59-7301-5cb7-911e-db05292bee8f,
+ 284352: FunctionGUID {
+ guid: f3d57592-4cb1-5356-9591-1f9327c58565,
},
- FunctionGUID {
- guid: 88d9b26b-3884-58d6-b164-435d8d888e5c,
+ 284768: FunctionGUID {
+ guid: fb20fa7c-c796-58e4-a2f9-7a128baaf5b1,
},
- FunctionGUID {
- guid: 89f0e45b-c1f6-5aeb-94a8-26e72bb66e6c,
+ 285216: FunctionGUID {
+ guid: 1c037c1e-9af1-59af-b19e-122fdf885148,
},
- FunctionGUID {
- guid: 8a2b7bda-5fdb-5ba3-888b-20d5b8d7b2cd,
+ 285712: FunctionGUID {
+ guid: 4345609a-e619-5f33-84ca-7cc1ccdfa2d0,
},
- FunctionGUID {
- guid: 8c736b66-b63f-5000-a060-2e60a1b8952a,
+ 286208: FunctionGUID {
+ guid: c71f4559-f0af-54dc-a224-e1f341311988,
},
- FunctionGUID {
- guid: 8c736b66-b63f-5000-a060-2e60a1b8952a,
+ 286688: FunctionGUID {
+ guid: 479b6feb-0dff-5c33-8f30-fd0c563b9abc,
},
- FunctionGUID {
- guid: 8c736b66-b63f-5000-a060-2e60a1b8952a,
+ 288432: FunctionGUID {
+ guid: 7d4d1429-5fef-5c8b-9074-93e9d97ab57e,
},
- FunctionGUID {
- guid: 8dca0c9d-bdb7-56a1-9832-431aec16a68f,
+ 290768: FunctionGUID {
+ guid: c71f4559-f0af-54dc-a224-e1f341311988,
},
- FunctionGUID {
+ 291232: FunctionGUID {
guid: 9a9e3287-e407-5526-885b-1b5ee78a5300,
},
- FunctionGUID {
- guid: 9e4c3806-b291-569c-a687-0bcaa48e248b,
+ 291600: FunctionGUID {
+ guid: 84429ba2-9f72-585f-8cda-31e8ef4983fd,
},
- FunctionGUID {
- guid: 9ef21651-3eaf-5940-8e72-104bda834e85,
+ 291984: FunctionGUID {
+ guid: 07c4bac9-cf7d-5e00-833d-d23ba44c07d9,
},
- FunctionGUID {
- guid: a0c07bfa-a027-5c19-9da4-d2d35d7beb40,
+ 292384: FunctionGUID {
+ guid: 4f0bdeb9-691c-52f8-8541-6c834f3fbe64,
},
- FunctionGUID {
- guid: a0c07bfa-a027-5c19-9da4-d2d35d7beb40,
+ 296016: FunctionGUID {
+ guid: ecca9a9d-1722-5344-b68f-54f4527c0d78,
},
- FunctionGUID {
- guid: a23505e4-e7cd-5543-897c-46b97c1eaef3,
+ 296912: FunctionGUID {
+ guid: ed23805d-3729-590d-83a3-3b0803a56870,
},
- FunctionGUID {
- guid: a482559e-6a69-505e-b692-147deeced692,
+ 297280: FunctionGUID {
+ guid: 410b1cab-55ff-5931-a894-54842e56bee2,
},
- FunctionGUID {
- guid: a482559e-6a69-505e-b692-147deeced692,
+ 297648: FunctionGUID {
+ guid: 11592e15-a2a6-51b3-bda2-534cc0434df0,
},
- FunctionGUID {
- guid: a5b947d4-a7e0-5072-86b3-240474c16595,
+ 303056: FunctionGUID {
+ guid: 3c38a678-9c4e-527a-b543-3075b345d532,
},
- FunctionGUID {
+ 303392: FunctionGUID {
guid: a603d813-2b33-567d-80c8-06e31562a400,
},
- FunctionGUID {
- guid: a707a2d3-c7d2-5b93-97c9-1db77d8ccfe5,
- },
- FunctionGUID {
- guid: a707a2d3-c7d2-5b93-97c9-1db77d8ccfe5,
- },
- FunctionGUID {
- guid: a707a2d3-c7d2-5b93-97c9-1db77d8ccfe5,
- },
- FunctionGUID {
- guid: a707a2d3-c7d2-5b93-97c9-1db77d8ccfe5,
- },
- FunctionGUID {
- guid: a707a2d3-c7d2-5b93-97c9-1db77d8ccfe5,
+ 303744: FunctionGUID {
+ guid: 56c82b1f-380c-52fe-ae70-a39962e5ffdc,
},
- FunctionGUID {
- guid: a707a2d3-c7d2-5b93-97c9-1db77d8ccfe5,
+ 304096: FunctionGUID {
+ guid: eefbc8a9-f4f1-5725-9a4f-9dcd04ea3478,
},
- FunctionGUID {
- guid: adadfe23-cb3f-5c9f-ae8d-1dd2fd922367,
+ 304528: FunctionGUID {
+ guid: 1b969932-96ce-5a6f-b91c-82e5f7b7f33d,
},
- FunctionGUID {
- guid: b098f8a1-ecdd-5938-8da3-bb37935c6b7e,
+ 304848: FunctionGUID {
+ guid: a23505e4-e7cd-5543-897c-46b97c1eaef3,
},
- FunctionGUID {
- guid: b098f8a1-ecdd-5938-8da3-bb37935c6b7e,
+ 305216: FunctionGUID {
+ guid: 9ef21651-3eaf-5940-8e72-104bda834e85,
},
- FunctionGUID {
- guid: b0aca296-adc3-5b46-a6b1-e82ef2100ead,
+ 305568: FunctionGUID {
+ guid: 6c01cd47-89a2-59f3-ac6a-cd7806fb8f79,
},
- FunctionGUID {
- guid: b9111cf4-dcff-52a5-8755-2dedea6d5c72,
+ 306128: FunctionGUID {
+ guid: d72f40b9-0557-5695-aa4d-468515ec4b8e,
},
- FunctionGUID {
- guid: bbb9b018-a0cd-5f98-b941-367f8707434d,
+ 306464: FunctionGUID {
+ guid: 3c38a678-9c4e-527a-b543-3075b345d532,
},
- FunctionGUID {
- guid: bbb9b018-a0cd-5f98-b941-367f8707434d,
+ 306816: FunctionGUID {
+ guid: e480dd82-0b7d-5c75-ac67-ba8c0bb755cb,
},
- FunctionGUID {
- guid: bfaf2f0a-d92d-5ef7-879d-86eb0ca01257,
+ 307168: FunctionGUID {
+ guid: e1b1e1df-fdb9-59d6-b966-5b7f926b5649,
},
- FunctionGUID {
- guid: bfaf2f0a-d92d-5ef7-879d-86eb0ca01257,
+ 309232: FunctionGUID {
+ guid: f37337a6-4cf0-55b4-a0da-25a15e3d9bcb,
},
- FunctionGUID {
- guid: c3842e85-b9f2-54d5-b353-e9bb2a0b1203,
+ 309968: FunctionGUID {
+ guid: b0aca296-adc3-5b46-a6b1-e82ef2100ead,
},
- FunctionGUID {
- guid: c3842e85-b9f2-54d5-b353-e9bb2a0b1203,
+ 310352: FunctionGUID {
+ guid: db957206-4cca-5631-893d-1d3dc500c804,
},
- FunctionGUID {
- guid: c44bdef1-d78a-57ec-bff5-4e7d32ff4337,
+ 311760: FunctionGUID {
+ guid: adadfe23-cb3f-5c9f-ae8d-1dd2fd922367,
},
- FunctionGUID {
- guid: c71f4559-f0af-54dc-a224-e1f341311988,
+ 312416: FunctionGUID {
+ guid: dd480d6b-04f7-5fb2-a7b2-236e1d78b2b0,
},
- FunctionGUID {
- guid: c71f4559-f0af-54dc-a224-e1f341311988,
+ 312784: FunctionGUID {
+ guid: 5779439c-a22f-5d33-8868-4e1832ad49e9,
},
- FunctionGUID {
- guid: c74ed160-24fe-50d7-ba1c-9751c4599420,
+ 313216: FunctionGUID {
+ guid: 0916139e-a5db-51c8-be24-2c6fb20a9e3a,
},
- FunctionGUID {
+ 313696: FunctionGUID {
guid: ccbd9943-6b24-59d8-ae4d-3ee868a2729c,
},
- FunctionGUID {
+ 314064: FunctionGUID {
guid: ccbd9943-6b24-59d8-ae4d-3ee868a2729c,
},
- FunctionGUID {
- guid: ce28e080-0609-5bcd-abe1-2a6e57c91f83,
+ 314432: FunctionGUID {
+ guid: 669a32f2-f1e9-5b38-98aa-9eb8431ebc4e,
},
- FunctionGUID {
- guid: ce28e080-0609-5bcd-abe1-2a6e57c91f83,
+ 315360: FunctionGUID {
+ guid: 02d864ef-80bc-5265-b78f-0e42815a4d1d,
},
- FunctionGUID {
- guid: ce28e080-0609-5bcd-abe1-2a6e57c91f83,
+ 315696: FunctionGUID {
+ guid: 02d864ef-80bc-5265-b78f-0e42815a4d1d,
},
- FunctionGUID {
- guid: cf4808fe-2fea-517d-8f85-f88ddc96cc78,
+ 316032: FunctionGUID {
+ guid: 8dca0c9d-bdb7-56a1-9832-431aec16a68f,
},
- FunctionGUID {
- guid: cf4808fe-2fea-517d-8f85-f88ddc96cc78,
+ 318192: FunctionGUID {
+ guid: 9e4c3806-b291-569c-a687-0bcaa48e248b,
},
- FunctionGUID {
- guid: cfa4374b-0fc7-575d-9ba1-4a37d2de0406,
+ 318960: FunctionGUID {
+ guid: 4ba8a311-60f3-57d4-a17a-c0cc93f5c2a4,
},
- FunctionGUID {
- guid: d72f40b9-0557-5695-aa4d-468515ec4b8e,
+ 319616: FunctionGUID {
+ guid: 31a8e73e-74fe-5a33-b0ab-aa1b136e021d,
},
- FunctionGUID {
- guid: d8ba616a-1747-587c-9265-cd4e26ae35c8,
+ 320288: FunctionGUID {
+ guid: c3842e85-b9f2-54d5-b353-e9bb2a0b1203,
},
- FunctionGUID {
- guid: db957206-4cca-5631-893d-1d3dc500c804,
+ 320912: FunctionGUID {
+ guid: c3842e85-b9f2-54d5-b353-e9bb2a0b1203,
},
- FunctionGUID {
- guid: dd480d6b-04f7-5fb2-a7b2-236e1d78b2b0,
+ 321536: FunctionGUID {
+ guid: 8e490dee-4a47-5582-bb8d-0b502f4d70ba,
},
- FunctionGUID {
- guid: e1b1e1df-fdb9-59d6-b966-5b7f926b5649,
+ 322800: FunctionGUID {
+ guid: 88d9b26b-3884-58d6-b164-435d8d888e5c,
},
- FunctionGUID {
- guid: e480dd82-0b7d-5c75-ac67-ba8c0bb755cb,
+ 323168: FunctionGUID {
+ guid: 3cc2e827-b707-5c9d-82e9-d76ac3abc904,
},
- FunctionGUID {
- guid: e4ba95bb-0221-5dbd-9884-13a39c4f9d55,
+ 323504: FunctionGUID {
+ guid: bfaf2f0a-d92d-5ef7-879d-86eb0ca01257,
},
- FunctionGUID {
+ 323824: FunctionGUID {
guid: e8290b3d-cf56-5c1b-8315-abce0f135559,
},
- FunctionGUID {
- guid: e8290b3d-cf56-5c1b-8315-abce0f135559,
+ 324176: FunctionGUID {
+ guid: 53b484f3-751a-505d-beae-acc9d69c261d,
},
- FunctionGUID {
- guid: ecca9a9d-1722-5344-b68f-54f4527c0d78,
+ 324496: FunctionGUID {
+ guid: bfaf2f0a-d92d-5ef7-879d-86eb0ca01257,
},
- FunctionGUID {
- guid: ed23805d-3729-590d-83a3-3b0803a56870,
+ 324816: FunctionGUID {
+ guid: e8290b3d-cf56-5c1b-8315-abce0f135559,
},
- FunctionGUID {
- guid: eefbc8a9-f4f1-5725-9a4f-9dcd04ea3478,
+ 325168: FunctionGUID {
+ guid: 5b7f41e9-de1e-558c-ac7d-888acca3a76c,
},
- FunctionGUID {
- guid: f37337a6-4cf0-55b4-a0da-25a15e3d9bcb,
+ 325520: FunctionGUID {
+ guid: 43a8c54b-dd4f-5334-ae00-218cb758ad4c,
},
- FunctionGUID {
- guid: f37cf37b-b19d-5575-b2c7-a6604d3faa69,
+ 325952: FunctionGUID {
+ guid: b098f8a1-ecdd-5938-8da3-bb37935c6b7e,
},
- FunctionGUID {
- guid: f3d57592-4cb1-5356-9591-1f9327c58565,
+ 326256: FunctionGUID {
+ guid: 53b484f3-751a-505d-beae-acc9d69c261d,
},
- FunctionGUID {
- guid: fb20fa7c-c796-58e4-a2f9-7a128baaf5b1,
+ 326576: FunctionGUID {
+ guid: b098f8a1-ecdd-5938-8da3-bb37935c6b7e,
+ },
+ 326880: FunctionGUID {
+ guid: f37cf37b-b19d-5575-b2c7-a6604d3faa69,
},
-]
+}
diff --git a/plugins/warp/src/snapshots/warp_ninja__tests__snapshot_atoldbl.snap b/plugins/warp/tests/snapshots/determinism__atoldbl.obj.snap
index 5dc305cc..2ff5221d 100644
--- a/plugins/warp/src/snapshots/warp_ninja__tests__snapshot_atoldbl.snap
+++ b/plugins/warp/tests/snapshots/determinism__atoldbl.obj.snap
@@ -1,192 +1,192 @@
---
-source: plugins/warp/src/lib.rs
+source: plugins/warp/tests/determinism.rs
expression: functions
---
-[
- FunctionGUID {
- guid: 02d864ef-80bc-5265-b78f-0e42815a4d1d,
+{
+ 223424: FunctionGUID {
+ guid: 29690354-fa27-54d1-a8be-18535b19b1c3,
},
- FunctionGUID {
- guid: 07b3f23f-333b-5c13-873f-5f1973cee8e2,
+ 223744: FunctionGUID {
+ guid: c5717076-f595-525b-8db8-ca79496236ce,
},
- FunctionGUID {
- guid: 13b16f81-0c6f-5aee-ad9f-0d142658cf18,
+ 224800: FunctionGUID {
+ guid: c5717076-f595-525b-8db8-ca79496236ce,
},
- FunctionGUID {
+ 225856: FunctionGUID {
guid: 1de88f85-a19c-5454-9122-ddc80f7509b4,
},
- FunctionGUID {
- guid: 1e794537-6289-59e7-bef9-0c72f3989db8,
- },
- FunctionGUID {
- guid: 22c32840-7bb0-5d80-858c-55dd04553db0,
+ 226240: FunctionGUID {
+ guid: c5766cb0-afbc-5701-ad2f-23d980f4bc15,
},
- FunctionGUID {
+ 226720: FunctionGUID {
guid: 29690354-fa27-54d1-a8be-18535b19b1c3,
},
- FunctionGUID {
- guid: 29690354-fa27-54d1-a8be-18535b19b1c3,
+ 227024: FunctionGUID {
+ guid: ce28e080-0609-5bcd-abe1-2a6e57c91f83,
},
- FunctionGUID {
- guid: 30f44555-ef86-5e85-96c3-6153f4bcc11e,
+ 228320: FunctionGUID {
+ guid: 41fd6d24-0f8b-5848-9873-65b98bd370ab,
},
- FunctionGUID {
- guid: 364c5864-0cd1-5527-8ca0-2378d5b4c0a5,
+ 232656: FunctionGUID {
+ guid: 4f645f3c-3e89-5b5d-a185-9f1a25e94ed2,
},
- FunctionGUID {
- guid: 39cd0d51-d688-58e2-a757-ac868ff8635e,
+ 233840: FunctionGUID {
+ guid: 7c43c697-22ef-57a9-a43a-2214e627749d,
},
- FunctionGUID {
- guid: 3c83a5f5-215d-5d25-97c8-ffe65f73907c,
+ 235280: FunctionGUID {
+ guid: 45770948-1c56-5b24-bd38-f5a559ae8d22,
},
- FunctionGUID {
- guid: 3cc2e827-b707-5c9d-82e9-d76ac3abc904,
+ 235824: FunctionGUID {
+ guid: cf4808fe-2fea-517d-8f85-f88ddc96cc78,
},
- FunctionGUID {
- guid: 41fd6d24-0f8b-5848-9873-65b98bd370ab,
+ 236368: FunctionGUID {
+ guid: 86ab6ab4-45e5-5c08-9a47-05344ea503ab,
},
- FunctionGUID {
- guid: 43a8c54b-dd4f-5334-ae00-218cb758ad4c,
+ 237872: FunctionGUID {
+ guid: 364c5864-0cd1-5527-8ca0-2378d5b4c0a5,
},
- FunctionGUID {
- guid: 45770948-1c56-5b24-bd38-f5a559ae8d22,
+ 238512: FunctionGUID {
+ guid: 9891b84c-000b-529a-8c5b-d762120eff9d,
},
- FunctionGUID {
- guid: 4738dcb9-abab-5157-baef-8edf38d9fabc,
+ 239136: FunctionGUID {
+ guid: 61d32a04-c077-5985-b241-c326dd5b1864,
},
- FunctionGUID {
+ 239760: FunctionGUID {
guid: 48551d5f-2e1f-557b-85a3-a122d3ce07cf,
},
- FunctionGUID {
- guid: 4ba8a311-60f3-57d4-a17a-c0cc93f5c2a4,
+ 240384: FunctionGUID {
+ guid: a707a2d3-c7d2-5b93-97c9-1db77d8ccfe5,
},
- FunctionGUID {
- guid: 4f645f3c-3e89-5b5d-a185-9f1a25e94ed2,
+ 240816: FunctionGUID {
+ guid: a707a2d3-c7d2-5b93-97c9-1db77d8ccfe5,
},
- FunctionGUID {
- guid: 5779439c-a22f-5d33-8868-4e1832ad49e9,
+ 241248: FunctionGUID {
+ guid: a707a2d3-c7d2-5b93-97c9-1db77d8ccfe5,
},
- FunctionGUID {
- guid: 5b7f41e9-de1e-558c-ac7d-888acca3a76c,
+ 241680: FunctionGUID {
+ guid: a482559e-6a69-505e-b692-147deeced692,
},
- FunctionGUID {
- guid: 61d32a04-c077-5985-b241-c326dd5b1864,
+ 242112: FunctionGUID {
+ guid: 1e794537-6289-59e7-bef9-0c72f3989db8,
},
- FunctionGUID {
- guid: 6444ff14-38c5-520e-b9e3-6e3ae15215c4,
+ 242800: FunctionGUID {
+ guid: 7995ca73-04fe-5b30-9e88-1e69197007c1,
},
- FunctionGUID {
- guid: 6589669f-6b11-569e-9adf-ca3c6d6bc62e,
+ 243184: FunctionGUID {
+ guid: 6abe3fc2-6d29-5fde-a31e-bb8249db6bf9,
},
- FunctionGUID {
- guid: 6590d978-d2b0-510c-b5a6-a81361800609,
+ 243536: FunctionGUID {
+ guid: 8c736b66-b63f-5000-a060-2e60a1b8952a,
},
- FunctionGUID {
- guid: 67f303c6-d2e4-55e1-8a5f-5492da69e69f,
+ 243952: FunctionGUID {
+ guid: 8c736b66-b63f-5000-a060-2e60a1b8952a,
},
- FunctionGUID {
- guid: 6abe3fc2-6d29-5fde-a31e-bb8249db6bf9,
+ 244368: FunctionGUID {
+ guid: 8c736b66-b63f-5000-a060-2e60a1b8952a,
},
- FunctionGUID {
- guid: 6ef93fa8-909e-596c-9454-74b89e8cabb7,
+ 244784: FunctionGUID {
+ guid: 13b16f81-0c6f-5aee-ad9f-0d142658cf18,
},
- FunctionGUID {
- guid: 7995ca73-04fe-5b30-9e88-1e69197007c1,
+ 245088: FunctionGUID {
+ guid: 8a2b7bda-5fdb-5ba3-888b-20d5b8d7b2cd,
},
- FunctionGUID {
- guid: 7c43c697-22ef-57a9-a43a-2214e627749d,
+ 245424: FunctionGUID {
+ guid: a030f6f1-e29b-5e4a-8f83-56e6c6c51c15,
},
- FunctionGUID {
- guid: 86ab6ab4-45e5-5c08-9a47-05344ea503ab,
+ 245952: FunctionGUID {
+ guid: b9e4b6b0-47c3-54ca-9a01-56916ea0db8e,
},
- FunctionGUID {
- guid: 88d9b26b-3884-58d6-b164-435d8d888e5c,
+ 246352: FunctionGUID {
+ guid: b64bd893-5369-5ab2-a869-5f12d3fe313e,
},
- FunctionGUID {
- guid: 8a2b7bda-5fdb-5ba3-888b-20d5b8d7b2cd,
+ 246768: FunctionGUID {
+ guid: 6763e6c5-c15f-5eed-9dc9-e0c73284bb59,
},
- FunctionGUID {
- guid: 8c736b66-b63f-5000-a060-2e60a1b8952a,
+ 247248: FunctionGUID {
+ guid: f489ca06-a1f3-5993-80ed-368eff1b7766,
},
- FunctionGUID {
- guid: 8c736b66-b63f-5000-a060-2e60a1b8952a,
+ 247728: FunctionGUID {
+ guid: e12a9f6c-c7cc-573d-b488-e617331aedd9,
},
- FunctionGUID {
- guid: 8c736b66-b63f-5000-a060-2e60a1b8952a,
+ 248208: FunctionGUID {
+ guid: 1556466b-b8ff-5131-b8f3-ce53b21e973b,
},
- FunctionGUID {
- guid: 8ccf8b37-65c5-525a-b96a-64f26d711c75,
+ 248672: FunctionGUID {
+ guid: 73674636-a0a6-503a-bda7-55fc27f1bb58,
},
- FunctionGUID {
+ 249680: FunctionGUID {
guid: 9676fc90-ecba-595b-8d24-5df4e66ac683,
},
- FunctionGUID {
- guid: 9891b84c-000b-529a-8c5b-d762120eff9d,
+ 250048: FunctionGUID {
+ guid: 3c83a5f5-215d-5d25-97c8-ffe65f73907c,
},
- FunctionGUID {
- guid: a030f6f1-e29b-5e4a-8f83-56e6c6c51c15,
+ 250416: FunctionGUID {
+ guid: 4c636a77-6420-5996-95de-d3c1497d60b7,
},
- FunctionGUID {
- guid: a23505e4-e7cd-5543-897c-46b97c1eaef3,
+ 251088: FunctionGUID {
+ guid: a603d813-2b33-567d-80c8-06e31562a400,
},
- FunctionGUID {
- guid: a45d9322-0d45-5dc9-b17b-32b843416c4b,
+ 251440: FunctionGUID {
+ guid: 22c32840-7bb0-5d80-858c-55dd04553db0,
},
- FunctionGUID {
- guid: a482559e-6a69-505e-b692-147deeced692,
+ 252064: FunctionGUID {
+ guid: eefbc8a9-f4f1-5725-9a4f-9dcd04ea3478,
},
- FunctionGUID {
- guid: a603d813-2b33-567d-80c8-06e31562a400,
+ 252496: FunctionGUID {
+ guid: a23505e4-e7cd-5543-897c-46b97c1eaef3,
},
- FunctionGUID {
- guid: a707a2d3-c7d2-5b93-97c9-1db77d8ccfe5,
+ 252864: FunctionGUID {
+ guid: 8ccf8b37-65c5-525a-b96a-64f26d711c75,
},
- FunctionGUID {
- guid: a707a2d3-c7d2-5b93-97c9-1db77d8ccfe5,
+ 253424: FunctionGUID {
+ guid: f9a15815-d271-5c30-893a-239acdb42d21,
},
- FunctionGUID {
- guid: a707a2d3-c7d2-5b93-97c9-1db77d8ccfe5,
+ 255936: FunctionGUID {
+ guid: 7a4eafb9-4b86-5aac-b64f-af5d85be4a39,
},
- FunctionGUID {
- guid: b9e4b6b0-47c3-54ca-9a01-56916ea0db8e,
+ 256768: FunctionGUID {
+ guid: 0e4df1e2-2b70-5087-8518-d9cbbe62e048,
},
- FunctionGUID {
- guid: bae57993-863a-5c22-b8bb-92b862148af3,
- },
- FunctionGUID {
- guid: bfaf2f0a-d92d-5ef7-879d-86eb0ca01257,
+ 257264: FunctionGUID {
+ guid: 5779439c-a22f-5d33-8868-4e1832ad49e9,
},
- FunctionGUID {
- guid: c3842e85-b9f2-54d5-b353-e9bb2a0b1203,
+ 257696: FunctionGUID {
+ guid: ccbd9943-6b24-59d8-ae4d-3ee868a2729c,
},
- FunctionGUID {
- guid: c5766cb0-afbc-5701-ad2f-23d980f4bc15,
+ 258064: FunctionGUID {
+ guid: bae57993-863a-5c22-b8bb-92b862148af3,
},
- FunctionGUID {
- guid: cb8ebff6-30e8-5a8a-9c1b-ec5f26041d65,
+ 258864: FunctionGUID {
+ guid: 02d864ef-80bc-5265-b78f-0e42815a4d1d,
},
- FunctionGUID {
- guid: ccbd9943-6b24-59d8-ae4d-3ee868a2729c,
+ 259200: FunctionGUID {
+ guid: 6ef93fa8-909e-596c-9454-74b89e8cabb7,
},
- FunctionGUID {
- guid: ce28e080-0609-5bcd-abe1-2a6e57c91f83,
+ 259648: FunctionGUID {
+ guid: a45d9322-0d45-5dc9-b17b-32b843416c4b,
},
- FunctionGUID {
- guid: cf4808fe-2fea-517d-8f85-f88ddc96cc78,
+ 260000: FunctionGUID {
+ guid: 4ba8a311-60f3-57d4-a17a-c0cc93f5c2a4,
},
- FunctionGUID {
- guid: e3b83b64-6ba1-5fe5-97da-5db987d79b07,
+ 260656: FunctionGUID {
+ guid: c3842e85-b9f2-54d5-b353-e9bb2a0b1203,
},
- FunctionGUID {
- guid: eefbc8a9-f4f1-5725-9a4f-9dcd04ea3478,
+ 261280: FunctionGUID {
+ guid: 88d9b26b-3884-58d6-b164-435d8d888e5c,
},
- FunctionGUID {
- guid: efcd394f-9201-57f7-aa2d-fc89ad355326,
+ 261648: FunctionGUID {
+ guid: 3cc2e827-b707-5c9d-82e9-d76ac3abc904,
},
- FunctionGUID {
- guid: efcd394f-9201-57f7-aa2d-fc89ad355326,
+ 261984: FunctionGUID {
+ guid: bfaf2f0a-d92d-5ef7-879d-86eb0ca01257,
},
- FunctionGUID {
+ 262304: FunctionGUID {
guid: f25e7750-c61c-5ed8-a43d-959c3ba3a5b4,
},
-]
+ 262976: FunctionGUID {
+ guid: 5b7f41e9-de1e-558c-ac7d-888acca3a76c,
+ },
+ 263328: FunctionGUID {
+ guid: 43a8c54b-dd4f-5334-ae00-218cb758ad4c,
+ },
+}
diff --git a/plugins/warp/src/snapshots/warp_ninja__tests__snapshot_atox.snap b/plugins/warp/tests/snapshots/determinism__atox.obj.snap
index c9960a24..57e4e2e9 100644
--- a/plugins/warp/src/snapshots/warp_ninja__tests__snapshot_atox.snap
+++ b/plugins/warp/tests/snapshots/determinism__atox.obj.snap
@@ -1,186 +1,186 @@
---
-source: plugins/warp/src/lib.rs
+source: plugins/warp/tests/determinism.rs
expression: functions
---
-[
- FunctionGUID {
- guid: 0244fa6b-6425-5aa9-ad87-2378cc4992e7,
- },
- FunctionGUID {
- guid: 02d864ef-80bc-5265-b78f-0e42815a4d1d,
- },
- FunctionGUID {
- guid: 02d864ef-80bc-5265-b78f-0e42815a4d1d,
- },
- FunctionGUID {
- guid: 067d6811-472a-5e4a-b65f-3760df0b7bd5,
+{
+ 220944: FunctionGUID {
+ guid: 29690354-fa27-54d1-a8be-18535b19b1c3,
},
- FunctionGUID {
- guid: 0916139e-a5db-51c8-be24-2c6fb20a9e3a,
+ 221264: FunctionGUID {
+ guid: 39d556f9-435a-5105-b56c-6558ddc63fc0,
},
- FunctionGUID {
- guid: 13b16f81-0c6f-5aee-ad9f-0d142658cf18,
+ 221760: FunctionGUID {
+ guid: 46eae5e0-61e2-5316-9e4d-030dd2240567,
},
- FunctionGUID {
+ 222304: FunctionGUID {
guid: 1de88f85-a19c-5454-9122-ddc80f7509b4,
},
- FunctionGUID {
+ 222688: FunctionGUID {
guid: 1de88f85-a19c-5454-9122-ddc80f7509b4,
},
- FunctionGUID {
- guid: 1e794537-6289-59e7-bef9-0c72f3989db8,
- },
- FunctionGUID {
- guid: 283e5e36-a239-5f0c-8937-f48a4b1d86e0,
- },
- FunctionGUID {
- guid: 29690354-fa27-54d1-a8be-18535b19b1c3,
- },
- FunctionGUID {
- guid: 31a8e73e-74fe-5a33-b0ab-aa1b136e021d,
+ 223072: FunctionGUID {
+ guid: 067d6811-472a-5e4a-b65f-3760df0b7bd5,
},
- FunctionGUID {
+ 225616: FunctionGUID {
guid: 33717d5d-a15f-5c10-bc4a-d85cce963aca,
},
- FunctionGUID {
- guid: 39d556f9-435a-5105-b56c-6558ddc63fc0,
+ 228160: FunctionGUID {
+ guid: 65cb411a-0ae9-546f-9293-3abaa3fd1c31,
},
- FunctionGUID {
- guid: 3cc2e827-b707-5c9d-82e9-d76ac3abc904,
+ 230912: FunctionGUID {
+ guid: 0244fa6b-6425-5aa9-ad87-2378cc4992e7,
},
- FunctionGUID {
+ 233680: FunctionGUID {
guid: 3f9f8782-0b8c-5843-8b1d-831a1ef9b024,
},
- FunctionGUID {
+ 234128: FunctionGUID {
guid: 3f9f8782-0b8c-5843-8b1d-831a1ef9b024,
},
- FunctionGUID {
+ 234592: FunctionGUID {
guid: 3f9f8782-0b8c-5843-8b1d-831a1ef9b024,
},
- FunctionGUID {
+ 235056: FunctionGUID {
guid: 3f9f8782-0b8c-5843-8b1d-831a1ef9b024,
},
- FunctionGUID {
- guid: 43a8c54b-dd4f-5334-ae00-218cb758ad4c,
+ 235520: FunctionGUID {
+ guid: a482559e-6a69-505e-b692-147deeced692,
},
- FunctionGUID {
- guid: 46eae5e0-61e2-5316-9e4d-030dd2240567,
+ 235952: FunctionGUID {
+ guid: a482559e-6a69-505e-b692-147deeced692,
},
- FunctionGUID {
- guid: 4ba8a311-60f3-57d4-a17a-c0cc93f5c2a4,
+ 236384: FunctionGUID {
+ guid: 1e794537-6289-59e7-bef9-0c72f3989db8,
},
- FunctionGUID {
- guid: 56c82b1f-380c-52fe-ae70-a39962e5ffdc,
+ 237072: FunctionGUID {
+ guid: 7995ca73-04fe-5b30-9e88-1e69197007c1,
},
- FunctionGUID {
- guid: 5779439c-a22f-5d33-8868-4e1832ad49e9,
+ 237456: FunctionGUID {
+ guid: 7995ca73-04fe-5b30-9e88-1e69197007c1,
},
- FunctionGUID {
- guid: 580affd8-2657-50b2-9b65-3327422894de,
+ 237840: FunctionGUID {
+ guid: 6abe3fc2-6d29-5fde-a31e-bb8249db6bf9,
},
- FunctionGUID {
- guid: 580affd8-2657-50b2-9b65-3327422894de,
+ 238192: FunctionGUID {
+ guid: 13b16f81-0c6f-5aee-ad9f-0d142658cf18,
},
- FunctionGUID {
- guid: 580affd8-2657-50b2-9b65-3327422894de,
+ 238496: FunctionGUID {
+ guid: 8a2b7bda-5fdb-5ba3-888b-20d5b8d7b2cd,
},
- FunctionGUID {
- guid: 580affd8-2657-50b2-9b65-3327422894de,
+ 238832: FunctionGUID {
+ guid: a603d813-2b33-567d-80c8-06e31562a400,
},
- FunctionGUID {
- guid: 580affd8-2657-50b2-9b65-3327422894de,
+ 239184: FunctionGUID {
+ guid: 56c82b1f-380c-52fe-ae70-a39962e5ffdc,
},
- FunctionGUID {
- guid: 580affd8-2657-50b2-9b65-3327422894de,
+ 239536: FunctionGUID {
+ guid: a23505e4-e7cd-5543-897c-46b97c1eaef3,
},
- FunctionGUID {
- guid: 580affd8-2657-50b2-9b65-3327422894de,
+ 239904: FunctionGUID {
+ guid: 9ef21651-3eaf-5940-8e72-104bda834e85,
},
- FunctionGUID {
- guid: 580affd8-2657-50b2-9b65-3327422894de,
+ 240256: FunctionGUID {
+ guid: c66dd704-899b-5d58-9d4a-136063f5f552,
},
- FunctionGUID {
- guid: 5b7f41e9-de1e-558c-ac7d-888acca3a76c,
+ 240560: FunctionGUID {
+ guid: 283e5e36-a239-5f0c-8937-f48a4b1d86e0,
},
- FunctionGUID {
- guid: 65cb411a-0ae9-546f-9293-3abaa3fd1c31,
+ 240864: FunctionGUID {
+ guid: a8a9d162-b637-5ce6-82f8-a8ac0fb740dc,
},
- FunctionGUID {
- guid: 6abe3fc2-6d29-5fde-a31e-bb8249db6bf9,
+ 241168: FunctionGUID {
+ guid: f674b84a-7a05-5ca0-aac4-5b5598683087,
},
- FunctionGUID {
- guid: 7995ca73-04fe-5b30-9e88-1e69197007c1,
+ 241472: FunctionGUID {
+ guid: 5779439c-a22f-5d33-8868-4e1832ad49e9,
},
- FunctionGUID {
- guid: 7995ca73-04fe-5b30-9e88-1e69197007c1,
+ 241904: FunctionGUID {
+ guid: 0916139e-a5db-51c8-be24-2c6fb20a9e3a,
},
- FunctionGUID {
- guid: 88d9b26b-3884-58d6-b164-435d8d888e5c,
+ 242384: FunctionGUID {
+ guid: ccbd9943-6b24-59d8-ae4d-3ee868a2729c,
},
- FunctionGUID {
- guid: 8a2b7bda-5fdb-5ba3-888b-20d5b8d7b2cd,
+ 242752: FunctionGUID {
+ guid: ccbd9943-6b24-59d8-ae4d-3ee868a2729c,
},
- FunctionGUID {
- guid: 9ef21651-3eaf-5940-8e72-104bda834e85,
+ 243120: FunctionGUID {
+ guid: 02d864ef-80bc-5265-b78f-0e42815a4d1d,
},
- FunctionGUID {
- guid: a23505e4-e7cd-5543-897c-46b97c1eaef3,
+ 243456: FunctionGUID {
+ guid: 02d864ef-80bc-5265-b78f-0e42815a4d1d,
},
- FunctionGUID {
- guid: a482559e-6a69-505e-b692-147deeced692,
+ 243792: FunctionGUID {
+ guid: 4ba8a311-60f3-57d4-a17a-c0cc93f5c2a4,
},
- FunctionGUID {
- guid: a482559e-6a69-505e-b692-147deeced692,
+ 244448: FunctionGUID {
+ guid: 31a8e73e-74fe-5a33-b0ab-aa1b136e021d,
},
- FunctionGUID {
- guid: a603d813-2b33-567d-80c8-06e31562a400,
+ 245120: FunctionGUID {
+ guid: c3842e85-b9f2-54d5-b353-e9bb2a0b1203,
},
- FunctionGUID {
- guid: a8a9d162-b637-5ce6-82f8-a8ac0fb740dc,
+ 245744: FunctionGUID {
+ guid: c3842e85-b9f2-54d5-b353-e9bb2a0b1203,
},
- FunctionGUID {
- guid: bdf90a04-d375-59b1-8a92-b962b3f914ae,
+ 246368: FunctionGUID {
+ guid: 8e490dee-4a47-5582-bb8d-0b502f4d70ba,
},
- FunctionGUID {
- guid: bdf90a04-d375-59b1-8a92-b962b3f914ae,
+ 247632: FunctionGUID {
+ guid: 88d9b26b-3884-58d6-b164-435d8d888e5c,
},
- FunctionGUID {
- guid: bdf90a04-d375-59b1-8a92-b962b3f914ae,
+ 248000: FunctionGUID {
+ guid: 3cc2e827-b707-5c9d-82e9-d76ac3abc904,
},
- FunctionGUID {
+ 248336: FunctionGUID {
guid: bdf90a04-d375-59b1-8a92-b962b3f914ae,
},
- FunctionGUID {
- guid: bdf90a04-d375-59b1-8a92-b962b3f914ae,
+ 248640: FunctionGUID {
+ guid: 580affd8-2657-50b2-9b65-3327422894de,
},
- FunctionGUID {
- guid: bdf90a04-d375-59b1-8a92-b962b3f914ae,
+ 248976: FunctionGUID {
+ guid: 580affd8-2657-50b2-9b65-3327422894de,
+ },
+ 249296: FunctionGUID {
+ guid: 580affd8-2657-50b2-9b65-3327422894de,
+ },
+ 249616: FunctionGUID {
+ guid: 580affd8-2657-50b2-9b65-3327422894de,
+ },
+ 249936: FunctionGUID {
+ guid: 5b7f41e9-de1e-558c-ac7d-888acca3a76c,
+ },
+ 250288: FunctionGUID {
+ guid: 43a8c54b-dd4f-5334-ae00-218cb758ad4c,
},
- FunctionGUID {
+ 250720: FunctionGUID {
guid: bdf90a04-d375-59b1-8a92-b962b3f914ae,
},
- FunctionGUID {
+ 251024: FunctionGUID {
guid: bdf90a04-d375-59b1-8a92-b962b3f914ae,
},
- FunctionGUID {
- guid: c3842e85-b9f2-54d5-b353-e9bb2a0b1203,
+ 251328: FunctionGUID {
+ guid: 580affd8-2657-50b2-9b65-3327422894de,
},
- FunctionGUID {
- guid: c3842e85-b9f2-54d5-b353-e9bb2a0b1203,
+ 251664: FunctionGUID {
+ guid: 580affd8-2657-50b2-9b65-3327422894de,
},
- FunctionGUID {
- guid: c66dd704-899b-5d58-9d4a-136063f5f552,
+ 251984: FunctionGUID {
+ guid: bdf90a04-d375-59b1-8a92-b962b3f914ae,
},
- FunctionGUID {
- guid: ccbd9943-6b24-59d8-ae4d-3ee868a2729c,
+ 252288: FunctionGUID {
+ guid: 580affd8-2657-50b2-9b65-3327422894de,
},
- FunctionGUID {
- guid: ccbd9943-6b24-59d8-ae4d-3ee868a2729c,
+ 252608: FunctionGUID {
+ guid: bdf90a04-d375-59b1-8a92-b962b3f914ae,
},
- FunctionGUID {
- guid: f674b84a-7a05-5ca0-aac4-5b5598683087,
+ 252912: FunctionGUID {
+ guid: 580affd8-2657-50b2-9b65-3327422894de,
+ },
+ 253232: FunctionGUID {
+ guid: bdf90a04-d375-59b1-8a92-b962b3f914ae,
},
- FunctionGUID {
- guid: f762c527-0674-52ce-b59f-49bf4e13d878,
+ 253536: FunctionGUID {
+ guid: bdf90a04-d375-59b1-8a92-b962b3f914ae,
+ },
+ 253840: FunctionGUID {
+ guid: bdf90a04-d375-59b1-8a92-b962b3f914ae,
},
-]
+}
diff --git a/plugins/warp/ui/CMakeLists.txt b/plugins/warp/ui/CMakeLists.txt
new file mode 100644
index 00000000..ebfaddc4
--- /dev/null
+++ b/plugins/warp/ui/CMakeLists.txt
@@ -0,0 +1,51 @@
+cmake_minimum_required(VERSION 3.13 FATAL_ERROR)
+
+project(warp_ui CXX C)
+
+file(GLOB SOURCES CONFIGURE_DEPENDS
+ plugin.cpp plugin.h
+ matches.cpp matches.h
+ matched.cpp matched.h
+ shared/misc.cpp shared/misc.h
+ shared/constraint.cpp shared/constraint.h
+ shared/function.cpp shared/function.h
+ shared/container.cpp shared/container.h)
+
+set(CMAKE_AUTOMOC ON)
+set(CMAKE_AUTORCC ON)
+
+find_package(Qt6 COMPONENTS Core Gui Widgets REQUIRED)
+
+add_library(${PROJECT_NAME} SHARED ${SOURCES} ${MOCS})
+
+target_include_directories(${PROJECT_NAME} PRIVATE ../api)
+
+if (NOT BN_API_BUILD_EXAMPLES AND NOT BN_INTERNAL_BUILD)
+ # Out-of-tree build
+ find_path(
+ BN_API_PATH
+ NAMES binaryninjaapi.h
+ HINTS ../.. binaryninjaapi $ENV{BN_API_PATH}
+ REQUIRED
+ )
+ add_subdirectory(${BN_API_PATH} api)
+endif ()
+
+target_link_libraries(${PROJECT_NAME} binaryninjaui warp_api Qt6::Core Qt6::Gui Qt6::Widgets)
+
+set_target_properties(${PROJECT_NAME} PROPERTIES
+ CXX_STANDARD 17
+ CXX_VISIBILITY_PRESET hidden
+ CXX_STANDARD_REQUIRED ON
+ VISIBILITY_INLINES_HIDDEN ON
+ POSITION_INDEPENDENT_CODE ON)
+
+if (BN_INTERNAL_BUILD)
+ ui_plugin_rpath(${PROJECT_NAME})
+
+ set_target_properties(${PROJECT_NAME} PROPERTIES
+ LIBRARY_OUTPUT_DIRECTORY ${BN_CORE_PLUGIN_DIR}
+ RUNTIME_OUTPUT_DIRECTORY ${BN_CORE_PLUGIN_DIR})
+else ()
+ bn_install_plugin(${PROJECT_NAME})
+endif ()
diff --git a/plugins/warp/ui/matched.cpp b/plugins/warp/ui/matched.cpp
new file mode 100644
index 00000000..475e0cdd
--- /dev/null
+++ b/plugins/warp/ui/matched.cpp
@@ -0,0 +1,83 @@
+#include "matched.h"
+
+#include <QGridLayout>
+
+#include "theme.h"
+
+const char *WARP_APPLY_ACTIVITY = "analysis.warp.apply";
+
+WarpMatchedWidget::WarpMatchedWidget(BinaryViewRef current)
+{
+ m_current = current;
+ // Create the QT stuff
+ QGridLayout *layout = new QGridLayout(this);
+ layout->setContentsMargins(2, 2, 2, 2);
+ layout->setSpacing(2);
+ auto newPalette = palette();
+ newPalette.setColor(QPalette::Window, getThemeColor(SidebarWidgetBackgroundColor));
+ setAutoFillBackground(true);
+ setPalette(newPalette);
+
+ // TODO: Split horizontally if the widget is displayed in a sidebar that is vertically challenged.
+ m_splitter = new QSplitter(Qt::Vertical);
+ m_splitter->setContentsMargins(0, 0, 0, 0);
+
+ // Add a widget to display the matches.
+ m_tableWidget = new WarpFunctionTableWidget(this);
+ m_tableWidget->setContentsMargins(0, 0, 0, 0);
+ m_splitter->addWidget(m_tableWidget);
+
+ // Toggle the applying workflow, this workflow sets all the data for the function based on the matched function data.
+ m_tableWidget->RegisterContextMenuAction("Toggle Application",
+ [this](WarpFunctionItem *, std::optional<uint64_t> address) {
+ if (!address.has_value())
+ return;
+ for (const auto &func: m_current->GetAnalysisFunctionsForAddress(
+ *address))
+ {
+ const bool previous = BinaryNinja::Settings::Instance()->Get<bool>(
+ WARP_APPLY_ACTIVITY, func);
+ BinaryNinja::Settings::Instance()->Set(
+ WARP_APPLY_ACTIVITY, !previous, func);
+ func->Reanalyze();
+ }
+ });
+
+ layout->addWidget(m_splitter, 1, 0, 1, 5);
+ setLayout(layout);
+
+ Update();
+
+ connect(m_tableWidget->GetTableView(), &QTableView::clicked, this,
+ [this](const QModelIndex &index) {
+ if (m_current == nullptr)
+ return;
+ if (!index.isValid())
+ return;
+ const QModelIndex sourceIndex = m_tableWidget->GetProxyModel()->mapToSource(index);
+ if (!sourceIndex.isValid())
+ return;
+ auto selectedItem = m_tableWidget->GetModel()->GetAddress(sourceIndex);
+ if (!selectedItem.has_value())
+ return;
+ // Navigate to the address in the view, so the user feels like they are doing something.
+ auto currentView = m_current->GetCurrentView();
+ m_current->Navigate(currentView, selectedItem.value());
+ });
+}
+
+void WarpMatchedWidget::Update()
+{
+ m_tableWidget->GetTableView()->setSortingEnabled(false);
+ m_tableWidget->GetTableView()->setEnabled(false);
+ for (const auto &analysisFunction: m_current->GetAnalysisFunctionList())
+ {
+ if (const auto &matchedFunction = Warp::Function::GetMatched(*analysisFunction))
+ {
+ uint64_t startAddress = analysisFunction->GetStart();
+ m_tableWidget->InsertFunction(startAddress, new WarpFunctionItem(matchedFunction, analysisFunction));
+ }
+ }
+ m_tableWidget->GetTableView()->setEnabled(true);
+ m_tableWidget->GetTableView()->setSortingEnabled(true);
+}
diff --git a/plugins/warp/ui/matched.h b/plugins/warp/ui/matched.h
new file mode 100644
index 00000000..5e333e25
--- /dev/null
+++ b/plugins/warp/ui/matched.h
@@ -0,0 +1,27 @@
+#pragma once
+#include <QSplitter>
+
+#include "uitypes.h"
+#include "shared/function.h"
+
+class WarpMatchedFunctionTableWidget : public WarpFunctionTableWidget
+{
+ Q_OBJECT
+};
+
+class WarpMatchedWidget : public QWidget
+{
+ Q_OBJECT
+ BinaryViewRef m_current;
+
+ QSplitter *m_splitter;
+
+ WarpFunctionTableWidget *m_tableWidget;
+
+public:
+ explicit WarpMatchedWidget(BinaryViewRef current);
+
+ ~WarpMatchedWidget() override = default;
+
+ void Update();
+};
diff --git a/plugins/warp/ui/matches.cpp b/plugins/warp/ui/matches.cpp
new file mode 100644
index 00000000..fad729d6
--- /dev/null
+++ b/plugins/warp/ui/matches.cpp
@@ -0,0 +1,164 @@
+#include <QGridLayout>
+#include <QHeaderView>
+
+#include "matches.h"
+
+#include <QClipboard>
+#include <QFormLayout>
+
+#include "theme.h"
+#include "warp.h"
+#include "shared/misc.h"
+
+WarpCurrentFunctionWidget::WarpCurrentFunctionWidget(FunctionRef current)
+{
+ // NOTE: Might be nullptr if the no selected function.
+ m_current = current;
+
+ // Create the QT stuff
+ QGridLayout *layout = new QGridLayout(this);
+ layout->setContentsMargins(2, 2, 2, 2);
+ layout->setSpacing(2);
+ auto newPalette = palette();
+ newPalette.setColor(QPalette::Window, getThemeColor(SidebarWidgetBackgroundColor));
+ setAutoFillBackground(true);
+ setPalette(newPalette);
+
+ // TODO: Split horizontally if the widget is displayed in a sidebar that is vertically challenged.
+ m_splitter = new QSplitter(Qt::Vertical);
+ m_splitter->setContentsMargins(0, 0, 0, 0);
+
+ // Add a widget to display the matches.
+ m_tableWidget = new WarpFunctionTableWidget(this);
+ m_tableWidget->setContentsMargins(0, 0, 0, 0);
+ m_splitter->addWidget(m_tableWidget);
+
+ // Add a widget to display the info about the selected function match.
+ m_infoWidget = new WarpFunctionInfoWidget(this);
+ m_infoWidget->setContentsMargins(0, 0, 0, 0);
+ m_splitter->addWidget(m_infoWidget);
+
+ layout->addWidget(m_splitter, 1, 0, 1, 5);
+ setLayout(layout);
+
+ m_tableWidget->RegisterContextMenuAction("Apply", [this](WarpFunctionItem *item, std::optional<uint64_t>) {
+ if (item == nullptr)
+ return;
+ Warp::Ref<Warp::Function> selectedFunction = item->GetFunction();
+ if (!selectedFunction)
+ return;
+ selectedFunction->Apply(*m_current);
+ // Update analysis so that the selected function shows.
+ m_current->GetView()->UpdateAnalysis();
+ // So it shows visually as selected.
+ m_tableWidget->GetModel()->SetMatchedFunction(selectedFunction);
+ });
+ m_tableWidget->RegisterContextMenuAction("Search for Source",
+ [this](WarpFunctionItem *item, std::optional<uint64_t>) {
+ // Apply the source as the filter.
+ if (const auto source = item->GetSource(); source)
+ m_tableWidget->setFilter(source->ToString());
+ });
+
+ connect(m_tableWidget->GetTableView(), &QTableView::clicked, this,
+ [this](const QModelIndex &index) {
+ if (m_current == nullptr)
+ return;
+ if (!index.isValid())
+ return;
+ const QModelIndex sourceIndex = m_tableWidget->GetProxyModel()->mapToSource(index);
+ if (!sourceIndex.isValid())
+ return;
+ auto selectedItem = m_tableWidget->GetModel()->GetItem(sourceIndex);
+ // Access the first column in the row
+ if (!selectedItem)
+ return;
+ m_infoWidget->SetFunction(selectedItem->GetFunction());
+ m_infoWidget->UpdateInfo();
+ });
+
+
+ connect(m_tableWidget->GetTableView(), &QTableView::doubleClicked, this, [=](const QModelIndex &index) {
+ if (m_current == nullptr)
+ return;
+ // Get the selected row for the given index.
+ if (!index.isValid())
+ return;
+ const QModelIndex sourceIndex = m_tableWidget->GetProxyModel()->mapToSource(index);
+ if (!sourceIndex.isValid())
+ return;
+ auto selectedItem = m_tableWidget->GetModel()->GetItem(sourceIndex);
+ // Access the first column in the row
+ if (!selectedItem)
+ return;
+ Warp::Ref<Warp::Function> selectedFunction = selectedItem->GetFunction();
+
+ // Actually apply the newly selected function.
+ selectedFunction->Apply(*m_current);
+
+ // Update analysis so that the selected function shows.
+ m_current->GetView()->UpdateAnalysis();
+
+ // So it shows visually as selected.
+ m_tableWidget->GetModel()->SetMatchedFunction(selectedFunction);
+ });
+}
+
+void WarpCurrentFunctionWidget::SetCurrentFunction(FunctionRef current)
+{
+ if (m_current == current)
+ return;
+ m_current = current;
+ m_infoWidget->SetAnalysisFunction(m_current);
+ UpdateMatches();
+}
+
+void WarpCurrentFunctionWidget::UpdateMatches()
+{
+ if (!m_current)
+ return;
+ const auto guid = Warp::GetAnalysisFunctionGUID(*m_current);
+ if (!guid.has_value())
+ return;
+
+ // Set the matched function for highlighting.
+ Warp::Ref<Warp::Function> matchedFunction = Warp::Function::GetMatched(*m_current);
+ m_tableWidget->GetModel()->SetMatchedFunction(matchedFunction);
+
+ // We swapped functions, reset the info widget to the default state with new analysis function.
+ m_infoWidget->SetFunction(matchedFunction);
+ m_infoWidget->UpdateInfo();
+
+ Warp::Ref<Warp::Target> target = Warp::Target::FromPlatform(*m_current->GetPlatform());
+
+ // Add all the possible matches for the current function to the model.
+ QVector<WarpFunctionItem *> matches;
+ bool matchedFuncAdded = false;
+ // TODO: When we add in the networked container we need to update this stuff on a separate thread and show a spinny thing.
+ for (const auto &container: Warp::Container::All())
+ {
+ for (const auto &source: container->GetSourcesWithFunctionGUID(*target, guid.value()))
+ {
+ for (const auto &function: container->GetFunctionsWithGUID(*target, source, guid.value()))
+ {
+ // TODO: This does not work.
+ if (matchedFunction && BNWARPFunctionsEqual(function->m_object, matchedFunction->m_object))
+ matchedFuncAdded = true;
+ auto item = new WarpFunctionItem(function, m_current);
+ item->SetContainer(container);
+ item->SetSource(source);
+ matches.emplace_back(item);
+ }
+ }
+ }
+
+ // Add the matched function unconditionally, assuming it has not been found in a container.
+ // NOTE: This happens when you load from a database for example.
+ if (matchedFunction && !matchedFuncAdded)
+ {
+ auto item = new WarpFunctionItem(matchedFunction, m_current);
+ matches.emplace_back(item);
+ }
+
+ m_tableWidget->SetFunctions(matches);
+}
diff --git a/plugins/warp/ui/matches.h b/plugins/warp/ui/matches.h
new file mode 100644
index 00000000..4021f10c
--- /dev/null
+++ b/plugins/warp/ui/matches.h
@@ -0,0 +1,29 @@
+#pragma once
+
+#include <QSplitter>
+
+#include "filter.h"
+#include "render.h"
+#include "shared/function.h"
+
+class WarpCurrentFunctionWidget : public QWidget
+{
+ Q_OBJECT
+ FunctionRef m_current;
+
+ QSplitter *m_splitter;
+
+ WarpFunctionTableWidget *m_tableWidget;
+ WarpFunctionInfoWidget *m_infoWidget;
+
+public:
+ explicit WarpCurrentFunctionWidget(FunctionRef current);
+
+ ~WarpCurrentFunctionWidget() override = default;
+
+ void SetCurrentFunction(FunctionRef current);
+
+ FunctionRef GetCurrentFunction() { return m_current; };
+
+ void UpdateMatches();
+};
diff --git a/plugins/warp/ui/plugin.cpp b/plugins/warp/ui/plugin.cpp
new file mode 100644
index 00000000..69aaaf34
--- /dev/null
+++ b/plugins/warp/ui/plugin.cpp
@@ -0,0 +1,198 @@
+#include "plugin.h"
+
+#include <QToolBar>
+
+#include "matched.h"
+#include "matches.h"
+#include "symbollist.h"
+#include "viewframe.h"
+
+using namespace BinaryNinja;
+
+QIcon GetColoredIcon(const QString &iconPath, const QColor &color)
+{
+ auto pixmap = QPixmap(iconPath);
+ auto mask = pixmap.createMaskFromColor(QColor(0, 0, 0), Qt::MaskInColor);
+ pixmap.fill(color);
+ pixmap.setMask(mask);
+ return QIcon(pixmap);
+}
+
+Ref<BackgroundTask> GetMatcherTask()
+{
+ // TODO: What happens if we have multiple views open matching? This fails.
+ // Look for the matcher background task to determine if we are stopping or starting it.
+ Ref<BackgroundTask> matcherTask = nullptr;
+ for (const auto &task: BackgroundTask::GetRunningTasks())
+ {
+ std::string progressText = task->GetProgressText();
+ if (progressText.find("Matching on WARP") != std::string::npos)
+ matcherTask = task;
+ }
+ return matcherTask;
+}
+
+WarpSidebarWidget::WarpSidebarWidget(BinaryViewRef data) : SidebarWidget("WARP"), m_data(data)
+{
+ m_logger = LogRegistry::CreateLogger("WARPUI");
+ m_currentFrame = nullptr;
+
+ m_headerWidget = new QWidget();
+ QHBoxLayout *headerLayout = new QHBoxLayout();
+ headerLayout->setContentsMargins(0, 0, 0, 0);
+ headerLayout->setSpacing(0);
+
+ QToolBar *headerToolbar = new QToolBar(this);
+ headerToolbar->setContentsMargins(0, 0, 0, 0);
+ headerToolbar->setIconSize(QSize(20, 20));
+
+ static auto matcherStopIcon = GetColoredIcon(":/icons/images/stop.png", getThemeColor(RedStandardHighlightColor));
+ static auto matcherStartIcon = GetColoredIcon(":/icons/images/start.png",
+ getThemeColor(GreenStandardHighlightColor));
+ m_matcherAction = headerToolbar->addAction(matcherStartIcon, "Run Matcher", [this]() {
+ UIActionHandler *handler = m_currentFrame->getCurrentViewInterface()->actionHandler();
+ if (Ref<BackgroundTask> matcherTask = GetMatcherTask())
+ matcherTask->Cancel();
+ else if (!isMatcherRunning)
+ {
+ handler->executeAction("WARP\\Run Matcher");
+ setMatcherActionIcon(true);
+ }
+ });
+ m_matcherAction->setToolTip("Run the matcher on all functions");
+
+ auto loadIcon = GetColoredIcon(":/icons/images/file-add.png", getThemeColor(BlueStandardHighlightColor));
+ auto loadAction = headerToolbar->addAction(loadIcon, "Load Signature File", [this]() {
+ UIActionHandler *handler = m_currentFrame->getCurrentViewInterface()->actionHandler();
+ handler->executeAction("WARP\\Load File");
+ });
+ loadAction->setToolTip("Load a signature file to match against");
+
+ auto saveIcon = GetColoredIcon(":/icons/images/edit.png", getThemeColor(BlueStandardHighlightColor));
+ auto saveAction = headerToolbar->addAction(saveIcon, "Create Signature File", [this]() {
+ UIActionHandler *handler = m_currentFrame->getCurrentViewInterface()->actionHandler();
+ handler->executeAction("WARP\\Create\\From Current View");
+ });
+ saveAction->setToolTip("Save data to a signature file");
+
+ auto refreshIcon = GetColoredIcon(":/icons/images/refresh.png", getThemeColor(BlueStandardHighlightColor));
+ auto refreshAction = headerToolbar->addAction(refreshIcon, "Refresh the view data", [this]() {
+ Update();
+ });
+ refreshAction->setToolTip("Refresh the sidebar data");
+
+ // TODO: Add action for pushing to network sources.
+
+ // Push the toolbar to the right using a stretch space.
+ headerLayout->addStretch();
+ headerLayout->addWidget(headerToolbar, 0);
+ m_headerWidget->setLayout(headerLayout);
+
+ QFrame *currentFunctionFrame = new QFrame(this);
+ m_currentFunctionWidget = new WarpCurrentFunctionWidget(nullptr);
+ QVBoxLayout *currentFunctionLayout = new QVBoxLayout();
+ currentFunctionLayout->setContentsMargins(0, 0, 0, 0);
+ currentFunctionLayout->setSpacing(0);
+ currentFunctionLayout->addWidget(m_currentFunctionWidget);
+ currentFunctionFrame->setLayout(currentFunctionLayout);
+
+ QFrame *matchedFrame = new QFrame(this);
+ m_matchedWidget = new WarpMatchedWidget(m_data);
+ QVBoxLayout *matchedLayout = new QVBoxLayout();
+ matchedLayout->setContentsMargins(0, 0, 0, 0);
+ matchedLayout->setSpacing(0);
+ matchedLayout->addWidget(m_matchedWidget);
+ matchedFrame->setLayout(matchedLayout);
+
+ QVBoxLayout *layout = new QVBoxLayout(this);
+ layout->setContentsMargins(0, 0, 0, 0);
+ layout->setSpacing(0);
+
+ auto tabWidget = new QTabWidget(this);
+ tabWidget->addTab(currentFunctionFrame, "Current Function");
+ tabWidget->addTab(matchedFrame, "Matched Functions");
+
+ m_analysisEvent = new AnalysisCompletionEvent(m_data, [this]() {
+ ExecuteOnMainThread([this]() {
+ Update();
+ });
+ });
+
+ layout->addWidget(tabWidget);
+ this->setLayout(layout);
+}
+
+WarpSidebarWidget::~WarpSidebarWidget()
+{
+ m_analysisEvent->Cancel();
+}
+
+void WarpSidebarWidget::focus()
+{
+}
+
+void WarpSidebarWidget::Update()
+{
+ m_matchedWidget->Update();
+ if (!GetMatcherTask())
+ setMatcherActionIcon(false);
+}
+
+void WarpSidebarWidget::setMatcherActionIcon(bool running)
+{
+ static auto matcherStopIcon = GetColoredIcon(":/icons/images/stop.png", getThemeColor(RedStandardHighlightColor));
+ static auto matcherStartIcon = GetColoredIcon(":/icons/images/start.png",
+ getThemeColor(GreenStandardHighlightColor));
+ isMatcherRunning = running;
+ if (running)
+ {
+ m_matcherAction->setIcon(matcherStopIcon);
+ m_matcherAction->setToolTip("Stop the matcher");
+ m_matcherAction->setIconText("Stop Matcher");
+ } else
+ {
+ m_matcherAction->setIcon(matcherStartIcon);
+ m_matcherAction->setToolTip("Run the matcher on all functions");
+ m_matcherAction->setIconText("Run Matcher");
+ }
+}
+
+void WarpSidebarWidget::notifyViewChanged(ViewFrame *view)
+{
+ if (!view)
+ return;
+
+ if (view == m_currentFrame)
+ return;
+ m_currentFrame = view;
+ // TODO: We need to set some stuff here prolly.
+}
+
+void WarpSidebarWidget::notifyViewLocationChanged(View *view, const ViewLocation &location)
+{
+ auto function = location.getFunction();
+ // TODO: Only update if the function exists?
+ // NOTE: The function called will exit early if it is the same function.
+ m_currentFunctionWidget->SetCurrentFunction(function);
+}
+
+WarpSidebarWidgetType::WarpSidebarWidgetType() : SidebarWidgetType(QImage(":/icons/images/warp.png"), "WARP")
+{
+}
+
+
+extern "C" {
+BN_DECLARE_UI_ABI_VERSION
+
+BINARYNINJAPLUGIN void CorePluginDependencies()
+{
+ // We must have WARP to enable this plugin!
+ AddRequiredPluginDependency("warp_ninja");
+}
+
+BINARYNINJAPLUGIN bool UIPluginInit()
+{
+ Sidebar::addSidebarWidgetType(new WarpSidebarWidgetType());
+ return true;
+}
+}
diff --git a/plugins/warp/ui/plugin.h b/plugins/warp/ui/plugin.h
new file mode 100644
index 00000000..18525218
--- /dev/null
+++ b/plugins/warp/ui/plugin.h
@@ -0,0 +1,53 @@
+#pragma once
+
+#include "matched.h"
+#include "matches.h"
+#include "sidebar.h"
+#include "sidebarwidget.h"
+
+class WarpSidebarWidget : public SidebarWidget
+{
+ Q_OBJECT
+ BinaryNinja::Ref<BinaryNinja::Logger> m_logger;
+ BinaryViewRef m_data;
+ ViewFrame *m_currentFrame;
+ QWidget *m_headerWidget;
+
+ BinaryNinja::Ref<BinaryNinja::AnalysisCompletionEvent> m_analysisEvent;
+ QAction *m_matcherAction;
+ bool isMatcherRunning = false;
+
+ WarpCurrentFunctionWidget *m_currentFunctionWidget;
+ WarpMatchedWidget *m_matchedWidget;
+
+public:
+ explicit WarpSidebarWidget(BinaryViewRef data);
+
+ ~WarpSidebarWidget() override;
+
+ QWidget *headerWidget() override { return m_headerWidget; }
+
+ void focus() override;
+
+ void Update();
+
+ void setMatcherActionIcon(bool running);
+
+ void notifyViewChanged(ViewFrame *) override;
+
+ void notifyViewLocationChanged(View *, const ViewLocation &) override;
+};
+
+class WarpSidebarWidgetType : public SidebarWidgetType
+{
+public:
+ WarpSidebarWidgetType();
+
+ SidebarWidgetLocation defaultLocation() const override { return SidebarWidgetLocation::RightContent; }
+ SidebarContextSensitivity contextSensitivity() const override { return PerViewTypeSidebarContext; }
+
+ WarpSidebarWidget *createWidget(ViewFrame *viewFrame, BinaryViewRef data) override
+ {
+ return new WarpSidebarWidget(data);
+ }
+};
diff --git a/plugins/warp/ui/shared/constraint.cpp b/plugins/warp/ui/shared/constraint.cpp
new file mode 100644
index 00000000..1d369bcd
--- /dev/null
+++ b/plugins/warp/ui/shared/constraint.cpp
@@ -0,0 +1,118 @@
+#include "constraint.h"
+
+#include <QGridLayout>
+#include <QHeaderView>
+
+WarpConstraintItem::WarpConstraintItem(const Warp::Constraint &constraint) : m_constraint(constraint)
+{
+ QString guidStr = QString::fromStdString(constraint.guid.ToString());
+ if (const auto offset = constraint.offset; offset)
+ guidStr += QString(" @ %1").arg(*offset, 0, 16);
+ setText(guidStr);
+}
+
+WarpConstraintItemModel::WarpConstraintItemModel(const QStringList &labels, QObject *parent)
+{
+ this->setHorizontalHeaderLabels(labels);
+}
+
+void WarpConstraintItemModel::AddItem(WarpConstraintItem *item)
+{
+ QList<QStandardItem *> row = {};
+ row.insert(COL_CONSTRAINT_ITEM, item);
+ appendRow(row);
+}
+
+WarpConstraintItem *WarpConstraintItemModel::GetItem(const QModelIndex &index) const
+{
+ if (!index.isValid())
+ return nullptr;
+ return dynamic_cast<WarpConstraintItem *>(item(index.row(), COL_CONSTRAINT_ITEM));
+}
+
+QVariant WarpConstraintItemModel::data(const QModelIndex &index, int role) const
+{
+ // Highlight constraints that are found in analysis.
+ if (role == Qt::BackgroundRole)
+ {
+ if (const auto item = GetItem(index); item)
+ {
+ auto itemConstraint = item->GetConstraint();
+ // TODO: We really should store the guid in a hashmap or something instead of looping over it for every item.
+ // TODO: A less intense green?
+ // TODO: Take into account the constraint offset.
+ for (const auto &constraint: m_matchedConstraints)
+ if (constraint.guid == itemConstraint.guid)
+ return QBrush(Qt::green);
+ }
+ }
+
+ return QStandardItemModel::data(index, role);
+}
+
+WarpConstraintTableWidget::WarpConstraintTableWidget(QWidget *parent)
+{
+ QGridLayout *layout = new QGridLayout(this);
+ layout->setContentsMargins(2, 2, 2, 2);
+ layout->setVerticalSpacing(4);
+
+ m_table = new QTableView(this);
+ m_model = new WarpConstraintItemModel({"Constraint"}, this);
+ m_proxyModel = new GenericTextFilterModel(this);
+ m_proxyModel->setSourceModel(m_model);
+ m_table->setModel(m_proxyModel);
+
+ m_filterEdit = new FilterEdit(this);
+ m_filterView = new FilteredView(this, m_table, this, m_filterEdit);
+ m_filterView->setFilterPlaceholderText("Search constraints");
+
+ layout->addWidget(m_filterEdit, 0, 0, 1, 5);
+ layout->addWidget(m_table, 1, 0, 1, 5);
+
+ // Make the table look nice.
+ m_table->horizontalHeader()->setStretchLastSection(true);
+ m_table->verticalHeader()->hide();
+ m_table->setSelectionBehavior(QAbstractItemView::SelectRows);
+ m_table->setSelectionMode(QAbstractItemView::SingleSelection);
+ m_table->setEditTriggers(QAbstractItemView::NoEditTriggers);
+ m_table->setFocusPolicy(Qt::NoFocus);
+ m_table->setShowGrid(false);
+ m_table->setAlternatingRowColors(false);
+ m_table->setSortingEnabled(true);
+ // NOTE: We only have a single column right now, so disable the header.
+ m_table->horizontalHeader()->hide();
+ // Decrease row height to make it look nice.
+ m_table->verticalHeader()->setDefaultSectionSize(30);
+}
+
+void WarpConstraintTableWidget::SetConstraints(QVector<WarpConstraintItem *> constraints)
+{
+ // Clear matches as they are no longer valid.
+ m_model->clear();
+ m_model->setRowCount(0);
+
+ // Temporarily disable sorting so we can add rows faster
+ m_table->setModel(m_model);
+ m_table->setSortingEnabled(false);
+ m_table->setEnabled(false);
+
+ for (const auto &constraint: constraints)
+ m_model->AddItem(constraint);
+
+ // We are done, re-enable table.
+ m_table->setEnabled(true);
+ m_table->setModel(m_proxyModel);
+ m_table->setSortingEnabled(true);
+}
+
+void WarpConstraintTableWidget::SetMatchedConstraints(
+ const std::vector<Warp::Constraint> &analysisConstraints)
+{
+ m_model->SetMatchedConstraints(analysisConstraints);
+}
+
+void WarpConstraintTableWidget::setFilter(const std::string &filter)
+{
+ m_proxyModel->setFilterFixedString(QString::fromStdString(filter));
+ m_filterView->showFilter(QString::fromStdString(filter));
+}
diff --git a/plugins/warp/ui/shared/constraint.h b/plugins/warp/ui/shared/constraint.h
new file mode 100644
index 00000000..2f064836
--- /dev/null
+++ b/plugins/warp/ui/shared/constraint.h
@@ -0,0 +1,78 @@
+#pragma once
+#include <qstandarditemmodel.h>
+#include <QTableView>
+#include <QWidget>
+
+#include "filter.h"
+#include "misc.h"
+#include "warp.h"
+
+class WarpConstraintItem : public QStandardItem
+{
+ Warp::Constraint m_constraint;
+
+public:
+ WarpConstraintItem(const Warp::Constraint &constraint);
+
+ Warp::Constraint GetConstraint() { return m_constraint; }
+};
+
+class WarpConstraintItemModel : public QStandardItemModel
+{
+ Q_OBJECT
+
+ // The current analysis constraints used to highlight matching constraints.
+ std::vector<Warp::Constraint> m_matchedConstraints;
+
+public:
+ WarpConstraintItemModel(const QStringList &labels, QObject *parent);
+
+ static constexpr int COL_CONSTRAINT_ITEM = 0;
+
+ void AddItem(WarpConstraintItem *item);
+
+ WarpConstraintItem *GetItem(const QModelIndex &index) const;
+
+ QVariant data(const QModelIndex &index, int role) const override;
+
+ void SetMatchedConstraints(const std::vector<Warp::Constraint> &analysisConstraints)
+ {
+ m_matchedConstraints = analysisConstraints;
+ }
+};
+
+class WarpConstraintTableWidget : public QWidget, public FilterTarget
+{
+ Q_OBJECT
+
+ QTableView *m_table;
+ WarpConstraintItemModel *m_model;
+ GenericTextFilterModel *m_proxyModel;
+ FilterEdit *m_filterEdit;
+ FilteredView *m_filterView;
+
+public:
+ explicit WarpConstraintTableWidget(QWidget *parent = nullptr);
+
+ void SetConstraints(QVector<WarpConstraintItem *> constraints);
+
+ void SetMatchedConstraints(const std::vector<Warp::Constraint> &analysisConstraints);
+
+ void setFilter(const std::string &) override;
+
+ void scrollToFirstItem() override
+ {
+ }
+
+ void scrollToCurrentItem() override
+ {
+ }
+
+ void selectFirstItem() override
+ {
+ }
+
+ void activateFirstItem() override
+ {
+ }
+};
diff --git a/plugins/warp/ui/shared/function.cpp b/plugins/warp/ui/shared/function.cpp
new file mode 100644
index 00000000..2202fb4e
--- /dev/null
+++ b/plugins/warp/ui/shared/function.cpp
@@ -0,0 +1,365 @@
+#include "theme.h"
+
+#include "function.h"
+
+#include <QClipboard>
+#include <QGridLayout>
+#include <QHeaderView>
+
+#include "constraint.h"
+#include "misc.h"
+
+WarpFunctionItem::WarpFunctionItem(Warp::Ref<Warp::Function> function,
+ BinaryNinja::Ref<BinaryNinja::Function> analysisFunction)
+{
+ m_function = function;
+
+ // TODO: This needs to be better. Symbol can be nullptr.
+ BinaryNinja::Ref<BinaryNinja::Symbol> symbol = m_function->GetSymbol(*analysisFunction);
+ std::string symbolName = symbol->GetShortName();
+ setText(QString::fromStdString(symbolName));
+ BinaryNinja::InstructionTextToken nameToken = {255, TextToken, symbolName};
+
+ // Serialize the tokens to make it accessible via QModelIndex.
+ // We will take these tokens and then user them in our custom item delegate.
+ TokenData tokenData = {};
+
+ // TODO: Make this not look like garbage
+ BinaryNinja::Ref<BinaryNinja::Type> type = m_function->GetType(*analysisFunction);
+ if (type)
+ {
+ BinaryNinja::Ref<BinaryNinja::Platform> platform = analysisFunction->GetPlatform();
+ std::vector<BinaryNinja::InstructionTextToken> beforeTokens = type->GetTokensBeforeName(platform);
+ std::vector<BinaryNinja::InstructionTextToken> afterTokens = type->GetTokensAfterName(platform);
+
+ for (const auto &token: beforeTokens)
+ tokenData.tokens.emplace_back(token);
+ tokenData.tokens.emplace_back(255, TextToken, " ");
+ tokenData.tokens.emplace_back(nameToken);
+ for (const auto &token: afterTokens)
+ tokenData.tokens.emplace_back(token);
+ } else
+ {
+ tokenData.tokens.emplace_back(nameToken);
+ }
+
+ setData(QVariant::fromValue(tokenData), Qt::UserRole);
+}
+
+void WarpFunctionItem::SetContainer(const Warp::Ref<Warp::Container> &container)
+{
+ m_container = container;
+
+ // Add the container string to data so the filter model picks it up.
+ auto containerName = m_container->GetName();
+ setData(QString::fromStdString(containerName), Qt::UserRole + 2);
+}
+
+void WarpFunctionItem::SetSource(Warp::Source source)
+{
+ m_source = source;
+
+ // Add the source string to data so the filter model picks it up.
+ std::string sourceStr = m_source->ToString();
+ setData(QString::fromStdString(sourceStr), Qt::UserRole + 1);
+}
+
+WarpFunctionItemModel::WarpFunctionItemModel(const QStringList &labels, QObject *parent)
+{
+ this->setHorizontalHeaderLabels(labels);
+}
+
+void WarpFunctionItemModel::AppendFunction(WarpFunctionItem *item)
+{
+ QList<QStandardItem *> row = {};
+ row.insert(COL_FUNCTION_ITEM, item);
+ appendRow(row);
+}
+
+void WarpFunctionItemModel::InsertFunction(uint64_t address, WarpFunctionItem *item)
+{
+ // Update item if already available, this lets us keep the model
+ const auto iter = m_insertableFunctionRows.find(address);
+ if (iter != m_insertableFunctionRows.end())
+ {
+ setItem(iter->second, COL_FUNCTION_ITEM, item);
+ return;
+ }
+
+ AppendFunction(item);
+ m_insertableFunctionRows[address] = rowCount() - 1;
+}
+
+WarpFunctionItem *WarpFunctionItemModel::GetItem(const QModelIndex &index) const
+{
+ if (!index.isValid())
+ return nullptr;
+ return dynamic_cast<WarpFunctionItem *>(item(index.row(), COL_FUNCTION_ITEM));
+}
+
+std::optional<uint64_t> WarpFunctionItemModel::GetAddress(const QModelIndex &index) const
+{
+ if (!index.isValid())
+ return std::nullopt;
+ // TODO: This is a hack, this means we must enumerate all rows to get the address.
+ for (const auto &[addr, row]: m_insertableFunctionRows)
+ if (row == index.row())
+ return addr;
+ return std::nullopt;
+}
+
+QVariant WarpFunctionItemModel::data(const QModelIndex &index, int role) const
+{
+ if (role == Qt::BackgroundRole)
+ {
+ auto itemFunction = GetItem(index);
+ // Check if we have a valid item and it's the matched function
+ if (m_matchedFunction && itemFunction)
+ {
+ // TODO: Why wont == go to the correct call???
+ if (BNWARPFunctionsEqual(itemFunction->GetFunction()->m_object, m_matchedFunction->m_object))
+ {
+ // TODO: Better color?
+ QColor matchedColor = getThemeColor(BlueStandardHighlightColor);
+ matchedColor.setAlpha(128);
+ return matchedColor;
+ }
+ }
+ }
+
+ if (role == Qt::DisplayRole)
+ {
+ // We really only use this for searching as we have TokenData for our delegate.
+ WarpFunctionItem *item = GetItem(index);
+ if (!item)
+ return QVariant();
+ TokenData tokenData = item->data(Qt::UserRole).value<TokenData>();
+ // Add the function guid so we can filter by that.
+ QString text = tokenData.toString() + " " + QString::fromStdString(item->GetFunction()->GetGUID().ToString());
+ if (auto source = item->GetSource(); source)
+ {
+ // Add the source guid so we can also filter by that.
+ std::string sourceStr = source->ToString();
+ text = text + " " + QString::fromStdString(sourceStr);
+ }
+ return text;
+ }
+
+ return QStandardItemModel::data(index, role);
+}
+
+bool WarpFunctionFilterModel::filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) const
+{
+ const QString filterString = filterRegularExpression().pattern();
+ if (filterString.isEmpty())
+ return true;
+
+ // Filter on the first column only, this contains our actual function.
+ auto index = sourceModel()->index(sourceRow, 0, sourceParent);
+ auto data = QRegularExpression::escape(index.data().toString());
+ if (data.contains(filterString, Qt::CaseInsensitive))
+ return true;
+ return false;
+}
+
+bool WarpFunctionFilterModel::lessThan(const QModelIndex &sourceLeft, const QModelIndex &sourceRight) const
+{
+ // TODO: When we make the stuff _actually_ sortable.
+ return sourceLeft.row() < sourceRight.row();
+}
+
+WarpFunctionTableWidget::WarpFunctionTableWidget(QWidget *parent) : QWidget(parent)
+{
+ QGridLayout *layout = new QGridLayout(this);
+ layout->setContentsMargins(0, 0, 0, 0);
+ layout->setSpacing(2);
+
+ m_table = new QTableView(this);
+ m_model = new WarpFunctionItemModel({"Function"}, this);
+ m_proxyModel = new WarpFunctionFilterModel(this);
+ m_proxyModel->setSourceModel(m_model);
+ m_table->setModel(m_proxyModel);
+
+ m_filterEdit = new FilterEdit(this);
+ m_filterView = new FilteredView(this, m_table, this, m_filterEdit);
+ m_filterView->setFilterPlaceholderText("Search functions (By GUID, name or source)");
+
+ layout->addWidget(m_filterEdit, 0, 0, 1, 5);
+ layout->addWidget(m_table, 1, 0, 1, 5);
+
+ // Make the table look nice.
+ m_table->horizontalHeader()->setStretchLastSection(true);
+ m_table->verticalHeader()->hide();
+ m_table->setSelectionBehavior(QAbstractItemView::SelectRows);
+ m_table->setSelectionMode(QAbstractItemView::SingleSelection);
+ m_table->setEditTriggers(QAbstractItemView::NoEditTriggers);
+ m_table->setFocusPolicy(Qt::NoFocus);
+ m_table->setShowGrid(false);
+ m_table->setAlternatingRowColors(false);
+ m_table->setSortingEnabled(true);
+ // NOTE: We only have a single column right now, so disable header.
+ m_table->horizontalHeader()->hide();
+ // Decrease row height to make it look nice.
+ m_table->verticalHeader()->setDefaultSectionSize(30);
+
+ TokenDataDelegate *tokenDelegate = new TokenDataDelegate(this);
+ // NOTE: Column 0 is assumed to be the function with the token data.
+ m_table->setItemDelegateForColumn(0, tokenDelegate);
+
+ AddressColorDelegate *addressDelegate = new AddressColorDelegate(this);
+ // NOTE: Column 1 is assumed to be the function address.
+ m_table->setItemDelegateForColumn(1, addressDelegate);
+
+ // Add a dynamic context menu to the table.
+ // NOTE: This is a bit stupid, I am sure there is a better way to do this in QT.
+ m_contextMenu = new QMenu(this);
+ RegisterContextMenuAction("Copy Name", [](WarpFunctionItem *item, std::optional<uint64_t>) {
+ QClipboard *clipboard = QGuiApplication::clipboard();
+ clipboard->setText(item->text());
+ });
+ RegisterContextMenuAction("Copy GUID", [](WarpFunctionItem *item, std::optional<uint64_t>) {
+ QClipboard *clipboard = QGuiApplication::clipboard();
+ Warp::Ref<Warp::Function> function = item->GetFunction();
+ std::string guidStr = function->GetGUID().ToString();
+ clipboard->setText(QString::fromStdString(guidStr));
+ });
+
+ m_table->setContextMenuPolicy(Qt::CustomContextMenu);
+ connect(m_table, &QTableView::customContextMenuRequested, this, [&](QPoint pos) {
+ const QModelIndex index = m_table->indexAt(pos);
+ if (!index.isValid())
+ return;
+ const QModelIndex sourceIndex = m_proxyModel->mapToSource(index);
+ WarpFunctionItem *item = m_model->GetItem(sourceIndex);
+ if (!item || !item->GetFunction())
+ return;
+
+ // Execute the menu and get the selected action
+ const QAction *selectedAction = m_contextMenu->exec(m_table->viewport()->mapToGlobal(pos));
+ if (!selectedAction)
+ return;
+
+ const auto name = selectedAction->text();
+ const auto iter = m_contextMenuActions.find(name);
+ if (iter != m_contextMenuActions.end())
+ iter->second(item, m_model->GetAddress(sourceIndex));
+ });
+}
+
+void WarpFunctionTableWidget::RegisterContextMenuAction(const QString &name,
+ const std::function<void(
+ WarpFunctionItem *, std::optional<uint64_t>)> &callback)
+{
+ m_contextMenu->addAction(name);
+ m_contextMenuActions[name] = callback;
+}
+
+void WarpFunctionTableWidget::SetFunctions(QVector<WarpFunctionItem *> functions)
+{
+ // Clear matches as they are no longer valid.
+ m_model->clear();
+ m_model->setRowCount(0);
+
+ // Temporarily disable sorting so we can add rows faster
+ m_table->setModel(m_model);
+ m_table->setSortingEnabled(false);
+ m_table->setEnabled(false);
+
+ for (const auto &function: functions)
+ m_model->AppendFunction(function);
+
+ // We are done, re-enable table.
+ m_table->setEnabled(true);
+ m_table->setModel(m_proxyModel);
+ m_table->setSortingEnabled(true);
+
+ // Update the filter text with the new count of functions.
+ m_filterView->setFilterPlaceholderText(QString("Search %1 functions").arg(m_model->rowCount()));
+}
+
+void WarpFunctionTableWidget::InsertFunction(uint64_t address, WarpFunctionItem *function)
+{
+ m_model->InsertFunction(address, function);
+}
+
+void WarpFunctionTableWidget::setFilter(const std::string &filter)
+{
+ m_proxyModel->setFilterFixedString(QString::fromStdString(filter));
+ m_filterView->showFilter(QString::fromStdString(filter));
+}
+
+WarpFunctionInfoWidget::WarpFunctionInfoWidget(QWidget *parent)
+ : QWidget(parent)
+{
+ // Create a tab widget
+ QTabWidget *tabWidget = new QTabWidget(this);
+ tabWidget->setContentsMargins(0, 0, 0, 0);
+
+ // Create tables for the "Constraints", "Comments", and "Variables" tabs
+ m_commentsTable = new QTableView(this);
+ // m_variablesTable = new QTableView(this);
+
+ // TODO: On click navigate to where the constraint is located.
+ m_constraintsTable = new WarpConstraintTableWidget(this);
+ tabWidget->addTab(m_constraintsTable, "Constraints");
+
+ // Set up comments tab
+ m_commentsModel = new QStandardItemModel(this);
+ m_commentsModel->setHorizontalHeaderLabels({"Offset", "Text"});
+ m_commentsModel->setColumnCount(2);
+ m_commentsTable->setModel(m_commentsModel);
+ m_commentsTable->horizontalHeader()->setStretchLastSection(true);
+ m_commentsTable->horizontalHeader()->setSelectionBehavior(QAbstractItemView::SelectRows);
+ m_commentsTable->horizontalHeader()->setSelectionMode(QAbstractItemView::SingleSelection);
+ m_commentsTable->horizontalHeader()->setEditTriggers(QAbstractItemView::NoEditTriggers);
+ m_commentsTable->verticalHeader()->hide();
+ m_commentsTable->horizontalHeader()->hide();
+ tabWidget->addTab(m_commentsTable, "Comments");
+
+ // Set up variables tab
+ // m_variablesTable->setModel(new QStandardItemModel(this));
+ // TODO: Add variables to data.
+ // tabWidget->addTab(m_variablesTable, "Variables");
+
+ // Add the tab widget to this widget's layout
+ QVBoxLayout *layout = new QVBoxLayout(this);
+ layout->setContentsMargins(0, 0, 0, 0);
+ layout->setSpacing(0);
+ layout->addWidget(tabWidget);
+
+ setLayout(layout);
+}
+
+void WarpFunctionInfoWidget::UpdateInfo()
+{
+ m_commentsModel->clear();
+ m_commentsModel->setRowCount(0);
+ m_constraintsTable->SetMatchedConstraints({});
+ m_constraintsTable->SetConstraints({});
+
+ Warp::Ref<Warp::Function> function = GetFunction();
+ if (!function)
+ return;
+
+ // Set the analysis constraints if there is an analysis function.
+ if (const auto analysisFunction = GetAnalysisFunction())
+ {
+ const auto analysisConstraints = Warp::Function::Get(*analysisFunction)->GetConstraints();
+ m_constraintsTable->SetMatchedConstraints(analysisConstraints);
+ }
+
+ // Add all the constraints for the current function to the model.
+ QVector<WarpConstraintItem *> constraints;
+ for (const auto &constraint: function->GetConstraints())
+ constraints.push_back(new WarpConstraintItem(constraint));
+ m_constraintsTable->SetConstraints(constraints);
+
+ // Add all the comments to the model.
+ for (const auto &comment: function->GetComments())
+ {
+ m_commentsModel->appendRow({
+ new QStandardItem(QString("0x%1").arg(comment.offset, 0, 16)),
+ new QStandardItem(QString::fromStdString(comment.text))
+ });
+ }
+}
diff --git a/plugins/warp/ui/shared/function.h b/plugins/warp/ui/shared/function.h
new file mode 100644
index 00000000..90d52092
--- /dev/null
+++ b/plugins/warp/ui/shared/function.h
@@ -0,0 +1,168 @@
+#pragma once
+#include <QStandardItemModel>
+#include <QTableView>
+
+#include "binaryninjaapi.h"
+#include "constraint.h"
+#include "filter.h"
+#include "misc.h"
+#include "warp.h"
+
+class WarpFunctionItem : public QStandardItem
+{
+ Warp::Ref<Warp::Function> m_function;
+
+ // Optional attached data used to show/manage the function.
+ Warp::Ref<Warp::Container> m_container;
+ std::optional<Warp::Source> m_source;
+
+public:
+ WarpFunctionItem(Warp::Ref<Warp::Function> function,
+ BinaryNinja::Ref<BinaryNinja::Function> analysisFunction);
+
+ void SetContainer(const Warp::Ref<Warp::Container> &container);
+
+ void SetSource(Warp::Source source);
+
+ Warp::Ref<Warp::Function> GetFunction() { return m_function; }
+ Warp::Ref<Warp::Container> GetContainer() { return m_container; }
+ std::optional<Warp::Source> GetSource() { return m_source; }
+};
+
+class WarpFunctionItemModel : public QStandardItemModel
+{
+ Q_OBJECT
+
+ // The current matched function, used to highlight currently.
+ Warp::Ref<Warp::Function> m_matchedFunction;
+
+ // Mapping of function start address to the row index.
+ // This is used to identify unique functions for updating instead of resetting the entire model.
+ std::unordered_map<uint64_t, int> m_insertableFunctionRows;
+
+public:
+ WarpFunctionItemModel(const QStringList &labels, QObject *parent);
+
+ static constexpr int COL_FUNCTION_ITEM = 0;
+ static constexpr int COL_ADDRESS_ITEM = 1;
+
+ void AppendFunction(WarpFunctionItem *item);
+
+ void InsertFunction(uint64_t address, WarpFunctionItem *item);
+
+ WarpFunctionItem *GetItem(const QModelIndex &index) const;
+
+ std::optional<uint64_t> GetAddress(const QModelIndex &index) const;
+
+ QVariant data(const QModelIndex &index, int role) const override;
+
+ void SetMatchedFunction(const Warp::Ref<Warp::Function> &matchedFunction)
+ {
+ Warp::Ref<Warp::Function> previousMatchedFunction = m_matchedFunction;
+ m_matchedFunction = matchedFunction;
+
+ // Make sure to refresh the highlights so we don't keep the highlights from the previous function.
+ if (previousMatchedFunction)
+ {
+ const QModelIndex topLeft = index(0, 0);
+ const QModelIndex bottomRight = index(rowCount() - 1, 0);
+ emit dataChanged(topLeft, bottomRight);
+ }
+ }
+};
+
+class WarpFunctionFilterModel : public QSortFilterProxyModel
+{
+ Q_OBJECT
+
+public:
+ WarpFunctionFilterModel(QObject *parent): QSortFilterProxyModel(parent)
+ {
+ }
+
+ ~WarpFunctionFilterModel() override = default;
+
+ bool filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) const override;
+
+ bool lessThan(const QModelIndex &sourceLeft, const QModelIndex &sourceRight) const override;
+};
+
+class WarpFunctionTableWidget : public QWidget, public FilterTarget
+{
+ Q_OBJECT
+
+ QTableView *m_table;
+ WarpFunctionItemModel *m_model;
+ WarpFunctionFilterModel *m_proxyModel;
+ FilterEdit *m_filterEdit;
+ FilteredView *m_filterView;
+ QMenu *m_contextMenu;
+ std::map<QString, std::function<void(WarpFunctionItem *, std::optional<uint64_t>)> > m_contextMenuActions;
+
+public:
+ explicit WarpFunctionTableWidget(QWidget *parent = nullptr);
+
+ // TODO: Invert this and provide OnCallback functions that wrap the connect call.
+ QTableView *GetTableView() const { return m_table; }
+ WarpFunctionItemModel *GetModel() const { return m_model; }
+ WarpFunctionFilterModel *GetProxyModel() const { return m_proxyModel; }
+
+ void RegisterContextMenuAction(const QString &name,
+ const std::function<void(WarpFunctionItem *, std::optional<uint64_t>)> &callback);
+
+ void SetFunctions(QVector<WarpFunctionItem *> functions);
+
+ void InsertFunction(uint64_t address, WarpFunctionItem *function);
+
+ void setFilter(const std::string &) override;
+
+ void scrollToFirstItem() override
+ {
+ }
+
+ void scrollToCurrentItem() override
+ {
+ }
+
+ void selectFirstItem() override
+ {
+ }
+
+ void activateFirstItem() override
+ {
+ }
+};
+
+class WarpFunctionInfoWidget : public QWidget
+{
+ Q_OBJECT
+
+ Warp::Ref<Warp::Function> m_function;
+ BinaryNinja::Ref<BinaryNinja::Function> m_analysisFunction;
+
+ // Optionally provide this information to show the source information.
+ Warp::Ref<Warp::Container> m_container;
+ std::string source;
+
+ WarpConstraintTableWidget *m_constraintsTable;
+
+ QTableView *m_commentsTable;
+ QStandardItemModel *m_commentsModel;
+
+ QTableView *m_variablesTable;
+
+public:
+ explicit WarpFunctionInfoWidget(QWidget *parent = nullptr);
+
+ Warp::Ref<Warp::Function> GetFunction() { return m_function; }
+ void SetFunction(Warp::Ref<Warp::Function> function) { m_function = function; };
+
+ void SetAnalysisFunction(BinaryNinja::Ref<BinaryNinja::Function> analysisFunction)
+ {
+ m_analysisFunction = analysisFunction;
+ };
+ BinaryNinja::Ref<BinaryNinja::Function> GetAnalysisFunction() { return m_analysisFunction; }
+
+ // TODO: Make this private?
+ void UpdateInfo();
+};
diff --git a/plugins/warp/ui/shared/misc.cpp b/plugins/warp/ui/shared/misc.cpp
new file mode 100644
index 00000000..71147079
--- /dev/null
+++ b/plugins/warp/ui/shared/misc.cpp
@@ -0,0 +1,81 @@
+#include "misc.h"
+
+#include <QGridLayout>
+#include <QHeaderView>
+
+#include "action.h"
+#include "fontsettings.h"
+#include "render.h"
+#include "theme.h"
+
+void TokenDataDelegate::paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const
+{
+ painter->save();
+
+ auto tokenData = index.data(Qt::UserRole).value<TokenData>();
+
+ // Draw either the selected row or background color.
+ QVariant background = index.data(Qt::BackgroundRole);
+ if (background.canConvert<QBrush>())
+ painter->fillRect(option.rect, background.value<QBrush>());
+ else if (option.state & QStyle::State_Selected)
+ painter->fillRect(option.rect, option.palette.highlight());
+ painter->translate(option.rect.topLeft());
+
+
+ auto renderContext = RenderContext((QWidget *) option.widget);
+ renderContext.init(*painter);
+ HighlightTokenState highlightState;
+ renderContext.drawDisassemblyLine(*painter, 5, 5, {tokenData.tokens.begin(), tokenData.tokens.end()},
+ highlightState);
+
+ painter->restore();
+}
+
+QSize TokenDataDelegate::sizeHint(const QStyleOptionViewItem &option, const QModelIndex &index) const
+{
+ auto tokenData = index.data(Qt::UserRole).value<TokenData>();
+ auto renderContext = RenderContext((QWidget *) option.widget);
+ QFontMetrics fontMetrics = QFontMetrics(renderContext.getFont());
+ QString line = "";
+ for (const auto &token: tokenData.tokens)
+ line += token.text;
+ int width = qMax(0, fontMetrics.horizontalAdvance(line));
+ return QSize(width, renderContext.getFontHeight());
+}
+
+void AddressColorDelegate::paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const
+{
+ QStyleOptionViewItem opt = option;
+ initStyleOption(&opt, index);
+
+ opt.font = getMonospaceFont(qobject_cast<QWidget *>(parent()));
+ opt.palette.setColor(QPalette::Text, getThemeColor(BNThemeColor::AddressColor));
+ opt.displayAlignment = Qt::AlignCenter | Qt::AlignVCenter;
+
+ QStyledItemDelegate::paint(painter, opt, index);
+}
+
+bool GenericTextFilterModel::filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) const
+{
+ auto filterString = filterRegularExpression().pattern();
+ if (filterString.isEmpty())
+ return true;
+
+ for (int i = 0; i < sourceModel()->columnCount(); i++)
+ {
+ auto index = sourceModel()->index(sourceRow, i, sourceParent);
+ auto data = QRegularExpression::escape(index.data().toString());
+ if (data.contains(filterString, Qt::CaseInsensitive))
+ return true;
+ }
+
+ return false;
+}
+
+bool GenericTextFilterModel::lessThan(const QModelIndex &sourceLeft, const QModelIndex &sourceRight) const
+{
+ auto leftData = sourceLeft.data().toString();
+ auto rightData = sourceRight.data().toString();
+ return QString::localeAwareCompare(leftData, rightData) < 0;
+}
diff --git a/plugins/warp/ui/shared/misc.h b/plugins/warp/ui/shared/misc.h
new file mode 100644
index 00000000..d2e6a1a8
--- /dev/null
+++ b/plugins/warp/ui/shared/misc.h
@@ -0,0 +1,84 @@
+#pragma once
+#include <qmetatype.h>
+#include <QSortFilterProxyModel>
+#include <qstandarditemmodel.h>
+#include <QStyledItemDelegate>
+#include <QTableView>
+#include <QVector>
+
+#include "binaryninjaapi.h"
+#include "filter.h"
+
+// Used to serialize into the item data for rendering with TokenDataDelegate.
+struct TokenData
+{
+ QVector<BinaryNinja::InstructionTextToken> tokens{};
+
+ TokenData() = default;
+
+ TokenData(const std::vector<BinaryNinja::InstructionTextToken> &tokens)
+ {
+ for (const auto &token: tokens)
+ this->tokens.push_back(token);
+ }
+
+ TokenData(const BinaryNinja::InstructionTextToken &token)
+ {
+ this->tokens.push_back(token);
+ }
+
+ QString toString() const
+ {
+ QStringList tokenStrings;
+ for (const auto &token: tokens)
+ {
+ tokenStrings.append(QString::fromStdString(token.text));
+ }
+ return tokenStrings.join("");
+ }
+};
+
+Q_DECLARE_METATYPE(TokenData)
+
+class TokenDataDelegate final : public QStyledItemDelegate
+{
+ Q_OBJECT
+
+public:
+ explicit TokenDataDelegate(QObject *parent = nullptr) : QStyledItemDelegate(parent)
+ {
+ }
+
+ void paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const override;
+
+ QSize sizeHint(const QStyleOptionViewItem &option, const QModelIndex &index) const override;
+};
+
+class AddressColorDelegate final : public QStyledItemDelegate
+{
+ Q_OBJECT
+
+public:
+ explicit AddressColorDelegate(QObject *parent = nullptr) : QStyledItemDelegate(parent)
+ {
+ }
+
+ void paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const override;
+};
+
+
+class GenericTextFilterModel : public QSortFilterProxyModel
+{
+ Q_OBJECT
+
+public:
+ GenericTextFilterModel(QObject *parent): QSortFilterProxyModel(parent)
+ {
+ }
+
+ ~GenericTextFilterModel() override = default;
+
+ bool filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) const override;
+
+ bool lessThan(const QModelIndex &sourceLeft, const QModelIndex &sourceRight) const override;
+};