Disassemble machine readable code into human readable statements.

    Args:
        code(bytes): The machine code that is to be disassembled.
        addr(int): The memory address of the code (used for relative
            references).
        syntax(AsmSyntax): The output assembler syntax. This defaults to
            nasm on x86 architectures, AT&T on all other architectures.
        target(~pwnypack.target.Target): The architecture for which the code
            was written.  The global target is used if this argument is
            ``None``.

    Returns:
        list of str: The disassembled machine code.

    Raises:
        NotImplementedError: In an unsupported target platform is specified.
        RuntimeError: If ndisasm encounters an error.

    Example:
        >>> from pwny import *
        >>> disasm(b'_\\xc3', target=Target(arch=Target.Arch.x86, bits=64))
        ['pop rdi', 'ret']

def disasm(code, addr=0, syntax=None, target=None):
    """
    Disassemble machine readable code into human readable statements.

    Args:
        code(bytes): The machine code that is to be disassembled.
        addr(int): The memory address of the code (used for relative
            references).
        syntax(AsmSyntax): The output assembler syntax. This defaults to
            nasm on x86 architectures, AT&T on all other architectures.
        target(~pwnypack.target.Target): The architecture for which the code
            was written.  The global target is used if this argument is
            ``None``.

    Returns:
        list of str: The disassembled machine code.

    Raises:
        NotImplementedError: In an unsupported target platform is specified.
        RuntimeError: If ndisasm encounters an error.

    Example:
        >>> from pwny import *
        >>> disasm(b'_\\xc3', target=Target(arch=Target.Arch.x86, bits=64))
        ['pop rdi', 'ret']
    """

    if target is None:
        target = pwnypack.target.target

    if syntax is None:
        if target.arch is pwnypack.target.Target.Arch.x86:
            syntax = AsmSyntax.nasm
        else:
            syntax = AsmSyntax.att

    if syntax is AsmSyntax.nasm:
        if target.arch is not pwnypack.target.Target.Arch.x86:
            raise NotImplementedError('nasm only supports x86.')

        p = subprocess.Popen(
            [
                'ndisasm',
                '-b',
                str(target.bits.value),
                '-o',
                str(addr),
                '-',
            ],
            stdin=subprocess.PIPE,
            stdout=subprocess.PIPE,
            stderr=subprocess.PIPE,
        )
        stdout, stderr = p.communicate(code)
        if p.returncode:
            raise RuntimeError(stderr.decode('utf-8'))

        return [
            line.split(None, 2)[2]
            for line in stdout.decode('utf-8').split('\n')
            if line and not line.startswith(' ')
        ]
    elif syntax in (AsmSyntax.intel, AsmSyntax.att):
        md = prepare_capstone(syntax, target)
        statements = []
        total_size = 0
        for (_, size, mnemonic, op_str) in md.disasm_lite(code, addr):
            statements.append((mnemonic + ' ' + op_str).strip())
            total_size += size
        return statements
    else:
        raise NotImplementedError('Unsupported syntax for host platform.')