# Copyright (c) 2015-2026 Vector 35 Inc
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to
# deal in the Software without restriction, including without limitation the
# rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
# sell copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in
# all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
# IN THE SOFTWARE.
import traceback
import ctypes
from typing import Optional, Union, List, Dict, Tuple
from dataclasses import dataclass
# Binary Ninja components
from . import _binaryninjacore as core
from .log import log_error_for_exception
from . import variable
from . import function
from . import architecture
from . import types
from . import binaryview
FunctionOrILFunction = Union["binaryninja.function.Function", "binaryninja.lowlevelil.LowLevelILFunction",
"binaryninja.mediumlevelil.MediumLevelILFunction",
"binaryninja.highlevelil.HighLevelILFunction"]
[docs]
@dataclass
class CallLayout:
parameters: List['types.ValueLocation']
return_value: Optional['types.ValueLocation']
stack_adjustment: int
reg_stack_adjustments: Dict['architecture.RegisterIndex', int]
@staticmethod
def _from_core_struct(struct: core.BNCallLayout, func: Optional['function.Function'] = None) -> 'CallLayout':
params = []
if func is None:
arch = None
else:
arch = func.arch
for i in range(struct.parameterCount):
params.append(types.ValueLocation._from_core_struct(struct.parameters[i], arch))
if struct.returnValueValid:
return_value = types.ValueLocation._from_core_struct(struct.returnValue, arch)
else:
return_value = None
stack_adjust = struct.stackAdjustment
reg_stack_adjust = dict()
for i in range(struct.registerStackAdjustmentCount):
reg = architecture.RegisterIndex(struct.registerStackAdjustmentRegisters[i])
reg_stack_adjust[reg] = struct.registerStackAdjustmentAmounts[i]
return CallLayout(params, return_value, stack_adjust, reg_stack_adjust)
def _to_core_struct(self):
struct = core.BNCallLayout()
struct.parameters = (core.BNValueLocation * len(self.parameters))()
struct.parameterCount = len(self.parameters)
for i in range(len(self.parameters)):
struct.parameters[i] = self.parameters[i]._to_core_struct()
if self.return_value is None:
struct.returnValueValid = False
else:
struct.returnValue = self.return_value._to_core_struct()
struct.returnValueValid = True
struct.stackAdjustment = self.stack_adjustment
struct.registerStackAdjustmentRegisters = (ctypes.c_uint * len(self.reg_stack_adjustments))()
struct.registerStackAdjustmentAmounts = (ctypes.c_int * len(self.reg_stack_adjustments))()
struct.registerStackAdjustmentCount = len(self.reg_stack_adjustments)
for i, (reg, amount) in enumerate(self.reg_stack_adjustments.items()):
struct.registerStackAdjustmentRegisters[i] = reg
struct.registerStackAdjustmentAmounts[i] = amount
return struct
[docs]
class CallingConvention:
"""
``class CallingConvention`` describes how parameters, return values, and the stack are handled
when a function is called. Subclasses of ``CallingConvention`` define the behavior of a calling
convention by setting the class attributes below and, where necessary, overriding the methods.
:cvar name: The name of this calling convention.
:cvar caller_saved_regs: The list of registers that are not preserved across a call
(caller-saved / volatile registers).
:cvar callee_saved_regs: The list of registers that a callee must preserve across a call
(callee-saved / non-volatile registers).
:cvar int_arg_regs: The registers used to pass integer and pointer arguments, in the order
they are used.
:cvar float_arg_regs: The registers used to pass floating point arguments, in the order they
are used.
:cvar required_arg_regs: The set of registers that must be arguments for heuristic calling
convention detection to consider this calling convention as a valid option.
:cvar required_clobbered_regs: The set of registers that must be clobbered for heuristic
calling convention detection to consider this calling convention as a valid option.
:cvar arg_regs_share_index: Whether the integer and floating point argument registers share a
single argument index. When ``True``, the Nth argument consumes the Nth slot of both the
integer and float register lists regardless of its type. When ``False``, integer and float
arguments are assigned from their respective register lists independently.
:cvar arg_regs_for_varargs: Whether argument registers are used to pass variadic arguments.
:cvar stack_reserved_for_arg_regs: Whether stack space is reserved by the caller for the
register arguments (for example, the shadow/home space used by the Windows x64 calling
convention).
:cvar stack_adjusted_on_return: Whether the callee adjusts the stack to remove the arguments
before returning (as in stdcall), rather than leaving the caller to clean up the stack (as
in cdecl).
:cvar eligible_for_heuristics: Whether this calling convention may be selected by heuristic
calling convention detection.
:cvar int_return_reg: The register that holds the integer return value.
:cvar high_int_return_reg: The register that holds the high part of an integer return value
that is too large to fit in a single register, or ``None`` if there is none.
:cvar float_return_reg: The register that holds the floating point return value, or ``None``
if there is none.
:cvar global_pointer_reg: The register that holds the global pointer, if the calling
convention defines one, or ``None`` if there is none.
:cvar implicitly_defined_regs: The registers that are implicitly given a known value on
function entry by this calling convention.
:cvar stack_args_naturally_aligned: Whether arguments passed on the stack are aligned to their
natural alignment. If ``False``, arguments are aligned to the address size.
:cvar stack_args_pushed_left_to_right: Whether arguments passed on the stack are pushed
left-to-right, as opposed to the more common right-to-left order.
"""
name = None
caller_saved_regs = []
callee_saved_regs = []
int_arg_regs = []
float_arg_regs = []
required_arg_regs = []
required_clobbered_regs = []
arg_regs_share_index = False
arg_regs_for_varargs = True
stack_reserved_for_arg_regs = False
stack_adjusted_on_return = False
eligible_for_heuristics = True
int_return_reg = None
high_int_return_reg = None
float_return_reg = None
global_pointer_reg = None
implicitly_defined_regs = []
stack_args_naturally_aligned = False
stack_args_pushed_left_to_right = False
_registered_calling_conventions = []
_pending_value_locations = {}
_pending_value_location_lists = {}
_pending_variable_lists = {}
_pending_reg_stack_adjustment_reg_lists = {}
_pending_reg_stack_adjustment_amount_lists = {}
[docs]
def __init__(
self, arch: Optional['architecture.Architecture'] = None, name: Optional[str] = None, handle=None,
confidence: int = core.max_confidence
):
if handle is None:
if arch is None or name is None:
raise ValueError("Must specify either handle or architecture and name")
self._arch = arch
self._pending_reg_lists = {}
self._cb = core.BNCustomCallingConvention()
self._cb.context = 0
self._cb.getCallerSavedRegisters = self._cb.getCallerSavedRegisters.__class__(self._get_caller_saved_regs)
self._cb.getCalleeSavedRegisters = self._cb.getCalleeSavedRegisters.__class__(self._get_callee_saved_regs)
self._cb.getIntegerArgumentRegisters = self._cb.getIntegerArgumentRegisters.__class__(
self._get_int_arg_regs
)
self._cb.getFloatArgumentRegisters = self._cb.getFloatArgumentRegisters.__class__(self._get_float_arg_regs)
self._cb.getRequiredArgumentRegisters = self._cb.getRequiredArgumentRegisters.__class__(
self._get_required_arg_regs
)
self._cb.getRequiredClobberedRegisters = self._cb.getRequiredClobberedRegisters.__class__(
self._get_required_clobbered_regs
)
self._cb.freeRegisterList = self._cb.freeRegisterList.__class__(self._free_register_list)
self._cb.areArgumentRegistersSharedIndex = self._cb.areArgumentRegistersSharedIndex.__class__(
self._arg_regs_share_index
)
self._cb.areArgumentRegistersUsedForVarArgs = self._cb.areArgumentRegistersUsedForVarArgs.__class__(
self._arg_regs_used_for_varargs
)
self._cb.isStackReservedForArgumentRegisters = self._cb.isStackReservedForArgumentRegisters.__class__(
self._stack_reserved_for_arg_regs
)
self._cb.isStackAdjustedOnReturn = self._cb.isStackAdjustedOnReturn.__class__(
self._stack_adjusted_on_return
)
self._cb.isEligibleForHeuristics = self._cb.isEligibleForHeuristics.__class__(self._eligible_for_heuristics)
self._cb.getIntegerReturnValueRegister = self._cb.getIntegerReturnValueRegister.__class__(
self._get_int_return_reg
)
self._cb.getHighIntegerReturnValueRegister = self._cb.getHighIntegerReturnValueRegister.__class__(
self._get_high_int_return_reg
)
self._cb.getFloatReturnValueRegister = self._cb.getFloatReturnValueRegister.__class__(
self._get_float_return_reg
)
self._cb.getGlobalPointerRegister = self._cb.getGlobalPointerRegister.__class__(
self._get_global_pointer_reg
)
self._cb.getImplicitlyDefinedRegisters = self._cb.getImplicitlyDefinedRegisters.__class__(
self._get_implicitly_defined_regs
)
self._cb.getIncomingRegisterValue = self._cb.getIncomingRegisterValue.__class__(
self._get_incoming_reg_value
)
self._cb.getIncomingFlagValue = self._cb.getIncomingFlagValue.__class__(self._get_incoming_flag_value)
self._cb.getIncomingVariableForParameterVariable = self._cb.getIncomingVariableForParameterVariable.__class__(
self._get_incoming_var_for_parameter_var
)
self._cb.getParameterVariableForIncomingVariable = self._cb.getParameterVariableForIncomingVariable.__class__(
self._get_parameter_var_for_incoming_var
)
self._cb.isReturnTypeRegisterCompatible = self._cb.isReturnTypeRegisterCompatible.__class__(
self._is_return_type_reg_compatible
)
self._cb.getIndirectReturnValueLocation = self._cb.getIndirectReturnValueLocation.__class__(
self._get_indirect_return_value_location
)
self._cb.getReturnedIndirectReturnValuePointer = self._cb.getReturnedIndirectReturnValuePointer.__class__(
self._get_returned_indirect_return_value_pointer
)
self._cb.isArgumentTypeRegisterCompatible = self._cb.isArgumentTypeRegisterCompatible.__class__(
self._is_arg_type_reg_compatible
)
self._cb.isNonRegisterArgumentIndirect = self._cb.isNonRegisterArgumentIndirect.__class__(
self._is_non_reg_arg_indirect
)
self._cb.areStackArgumentsNaturallyAligned = self._cb.areStackArgumentsNaturallyAligned.__class__(
self._are_stack_args_naturally_aligned
)
self._cb.areStackArgumentsPushedLeftToRight = self._cb.areStackArgumentsPushedLeftToRight.__class__(
self._are_stack_args_pushed_left_to_right
)
self._cb.getCallLayout = self._cb.getCallLayout.__class__(self._get_call_layout)
self._cb.freeCallLayout = self._cb.freeCallLayout.__class__(self._free_call_layout)
self._cb.getReturnValueLocation = self._cb.getReturnValueLocation.__class__(self._get_return_value_location)
self._cb.freeValueLocation = self._cb.freeValueLocation.__class__(self._free_value_location)
self._cb.getParameterLocations = self._cb.getParameterLocations.__class__(self._get_parameter_locations)
self._cb.freeParameterLocations = self._cb.freeParameterLocations.__class__(self._free_parameter_locations)
self._cb.getParameterOrderingForVariables = self._cb.getParameterOrderingForVariables.__class__(
self._get_parameter_ordering_for_variables
)
self._cb.freeVariableList = self._cb.freeVariableList.__class__(self._free_variable_list)
self._cb.getStackAdjustmentForLocations = self._cb.getStackAdjustmentForLocations.__class__(
self._get_stack_adjustment_for_locations
)
self._cb.getRegisterStackAdjustments = self._cb.getRegisterStackAdjustments.__class__(
self._get_register_stack_adjustments
)
self._cb.freeRegisterStackAdjustments = self._cb.freeRegisterStackAdjustments.__class__(
self._free_register_stack_adjustments
)
_handle = core.BNCreateCallingConvention(arch.handle, name, self._cb)
self.__class__._registered_calling_conventions.append(self)
else:
_handle = handle
assert _handle is not None
self.handle = _handle
self.confidence = confidence
def __del__(self):
if core is not None:
core.BNFreeCallingConvention(self.handle)
def __repr__(self):
return f"<calling convention: {self.arch.name} {self.name}>"
def __str__(self):
return self.name
def __eq__(self, other):
if not isinstance(other, self.__class__):
return NotImplemented
return ctypes.addressof(self.handle.contents) == ctypes.addressof(other.handle.contents)
def __ne__(self, other):
if not isinstance(other, self.__class__):
return NotImplemented
return not (self == other)
def __hash__(self):
return hash(ctypes.addressof(self.handle.contents))
def _get_caller_saved_regs(self, ctxt, count):
try:
regs = self.__class__.caller_saved_regs
count[0] = len(regs)
reg_buf = (ctypes.c_uint * len(regs))()
for i in range(0, len(regs)):
reg_buf[i] = self.arch.regs[regs[i]].index
result = ctypes.cast(reg_buf, ctypes.c_void_p)
self._pending_reg_lists[result.value] = (result, reg_buf)
return result.value
except Exception:
log_error_for_exception("Unhandled Python exception in CallingConvention._get_caller_saved_regs")
count[0] = 0
return None
def _get_callee_saved_regs(self, ctxt, count):
try:
regs = self.__class__.callee_saved_regs
count[0] = len(regs)
reg_buf = (ctypes.c_uint * len(regs))()
for i in range(0, len(regs)):
reg_buf[i] = self.arch.regs[regs[i]].index
result = ctypes.cast(reg_buf, ctypes.c_void_p)
self._pending_reg_lists[result.value] = (result, reg_buf)
return result.value
except Exception:
log_error_for_exception("Unhandled Python exception in CallingConvention._get_callee_saved_regs")
count[0] = 0
return None
def _get_int_arg_regs(self, ctxt, count):
try:
regs = self.__class__.int_arg_regs
count[0] = len(regs)
reg_buf = (ctypes.c_uint * len(regs))()
for i in range(0, len(regs)):
reg_buf[i] = self.arch.regs[regs[i]].index
result = ctypes.cast(reg_buf, ctypes.c_void_p)
self._pending_reg_lists[result.value] = (result, reg_buf)
return result.value
except Exception:
log_error_for_exception("Unhandled Python exception in CallingConvention._get_int_arg_regs")
count[0] = 0
return None
def _get_float_arg_regs(self, ctxt, count):
try:
regs = self.__class__.float_arg_regs
count[0] = len(regs)
reg_buf = (ctypes.c_uint * len(regs))()
for i in range(0, len(regs)):
reg_buf[i] = self.arch.regs[regs[i]].index
result = ctypes.cast(reg_buf, ctypes.c_void_p)
self._pending_reg_lists[result.value] = (result, reg_buf)
return result.value
except Exception:
log_error_for_exception("Unhandled Python exception in CallingConvention._get_float_arg_regs")
count[0] = 0
return None
def _get_required_arg_regs(self, ctxt, count):
try:
regs = self.__class__.required_arg_regs
count[0] = len(regs)
reg_buf = (ctypes.c_uint * len(regs))()
for i in range(0, len(regs)):
reg_buf[i] = self.arch.regs[regs[i]].index
result = ctypes.cast(reg_buf, ctypes.c_void_p)
self._pending_reg_lists[result.value] = (result, reg_buf)
return result.value
except Exception:
log_error_for_exception("Unhandled Python exception in CallingConvention._get_required_arg_regs")
count[0] = 0
return None
def _get_required_clobbered_regs(self, ctxt, count):
try:
regs = self.__class__.required_clobbered_regs
count[0] = len(regs)
reg_buf = (ctypes.c_uint * len(regs))()
for i in range(0, len(regs)):
reg_buf[i] = self.arch.regs[regs[i]].index
result = ctypes.cast(reg_buf, ctypes.c_void_p)
self._pending_reg_lists[result.value] = (result, reg_buf)
return result.value
except Exception:
log_error_for_exception("Unhandled Python exception in CallingConvention._get_required_clobbered_regs")
count[0] = 0
return None
def _free_register_list(self, ctxt, regs, count):
try:
buf = ctypes.cast(regs, ctypes.c_void_p)
if buf.value not in self._pending_reg_lists:
raise ValueError("freeing register list that wasn't allocated")
del self._pending_reg_lists[buf.value]
except Exception:
log_error_for_exception("Unhandled Python exception in CallingConvention._free_register_list")
def _arg_regs_share_index(self, ctxt):
try:
return self.__class__.arg_regs_share_index
except Exception:
log_error_for_exception("Unhandled Python exception in CallingConvention._arg_regs_share_index")
return False
def _arg_regs_used_for_varargs(self, ctxt):
try:
return self.__class__.arg_regs_for_varargs
except Exception:
log_error_for_exception("Unhandled Python exception in CallingConvention._arg_regs_used_for_varargs")
return False
def _stack_reserved_for_arg_regs(self, ctxt):
try:
return self.__class__.stack_reserved_for_arg_regs
except Exception:
log_error_for_exception("Unhandled Python exception in CallingConvention._stack_reserved_for_arg_regs")
return False
def _stack_adjusted_on_return(self, ctxt):
try:
return self.__class__.stack_adjusted_on_return
except Exception:
log_error_for_exception("Unhandled Python exception in CallingConvention._stack_adjusted_on_return")
return False
def _eligible_for_heuristics(self, ctxt):
try:
return self.__class__.eligible_for_heuristics
except Exception:
log_error_for_exception("Unhandled Python exception in CallingConvention._eligible_for_heuristics")
return False
def _get_int_return_reg(self, ctxt):
if self.__class__.int_return_reg is None:
return False
assert isinstance(self.__class__.int_return_reg, str), "int_return_reg return reg must be a string"
try:
return self.arch.regs[self.__class__.int_return_reg].index
except Exception:
log_error_for_exception("Unhandled Python exception in CallingConvention._get_int_return_reg")
return False
def _get_high_int_return_reg(self, ctxt):
try:
if self.__class__.high_int_return_reg is None:
return 0xffffffff
return self.arch.regs[self.__class__.high_int_return_reg].index
except Exception:
log_error_for_exception("Unhandled Python exception in CallingConvention._get_high_int_return_reg")
return False
def _get_float_return_reg(self, ctxt):
try:
if self.__class__.float_return_reg is None:
return 0xffffffff
return self.arch.regs[self.__class__.float_return_reg].index
except Exception:
log_error_for_exception("Unhandled Python exception in CallingConvention._get_float_return_reg")
return False
def _get_global_pointer_reg(self, ctxt):
try:
if self.__class__.global_pointer_reg is None:
return 0xffffffff
return self.arch.regs[self.__class__.global_pointer_reg].index
except Exception:
log_error_for_exception("Unhandled Python exception in CallingConvention._get_global_pointer_reg")
return False
def _get_implicitly_defined_regs(self, ctxt, count):
try:
regs = self.__class__.implicitly_defined_regs
count[0] = len(regs)
reg_buf = (ctypes.c_uint * len(regs))()
for i in range(0, len(regs)):
reg_buf[i] = self.arch.regs[regs[i]].index
result = ctypes.cast(reg_buf, ctypes.c_void_p)
self._pending_reg_lists[result.value] = (result, reg_buf)
return result.value
except Exception:
log_error_for_exception("Unhandled Python exception in CallingConvention._get_implicitly_defined_regs")
count[0] = 0
return None
def _get_incoming_reg_value(self, ctxt, reg, func, result):
try:
func_obj = function.Function(handle=core.BNNewFunctionReference(func))
reg_name = self.arch.get_reg_name(reg)
api_obj = self.perform_get_incoming_reg_value(reg_name, func_obj)._to_core_struct()
except Exception:
log_error_for_exception("Unhandled Python exception in CallingConvention._get_incoming_reg_value")
api_obj = variable.Undetermined()._to_core_struct()
result[0].state = api_obj.state
result[0].value = api_obj.value
def _get_incoming_flag_value(self, ctxt, flag, func, result):
try:
func_obj = function.Function(handle=core.BNNewFunctionReference(func))
flag_name = self.arch.get_flag_name(flag)
api_obj = self.perform_get_incoming_flag_value(flag_name, func_obj)._to_core_struct()
except Exception:
log_error_for_exception("Unhandled Python exception in CallingConvention._get_incoming_flag_value")
api_obj = variable.Undetermined()._to_core_struct()
result[0].state = api_obj.state
result[0].value = api_obj.value
def _get_incoming_var_for_parameter_var(self, ctxt, in_var, func, result):
try:
if func is None:
func_obj = None
else:
func_obj = function.Function(handle=core.BNNewFunctionReference(func))
in_var_obj = variable.CoreVariable.from_BNVariable(in_var[0])
out_var = self.perform_get_incoming_var_for_parameter_var(in_var_obj, func_obj)
result[0].type = out_var.source_type
result[0].index = out_var.index
result[0].storage = out_var.storage
except Exception:
log_error_for_exception("Unhandled Python exception in CallingConvention._get_incoming_var_for_parameter_var")
result[0].type = in_var[0].type
result[0].index = in_var[0].index
result[0].storage = in_var[0].storage
def _get_parameter_var_for_incoming_var(self, ctxt, in_var, func, result):
try:
if func is None:
func_obj = None
else:
func_obj = function.Function(handle=core.BNNewFunctionReference(func))
in_var_obj = variable.CoreVariable.from_BNVariable(in_var[0])
out_var = self.perform_get_parameter_var_for_incoming_var(in_var_obj, func_obj)
result[0].type = out_var.source_type
result[0].index = out_var.index
result[0].storage = out_var.storage
except Exception:
log_error_for_exception("Unhandled Python exception in CallingConvention._get_parameter_var_for_incoming_var")
result[0].type = in_var[0].type
result[0].index = in_var[0].index
result[0].storage = in_var[0].storage
def _is_return_type_reg_compatible(self, ctxt, view, type):
try:
if type:
if view:
view_obj = binaryview.BinaryView(handle=core.BNNewViewReference(view))
else:
view_obj = None
type_obj = types.Type.create(handle=core.BNNewTypeReference(type))
return self.is_return_type_reg_compatible(view_obj, type_obj)
else:
return False
except:
log_error_for_exception("Unhandled Python exception in CallingConvention._is_return_type_reg_compatible")
return False
def _get_indirect_return_value_location(self, ctxt, out_var):
try:
out_var[0] = self.get_indirect_return_value_location().to_BNVariable()
except:
log_error_for_exception(
"Unhandled Python exception in CallingConvention._get_indirect_return_value_location")
def _get_returned_indirect_return_value_pointer(self, ctxt, out_var):
try:
result = self.get_returned_indirect_return_value_pointer()
if result is None:
return False
out_var[0] = result.to_BNVariable()
return True
except:
log_error_for_exception(
"Unhandled Python exception in CallingConvention._get_returned_indirect_return_value_pointer")
return False
def _is_arg_type_reg_compatible(self, ctxt, view, type):
try:
if type:
if view:
view_obj = binaryview.BinaryView(handle=core.BNNewViewReference(view))
else:
view_obj = None
type_obj = types.Type.create(handle=core.BNNewTypeReference(type))
return self.is_arg_type_reg_compatible(view_obj, type_obj)
else:
return False
except:
log_error_for_exception("Unhandled Python exception in CallingConvention._is_arg_type_reg_compatible")
return False
def _is_non_reg_arg_indirect(self, ctxt, view, type):
try:
if view:
view_obj = binaryview.BinaryView(handle=core.BNNewViewReference(view))
else:
view_obj = None
if type:
type_obj = types.Type.create(handle=core.BNNewTypeReference(type))
return self.is_non_reg_arg_indirect(view_obj, type_obj)
else:
return self.is_non_reg_arg_indirect(view_obj, None)
except:
log_error_for_exception("Unhandled Python exception in CallingConvention._is_non_reg_arg_indirect")
return False
def _are_stack_args_naturally_aligned(self, ctxt):
try:
return self.__class__.stack_args_naturally_aligned
except:
log_error_for_exception("Unhandled Python exception in CallingConvention._are_stack_args_naturally_aligned")
return False
def _are_stack_args_pushed_left_to_right(self, ctxt):
try:
return self.__class__.stack_args_pushed_left_to_right
except:
log_error_for_exception("Unhandled Python exception in CallingConvention._are_stack_args_pushed_left_to_right")
return False
def _get_call_layout(
self, ctxt, view, ret_value, params, param_count, has_permitted_regs, permitted_regs,
permitted_reg_count, out_layout
):
try:
if view:
view_obj = binaryview.BinaryView(handle=core.BNNewViewReference(view))
else:
view_obj = None
if ret_value:
ret_value_obj = types.ReturnValue._from_core_struct(ret_value[0])
else:
ret_value_obj = None
param_objs = []
for i in range(param_count):
param_objs.append(types.FunctionParameter._from_core_struct(params[i]))
if has_permitted_regs:
reg_objs = []
for i in range(permitted_reg_count):
reg_objs.append(architecture.RegisterIndex(permitted_regs[i]))
else:
reg_objs = None
layout = self.get_call_layout(view_obj, ret_value_obj, param_objs, permitted_regs = reg_objs)
result = layout._to_core_struct()
param_ptr = ctypes.cast(result.parameters, ctypes.c_void_p)
self._pending_value_location_lists[param_ptr.value] = (param_ptr.value, result.parameters)
if result.returnValueValid:
ret_ptr = ctypes.cast(result.returnValue.components, ctypes.c_void_p)
self._pending_value_locations[ret_ptr.value] = (ret_ptr.value, result.returnValue)
reg_ptr = ctypes.cast(result.registerStackAdjustmentRegisters, ctypes.c_void_p)
self._pending_reg_stack_adjustment_reg_lists[reg_ptr.value] = (reg_ptr.value, result.registerStackAdjustmentRegisters)
amount_ptr = ctypes.cast(result.registerStackAdjustmentAmounts, ctypes.c_void_p)
self._pending_reg_stack_adjustment_amount_lists[amount_ptr.value] = (amount_ptr.value, result.registerStackAdjustmentAmounts)
out_layout[0] = result
except:
log_error_for_exception("Unhandled Python exception in CallingConvention._get_call_layout")
result = core.BNCallLayout()
result.parameterCount = 0
result.returnValueValid = False
result.stackAdjustment = 0
result.registerStackAdjustmentCount = 0
out_layout[0] = result
def _free_call_layout(self, ctxt, layout_ptr):
try:
layout = layout_ptr[0]
param_ptr = ctypes.cast(layout.parameters, ctypes.c_void_p)
if param_ptr.value is not None:
if param_ptr.value not in self._pending_value_location_lists:
raise ValueError("freeing parameter location list that wasn't allocated")
del self._pending_value_location_lists[param_ptr.value]
if layout.returnValueValid:
ret_ptr = ctypes.cast(layout.returnValue.components, ctypes.c_void_p)
if ret_ptr.value is not None:
if ret_ptr.value not in self._pending_value_locations:
raise ValueError("freeing return value location that wasn't allocated")
del self._pending_value_locations[ret_ptr.value]
reg_ptr = ctypes.cast(layout.registerStackAdjustmentRegisters, ctypes.c_void_p)
if reg_ptr.value is not None:
if reg_ptr.value not in self._pending_reg_stack_adjustment_reg_lists:
raise ValueError("freeing register list that wasn't allocated")
del self._pending_reg_stack_adjustment_reg_lists[reg_ptr.value]
amount_ptr = ctypes.cast(layout.registerStackAdjustmentAmounts, ctypes.c_void_p)
if amount_ptr.value is not None:
if amount_ptr.value not in self._pending_reg_stack_adjustment_amount_lists:
raise ValueError("freeing adjustment list that wasn't allocated")
del self._pending_reg_stack_adjustment_amount_lists[amount_ptr.value]
except:
log_error_for_exception("Unhandled Python exception in CallingConvention._free_call_layout")
def _get_return_value_location(self, ctxt, view, ret_value, out_location):
try:
if view:
view_obj = binaryview.BinaryView(handle=core.BNNewViewReference(view))
else:
view_obj = None
ret = types.ReturnValue._from_core_struct(ret_value[0])
location = self.get_return_value_location(view_obj, ret)
if location is None:
location = types.ValueLocation([])
result = location._to_core_struct()
result_ptr = ctypes.cast(result.components, ctypes.c_void_p)
self._pending_value_locations[result_ptr.value] = (result_ptr.value, result)
out_location[0] = result
except:
log_error_for_exception("Unhandled Python exception in CallingConvention._get_return_value_location")
result = core.BNValueLocation()
result.count = 0
result.components = None
out_location[0] = result
def _free_value_location(self, ctxt, location_ptr):
try:
location = location_ptr[0]
loc_ptr = ctypes.cast(location.components, ctypes.c_void_p)
if loc_ptr.value is not None:
if loc_ptr.value not in self._pending_value_locations:
raise ValueError("freeing value location that wasn't allocated")
del self._pending_value_locations[loc_ptr.value]
except:
log_error_for_exception("Unhandled Python exception in CallingConvention._free_value_location")
def _get_parameter_locations(
self, ctxt, view, ret_value, params, param_count, has_permitted_regs, permitted_regs, permitted_reg_count,
out_location_count
):
try:
if view:
view_obj = binaryview.BinaryView(handle=core.BNNewViewReference(view))
else:
view_obj = None
if ret_value:
ret_value_obj = types.ValueLocation._from_core_struct(ret_value[0])
else:
ret_value_obj = None
param_objs = []
for i in range(param_count):
param_objs.append(types.FunctionParameter._from_core_struct(params[i]))
if has_permitted_regs:
reg_objs = []
for i in range(permitted_reg_count):
reg_objs.append(architecture.RegisterIndex(permitted_regs[i]))
else:
reg_objs = None
locations = self.get_parameter_locations(view_obj, ret_value_obj, param_objs, permitted_regs = reg_objs)
out_location_count[0] = len(locations)
result = (core.BNValueLocation * len(locations))()
for i, location in enumerate(locations):
result[i] = location._to_core_struct()
result_ptr = ctypes.cast(result, ctypes.c_void_p)
self._pending_value_location_lists[result_ptr.value] = (result_ptr.value, result)
return result_ptr.value
except:
log_error_for_exception("Unhandled Python exception in CallingConvention._get_parameter_locations")
out_location_count[0] = 0
return None
def _free_parameter_locations(self, ctxt, locations, count):
try:
location_ptr = ctypes.cast(locations, ctypes.c_void_p)
if location_ptr.value is not None:
if location_ptr.value not in self._pending_value_location_lists:
raise ValueError("freeing parameter location list that wasn't allocated")
del self._pending_value_location_lists[location_ptr.value]
except:
log_error_for_exception("Unhandled Python exception in CallingConvention._free_parameter_locations")
def _get_parameter_ordering_for_variables(self, ctxt, view, vars, type_list, param_count, out_count):
try:
if view:
view_obj = binaryview.BinaryView(handle=core.BNNewViewReference(view))
else:
view_obj = None
params = {}
for i in range(param_count):
var = variable.CoreVariable.from_BNVariable(vars[i])
ty = types.Type.from_core_struct(type_list[i])
params[var] = ty
var_list = self.get_parameter_ordering_for_variables(view_obj, params)
out_count[0] = len(var_list)
result = (core.BNVariable * len(var_list))()
for i, var in enumerate(var_list):
result[i] = var.to_BNVariable()
result_ptr = ctypes.cast(result, ctypes.c_void_p)
self._pending_variable_lists[result_ptr.value] = (result_ptr.value, result)
return result_ptr.value
except:
log_error_for_exception(
"Unhandled Python exception in CallingConvention._get_parameter_ordering_for_variables")
out_count[0] = 0
return None
def _free_variable_list(self, ctxt, vars, count):
try:
var_list_ptr = ctypes.cast(vars, ctypes.c_void_p)
if var_list_ptr.value is not None:
if var_list_ptr.value not in self._pending_variable_lists:
raise ValueError("freeing variable list that wasn't allocated")
del self._pending_variable_lists[var_list_ptr.value]
except:
log_error_for_exception("Unhandled Python exception in CallingConvention._free_variable_list")
def _get_stack_adjustment_for_locations(self, ctxt, view, ret_value, locations, type_list, param_count):
try:
if view:
view_obj = binaryview.BinaryView(handle=core.BNNewViewReference(view))
else:
view_obj = None
if ret_value:
ret_value_obj = types.ValueLocation._from_core_struct(ret_value[0])
else:
ret_value_obj = None
params = []
for i in range(param_count):
loc = types.ValueLocation._from_core_struct(locations[i])
ty = types.Type.from_core_struct(type_list[i])
params.append((loc, ty))
return self.get_stack_adjustment_for_locations(view_obj, ret_value_obj, params)
except:
log_error_for_exception("Unhandled Python exception in CallingConvention._get_stack_adjustment_for_locations")
return 0
def _get_register_stack_adjustments(self, ctxt, view, ret_value, params, param_count, out_regs, out_adjust):
try:
if view:
view_obj = binaryview.BinaryView(handle=core.BNNewViewReference(view))
else:
view_obj = None
if ret_value:
ret_value_obj = types.ValueLocation._from_core_struct(ret_value[0])
else:
ret_value_obj = None
param_objs = []
for i in range(param_count):
param_objs.append(types.ValueLocation._from_core_struct(params[i]))
adjustment = self.get_register_stack_adjustments(view_obj, ret_value_obj, param_objs)
regs = (ctypes.c_uint * len(adjustment))()
adjust = (ctypes.c_int * len(adjustment))()
for i, (reg, adj) in enumerate(adjustment.items()):
regs[i] = int(reg)
adjust[i] = adj
reg_ptr = ctypes.cast(regs, ctypes.c_void_p)
self._pending_reg_stack_adjustment_reg_lists[reg_ptr.value] = (reg_ptr.value, regs)
adjust_ptr = ctypes.cast(adjust, ctypes.c_void_p)
self._pending_reg_stack_adjustment_amount_lists[adjust_ptr.value] = (adjust_ptr.value, adjust)
out_regs[0] = regs
out_adjust[0] = adjust
return len(adjustment)
except:
log_error_for_exception("Unhandled Python exception in CallingConvention._get_register_stack_adjustments")
out_regs[0] = None
out_adjust[0] = None
return 0
def _free_register_stack_adjustments(self, ctxt, regs, adjust, count):
try:
reg_ptr = ctypes.cast(regs, ctypes.c_void_p)
if reg_ptr.value is not None:
if reg_ptr.value not in self._pending_reg_stack_adjustment_reg_lists:
raise ValueError("freeing register list that wasn't allocated")
del self._pending_reg_stack_adjustment_reg_lists[reg_ptr.value]
adjust_ptr = ctypes.cast(adjust, ctypes.c_void_p)
if adjust_ptr.value is not None:
if adjust_ptr.value not in self._pending_reg_stack_adjustment_amount_lists:
raise ValueError("freeing adjustment list that wasn't allocated")
del self._pending_reg_stack_adjustment_amount_lists[adjust_ptr.value]
except:
log_error_for_exception("Unhandled Python exception in CallingConvention._free_register_stack_adjustments")
[docs]
def get_incoming_reg_value(
self, reg: 'architecture.RegisterName', func: 'function.Function'
) -> 'variable.RegisterValue':
"""
``get_incoming_reg_value`` gets the known value of a register on entry to a function.
:param RegisterName reg: register to query
:param Function func: function being analyzed
:return: the incoming value of the register
:rtype: RegisterValue
"""
return self.perform_get_incoming_reg_value(reg, func)
[docs]
def get_incoming_flag_value(
self, reg: 'architecture.RegisterName', func: 'function.Function'
) -> 'variable.RegisterValue':
"""
``get_incoming_flag_value`` gets the known value of a flag on entry to a function.
:param reg: flag to query
:param Function func: function being analyzed
:return: the incoming value of the flag
:rtype: RegisterValue
"""
return self.perform_get_incoming_flag_value(reg, func)
[docs]
def get_incoming_var_for_parameter_var(
self, in_var: 'variable.CoreVariable', func: Optional['function.Function'] = None
) -> 'variable.CoreVariable':
"""
``get_incoming_var_for_parameter_var`` gets the incoming variable that corresponds to the
given parameter variable. This is the inverse of :py:meth:`get_parameter_var_for_incoming_var`.
:param CoreVariable in_var: parameter variable
:param Function func: function being analyzed
:return: the incoming variable corresponding to the parameter variable
:rtype: CoreVariable
"""
return self.perform_get_incoming_var_for_parameter_var(in_var, func)
[docs]
def get_parameter_var_for_incoming_var(
self, in_var: 'variable.CoreVariable', func: Optional['function.Function'] = None
) -> 'variable.CoreVariable':
"""
``get_parameter_var_for_incoming_var`` gets the parameter variable that corresponds to the
given incoming variable. This is the inverse of :py:meth:`get_incoming_var_for_parameter_var`.
:param CoreVariable in_var: incoming variable
:param Function func: function being analyzed
:return: the parameter variable corresponding to the incoming variable
:rtype: CoreVariable
"""
return self.perform_get_incoming_var_for_parameter_var(in_var, func)
[docs]
def is_return_type_reg_compatible(self, view: Optional['binaryview.BinaryView'], type: 'types.Type') -> bool:
"""
``is_return_type_reg_compatible`` determines whether a value of the given type can be
returned in registers, as opposed to being returned indirectly through memory.
:param BinaryView view: binary view providing type information
:param Type type: return type to check
:return: whether the return type is register compatible
:rtype: bool
"""
return self.default_is_return_type_reg_compatible(type)
[docs]
def is_non_reg_arg_indirect(self, view: Optional['binaryview.BinaryView'], type: Optional['types.Type']) -> bool:
"""
``is_non_reg_arg_indirect`` determines whether an argument that cannot be passed in
registers is passed indirectly by pointer as opposed to being passed directly on the stack.
:param BinaryView view: binary view providing type information
:param Type type: argument type to check
:return: whether the non-register argument is passed indirectly by pointer
:rtype: bool
"""
return False
[docs]
def default_is_return_type_reg_compatible(self, type: 'types.Type') -> bool:
"""
``default_is_return_type_reg_compatible`` is the default implementation of
:py:meth:`is_return_type_reg_compatible`. The default implementation allows register
returns for types that fit in a single register, have a size equal to two registers when
``high_int_return_reg`` is set, or are a floating point type when ``float_return_reg`` is set.
:param Type type: return type to check
:return: whether the return type is register compatible
:rtype: bool
"""
return core.BNDefaultIsReturnTypeRegisterCompatible(self.handle, type.handle)
[docs]
def get_indirect_return_value_location(self) -> 'variable.CoreVariable':
"""
``get_indirect_return_value_location`` gets the location used to pass the hidden pointer
argument for return values that are returned indirectly through memory.
:return: the location of the indirect return value pointer
:rtype: CoreVariable
"""
return self.get_default_indirect_return_value_location()
[docs]
def get_default_indirect_return_value_location(self) -> 'variable.CoreVariable':
"""
``get_default_indirect_return_value_location`` is the default implementation of
:py:meth:`get_indirect_return_value_location`. The default location is the first integer
argument register, or the first stack slot if there are no integer argument registers.
:return: the location of the indirect return value pointer
:rtype: CoreVariable
"""
result = core.BNGetDefaultIndirectReturnValueLocation(self.handle)
return variable.CoreVariable.from_BNVariable(result)
[docs]
def get_returned_indirect_return_value_pointer(self) -> Optional['variable.CoreVariable']:
"""
``get_returned_indirect_return_value_pointer`` gets the location in which the hidden
indirect return value pointer is returned to the caller, for calling conventions that
return it.
:return: the location the indirect return value pointer is returned in, or ``None`` if it is not returned
:rtype: Optional[CoreVariable]
"""
return None
[docs]
def is_arg_type_reg_compatible(self, view: Optional['binaryview.BinaryView'], type: 'types.Type') -> bool:
"""
``is_arg_type_reg_compatible`` determines whether a value of the given type can be passed
as an argument in registers.
:param BinaryView view: binary view providing type information
:param Type type: argument type to check
:return: whether the argument type is register compatible
:rtype: bool
"""
return self.default_is_arg_type_reg_compatible(type)
[docs]
def default_is_arg_type_reg_compatible(self, type: 'types.Type') -> bool:
"""
``default_is_arg_type_reg_compatible`` is the default implementation of
:py:meth:`is_arg_type_reg_compatible`. The default implementation allows register arguments
for types that fit in a single register, or are a floating point type when ``float_arg_regs``
has valid registers.
:param Type type: argument type to check
:return: whether the argument type is register compatible
:rtype: bool
"""
return core.BNDefaultIsArgumentTypeRegisterCompatible(self.handle, type.handle)
[docs]
def get_call_layout(
self, view: Optional['binaryview.BinaryView'], return_value: Optional['types.ReturnValueOrType'],
params: 'types.ParamsType', func: Optional['function.Function'] = None,
permitted_regs: Optional[List['architecture.RegisterIndex']] = None
) -> 'CallLayout':
"""
``get_call_layout`` computes the complete call layout (parameter locations, return value
location, and stack adjustments) for a call with the given return value and parameters. It
is recommended to only override this method if the calling convention behavior cannot be
modeled with :py:meth:`get_return_value_location` and/or :py:meth:`get_parameter_locations`.
The default implementation calls :py:meth:`get_default_call_layout`.
When calling this method to query the layout of a function, the return value and parameters
should have their named type references dereferenced before passing them to this method.
Calling the methods :py:meth:`BinaryView.deref_return_value_named_type_references` and
:py:meth:`BinaryView.deref_parameter_named_type_references` will perform this dereferencing.
:param BinaryView view: binary view providing type information
:param return_value: return value of the call
:param params: parameters of the call
:param Function func: function used to resolve the architecture of the resulting locations
:param permitted_regs: optional set of register indices that argument passing is restricted \
to; if not provided, the calling convention's default registers are used
:return: the computed call layout
:rtype: CallLayout
"""
return self.get_default_call_layout(view, return_value, params, func, permitted_regs)
[docs]
def get_default_call_layout(
self, view: Optional['binaryview.BinaryView'], return_value: Optional['types.ReturnValueOrType'],
params: 'types.ParamsType', func: Optional['function.Function'] = None,
permitted_regs: Optional[List['architecture.RegisterIndex']] = None
) -> 'CallLayout':
"""
``get_default_call_layout`` is the default implementation of :py:meth:`get_call_layout`. The
default implementation uses :py:meth:`get_return_value_location`,
:py:meth:`get_parameter_locations`, :py:meth:`get_stack_adjustment_for_locations`, and
:py:meth:`get_register_stack_adjustments` to compute the layout.
:param BinaryView view: binary view providing type information
:param return_value: return value of the call
:param params: parameters of the call
:param Function func: function used to resolve the architecture of the resulting locations
:param permitted_regs: optional set of register indices that argument passing is restricted \
to; if not provided, the calling convention's default registers are used
:return: the computed call layout
:rtype: CallLayout
"""
if view is None:
view_obj = None
else:
view_obj = view.handle
if return_value is None:
ret = types.ReturnValue(types.Type.void())._to_core_struct()
elif isinstance(return_value, types.ReturnValue):
ret = return_value._to_core_struct()
else:
ret = types.ReturnValue(return_value)._to_core_struct()
param_structs, type_list = types.FunctionBuilder._to_core_struct(params)
if permitted_regs is None:
layout = core.BNGetDefaultCallLayoutDefaultPermittedArgs(self.handle, view_obj, ret, param_structs, len(params))
else:
regs = (ctypes.c_uint * len(permitted_regs))()
for i in range(len(permitted_regs)):
regs[i] = int(permitted_regs[i])
layout = core.BNGetDefaultCallLayout(self.handle, view_obj, ret, param_structs, len(params), regs,
len(permitted_regs))
result = CallLayout._from_core_struct(layout, func)
core.BNFreeCallLayout(layout)
return result
[docs]
def get_return_value_location(
self, view: Optional['binaryview.BinaryView'], return_value: 'types.ReturnValueOrType'
) -> Optional['types.ValueLocation']:
"""
``get_return_value_location`` computes the location of the return value for the given return
value type and location structure.
The default implementation calls :py:meth:`get_default_return_value_location`.
:param BinaryView view: binary view providing type information
:param return_value: return value to compute the location for
:return: the location of the return value
:rtype: Optional[ValueLocation]
"""
return self.get_default_return_value_location(view, return_value)
[docs]
def get_default_return_value_location(
self, view: Optional['binaryview.BinaryView'], return_value: 'types.ReturnValueOrType'
) -> Optional['types.ValueLocation']:
"""
``get_default_return_value_location`` is the default implementation of
:py:meth:`get_return_value_location`. The default implementation checks
:py:meth:`is_return_type_reg_compatible` and places the return value in registers if it
can, or uses an indirect return by pointer if not. If an indirect return is required, then
:py:meth:`get_indirect_return_value_location` and
:py:meth:`get_returned_indirect_return_value_pointer` are used to provide the location of
the indirect return value.
:param BinaryView view: binary view providing type information
:param return_value: return value to compute the location for
:return: the location of the return value
:rtype: Optional[ValueLocation]
"""
if view is None:
view_obj = None
else:
view_obj = view.handle
if return_value is None:
ret = types.ReturnValue(types.Type.void())._to_core_struct()
elif isinstance(return_value, types.ReturnValue):
ret = return_value._to_core_struct()
else:
ret = types.ReturnValue(return_value)._to_core_struct()
location = core.BNGetDefaultReturnValueLocation(self.handle, view_obj, ret)
if location.count == 0:
result = None
else:
result = types.ValueLocation._from_core_struct(location)
core.BNFreeValueLocation(location)
return result
[docs]
def get_parameter_locations(
self, view: Optional['binaryview.BinaryView'], return_value: Optional['types.ValueLocation'],
params: 'types.ParamsType', arch: Optional['architecture.Architecture'] = None,
permitted_regs: Optional[List['architecture.RegisterIndex']] = None
) -> List['types.ValueLocation']:
"""
``get_parameter_locations`` computes the locations of the parameters for a call with the
given return value and parameters.
The default implementation calls :py:meth:`get_default_parameter_locations`.
:param BinaryView view: binary view providing type information
:param return_value: optional location of the return value, which may affect parameter \
placement (for example, when an indirect return pointer consumes an argument register)
:param params: parameters of the call
:param Architecture arch: architecture used to resolve the resulting locations
:param permitted_regs: optional set of register indices that argument passing is restricted \
to; if not provided, the calling convention's default registers are used
:return: the locations of the parameters, in order
:rtype: List[ValueLocation]
"""
return self.get_default_parameter_locations(view, return_value, params, arch, permitted_regs)
[docs]
def get_default_parameter_locations(
self, view: Optional['binaryview.BinaryView'], return_value: Optional['types.ValueLocation'],
params: 'types.ParamsType', arch: Optional['architecture.Architecture'] = None,
permitted_regs: Optional[List['architecture.RegisterIndex']] = None
) -> List['types.ValueLocation']:
"""
``get_default_parameter_locations`` is the default implementation of
:py:meth:`get_parameter_locations`. The default implementation uses ``int_arg_regs``,
``float_arg_regs``, ``arg_regs_share_index``, ``stack_reserved_for_arg_regs``,
:py:meth:`is_arg_type_reg_compatible`, :py:meth:`is_non_reg_arg_indirect`,
``stack_args_naturally_aligned``, and ``stack_args_pushed_left_to_right`` to compute
the parameter layout.
This function is usually sufficient unless the calling convention has unusual parameter
passing behavior. Most calling conventions can be defined per-argument using the attributes
and methods listed above.
:param BinaryView view: binary view providing type information
:param return_value: optional location of the return value
:param params: parameters of the call
:param Architecture arch: architecture used to resolve the resulting locations
:param permitted_regs: optional set of register indices that argument passing is restricted \
to; if not provided, the calling convention's default registers are used
:return: the locations of the parameters, in order
:rtype: List[ValueLocation]
"""
if view is None:
view_obj = None
else:
view_obj = view.handle
if return_value is None:
ret = None
else:
ret = return_value._to_core_struct()
param_structs, type_list = types.FunctionBuilder._to_core_struct(params)
count = ctypes.c_ulonglong()
if permitted_regs is None:
locations = core.BNGetDefaultParameterLocationsDefaultPermittedArgs(self.handle, view_obj, ret, param_structs,
len(params), count)
else:
regs = (ctypes.c_uint * len(permitted_regs))()
for i in range(len(permitted_regs)):
regs[i] = int(permitted_regs[i])
locations = core.BNGetDefaultParameterLocations(self.handle, view_obj, ret, param_structs, len(params), regs,
len(permitted_regs), count)
result = []
for i in range(count.value):
result.append(types.ValueLocation._from_core_struct(locations[i], arch))
core.BNFreeValueLocationList(locations, count.value)
return result
[docs]
def get_parameter_ordering_for_variables(
self, view: Optional['binaryview.BinaryView'], params: Dict['variable.CoreVariable', 'types.Type']
) -> List['variable.CoreVariable']:
"""
``get_parameter_ordering_for_variables`` computes the order in which the given parameter
variables are passed. Used by the heuristic calling convention detection to create a
function type from a list of parameter variables.
The default implementation calls :py:meth:`get_default_parameter_ordering_for_variables`.
:param BinaryView view: binary view providing type information
:param params: map of parameter variables to their types
:return: the parameter variables in the order they are passed
:rtype: List[CoreVariable]
"""
return self.get_default_parameter_ordering_for_variables(params)
[docs]
def get_default_parameter_ordering_for_variables(self, params: Dict['variable.CoreVariable', 'types.Type']) -> List['variable.CoreVariable']:
"""
``get_default_parameter_ordering_for_variables`` is the default implementation of
:py:meth:`get_parameter_ordering_for_variables`. The default implementation first checks
``arg_regs_share_index`` to see if the parameter ordering is well defined. If the arguments
do not share an index, it places all integer arguments before the floating point arguments.
Arguments that are not passed in a normal location are placed last.
:param params: map of parameter variables to their types
:return: the parameter variables in the order they are passed
:rtype: List[CoreVariable]
"""
vars = (core.BNVariable * len(params))()
types = (ctypes.POINTER(core.BNType) * len(params))()
for (i, (var, ty)) in enumerate(params.items()):
vars[i] = var.to_BNVariable()
types[i] = ty.handle
i += 1
count = ctypes.c_ulonglong()
var_list = core.BNGetDefaultParameterOrderingForVariables(self.handle, vars, types, len(params), count)
result = []
for i in range(count.value):
result.append(variable.CoreVariable.from_BNVariable(var_list[i]))
core.BNFreeVariableList(var_list)
return result
[docs]
def get_stack_adjustment_for_locations(
self, view: Optional['binaryview.BinaryView'], return_value: Optional['types.ValueLocation'],
params: List[Tuple['types.ValueLocation', 'types.Type']]
):
"""
``get_stack_adjustment_for_locations`` computes the stack adjustment applied on return for
a call with the given return value and parameter locations.
The default implementation calls :py:meth:`get_default_stack_adjustment_for_locations`.
:param BinaryView view: binary view providing type information
:param return_value: optional location of the return value
:param params: list of ``(location, type)`` tuples for the parameters
:return: the stack adjustment in bytes
:rtype: int
"""
return self.get_default_stack_adjustment_for_locations(return_value, params)
[docs]
def get_default_stack_adjustment_for_locations(
self, return_value: Optional['types.ValueLocation'],
params: List[Tuple['types.ValueLocation', 'types.Type']]
):
"""
``get_default_stack_adjustment_for_locations`` is the default implementation of
:py:meth:`get_stack_adjustment_for_locations`. The default implementation first checks
``stack_adjusted_on_return``, and returns zero if that is ``False``. Otherwise, it checks
the stack parameter locations and ``stack_args_naturally_aligned`` to compute the stack
adjustment necessary to cover all parameters.
:param return_value: optional location of the return value
:param params: list of ``(location, type)`` tuples for the parameters
:return: the stack adjustment in bytes
:rtype: int
"""
if return_value is None:
ret = None
else:
ret = return_value._to_core_struct()
locations = (core.BNValueLocation * len(params))()
type_list = (ctypes.POINTER(core.BNType) * len(params))()
for i, (loc, ty) in enumerate(params):
locations[i] = loc._to_core_struct()
type_list[i] = ty.handle
return core.BNGetDefaultStackAdjustmentForLocations(self.handle, ret, locations, type_list, len(params))
[docs]
def get_register_stack_adjustments(
self, view: Optional['binaryview.BinaryView'], return_value: Optional['types.ValueLocation'],
params: List['types.ValueLocation']
) -> Dict['architecture.RegisterIndex', int]:
"""
``get_register_stack_adjustments`` computes the per-register-stack adjustments (for
architectures with register stacks, such as the x87 floating point stack) for a call with
the given return value and parameter locations.
The default implementation calls :py:meth:`get_default_register_stack_adjustments`.
:param BinaryView view: binary view providing type information
:param return_value: optional location of the return value
:param params: locations of the parameters
:return: a map from register stack index to its adjustment
:rtype: Dict[RegisterIndex, int]
"""
return self.get_default_register_stack_adjustments(return_value, params)
[docs]
def get_default_register_stack_adjustments(
self, return_value: Optional['types.ValueLocation'], params: List['types.ValueLocation']
) -> Dict['architecture.RegisterIndex', int]:
"""
``get_default_register_stack_adjustments`` is the default implementation of
:py:meth:`get_register_stack_adjustments`. The default implementation compares the register
stack slots used by the parameters and the return value to compute the adjustments.
:param return_value: optional location of the return value
:param params: locations of the parameters
:return: a map from register stack index to its adjustment
:rtype: Dict[RegisterIndex, int]
"""
if return_value is None:
ret = None
else:
ret = return_value._to_core_struct()
locations = (core.BNValueLocation * len(params))()
for i, loc in enumerate(params):
locations[i] = loc._to_core_struct()
out_regs = ctypes.POINTER(ctypes.c_uint)()
out_adjust = ctypes.POINTER(ctypes.c_int)()
count = core.BNGetCallingConventionDefaultRegisterStackAdjustments(self.handle, ret, locations, len(params),
out_regs, out_adjust)
result = {}
for i in range(count):
result[architecture.RegisterIndex(out_regs[i])] = out_adjust[i]
core.BNFreeCallingConventionRegisterStackAdjustments(out_regs, out_adjust)
return result
[docs]
def with_confidence(self, confidence: int) -> 'CallingConvention':
return CallingConvention(
self.arch, handle=core.BNNewCallingConventionReference(self.handle), confidence=confidence
)
@property
def arch(self) -> 'architecture.Architecture':
"""
The architecture this calling convention applies to.
:getter: returns the architecture this calling convention applies to
:setter: sets the architecture this calling convention applies to
:type: Architecture
"""
return self._arch
@arch.setter
def arch(self, value: 'architecture.Architecture') -> None:
self._arch = value
[docs]
class CoreCallingConvention(CallingConvention):
[docs]
def __init__(self, handle, confidence: int = core.max_confidence):
super().__init__(handle=handle, confidence=confidence)
self.arch = architecture.CoreArchitecture._from_cache(core.BNGetCallingConventionArchitecture(handle))
self.__dict__["name"] = core.BNGetCallingConventionName(handle)
self.__dict__["arg_regs_share_index"] = core.BNAreArgumentRegistersSharedIndex(handle)
self.__dict__["arg_regs_for_varargs"] = core.BNAreArgumentRegistersUsedForVarArgs(handle)
self.__dict__["stack_reserved_for_arg_regs"] = core.BNIsStackReservedForArgumentRegisters(handle)
self.__dict__["stack_adjusted_on_return"] = core.BNIsStackAdjustedOnReturn(handle)
self.__dict__["eligible_for_heuristics"] = core.BNIsEligibleForHeuristics(handle)
self.__dict__["stack_args_naturally_aligned"] = core.BNAreStackArgumentsNaturallyAligned(handle)
self.__dict__["stack_args_pushed_left_to_right"] = core.BNAreStackArgumentsPushedLeftToRight(handle)
count = ctypes.c_ulonglong()
regs = core.BNGetCallerSavedRegisters(handle, count)
assert regs is not None, "core.BNGetCallerSavedRegisters returned None"
result = []
arch = self.arch
for i in range(0, count.value):
result.append(arch.get_reg_name(regs[i]))
core.BNFreeRegisterList(regs)
self.__dict__["caller_saved_regs"] = result
count = ctypes.c_ulonglong()
regs = core.BNGetCalleeSavedRegisters(handle, count)
assert regs is not None, "core.BNGetCalleeSavedRegisters returned None"
result = []
arch = self.arch
for i in range(0, count.value):
result.append(arch.get_reg_name(regs[i]))
core.BNFreeRegisterList(regs)
self.__dict__["callee_saved_regs"] = result
count = ctypes.c_ulonglong()
regs = core.BNGetIntegerArgumentRegisters(handle, count)
assert regs is not None, "core.BNGetIntegerArgumentRegisters returned None"
result = []
arch = self.arch
for i in range(0, count.value):
result.append(arch.get_reg_name(regs[i]))
core.BNFreeRegisterList(regs)
self.__dict__["int_arg_regs"] = result
count = ctypes.c_ulonglong()
regs = core.BNGetFloatArgumentRegisters(handle, count)
assert regs is not None, "core.BNGetFloatArgumentRegisters returned None"
result = []
arch = self.arch
for i in range(0, count.value):
result.append(arch.get_reg_name(regs[i]))
core.BNFreeRegisterList(regs)
self.__dict__["float_arg_regs"] = result
count = ctypes.c_ulonglong()
regs = core.BNGetRequiredArgumentRegisters(handle, count)
assert regs is not None, "core.BNGetRequiredArgumentRegisters returned None"
result = []
arch = self.arch
for i in range(0, count.value):
result.append(arch.get_reg_name(regs[i]))
core.BNFreeRegisterList(regs)
self.__dict__["required_arg_regs"] = result
count = ctypes.c_ulonglong()
regs = core.BNGetRequiredClobberedRegisters(handle, count)
assert regs is not None, "core.BNGetRequiredClobberedRegisters returned None"
result = []
arch = self.arch
for i in range(0, count.value):
result.append(arch.get_reg_name(regs[i]))
core.BNFreeRegisterList(regs)
self.__dict__["required_clobbered_regs"] = result
reg = core.BNGetIntegerReturnValueRegister(handle)
if reg == 0xffffffff:
self.__dict__["int_return_reg"] = None
else:
self.__dict__["int_return_reg"] = self.arch.get_reg_name(reg)
reg = core.BNGetHighIntegerReturnValueRegister(handle)
if reg == 0xffffffff:
self.__dict__["high_int_return_reg"] = None
else:
self.__dict__["high_int_return_reg"] = self.arch.get_reg_name(reg)
reg = core.BNGetFloatReturnValueRegister(handle)
if reg == 0xffffffff:
self.__dict__["float_return_reg"] = None
else:
self.__dict__["float_return_reg"] = self.arch.get_reg_name(reg)
reg = core.BNGetGlobalPointerRegister(handle)
if reg == 0xffffffff:
self.__dict__["global_pointer_reg"] = None
else:
self.__dict__["global_pointer_reg"] = self.arch.get_reg_name(reg)
count = ctypes.c_ulonglong()
regs = core.BNGetImplicitlyDefinedRegisters(handle, count)
assert regs is not None, "core.BNGetImplicitlyDefinedRegisters returned None"
result = []
arch = self.arch
for i in range(0, count.value):
result.append(arch.get_reg_name(regs[i]))
core.BNFreeRegisterList(regs)
self.__dict__["implicitly_defined_regs"] = result
[docs]
def get_incoming_reg_value(
self, reg: 'architecture.RegisterType', func: 'function.Function'
) -> 'variable.RegisterValue':
reg_num = self.arch.get_reg_index(reg)
func_handle = None
if func is not None:
func_handle = func.handle
return variable.RegisterValue.from_BNRegisterValue(
core.BNGetIncomingRegisterValue(self.handle, reg_num, func_handle), self.arch
)
[docs]
def get_incoming_flag_value(
self, flag: 'architecture.FlagType', func: 'function.Function'
) -> 'variable.RegisterValue':
flag_index = self.arch.get_flag_index(flag)
func_handle = None
if func is not None:
func_handle = func.handle
return variable.RegisterValue.from_BNRegisterValue(
core.BNGetIncomingFlagValue(self.handle, flag_index, func_handle), self.arch
)
[docs]
def get_incoming_var_for_parameter_var(
self, in_var: 'variable.CoreVariable', func: FunctionOrILFunction = None
) -> 'variable.Variable':
in_buf = in_var.to_BNVariable()
if func is None:
func_obj = None
else:
func_obj = func.handle
out_var = core.BNGetIncomingVariableForParameterVariable(self.handle, in_buf, func_obj)
return variable.Variable.from_BNVariable(func, out_var)
[docs]
def get_parameter_var_for_incoming_var(
self, in_var: 'variable.CoreVariable', func: FunctionOrILFunction = None
) -> 'variable.Variable':
in_buf = in_var.to_BNVariable()
if func is None:
func_obj = None
else:
func_obj = func.handle
out_var = core.BNGetParameterVariableForIncomingVariable(self.handle, in_buf, func_obj)
return variable.Variable.from_BNVariable(func, out_var)
[docs]
def is_return_type_reg_compatible(self, view: Optional['binaryview.BinaryView'], type: 'types.Type') -> bool:
if view is None:
view_obj = None
else:
view_obj = view.handle
return core.BNIsReturnTypeRegisterCompatible(self.handle, view_obj, type.handle)
[docs]
def get_indirect_return_value_location(self) -> 'variable.CoreVariable':
result = core.BNGetIndirectReturnValueLocation(self.handle)
return variable.CoreVariable.from_BNVariable(result)
[docs]
def get_returned_indirect_return_value_pointer(self) -> Optional['variable.CoreVariable']:
var = core.BNVariable()
if core.BNGetReturnedIndirectReturnValuePointer(self.handle, var):
return variable.CoreVariable.from_BNVariable(var)
return None
[docs]
def is_arg_type_reg_compatible(self, view: Optional['binaryview.BinaryView'], type: 'types.Type') -> bool:
if view is None:
view_obj = None
else:
view_obj = view.handle
return core.BNIsArgumentTypeRegisterCompatible(self.handle, view_obj, type.handle)
[docs]
def is_non_reg_arg_indirect(self, view: Optional['binaryview.BinaryView'], type: Optional['types.Type']) -> bool:
if view is None:
view_obj = None
else:
view_obj = view.handle
if type is None:
return core.BNIsNonRegisterArgumentIndirect(self.handle, view_obj, None)
else:
return core.BNIsNonRegisterArgumentIndirect(self.handle, view_obj, type.handle)
[docs]
def get_call_layout(
self, view: Optional['binaryview.BinaryView'], return_value: Optional['types.ReturnValueOrType'],
params: 'types.ParamsType', func: Optional['function.Function'] = None,
permitted_regs: Optional[List['architecture.RegisterIndex']] = None
) -> 'CallLayout':
if view is None:
view_obj = None
else:
view_obj = view.handle
if return_value is None:
ret = types.ReturnValue(types.Type.void())._to_core_struct()
elif isinstance(return_value, types.ReturnValue):
ret = return_value._to_core_struct()
else:
ret = types.ReturnValue(return_value)._to_core_struct()
param_structs, type_list = types.FunctionBuilder._to_core_struct(params)
if permitted_regs is None:
layout = core.BNGetCallLayoutDefaultPermittedArgs(self.handle, view_obj, ret, param_structs, len(params))
else:
regs = (ctypes.c_uint * len(permitted_regs))()
for i in range(len(permitted_regs)):
regs[i] = int(permitted_regs[i])
layout = core.BNGetCallLayout(self.handle, view_obj, ret, param_structs, len(params), regs, len(permitted_regs))
result = CallLayout._from_core_struct(layout, func)
core.BNFreeCallLayout(layout)
return result
[docs]
def get_return_value_location(
self, view: Optional['binaryview.BinaryView'], return_value: 'types.ReturnValueOrType'
) -> Optional['types.ValueLocation']:
if view is None:
view_obj = None
else:
view_obj = view.handle
if return_value is None:
ret = types.ReturnValue(types.Type.void())._to_core_struct()
elif isinstance(return_value, types.ReturnValue):
ret = return_value._to_core_struct()
else:
ret = types.ReturnValue(return_value)._to_core_struct()
location = core.BNGetReturnValueLocation(self.handle, view_obj, ret)
if location.count == 0:
result = None
else:
result = types.ValueLocation._from_core_struct(location)
core.BNFreeValueLocation(location)
return result
[docs]
def get_parameter_locations(
self, view: Optional['binaryview.BinaryView'], return_value: Optional['types.ValueLocation'],
params: 'types.ParamsType', arch: Optional['architecture.Architecture'] = None,
permitted_regs: Optional[List['architecture.RegisterIndex']] = None
) -> List['types.ValueLocation']:
if view is None:
view_obj = None
else:
view_obj = view.handle
if return_value is None:
ret = None
else:
ret = return_value._to_core_struct()
param_structs, type_list = types.FunctionBuilder._to_core_struct(params)
count = ctypes.c_ulonglong()
if permitted_regs is None:
locations = core.BNGetParameterLocationsDefaultPermittedArgs(self.handle, view_obj, ret, param_structs,
len(params), count)
else:
regs = (ctypes.c_uint * len(permitted_regs))()
for i in range(len(permitted_regs)):
regs[i] = int(permitted_regs[i])
locations = core.BNGetParameterLocations(self.handle, view_obj, ret, param_structs, len(params), regs,
len(permitted_regs), count)
result = []
for i in range(count.value):
result.append(types.ValueLocation._from_core_struct(locations[i], arch))
core.BNFreeValueLocationList(locations, count.value)
return result
[docs]
def get_parameter_ordering_for_variables(
self, view: Optional['binaryview.BinaryView'], params: Dict['variable.CoreVariable', 'types.Type']
) -> List['variable.CoreVariable']:
if view is None:
view_obj = None
else:
view_obj = view.handle
vars = (core.BNVariable * len(params))()
types = (ctypes.POINTER(core.BNType) * len(params))()
for (i, (var, ty)) in enumerate(params.items()):
vars[i] = var.to_BNVariable()
types[i] = ty.handle
i += 1
count = ctypes.c_ulonglong()
var_list = core.BNGetParameterOrderingForVariables(self.handle, view_obj, vars, types, len(params), count)
result = []
for i in range(count.value):
result.append(variable.CoreVariable.from_BNVariable(var_list[i]))
core.BNFreeVariableList(var_list)
return result
[docs]
def get_stack_adjustment_for_locations(
self, view: Optional['binaryview.BinaryView'], return_value: Optional['types.ValueLocation'],
params: List[Tuple['types.ValueLocation', 'types.Type']]
):
if view is None:
view_obj = None
else:
view_obj = view.handle
if return_value is None:
ret = None
else:
ret = return_value._to_core_struct()
locations = (core.BNValueLocation * len(params))()
type_list = (ctypes.POINTER(core.BNType) * len(params))()
for i, (loc, ty) in enumerate(params):
locations[i] = loc._to_core_struct()
type_list[i] = ty.handle
return core.BNGetStackAdjustmentForLocations(self.handle, view_obj, ret, locations, type_list, len(params))
[docs]
def get_register_stack_adjustments(
self, view: Optional['binaryview.BinaryView'], return_value: Optional['types.ValueLocation'],
params: List['types.ValueLocation']
) -> Dict['architecture.RegisterIndex', int]:
if view is None:
view_obj = None
else:
view_obj = view.handle
if return_value is None:
ret = None
else:
ret = return_value._to_core_struct()
locations = (core.BNValueLocation * len(params))()
for i, loc in enumerate(params):
locations[i] = loc._to_core_struct()
out_regs = ctypes.POINTER(ctypes.c_uint)()
out_adjust = ctypes.POINTER(ctypes.c_int)()
count = core.BNGetCallingConventionRegisterStackAdjustments(self.handle, view_obj, ret, locations, len(params),
out_regs, out_adjust)
result = {}
for i in range(count):
result[architecture.RegisterIndex(out_regs[i])] = out_adjust[i]
core.BNFreeCallingConventionRegisterStackAdjustments(out_regs, out_adjust)
return result