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
52 changes: 52 additions & 0 deletions Delegate/ActivityActionDelegate.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
using System;

namespace BinaryNinja
{
// Action invoked when an Activity runs. The core hands the action the AnalysisContext the
// activity is executing under. Mirrors Python Activity.action (workflow.py): the raw
// BNAnalysisContext* is wrapped before reaching managed code.
public delegate void ActivityAction(AnalysisContext analysisContext);

internal static partial class NativeDelegates
{
// void (*actionCallback)(void* ctxt, BNAnalysisContext* ac)
public delegate void ActivityActionDelegate(IntPtr context, IntPtr analysisContext);
}

internal static partial class UnsafeUtils
{
// Adapts a public ActivityAction into the native action-callback shape, wrapping the raw
// BNAnalysisContext* via AnalysisContext.NewFromHandle. The core passes a borrowed handle;
// NewFromHandle adds a reference so the wrapper owns its own and frees it on dispose.
internal sealed class ActivityActionContext
{
private readonly ActivityAction m_callback;

internal ActivityActionContext(ActivityAction callback)
{
this.m_callback = callback;
}

internal void OnAction(IntPtr context, IntPtr analysisContextHandle)
{
AnalysisContext? analysisContext = AnalysisContext.NewFromHandle(analysisContextHandle);

// The core always provides a non-null AnalysisContext to a running activity; skip
// defensively if it does not.
if (null == analysisContext)
{
return;
}

this.m_callback(analysisContext);
}
}

internal static NativeDelegates.ActivityActionDelegate WrapActivityAction(ActivityAction callback)
{
ActivityActionContext context = new ActivityActionContext(callback);

return new NativeDelegates.ActivityActionDelegate(context.OnAction);
}
}
}
51 changes: 51 additions & 0 deletions Delegate/ActivityEligibilityDelegate.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
using System;

namespace BinaryNinja
{
// Eligibility check for an Activity: given the Activity and the AnalysisContext it would run
// under, return whether it may run. Mirrors Python Activity eligibility (workflow.py).
public delegate bool ActivityEligibility(Activity activity, AnalysisContext analysisContext);

internal static partial class NativeDelegates
{
// bool (*eligibilityCallback)(void* ctxt, BNActivity* activity, BNAnalysisContext* ac)
public delegate bool ActivityEligibilityDelegate(IntPtr context, IntPtr activity, IntPtr analysisContext);
}

internal static partial class UnsafeUtils
{
// Adapts a public ActivityEligibility into the native eligibility-callback shape, wrapping
// the raw BNActivity* and BNAnalysisContext* via their NewFromHandle factories (borrowed
// handles; each wrapper owns the reference it adds and frees it on dispose).
internal sealed class ActivityEligibilityContext
{
private readonly ActivityEligibility m_callback;

internal ActivityEligibilityContext(ActivityEligibility callback)
{
this.m_callback = callback;
}

internal bool OnEligibility(IntPtr context, IntPtr activityHandle, IntPtr analysisContextHandle)
{
Activity? activity = Activity.NewFromHandle(activityHandle);
AnalysisContext? analysisContext = AnalysisContext.NewFromHandle(analysisContextHandle);

// If either handle is missing the activity is treated as not eligible.
if (null == activity || null == analysisContext)
{
return false;
}

return this.m_callback(activity, analysisContext);
}
}

internal static NativeDelegates.ActivityEligibilityDelegate WrapActivityEligibility(ActivityEligibility callback)
{
ActivityEligibilityContext context = new ActivityEligibilityContext(callback);

return new NativeDelegates.ActivityEligibilityDelegate(context.OnEligibility);
}
}
}
67 changes: 43 additions & 24 deletions Handle/BNActivity.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,12 @@ public sealed class Activity : AbstractSafeHandle<Activity>
// Rooting the caller-supplied delegates on the instance keeps them alive as long as the
// Activity; without this, a temporary delegate passed to Create would be GC-eligible after
// Create returns and the next activity callback would dereference freed memory.
private Action<IntPtr>? m_action = null;
// Holds the NATIVE wrapper delegate (not the user delegate): the wrapper owns the GC root
// for the user callback via its holder context, and its function pointer is what the core
// invokes. Rooting the wrapper for the Activity's lifetime keeps the callback reachable.
private NativeDelegates.ActivityActionDelegate? m_action = null;

private Action<IntPtr>? m_eligibilityHandler = null;
private NativeDelegates.ActivityEligibilityDelegate? m_eligibilityHandler = null;

internal Activity(IntPtr handle , bool owner)
: base(handle , owner)
Expand Down Expand Up @@ -114,16 +117,22 @@ public string Name
/// <param name="configuration">The configuration name for the activity.</param>
/// <param name="action">The action callback delegate, or null for no action.</param>
/// <returns>A new owned Activity, or null on failure.</returns>
public static Activity? Create(string configuration , Action<IntPtr>? action = null)
public static Activity? Create(string configuration , ActivityAction? action = null)
{
// 1. Marshal the action delegate to a function pointer if provided.
IntPtr actionPtr = IntPtr.Zero;
// 1. Wrap the action into the native (ctxt, analysisContext) callback shape, then take a
// function pointer from the wrapper. The wrapper carries the AnalysisContext the core
// hands the callback (Action<IntPtr> dropped it, so the action never saw its context).
NativeDelegates.ActivityActionDelegate? actionWrapper = null;

if (null != action)
{
actionPtr = Marshal.GetFunctionPointerForDelegate<Action<IntPtr>>(action);
actionWrapper = UnsafeUtils.WrapActivityAction(action);
}

IntPtr actionPtr = null != actionWrapper
? Marshal.GetFunctionPointerForDelegate<NativeDelegates.ActivityActionDelegate>(actionWrapper)
: IntPtr.Zero;

// 2. Create the activity via the native API.
Activity? activity = Activity.TakeHandle(
NativeMethods.BNCreateActivity(
Expand All @@ -133,12 +142,12 @@ public string Name
)
);

// 3. Root the action delegate on the instance for the Activity's lifetime; otherwise the
// function pointer handed to the core would dangle once Create returns and the caller's
// delegate is collected (see m_action).
if (null != activity && null != action)
// 3. Root the wrapper (not the user delegate) on the instance for the Activity's lifetime;
// the wrapper holds the user callback via its context, and its function pointer is what the
// core invokes (see m_action).
if (null != activity && null != actionWrapper)
{
activity.m_action = action;
activity.m_action = actionWrapper;
}

return activity;
Expand All @@ -153,26 +162,36 @@ public string Name
/// <returns>A new owned Activity, or null on failure.</returns>
public static Activity? CreateWithEligibility(
string configuration ,
Action<IntPtr>? action = null ,
Action<IntPtr>? eligibilityHandler = null
ActivityAction? action = null ,
ActivityEligibility? eligibilityHandler = null
)
{
// 1. Marshal the action delegate to a function pointer if provided.
IntPtr actionPtr = IntPtr.Zero;
// 1. Wrap the action into the native (ctxt, analysisContext) callback shape.
NativeDelegates.ActivityActionDelegate? actionWrapper = null;

if (null != action)
{
actionPtr = Marshal.GetFunctionPointerForDelegate<Action<IntPtr>>(action);
actionWrapper = UnsafeUtils.WrapActivityAction(action);
}

// 2. Marshal the eligibility handler delegate to a function pointer if provided.
IntPtr eligibilityPtr = IntPtr.Zero;
IntPtr actionPtr = null != actionWrapper
? Marshal.GetFunctionPointerForDelegate<NativeDelegates.ActivityActionDelegate>(actionWrapper)
: IntPtr.Zero;

// 2. Wrap the eligibility handler into the native (ctxt, activity, analysisContext) -> bool
// callback shape. Action<IntPtr> dropped both handles and returned garbage bool, so the
// handler never received its arguments and eligibility was effectively random.
NativeDelegates.ActivityEligibilityDelegate? eligibilityWrapper = null;

if (null != eligibilityHandler)
{
eligibilityPtr = Marshal.GetFunctionPointerForDelegate<Action<IntPtr>>(eligibilityHandler);
eligibilityWrapper = UnsafeUtils.WrapActivityEligibility(eligibilityHandler);
}

IntPtr eligibilityPtr = null != eligibilityWrapper
? Marshal.GetFunctionPointerForDelegate<NativeDelegates.ActivityEligibilityDelegate>(eligibilityWrapper)
: IntPtr.Zero;

// 3. Create the activity with eligibility via the native API.
Activity? activity = Activity.TakeHandle(
NativeMethods.BNCreateActivityWithEligibility(
Expand All @@ -183,14 +202,14 @@ public string Name
)
);

// 4. Root both delegates on the instance for the Activity's lifetime; otherwise the
// function pointers handed to the core would dangle once CreateWithEligibility returns
// and the caller's delegates are collected (see m_action / m_eligibilityHandler).
// 4. Root both wrappers on the instance for the Activity's lifetime; the wrappers hold the
// user callbacks via their contexts, and their function pointers are what the core invokes
// (see m_action / m_eligibilityHandler).
if (null != activity)
{
activity.m_action = action;
activity.m_action = actionWrapper;

activity.m_eligibilityHandler = eligibilityHandler;
activity.m_eligibilityHandler = eligibilityWrapper;
}

return activity;
Expand Down
Loading