from __future__ import annotations
from typing import TYPE_CHECKING
import logging
import re
import string
import struct
from collections import defaultdict
from itertools import count

import capstone
import cle
import networkx
import pyvex

from angr.analyses import AnalysesHub
from . import Analysis
from .cfg.cfg_emulated import CFGEmulated
from .ddg import DDG
from .cfg.cfg_fast import CFGFast
from angr.codenode import CodeNode
from angr.knowledge_plugins.cfg.memory_data import MemoryDataSort
from angr.knowledge_plugins.functions import Function
from angr.knowledge_base import KnowledgeBase
from angr.sim_variable import SimMemoryVariable, SimTemporaryVariable

if TYPE_CHECKING:
    from .cfg import CFGNode

l = logging.getLogger(name=__name__)


#
# Exceptions
#


class BinaryError(Exception):
    pass


class InstructionError(BinaryError):
    pass


class ReassemblerFailureNotice(BinaryError):
    pass


#
# Constants
#

OP_TYPE_REG = 1
OP_TYPE_IMM = 2
OP_TYPE_MEM = 3
OP_TYPE_RAW = 4

OP_TYPE_MAP = {
    OP_TYPE_REG: "REG",
    OP_TYPE_IMM: "IMM",
    OP_TYPE_MEM: "MEM",
    OP_TYPE_RAW: "RAW",
}

CAPSTONE_OP_TYPE_MAP = {
    "X86": {
        capstone.x86.X86_OP_REG: OP_TYPE_REG,
        capstone.x86.X86_OP_IMM: OP_TYPE_IMM,
        capstone.x86.X86_OP_MEM: OP_TYPE_MEM,
    },
    "AMD64": {
        capstone.x86.X86_OP_REG: OP_TYPE_REG,
        capstone.x86.X86_OP_IMM: OP_TYPE_IMM,
        capstone.x86.X86_OP_MEM: OP_TYPE_MEM,
    },
}

CAPSTONE_REG_MAP = {
    # will be filled up by fill_reg_map()
    "X86": {},
    "AMD64": {},
}

# Utils


def string_escape(s):
    if isinstance(s, bytes):
        s = "".join(chr(i) for i in s)

    s = s.encode("unicode_escape").decode("utf-8")

    s = s.replace("\\'", "'")
    return s.replace('"', '\\"')


def fill_reg_map():
    # TODO: Support more architectures
    for attr in dir(capstone.x86):
        if attr.startswith("X86_REG_"):
            reg_name = attr[8:]
            reg_offset = getattr(capstone.x86, attr)
            CAPSTONE_REG_MAP["X86"][reg_offset] = reg_name.lower()

    for attr in dir(capstone.x86):
        if attr.startswith("X86_REG_"):
            reg_name = attr[8:]
            reg_offset = getattr(capstone.x86, attr)
            CAPSTONE_REG_MAP["AMD64"][reg_offset] = reg_name.lower()


def split_operands(s):
    operands = []
    operand = ""
    in_parenthesis = False
    for i, c in enumerate(s):
        if in_parenthesis and c == ")":
            in_parenthesis = False
        if c == "(":
            in_parenthesis = True
        if not in_parenthesis and c == "," and (i == len(s) - 1 or s[i + 1] == " "):
            operands.append(operand)
            operand = ""
            continue
        operand += c

    if operand:
        operands.append(operand)

    return operands


def is_hex(s):
    try:
        int(s, 16)
        return True
    except ValueError:
        return False


fill_reg_map()


class Label:
    g_label_ctr = count()

    def __init__(self, binary, name, original_addr=None):
        self.binary = binary
        self.name = name

        self.assigned = False

        self.var_size = None

        if self.name is None:
            self.name = f"label_{next(Label.g_label_ctr)}"

        self.original_addr = original_addr
        self.base_addr = None

    #
    # Overridden predefined methods
    #

    def __str__(self):
        """

        :return:
        """

        # if self.var_size is not None:
        #    s = ".type {name},@object\n.comm {name},{size},{size}".format(name=self.name, size=self.var_size)
        # else:
        return f".{self.name}:"

    def __hash__(self):
        return hash(self.name)

    def __eq__(self, other):
        return self.name == other.name

    #
    # Properties
    #

    @property
    def operand_str(self):
        if self.base_addr is None:
            return f".{self.name}"
        offset = self.offset
        sign = "+" if offset >= 0 else "-"
        offset = abs(offset)
        return f".{self.name}{sign}{offset}"

    @property
    def offset(self):
        if self.base_addr is None:
            return 0
        return self.original_addr - self.base_addr

    #
    # Static methods
    #

    @staticmethod
    def new_label(binary, name=None, function_name=None, original_addr=None, data_label=False):
        if function_name is not None:
            return FunctionLabel(binary, function_name, original_addr)
        if data_label:
            return DataLabel(binary, original_addr)
        return Label(binary, name, original_addr=original_addr)


class DataLabel(Label):
    def __init__(self, binary, original_addr, name=None):
        Label.__init__(self, binary, name, original_addr=original_addr)

    @property
    def operand_str(self):
        if self.base_addr is None:
            return self.name
        offset = self.offset
        sign = "+" if offset >= 0 else "-"
        offset = abs(offset)
        return f"({self.name}{sign}{offset})"

    def __str__(self):
        # if self.var_size is not None:
        #    s = ".comm {name},{size},{size}".format(name=self.name, size=self.var_size)
        # else:
        return f"{self.name}:"


class FunctionLabel(Label):
    def __init__(self, binary, function_name, original_addr, plt=False):
        Label.__init__(self, binary, function_name, original_addr=original_addr)

        self.plt = plt

    @property
    def function_name(self):
        return self.name

    @property
    def operand_str(self):
        return self.name

    def __str__(self):
        return ("\t.globl {func_name}\n" + "\t.type {func_name}, @function\n" + "{func_name}:").format(
            func_name=self.function_name
        )


class ObjectLabel(Label):
    def __init__(self, binary, symbol_name, original_addr, plt=False):
        Label.__init__(self, binary, symbol_name, original_addr=original_addr)

        self.plt = plt

    @property
    def symbol_name(self):
        return self.name

    @property
    def operand_str(self):
        return self.name

    def __str__(self):
        return ("\t.globl {symbol_name}\n" + "\t.type {symbol_name}, @object\n" + "{symbol_name}:").format(
            symbol_name=self.symbol_name
        )


class NotypeLabel(Label):
    def __init__(self, binary, symbol_name, original_addr, plt=False):
        Label.__init__(self, binary, symbol_name, original_addr=original_addr)

        self.plt = plt

    @property
    def symbol_name(self):
        return self.name

    @property
    def operand_str(self):
        return self.name

    def __str__(self):
        return ("\t.globl {symbol_name}\n" + "\t.type {symbol_name}, @notype\n" + "{symbol_name}:").format(
            symbol_name=self.symbol_name
        )


class SymbolManager:
    """
    SymbolManager manages all symbols in the binary.
    """

    def __init__(self, binary, cfg):
        """
        Constructor.

        :param Reassembler binary: The Binary analysis instance.
        :param angr.analyses.CFG cfg: The CFG analysis instance.
        :return: None
        """

        self.binary = binary
        self.project = binary.project
        self.cfg = cfg

        self.addr_to_label = defaultdict(list)
        self.symbol_names = set()  # deduplicate symbol names

    def get_unique_symbol_name(self, symbol_name):
        if symbol_name not in self.symbol_names:
            self.symbol_names.add(symbol_name)
            return symbol_name

        i = 0
        while True:
            name = f"{symbol_name}_{i}"
            if name not in self.symbol_names:
                self.symbol_names.add(name)
                return name
            i += 1

    def new_label(self, addr, name=None, is_function=None, force=False):
        if force:
            if self.binary.main_nonexecutable_regions_contain(addr):
                label = DataLabel(self.binary, addr, name=name)
            else:
                label = Label.new_label(self.binary, name=name, original_addr=addr)
            self.addr_to_label[addr].append(label)
            return label

        if addr in self.addr_to_label:
            return self.addr_to_label[addr][0]

        # Check if the address points to a function by checking the plt of main binary
        reverse_plt = self.project.loader.main_object.reverse_plt

        if addr in reverse_plt:
            # It's a PLT entry!
            label = FunctionLabel(self.binary, reverse_plt[addr], addr, plt=True)
        elif addr is not None and self.project.loader.find_symbol(addr) is not None:
            # It's an extern symbol
            symbol = self.project.loader.find_symbol(addr)
            if symbol.owner is self.project.loader.main_object:
                symbol_name = symbol.name
                if "@" in symbol_name:
                    symbol_name = symbol_name[: symbol_name.index("@")]

                # check the type...
                if symbol.type == cle.SymbolType.TYPE_FUNCTION:
                    # it's a function!
                    unique_symbol_name = self.get_unique_symbol_name(symbol_name)
                    label = FunctionLabel(self.binary, unique_symbol_name, addr)
                elif symbol.type == cle.SymbolType.TYPE_OBJECT:
                    # it's an object
                    unique_symbol_name = self.get_unique_symbol_name(symbol_name)
                    label = ObjectLabel(self.binary, unique_symbol_name, addr)
                elif symbol.type == cle.SymbolType.TYPE_NONE:
                    # notype
                    unique_symbol_name = self.get_unique_symbol_name(symbol_name)
                    label = NotypeLabel(self.binary, unique_symbol_name, addr)
                elif symbol.type == cle.SymbolType.TYPE_SECTION:
                    # section label
                    # use a normal label instead
                    if not name:
                        # handle empty names
                        name = None
                    label = Label.new_label(self.binary, name=name, original_addr=addr)
                else:
                    raise Exception(f"Unsupported symbol type {symbol.type}. Bug Fish about it!")

            else:
                raise Exception(
                    f"the symbol {symbol.name} is not owned by the main object. Try reload the project with"
                    '"auto_load_libs=False". If that does not solve the issue, please report to GitHub.'
                )

        elif (addr is not None and addr in self.cfg.functions) or is_function:
            # It's a function identified by angr's CFG recovery

            if is_function and name is not None:
                function_name = name
            else:
                function_name = self.cfg.functions[addr].name

                # special function name for entry point
                if addr == self.project.entry:
                    function_name = "_start"

            label = FunctionLabel(self.binary, function_name, addr)
        elif addr is not None and self.binary.main_nonexecutable_regions_contain(addr):
            label = DataLabel(self.binary, addr)
        else:
            label = Label.new_label(self.binary, name=name, original_addr=addr)

        if addr is not None:
            self.addr_to_label[addr].append(label)

        return label

    def label_got(self, addr, label):
        """
        Mark a certain label as assigned (to an instruction or a block of data).

        :param int addr: The address of the label.
        :param angr.analyses.reassembler.Label label:
                         The label that is just assigned.
        :return: None
        """

        if label in self.addr_to_label[addr]:
            label.assigned = True


class Operand:
    def __init__(
        self, binary, insn_addr, insn_size, capstone_operand, operand_str, mnemonic, operand_offset, syntax=None
    ):
        """
        Constructor.

        :param Reassembler binary: The Binary analysis.
        :param int insn_addr: Address of the instruction.
        :param capstone_operand:
        :param str operand_str: the string representation of this operand
        :param str mnemonic: Mnemonic of the instruction that this operand belongs to.
        :param int operand_offset: offset of the operand into the instruction.
        :param str syntax: Provide a way to override the default syntax coming from `binary`.
        :return: None
        """

        self.binary = binary
        self.project = binary.project
        self.insn_addr = insn_addr
        self.insn_size = insn_size
        self.operand_str = operand_str
        self.mnemonic = mnemonic
        self.operand_offset = operand_offset
        self.syntax = self.binary.syntax if syntax is None else syntax
        self.type = None
        self.size = capstone_operand.size

        # IMM
        self.is_coderef = None
        self.is_dataref = None
        self.label = None
        self.label_offset = 0

        # MEM
        self.base = None
        self.index = None
        self.scale = None
        self.disp = None

        # RAW
        self.raw_asm = None

        self.disp_is_coderef = None
        self.disp_is_dataref = None
        self.disp_label = None
        self.disp_label_offset = 0

        self._initialize(capstone_operand)

    #
    # Public methods
    #

    def assembly(self):
        if self.type == OP_TYPE_IMM and self.label:
            if self.label_offset > 0:
                return f"{self.label.operand_str} + {self.label_offset}"
            if self.label_offset < 0:
                return f"{self.label.operand_str} - {abs(self.label_offset)}"
            return self.label.operand_str

        if self.type == OP_TYPE_MEM:
            disp = ""
            if self.disp:
                if self.disp_label:
                    if self.disp_label_offset > 0:
                        disp = f"{self.disp_label.operand_str} + {self.disp_label_offset}"
                    elif self.disp_label_offset < 0:
                        disp = f"{self.disp_label.operand_str} - {abs(self.disp_label_offset)}"
                    else:
                        disp = self.disp_label.operand_str
                else:
                    disp = f"{self.disp}"

            base = ""
            if self.base:
                base = CAPSTONE_REG_MAP[self.project.arch.name][self.base]

            if self.syntax == "at&t":
                # displacement(base, index, scale)
                base = f"%{base}" if base else ""

                if "*" in self.operand_str and disp:
                    # absolute memory address
                    disp = "*" + disp

                if self.index:
                    s = f"{disp}({base}, %{CAPSTONE_REG_MAP[self.project.arch.name][self.index]}, {self.scale})"
                elif self.base:  # not self.index
                    s = f"{disp}({base})"
                else:
                    s = disp

                return s

            s = []
            if base:
                s.append(base)

            if self.index and self.scale:
                if s:
                    s.append("+")
                s.append(f"({CAPSTONE_REG_MAP[self.project.arch.name][self.index]} * {self.scale})")

            if disp:
                if disp.startswith("-"):
                    s.append("-")
                    s.append(disp[1:])
                else:
                    if s:
                        s.append("+")
                    s.append(disp)

            asm = " ".join(s)

            # we need to specify the size here
            if self.size == 16:
                asm = f"xmmword ptr [{asm}]"
            elif self.size == 10:
                asm = f"xword ptr [{asm}]"
            elif self.size == 8:
                asm = f"qword ptr [{asm}]"
            elif self.size == 4:
                asm = f"dword ptr [{asm}]"
            elif self.size == 2:
                asm = f"word ptr [{asm}]"
            elif self.size == 1:
                asm = f"byte ptr [{asm}]"
            else:
                raise BinaryError(f'Unsupported memory operand size for operand "{self.operand_str}"')

            return asm

        if self.type == OP_TYPE_RAW:
            return self.raw_asm

        # Nothing special
        return None

    #
    # Overridden predefined methods
    #

    def __str__(self):
        """

        :return:
        """

        op_type = OP_TYPE_MAP[self.type]

        ref_type = ""
        if self.is_coderef:
            ref_type = "CODEREF"
        elif self.is_dataref:
            ref_type = "DATAREF"

        if ref_type:
            return f"{op_type} <{ref_type}>"
        return op_type

    #
    # Properties
    #

    @property
    def is_immediate(self):
        return self.type == OP_TYPE_IMM

    @property
    def symbolized(self):
        return self.label is not None or self.disp_label is not None

    #
    # Private methods
    #

    def _initialize(self, capstone_operand):
        arch_name = self.project.arch.name
        self.type = CAPSTONE_OP_TYPE_MAP[arch_name][capstone_operand.type]

        if self.type == OP_TYPE_IMM:
            # Check if this is a reference to code
            imm = capstone_operand.imm

            self.is_coderef, self.is_dataref, baseaddr = self._imm_to_ptr(imm, self.type, self.mnemonic)

            if self.is_coderef or self.is_dataref:
                self.label = self.binary.symbol_manager.new_label(addr=baseaddr)
                self.label_offset = imm - baseaddr

                if self.mnemonic.startswith("j") or self.mnemonic.startswith("loop"):
                    sort = "jump"
                elif self.mnemonic.startswith("call"):
                    sort = "call"
                else:
                    sort = "absolute"
                self.binary.register_instruction_reference(self.insn_addr, imm, sort, self.operand_offset)

        elif self.type == OP_TYPE_MEM:
            self.base = capstone_operand.mem.base
            self.index = capstone_operand.mem.index
            self.scale = capstone_operand.mem.scale
            self.disp = capstone_operand.mem.disp

            if self.binary.project.arch.name == "AMD64" and CAPSTONE_REG_MAP["AMD64"][self.base] == "rip":
                # rip-relative addressing
                self.disp += self.insn_addr + self.insn_size

            self.disp_is_coderef, self.disp_is_dataref, baseaddr = self._imm_to_ptr(self.disp, self.type, self.mnemonic)

            if self.disp_is_coderef or self.disp_is_dataref:
                self.disp_label = self.binary.symbol_manager.new_label(addr=baseaddr)
                self.disp_label_offset = self.disp - baseaddr

                self.binary.register_instruction_reference(self.insn_addr, self.disp, "absolute", self.operand_offset)

    def _imm_to_ptr(self, imm, operand_type, mnemonic):  # pylint:disable=no-self-use,unused-argument
        """
        Try to classify an immediate as a pointer.

        :param int imm: The immediate to test.
        :param int operand_type: Operand type of this operand, can either be IMM or MEM.
        :param str mnemonic: Mnemonic of the instruction that this operand belongs to.
        :return: A tuple of (is code reference, is data reference, base address, offset)
        :rtype: tuple
        """

        is_coderef, is_dataref = False, False
        baseaddr = None

        if (
            not is_coderef
            and not is_dataref
            and self.binary.main_executable_regions_contain(imm)
            # does it point to the beginning of an instruction?
            and imm in self.binary.all_insn_addrs
        ):
            is_coderef = True
            baseaddr = imm

        if not is_coderef and not is_dataref and self.binary.main_nonexecutable_regions_contain(imm):
            is_dataref = True
            baseaddr = imm

        if not is_coderef and not is_dataref:
            tolerance_before = 1024 if operand_type == OP_TYPE_MEM else 64
            contains_, baseaddr_ = self.binary.main_nonexecutable_region_limbos_contain(
                imm, tolerance_before=tolerance_before, tolerance_after=1024
            )
            if contains_:
                is_dataref = True
                baseaddr = baseaddr_

            if not contains_:
                contains_, baseaddr_ = self.binary.main_executable_region_limbos_contain(imm)
                if contains_:
                    is_coderef = True
                    baseaddr = baseaddr_

        return (is_coderef, is_dataref, baseaddr)


class Instruction:
    """
    High-level representation of an instruction in the binary
    """

    def __init__(self, binary, addr, size, insn_bytes, capstone_instr):
        """

        :param Reassembler binary: The Binary analysis
        :param int addr: Address of the instruction
        :param int size: Size of the instruction
        :param str insn_bytes: Instruction bytes
        :param capstone_instr: Capstone Instr object.
        :return: None
        """

        self.binary = binary
        self.project = binary.project
        self.addr = addr
        self.size = size
        self.bytes = insn_bytes

        self.mnemonic = capstone_instr.mnemonic
        self.op_str = capstone_instr.op_str
        self.capstone_operand_types = [operand.type for operand in capstone_instr.operands]

        self.operands = []

        self.labels = []

        operand_offsets = []
        for operand in capstone_instr.operands:
            if operand.type == capstone.CS_OP_IMM:
                operand_offsets.append(capstone_instr.imm_offset)
            elif operand.type == capstone.CS_OP_MEM:
                operand_offsets.append(capstone_instr.disp_offset)
            else:
                operand_offsets.append(0)

        if self.addr is not None:
            self._initialize(capstone_instr.operands, operand_offsets)

    #
    # Overridden predefined instructions
    #
    def __str__(self):
        """

        :return:
        """

        return self.assembly(comments=True, symbolized=False)

    #
    # Public methods
    #

    def assign_labels(self):
        if self.addr in self.binary.symbol_manager.addr_to_label:
            labels = self.binary.symbol_manager.addr_to_label[self.addr]
            for label in labels:
                if label not in self.labels:
                    self.labels.append(label)

    def dbg_comments(self):
        operands = ", ".join([str(operand) for operand in self.operands])
        capstone_str = f"{self.addr:#08x}:\t{self.mnemonic}\t{self.op_str}"
        return f"\t# {capstone_str} [{operands}]"

    def assembly(self, comments=False, symbolized=True):
        """

        :return:
        """

        dbg_comments = self.dbg_comments() if comments else ""

        labels = "\n".join([str(lbl) for lbl in self.labels])

        inserted_asm_before_label = ""
        if self.addr in self.binary.inserted_asm_before_label:
            # put all assembly code there
            if comments:
                inserted_asm_before_label += "\t# Inserted assembly code (before label):\n"
            inserted_asm_before_label = "\n".join(self.binary.inserted_asm_before_label[self.addr])
            inserted_asm_before_label += "\n"

        inserted_asm_after_label = ""
        if self.addr in self.binary.inserted_asm_after_label:
            # put all assembly code there
            if comments:
                inserted_asm_after_label += "\t# Inserted assembly code (after label):\n"
            inserted_asm_after_label = "\n".join(self.binary.inserted_asm_after_label[self.addr])
            inserted_asm_after_label += "\n"

        not_symbolized = f"\t{self.mnemonic}\t{self.op_str}"
        if not symbolized:
            asm = not_symbolized

        elif not any((operand.symbolized or operand.type == OP_TYPE_RAW) for operand in self.operands):
            # No label is involved
            asm = not_symbolized

        elif not self.operands:
            # There is no operand
            asm = not_symbolized

        else:
            # Now it's the tricky part. capstone doesn't give us anyway to print individual operand. We gotta parse it
            # by ourselves
            # Remove the address
            # capstone_str = capstone_str[capstone_str.find('\t') + 1 : ]

            all_operands = [operand.operand_str for operand in self.operands]
            mnemonic = self.mnemonic

            for i, op in enumerate(self.operands):
                op_asm = op.assembly()
                if op_asm is not None:
                    if op.type in (OP_TYPE_IMM, OP_TYPE_MEM, OP_TYPE_RAW):
                        all_operands[i] = op_asm
                    else:
                        raise BinaryError(f"Unsupported operand type {op.type}.")

                    if op.type != OP_TYPE_RAW and self.capstone_operand_types[i] == capstone.CS_OP_IMM:
                        if mnemonic.startswith(("j", "call", "loop")):
                            pass
                        else:
                            # mark the size of the variable
                            if op.is_dataref:
                                op.label.var_size = op.size
                            if self.binary.syntax == "at&t":
                                all_operands[i] = "$" + all_operands[i]
                            else:
                                all_operands[i] = "OFFSET FLAT:" + all_operands[i]

            asm = "\t{}{}".format(mnemonic, "\t" + ", ".join(all_operands))

        if self.addr in self.binary._removed_instructions:
            contents = [dbg_comments, inserted_asm_before_label, labels, inserted_asm_after_label]
        else:
            contents = [dbg_comments, inserted_asm_before_label, labels, inserted_asm_after_label, asm]
        contents = [a for a in contents if a]

        return "\n".join(contents)

    #
    # Private methods
    #

    def _initialize(self, capstone_operands, operand_offsets):
        """
        Initialize this object

        :return: None
        """

        if self.addr is None:
            raise InstructionError("self.addr must be specified")

        self._initialize_operands(capstone_operands, operand_offsets)

    def _initialize_operands(self, capstone_operands, operand_offsets):
        """

        :return:
        """

        all_operands = split_operands(self.op_str)
        capstone_operands = capstone_operands[
            -len(all_operands) :
        ]  # sometimes there are more operands than expected...
        operand_offsets = operand_offsets[-len(all_operands) :]

        for operand, operand_str, offset in zip(capstone_operands, all_operands, operand_offsets):
            self.operands.append(
                Operand(self.binary, self.addr, self.size, operand, operand_str, self.mnemonic, offset)
            )


class BasicBlock:
    """
    BasicBlock represents a basic block in the binary.
    """

    def __init__(self, binary, addr, size, x86_getpc_retsite: bool = False):
        """
        Constructor.

        :param Reassembler binary: The Binary analysis.
        :param int addr: Address of the block
        :param int size: Size of the block
        :return: None
        """

        self.binary = binary
        self.project = binary.project

        self.addr = addr
        self.size = size
        self.x86_getpc_retsite = x86_getpc_retsite

        self.instructions = []

        self._initialize()

    #
    # Overridden predefined methods
    #

    def __str__(self):
        """
        Return a linear representation of all instructions in this block.
        :return:
        """

        return self.assembly(symbolized=False)

    def __repr__(self):
        return f"<BasicBlock {self.addr:#08x}>"

    #
    # Public methods
    #

    def assign_labels(self):
        for ins in self.instructions:
            ins.assign_labels()

    def assembly(self, comments=False, symbolized=True):
        return "\n".join([ins.assembly(comments=comments, symbolized=symbolized) for ins in self.instructions])

    def instruction_addresses(self):
        return sorted([(ins.addr, ins.size) for ins in self.instructions], key=lambda x: x[0])

    #
    # Private methods
    #

    def _initialize(self):
        """

        :return:
        """

        # re-lifting
        block = self.project.factory.fresh_block(self.addr, self.size)
        capstone_obj = block.capstone

        # Fill in instructions
        for idx, instr in enumerate(capstone_obj.insns):
            # special handling for X86 PIE binaries
            instruction = Instruction(self.binary, instr.address, instr.size, None, instr)

            if self.x86_getpc_retsite and idx == 0:
                if (
                    self.binary.syntax == "at&t"
                    and instr.mnemonic == "addl"
                    and instr.operands[1].type == capstone.CS_OP_REG
                    and instr.operands[0].type == capstone.CS_OP_IMM
                ):
                    instruction.operands[0].type = OP_TYPE_RAW
                    instruction.operands[0].raw_asm = "$_GLOBAL_OFFSET_TABLE_"
                elif (
                    self.binary.syntax == "intel"
                    and instr.mnemonic == "add"
                    and instr.operands[0].type == capstone.CS_OP_REG
                    and instr.operands[1].type == capstone.CS_OP_IMM
                ):
                    instruction.operands[1].type = OP_TYPE_RAW
                    instruction.operands[1].raw_asm = "OFFSET FLAG:_GLOBAL_OFFSET_TABLE_"

            self.instructions.append(instruction)

        self.instructions = sorted(self.instructions, key=lambda x: x.addr)


class Procedure:
    """
    Procedure in the binary.
    """

    def __init__(self, binary, function=None, addr=None, size=None, name=None, section=".text", asm_code=None):
        """
        Constructor.

        :param Reassembler binary: The Binary analysis.
        :param angr.knowledge.Function function: The function it represents
        :param int addr: Address of the function. Not required if `function` is provided.
        :param int size: Size of the function. Not required if `function` is provided.
        :param str section: Which section this function comes from.
        :return: None
        """

        self.binary = binary
        self.project = binary.project

        if function is None:
            self.addr = addr
            self.size = size

            self.function = None
            self._name = name

        else:
            self.addr = function.addr
            self.size = None  # FIXME:

            self.function = function
            self._name = function.name

        self.asm_code = asm_code
        self.section = section

        self.blocks = []

        self._initialize()

    #
    # Attributes
    #

    @property
    def name(self):
        """
        Get function name from the labels of the very first block.
        :return: Function name if there is any, None otherwise
        :rtype: string
        """

        if self._name is not None:
            return self._name

        if not self.blocks:
            return None

        if not self.blocks[0].instructions:
            return None

        if not self.blocks[0].instructions[0].labels:
            return None

        lbl = self.blocks[0].instructions[0].labels[0]

        if isinstance(lbl, FunctionLabel):
            return lbl.function_name

        return None

    @property
    def is_plt(self):
        """
        If this function is a PLT entry or not.
        :return: True if this function is a PLT entry, False otherwise
        :rtype: bool
        """

        if self.section == ".plt":
            return True

        if not self.blocks:
            return False

        initial_block = next((b for b in self.blocks if b.addr == self.addr), None)
        if initial_block is None:
            return False

        if not initial_block.instructions:
            return False

        if not initial_block.instructions[0].labels:
            return False

        lbl = initial_block.instructions[0].labels[0]

        if isinstance(lbl, FunctionLabel):
            return lbl.plt

        return False

    #
    # Overridden predefined methods
    #
    def __str__(self):
        """
        Output all instructions of the current procedure
        :return:
        """

        return self.assembly(symbolized=False)

    #
    # Public methods
    #

    def assign_labels(self):
        for block in self.blocks:
            block.assign_labels()

    def assembly(self, comments=False, symbolized=True):
        """
        Get the assembly manifest of the procedure.

        :param comments:
