summaryrefslogtreecommitdiff
path: root/docs/dev
diff options
context:
space:
mode:
authorGlenn Smith <glenn@vector35.com>2025-07-31 17:27:52 -0400
committerGlenn Smith <glenn@vector35.com>2025-08-01 21:37:39 -0400
commit1681053397485739253763507272419110508907 (patch)
tree9bbd1e97da03d94b797788e80bc5f135e4742ead /docs/dev
parent0996601386125530284705160083919973992954 (diff)
Update IL modification docs
Diffstat (limited to 'docs/dev')
-rw-r--r--docs/dev/bnil-modifying.md786
1 files changed, 586 insertions, 200 deletions
diff --git a/docs/dev/bnil-modifying.md b/docs/dev/bnil-modifying.md
index 4c29d63b..629f862f 100644
--- a/docs/dev/bnil-modifying.md
+++ b/docs/dev/bnil-modifying.md
@@ -16,10 +16,10 @@ Key:
|-----------------|:---:|:------:|:----:|
| Lifted IL | ✅ | ✅ | ✅ |
| Low Level IL | ✅ | ✅ | ✅ |
-| Medium Level IL | ⚠️* | ✅ | ❌ |
+| Medium Level IL | ✅ | ✅ | ❌ |
| High Level IL | ⚠️* | ❌ | ❌ |
-\* Modifying Medium and High Level IL in C++, while some APIs exist, is incomplete and [certain more complicated operations are not possible yet](#il-mappings-for-mlil-and-higher-in-c).
+\* Modifying High Level IL in C++, while some APIs exist, is incomplete and [certain more complicated operations are not possible yet](#notes-on-hlil).
## Choosing A Level of IL To Modify
@@ -28,6 +28,7 @@ Depending on what effects you want to have, different levels will work better.
- Modifying **Lifted IL** is similar to a generic Architecture Extension, letting you change instructions directly out of the lifter.
Note that Lifted IL has no SSA forms, dataflow, stack offsets, or even resolved flags.
+ - **You probably do not want to modify Lifted IL with a Workflow**. Instead, consider modifying the Architecture directly ([most of which are Open Source on our GitHub](https://github.com/Vector35/binaryninja-api/tree/dev/arch)) or making your Activity modify Low Level IL.
- Modifying **Low Level IL** lets you affect operations on registers, which eventually affect values of variables (in MLIL).
Depending on where in the pipeline you insert your action, stack offsets may or may not be calculated, and most dataflow information is not available.
Notably, any `PossibleValueSet` calculations require MLIL or higher, so only `RegisterValue` dataflow results are available (and only after generating SSA form).
@@ -37,8 +38,7 @@ Notably, any `PossibleValueSet` calculations require MLIL or higher, so only `Re
There are certain ILs which you cannot modify, for a variety of reasons:
- **SSA Forms** are generated by the core from the various Non-SSA Form ILs at each stage of analysis. You should not modify them, as they are considered analysis derived from the Non-SSA Form. They will likely be regenerated from the source Non-SSA Form without consulting any changes you might make to them.
-- **Disassembly** should be modified using an Architecture Extension, not a Workflow Activity.
-- **Lifted IL SSA Form** does not exist, as Lifted IL gets lifted directly to LLIL, which does have an SSA form.
+- **Disassembly** should be modified by changing the Architecture plugin if it is open source, or by using an Architecture Extension if it is not. A Workflow Activity cannot affect Disassembly.
- **Mapped MLIL** is lifted directly from LLIL SSA Form and not user-editable. Generally speaking, users and developers of Workflow Actions should not need to consult Mapped MLIL.
- **Pseudo-C** and other Language Representation forms are not a distinct level of IL, but are simply alternate renderings of HLIL.
@@ -47,7 +47,6 @@ There are certain ILs which you cannot modify, for a variety of reasons:
Based on your IL of choice, there are a bunch of places you can insert your modification action, which affects the information available to use or modify.
- **Lifted IL** modification needs to happen before the `core.function.analyzeAndExpandFlags` stage, which translates Lifted IL into Low Level IL. After that stage, Lifted IL is never again consulted.
- - **You probably do not want to modify Lifted IL with a Workflow**. Instead, consider modifying the Architecture directly ([most of which are Open Source on our GitHub](https://github.com/Vector35/binaryninja-api/tree/dev/arch)) or making your Activity modify Low Level IL.
- Modifying flag definitions and usages is likely impossible, as the Semantic Flags system will consult the Architecture to determine what IL to generate when a flag is used, not your Workflow Action. There is currently no way to change this behavior.
- **Low Level IL** modification is generally done right before the `core.function.generateMediumLevelIL` stage, after the final Low Level IL function is emitted. After Medium Level IL is generated, changes to the Low Level IL will have limited effects on the other ILs (because most things refer to MLIL).
- If you want to make changes to LLIL that can affect the stack pointer resolution, you will want to insert your action before `core.function.analyzeStackAdjustment` instead.
@@ -58,10 +57,11 @@ Based on your IL of choice, there are a bunch of places you can insert your modi
- An **IL Function** is a collection of **IL Instructions**, grouped into **IL Basic Blocks**
- An **IL Basic Block** is a contiguous sequence of **IL Instructions** between a start and end **Instruction Index**, which only has incoming branches at the start and outgoing branches at the end.
-- An **IL Expression** is a single-operation expression, which may have child expressions or be the child of another expression
+- An **IL Expression** is a single-operation expression, which may have child expressions or be the child of another expression.
+ - The `*LevelILInstruction` class represents a single **IL Expression**. The class name may be confusing since **IL Instructions** are a different concept.
- An **Expression Index** points to an **IL Expression** and may be used as an operand of another **IL Expression**.
-- An **IL Instruction** is a top-level **IL Expression**.
-- An **Instruction Index** points to an **IL Instruction** and may be contained within the start and end regions of an **IL Basic Block**.
+- An **IL Instruction** is an **IL Expression** that has been added as a top-level instruction to the function with a call to `*LevelILFunction::AddInstruction` (Python: `*LevelILFunction.append()`)
+- An **Instruction Index** is assigned to an **IL Expression** that is added as an **IL Instruction**. Child expressions of an **IL Instruction** do not inherently have an **Instruction Index**, though the API tries to provide this for you.
## Writing The Plugin
@@ -80,46 +80,95 @@ Also note that, as of writing, Objective-C support registers its own workflow as
If you want your Workflow Activity to apply to Objective-C files, you will need to do this whole process twice, once for `core.function.metaAnalysis` and once for `core.function.objectiveC`.
The same applies if any of your other plugins register a custom workflow that is not `core.function.metaAnalysis`.
-```py
-# This Workflow will replace metaAnalysis and be used by default
-wf = Workflow("core.function.metaAnalysis").clone("core.function.metaAnalysis")
-
-# Users will need to pick this Workflow in Open with Options
-wf = Workflow("core.function.metaAnalysis").clone("MyCustomWorkflow")
+=== "Python"
+ ```py
+ # This Workflow will replace metaAnalysis and be used by default
+ wf = Workflow("core.function.metaAnalysis").clone("core.function.metaAnalysis")
+
+ # Users will need to pick this Workflow in Open with Options
+ wf = Workflow("core.function.metaAnalysis").clone("MyCustomWorkflow")
+
+ # As of 5.1: To modify functions in binaries using the Objective-C workflow,
+ # you need to do the entire rest of this section twice:
+ # once as above, and once for the Objective-C workflow as shown here.
+ wf = Workflow("core.function.objectiveC").clone("core.function.objectiveC")
+ ```
-# As of 5.1: To modify functions in binaries using the Objective-C workflow,
-# you need to do the entire rest of this section twice:
-# once as above, and once for the Objective-C workflow as shown here.
-wf = Workflow("core.function.objectiveC").clone("core.function.objectiveC")
-```
+=== "C++"
+ ```c++
+ // This Workflow will replace metaAnalysis and be used by default
+ auto wf = Workflow::Instance("core.function.metaAnalysis")->Clone("core.function.metaAnalysis");
+
+ // Users will need to pick this Workflow in Open with Options
+ auto wf = Workflow::Instance("core.function.metaAnalysis")->Clone("MyCustomWorkflow");
+
+ // As of 5.1: To modify functions in binaries using the Objective-C workflow,
+ // you need to do the entire rest of this section twice:
+ // once as above, and once for the Objective-C workflow as shown here.
+ auto wf = Workflow::Instance("core.function.objectiveC")->Clone("core.function.objectiveC");
+ ```
Then, define a new Activity on the Workflow, which allows you to run your code to make modifications.
-```py
-def rewrite_action(context: AnalysisContext):
- # Actual modification code goes here (documented below)...
+=== "Python"
-# Define the custom activity configuration
-wf.register_activity(Activity(
- configuration=json.dumps({
- "name": "extension.my_extension.do_the_thing",
- "title": "My IL Modifications",
- "description": "Does some IL modifications.",
- "eligibility": {
- "auto": {
- "default": False # Controls if the workflow is enabled by default
+ ```py
+ def rewrite_action(context: AnalysisContext):
+ # Actual modification code goes here (documented below)...
+
+ # Define the custom activity configuration
+ wf.register_activity(Activity(
+ configuration=json.dumps({
+ "name": "extension.my_extension.do_the_thing",
+ "title": "My IL Modifications",
+ "description": "Does some IL modifications.",
+ "eligibility": {
+ "auto": {
+ "default": False # Controls if the workflow is enabled by default
+ }
}
- }
- }),
- action=rewrite_action
-))
+ }),
+ action=rewrite_action
+ ))
+
+ # Add the new action to the workflow, with position as described above
+ wf.insert_after("core.function.generateMediumLevelIL", [
+ "extension.my_extension.do_the_thing"
+ ])
+ wf.register()
+ ```
-# Add the new action to the workflow, with position as described above
-wf.insert_after("core.function.generateMediumLevelIL", [
- "extension.my_extension.do_the_thing"
-])
-wf.register()
-```
+=== "C++"
+
+ ```c++
+ void RewriteAction(Ref<AnalysisContext> context)
+ {
+ // Actual modification code goes here (documented below)...
+ }
+
+ BINARYNINJAPLUGIN bool CorePluginInit()
+ {
+ // Rest of your plugin init here ...
+
+ // Define the custom activity configuration
+ wf->RegisterActivity(new Activity(R"~(
+ {
+ "name": "extension.my_extension.do_the_thing",
+ "title": "My IL Modifications",
+ "description": "Does some IL modifications.",
+ "eligibility": {
+ "auto": {
+ "default": false
+ }
+ }
+ }
+ )~", RewriteAction));
+ wf->InsertAfter("core.function.generateMediumLevelIL", std::vector<std::string>{
+ "extension.my_extension.do_the_thing"
+ });
+ Workflow::RegisterWorkflow(wf);
+ }
+ ```
A more in-depth explanation of the Workflows system is available [here](./workflows.md), with complete documentation of the Eligibility system and Workflows in general. This guide, however, will just give enough of an example for you to get to writing IL modification code.
@@ -144,112 +193,252 @@ This is a rather cumbersome process and it can be easy to make mistakes.
First, you need to construct a new **IL Function** based on the existing function.
Let's call the new function `new_func` and the existing function `old_func`.
-```py
-# ... workflow boilerplate
-def wf_activity(context: AnalysisContext):
- old_func = context.mlil
+=== "Python"
+
+ ```py
+ # ... workflow boilerplate
+ def rewrite_action(context: AnalysisContext):
+ old_func = context.mlil
+
+ # Create a new IL Function based on the old one.
+ # Make sure you use the LLIL from the analysis context,
+ # and *do not* go through Function.llil (it will not have been updated yet)
+ new_func = MediumLevelILFunction(old_func.arch, low_level_il=context.llil)
+
+ # Tell the new function that we are copying from an existing function
+ # (this transfers various metadata like block labels)
+ new_func.prepare_to_copy_function(old_func)
+
+ # continues ...
+ ```
- # Create a new IL Function based on the old one.
- # Make sure you use the LLIL from the analysis context, and *do not* go through Function.llil (it has not been updated yet)
- new_func = MediumLevelILFunction(old_func.arch, low_level_il=context.llil)
+=== "C++"
- # Tell the new function that we are copying from an existing function (this transfers various metadata like block labels)
- new_func.prepare_to_copy_function(old_func)
+ ```c++
+ void RewriteAction(Ref<AnalysisContext> context)
+ {
+ auto oldFunc = context->GetMediumLevelILFunction();
- # continues ...
-```
+ // Create a new IL Function based on the old one.
+ // Make sure you use the LLIL from the analysis context,
+ // and *do not* go through Function::GetLowLevelIL (it will not have been updated yet)
+
+ // Also (C++ specific), make sure you use Ref<> to hold
+ // a strong reference so the new function doesn't get deleted
+ Ref<MediumLevelILFunction> newFunc = new MediumLevelILFunction(
+ oldFunc->GetArchitecture(),
+ oldFunc->GetFunction(),
+ context->GetLowLevelILFunction()
+ );
+
+ // Tell the new function that we are copying from an existing function
+ // (this transfers various metadata like block labels)
+ newFunc->PrepareToCopyFunction(oldFunc);
+
+ // Continues...
+ }
+ ```
Then, going block-by-block, copy the instructions from `old_func` to `new_func`.
When you reach a part where you want to insert new instructions (or replace instructions), do that instead of simply copying the old instructions.
-```py
- # previous code...
-
- # Copy each block in the old function to the new function
- for old_block in old_func.basic_blocks:
- # Copy block labels and tell new IL instructions they came from this block
- new_func.prepare_to_copy_block(old_block)
+=== "Python"
+
+ ```py
+ # previous code...
- # Copy each instruction from the old block to the new function
- # Blocks "contain" instructions by knowing their start and end instruction indices
- # Any instructions in that range as in the block
- for old_instr_index in range(old_block.start, old_block.end):
- # Retrieve instruction from old function
- old_instr: MediumLevelILInstruction = old_func[old_instr_index]
+ # Copy each block in the old function to the new function
+ for old_block in old_func.basic_blocks:
+ # Copy block labels and tell new IL instructions they came from this block
+ new_func.prepare_to_copy_block(old_block)
+
+ # Copy each instruction from the old block to the new function
+ # Blocks "contain" instructions by knowing their start and end instruction indices
+ # Any instructions in that range as in the block
+ for old_instr_index in range(old_block.start, old_block.end):
+ # Retrieve instruction from old function
+ old_instr: MediumLevelILInstruction = old_func[old_instr_index]
+
+ # Make sure the new instruction we insert has the correct location information
+ new_func.set_current_address(old_instr.address, old_block.arch)
+
+ # continues...
+ ```
- # Make sure the new instruction we insert has the correct location information
- new_func.set_current_address(old_instr.address, old_block.arch)
+=== "C++"
- # continues...
-```
+ ```c++
+ // ... previous code
+
+ // Copy each block in the old function to the new function
+ for (auto& oldBlock: oldFunc->GetBasicBlocks())
+ {
+ // Copy block labels and tell new IL instructions they came from this block
+ newFunc->PrepareToCopyBlock(oldBlock);
+
+ // Copy each instruction from the old block to the new function
+ // Blocks "contain" instructions by knowing their start and end instruction indices
+ // Any instructions in that range as in the block
+ for (size_t oldInstrIndex = oldBlock->GetStart(); oldInstrIndex < oldBlock->GetEnd(); oldInstrIndex++)
+ {
+ // Retrieve instruction from old function
+ auto oldInstr = oldFunc->GetInstruction(oldInstrIndex);
+
+ // Make sure the new instruction we insert has the correct location information
+ newFunc->SetCurrentAddress(oldBlock->GetArchitecture(), oldInstr.address);
+
+ // continues...
+ ```
For each instruction, determine if you want to insert new instructions before it, or replace it with something else. You _probably_ want to do this step ahead of time and apply all the changes at once, but if your changes are simple enough you can get away with doing it inside the loop.
-```py
- # previous code...
+=== "Python"
+
+ ```py
+ # previous code...
+
+ if want_changes:
+ # Create new instructions in the function for whatever changes you desire
+
+ # All ILs: Be sure to pass the location of the previous instruction
+ # when creating a new expression, so mappings work
+ new_expr = new_func.nop(ILSourceLocation.from_instruction(old_instr))
+
+ # MLIL and higher: pass the location of the previous instruction
+ # to append(), so cross-IL mappings are preserved
+ new_func.append(new_expr, ILSourceLocation.from_instruction(old_instr))
+ else:
+ # Copy the instruction as-is
+
+ # Copy the instruction from the old function to the new function
+ # using `copy_to` (deep copy)
+ # MLIL and higher: also pass the location of the previous instruction
+ # to append() so cross-IL mappings are preserved
+ new_func.append(old_instr.copy_to(new_func), ILSourceLocation.from_instruction(old_instr))
+
+ # continues ...
+ ```
- if want_changes:
- # Create new instructions in the function for whatever changes you desire
+=== "C++"
+
+ ```C++
+ // previous code...
+
+ if (wantChanges)
+ {
+ // Create new instructions in the function for whatever changes you desire
+
+ // All ILs: Be sure to pass the location of the previous instruction
+ // when creating a new expression, so mappings work
+ auto newExpr = newFunc->Nop(oldInstr);
+
+ // MLIL and higher: pass the location of the previous instruction
+ // to AddInstruction(), so cross-IL mappings are preserved
+ newFunc->AddInstruction(newExpr, oldInstr);
+ }
+ else
+ {
+ // Copy the instruction as-is
+
+ // Copy the instruction from the old function to the new function
+ // using `CopyTo()` (deep copy)
+ // MLIL and higher: also pass the location of the previous instruction
+ // to AddInstruction() so cross-IL mappings are preserved
+ newFunc->AddInstruction(oldInstr.CopyTo(newFunc), oldInstr);
+ }
+ }
+ // continues ...
+ ```
- # All ILs: Be sure to pass the location of the previous instruction when creating a new expression so mappings work
- new_expr = new_func.nop(ILSourceLocation.from_instruction(old_instr))
- # MLIL and higher: pass the location of the previous instruction to append() so cross-IL mappings are preserved
- new_func.append(new_expr, ILSourceLocation.from_instruction(old_instr))
- else:
- # Copy the instruction as-is
-
- # Copy the instruction from the old function to the new function using `copy_to` (deep copy)
- # MLIL and higher: also pass the location of the previous instruction to append() so cross-IL mappings are preserved
- new_func.append(old_instr.copy_to(new_func), ILSourceLocation.from_instruction(old_instr))
- # continues ...
-```
And finally, regenerate dataflow:
-```py
- # previous code ...
+=== "Python"
+
+ ```py
+ # previous code ...
+
+ # Create IL basic blocks
+ new_func.finalize()
+
+ # Do dataflow, etc
+ new_func.generate_ssa_form()
+
+ # Assign to the context to apply modifications
+ context.mlil = new_func
+ ```
- # Create IL basic blocks
- new_func.finalize()
+=== "C++"
- # Do dataflow, etc
- new_func.generate_ssa_form()
+ ```C++
+ // previous code ...
+
+ // Create IL basic blocks
+ newFunc->Finalize();
+
+ // Do dataflow, etc
+ newFunc->GenerateSSAForm();
+
+ // Assign to the context to apply modifications
+ context->SetMediumLevelILFunction(newFunc);
+ }
+ ```
- # Assign to the context to apply modifications
- context.mlil = new_func
-```
### Transforming Multiple Times
If your action requires sufficiently complex logic that you want to transform the function twice, there are a few extra things to consider.
Due to limitations currently in the Python bindings, IL mappings will only update properly if you assign the intermediate MLIL function to the Analysis Context before transforming again:
-```py
- # previous code ...
+=== "Python"
- # Need to commit this for mappings to work (as of 5.1 at least)
- context.mlil = new_func
+ ```py
+ # previous code ...
- # Now we want to modify the function again...
+ # Need to commit this for mappings to work (as of 5.1 at least)
+ context.mlil = new_func
+
+ # Now we want to modify the function again...
+
+ # The newly created function is now our "old function"
+ old_func = new_func
+
+ # All of the rest of this is the same as before
+ new_func = MediumLevelILFunction(old_func.arch, low_level_il=context.llil)
+ new_func.prepare_to_copy_function(old_func)
+ ```
+
+=== "C++"
- # The newly created function is now our "old function"
- old_func = new_func
+ ```C++
+ // previous code ...
- # All of the rest of this is the same as before
- new_func = MediumLevelILFunction(old_func.arch, low_level_il=context.llil)
- new_func.prepare_to_copy_function(old_func)
-```
+ // Need to commit this for mappings to work (as of 5.1 at least)
+ context->SetMediumLevelILFunction(new_func);
+
+ // Now we want to modify the function again...
+
+ // The newly created function is now our "old function"
+ oldFunc = newFunc;
+
+ // All of the rest of this is the same as before
+ Ref<MediumLevelILFunction> newFunc = new MediumLevelILFunction(
+ oldFunc->GetArchitecture(),
+ oldFunc->GetFunction(),
+ context->GetLowLevelILFunction()
+ );
+ newFunc->PrepareToCopyFunction(oldFunc);
+ ```
You may realize this is rather at odds with the section below on having a [dry run mode](#dry-run-mode).
Sadly, there is currently no good solution for that, since the IL mappings are generated during assignment to the Analysis Context.
This may change in future versions, but is required for now.
-### IL Mappings for MLIL and Higher in C++
+### Notes on HLIL
-Using the `ILSourceLocation(const MediumLevelILInstruction&)` constructor when building IL Expressions is recommended, as it allows the expressions to have address and operand information tracked.
-However, cross-IL mappings are currently not implemented (see [Support](#support)), and modified functions will lose the ability to follow an expression to its LLIL equivalent (and likewise LLIL -> MLIL will be unavailable).
+Cross-IL mappings are currently not implemented when modifying HLIL (see [Support](#support)), and modified functions will lose the ability to follow an expression to its MLIL equivalent (and likewise MLIL -> HLIL will be unavailable).
Support for this is planned soon, but not implemented as of Binary Ninja 5.1.
## Useful Tools
@@ -336,70 +525,194 @@ current_function.request_debug_report("my_report")
In your Activity, you can check for one of these requests and then, if detected, generate a Debug Report and present it to the user:
-```py
-def rewrite_action(context: AnalysisContext):
- report = None
- if context.function.check_for_debug_report("my_report"):
- report = ReportCollection()
+=== "Python"
+
+ ```py
+ def rewrite_action(context: AnalysisContext):
+ report = None
+ if context.function.check_for_debug_report("my_report"):
+ report = ReportCollection()
+
+ # ... do changes and build report
- # ... do changes and build report
+ if report is not None:
+ show_report_collection("My Debug Report", report)
+ ```
- if report is not None:
- show_report_collection("My Debug Report", report)
-```
+=== "C++"
-It can be exceedingly helpful to put your entire modification script inside a `try`/`finally` block and present the report even on failure:
+ ```C++
+ void RewriteAction(Ref<AnalysisContext> context)
+ {
+ Ref<ReportCollection> report;
+ if (context->GetFunction()->CheckForDebugReport("my_report"))
+ {
+ report = new ReportCollection();
+ }
+
+ // ... do changes and build report
+
+ if (report)
+ {
+ ShowReportCollection("My Debug Report", report);
+ }
+ }
+ ```
-```py
-def rewrite_action(context: AnalysisContext):
- report = None
- if context.function.check_for_debug_report("my_report"):
- report = ReportCollection()
+It can be exceedingly helpful to put your entire modification script inside a `try` block and present the report even on failure:
- try:
- # ... do changes and build report
- # and if this throws an exception, the report will be presented anyway,
- # and filled up until the point of the exception
- raise
- finally:
- if report is not None:
- show_report_collection("My Debug Report", report)
-```
+=== "Python"
+
+ ```py
+ def rewrite_action(context: AnalysisContext):
+ report = None
+ if context.function.check_for_debug_report("my_report"):
+ report = ReportCollection()
+
+ try:
+ # ... do changes and build report
+ # and if this throws an exception, the report will be presented anyway,
+ # and filled up until the point of the exception
+ raise
+ finally:
+ if report is not None:
+ show_report_collection("My Debug Report", report)
+ ```
+
+=== "C++"
+
+ ```C++
+ void RewriteAction(Ref<AnalysisContext> context)
+ {
+ Ref<ReportCollection> report;
+ if (context->GetFunction()->CheckForDebugReport("my_report"))
+ {
+ report = new ReportCollection();
+ }
+
+ try
+ {
+ // ... do changes and build report
+ // and if this throws an exception, the report will be presented anyway,
+ // and filled up until the point of the exception
+
+ if (report)
+ {
+ ShowReportCollection("My Debug Report", report);
+ }
+ }
+ catch (...)
+ {
+ if (report)
+ {
+ ShowReportCollection("My Debug Report", report);
+ }
+
+ // Rethrowing the exception here might abort the program (if it is uncaught)
+ // So you probably want to just eat the exception and print an angry message
+ // throw;
+ char* trace = BNGetCurrentStackTraceString();
+ LogErrorF("Got an exception {}", trace);
+ free(trace);
+ }
+ }
+ ```
Then, you can build up the `ReportCollection` object by adding reports to it with the various Report APIs.
**Note:** If you are making function graphs using the various `create_graph` APIs, make sure to use `create_graph_immediate` as it will construct the graph from the function as it existed when called, instead of lazily once the view is rendered (otherwise you will only see the end state of the function).
-```py
-# For ReportCollection, create graph with the settings I want
-settings = DisassemblySettings()
-settings.set_option(DisassemblyOption.ShowAddress, True)
+=== "Python"
-# Create graph *immediate* from the function
-init_graph = old_func.create_graph_immediate(settings=settings)
+ ```py
+ # For ReportCollection, create graph with the settings I want
+ settings = DisassemblySettings()
+ settings.set_option(DisassemblyOption.ShowAddress, True)
+
+ # Create graph *immediate* from the function
+ graph = old_func.create_graph_immediate(settings=settings)
+
+ # And add it to my debug report
+ report.append(FlowGraphReport("Initial", graph, context.view))
+ ```
-# And add it to my debug report
-report.append(FlowGraphReport("Initial", init_graph, context.view))
-```
+=== "C++"
+
+ ```c++
+ // For ReportCollection, create graph with the settings I want
+ Ref<DisassemblySettings> settings = new DisassemblySettings();
+ settings->SetOption(ShowAddress, true);
+
+ // Create graph *immediate* from the function
+ auto graph = oldFunc->CreateFunctionGraphImmediate(settings);
+
+ // And add it to my debug report
+ report->AddGraphReport(context->GetBinaryView(), "Initial", graph);
+ ```
You can also use the various graph APIs to style the report and highlight nodes or instructions:
-```py
-init_graph = old_func.create_graph_immediate(settings=settings)
+=== "Python"
+
+ ```py
+ graph = old_func.create_graph_immediate(settings=settings)
+
+ # Go through the nodes in the graph and highlight some of them
+ for node in graph.nodes:
+ if some_condition:
+ node.highlight = HighlightStandardColor.RedHighlightColor
+ if other_condition:
+ node.highlight = HighlightStandardColor.GreenHighlightColor
+
+ # Update the graph with the highlighted node
+ graph.replace(node_index, node)
+
+ # And then you can add as normal
+ report.append(FlowGraphReport("Graph with Highlights", graph, context.view))
+ ```
+
+=== "C++"
+
+ ```C++
+
+ // Helper function for getting a BNHighlightColor for a BNHighlightStandardColor
+ BNHighlightColor GetHighlightColor(BNHighlightStandardColor color)
+ {
+ BNHighlightColor highlight;
+ highlight.style = StandardHighlightColor;
+ highlight.color = color;
+ highlight.mixColor = NoHighlightColor;
+ highlight.mix = 0;
+ highlight.r = 0;
+ highlight.g = 0;
+ highlight.b = 0;
+ highlight.alpha = 255;
+ return highlight;
+ }
-# Go through the nodes in the graph and highlight some of them
-for node in init_graph.nodes:
- if some_condition:
- node.highlight = HighlightStandardColor.RedHighlightColor
- if other_condition:
- node.highlight = HighlightStandardColor.GreenHighlightColor
+ auto graph = oldFunc->CreateFunctionGraphImmediate(settings);
- # Update the graph with the highlighted node
- init_graph.replace(node_index, node)
+ // Go through the nodes in the graph and highlight some of them
+ auto nodes = graph->GetNodes();
+ for (size_t i = 0; i < nodes.size(); i++)
+ {
+ auto node = nodes[i];
+ if (someCondition)
+ {
+ node->SetHighlight(GetHighlightColor(RedHighlightColor));
+ }
+ else if (otherCondition)
+ {
+ node->SetHighlight(GetHighlightColor(GreenHighlightColor));
+ }
+ // Update the graph with the highlighted node
+ graph->ReplaceNode(i, node);
+ }
+
+ // And then you can add as normal
+ report->AddGraphReport(context->GetBinaryView(), "Initial", graph);
+ ```
-# And then you can add as normal
-report.append(FlowGraphReport("Graph with Highlights", init_graph, context.view))
-```
### Dry Run Mode
@@ -411,61 +724,134 @@ There's no built-in solution for this, but you can model it pretty effectively b
First, modify your Activity function with another parameter for if this is a dry run:
-```py
-def rewrite_action(context: AnalysisContext, dry_run: bool):
- new_mlil = context.mlil
- # ... logic here
-
- # Only update analysis if this is NOT a dry run
- if not dry_run:
- context.mlil = new_mlil
-```
+=== "Python"
+
+ ```py
+ def rewrite_action(context: AnalysisContext, dry_run: bool):
+ # ... logic here
+ new_func = ...
+
+ # Only update analysis if this is NOT a dry run
+ if not dry_run:
+ context.mlil = new_func
+ ```
+
+=== "C++"
+
+ ```c++
+ void RewriteAction(Ref<AnalysisContext> context, bool dryRun)
+ {
+ // ... logic here
+ auto newFunc = ...;
+
+ // Only update analysis if this is NOT a dry run
+ if (!dryRun)
+ {
+ context->SetMediumLevelILFunction(newFunc);
+ }
+ }
+ ```
Then, when registering your Workflow Activity, make two copies of the Activity, having one be for the dry run:
-```py
-# Workflow setup boilerplate
-wf = Workflow("core.function.metaAnalysis").clone("core.function.metaAnalysis")
+=== "Python"
+
+ ```py
+ # Workflow setup boilerplate
+ wf = Workflow("core.function.metaAnalysis").clone("core.function.metaAnalysis")
+
+ # Define your activity twice, once as a "dry run" enabled by default
+ wf.register_activity(Activity(
+ configuration=json.dumps({
+ "name": "extension.my_extension.do_the_thing.dry_run",
+ "title": "My IL Modifications Dry Run",
+ "description": "My IL Modifications (dry run)",
+ "eligibility": {
+ "auto": {
+ "default": True # Dry run enabled by default
+ }
+ }
+ }),
+ action=lambda context: rewrite_action(context, True) # Dry run
+ ))
+
+ # ... and one that will actually apply changes, disabled by default
+ wf.register_activity(Activity(
+ configuration=json.dumps({
+ "name": "extension.my_extension.do_the_thing",
+ "title": "My IL Modifications",
+ "description": "My IL Modifications",
+ "eligibility": {
+ "auto": {
+ "default": False # Real run disabled by default
+ # (or enabled by default if you are confident)
+ }
+ }
+ }),
+ action=lambda context: rewrite_action(context, False) # Not dry run
+ ))
+ ```
-# Define your activity twice, once as a "dry run" enabled by default
-wf.register_activity(Activity(
- configuration=json.dumps({
- "name": "extension.my_extension.do_the_thing.dry_run",
- "title": "My IL Modifications Dry Run",
- "description": "My IL Modifications (dry run)",
- "eligibility": {
- "auto": {
- "default": True # Dry run enabled by default
+=== "C++"
+
+ ```c++
+ // Workflow setup boilerplate
+ auto wf = Workflow::Instance("core.function.metaAnalysis")->Clone("core.function.metaAnalysis");
+
+ // Define your activity twice, once as a "dry run" enabled by default
+ wf->RegisterActivity(new Activity(R"~(
+ {
+ "name": "extension.my_extension.do_the_thing.dry_run",
+ "title": "My IL Modifications",
+ "description": "Does some IL modifications.",
+ "eligibility": {
+ "auto": {
+ "default": true
+ }
}
}
- }),
- action=lambda context: rewrite_action(context, True) # Dry run
-))
-
-# ... and one that will actually apply changes, disabled by default
-wf.register_activity(Activity(
- configuration=json.dumps({
- "name": "extension.my_extension.do_the_thing",
- "title": "My IL Modifications",
- "description": "My IL Modifications",
- "eligibility": {
- "auto": {
- "default": False # Real run disabled by default
- # (or enabled by default if you are confident)
+ )~", [](Ref<AnalysisContext> context) {
+ RewriteAction(context, true); // Dry run
+ }));
+
+ // ... and one that will actually apply changes, disabled by default
+ wf->RegisterActivity(new Activity(R"~(
+ {
+ "name": "extension.my_extension.do_the_thing",
+ "title": "My IL Modifications",
+ "description": "Does some IL modifications.",
+ "eligibility": {
+ "auto": {
+ "default": false
+ }
}
}
- }),
- action=lambda context: rewrite_action(context, False) # Not dry run
-))
-```
+ )~", [](Ref<AnalysisContext> context) {
+ RewriteAction(context, false); // Not dry run
+ }));
+ ```
+
+
Finally, insert both actions into the workflow at the same place.
Insert the dry run first, so that, even if the real action is enabled, the dry run still runs on the unchanged state.
-```py
-wf.insert_after("core.function.generateMediumLevelIL", [
- "extension.my_extension.do_the_thing.dry_run",
- "extension.my_extension.do_the_thing"
-])
-wf.register()
-```
+=== "Python"
+
+ ```py
+ wf.insert_after("core.function.generateMediumLevelIL", [
+ "extension.my_extension.do_the_thing.dry_run",
+ "extension.my_extension.do_the_thing"
+ ])
+ wf.register()
+ ```
+
+=== "C++"
+
+ ```c++
+ wf->InsertAfter("core.function.generateMediumLevelIL", std::vector<std::string>{
+ "extension.my_extension.do_the_thing.dry_run",
+ "extension.my_extension.do_the_thing"
+ });
+ Workflow::RegisterWorkflow(wf);
+ ```