Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
224 changes: 224 additions & 0 deletions Delegate/TypeArchiveNotificationCallbacks.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,224 @@
using System;
using System.Runtime.InteropServices;

namespace BinaryNinja
{
internal static partial class NativeDelegates
{
// void (*typeAdded)(void* ctxt, BNTypeArchive* archive, const char* id, BNType* definition)
public delegate void TypeArchiveTypeAddedDelegate(
IntPtr context, IntPtr archive, IntPtr id, IntPtr definition);

// void (*typeUpdated)(void* ctxt, BNTypeArchive* archive, const char* id, BNType* oldDefinition, BNType* newDefinition)
public delegate void TypeArchiveTypeUpdatedDelegate(
IntPtr context, IntPtr archive, IntPtr id, IntPtr oldDefinition, IntPtr newDefinition);

// void (*typeRenamed)(void* ctxt, BNTypeArchive* archive, const char* id, BNQualifiedName* oldName, BNQualifiedName* newName)
public delegate void TypeArchiveTypeRenamedDelegate(
IntPtr context, IntPtr archive, IntPtr id, IntPtr oldName, IntPtr newName);

// void (*typeDeleted)(void* ctxt, BNTypeArchive* archive, const char* id, BNType* definition)
public delegate void TypeArchiveTypeDeletedDelegate(
IntPtr context, IntPtr archive, IntPtr id, IntPtr definition);
}

internal static partial class UnsafeUtils
{
// Adapts a TypeArchiveNotification into the four native TypeArchive callback shapes. Each
// method marshals the native arguments into managed objects and forwards to the user's
// virtual override. The core invokes the function pointers stored on the
// BNTypeArchiveNotification struct; this context is rooted by those delegates (see
// TypeArchiveNotificationContext.BuildNative), so it -- and the notification it holds --
// stays alive for the registration lifetime.
internal sealed class TypeArchiveNotificationContext
{
internal readonly TypeArchiveNotification Notification;

// The allocated BNTypeArchiveNotification struct (IntPtr to AllocHGlobal memory), kept
// alive until FreeNative so the core-held registration never sees freed memory.
internal IntPtr NativeStruct = IntPtr.Zero;

// Rooting the four wrapper delegates on this context prevents the GC from reclaiming
// their function pointers while the core holds the registration.
private NativeDelegates.TypeArchiveTypeAddedDelegate? m_added;
private NativeDelegates.TypeArchiveTypeUpdatedDelegate? m_updated;
private NativeDelegates.TypeArchiveTypeRenamedDelegate? m_renamed;
private NativeDelegates.TypeArchiveTypeDeletedDelegate? m_deleted;

// The GCHandle stored as the struct's context field; freed on unregistration. The
// delegates already root this context, but the handle makes the rooting explicit and
// gives the core a stable context value to pass back.
private GCHandle m_selfHandle;

internal TypeArchiveNotificationContext(TypeArchiveNotification notification)
{
this.Notification = notification;
}

// Marshals the native arguments and forwards to the user override. A throwing override
// must not abort the core's notification dispatch, so exceptions are swallowed (Python
// logs and continues).
internal void OnTypeAdded(IntPtr context, IntPtr archive, IntPtr id, IntPtr definition)
{
try
{
TypeArchive? archiveObject = TypeArchive.BorrowHandle(archive);
if (null == archiveObject)
{
return;
}

string idString = PtrToAnsiString(id);
Type? definitionType = Type.NewFromHandle(definition);

this.Notification.OnTypeAdded(archiveObject, idString, definitionType);
}
catch (Exception)
{
// Swallowed: a failing callback must not crash the core dispatch.
}
}

internal void OnTypeUpdated(
IntPtr context, IntPtr archive, IntPtr id, IntPtr oldDefinition, IntPtr newDefinition)
{
try
{
TypeArchive? archiveObject = TypeArchive.BorrowHandle(archive);
if (null == archiveObject)
{
return;
}

string idString = PtrToAnsiString(id);
Type? oldType = Type.NewFromHandle(oldDefinition);
Type? newType = Type.NewFromHandle(newDefinition);

this.Notification.OnTypeUpdated(archiveObject, idString, oldType, newType);
}
catch (Exception)
{
// Swallowed: a failing callback must not crash the core dispatch.
}
}

internal void OnTypeRenamed(
IntPtr context, IntPtr archive, IntPtr id, IntPtr oldName, IntPtr newName)
{
try
{
TypeArchive? archiveObject = TypeArchive.BorrowHandle(archive);
if (null == archiveObject)
{
return;
}

string idString = PtrToAnsiString(id);
QualifiedName oldNameValue = QualifiedNameFromPtr(oldName);
QualifiedName newNameValue = QualifiedNameFromPtr(newName);

this.Notification.OnTypeRenamed(archiveObject, idString, oldNameValue, newNameValue);
}
catch (Exception)
{
// Swallowed: a failing callback must not crash the core dispatch.
}
}

internal void OnTypeDeleted(IntPtr context, IntPtr archive, IntPtr id, IntPtr definition)
{
try
{
TypeArchive? archiveObject = TypeArchive.BorrowHandle(archive);
if (null == archiveObject)
{
return;
}

string idString = PtrToAnsiString(id);
Type? definitionType = Type.NewFromHandle(definition);

this.Notification.OnTypeDeleted(archiveObject, idString, definitionType);
}
catch (Exception)
{
// Swallowed: a failing callback must not crash the core dispatch.
}
}

// Allocates the BNTypeArchiveNotification struct, sets its context and the four function
// pointers (taken from the rooted wrapper delegates), and returns the struct pointer.
// Idempotent: a second call returns the existing struct.
internal IntPtr BuildNative()
{
if (IntPtr.Zero != this.NativeStruct)
{
return this.NativeStruct;
}

// 1. Create the four wrapper delegates bound to this context and root them as fields.
this.m_added = new NativeDelegates.TypeArchiveTypeAddedDelegate(this.OnTypeAdded);
this.m_updated = new NativeDelegates.TypeArchiveTypeUpdatedDelegate(this.OnTypeUpdated);
this.m_renamed = new NativeDelegates.TypeArchiveTypeRenamedDelegate(this.OnTypeRenamed);
this.m_deleted = new NativeDelegates.TypeArchiveTypeDeletedDelegate(this.OnTypeDeleted);

// 2. Allocate a stable context handle so the core has a value to pass back, and so
// this context is explicitly rooted.
this.m_selfHandle = GCHandle.Alloc(this, GCHandleType.Normal);

// 3. Build the struct: context + four function pointers.
BNTypeArchiveNotification native = new BNTypeArchiveNotification();
native.context = GCHandle.ToIntPtr(this.m_selfHandle);
native.typeAdded = Marshal.GetFunctionPointerForDelegate(this.m_added);
native.typeUpdated = Marshal.GetFunctionPointerForDelegate(this.m_updated);
native.typeRenamed = Marshal.GetFunctionPointerForDelegate(this.m_renamed);
native.typeDeleted = Marshal.GetFunctionPointerForDelegate(this.m_deleted);

this.NativeStruct = Marshal.AllocHGlobal(Marshal.SizeOf<BNTypeArchiveNotification>());
Marshal.StructureToPtr<BNTypeArchiveNotification>(native, this.NativeStruct, false);

return this.NativeStruct;
}

// Frees the struct and the context handle. Called after the core has unregistered.
internal void FreeNative()
{
if (IntPtr.Zero != this.NativeStruct)
{
Marshal.DestroyStructure<BNTypeArchiveNotification>(this.NativeStruct);
Marshal.FreeHGlobal(this.NativeStruct);
this.NativeStruct = IntPtr.Zero;
}

if (this.m_selfHandle.IsAllocated)
{
this.m_selfHandle.Free();
}
}

private static string PtrToAnsiString(IntPtr pointer)
{
if (IntPtr.Zero == pointer)
{
return string.Empty;
}

return Marshal.PtrToStringAnsi(pointer) ?? string.Empty;
}

// Reads a BNQualifiedName out of a pointer the core passes (the archive owns the name
// storage, so it is read without freeing, matching Python's _from_core_struct).
private static QualifiedName QualifiedNameFromPtr(IntPtr pointer)
{
if (IntPtr.Zero == pointer)
{
return new QualifiedName(string.Empty);
}

BNQualifiedName native = Marshal.PtrToStructure<BNQualifiedName>(pointer);

return QualifiedName.FromNative(native);
}
}
}
}
57 changes: 57 additions & 0 deletions Handle/BNTypeArchive.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,10 @@ namespace BinaryNinja
/// </summary>
public sealed class TypeArchive : AbstractSafeHandle<TypeArchive>
{
// Live notification registrations, keyed by the user's TypeArchiveNotification instance. Each
// entry roots its native callback delegates and struct for the registration lifetime.
private readonly Dictionary<TypeArchiveNotification, UnsafeUtils.TypeArchiveNotificationContext>
m_notifications = new Dictionary<TypeArchiveNotification, UnsafeUtils.TypeArchiveNotificationContext>();
/// <summary>
/// Initializes a new TypeArchive wrapper around an existing native handle.
/// </summary>
Expand Down Expand Up @@ -890,5 +894,58 @@ public unsafe QualifiedNameTypeAndId[] GetTypes(string snapshot = "")
NativeMethods.BNFreeQualifiedNameTypeAndId
);
}

/// <summary>
/// Registers a notification object to receive type-add/update/rename/delete events from this
/// archive, mirroring Python <c>TypeArchive.register_notification</c> (typearchive.py:711).
/// The notification must be unregistered (or this archive released) to stop delivery.
/// </summary>
/// <param name="notification">The notification whose virtual methods receive the events.</param>
public void RegisterNotification(TypeArchiveNotification notification)
{
if (null == notification)
{
throw new ArgumentNullException(nameof(notification));
}

// A notification already registered on this archive is a no-op (Python tolerates this).
if (this.m_notifications.ContainsKey(notification))
{
return;
}

// 1. Build the native callback struct (roots the four delegates + the context).
UnsafeUtils.TypeArchiveNotificationContext context =
new UnsafeUtils.TypeArchiveNotificationContext(notification);
IntPtr nativePointer = context.BuildNative();

// 2. Register with the core, then keep the context rooted so the delegates outlive the
// registration.
NativeMethods.BNRegisterTypeArchiveNotification(this.handle, nativePointer);
this.m_notifications[notification] = context;
}

/// <summary>
/// Unregisters a previously-registered notification, mirroring Python
/// <c>TypeArchive.unregister_notification</c> (typearchive.py:721). Frees the native callback
/// struct and releases the rooted delegates.
/// </summary>
/// <param name="notification">The notification to unregister.</param>
public void UnregisterNotification(TypeArchiveNotification notification)
{
if (null == notification)
{
throw new ArgumentNullException(nameof(notification));
}

if (!this.m_notifications.TryGetValue(notification, out UnsafeUtils.TypeArchiveNotificationContext? context))
{
return;
}

NativeMethods.BNUnregisterTypeArchiveNotification(this.handle, context.NativeStruct);
context.FreeNative();
this.m_notifications.Remove(notification);
}
}
}
60 changes: 54 additions & 6 deletions Struct/BNTypeArchiveNotification.cs
Original file line number Diff line number Diff line change
Expand Up @@ -34,12 +34,60 @@ internal unsafe struct BNTypeArchiveNotification
public IntPtr typeDeleted;
}

public class TypeArchiveNotification
/// <summary>
/// Base class for receiving event notifications from a <see cref="TypeArchive"/>, mirroring
/// Python <c>TypeArchiveNotification</c> (typearchive.py:732). Subclass and override the
/// virtual methods for the events of interest, then register an instance via
/// <see cref="TypeArchive.RegisterNotification"/>.
/// </summary>
public abstract class TypeArchiveNotification
{

public TypeArchiveNotification()
{

}
/// <summary>
/// Called when a type is added to the archive, mirroring Python <c>type_added</c>
/// (typearchive.py:741).
/// </summary>
/// <param name="archive">The archive the type was added to.</param>
/// <param name="id">The id of the added type.</param>
/// <param name="definition">The definition of the added type.</param>
public virtual void OnTypeAdded(TypeArchive archive, string id, Type? definition)
{
}

/// <summary>
/// Called when a type in the archive is updated to a new definition, mirroring Python
/// <c>type_updated</c> (typearchive.py:748).
/// </summary>
/// <param name="archive">The archive the type belongs to.</param>
/// <param name="id">The id of the updated type.</param>
/// <param name="oldDefinition">The previous definition.</param>
/// <param name="newDefinition">The current definition.</param>
public virtual void OnTypeUpdated(
TypeArchive archive, string id, Type? oldDefinition, Type? newDefinition)
{
}

/// <summary>
/// Called when a type in the archive is renamed, mirroring Python <c>type_renamed</c>
/// (typearchive.py:755).
/// </summary>
/// <param name="archive">The archive the type belongs to.</param>
/// <param name="id">The id of the renamed type.</param>
/// <param name="oldName">The previous name.</param>
/// <param name="newName">The current name.</param>
public virtual void OnTypeRenamed(
TypeArchive archive, string id, QualifiedName oldName, QualifiedName newName)
{
}

/// <summary>
/// Called when a type is deleted from the archive, mirroring Python <c>type_deleted</c>
/// (typearchive.py:762).
/// </summary>
/// <param name="archive">The archive the type was deleted from.</param>
/// <param name="id">The id of the deleted type.</param>
/// <param name="definition">The definition of the deleted type.</param>
public virtual void OnTypeDeleted(TypeArchive archive, string id, Type? definition)
{
}
}
}
Loading