# Buggy code
def _process_irrational_function_starts(
    self, functions, predetermined_function_addrs, blockaddr_to_function
):
    """
    Functions that are identified via function prologues can be starting after the actual beginning of the function.
    For example, the following function (with an incorrect start) might exist after a CFG recovery:

    sub_8049f70:
      push    esi

    sub_8049f71:
      sub     esp, 0A8h
      mov     esi, [esp+0ACh+arg_0]
      mov     [esp+0ACh+var_88], 0

    If the following conditions are met, we will remove the second function and merge it into the first function:
    - The second function is not called by other code.
    - The first function has only one jumpout site, which points to the second function.
    - The first function and the second function are adjacent.

    :param FunctionManager functions:   All functions that angr recovers.
    :return:                            A set of addresses of all removed functions.
    :rtype:                             set
    """

    addrs = sorted(functions.keys())
    functions_to_remove = set()
    adjusted_cfgnodes = set()

    for addr_0, addr_1 in zip(addrs[:-1], addrs[1:]):
        if addr_1 in predetermined_function_addrs:
            continue
        if self.project.is_hooked(addr_0) or self.project.is_hooked(addr_1):
            continue

        func_0 = functions[addr_0]

        if len(func_0.block_addrs) == 1:
            block = next(func_0.blocks)
            if block.vex.jumpkind not in ("Ijk_Boring", "Ijk_InvalICache"):
                continue
            # Skip alignment blocks
            if self._is_noop_block(self.project.arch, block):
                continue

            target = block.vex.next
            if type(target) is pyvex.IRExpr.Const:  # pylint: disable=unidiomatic-typecheck
                target_addr = target.con.value
            elif type(target) in (pyvex.IRConst.U32, pyvex.IRConst.U64):  # pylint: disable=unidiomatic-typecheck
                target_addr = target.value
            elif type(target) is int:  # pylint: disable=unidiomatic-typecheck
                target_addr = target
            else:
                continue

            if target_addr != addr_1:
                continue

            cfgnode_0 = self.get_any_node(addr_0)
            cfgnode_1 = self.get_any_node(addr_1)

            # Are func_0 adjacent to func_1?
            if cfgnode_0.addr + cfgnode_0.size != addr_1:
                continue

            # Merge block addr_0 and block addr_1
            l.debug("Merging function %#x into %#x.", addr_1, addr_0)
            self._merge_cfgnodes(cfgnode_0, cfgnode_1)
            adjusted_cfgnodes.add(cfgnode_0)
            adjusted_cfgnodes.add(cfgnode_1)

            # Merge it
            func_1 = functions[addr_1]
            for block_addr in func_1.block_addrs:
                if block_addr == addr_1:
                    # Skip addr_1 (since it has been merged to the preceding block)
                    continue
                merge_with = self._addr_to_function(
                    addr_0, blockaddr_to_function, functions
                )
                blockaddr_to_function[block_addr] = merge_with

            functions_to_remove.add(addr_1)

    for to_remove in functions_to_remove:
        del functions[to_remove]

    return functions_to_remove, adjusted_cfgnodes


# Traceback (angr.errors.SimEngineError)
Traceback (most recent call last):
File "cfg_fast.py", line 61, in <module>
analyze(sys.argv[1])
File "cfg_fast.py", line 30, in analyze
cfg = proj.analyses.CFGFast()
File "/home/wgh/angr-dev/angr/angr/analyses/analysis.py", line 108, in __call__
oself.__init__(*args, **kwargs)
File "/home/wgh/angr-dev/angr/angr/analyses/cfg/cfg_fast.py", line 1026, in __init__
self._analyze()
File "/home/wgh/angr-dev/angr/angr/analyses/forward_analysis.py", line 559, in _analyze
self._post_analysis()
File "/home/wgh/angr-dev/angr/angr/analyses/cfg/cfg_fast.py", line 1531, in _post_analysis
self.make_functions()
File "/home/wgh/angr-dev/angr/angr/analyses/cfg/cfg_base.py", line 1439, in make_functions
blockaddr_to_function
File "/home/wgh/angr-dev/angr/angr/analyses/cfg/cfg_base.py", line 1711, in _process_irrational_function_starts
if block.vex.jumpkind not in ('Ijk_Boring', 'Ijk_InvalICache'):
File "/home/wgh/angr-dev/angr/angr/block.py", line 152, in vex
collect_data_refs=self._collect_data_refs,
File "/home/wgh/angr-dev/angr/angr/engines/vex/engine.py", line 568, in lift
raise SimEngineError("No bytes in memory for block starting at %#x." % addr)
angr.errors.SimEngineError: No bytes in memory for block starting at 0x3000002.

# Fixed code
def _process_irrational_function_starts(
    self, functions, predetermined_function_addrs, blockaddr_to_function
):
    """
    Functions that are identified via function prologues can be starting after the actual beginning of the function.
    For example, the following function (with an incorrect start) might exist after a CFG recovery:

    sub_8049f70:
      push    esi

    sub_8049f71:
      sub     esp, 0A8h
      mov     esi, [esp+0ACh+arg_0]
      mov     [esp+0ACh+var_88], 0

    If the following conditions are met, we will remove the second function and merge it into the first function:
    - The second function is not called by other code.
    - The first function has only one jumpout site, which points to the second function.
    - The first function and the second function are adjacent.

    :param FunctionManager functions:   All functions that angr recovers.
    :return:                            A set of addresses of all removed functions.
    :rtype:                             set
    """

    addrs = sorted(
        k
        for k in functions.keys()
        if not self.project.is_hooked(k) and not self.project.simos.is_syscall_addr(k)
    )
    functions_to_remove = set()
    adjusted_cfgnodes = set()

    for addr_0, addr_1 in zip(addrs[:-1], addrs[1:]):
        if addr_1 in predetermined_function_addrs:
            continue

        func_0 = functions[addr_0]

        if len(func_0.block_addrs) == 1:
            block = next(func_0.blocks)
            if block.vex.jumpkind not in ("Ijk_Boring", "Ijk_InvalICache"):
                continue
            # Skip alignment blocks
            if self._is_noop_block(self.project.arch, block):
                continue

            target = block.vex.next
            if type(target) is pyvex.IRExpr.Const:  # pylint: disable=unidiomatic-typecheck
                target_addr = target.con.value
            elif type(target) in (pyvex.IRConst.U32, pyvex.IRConst.U64):  # pylint: disable=unidiomatic-typecheck
                target_addr = target.value
            elif type(target) is int:  # pylint: disable=unidiomatic-typecheck
                target_addr = target
            else:
                continue

            if target_addr != addr_1:
                continue

            cfgnode_0 = self.get_any_node(addr_0)
            cfgnode_1 = self.get_any_node(addr_1)

            # Are func_0 adjacent to func_1?
            if cfgnode_0.addr + cfgnode_0.size != addr_1:
                continue

            # Merge block addr_0 and block addr_1
            l.debug("Merging function %#x into %#x.", addr_1, addr_0)
            self._merge_cfgnodes(cfgnode_0, cfgnode_1)
            adjusted_cfgnodes.add(cfgnode_0)
            adjusted_cfgnodes.add(cfgnode_1)

            # Merge it
            func_1 = functions[addr_1]
            for block_addr in func_1.block_addrs:
                if block_addr == addr_1:
                    # Skip addr_1 (since it has been merged to the preceding block)
                    continue
                merge_with = self._addr_to_function(
                    addr_0, blockaddr_to_function, functions
                )
                blockaddr_to_function[block_addr] = merge_with

            functions_to_remove.add(addr_1)

    for to_remove in functions_to_remove:
        del functions[to_remove]

    return functions_to_remove, adjusted_cfgnodes