def _generate_cfgnode(self, cfg_job, current_function_addr):
        """
        Generate a CFGNode that starts at `cfg_job.addr`.

        Since lifting machine code to IRSBs is slow, self._nodes is used as a cache of CFGNodes.

        If the current architecture is ARM, this method will try to lift the block in the mode specified by the address
        (determined by the parity of the address: even for ARM, odd for THUMB), and in case of decoding failures, try
        the other mode. If the basic block is successfully decoded in the other mode (different from the initial one),
         `addr` and `current_function_addr` are updated.

        :param CFGJob cfg_job: The CFGJob instance.
        :param int current_function_addr: Address of the current function.
        :return: A 4-tuple of (new address, new function address, CFGNode instance, IRSB object)
        :rtype: tuple
        """

        addr = cfg_job.addr

        try:

            if addr in self._nodes:
                cfg_node = self._nodes[addr]
                irsb = cfg_node.irsb

                if cfg_node.function_address != current_function_addr:
                    # the node has been assigned to another function before.
                    # we should update the function address.
                    current_function_addr = cfg_node.function_address

                return addr, current_function_addr, cfg_node, irsb

            is_x86_x64_arch = self.project.arch.name in ('X86', 'AMD64')

            if is_arm_arch(self.project.arch):
                real_addr = addr & (~1)
            else:
                real_addr = addr

            # if possible, check the distance between `addr` and the end of this section
            distance = VEX_IRSB_MAX_SIZE
            obj = self.project.loader.find_object_containing(addr, membership_check=False)
            if obj:
                # is there a section?
                has_executable_section = len([ sec for sec in obj.sections if sec.is_executable ]) > 0  # pylint:disable=len-as-condition
                section = self.project.loader.find_section_containing(addr)
                if has_executable_section and section is None:
                    # the basic block should not exist here...
                    return None, None, None, None
                if section is not None:
                    if not section.is_executable:
                        # the section is not executable...
                        return None, None, None, None
                    distance = section.vaddr + section.memsize - real_addr
                    distance = min(distance, VEX_IRSB_MAX_SIZE)
                # TODO: handle segment information as well

            # also check the distance between `addr` and the closest function.
            # we don't want to have a basic block that spans across function boundaries
            next_func = self.functions.ceiling_func(addr + 1)
            if next_func is not None:
                distance_to_func = (next_func.addr & (~1) if is_arm_arch(self.project.arch) else next_func.addr) - real_addr
                if distance_to_func != 0:
                    if distance is None:
                        distance = distance_to_func
                    else:
                        distance = min(distance, distance_to_func)

            # in the end, check the distance between `addr` and the closest occupied region in segment list
            next_noncode_addr = self._seg_list.next_pos_with_sort_not_in(addr, { "code" }, max_distance=distance)
            if next_noncode_addr is not None:
                distance_to_noncode_addr = next_noncode_addr - addr
                distance = min(distance, distance_to_noncode_addr)

            # Let's try to create the pyvex IRSB directly, since it's much faster
            nodecode = False
            irsb = None
            irsb_string = None
            try:
                lifted_block = self._lift(addr, size=distance, opt_level=self._iropt_level, collect_data_refs=True)
                irsb = lifted_block.vex_nostmt
                irsb_string = lifted_block.bytes[:irsb.size]
            except SimTranslationError:
                nodecode = True

            if (nodecode or irsb.size == 0 or irsb.jumpkind == 'Ijk_NoDecode') and \
                    is_arm_arch(self.project.arch) and \
                    self._arch_options.switch_mode_on_nodecode:
                # maybe the current mode is wrong?
                nodecode = False
                if addr % 2 == 0:
                    addr_0 = addr + 1
                else:
                    addr_0 = addr - 1

                if addr_0 in self._nodes:
                    # it has been analyzed before
                    cfg_node = self._nodes[addr_0]
                    irsb = cfg_node.irsb
                    return addr_0, cfg_node.function_address, cfg_node, irsb

                try:
                    lifted_block = self._lift(addr_0, size=distance, opt_level=self._iropt_level,
                                              collect_data_refs=True)
                    irsb = lifted_block.vex_nostmt
                    irsb_string = lifted_block.bytes[:irsb.size]
                except SimTranslationError:
                    nodecode = True

                if not (nodecode or irsb.size == 0 or irsb.jumpkind == 'Ijk_NoDecode'):
                    # it is decodeable
                    if current_function_addr == addr:
                        current_function_addr = addr_0
                    addr = addr_0

            if nodecode or irsb.size == 0 or irsb.jumpkind == 'Ijk_NoDecode':
                # decoding error
                # we still occupy that location since it cannot be decoded anyways
                if irsb is None:
                    irsb_size = 0
                else:
                    irsb_size = irsb.size
                # special handling for ud, ud1, and ud2 on x86 and x86-64
                if is_x86_x64_arch \
                        and len(irsb_string) >= 2 \
                        and irsb_string[-2:] in {
                            b'\x0f\xff',  # ud0
                            b'\x0f\xb9',  # ud1
                            b'\x0f\x0b',  # ud2
                        }:
                    # ud0, ud1, and ud2 are actually valid instructions.
                    valid_ins = True
                    nodecode_size = 2
                else:
                    valid_ins = False
                    nodecode_size = 1
                self._seg_list.occupy(addr, irsb_size, 'code')
                self._seg_list.occupy(addr + irsb_size, nodecode_size, 'nodecode')
                if not valid_ins:
                    l.error("Decoding error occurred at address %#x of function %#x.",
                            addr + irsb_size,
                            current_function_addr
                            )
                    return None, None, None, None

            is_thumb = False
            # Occupy the block in segment list
            if irsb.size > 0:
                if is_arm_arch(self.project.arch) and addr % 2 == 1:
                    # thumb mode
                    is_thumb=True
                self._seg_list.occupy(real_addr, irsb.size, "code")

            # Create a CFG node, and add it to the graph
            cfg_node = CFGNode(addr, irsb.size, self.model,
                               function_address=current_function_addr,
                               block_id=addr,
                               irsb=irsb,
                               thumb=is_thumb,
                               byte_string=irsb_string,
                               )
            if self._cfb is not None:
                self._cfb.add_obj(addr, lifted_block)

            self._nodes[addr] = cfg_node
            self._nodes_by_addr[addr].append(cfg_node)

            return addr, current_function_addr, cfg_node, irsb

        except (SimMemoryError, SimEngineError):
            return None, None, None, None