def emulated_function(*args, context: ProcessorContext = None):
            """
            Emulates a function and returns result in rax.

            :param *args: Arguments to pass into function before emulation.
                If enforce_args is not enabled, the number of arguments provided can be less or more
                than the number of arguments required by the function.
                Any arguments not provided will default to whatever is set in the context.
            :param context: CPU context to use. If not provided an empty context will be used.
                If you would like to examine the context after emulation, you must provide your own.

            :returns: Value or derefernced value in rax.

            :raises TypeError: If enforce_args is enabled and incorrect number of positional args have been provided.
            """
            if context and context.emulator != self:
                raise ValueError(
                    "Supplied context must be created from same emulator.")
            context = context or self.new_context()
            # (ip must be set in order to get correct function arguments in signature.)
            context.ip = func_obj.start_ea

            # Temporarily turn off branch tracking since it is unneeded and will just waste time.
            orig_branch_tracking = self.branch_tracking
            self.branch_tracking = False

            # Fill in context with argument values.
            func_sig = context.get_function_signature(func_obj.start_ea)
            if enforce_args and len(func_sig.args) != len(args):
                raise TypeError(
                    f"Function takes {len(func_sig.args)} positional arguments, but {len(args)} were given."
                )

            logger.debug(f'Emulating {func_sig.name}')
            for arg, func_arg in zip(args, func_sig.args):
                if isinstance(arg, int):
                    logger.debug(
                        f'Setting argument {func_arg.name} = {repr(arg)}')
                    func_arg.value = arg
                elif isinstance(arg, bytes):
                    ptr = context.mem_alloc(len(arg))
                    context.mem_write(ptr, arg)
                    logger.debug(
                        f'Setting argument {func_arg.name} = {hex(ptr)} ({repr(arg)})'
                    )
                    func_arg.value = ptr
                else:
                    raise TypeError(f'Invalid arg type {type(arg)}')

            context.execute(func_obj.start_ea,
                            end=func_obj.end_ea,
                            max_instructions=self.max_instructions)

            if return_type is not None or return_size is not None:
                result = context.read_data(context.ret,
                                           size=return_size,
                                           data_type=return_type)
            else:
                result = context.ret

            logger.debug(f'Returned: {repr(result)}')
            self.branch_tracking = orig_branch_tracking
            return result
    def _execute_to(self,
                    ea,
                    *,
                    context: ProcessorContext = None) -> ProcessorContext:
        """
        Creates a cpu_context (or emulates on top of the given one) for instructions up to, but not
        including, the given ea within the current function.
        This function is a hybrid approach to the non-loop following mode in which it will
        force the other branch to be taken if the branch it wants to take will not lead to the
        desired end address.

        This is an internal function used as a helper for iter_context_at() when following loops.

        :param int ea: ea of interest
        :param context: ProcessorContext to use during emulation, a new one will be created if not provided.

        :raises RuntimeError: If maximum number of instructions have been hit.
        """
        if not context:
            context = self.new_context()

        flowchart = Flowchart.from_cache(ea)
        func_obj = utils.Function(ea)

        start_block = flowchart.find_block(func_obj.start_ea)
        end_block = flowchart.find_block(ea)
        valid_blocks = end_block.ancestors()
        valid_blocks.add(end_block)
        count = self.max_instructions

        # Starting from start_block, we are going to emulate each instruction in each basic block
        # until we get to the end_block.
        # If execution tries to branch us into a block that can't lead us to the end_block,
        # we will force the branch to go in the other direction.
        current_block = start_block
        while current_block != end_block:
            # We can't use execute() with start and end here because the end_ea of a block
            # is not actually in the block.
            for _ea in current_block.heads():
                context.execute(_ea)
                count -= 1

            if count <= 0:
                raise RuntimeError("Hit maximum number of instructions.")

            # Get the successor block that execution branched to as well as
            # is a valid block that can reach the end block.
            # If no such block exists, just pick the first valid successor block.
            valid_successors = [
                bb for bb in current_block.succs() if bb in valid_blocks
            ]
            assert valid_successors, "Expected there to be at least 1 valid successor block."
            for successor in valid_successors:
                if context.ip == successor.start_ea:
                    break
            else:
                # If no valid successor, force branch.
                successor = valid_successors[0]
                context.ip = successor.start_ea
                context.prep_for_branch(successor.start_ea)

            current_block = successor

        # Emulate the instructions in the final block.
        context.execute(start=current_block.start_ea, end=ea)

        return context