#!/usr/local/bin/python3.10
# -*- coding: utf-8 -*-

# Description
# -----------
# Print information about a Mach-O binary

import sys
import os
import argparse
import traceback
import lief
from lief import MachO

terminal_rows, terminal_columns = 100, 100
try:
    terminal_rows, terminal_columns = os.popen('stty size', 'r').read().split()
except ValueError:
    pass

terminal_columns = int(terminal_columns)
terminal_rows    = int(terminal_rows)
EXIT_STATUS = 0

class exceptions_handler(object):
    func = None

    def __init__(self, exceptions, on_except_callback=None):
        self.exceptions         = exceptions
        self.on_except_callback = on_except_callback

    def __call__(self, *args, **kwargs):
        if self.func is None:
            self.func = args[0]
            return self
        try:
            return self.func(*args, **kwargs)
        except self.exceptions as e:
            global EXIT_STATUS
            print("{} raised: {}".format(self.func.__name__, e))
            EXIT_STATUS = 1
            if self.on_except_callback is not None:
                self.on_except_callback(e)
            else:
                print("-" * 60)
                print("Exception in {}: {}".format(self.func.__name__, e))
                exc_type, exc_value, exc_traceback = sys.exc_info()
                traceback.print_tb(exc_traceback)
                print("-" * 60)



@exceptions_handler(Exception)
def print_information(binary):
    print("== Information ==")
    format_str = "{:<30} {:<30}"
    format_hex = "{:<30} 0x{:<28x}"
    format_dec = "{:<30} {:<30d}"
    print(format_str.format("Name:",         binary.name))
    print(format_hex.format("Address base:", binary.imagebase))
    print(format_str.format("PIE:",          str(binary.is_pie)))
    print(format_str.format("NX:",           str(binary.has_nx)))
    print("")

@exceptions_handler(Exception)
def print_header(binary):
    format_str = "{:<33} {:<30}"
    format_hex = "{:<33} 0x{:<28x}"
    format_dec = "{:<33} {:<30d}"

    print("== Header ==")
    header = binary.header
    flags_str =  " - ".join([str(s).split(".")[-1] for s in header.flags_list])

    print(format_str.format("Magic:",              str(header.magic).split(".")[-1]))
    print(format_str.format("CPU Type:",           str(header.cpu_type).split(".")[-1]))
    print(format_hex.format("CPU sub-type:",       header.cpu_subtype))
    print(format_str.format("File Type:",          str(header.file_type).split(".")[-1]))
    print(format_str.format("Flags:",              flags_str))
    print(format_dec.format("Number of commands:", header.nb_cmds))
    print(format_hex.format("Size of commands:",   header.sizeof_cmds))
    print(format_hex.format("Reserved:",           header.reserved))

@exceptions_handler(Exception)
def print_commands(binary):

    f_title = "|{:<20}|{:<11}|{:<11}|"
    f_value = "|{:<20}|0x{:08x} |0x{:08x} |"
    print("== Commands ==")
    print(f_title.format("Command", "Offset", "Size"))
    for command in binary.commands:
        print(f_value.format(
            str(command.command).split(".")[-1],
            command.command_offset,
            command.size))
    print("")

@exceptions_handler(Exception)
def print_libraries(binary):

    f_title = "|{:<30}|{:<10}|{:<16}|{:<22}|"
    f_value = "|{:<30}|{:<10d}|{:<16}|{:<22}|"
    print("== Libraries ==")
    print(f_title.format("Name", "Timestamp", "Current Version", "Compatibility Version"))
    for library in binary.libraries:
        current_version_str = "{:d}.{:d}.{:d}".format(*library.current_version)
        compatibility_version_str = "{:d}.{:d}.{:d}".format(*library.compatibility_version)
        print(f_value.format(
            library.name,
            library.timestamp,
            current_version_str,
            compatibility_version_str))
    print("")

@exceptions_handler(Exception)
def print_segments(binary):

    f_title = "|{:<20}|{:<16}|{:<16}|{:<16}|{:16}|{:16}|{:16}|{}"
    f_value = "|{:<20}|0x{:<13x} |0x{:<13x} |0x{:<13x} |0x{:<13x} |0x{:<13x} |0x{:<13x} |{}"
    print("== Segments ==")
    print(f_title.format(
        "Name", "Virtual Address", "Virtual Size",
        "Offset", "Size", "Max Protection",
        "Init Protection", "Sections"))
    for segment in binary.segments:
        sections = ", ".join(map(lambda s : s.name, segment.sections))
        print(f_value.format(
            segment.name,
            segment.virtual_address,
            segment.virtual_size,
            segment.file_offset,
            segment.file_size,
            segment.max_protection,
            segment.init_protection,
            sections))

    print("")

@exceptions_handler(Exception)
def print_sections(binary):

    f_title = "|{:<20}|{:<16}|{:<16}|{:<16}|{:16}|{:22}|{:19}|{:25}|{:25}|"
    f_value = "|{:<20}|0x{:<13x} |0x{:<13x} |0x{:<13x} |0x{:<13x} |0x{:<19x} |0x{:<16x} |{:<25}|{:<25}"

    print("== Sections ==")

    print(f_title.format(
        "Name", "Virtual Address", "Offset", "Size",
        "Alignement", "Number of Relocations", "Relocation offset",
        "Type", "Flags"))




    for section in binary.sections:
        flags_str =  " - ".join([str(s).split(".")[-1] for s in section.flags_list])
        print(f_value.format(
            section.name,
            section.virtual_address,
            section.offset,
            section.size,
            section.alignment,
            section.numberof_relocations,
            section.relocation_offset,
            str(section.type).split(".")[-1],
            flags_str))
        if len(section.relocations) > 0:
            for idx, reloc in enumerate(section.relocations):
                name = reloc.symbol.name if reloc.has_symbol else ""
                secname = " - " + reloc.section.name if reloc.has_section else ""
                type = str(reloc.type)
                if reloc.architecture == MachO.CPU_TYPES.x86:
                    type = str(MachO.X86_RELOCATION(reloc.type))

                if reloc.architecture == MachO.CPU_TYPES.x86_64:
                    type = str(MachO.X86_64_RELOCATION(reloc.type))

                if reloc.architecture == MachO.CPU_TYPES.ARM:
                    type = str(MachO.ARM_RELOCATION(reloc.type))

                if reloc.architecture == MachO.CPU_TYPES.ARM64:
                    type = str(MachO.ARM64_RELOCATION(reloc.type))

                if reloc.architecture == MachO.CPU_TYPES.POWERPC:
                    type = str(MachO.PPC_RELOCATION(reloc.type))


                print("    [Reloc #{:d} {section}] {name:<10} 0x{address:<6x} {type:<20} {size:d} {pcrel} {scat}".format(
                    idx,
                    section=secname,
                    name=name,
                    address=reloc.address,
                    type=type.split(".")[-1],
                    size=reloc.size,
                    pcrel=str(reloc.pc_relative),
                    scat=str(reloc.is_scattered)))
            print("")


    print("")

@exceptions_handler(Exception)
def print_symbols(binary):
    symbols = binary.symbols
    if len(symbols) == 0:
        return
    try:
        maxsize = max([len(symbol.demangled_name) for symbol in symbols])
    except:
        maxsize = max([len(symbol.name) for symbol in symbols])
    maxsize = min(maxsize, terminal_columns - 90) if terminal_columns > 90 else terminal_columns

    f_title = "|{:<" + str(maxsize) + "} |{:<6}|{:<19}|{:<16}|{:16}| {:s}"
    f_value = "|{:<" + str(maxsize) + "} |0x{:<3x} |0x{:<16x} |0x{:<13x} |0x{:<13x} | {:s}"
    print("== Symbols ==")
    print(f_title.format(
        "Name", "Type", "Number of Sections", "Description", "Value", "Library"))
    for symbol in binary.symbols:
        libname = ""
        if symbol.has_binding_info and symbol.binding_info.has_library:
            libname = symbol.binding_info.library.name


        symbol_value = symbol.value if symbol.value > 0 or not symbol.has_binding_info else symbol.binding_info.address

        try:
            symbol_name = symbol.demangled_name
        except:
            symbol_name = symbol.name
        print(f_value.format(
            symbol_name,
            symbol.type,
            symbol.numberof_sections,
            symbol.description,
            symbol_value,
            libname))
    print("")

@exceptions_handler(Exception)
def print_symbol_command(binary):
    print("== Symbol Command ==")

    scmd = binary.symbol_command

    format_str = "{:<17} {:<30}"
    format_hex = "{:<17} 0x{:<28x}"
    format_dec = "{:<17} {:<30d}"

    print(format_hex.format("Symbol offset", scmd.symbol_offset))
    print(format_dec.format("Number of symbols", scmd.numberof_symbols))

    print(format_hex.format("String offset", scmd.strings_offset))
    print(format_hex.format("String size", scmd.strings_size))

    print("")

@exceptions_handler(Exception)
def print_dynamic_symbol_command(binary):
    print("== Dynamic Symbol Command ==")

    dyscmd = binary.dynamic_symbol_command

    format_str = "{:<36} {:<30}"
    format_hex = "{:<36} 0x{:<28x}"
    format_dec = "{:<36} {:<30d}"

    print(format_dec.format("First local symbol index", dyscmd.idx_local_symbol))
    print(format_dec.format("Number of local symbols", dyscmd.nb_local_symbols))

    print(format_dec.format("External symbol index", dyscmd.idx_external_define_symbol))
    print(format_dec.format("Number of external symbols", dyscmd.nb_external_define_symbols))

    print(format_dec.format("Undefined symbol index", dyscmd.idx_undefined_symbol))
    print(format_dec.format("Number of undefined symbols", dyscmd.nb_undefined_symbols))

    print(format_dec.format("Table of content offset", dyscmd.toc_offset))
    print(format_dec.format("Number of entries in TOC", dyscmd.nb_toc))

    print(format_hex.format("Module table offset", dyscmd.module_table_offset))
    print(format_dec.format("Number of entries in module table", dyscmd.nb_module_table))

    print(format_hex.format("External reference table offset", dyscmd.external_reference_symbol_offset))
    print(format_dec.format("Number of external reference", dyscmd.nb_external_reference_symbols))

    print(format_hex.format("Indirect symbols offset", dyscmd.indirect_symbol_offset))
    print(format_dec.format("Number of indirect symbols", dyscmd.nb_indirect_symbols))

    print(format_hex.format("External relocation offset", dyscmd.external_relocation_offset))
    print(format_dec.format("Number of external relocations", dyscmd.nb_external_relocations))

    print(format_hex.format("Local relocation offset", dyscmd.local_relocation_offset))
    print(format_dec.format("Number of local relocations", dyscmd.nb_local_relocations))

    print("")


@exceptions_handler(Exception)
def print_uuid(binary):
    print("== UUID ==")
    cmd = binary.uuid
    uuid_str = " ".join(map(lambda e : "{:02x}".format(e), cmd.uuid))
    print("UUID: {}".format(uuid_str))

    print("")


@exceptions_handler(Exception)
def print_main_command(binary):

    format_str = "{:<13} {:<30}"
    format_hex = "{:<13} 0x{:<28x}"
    format_dec = "{:<13} {:<30d}"

    print("== Main Command ==")
    cmd = binary.main_command

    print(format_hex.format("Entry point:", cmd.entrypoint))
    print(format_hex.format("Stack size:", cmd.stack_size))

    print("")


@exceptions_handler(Exception)
def print_thread_command(binary):

    format_str = "{:<13} {:<30}"
    format_hex = "{:<13} 0x{:<28x}"
    format_dec = "{:<13} {:<30d}"

    print("== Thread Command ==")
    cmd = binary.thread_command

    print(format_hex.format("Flavor:", cmd.flavor))
    print(format_hex.format("Count:",  cmd.count))
    print(format_hex.format("PC:",     cmd.pc))

    print("")

@exceptions_handler(Exception)
def print_rpath_command(binary):

    format_str = "{:<13} {:<30}"
    format_hex = "{:<13} 0x{:<28x}"
    format_dec = "{:<13} {:<30d}"

    print("== Rpath Command ==")
    cmd = binary.rpath
    print("Path: {}".format(cmd.path))


    print("")



@exceptions_handler(Exception)
def print_dylinker(binary):
    print("== Dylinker ==")
    print("Path: {}".format(binary.dylinker.name))

    print("")

@exceptions_handler(Exception)
def print_function_starts(binary):
    format_str = "{:<13} {:<30}"
    format_hex = "{:<13} 0x{:<28x}"
    format_dec = "{:<13} {:<30d}"

    print("== Function Starts ==")

    fstarts = binary.function_starts

    print(format_hex.format("Offset:", fstarts.data_offset))
    print(format_hex.format("Size:",   fstarts.data_size))
    print("Functions: ({:d})".format(len(fstarts.functions)))
    for idx, address in enumerate(fstarts.functions):
        print("    [{:d}] __TEXT + 0x{:x}".format(idx, address))

    print("")

@exceptions_handler(Exception)
def print_data_in_code(binary):
    format_str = "{:<13} {:<30}"
    format_hex = "{:<13} 0x{:<28x}"
    format_dec = "{:<13} {:<30d}"

    print("== Data In Code ==")

    datacode = binary.data_in_code

    print(format_hex.format("Offset:", datacode.data_offset))
    print(format_hex.format("Size:",   datacode.data_size))
    print("")
    for entry in datacode.entries:
        type_str = str(entry.type).split(".")[-1]
        print("- {:<14}: 0x{:x} ({:d} bytes)".format(type_str, entry.offset, entry.length))
    print("")


@exceptions_handler(Exception)
def print_segment_split_info(binary):
    format_str = "{:<13} {:<30}"
    format_hex = "{:<13} 0x{:<28x}"
    format_dec = "{:<13} {:<30d}"

    print("== Segment Split Info ==")

    sinfo = binary.segment_split_info

    print(format_hex.format("Offset:", sinfo.data_offset))
    print(format_hex.format("Size:",   sinfo.data_size))

    print("")


@exceptions_handler(Exception)
def print_sub_framework(binary):
    format_str = "{:<13} {:<30}"
    format_hex = "{:<13} 0x{:<28x}"
    format_dec = "{:<13} {:<30d}"

    print("== Sub Framework ==")

    sinfo = binary.sub_framework
    print(format_str.format("Umbrella:", sinfo.umbrella))

    print("")

@exceptions_handler(Exception)
def print_dyld_environment(binary):
    format_str = "{:<13} {:<30}"
    format_hex = "{:<13} 0x{:<28x}"
    format_dec = "{:<13} {:<30d}"

    print("== Dyld Environment ==")

    env = binary.dyld_environment
    print(format_str.format("Value:", env.value))

    print("")


@exceptions_handler(Exception)
def print_dyld_info(binary):
    print("== Dyld Info ==")
    f_title = "|{:<12}|{:<11}|{:<11}|"
    f_value = "|{:<12}|0x{:<8x} |0x{:<8x} |"

    dyld_info = binary.dyld_info

    print(f_title.format("Type", "Offset", "Size"))
    print(f_value.format("Rebase",    dyld_info.rebase[0],      dyld_info.rebase[1]))
    print(f_value.format("Bind",      dyld_info.bind[0],        dyld_info.bind[1]))
    print(f_value.format("Weak Bind", dyld_info.weak_bind[0],   dyld_info.weak_bind[1]))
    print(f_value.format("Lazy Bind", dyld_info.lazy_bind[0],   dyld_info.lazy_bind[1]))
    print(f_value.format("Export",    dyld_info.export_info[0], dyld_info.export_info[1]))

    print("")

    print("Bindings")
    print("--------")
    for idx, binfo in enumerate(dyld_info.bindings):
        print("{:10}: {}".format("Class", str(binfo.binding_class).split(".")[-1]))
        print("{:10}: {}".format("Type", str(binfo.binding_type).split(".")[-1]))
        print("{:10}: {:x}".format("Address", binfo.address))

        if binfo.has_symbol:
            print("{:10}: {}".format("Symbol", binfo.symbol.name))

        if binfo.has_segment:
            print("{:10}: {}".format("Segment", binfo.segment.name))

        if binfo.has_library:
            print("{:10}: {}".format("Library", binfo.library.name))

        if binfo.binding_class == lief.MachO.BINDING_CLASS.LAZY:
            print("{:10}: {}".format("Offset", binfo.original_offset))

        print("")

    print("")

    print("Exports")
    print("-------")
    for idx, einfo in enumerate(dyld_info.exports):
        output = "0x{:08x} - {}".format(einfo.address, einfo.symbol.name)
        if einfo.alias:
            output += " - {}".format(einfo.alias.name)
            if einfo.alias_library:
                output += " from {}".format(einfo.alias_library.name)
        print(output)

        #print("{:10}: {:<10x}".format("Address", einfo.address))
        #print("{:10}: {:<10x}".format("Flags", einfo.flags))

        #if binfo.has_symbol:
        #    print("{:10}: {}".format("Symbol", einfo.symbol.name))

        print("")



    print("")


@exceptions_handler(Exception)
def print_rebase_opcodes(binary):
    print("== Rebase opcodes ==")

    print(binary.dyld_info.show_rebases_opcodes)

    print("")

@exceptions_handler(Exception)
def print_bind_opcodes(binary):
    print("== Bind opcodes ==")

    print(binary.dyld_info.show_bind_opcodes)

    print("")

@exceptions_handler(Exception)
def print_weak_bind_opcodes(binary):
    print("== Weak bind opcodes ==")

    print(binary.dyld_info.show_weak_bind_opcodes)

    print("")

@exceptions_handler(Exception)
def print_lazy_bind_opcodes(binary):
    print("== Lazy bind opcodes ==")

    print(binary.dyld_info.show_lazy_bind_opcodes)

    print("")

@exceptions_handler(Exception)
def print_export_trie(binary):
    print("== Export trie ==")

    print(binary.dyld_info.show_export_trie)

    print("")

@exceptions_handler(Exception)
def print_source_version(binary):
    print("== Source Version ==")

    version = binary.source_version.version

    print("Version: {:d}.{:d}.{:d}.{:d}.{:d}".format(*version))

    print("")


@exceptions_handler(Exception)
def print_version_min(binary):
    print("== Version Min ==")

    version = binary.version_min.version
    sdk     = binary.version_min.sdk

    print("Version: {:d}.{:d}.{:d}".format(*version))
    print("SDK: {:d}.{:d}.{:d}".format(*sdk))

    print("")


@exceptions_handler(Exception)
def print_relocations(binary):
    print("== Relocations ==")

    f_value = "|0x{address:<10x} | {size:<4d} | {type:<15} | {pcrel:<11} | {secseg:<23} | {symbol}"
    f_title = "|{address:<12} | {size:<4} | {type:<15} | {pcrel:<11} | {secseg:<23} | {symbol}"

    print(f_title.format(
        address="Address",
        size="Size",
        type="Type",
        pcrel="PC Relative",
        secseg="Section/Section",
        symbol="Symbol"))


    for reloc in binary.relocations:
        type_str = ""
        if reloc.origin == lief.MachO.RELOCATION_ORIGINS.DYLDINFO:
            type_str = str(lief.MachO.REBASE_TYPES(reloc.type)).split(".")[-1]

        if reloc.origin == lief.MachO.RELOCATION_ORIGINS.RELOC_TABLE:
            if reloc.architecture == MachO.CPU_TYPES.x86:
                type_str = str(MachO.X86_RELOCATION(reloc.type))

            if reloc.architecture == MachO.CPU_TYPES.x86_64:
                type_str = str(MachO.X86_64_RELOCATION(reloc.type))

            if reloc.architecture == MachO.CPU_TYPES.ARM:
                type_str = str(MachO.ARM_RELOCATION(reloc.type))

            if reloc.architecture == MachO.CPU_TYPES.ARM64:
                type_str = str(MachO.ARM64_RELOCATION(reloc.type))

            if reloc.architecture == MachO.CPU_TYPES.POWERPC:
                type_str = str(MachO.PPC_RELOCATION(reloc.type))

            type_str = type_str.split(".")[-1]

        symbol_name = ""
        if reloc.has_symbol:
            symbol_name = reloc.symbol.name

        secseg_name = ""
        if reloc.has_segment and reloc.has_section:
            secseg_name = "{}.{}".format(reloc.segment.name, reloc.section.name)
        else:
            if reloc.has_segment:
                secseg_name = reloc.segment.name

            if reloc.has_section:
                secseg_name = reloc.section.name

        print(f_value.format(
            address=reloc.address,
            size=reloc.size,
            type=type_str,
            pcrel=str(reloc.pc_relative),
            secseg=secseg_name,
            symbol=symbol_name))


    print("")

@exceptions_handler(Exception)
def print_encryption_info(binary):

    format_str = "{:<13} {:<30}"
    format_hex = "{:<13} 0x{:<28x}"
    format_dec = "{:<13} {:<30d}"

    print("== Encryption Info ==")
    cmd = binary.encryption_info

    print(format_hex.format("Offset:", cmd.crypt_offset))
    print(format_hex.format("Size:",   cmd.crypt_size))
    print(format_dec.format("ID:",     cmd.crypt_id))

    print("")


@exceptions_handler(Exception)
def print_ctor(binary):
    print("== Constructors ==\n")

    print("Functions: ({:d})".format(len(binary.ctor_functions)))
    for idx, f in enumerate(binary.ctor_functions):
        print("    [{:d}] {}: 0x{:x}".format(idx, f.name, f.address))


@exceptions_handler(Exception)
def print_unwind_functions(binary):
    print("== Unwind functions ==\n")

    print("Functions: ({:d})".format(len(binary.unwind_functions)))
    for idx, f in enumerate(binary.unwind_functions):
        print("    [{:d}] {}: 0x{:x}".format(idx, f.name, f.address))

@exceptions_handler(Exception)
def print_functions(binary):
    print("== Functions ==\n")

    print("Functions: ({:d})".format(len(binary.functions)))
    for idx, f in enumerate(binary.functions):
        print("    [{:d}] {}: 0x{:x}".format(idx, f.name, f.address))

@exceptions_handler(Exception)
def print_build_version(binary):
    print("== Build Version ==\n")

    build_version = binary.build_version

    print("Platform: {}".format(str(build_version.platform).split(".")[-1]))
    print("Min OS: {:d}.{:d}.{:d}".format(*build_version.minos))
    print("SDK: {:d}.{:d}.{:d}".format(*build_version.sdk))

    tools = build_version.tools
    if len(tools) > 0:
        print("~~ Tools ({}) ~~".format(len(tools)))
        for tool in tools:
            tool_str = str(tool.tool).split(".")[-1]
            print("    {} - {}.{}.{}".format(tool_str, *tool.version))

def main():
    parser = argparse.ArgumentParser(usage='%(prog)s [options] <macho-file>')
    parser.add_argument('-a', '--all',
            action='store_true', dest='show_all',
            help='Show all information')

    parser.add_argument('-c', '--commands',
            action='store_true', dest='show_commands',
            help='Display Commands')

    parser.add_argument('-H', '--header',
            action='store_true', dest='show_header',
            help='Display header')

    parser.add_argument('-L', '--libraries',
            action='store_true', dest='show_libraries',
            help='Display Imported Libraries')

    parser.add_argument('-l', '--segments',
            action='store_true', dest='show_segments',
            help='Display Segments')

    parser.add_argument('-r', '--relocations',
            action='store_true', dest='show_relocs',
            help='Display the relocations (if present)')

    parser.add_argument('-s', '--symbols',
            action='store_true', dest='show_symbols',
            help='Display Symbols')

    parser.add_argument('-S', '--sections',
            action='store_true', dest='show_sections',
            help='Display Sections')

    parser.add_argument('--uuid',
            action='store_true', dest='show_uuid',
            help='Display the UUID command')

    parser.add_argument('--main',
            action='store_true', dest='show_main',
            help='Display the Main command')

    parser.add_argument('--dylinker',
            action='store_true', dest='show_dylinker',
            help='Display the Dylinker command')

    parser.add_argument('--dyldinfo',
            action='store_true', dest='show_dyldinfo',
            help='Display the DyldInfo command')

    parser.add_argument('--function-starts',
            action='store_true', dest='show_function_starts',
            help='Display the FunctionStarts command')

    parser.add_argument('--rebase-opcodes',
            action='store_true', dest='show_rebase_opcodes',
            help='Display the "Rebase" opcodes')

    parser.add_argument('--source-version',
            action='store_true', dest='show_source_version',
            help="Display the 'Source Version' command")

    parser.add_argument('--version-min',
            action='store_true', dest='show_version_min',
            help="Display the 'Version Min' command")

    parser.add_argument('--thread-command',
            action='store_true', dest='show_thread_command',
            help="Display the 'Thread Command' command")

    parser.add_argument('--rpath-command',
            action='store_true', dest='show_rpath_command',
            help="Display the 'Rpath Command' command")

    parser.add_argument('--symbol-command',
            action='store_true', dest='show_symbol_command',
            help="Display the 'Symbol Command' command")

    parser.add_argument('--dynamic-symbol-command',
            action='store_true', dest='show_dynamic_symbol_command',
            help="Display the 'Symbol Command' command")

    parser.add_argument('--data-in-code',
            action='store_true', dest='show_data_in_code',
            help="Display the 'Data In Code' command")

    parser.add_argument('--segment-split-info',
            action='store_true', dest='show_segment_split_info',
            help="Display the 'Segment Split Info' command")

    parser.add_argument('--sub-framework',
            action='store_true', dest='show_sub_framework',
            help="Display the 'Sub Framework' command")

    parser.add_argument('--dyld-environment',
            action='store_true', dest='show_dyld_env',
            help="Display the 'Dyld Environment' command")

    parser.add_argument('--encryption-info',
            action='store_true', dest='show_encrypt_info',
            help="Display the 'Encryption Info' command")

    parser.add_argument('--bind-opcodes',
            action='store_true', dest='show_bind_opcodes',
            help='Display the "Bind" opcodes')

    parser.add_argument('--weak-bind-opcodes',
            action='store_true', dest='show_weak_bind_opcodes',
            help='Display the "Weak Bind" opcodes')

    parser.add_argument('--lazy-bind-opcodes',
            action='store_true', dest='show_lazy_bind_opcodes',
            help='Display the "Lazy Bind" opcodes')

    parser.add_argument('--export-trie',
            action='store_true', dest='show_export_trie',
            help='Display the export trie')

    parser.add_argument('--opcodes',
            action='store_true', dest='show_opcodes',
            help='Display the bind and rebase opcodes')

    parser.add_argument('--ctor',
            action='store_true', dest='show_ctor',
            help='Constructor functions')

    parser.add_argument('--unwind-functions',
            action='store_true', dest='show_ufunctions',
            help='Functions from unwind info')

    parser.add_argument('--functions',
            action='store_true', dest='show_functions',
            help='All functions found in the binary')

    parser.add_argument('--build-version',
            action='store_true', dest='show_build_version',
            help='Show build version')

    parser.add_argument("binary",
            metavar="<macho-file>",
            help='Target Mach-O File')

    # Logging setup
    logger_group = parser.add_argument_group('Logger')
    verbosity = logger_group.add_mutually_exclusive_group()

    verbosity.add_argument('--debug',
            dest='main_verbosity',
            action='store_const',
            const=lief.logging.LOGGING_LEVEL.DEBUG)

    verbosity.add_argument('--trace',
            dest='main_verbosity',
            action='store_const',
            const=lief.logging.LOGGING_LEVEL.TRACE)

    verbosity.add_argument('--info',
            dest='main_verbosity',
            action='store_const',
            const=lief.logging.LOGGING_LEVEL.INFO)

    verbosity.add_argument('--warn',
            dest='main_verbosity',
            action='store_const',
            const=lief.logging.LOGGING_LEVEL.WARNING)

    verbosity.add_argument('--err',
            dest='main_verbosity',
            action='store_const',
            const=lief.logging.LOGGING_LEVEL.ERROR)

    verbosity.add_argument('--critical',
            dest='main_verbosity',
            action='store_const',
            const=lief.logging.LOGGING_LEVEL.CRITICAL)

    parser.set_defaults(main_verbosity=lief.logging.LOGGING_LEVEL.WARNING)

    args = parser.parse_args()

    lief.logging.set_level(args.main_verbosity)

    binaries = None
    try:
        binaries = MachO.parse(args.binary)
    except lief.exception as e:
        print(e)
        sys.exit(1)

    if len(binaries) > 1:
        print("Fat Mach-O: {:d} binaries".format(len(binaries)))

    for binary in binaries:
        print_information(binary)

        if args.show_header or args.show_all:
            print_header(binary)

        if args.show_commands or args.show_all:
            print_commands(binary)

        if args.show_libraries or args.show_all:
            print_libraries(binary)

        if args.show_segments or args.show_all:
            print_segments(binary)

        if args.show_sections or args.show_all:
            print_sections(binary)

        if args.show_symbols or args.show_all:
            print_symbols(binary)

        if (args.show_uuid or args.show_all) and binary.has_uuid:
            print_uuid(binary)

        if (args.show_main or args.show_all) and binary.has_main_command:
            print_main_command(binary)

        if (args.show_dylinker or args.show_all) and binary.has_dylinker:
            print_dylinker(binary)

        if (args.show_dyldinfo or args.show_all) and binary.has_dyld_info:
            print_dyld_info(binary)

        if (args.show_function_starts or args.show_all) and binary.has_function_starts:
            print_function_starts(binary)

        if (args.show_source_version or args.show_all) and binary.has_source_version:
            print_source_version(binary)

        if (args.show_version_min or args.show_all) and binary.has_version_min:
            print_version_min(binary)

        if (args.show_relocs or args.show_all) and len(binary.relocations) > 0:
            print_relocations(binary)

        if (args.show_thread_command or args.show_all) and binary.has_thread_command:
            print_thread_command(binary)

        if (args.show_rpath_command or args.show_all) and binary.has_rpath:
            print_rpath_command(binary)

        if (args.show_symbol_command or args.show_all) and binary.has_symbol_command:
            print_symbol_command(binary)

        if (args.show_dynamic_symbol_command or args.show_all) and binary.has_dynamic_symbol_command:
            print_dynamic_symbol_command(binary)

        if (args.show_data_in_code or args.show_all) and binary.has_data_in_code:
            print_data_in_code(binary)

        if (args.show_segment_split_info or args.show_all) and binary.has_segment_split_info:
            print_segment_split_info(binary)

        if (args.show_sub_framework or args.show_all) and binary.has_sub_framework:
            print_sub_framework(binary)

        if (args.show_dyld_env or args.show_all) and binary.has_dyld_environment:
            print_dyld_environment(binary)

        if (args.show_encrypt_info or args.show_all) and binary.has_encryption_info:
            print_encryption_info(binary)

        if (args.show_rpath_command or args.show_all) and binary.has_rpath:
            print_rpath_command(binary)

        if (args.show_rebase_opcodes or args.show_opcodes) and binary.has_dyld_info:
            print_rebase_opcodes(binary)

        if (args.show_bind_opcodes  or args.show_opcodes) and binary.has_dyld_info:
            print_bind_opcodes(binary)

        if (args.show_weak_bind_opcodes or args.show_opcodes) and binary.has_dyld_info:
            print_weak_bind_opcodes(binary)

        if (args.show_lazy_bind_opcodes or args.show_opcodes) and binary.has_dyld_info:
            print_lazy_bind_opcodes(binary)

        if (args.show_export_trie or args.show_opcodes) and binary.has_dyld_info:
            print_export_trie(binary)

        if args.show_ctor or args.show_all:
            print_ctor(binary)

        if args.show_ufunctions or args.show_all:
            print_unwind_functions(binary)

        if args.show_functions or args.show_all:
            print_functions(binary)

        if (args.show_build_version or args.show_all) and binary.has_build_version:
            print_build_version(binary)

    sys.exit(EXIT_STATUS)


if __name__ == "__main__":
    main()
