diff options
| author | Rusty Wagner <rusty.wagner@gmail.com> | 2026-05-08 15:03:57 -0400 |
|---|---|---|
| committer | Rusty Wagner <rusty.wagner@gmail.com> | 2026-05-22 17:52:21 -0400 |
| commit | ede9c92ba6155be37571743df7172aec76dd3c4b (patch) | |
| tree | fc2d486ae476c25220d065bbf00afbad5961386b /python | |
| parent | 8f2e99d1b6e814fcf7b9142889b7f2dfab5dd94e (diff) | |
Update documentation for calling convention changes
Diffstat (limited to 'python')
| -rw-r--r-- | python/callingconvention.py | 325 |
1 files changed, 325 insertions, 0 deletions
diff --git a/python/callingconvention.py b/python/callingconvention.py index 14d9d0cc..a76f59f0 100644 --- a/python/callingconvention.py +++ b/python/callingconvention.py @@ -86,6 +86,51 @@ class CallLayout: 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 = [] @@ -867,46 +912,150 @@ class CallingConvention: 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) 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) 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) 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) 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) 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 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) 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() 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) 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 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) 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) def get_call_layout( @@ -914,6 +1063,28 @@ class CallingConvention: 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) def get_default_call_layout( @@ -921,6 +1092,21 @@ class CallingConvention: 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: @@ -947,11 +1133,36 @@ class CallingConvention: 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) 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: @@ -975,6 +1186,22 @@ class CallingConvention: 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) def get_default_parameter_locations( @@ -982,6 +1209,27 @@ class CallingConvention: 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: @@ -1010,9 +1258,32 @@ class CallingConvention: 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) 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()): @@ -1033,12 +1304,36 @@ class CallingConvention: 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) 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: @@ -1054,11 +1349,34 @@ class CallingConvention: 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) 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: @@ -1085,6 +1403,13 @@ class CallingConvention: @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 |
