summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--binaryninjaapi.h5
-rw-r--r--binaryninjacore.h5
-rw-r--r--docs/getting-started.md34
-rw-r--r--docs/img/merge-vars-dialog.pngbin0 -> 58976 bytes
-rw-r--r--docs/img/merge-vars.pngbin0 -> 63066 bytes
-rw-r--r--docs/img/split-var.pngbin0 -> 54707 bytes
-rw-r--r--function.cpp32
-rw-r--r--mediumlevelil.cpp7
-rw-r--r--python/function.py44
-rw-r--r--python/mediumlevelil.py16
-rw-r--r--ui/commands.h3
-rw-r--r--ui/flowgraphwidget.h1
-rw-r--r--ui/linearview.h1
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
![Dead Store Elimination Results](img/dead-store-after.png "Dead Store Elimination Results")
+### 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.
+
+![Merge Variables Menu >](img/merge-vars.png "Merge Variables Menu")
+
+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.
+
+![Merge Variables Dialog](img/merge-vars-dialog.png "Merge Variables Dialog")
+
+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.
+
+![Split Variable >](img/split-var.png "Split Variable")
+
+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
![console >](img/console.png "Console")
diff --git a/docs/img/merge-vars-dialog.png b/docs/img/merge-vars-dialog.png
new file mode 100644
index 00000000..722503c9
--- /dev/null
+++ b/docs/img/merge-vars-dialog.png
Binary files differ
diff --git a/docs/img/merge-vars.png b/docs/img/merge-vars.png
new file mode 100644
index 00000000..3038b1db
--- /dev/null
+++ b/docs/img/merge-vars.png
Binary files differ
diff --git a/docs/img/split-var.png b/docs/img/split-var.png
new file mode 100644
index 00000000..72f45e85
--- /dev/null
+++ b/docs/img/split-var.png
Binary files differ
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