summaryrefslogtreecommitdiff
path: root/plugins
diff options
context:
space:
mode:
Diffstat (limited to 'plugins')
-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
81 files changed, 11488 insertions, 3106 deletions
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;
+};