From aad70d416e2dd18d7d904353de97122525af7b84 Mon Sep 17 00:00:00 2001 From: Mark Rowe Date: Tue, 24 Mar 2026 09:05:49 -0700 Subject: [PATCH] Add a hook to allow BinaryView subclasses to run code after snapshot data is applied Snapshot data is applied when loading from a database, rebasing the view, etc. --- binaryninjaapi.h | 10 ++++++++++ binaryninjacore.h | 5 +++-- binaryview.cpp | 8 ++++++++ python/binaryview.py | 10 ++++++++++ rust/src/custom_binary_view.rs | 14 ++++++++++++++ 5 files changed, 45 insertions(+), 2 deletions(-) diff --git a/binaryninjaapi.h b/binaryninjaapi.h index 7b46f97fe..2fc0ef1af 100644 --- a/binaryninjaapi.h +++ b/binaryninjaapi.h @@ -5773,6 +5773,15 @@ namespace BinaryNinja { void PerformDefineRelocation(Architecture* arch, BNRelocationInfo& info, uint64_t target, uint64_t reloc); void PerformDefineRelocation(Architecture* arch, BNRelocationInfo& info, Ref sym, uint64_t reloc); + /*! DidApplySnapshotData is called when loading a view from a database, after snapshot data has been applied to it. + + \note This method **may** be overridden by custom BinaryViews. + + \warning This method **must not** be called directly. + + */ + virtual void DidApplySnapshotData() {} + public: void NotifyDataWritten(uint64_t offset, size_t len); void NotifyDataInserted(uint64_t offset, size_t len); @@ -5780,6 +5789,7 @@ namespace BinaryNinja { private: static bool InitCallback(void* ctxt); + static void DidApplySnapshotDataCallback(void* ctxt); static void FreeCallback(void* ctxt); static size_t ReadCallback(void* ctxt, void* dest, uint64_t offset, size_t len); static size_t WriteCallback(void* ctxt, uint64_t offset, const void* src, size_t len); diff --git a/binaryninjacore.h b/binaryninjacore.h index ef38cef85..560a08900 100644 --- a/binaryninjacore.h +++ b/binaryninjacore.h @@ -37,14 +37,14 @@ // Current ABI version for linking to the core. This is incremented any time // there are changes to the API that affect linking, including new functions, // new types, or modifications to existing functions or types. -#define BN_CURRENT_CORE_ABI_VERSION 162 +#define BN_CURRENT_CORE_ABI_VERSION 163 // Minimum ABI version that is supported for loading of plugins. Plugins that // are linked to an ABI version less than this will not be able to load and // will require rebuilding. The minimum version is increased when there are // incompatible changes that break binary compatibility, such as changes to // existing types or functions. -#define BN_MINIMUM_CORE_ABI_VERSION 161 +#define BN_MINIMUM_CORE_ABI_VERSION 163 #ifdef __GNUC__ #ifdef BINARYNINJACORE_LIBRARY @@ -1847,6 +1847,7 @@ extern "C" bool (*isRelocatable)(void* ctxt); size_t (*getAddressSize)(void* ctxt); bool (*save)(void* ctxt, BNFileAccessor* accessor); + void (*didApplySnapshotData)(void* ctxt); } BNCustomBinaryView; typedef struct BNCustomBinaryViewType diff --git a/binaryview.cpp b/binaryview.cpp index 85a7cfbe3..e4f6422ff 100644 --- a/binaryview.cpp +++ b/binaryview.cpp @@ -1365,6 +1365,7 @@ BinaryView::BinaryView(const std::string& typeName, FileMetadata* file, BinaryVi view.isRelocatable = IsRelocatableCallback; view.getAddressSize = GetAddressSizeCallback; view.save = SaveCallback; + view.didApplySnapshotData = DidApplySnapshotDataCallback; m_file = file; AddRefForRegistration(); m_object = BNCreateCustomBinaryView( @@ -1388,6 +1389,13 @@ bool BinaryView::InitCallback(void* ctxt) } +void BinaryView::DidApplySnapshotDataCallback(void* ctxt) +{ + CallbackRef view(ctxt); + view->DidApplySnapshotData(); +} + + void BinaryView::FreeCallback(void* ctxt) { BinaryView* view = (BinaryView*)ctxt; diff --git a/python/binaryview.py b/python/binaryview.py index 5c86e101e..3321ad535 100644 --- a/python/binaryview.py +++ b/python/binaryview.py @@ -3173,6 +3173,7 @@ def __init__( self._cb.isRelocatable = self._cb.isRelocatable.__class__(self._is_relocatable) self._cb.getAddressSize = self._cb.getAddressSize.__class__(self._get_address_size) self._cb.save = self._cb.save.__class__(self._save) + self._cb.didApplySnapshotData = self._cb.didApplySnapshotData.__class__(self._did_apply_snapshot_data) if file_metadata is None: raise Exception("Attempting to create a BinaryView with FileMetadata which is None") self._file = file_metadata @@ -4482,6 +4483,15 @@ def _save(self, ctxt, file_accessor): log_error_for_exception("Unhandled Python exception in BinaryView._save") return False + def _did_apply_snapshot_data(self, ctxt): + try: + self.perform_did_apply_snapshot_data() + except: + log_error_for_exception("Unhandled Python exception in BinaryView._did_apply_snapshot_data") + + def perform_did_apply_snapshot_data(self) -> None: + pass + def init(self) -> bool: return True diff --git a/rust/src/custom_binary_view.rs b/rust/src/custom_binary_view.rs index 4d21ce893..b43419f9b 100644 --- a/rust/src/custom_binary_view.rs +++ b/rust/src/custom_binary_view.rs @@ -460,6 +460,7 @@ pub unsafe trait CustomBinaryView: 'static + BinaryViewBase + Sync + Sized { fn new(handle: &BinaryView, args: &Self::Args) -> Result; fn init(&mut self, args: Self::Args) -> Result<()>; + fn did_apply_snapshot_data(&mut self) {} } /// Represents a partially initialized custom `BinaryView` that should be returned to the core @@ -600,6 +601,18 @@ impl<'a, T: CustomBinaryViewType> CustomViewBuilder<'a, T> { }) } + extern "C" fn cb_did_apply_snapshot_data(ctxt: *mut c_void) + where + V: CustomBinaryView, + { + ffi_wrap!("BinaryViewBase::didApplySnapshotData", unsafe { + let context = &mut *(ctxt as *mut CustomViewContext); + if let CustomViewContextState::Initialized { view } = &mut context.state { + view.did_apply_snapshot_data(); + } + }) + } + extern "C" fn cb_free_object(ctxt: *mut c_void) where V: CustomBinaryView, @@ -890,6 +903,7 @@ impl<'a, T: CustomBinaryViewType> CustomViewBuilder<'a, T> { isRelocatable: Some(cb_relocatable::), getAddressSize: Some(cb_address_size::), save: Some(cb_save::), + didApplySnapshotData: Some(cb_did_apply_snapshot_data::), }; let view_name = view_name.to_cstr();