diff options
| author | Josh Ferrell <josh@vector35.com> | 2025-09-11 18:29:59 -0400 |
|---|---|---|
| committer | Josh Ferrell <josh@vector35.com> | 2025-09-11 19:09:28 -0400 |
| commit | 5f21429815bfa13bc167aff4a9785424675b82b6 (patch) | |
| tree | d13caac55b8ad269062929413e286bab44ad8429 /docs/dev/bnil-modifying.md | |
| parent | 2d20ed145b9135a7d3390acb3ac10a900d947f91 (diff) | |
Docs grammar and spelling fixes
Diffstat (limited to 'docs/dev/bnil-modifying.md')
| -rw-r--r-- | docs/dev/bnil-modifying.md | 215 |
1 files changed, 108 insertions, 107 deletions
diff --git a/docs/dev/bnil-modifying.md b/docs/dev/bnil-modifying.md index ac521d4f..bcd02b5b 100644 --- a/docs/dev/bnil-modifying.md +++ b/docs/dev/bnil-modifying.md @@ -19,7 +19,7 @@ Key: | Medium Level IL | ✅ | ✅ | ❌ | | High Level IL | ⚠️* | ❌ | ❌ | -\* Modifying High Level IL in C++, while some APIs exist, is incomplete and [certain more complicated operations are not possible yet](#notes-on-hlil). +\* 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 @@ -37,7 +37,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. +- **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 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. @@ -48,7 +48,7 @@ Based on your IL of choice, there are a bunch of places you can insert your modi - **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. - 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). +- **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. - **Medium Level IL** modification is generally done right after `core.function.generateMediumLevelIL` since (as of writing) the MLIL translation is all done in that one monolithic step and future steps are simply processing and annotations. This may change some time in the future. - **High Level IL** modification is generally done right after `core.function.generateHighLevelIL` since HLIL generation is also monolithic. This also may change eventually. @@ -63,12 +63,12 @@ Support for this is planned soon, but not implemented as of Binary Ninja 5.1. - 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. - - The `*LevelILInstruction` class represents a single **IL Expression**. The class name may be confusing since **IL Instructions** are a different concept. + - 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 an **IL Expression** that has been added as a top-level instruction to the function with a call to `*LevelILFunction.append()` (C++: `*LevelILFunction::AddInstruction`) - 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. - A **Mutate Transformation** is a simple modification of **IL Instructions** in an **IL Function** as per [Replacing Trivial Instructions (Mutate Transformation)](#replacing-trivial-instructions-mutate-transformation) below. -- A **Copy Transformation** is a more in-depth modification of an **IL Function** which involves copying its **IL Instructions** into a newly constructed **IL Function** with modifications made along the way, as per [Adding Instructions and Replacing Multiple Instructions (Copy Transformation)](#adding-instructions-and-replacing-multiple-instructions-copy-transformation) below. +- A **Copy Transformation** is a more in-depth modification of an **IL Function** which involves copying its **IL Instructions** into a newly constructed **IL Function** with modifications made along the way, as per [Adding Instructions and Replacing Multiple Instructions (Copy Transformation)](#adding-instructions-and-replacing-multiple-instructions-copy-transformation) below. ## Writing a Transformation @@ -79,24 +79,24 @@ Once you figure out which level of IL you want to modify, and where in the Workf In order for your modifications to run, you need to register a Workflow Activity. There are a couple of steps involved in this process: -First, you need to make a clone of the default workflow, making it mutable and allowing you to insert your Activity. +First, you need to make a clone of the default workflow, making it mutable and allowing you to insert your Activity. You can either name the clone the same thing as the metaAnalysis workflow to make your Activity available by default, or clone to a different name, which will require users to select that workflow in Open with Options. Also note that, as of writing, Objective-C support registers its own workflow as `core.function.objectiveC`. 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`. +The same applies if any of your other plugins register a custom workflow that is not `core.function.metaAnalysis`. === "Python" ```py - # This Workflow will replace metaAnalysis and be used by default + # 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: + # 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") ``` @@ -105,10 +105,10 @@ The same applies if any of your other plugins register a custom workflow that is ```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. @@ -122,7 +122,7 @@ Then, define a new Activity on the Workflow, which allows you to run your code t ```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({ @@ -137,7 +137,7 @@ Then, define a new Activity on the Workflow, which allows you to run your code t }), 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" @@ -152,11 +152,11 @@ Then, define a new Activity on the Workflow, which allows you to run your code t { // 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"~( { @@ -195,7 +195,7 @@ If you wish to replace more than one instruction, or replace an instruction with ### Adding Instructions and Replacing Multiple Instructions (Copy Transformation) If you want to insert new instructions into a function, or want to replace an instruction with than one instruction, you will need to construct a new **IL Function** based on the original **IL Function**, and make your changes while copying the instructions. -This is a rather cumbersome process and it can be easy to make mistakes. +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`. @@ -206,16 +206,16 @@ Let's call the new function `new_func` and the existing function `old_func`. # ... 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 + + # Tell the new function that we are copying from an existing function # (this transfers various metadata like some block labels) new_func.prepare_to_copy_function(old_func) - + # continues ... ``` @@ -231,14 +231,14 @@ Let's call the new function `new_func` and the existing function `old_func`. // 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 + // 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 + + // Tell the new function that we are copying from an existing function // (this transfers various metadata like some block labels) newFunc->PrepareToCopyFunction(oldFunc); @@ -249,9 +249,9 @@ Let's call the new function `new_func` and the existing function `old_func`. ???+ Note "Note" You may notice that the sample code above mentions always using `AnalysisContext.llil` (C++: `AnalysisContext::GetLowLevelIL()`) to access the relevant Low Level IL function, and you may be wondering why the sample doesn't instead use `AnalysisContext.function.llil` (C++: `AnalysisContext::GetFunction()->GetLowLevelILFunction()`). - This is because you must **always access IL Functions directly through `AnalysisContext`** when writing Workflow Activities, as that is where the current, up-to-date analysis information is stored. + This is because you must **always access IL Functions directly through `AnalysisContext`** when writing Workflow Activities, as that is where the current, up-to-date analysis information is stored. During analysis, the `LowLevelILFunction` object stored in the `AnalysisContext` object contains the in-progress analysis information from the current run of the Workflow, - whereas the `LowLevelILFunction` object stored on the Function object is from the previous run of the Workflow and contains stale analysis data. + whereas the `LowLevelILFunction` object stored on the Function object is from the previous run of the Workflow and contains stale analysis data. 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. @@ -260,22 +260,22 @@ When you reach a part where you want to insert new instructions (or replace inst ```py # previous code... - + # Copy each block in the old function to the new function - for old_block in old_func.basic_blocks: + for old_block in old_func.basic_blocks: # Copy some 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... ``` @@ -283,13 +283,13 @@ When you reach a part where you want to insert new instructions (or replace inst ```c++ // ... previous code - + // Copy each block in the old function to the new function for (auto& oldBlock: oldFunc->GetBasicBlocks()) { // Copy some 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 @@ -297,39 +297,39 @@ When you reach a part where you want to insert new instructions (or replace inst { // 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. === "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 ... ``` @@ -337,15 +337,15 @@ For each instruction, determine if you want to insert new instructions before it ```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); @@ -353,7 +353,7 @@ For each instruction, determine if you want to insert new instructions before it 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 @@ -368,16 +368,16 @@ For each instruction, determine if you want to insert new instructions before it And finally, regenerate dataflow: === "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 ``` @@ -386,13 +386,13 @@ And finally, regenerate dataflow: ```C++ // previous code ... - + // Create IL basic blocks newFunc->Finalize(); - + // Do dataflow, etc newFunc->GenerateSSAForm(); - + // Assign to the context to apply modifications context->SetMediumLevelILFunction(newFunc); } @@ -408,15 +408,15 @@ Due to limitations currently in the Python bindings, IL mappings will only updat ```py # previous code ... - + # 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) @@ -426,15 +426,15 @@ Due to limitations currently in the Python bindings, IL mappings will only updat ```C++ // previous code ... - + // 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(), @@ -471,11 +471,11 @@ Simply call `*LevelILFunction.get_label_for_source_instruction` (C++: `*LevelILF new_func = MediumLevelILFunction(old_func.arch, low_level_il=context.llil) # Calling prepare_to_copy_function creates labels in new_func - # for all blocks in old_func, accessible via get_label_for_source_instruction + # for all blocks in old_func, accessible via get_label_for_source_instruction new_func.prepare_to_copy_function(old_func) for old_block in old_func.basic_blocks: - # Calling prepare_to_copy_block populates the labels created by + # Calling prepare_to_copy_block populates the labels created by # prepare_to_copy_function above. new_func.prepare_to_copy_block(old_block) @@ -494,10 +494,10 @@ Simply call `*LevelILFunction.get_label_for_source_instruction` (C++: `*LevelILF # Emit MLIL_GOTO with the label for our target instruction new_func.append(new_func.goto(label, ILSourceLocation.from_instruction(old_instr)), ILSourceLocation.from_instruction(old_instr)) continue - + # Otherwise, copy the instruction as-is new_func.append(old_instr.copy_to(new_func), ILSourceLocation.from_instruction(old_instr)) - + # Rest of function... new_func.finalize() new_func.generate_ssa_form() @@ -518,12 +518,12 @@ Simply call `*LevelILFunction.get_label_for_source_instruction` (C++: `*LevelILF ); // Calling PrepareToCopyFunction creates labels in newFunc - // for all blocks in oldFunc, accessible via GetLabelForSourceInstruction + // for all blocks in oldFunc, accessible via GetLabelForSourceInstruction newFunc->PrepareToCopyFunction(oldFunc); - + for (auto& oldBlock: oldFunc->GetBasicBlocks()) { - // Calling PrepareToCopyBlock populates the labels created by + // Calling PrepareToCopyBlock populates the labels created by // PrepareToCopyFunction above. newFunc->PrepareToCopyBlock(oldBlock); @@ -540,21 +540,22 @@ Simply call `*LevelILFunction.get_label_for_source_instruction` (C++: `*LevelILF // If the target is something else, see the section below. auto oldTarget = indexOfSomeInstructionAtStartOfBlockInOldFunc; BNMediumLevelILLabel* label = newFunc->GetLabelForSourceInstruction(oldTarget); - + // Emit MLIL_GOTO with the label for our target instruction newFunc->AddInstruction(newFunc->Goto(*label, oldInstr), oldInstr); continue; } - + // Otherwise, copy the instruction as-is newFunc->AddInstruction(oldInstr.CopyTo(newFunc), oldInstr); } } - + // Rest of function... newFunc->Finalize(); newFunc->GenerateSSAForm(); // ... + } ``` #### Jumping to New Blocks @@ -574,7 +575,7 @@ If you want to have a `*LIL_GOTO` instruction target the middle of an existing * 4. Mark the label for your target 5. Now emit your target instruction and the rest of the block -As mentioned previously, make sure to re-use the same `*LevelILLabel` object for your target in the call to `*LevelILFunction.goto`, `*LevelILFunction.mark_label`, and any other `*LIL_GOTO` instructions pointing at your target. +As mentioned previously, make sure to re-use the same `*LevelILLabel` object for your target in the call to `*LevelILFunction.goto`, `*LevelILFunction.mark_label`, and any other `*LIL_GOTO` instructions pointing at your target. ## Useful Tools @@ -642,9 +643,9 @@ Compare this to `current_il_instruction`, which only gives you the top-level ins ### IL Sidebar Included in the [BN Inspectors plugin](https://github.com/CouleeApps/bn_inspectors) is a sidebar that lists all IL Expressions and IL Instructions. -It shows you all the expressions' and instructions' indices in a big table, making it easier to determine where things are located. +It shows you all the expressions' and instructions' indices in a big table, making it easier to determine where things are located. It can be quite useful for determining the raw operand values of various expressions and finding dangling expressions that get lost after particularly messy calls to `replace_expr`. -It also shows what types the expressions have (MLIL and higher). +It also shows what types the expressions have (MLIL and higher).  @@ -661,15 +662,15 @@ 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: === "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 - + if report is not None: show_report_collection("My Debug Report", report) ``` @@ -684,9 +685,9 @@ In your Activity, you can check for one of these requests and then, if detected, { report = new ReportCollection(); } - + // ... do changes and build report - + if (report) { ShowReportCollection("My Debug Report", report); @@ -697,13 +698,13 @@ In your Activity, you can check for one of these requests and then, if detected, It can be exceedingly helpful to put your entire modification script inside a `try` block and present the report even on failure: === "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, @@ -715,7 +716,7 @@ It can be exceedingly helpful to put your entire modification script inside a `t ``` === "C++" - + ```C++ void RewriteAction(Ref<AnalysisContext> context) { @@ -724,7 +725,7 @@ It can be exceedingly helpful to put your entire modification script inside a `t { report = new ReportCollection(); } - + try { // ... do changes and build report @@ -744,7 +745,7 @@ It can be exceedingly helpful to put your entire modification script inside a `t } // 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 + // So you probably want to just eat the exception and print an angry message LogErrorWithStackTraceF("Got an exception"); // 5.2 and later // throw; } @@ -753,7 +754,7 @@ It can be exceedingly helpful to put your entire modification script inside a `t 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). +**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). === "Python" @@ -761,10 +762,10 @@ Then, you can build up the `ReportCollection` object by adding reports to it wit # 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)) ``` @@ -778,7 +779,7 @@ Then, you can build up the `ReportCollection` object by adding reports to it wit // Create graph *immediate* from the function auto graph = oldFunc->CreateFunctionGraphImmediate(settings); - + // And add it to my debug report report->AddGraphReport(context->GetBinaryView(), "Initial", graph); ``` @@ -786,20 +787,20 @@ Then, you can build up the `ReportCollection` object by adding reports to it wit You can also use the various graph APIs to style the report and highlight nodes or instructions: === "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)) ``` @@ -824,7 +825,7 @@ You can also use the various graph APIs to style the report and highlight nodes } auto graph = oldFunc->CreateFunctionGraphImmediate(settings); - + // 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++) @@ -841,7 +842,7 @@ You can also use the various graph APIs to style the report and highlight nodes // Update the graph with the highlighted node graph->ReplaceNode(i, node); } - + // And then you can add as normal report->AddGraphReport(context->GetBinaryView(), "Initial", graph); ``` @@ -852,31 +853,31 @@ You can also use the various graph APIs to style the report and highlight nodes Due to the nature of Workflows, a custom Debug Report is only able to be triggered if your Activity gets run. But what if you don't want to apply your changes yet? This might be because you suspect your changes are buggy, or so you can introspect the unchanged function while still seeing diagnostics from your changes. -Either way, sometimes it is helpful to have a variant of your Activity that does all of the logic _except_ for applying the updated analysis. +Either way, sometimes it is helpful to have a variant of your Activity that does all the logic _except_ for applying the updated analysis. There's no built-in solution for this, but you can model it pretty effectively by having two activities and a Dry Run switch. First, modify your Activity function with another parameter for if this is a dry run: === "Python" - + ```py def rewrite_action(context: AnalysisContext, dry_run: bool): # ... logic here - new_func = ... - + 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 = ...; - + auto newFunc = ...; + // Only update analysis if this is NOT a dry run if (!dryRun) { @@ -888,11 +889,11 @@ First, modify your Activity function with another parameter for if this is a dry Then, when registering your Workflow Activity, make two copies of the Activity, having one be for the dry run: === "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({ @@ -907,7 +908,7 @@ Then, when registering your Workflow Activity, make two copies of the Activity, }), 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({ @@ -930,7 +931,7 @@ Then, when registering your Workflow Activity, make two copies of the Activity, ```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"~( { @@ -946,7 +947,7 @@ Then, when registering your Workflow Activity, make two copies of the Activity, )~", [](Ref<AnalysisContext> context) { RewriteAction(context, true); // Dry run })); - + // ... and one that will actually apply changes, disabled by default wf->RegisterActivity(new Activity(R"~( { @@ -970,7 +971,7 @@ 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. === "Python" - + ```py wf.insert_after("core.function.generateMediumLevelIL", [ "extension.my_extension.do_the_thing.dry_run", |
