diff options
| -rw-r--r-- | binaryninjaapi.h | 5 | ||||
| -rw-r--r-- | binaryninjacore.h | 5 | ||||
| -rw-r--r-- | docs/getting-started.md | 34 | ||||
| -rw-r--r-- | docs/img/merge-vars-dialog.png | bin | 0 -> 58976 bytes | |||
| -rw-r--r-- | docs/img/merge-vars.png | bin | 0 -> 63066 bytes | |||
| -rw-r--r-- | docs/img/split-var.png | bin | 0 -> 54707 bytes | |||
| -rw-r--r-- | function.cpp | 32 | ||||
| -rw-r--r-- | mediumlevelil.cpp | 7 | ||||
| -rw-r--r-- | python/function.py | 44 | ||||
| -rw-r--r-- | python/mediumlevelil.py | 16 | ||||
| -rw-r--r-- | ui/commands.h | 3 | ||||
| -rw-r--r-- | ui/flowgraphwidget.h | 1 | ||||
| -rw-r--r-- | ui/linearview.h | 1 |
13 files changed, 148 insertions, 0 deletions
diff --git a/binaryninjaapi.h b/binaryninjaapi.h index 92f8f81e..1a0f1550 100644 --- a/binaryninjaapi.h +++ b/binaryninjaapi.h @@ -7756,6 +7756,9 @@ namespace BinaryNinja { std::map<Variable, std::set<Variable>> GetMergedVariables(); void MergeVariables(const Variable& target, const std::set<Variable>& sources); void UnmergeVariables(const Variable& target, const std::set<Variable>& sources); + std::set<Variable> GetSplitVariables(); + void SplitVariable(const Variable& var); + void UnsplitVariable(const Variable& var); uint64_t GetHighestAddress(); uint64_t GetLowestAddress(); @@ -8649,6 +8652,8 @@ namespace BinaryNinja { Ref<FlowGraph> CreateFunctionGraph(DisassemblySettings* settings = nullptr); std::set<size_t> GetLiveInstructionsForVariable(const Variable& var, bool includeLastUse = true); + + Variable GetSplitVariableForDefinition(const Variable& var, size_t instrIndex); }; struct HighLevelILInstruction; diff --git a/binaryninjacore.h b/binaryninjacore.h index 853ed61d..212c8b62 100644 --- a/binaryninjacore.h +++ b/binaryninjacore.h @@ -4010,6 +4010,9 @@ extern "C" size_t sourceCount); BINARYNINJACOREAPI void BNUnmergeVariables(BNFunction* func, const BNVariable* target, const BNVariable* sources, size_t sourceCount); + BINARYNINJACOREAPI BNVariable* BNGetSplitVariables(BNFunction* func, size_t* count); + BINARYNINJACOREAPI void BNSplitVariable(BNFunction* func, const BNVariable* var); + BINARYNINJACOREAPI void BNUnsplitVariable(BNFunction* func, const BNVariable* var); BINARYNINJACOREAPI BNReferenceSource* BNGetFunctionCallSites(BNFunction* func, size_t* count); BINARYNINJACOREAPI uint64_t* BNGetCallees(BNBinaryView* view, BNReferenceSource* callSite, size_t* count); @@ -4912,6 +4915,8 @@ extern "C" BNMediumLevelILFunction* func, const BNVariable* var, size_t* count); BINARYNINJACOREAPI size_t* BNGetMediumLevelILLiveInstructionsForVariable( BNMediumLevelILFunction* func, const BNVariable* var, bool includeLastUse, size_t* count); + BINARYNINJACOREAPI uint32_t BNGetDefaultIndexForMediumLevelILVariableDefinition( + BNMediumLevelILFunction* func, const BNVariable* var, size_t instrIndex); BINARYNINJACOREAPI BNRegisterValue BNGetMediumLevelILSSAVarValue( BNMediumLevelILFunction* func, const BNVariable* var, size_t version); diff --git a/docs/getting-started.md b/docs/getting-started.md index d6c6adc3..cddea0a4 100644 --- a/docs/getting-started.md +++ b/docs/getting-started.md @@ -233,6 +233,7 @@ Any action in the [action system](#command-palette) can have a custom hotkey map - `[CMD-SHIFT] -` (macOS) : Graph view zoom out - `[CTRL-SHIFT] +` (Windows/Linux) : Graph view zoom in - `[CTRL-SHIFT] -` (Windows/Linux) : Graph view zoom out + - `=` : Merge variables - Other hotkeys specifically for working with types are in the [type guide](guide/type.md#direct-ui-manipulation) ### Graph View @@ -499,6 +500,39 @@ Performing this action on both variables in the example results in the following  +### Merging and Splitting Variables + +Binary Ninja automatically splits all variables that the analysis determines to be safely splittable. This allows +the user to assign different types to different uses of the same register or memory location. Sometimes, however, +the code is more clear if two or more of these variables are merged together into a single variable. + + + +To merge variables, first click on the variable that the others should be merged into. Then, select "Merge Variables..." from the context menu (the default keybind for this action is `=`). This will bring up a dialog where the variables +to be merged in can be selected. All selected variables will be merged into the target variable, inheriting the name +and type of the variable that was first clicked. + + + +To unmerge a variable, either select "Split Merged Variable" from the context menu, or reenter the "Merge Variables..." +dialog and deselect the variables that should no longer be merged. + + + +Variables that have multiple definitions can be manually split into multiple variables by clicking on the +variable at a definition site and selecting "Split Variable at Definition" from the context menu. + +!!! Warning "Warning" + Splitting a variable manually can cause IL and decompilation to be incorrect. There are some + patterns where variables can be safely split semantically but analysis cannot determine that + it is safe, and this feature is provided to allow variable splitting to be performed in these + cases. + +Manually split variables will initially provide the selected definition as a separate variable, and all other +definitions will be automatically resolved into one or more other variables. If some of the definitions should +have been included as part of the same variable, use "Merge Variables..." from the context menu after splitting +to select which of the other definitions should be merged. + ### Script (Python) Console  diff --git a/docs/img/merge-vars-dialog.png b/docs/img/merge-vars-dialog.png Binary files differnew file mode 100644 index 00000000..722503c9 --- /dev/null +++ b/docs/img/merge-vars-dialog.png diff --git a/docs/img/merge-vars.png b/docs/img/merge-vars.png Binary files differnew file mode 100644 index 00000000..3038b1db --- /dev/null +++ b/docs/img/merge-vars.png diff --git a/docs/img/split-var.png b/docs/img/split-var.png Binary files differnew file mode 100644 index 00000000..72f45e85 --- /dev/null +++ b/docs/img/split-var.png diff --git a/function.cpp b/function.cpp index 06815c39..96795ef0 100644 --- a/function.cpp +++ b/function.cpp @@ -2611,6 +2611,38 @@ void Function::UnmergeVariables(const Variable& target, const std::set<Variable> } +std::set<Variable> Function::GetSplitVariables() +{ + size_t count = 0; + BNVariable* vars = BNGetSplitVariables(m_object, &count); + + std::set<Variable> result; + for (size_t i = 0; i < count; i++) + { + Variable var; + var.type = vars[i].type; + var.index = vars[i].index; + var.storage = vars[i].storage; + result.insert(var); + } + + BNFreeVariableList(vars); + return result; +} + + +void Function::SplitVariable(const Variable& var) +{ + BNSplitVariable(m_object, &var); +} + + +void Function::UnsplitVariable(const Variable& var) +{ + BNUnsplitVariable(m_object, &var); +} + + vector<ILReferenceSource> Function::GetMediumLevelILVariableReferences(const Variable& var) { size_t count; diff --git a/mediumlevelil.cpp b/mediumlevelil.cpp index 6827c356..cc619708 100644 --- a/mediumlevelil.cpp +++ b/mediumlevelil.cpp @@ -861,3 +861,10 @@ set<size_t> MediumLevelILFunction::GetLiveInstructionsForVariable(const Variable BNFreeILInstructionList(instrs); return result; } + + +Variable MediumLevelILFunction::GetSplitVariableForDefinition(const Variable& var, size_t instrIndex) +{ + return Variable( + var.type, BNGetDefaultIndexForMediumLevelILVariableDefinition(m_object, &var, instrIndex), var.storage); +} diff --git a/python/function.py b/python/function.py index 06a9beb5..93ed8b49 100644 --- a/python/function.py +++ b/python/function.py @@ -1397,6 +1397,21 @@ class Function: core.BNFreeMergedVariableList(data, count.value) return result + @property + def split_vars(self) -> List['variable.Variable']: + """ + Set of variables that have been split with ``split_var``. These variables correspond + to those unique to each definition site and are obtained by using + ``MediumLevelILInstruction.get_split_var_for_definition`` at the definitions. + """ + count = ctypes.c_ulonglong() + data = core.BNGetSplitVariables(self.handle, count) + result = [] + for i in range(count.value): + result.append(Variable.from_BNVariable(self, data[i])) + core.BNFreeVariableList(data) + return result + def mark_recent_use(self) -> None: core.BNMarkFunctionAsRecentlyUsed(self.handle) @@ -3374,6 +3389,35 @@ class Function: source_list[i].storage = sources[i].storage core.BNUnmergeVariables(self.handle, target.to_BNVariable(), source_list, len(sources)) + def split_var(self, var: 'variable.Variable') -> None: + """ + ``split_var`` splits a varible at the definition site. The given ``var`` must be the + variable unique to the definition and should be obtained by using + ``MediumLevelILInstruction.get_split_var_for_definition`` at the definition site. + + This function is not meant to split variables that have been previously merged. Use + ``unmerge_vars`` to split previously merged variables. + + .. warning:: Binary Ninja automatically splits all variables that the analysis determines \ + to be safely splittable. Splitting a variable manually with ``split_var`` can cause \ + IL and decompilation to be incorrect. There are some patterns where variables can be safely \ + split semantically but analysis cannot determine that it is safe. This function is provided \ + to allow variable splitting to be performed in these cases by plugins or by the user. + + :param Variable var: variable to split + """ + core.BNSplitVariable(self.handle, var.to_BNVariable()) + + def unsplit_var(self, var: 'variable.Variable') -> None: + """ + ``unsplit_var`` undoes varible splitting performed with ``split_var``. The given ``var`` + must be the variable unique to the definition and should be obtained by using + ``MediumLevelILInstruction.get_split_var_for_definition`` at the definition site. + + :param Variable var: variable to unsplit + """ + core.BNUnsplitVariable(self.handle, var.to_BNVariable()) + class AdvancedFunctionAnalysisDataRequestor: def __init__(self, func: 'Function' = None): diff --git a/python/mediumlevelil.py b/python/mediumlevelil.py index 06298e6e..47e7e2e1 100644 --- a/python/mediumlevelil.py +++ b/python/mediumlevelil.py @@ -730,6 +730,22 @@ class MediumLevelILInstruction(BaseILInstruction): core.BNGetMediumLevelILBranchDependence(self.function.handle, self.instr_index, branch_instr) ) + def get_split_var_for_definition(self, var: variable.Variable) -> variable.Variable: + """ + Gets the unique variable for a definition instruction. This unique variable can be passed + to ``Function.split_var`` to split a variable at a definition. The given ``var`` is the + assigned variable to query. + + :param Variable var: variable to query + :rtype: Variable + """ + return variable.Variable( + self.function.source_function, var.source_type, + core.BNGetDefaultIndexForMediumLevelILVariableDefinition( + self.function.handle, var.to_BNVariable(), self.instr_index + ), var.storage + ) + @property def operation(self) -> MediumLevelILOperation: return self.instr.operation diff --git a/ui/commands.h b/ui/commands.h index 9eee935e..2b1ab86c 100644 --- a/ui/commands.h +++ b/ui/commands.h @@ -43,3 +43,6 @@ uint64_t BINARYNINJAUIAPI getInnerMostStructureOffset( // Auto generate a structure name std::string BINARYNINJAUIAPI createStructureName(BinaryViewRef data); + +std::optional<BinaryNinja::Variable> BINARYNINJAUIAPI getSplitVariableForAssignment( + FunctionRef func, BNFunctionGraphType ilType, uint64_t location, const BinaryNinja::Variable& var); diff --git a/ui/flowgraphwidget.h b/ui/flowgraphwidget.h index 13efd46c..c69f8ea7 100644 --- a/ui/flowgraphwidget.h +++ b/ui/flowgraphwidget.h @@ -341,6 +341,7 @@ class BINARYNINJAUIAPI FlowGraphWidget : void tagAddressAccepted(TagTypeRef tt); void manageAddressTags(); void mergeVariables(); + void splitVariable(); void convertToNop(); void alwaysBranch(); diff --git a/ui/linearview.h b/ui/linearview.h index 845862ef..85fb0d5a 100644 --- a/ui/linearview.h +++ b/ui/linearview.h @@ -311,6 +311,7 @@ private Q_SLOTS: void createStruct(); void createNewTypes(); void mergeVariables(); + void splitVariable(); //! Get the length of of the string (if there is one) starting at the //! given address. String type is assumed to be UTF-8 by default, but the |
