mach_object 0.1.17

Mach-O File Format Parser for Rust
Documentation
#![allow(non_camel_case_types, clippy::many_single_char_names, clippy::large_enum_variant)]

use std::fmt;
use std::io::{BufRead, Cursor, Read};
use std::ops::Deref;
use std::rc::Rc;
use std::str::FromStr;

use byteorder::{ByteOrder, ReadBytesExt};
use uuid::Uuid;

use crate::{
    consts::*,
    errors::{
        Error::{self, *},
        Result,
    },
    loader::MachHeader,
};

/// The encoded version.
///
///  X.Y.Z is encoded in nibbles xxxx.yy.zz
///
#[derive(Debug, Default, Copy, Clone, PartialEq, Eq)]
pub struct VersionTag(u32);

impl VersionTag {
    pub fn major(self) -> u32 {
        self.0 >> 16
    }

    pub fn minor(self) -> u32 {
        (self.0 >> 8) & 0xFF
    }

    pub fn release(self) -> u32 {
        self.0 & 0xFF
    }
}

impl From<VersionTag> for u32 {
    fn from(v: VersionTag) -> Self {
        v.0
    }
}

impl FromStr for VersionTag {
    type Err = Error;

    fn from_str(s: &str) -> Result<Self> {
        let mut parts = s.split('.');

        let major = match parts.next() {
            Some(s) if !s.is_empty() => s.parse()?,
            _ => 0,
        };
        let minor = match parts.next() {
            Some(s) if !s.is_empty() => s.parse()?,
            _ => 0,
        };
        let release = match parts.next() {
            Some(s) if !s.is_empty() => s.parse()?,
            _ => 0,
        };

        Ok(VersionTag((major << 16) + (minor << 8) + release))
    }
}

impl fmt::Display for VersionTag {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        if self.release() == 0 {
            write!(f, "{}.{}", self.major(), self.minor())
        } else {
            write!(f, "{}.{}.{}", self.major(), self.minor(), self.release())
        }
    }
}

/// The packed version.
///
/// A.B.C.D.E packed as a24.b10.c10.d10.e10
///
#[derive(Debug, Default, Copy, Clone, PartialEq, Eq)]
pub struct SourceVersionTag(u64);

impl From<SourceVersionTag> for u64 {
    fn from(v: SourceVersionTag) -> Self {
        v.0
    }
}

impl From<SourceVersionTag> for (u32, u32, u32, u32, u32) {
    fn from(v: SourceVersionTag) -> (u32, u32, u32, u32, u32) {
        (
            ((v.0 >> 40) & 0xFFF) as u32,
            ((v.0 >> 30) & 0x3FF) as u32,
            ((v.0 >> 20) & 0x3FF) as u32,
            ((v.0 >> 10) & 0x3FF) as u32,
            (v.0 & 0x3FF) as u32,
        )
    }
}

impl fmt::Display for SourceVersionTag {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        let (a, b, c, d, e) = Self::into(*self);

        if e != 0 {
            write!(f, "{}.{}.{}.{}.{}", a, b, c, d, e)
        } else if d != 0 {
            write!(f, "{}.{}.{}.{}", a, b, c, d)
        } else if c != 0 {
            write!(f, "{}.{}.{}", a, b, c)
        } else {
            write!(f, "{}.{}", a, b)
        }
    }
}

/// The min OS version on which this binary was built to run.
///
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
pub enum BuildTarget {
    MacOsX,
    IPhoneOs,
    WatchOs,
    TvOs,
}

impl From<u32> for BuildTarget {
    fn from(cmd: u32) -> Self {
        match cmd {
            LC_VERSION_MIN_MACOSX => BuildTarget::MacOsX,
            LC_VERSION_MIN_IPHONEOS => BuildTarget::IPhoneOs,
            LC_VERSION_MIN_WATCHOS => BuildTarget::WatchOs,
            LC_VERSION_MIN_TVOS => BuildTarget::TvOs,
            _ => unreachable!(),
        }
    }
}
impl From<BuildTarget> for u32 {
    fn from(t: BuildTarget) -> u32 {
        match t {
            BuildTarget::MacOsX => LC_VERSION_MIN_MACOSX,
            BuildTarget::IPhoneOs => LC_VERSION_MIN_IPHONEOS,
            BuildTarget::WatchOs => LC_VERSION_MIN_WATCHOS,
            BuildTarget::TvOs => LC_VERSION_MIN_TVOS,
        }
    }
}

/// A variable length string in a load command is represented by an `LcString` structure.
///
/// The strings are stored just after the load command structure and
/// the offset is from the start of the load command structure.  The size
/// of the string is reflected in the cmdsize field of the load command.
/// Once again any padded bytes to bring the cmdsize field to a multiple
/// of 4 bytes must be zero.
///
#[derive(Debug, Default, Clone, PartialEq, Eq)]
pub struct LcString(pub usize, pub String);

impl LcString {
    pub fn size(&self) -> usize {
        self.0
    }

    pub fn as_str(&self) -> &str {
        self.1.as_str()
    }
}

impl fmt::Display for LcString {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        write!(f, "{}", self.1)
    }
}

impl Deref for LcString {
    type Target = str;

    fn deref(&self) -> &Self::Target {
        self.1.as_str()
    }
}

/// Fixed virtual memory shared libraries are identified by two things.
///
/// The target pathname (the name of the library as found for execution),
/// and the minor version number.
/// The address of where the headers are loaded is in `header_addr`.
/// (THIS IS OBSOLETE and no longer supported).
#[derive(Debug, Default, Clone, PartialEq, Eq)]
pub struct FvmLib {
    /// library's target pathname
    pub name: LcString,
    /// library's minor version number
    pub minor_version: u32,
    /// library's header address
    pub header_addr: u32,
}

/// Dynamically linked shared libraries are identified by two things.
///
/// The pathname (the name of the library as found for execution), and the
/// compatibility version number.  The pathname must match and the compatibility
/// number in the user of the library must be greater than or equal to the
/// library being used.  The time stamp is used to record the time a library was
/// built and copied into user so it can be use to determined if the library used
/// at runtime is exactly the same as used to built the program.
#[derive(Debug, Default, Clone, PartialEq, Eq)]
pub struct DyLib {
    /// library's path name
    pub name: LcString,
    /// library's build time stamp
    pub timestamp: u32,
    /// library's current version number
    pub current_version: VersionTag,
    /// library's compatibility vers number
    pub compatibility_version: VersionTag,
}

/// a table of contents entry
pub struct DyLibTocEntry {
    /// the defined external symbol (index into the symbol table)
    pub symbol_index: u32,
    /// index into the module table this symbol is defined in
    pub module_index: u32,
}

/// a module table entry
pub struct DyLibModule {
    /// the module name (index into string table)
    pub module_name: u32,

    /// index into externally defined symbols
    pub iextdefsym: u32,
    /// number of externally defined symbols
    pub nextdefsym: u32,
    /// index into reference symbol table
    pub irefsym: u32,
    /// number of reference symbol table entries
    pub nrefsym: u32,
    /// index into symbols for local symbols
    pub ilocalsym: u32,
    /// number of local symbols
    pub nlocalsym: u32,

    /// index into external relocation entries
    pub iextrel: u32,
    /// number of external relocation entries
    pub nextrel: u32,

    /// low 16 bits are the index into the init section,
    /// high 16 bits are the index into the term section
    pub iinit_iterm: u32,
    /// low 16 bits are the number of init section entries,
    /// high 16 bits are the number of term section entries
    pub ninit_nterm: u32,

    /// for this module address of the start of the (__OBJC,__module_info) section
    pub objc_module_info_addr: u32,
    /// for this module size of the (__OBJC,__module_info) section
    pub objc_module_info_size: usize,
}

/// The `LinkEditData` contains the offsets and sizes of a blob
/// of data in the __LINKEDIT segment.
///
#[derive(Debug, Default, Clone, PartialEq, Eq)]
pub struct LinkEditData {
    /// file offset of data in __LINKEDIT segment
    pub off: u32,
    /// file size of data in __LINKEDIT segment
    pub size: u32,
}

#[derive(Debug, Clone, Copy, PartialEq)]
pub enum X86ThreadFlavor {
    x86_THREAD_STATE32 = 1,
    x86_FLOAT_STATE32 = 2,
    x86_EXCEPTION_STATE32 = 3,
    x86_THREAD_STATE64 = 4,
    x86_FLOAT_STATE64 = 5,
    x86_EXCEPTION_STATE64 = 6,
    x86_THREAD_STATE = 7,
    x86_FLOAT_STATE = 8,
    x86_EXCEPTION_STATE = 9,
    x86_DEBUG_STATE32 = 10,
    x86_DEBUG_STATE64 = 11,
    x86_DEBUG_STATE = 12,
}

#[derive(Debug, Clone, Copy, PartialEq)]
pub enum ARMThreadFlavors {
    ARM_THREAD_STATE = 1,
    ARM_VFP_STATE = 2,
    ARM_EXCEPTION_STATE = 3,
    ARM_DEBUG_STATE = 4,
    ARN_THREAD_STATE_NONE = 5,
    ARM_THREAD_STATE64 = 6,
    ARM_EXCEPTION_STATE64 = 7,
}

#[derive(Debug, Clone, Copy, PartialEq)]
pub enum PPCThreadFlavors {
    PPC_THREAD_STATE = 1,
    PPC_FLOAT_STATE = 2,
    PPC_EXCEPTION_STATE = 3,
    PPC_VECTOR_STATE = 4,
    PPC_THREAD_STATE64 = 5,
    PPC_EXCEPTION_STATE64 = 6,
    PPC_THREAD_STATE_NONE = 7,
}

#[derive(Debug, Clone, PartialEq)]
pub enum ThreadState {
    I386 {
        __eax: u32,
        __ebx: u32,
        __ecx: u32,
        __edx: u32,
        __edi: u32,
        __esi: u32,
        __ebp: u32,
        __esp: u32,
        __ss: u32,
        __eflags: u32,
        __eip: u32,
        __cs: u32,
        __ds: u32,
        __es: u32,
        __fs: u32,
        __gs: u32,
    },
    X86_64 {
        __rax: u64,
        __rbx: u64,
        __rcx: u64,
        __rdx: u64,
        __rdi: u64,
        __rsi: u64,
        __rbp: u64,
        __rsp: u64,
        __r8: u64,
        __r9: u64,
        __r10: u64,
        __r11: u64,
        __r12: u64,
        __r13: u64,
        __r14: u64,
        __r15: u64,
        __rip: u64,
        __rflags: u64,
        __cs: u64,
        __fs: u64,
        __gs: u64,
    },
    Arm {
        /// General purpose register r0-r12
        __r: [u32; 13],
        /// Stack pointer r13
        __sp: u32,
        /// Link register r14
        __lr: u32,
        /// Program counter r15
        __pc: u32,
        /// Current program status register
        __cpsr: u32,
    },
    Arm64 {
        /// General purpose registers x0-x28
        __x: [u64; 29],
        /// Frame pointer x29
        __fp: u64,
        /// Link register x30
        __lr: u64,
        /// Stack pointer x31
        __sp: u64,
        /// Program counter
        __pc: u64,
        /// Current program status register
        __cpsr: u32,
        /// Same size for 32-bit or 64-bit clients
        __pad: u32,
    },
    PowerPC {
        __srr: [u32; 2],
        __r: [u32; 32],
        __ct: u32,
        __xer: u32,
        __lr: u32,
        __ctr: u32,
        __mq: u32,
        __vrsave: u32,
    },
    PowerPC64 {
        __srr: [u64; 2],
        __r: [u64; 32],
        __cr: u32,
        __xer: u64,
        __lr: u64,
        __ctr: u64,
        __vrsave: u32,
    },
}

#[derive(Debug, Clone, PartialEq)]
pub enum Platform {
    macOS,
    iOS,
    tvOS,
    watchOS,
    bridgeOS,
    Other(u32),
}

#[derive(Debug, Clone, PartialEq)]
pub enum Tool {
    Clang,
    Swift,
    LD,
    Other(u32),
}

/// The build_version_command contains the min OS version on which this
/// binary was built to run for its platform.  The list of known platforms and
/// tool values following it.
#[derive(Debug, Clone, PartialEq)]
pub struct BuildVersion {
    pub platform: u32,
    /// X.Y.Z is encoded in nibbles xxxx.yy.zz
    pub minos: VersionTag,
    /// X.Y.Z is encoded in nibbles xxxx.yy.zz
    pub sdk: VersionTag,
    /// build tools
    pub build_tools: Vec<BuildTool>,
}

impl BuildVersion {
    pub fn platform(&self) -> Platform {
        match self.platform {
            PLATFORM_MACOS => Platform::macOS,
            PLATFORM_IOS => Platform::iOS,
            PLATFORM_TVOS => Platform::tvOS,
            PLATFORM_WATCHOS => Platform::watchOS,
            PLATFORM_BRIDGEOS => Platform::bridgeOS,
            n => Platform::Other(n),
        }
    }
}

#[derive(Debug, Clone, PartialEq)]
pub struct BuildTool {
    pub tool: u32,
    pub version: VersionTag,
}

impl BuildTool {
    pub fn tool(&self) -> Tool {
        match self.tool {
            TOOL_CLANG => Tool::Clang,
            TOOL_SWIFT => Tool::Swift,
            TOOL_LD => Tool::LD,
            n => Tool::Other(n),
        }
    }
}

/// The load commands directly follow the mach header.
///
#[derive(Debug, Clone)]
pub enum LoadCommand {
    /// The segment load command indicates that a part of this file is to be
    /// mapped into the task's address space.
    ///
    /// The size of this segment in memory, vmsize, maybe equal to or
    /// larger than the amount to map from this file, filesize.
    /// The file is mapped starting at fileoff to the beginning of
    /// the segment in memory, vmaddr.  The rest of the memory of the segment,
    /// if any, is allocated zero fill on demand.  The segment's maximum virtual
    /// memory protection and initial virtual memory protection are specified
    /// by the maxprot and initprot fields.  If the segment has sections then the
    /// section structures directly follow the segment command and their size is
    /// reflected in cmdsize.
    ///
    Segment {
        /// segment name
        segname: String,
        /// memory address of this segment
        vmaddr: usize,
        /// memory size of this segment
        vmsize: usize,
        /// file offset of this segment
        fileoff: usize,
        /// amount to map from the file
        filesize: usize,
        /// maximum VM protection
        maxprot: vm_prot_t,
        /// initial VM protection
        initprot: vm_prot_t,
        /// flags
        flags: SegmentFlags,
        /// sections
        sections: Vec<Rc<Section>>,
    },
    /// The 64-bit segment load command indicates that a part of this file is to be
    /// mapped into a 64-bit task's address space.
    ///
    /// If the 64-bit segment has sections then section_64 structures directly follow
    /// the 64-bit segment command and their size is reflected in cmdsize.
    ///
    Segment64 {
        /// segment name
        segname: String,
        /// memory address of this segment
        vmaddr: usize,
        /// memory size of this segment
        vmsize: usize,
        /// file offset of this segment
        fileoff: usize,
        /// amount to map from the file
        filesize: usize,
        /// maximum VM protection
        maxprot: vm_prot_t,
        /// initial VM protection
        initprot: vm_prot_t,
        /// flags
        flags: SegmentFlags,
        /// sections
        sections: Vec<Rc<Section>>,
    },

    // A fixed virtual shared library (filetype == MH_FVMLIB in the mach header)
    // contains a fvmlib_command (cmd == LC_IDFVMLIB) to identify the library.
    //
    // An object that uses a fixed virtual shared library also contains a
    // fvmlib_command (cmd == LC_LOADFVMLIB) for each library it uses.
    // (THIS IS OBSOLETE and no longer supported).
    //
    /// fixed VM shared library identification
    IdFvmLib(FvmLib),
    /// load a specified fixed VM shared library
    LoadFvmLib(FvmLib),

    // A dynamically linked shared library (filetype == MH_DYLIB in the mach header)
    // contains a dylib_command (cmd == LC_ID_DYLIB) to identify the library.
    //
    // An object that uses a dynamically linked shared library also contains a
    // dylib_command (cmd == LC_LOAD_DYLIB, LC_LOAD_WEAK_DYLIB, or
    // LC_REEXPORT_DYLIB) for each library it uses.
    //
    /// dynamically linked shared lib ident
    IdDyLib(DyLib),
    /// load a dynamically linked shared library
    LoadDyLib(DyLib),
    /// load a dynamically linked shared library
    /// that is allowed to be missing (all symbols are weak imported).
    LoadWeakDyLib(DyLib),
    /// load and re-export dylib
    ReexportDyLib(DyLib),
    /// load upward dylib
    LoadUpwardDylib(DyLib),
    /// delay load of dylib until first use
    LazyLoadDylib(DyLib),
    /// add a runtime search path for shared libraries
    Rpath(String),

    // A program that uses a dynamic linker contains a dylinker_command to identify
    // the name of the dynamic linker (LC_LOAD_DYLINKER).  And a dynamic linker
    // contains a dylinker_command to identify the dynamic linker (LC_ID_DYLINKER).
    // A file can have at most one of these.
    // This struct is also used for the LC_DYLD_ENVIRONMENT load command and
    // contains string for dyld to treat like environment variable.
    //
    /// dynamic linker identification
    IdDyLinker(LcString),
    /// load a dynamic linker
    LoadDyLinker(LcString),
    /// string for dyld to treat like environment variable
    DyLdEnv(LcString),

    /// The symtab_command contains the offsets and sizes of the link-edit 4.3BSD
    /// "stab" style symbol table information as described in the header files
    /// <nlist.h> and <stab.h>.
    ///
    SymTab {
        /// symbol table offset
        symoff: u32,
        /// number of symbol table entries
        nsyms: u32,
        /// string table offset
        stroff: u32,
        /// string table size in bytes
        strsize: u32,
    },

    /// This is the second set of the symbolic information which is used to support
    /// the data structures for the dynamically link editor.
    ///
    /// The original set of symbolic information in the symtab_command which contains
    /// the symbol and string tables must also be present when this load command is
    /// present.  When this load command is present the symbol table is organized
    /// into three groups of symbols:
    ///  local symbols (static and debugging symbols) - grouped by module
    ///  defined external symbols - grouped by module (sorted by name if not lib)
    ///  undefined external symbols (sorted by name if MH_BINDATLOAD is not set,
    ///                      and in order the were seen by the static
    ///                  linker if MH_BINDATLOAD is set)
    /// In this load command there are offsets and counts to each of the three groups
    /// of symbols.
    ///
    /// This load command contains a the offsets and sizes of the following new
    /// symbolic information tables:
    ///  table of contents
    ///  module table
    ///  reference symbol table
    ///  indirect symbol table
    /// The first three tables above (the table of contents, module table and
    /// reference symbol table) are only present if the file is a dynamically linked
    /// shared library.  For executable and object modules, which are files
    /// containing only one module, the information that would be in these three
    /// tables is determined as follows:
    ///  table of contents - the defined external symbols are sorted by name
    ///  module table - the file contains only one module so everything in the
    ///             file is part of the module.
    ///  reference symbol table - is the defined and undefined external symbols
    ///
    /// For dynamically linked shared library files this load command also contains
    /// offsets and sizes to the pool of relocation entries for all sections
    /// separated into two groups:
    ///  external relocation entries
    ///  local relocation entries
    /// For executable and object modules the relocation entries continue to hang
    /// off the section structures.
    ///
    DySymTab {
        // The symbols indicated by symoff and nsyms of the LC_SYMTAB load command
        // are grouped into the following three groups:
        //    local symbols (further grouped by the module they are from)
        //    defined external symbols (further grouped by the module they are from)
        //    undefined symbols
        //
        // The local symbols are used only for debugging.  The dynamic binding
        // process may have to use them to indicate to the debugger the local
        // symbols for a module that is being bound.
        //
        // The last two groups are used by the dynamic binding process to do the
        // binding (indirectly through the module table and the reference symbol
        // table when this is a dynamically linked shared library file).
        //
        /// index to local symbols
        ilocalsym: u32,
        /// number of local symbols
        nlocalsym: u32,

        /// index to externally defined symbols
        iextdefsym: u32,
        /// number of externally defined symbols
        nextdefsym: u32,

        /// index to undefined symbols
        iundefsym: u32,
        /// number of undefined symbols
        nundefsym: u32,

        // For the for the dynamic binding process to find which module a symbol
        // is defined in the table of contents is used (analogous to the ranlib
        // structure in an archive) which maps defined external symbols to modules
        // they are defined in.  This exists only in a dynamically linked shared
        // library file.  For executable and object modules the defined external
        // symbols are sorted by name and is use as the table of contents.
        //
        /// file offset to table of contents
        tocoff: u32,
        /// number of entries in table of contents
        ntoc: u32,

        // To support dynamic binding of "modules" (whole object files) the symbol
        // table must reflect the modules that the file was created from.  This is
        // done by having a module table that has indexes and counts into the merged
        // tables for each module.  The module structure that these two entries
        // refer to is described below.  This exists only in a dynamically linked
        // shared library file.  For executable and object modules the file only
        // contains one module so everything in the file belongs to the module.
        //
        /// file offset to module table
        modtaboff: u32,
        /// number of module table entries
        nmodtab: u32,

        // To support dynamic module binding the module structure for each module
        // indicates the external references (defined and undefined) each module
        // makes.  For each module there is an offset and a count into the
        // reference symbol table for the symbols that the module references.
        // This exists only in a dynamically linked shared library file.  For
        // executable and object modules the defined external symbols and the
        // undefined external symbols indicates the external references.
        //
        /// offset to referenced symbol table
        extrefsymoff: u32,
        /// number of referenced symbol table entries
        nextrefsyms: u32,

        // The sections that contain "symbol pointers" and "routine stubs" have
        // indexes and (implied counts based on the size of the section and fixed
        // size of the entry) into the "indirect symbol" table for each pointer
        // and stub.  For every section of these two types the index into the
        // indirect symbol table is stored in the section header in the field
        // reserved1.  An indirect symbol table entry is simply a 32bit index into
        // the symbol table to the symbol that the pointer or stub is referring to.
        // The indirect symbol table is ordered to match the entries in the section.
        //
        /// file offset to the indirect symbol table
        indirectsymoff: u32,
        /// number of indirect symbol table entries
        nindirectsyms: u32,

        // To support relocating an individual module in a library file quickly the
        // external relocation entries for each module in the library need to be
        // accessed efficiently.  Since the relocation entries can't be accessed
        // through the section headers for a library file they are separated into
        // groups of local and external entries further grouped by module.  In this
        // case the presents of this load command who's extreloff, nextrel,
        // locreloff and nlocrel fields are non-zero indicates that the relocation
        // entries of non-merged sections are not referenced through the section
        // structures (and the reloff and nreloc fields in the section headers are
        // set to zero).
        //
        // Since the relocation entries are not accessed through the section headers
        // this requires the r_address field to be something other than a section
        // offset to identify the item to be relocated.  In this case r_address is
        // set to the offset from the vmaddr of the first LC_SEGMENT command.
        // For MH_SPLIT_SEGS images r_address is set to the the offset from the
        // vmaddr of the first read-write LC_SEGMENT command.
        //
        // The relocation entries are grouped by module and the module table
        // entries have indexes and counts into them for the group of external
        // relocation entries for that the module.
        //
        // For sections that are merged across modules there must not be any
        // remaining external relocation entries for them (for merged sections
        // remaining relocation entries must be local).
        //
        /// offset to external relocation entries
        extreloff: u32,
        /// number of external relocation entries
        nextrel: u32,

        // All the local relocation entries are grouped together (they are not
        // grouped by their module since they are only used if the object is moved
        // from it staticly link edited address).
        //
        /// offset to local relocation entries
        locreloff: u32,
        /// number of local relocation entries
        nlocrel: u32,
    },

    /// The uuid load command contains a single 128-bit unique random number that
    /// identifies an object produced by the static link editor.
    ///
    Uuid(Uuid),

    // The `LinkEditData` contains the offsets and sizes of a blob
    // of data in the __LINKEDIT segment.
    //
    /// local of code signature
    CodeSignature(LinkEditData),
    /// local of info to split segments
    SegmentSplitInfo(LinkEditData),
    /// compressed table of function start addresses
    FunctionStarts(LinkEditData),
    /// table of non-instructions in __text
    DataInCode(LinkEditData),
    /// Code signing DRs copied from linked dylibs
    DylibCodeSignDrs(LinkEditData),
    /// optimization hints in MH_OBJECT files
    LinkerOptimizationHint(LinkEditData),
    /// used with linkedit_data_command, payload is trie
    DyldExportsTrie(LinkEditData),
    /// used with linkedit_data_command
    DyldChainedFixups(LinkEditData),

    /// The version_min_command contains the min OS version on which this
    /// binary was built to run.
    ///
    VersionMin {
        target: BuildTarget,
        version: VersionTag,
        sdk: VersionTag,
    },

    /// The dyld_info_command contains the file offsets and sizes of
    /// the new compressed form of the information dyld needs to
    /// load the image.  This information is used by dyld on Mac OS X
    /// 10.6 and later.  All information pointed to by this command
    /// is encoded using byte streams, so no endian swapping is needed
    /// to interpret it.
    ///
    DyldInfo {
        // Dyld rebases an image whenever dyld loads it at an address different
        // from its preferred address.  The rebase information is a stream
        // of byte sized opcodes whose symbolic names start with REBASE_OPCODE_.
        // Conceptually the rebase information is a table of tuples:
        //    <seg-index, seg-offset, type>
        // The opcodes are a compressed way to encode the table by only
        // encoding when a column changes.  In addition simple patterns
        // like "every n'th offset for m times" can be encoded in a few
        // bytes.
        //
        /// file offset to rebase info
        rebase_off: u32,
        /// size of rebase info
        rebase_size: u32,

        // Dyld binds an image during the loading process, if the image
        // requires any pointers to be initialized to symbols in other images.
        // The bind information is a stream of byte sized
        // opcodes whose symbolic names start with BIND_OPCODE_.
        // Conceptually the bind information is a table of tuples:
        //    <seg-index, seg-offset, type, symbol-library-ordinal, symbol-name, addend>
        // The opcodes are a compressed way to encode the table by only
        // encoding when a column changes.  In addition simple patterns
        // like for runs of pointers initialzed to the same value can be
        // encoded in a few bytes.
        //
        /// file offset to binding info
        bind_off: u32,
        /// size of binding info
        bind_size: u32,

        // Some C++ programs require dyld to unique symbols so that all
        // images in the process use the same copy of some code/data.
        // This step is done after binding. The content of the weak_bind
        // info is an opcode stream like the bind_info.  But it is sorted
        // alphabetically by symbol name.  This enable dyld to walk
        // all images with weak binding information in order and look
        // for collisions.  If there are no collisions, dyld does
        // no updating.  That means that some fixups are also encoded
        // in the bind_info.  For instance, all calls to "operator new"
        // are first bound to libstdc++.dylib using the information
        // in bind_info.  Then if some image overrides operator new
        // that is detected when the weak_bind information is processed
        // and the call to operator new is then rebound.
        //
        /// file offset to weak binding info
        weak_bind_off: u32,
        /// size of weak binding info
        weak_bind_size: u32,

        // Some uses of external symbols do not need to be bound immediately.
        // Instead they can be lazily bound on first use.  The lazy_bind
        // are contains a stream of BIND opcodes to bind all lazy symbols.
        // Normal use is that dyld ignores the lazy_bind section when
        // loading an image.  Instead the static linker arranged for the
        // lazy pointer to initially point to a helper function which
        // pushes the offset into the lazy_bind area for the symbol
        // needing to be bound, then jumps to dyld which simply adds
        // the offset to lazy_bind_off to get the information on what
        // to bind.
        //
        /// file offset to lazy binding info
        lazy_bind_off: u32,
        /// size of lazy binding infs
        lazy_bind_size: u32,

        // The symbols exported by a dylib are encoded in a trie.  This
        // is a compact representation that factors out common prefixes.
        // It also reduces LINKEDIT pages in RAM because it encodes all
        // information (name, address, flags) in one small, contiguous range.
        // The export area is a stream of nodes.  The first node sequentially
        // is the start node for the trie.
        //
        // Nodes for a symbol start with a uleb128 that is the length of
        // the exported symbol information for the string so far.
        // If there is no exported symbol, the node starts with a zero byte.
        // If there is exported info, it follows the length.
        //
        // First is a uleb128 containing flags. Normally, it is followed by
        // a uleb128 encoded offset which is location of the content named
        // by the symbol from the mach_header for the image.  If the flags
        // is EXPORT_SYMBOL_FLAGS_REEXPORT, then following the flags is
        // a uleb128 encoded library ordinal, then a zero terminated
        // UTF8 string.  If the string is zero length, then the symbol
        // is re-export from the specified dylib with the same name.
        // If the flags is EXPORT_SYMBOL_FLAGS_STUB_AND_RESOLVER, then following
        // the flags is two uleb128s: the stub offset and the resolver offset.
        // The stub is used by non-lazy pointers.  The resolver is used
        // by lazy pointers and must be called to get the actual address to use.
        //
        // After the optional exported symbol information is a byte of
        // how many edges (0-255) that this node has leaving it,
        // followed by each edge.
        // Each edge is a zero terminated UTF8 of the addition chars
        // in the symbol, followed by a uleb128 offset for the node that
        // edge points to.
        //
        //
        /// file offset to lazy binding info
        export_off: u32,
        /// size of lazy binding infs
        export_size: u32,
    },

    /// The entry_point_command is a replacement for thread_command.
    /// It is used for main executables to specify the location (file offset)
    /// of main().  If -stack_size was used at link time, the stacksize
    /// field will contain the stack size need for the main thread.
    ///
    EntryPoint {
        /// file (__TEXT) offset of main()
        entryoff: u64,
        /// if not zero, initial stack size
        stacksize: u64,
    },
    /// The source_version_command is an optional load command containing
    /// the version of the sources used to build the binary.
    ///
    SourceVersion(SourceVersionTag),

    /// Thread commands contain machine-specific data structures suitable for
    /// use in the thread state primitives.  The machine specific data structures
    /// follow the struct thread_command as follows.
    /// Each flavor of machine specific data structure is preceded by an unsigned
    /// long constant for the flavor of that data structure, an
    /// that is the count of longs of the size of the state data structure and then
    /// the state data structure follows.  This triple may be repeated for many
    /// flavors.  The constants for the flavors, counts and state data structure
    /// definitions are expected to be in the header file <machine/thread_status.h>.
    /// These machine specific data structures sizes must be multiples of
    /// 4 bytes  The cmdsize reflects the total size of the thread_command
    /// and all of the sizes of the constants for the flavors, counts and state
    /// data structures.
    UnixThread {
        /// flavor of thread state
        flavor: u32,
        /// count of longs in thread state
        count: u32,
        /// thread state for this flavor
        state: ThreadState,
    },

    /// A dynamically linked shared library may be a subframework of an umbrella
    /// framework.  If so it will be linked with "-umbrella umbrella_name" where
    /// Where "umbrella_name" is the name of the umbrella framework. A subframework
    /// can only be linked against by its umbrella framework or other subframeworks
    /// that are part of the same umbrella framework.  Otherwise the static link
    /// editor produces an error and states to link against the umbrella framework.
    /// The name of the umbrella framework for subframeworks is recorded in the
    /// following structure.
    SubFramework(LcString),

    /// A dynamically linked shared library may be a sub_umbrella of an umbrella
    /// framework.  If so it will be linked with "-sub_umbrella umbrella_name" where
    /// Where "umbrella_name" is the name of the sub_umbrella framework.  When
    /// staticly linking when -twolevel_namespace is in effect a twolevel namespace
    /// umbrella framework will only cause its subframeworks and those frameworks
    /// listed as sub_umbrella frameworks to be implicited linked in.  Any other
    /// dependent dynamic libraries will not be linked it when -twolevel_namespace
    /// is in effect.  The primary library recorded by the static linker when
    /// resolving a symbol in these libraries will be the umbrella framework.
    /// Zero or more sub_umbrella frameworks may be use by an umbrella framework.
    /// The name of a sub_umbrella framework is recorded in the following structure.
    SubUmbrella(LcString),

    /// For dynamically linked shared libraries that are subframework of an umbrella
    /// framework they can allow clients other than the umbrella framework or other
    /// subframeworks in the same umbrella framework.  To do this the subframework
    /// is built with "-allowable_client client_name" and an LC_SUB_CLIENT load
    /// command is created for each -allowable_client flag.  The client_name is
    /// usually a framework name.  It can also be a name used for bundles clients
    /// where the bundle is built with "-client_name client_name".
    SubClient(LcString),

    /// A dynamically linked shared library may be a sub_library of another shared
    /// library.  If so it will be linked with "-sub_library library_name" where
    /// Where "library_name" is the name of the sub_library shared library.  When
    /// staticly linking when -twolevel_namespace is in effect a twolevel namespace
    /// shared library will only cause its subframeworks and those frameworks
    /// listed as sub_umbrella frameworks and libraries listed as sub_libraries to
    /// be implicited linked in.  Any other dependent dynamic libraries will not be
    /// linked it when -twolevel_namespace is in effect.  The primary library
    /// recorded by the static linker when resolving a symbol in these libraries
    /// will be the umbrella framework (or dynamic library). Zero or more sub_library
    /// shared libraries may be use by an umbrella framework or (or dynamic library).
    /// The name of a sub_library framework is recorded in the following structure.
    /// For example /usr/lib/libobjc_profile.A.dylib would be recorded as "libobjc".
    SubLibrary(LcString),

    /// The linker_option_command contains linker options embedded in object files.
    LinkerOption(Vec<String>),

    /// The routines command contains the address of the dynamic shared library
    /// initialization routine and an index into the module table for the module
    /// that defines the routine.  Before any modules are used from the library the
    /// dynamic linker fully binds the module that defines the initialization routine
    /// and then calls it.  This gets called before any module initialization
    /// routines (used for C++ static constructors) in the library.
    Routines {
        /// address of initialization routine
        init_address: u32,
        /// index into the module table that the init routine is defined in
        init_module: u32,
    },

    Routines64 {
        /// address of initialization routine
        init_address: u64,
        /// index into the module table that the init routine is defined in
        init_module: u64,
    },

    /// The encryption_info_command contains the file offset and size of an of an encrypted segment.
    EncryptionInfo {
        /// file offset of encrypted range
        offset: u32,
        /// file size of encrypted range
        size: u32,
        /// which enryption system,
        /// 0 means not-encrypted yet
        id: u32,
    },

    EncryptionInfo64 {
        /// file offset of encrypted range
        offset: u32,
        /// file size of encrypted range
        size: u32,
        /// which enryption system,
        /// 0 means not-encrypted yet
        id: u32,
    },

    /// The twolevel_hints_command contains the offset and number of hints in the
    /// two-level namespace lookup hints table.
    TwoLevelHints {
        /// offset to the hint table
        offset: u32,
        /// number of hints in the hint table
        nhints: u32,
    },

    /// The build_version_command contains the min OS version on which this
    /// binary was built to run for its platform.  The list of known platforms and
    /// tool values following it.
    BuildVersion(BuildVersion),

    Command {
        /// type of load command
        cmd: u32,
        ///
        /// command in bytes
        payload: Vec<u8>,
    },
}

/// Read a fixed size string
pub trait ReadStringExt: Read {
    /// Read the fixed size string
    fn read_fixed_size_string(&mut self, len: usize) -> Result<String> {
        let mut buf = vec![0u8; len];

        self.read_exact(&mut buf)?;

        Ok(String::from_utf8(buf.split(|&b| b == 0).next().unwrap().to_vec())?)
    }
}

impl<R: Read + ?Sized> ReadStringExt for R {}

const LOAD_COMMAND_HEADER_SIZE: usize = 8; // cmd + cmdsize

impl LoadCommand {
    pub fn parse<O: ByteOrder, T: AsRef<[u8]>>(
        header: &MachHeader,
        buf: &mut Cursor<T>,
    ) -> Result<(LoadCommand, usize)> {
        let begin = buf.position();
        let cmd = buf.read_u32::<O>()?;
        let cmdsize = buf.read_u32::<O>()? as usize;

        if cmdsize < LOAD_COMMAND_HEADER_SIZE || begin as usize + cmdsize > buf.get_ref().as_ref().len() {
            return Err(BufferOverflow(cmdsize));
        }

        let cmd = match cmd {
            LC_SEGMENT => {
                let segname = buf.read_fixed_size_string(16)?;
                let vmaddr = buf.read_u32::<O>()? as usize;
                let vmsize = buf.read_u32::<O>()? as usize;
                let fileoff = buf.read_u32::<O>()? as usize;
                let filesize = buf.read_u32::<O>()? as usize;
                let maxprot = buf.read_i32::<O>()?;
                let initprot = buf.read_i32::<O>()?;
                let nsects = buf.read_u32::<O>()?;
                let flags = buf.read_u32::<O>()?;
                let mut sections = Vec::new();

                for _ in 0..nsects {
                    sections.push(Rc::new(Section::parse_section::<Cursor<T>, O>(buf)?));
                }

                LoadCommand::Segment {
                    segname,
                    vmaddr,
                    vmsize,
                    fileoff,
                    filesize,
                    maxprot,
                    initprot,
                    flags: SegmentFlags::from_bits_truncate(flags),
                    sections,
                }
            }
            LC_SEGMENT_64 => {
                let segname = buf.read_fixed_size_string(16)?;
                let vmaddr = buf.read_u64::<O>()? as usize;
                let vmsize = buf.read_u64::<O>()? as usize;
                let fileoff = buf.read_u64::<O>()? as usize;
                let filesize = buf.read_u64::<O>()? as usize;
                let maxprot = buf.read_i32::<O>()?;
                let initprot = buf.read_i32::<O>()?;
                let nsects = buf.read_u32::<O>()?;
                let flags = buf.read_u32::<O>()?;
                let mut sections = Vec::new();

                for _ in 0..nsects {
                    sections.push(Rc::new(Section::parse_section64::<Cursor<T>, O>(buf)?));
                }

                LoadCommand::Segment64 {
                    segname,
                    vmaddr,
                    vmsize,
                    fileoff,
                    filesize,
                    maxprot,
                    initprot,
                    flags: SegmentFlags::from_bits_truncate(flags),
                    sections,
                }
            }
            LC_IDFVMLIB => LoadCommand::IdFvmLib(Self::read_fvmlib::<O, T>(buf)?),
            LC_LOADFVMLIB => LoadCommand::LoadFvmLib(Self::read_fvmlib::<O, T>(buf)?),

            LC_ID_DYLIB => LoadCommand::IdDyLib(Self::read_dylib::<O, T>(buf)?),
            LC_LOAD_DYLIB => LoadCommand::LoadDyLib(Self::read_dylib::<O, T>(buf)?),
            LC_LOAD_WEAK_DYLIB => LoadCommand::LoadWeakDyLib(Self::read_dylib::<O, T>(buf)?),
            LC_REEXPORT_DYLIB => LoadCommand::ReexportDyLib(Self::read_dylib::<O, T>(buf)?),
            LC_LOAD_UPWARD_DYLIB => LoadCommand::LoadUpwardDylib(Self::read_dylib::<O, T>(buf)?),
            LC_LAZY_LOAD_DYLIB => LoadCommand::LazyLoadDylib(Self::read_dylib::<O, T>(buf)?),
            LC_RPATH => LoadCommand::Rpath({
                let offset = buf.read_u32::<O>()? as usize;
                buf.read_fixed_size_string(cmdsize - offset)?
            }),

            LC_ID_DYLINKER => LoadCommand::IdDyLinker(Self::read_lcstr::<O, T>(buf)?),
            LC_LOAD_DYLINKER => LoadCommand::LoadDyLinker(Self::read_lcstr::<O, T>(buf)?),
            LC_DYLD_ENVIRONMENT => LoadCommand::DyLdEnv(Self::read_lcstr::<O, T>(buf)?),

            LC_SYMTAB => LoadCommand::SymTab {
                symoff: buf.read_u32::<O>()?,
                nsyms: buf.read_u32::<O>()?,
                stroff: buf.read_u32::<O>()?,
                strsize: buf.read_u32::<O>()?,
            },
            LC_DYSYMTAB => LoadCommand::DySymTab {
                ilocalsym: buf.read_u32::<O>()?,
                nlocalsym: buf.read_u32::<O>()?,
                iextdefsym: buf.read_u32::<O>()?,
                nextdefsym: buf.read_u32::<O>()?,
                iundefsym: buf.read_u32::<O>()?,
                nundefsym: buf.read_u32::<O>()?,
                tocoff: buf.read_u32::<O>()?,
                ntoc: buf.read_u32::<O>()?,
                modtaboff: buf.read_u32::<O>()?,
                nmodtab: buf.read_u32::<O>()?,
                extrefsymoff: buf.read_u32::<O>()?,
                nextrefsyms: buf.read_u32::<O>()?,
                indirectsymoff: buf.read_u32::<O>()?,
                nindirectsyms: buf.read_u32::<O>()?,
                extreloff: buf.read_u32::<O>()?,
                nextrel: buf.read_u32::<O>()?,
                locreloff: buf.read_u32::<O>()?,
                nlocrel: buf.read_u32::<O>()?,
            },
            LC_UUID => {
                let mut uuid = [0; 16];

                buf.read_exact(&mut uuid[..])?;

                LoadCommand::Uuid(Uuid::from_slice(&uuid)?)
            }
            LC_CODE_SIGNATURE => LoadCommand::CodeSignature(Self::read_linkedit_data::<O, T>(buf)?),
            LC_SEGMENT_SPLIT_INFO => LoadCommand::SegmentSplitInfo(Self::read_linkedit_data::<O, T>(buf)?),
            LC_FUNCTION_STARTS => LoadCommand::FunctionStarts(Self::read_linkedit_data::<O, T>(buf)?),
            LC_DATA_IN_CODE => LoadCommand::DataInCode(Self::read_linkedit_data::<O, T>(buf)?),
            LC_DYLIB_CODE_SIGN_DRS => LoadCommand::DylibCodeSignDrs(Self::read_linkedit_data::<O, T>(buf)?),
            LC_LINKER_OPTIMIZATION_HINT => LoadCommand::LinkerOptimizationHint(Self::read_linkedit_data::<O, T>(buf)?),
            LC_DYLD_EXPORTS_TRIE => LoadCommand::DyldExportsTrie(Self::read_linkedit_data::<O, T>(buf)?),
            LC_DYLD_CHAINED_FIXUPS => LoadCommand::DyldChainedFixups(Self::read_linkedit_data::<O, T>(buf)?),

            LC_VERSION_MIN_MACOSX | LC_VERSION_MIN_IPHONEOS | LC_VERSION_MIN_WATCHOS | LC_VERSION_MIN_TVOS => {
                LoadCommand::VersionMin {
                    target: BuildTarget::from(cmd),
                    version: VersionTag(buf.read_u32::<O>()?),
                    sdk: VersionTag(buf.read_u32::<O>()?),
                }
            }
            LC_DYLD_INFO | LC_DYLD_INFO_ONLY => LoadCommand::DyldInfo {
                rebase_off: buf.read_u32::<O>()?,
                rebase_size: buf.read_u32::<O>()?,
                bind_off: buf.read_u32::<O>()?,
                bind_size: buf.read_u32::<O>()?,
                weak_bind_off: buf.read_u32::<O>()?,
                weak_bind_size: buf.read_u32::<O>()?,
                lazy_bind_off: buf.read_u32::<O>()?,
                lazy_bind_size: buf.read_u32::<O>()?,
                export_off: buf.read_u32::<O>()?,
                export_size: buf.read_u32::<O>()?,
            },
            LC_MAIN => LoadCommand::EntryPoint {
                entryoff: buf.read_u64::<O>()?,
                stacksize: buf.read_u64::<O>()?,
            },
            LC_SOURCE_VERSION => LoadCommand::SourceVersion(SourceVersionTag(buf.read_u64::<O>()?)),
            LC_UNIXTHREAD if header.cputype == CPU_TYPE_I386 => LoadCommand::UnixThread {
                flavor: buf.read_u32::<O>()?,
                count: buf.read_u32::<O>()?,
                state: ThreadState::I386 {
                    __eax: buf.read_u32::<O>()?,
                    __ebx: buf.read_u32::<O>()?,
                    __ecx: buf.read_u32::<O>()?,
                    __edx: buf.read_u32::<O>()?,
                    __edi: buf.read_u32::<O>()?,
                    __esi: buf.read_u32::<O>()?,
                    __ebp: buf.read_u32::<O>()?,
                    __esp: buf.read_u32::<O>()?,
                    __ss: buf.read_u32::<O>()?,
                    __eflags: buf.read_u32::<O>()?,
                    __eip: buf.read_u32::<O>()?,
                    __cs: buf.read_u32::<O>()?,
                    __ds: buf.read_u32::<O>()?,
                    __es: buf.read_u32::<O>()?,
                    __fs: buf.read_u32::<O>()?,
                    __gs: buf.read_u32::<O>()?,
                },
            },
            LC_UNIXTHREAD if header.cputype == CPU_TYPE_X86_64 => LoadCommand::UnixThread {
                flavor: buf.read_u32::<O>()?,
                count: buf.read_u32::<O>()?,
                state: ThreadState::X86_64 {
                    __rax: buf.read_u64::<O>()?,
                    __rbx: buf.read_u64::<O>()?,
                    __rcx: buf.read_u64::<O>()?,
                    __rdx: buf.read_u64::<O>()?,
                    __rdi: buf.read_u64::<O>()?,
                    __rsi: buf.read_u64::<O>()?,
                    __rbp: buf.read_u64::<O>()?,
                    __rsp: buf.read_u64::<O>()?,
                    __r8: buf.read_u64::<O>()?,
                    __r9: buf.read_u64::<O>()?,
                    __r10: buf.read_u64::<O>()?,
                    __r11: buf.read_u64::<O>()?,
                    __r12: buf.read_u64::<O>()?,
                    __r13: buf.read_u64::<O>()?,
                    __r14: buf.read_u64::<O>()?,
                    __r15: buf.read_u64::<O>()?,
                    __rip: buf.read_u64::<O>()?,
                    __rflags: buf.read_u64::<O>()?,
                    __cs: buf.read_u64::<O>()?,
                    __fs: buf.read_u64::<O>()?,
                    __gs: buf.read_u64::<O>()?,
                },
            },
            LC_UNIXTHREAD if header.cputype == CPU_TYPE_ARM => {
                let flavor = buf.read_u32::<O>()?;
                let count = buf.read_u32::<O>()?;
                let mut __r = [0u32; 13];

                buf.read_u32_into::<O>(&mut __r)?;

                LoadCommand::UnixThread {
                    flavor,
                    count,
                    state: ThreadState::Arm {
                        __r,
                        __sp: buf.read_u32::<O>()?,
                        __lr: buf.read_u32::<O>()?,
                        __pc: buf.read_u32::<O>()?,
                        __cpsr: buf.read_u32::<O>()?,
                    },
                }
            }
            LC_UNIXTHREAD if header.cputype == CPU_TYPE_ARM64 => {
                let flavor = buf.read_u32::<O>()?;
                let count = buf.read_u32::<O>()?;
                let mut __x = [0u64; 29];

                buf.read_u64_into::<O>(&mut __x)?;

                LoadCommand::UnixThread {
                    flavor,
                    count,
                    state: ThreadState::Arm64 {
                        __x,
                        __fp: buf.read_u64::<O>()?,
                        __lr: buf.read_u64::<O>()?,
                        __sp: buf.read_u64::<O>()?,
                        __pc: buf.read_u64::<O>()?,
                        __cpsr: buf.read_u32::<O>()?,
                        __pad: buf.read_u32::<O>()?,
                    },
                }
            }
            LC_UNIXTHREAD if header.cputype == CPU_TYPE_POWERPC => {
                let flavor = buf.read_u32::<O>()?;
                let count = buf.read_u32::<O>()?;
                let mut __srr = [0u32; 2];
                let mut __r = [0u32; 32];

                buf.read_u32_into::<O>(&mut __srr)?;
                buf.read_u32_into::<O>(&mut __r)?;

                LoadCommand::UnixThread {
                    flavor,
                    count,
                    state: ThreadState::PowerPC {
                        __srr,
                        __r,
                        __ct: buf.read_u32::<O>()?,
                        __xer: buf.read_u32::<O>()?,
                        __lr: buf.read_u32::<O>()?,
                        __ctr: buf.read_u32::<O>()?,
                        __mq: buf.read_u32::<O>()?,
                        __vrsave: buf.read_u32::<O>()?,
                    },
                }
            }
            LC_UNIXTHREAD if header.cputype == CPU_TYPE_POWERPC64 => {
                let flavor = buf.read_u32::<O>()?;
                let count = buf.read_u32::<O>()?;
                let mut __srr = [0u64; 2];
                let mut __r = [0u64; 32];

                buf.read_u64_into::<O>(&mut __srr)?;
                buf.read_u64_into::<O>(&mut __r)?;

                LoadCommand::UnixThread {
                    flavor,
                    count,
                    state: ThreadState::PowerPC64 {
                        __srr,
                        __r,
                        __cr: buf.read_u32::<O>()?,
                        __xer: buf.read_u64::<O>()?,
                        __lr: buf.read_u64::<O>()?,
                        __ctr: buf.read_u64::<O>()?,
                        __vrsave: buf.read_u32::<O>()?,
                    },
                }
            }
            LC_SUB_FRAMEWORK => LoadCommand::SubFramework(Self::read_lcstr::<O, T>(buf)?),
            LC_SUB_UMBRELLA => LoadCommand::SubUmbrella(Self::read_lcstr::<O, T>(buf)?),
            LC_SUB_CLIENT => LoadCommand::SubClient(Self::read_lcstr::<O, T>(buf)?),
            LC_SUB_LIBRARY => LoadCommand::SubLibrary(Self::read_lcstr::<O, T>(buf)?),
            LC_LINKER_OPTION => {
                let count = buf.read_u32::<O>()?;

                LoadCommand::LinkerOption(
                    (0..count)
                        .map(|_| Self::read_utf8_str(buf))
                        .collect::<Result<Vec<_>>>()?,
                )
            }
            LC_ROUTINES => LoadCommand::Routines {
                init_address: buf.read_u32::<O>()?,
                init_module: buf.read_u32::<O>()?,
            },
            LC_ROUTINES_64 => LoadCommand::Routines64 {
                init_address: buf.read_u64::<O>()?,
                init_module: buf.read_u64::<O>()?,
            },
            LC_ENCRYPTION_INFO => LoadCommand::EncryptionInfo {
                offset: buf.read_u32::<O>()?,
                size: buf.read_u32::<O>()?,
                id: buf.read_u32::<O>()?,
            },
            LC_ENCRYPTION_INFO_64 => LoadCommand::EncryptionInfo64 {
                offset: buf.read_u32::<O>()?,
                size: buf.read_u32::<O>()?,
                id: buf.read_u32::<O>()?,
            },
            LC_TWOLEVEL_HINTS => LoadCommand::TwoLevelHints {
                offset: buf.read_u32::<O>()?,
                nhints: buf.read_u32::<O>()?,
            },
            LC_BUILD_VERSION => LoadCommand::BuildVersion(BuildVersion {
                platform: buf.read_u32::<O>()?,
                minos: VersionTag(buf.read_u32::<O>()?),
                sdk: VersionTag(buf.read_u32::<O>()?),
                build_tools: (0..buf.read_u32::<O>()?)
                    .map(|_| {
                        let tool = buf.read_u32::<O>()?;
                        let version = VersionTag(buf.read_u32::<O>()?);

                        Ok(BuildTool { tool, version })
                    })
                    .collect::<Result<Vec<_>>>()?,
            }),
            _ => {
                let mut payload = vec![0; cmdsize as usize - LOAD_COMMAND_HEADER_SIZE];

                debug!(
                    "load unsupported {} command with {} bytes payload",
                    LoadCommand::cmd_name(cmd),
                    payload.len()
                );

                buf.read_exact(&mut payload)?;

                LoadCommand::Command { cmd, payload }
            }
        };

        let read = (buf.position() - begin) as usize;

        debug!(
            "parsed {} command with {}/{} bytes: {:?}",
            cmd.name(),
            read,
            cmdsize,
            cmd
        );

        if cmdsize < read {
            return Err(BufferOverflow(cmdsize));
        }

        if cmdsize > read {
            // skip the reserved or padding bytes
            buf.consume(cmdsize - read);
        }

        Ok((cmd, cmdsize))
    }

    fn read_lcstr<O: ByteOrder, T: AsRef<[u8]>>(buf: &mut Cursor<T>) -> Result<LcString> {
        let off = buf.read_u32::<O>()? as usize;

        buf.consume(off - 12);

        Ok(LcString(off, buf.read_cstr()?))
    }

    fn read_utf8_str<T: AsRef<[u8]>>(cur: &mut Cursor<T>) -> Result<String> {
        let mut buf = vec![];

        cur.read_until(0, &mut buf)?;

        if buf.last() == Some(&0) {
            let _ = buf.pop();
        }

        Ok(String::from_utf8(buf)?)
    }

    fn read_fvmlib<O: ByteOrder, T: AsRef<[u8]>>(buf: &mut Cursor<T>) -> Result<FvmLib> {
        let off = buf.read_u32::<O>()? as usize;
        let minor_version = buf.read_u32::<O>()?;
        let header_addr = buf.read_u32::<O>()?;

        buf.consume(off - 20);

        Ok(FvmLib {
            name: LcString(off, buf.read_cstr()?),
            minor_version,
            header_addr,
        })
    }

    fn read_dylib<O: ByteOrder, T: AsRef<[u8]>>(buf: &mut Cursor<T>) -> Result<DyLib> {
        let off = buf.read_u32::<O>()? as usize;
        let timestamp = buf.read_u32::<O>()?;
        let current_version = buf.read_u32::<O>()?;
        let compatibility_version = buf.read_u32::<O>()?;

        buf.consume(off - 24);

        Ok(DyLib {
            name: LcString(off, buf.read_cstr()?),
            timestamp,
            current_version: VersionTag(current_version),
            compatibility_version: VersionTag(compatibility_version),
        })
    }

    fn read_linkedit_data<O: ByteOrder, T: AsRef<[u8]>>(buf: &mut Cursor<T>) -> Result<LinkEditData> {
        Ok(LinkEditData {
            off: buf.read_u32::<O>()?,
            size: buf.read_u32::<O>()?,
        })
    }

    pub fn cmd(&self) -> u32 {
        match *self {
            LoadCommand::Segment { .. } => LC_SEGMENT,
            LoadCommand::Segment64 { .. } => LC_SEGMENT_64,
            LoadCommand::IdFvmLib(_) => LC_IDFVMLIB,
            LoadCommand::LoadFvmLib(_) => LC_LOADFVMLIB,
            LoadCommand::IdDyLib(_) => LC_ID_DYLIB,
            LoadCommand::LoadDyLib(_) => LC_LOAD_DYLIB,
            LoadCommand::LoadWeakDyLib(_) => LC_LOAD_WEAK_DYLIB,
            LoadCommand::ReexportDyLib(_) => LC_REEXPORT_DYLIB,
            LoadCommand::LoadUpwardDylib(_) => LC_LOAD_UPWARD_DYLIB,
            LoadCommand::LazyLoadDylib(_) => LC_LAZY_LOAD_DYLIB,
            LoadCommand::Rpath(_) => LC_RPATH,
            LoadCommand::IdDyLinker(_) => LC_ID_DYLINKER,
            LoadCommand::LoadDyLinker(_) => LC_LOAD_DYLINKER,
            LoadCommand::DyLdEnv(_) => LC_DYLD_ENVIRONMENT,
            LoadCommand::SymTab { .. } => LC_SYMTAB,
            LoadCommand::DySymTab { .. } => LC_DYSYMTAB,
            LoadCommand::Uuid(_) => LC_UUID,
            LoadCommand::CodeSignature(_) => LC_CODE_SIGNATURE,
            LoadCommand::SegmentSplitInfo(_) => LC_SEGMENT_SPLIT_INFO,
            LoadCommand::FunctionStarts(_) => LC_FUNCTION_STARTS,
            LoadCommand::DataInCode(_) => LC_DATA_IN_CODE,
            LoadCommand::DylibCodeSignDrs(_) => LC_DYLIB_CODE_SIGN_DRS,
            LoadCommand::LinkerOptimizationHint(_) => LC_LINKER_OPTIMIZATION_HINT,
            LoadCommand::VersionMin { target, .. } => BuildTarget::into(target),
            LoadCommand::DyldInfo { .. } => LC_DYLD_INFO_ONLY,
            LoadCommand::EntryPoint { .. } => LC_MAIN,
            LoadCommand::SourceVersion(_) => LC_SOURCE_VERSION,
            LoadCommand::UnixThread { .. } => LC_UNIXTHREAD,
            LoadCommand::SubFramework(_) => LC_SUB_FRAMEWORK,
            LoadCommand::SubUmbrella(_) => LC_SUB_UMBRELLA,
            LoadCommand::SubClient(_) => LC_SUB_CLIENT,
            LoadCommand::SubLibrary(_) => LC_SUB_LIBRARY,
            LoadCommand::LinkerOption(_) => LC_LINKER_OPTION,
            LoadCommand::Routines { .. } => LC_ROUTINES,
            LoadCommand::Routines64 { .. } => LC_ROUTINES_64,
            LoadCommand::EncryptionInfo { .. } => LC_ENCRYPTION_INFO,
            LoadCommand::EncryptionInfo64 { .. } => LC_ENCRYPTION_INFO_64,
            LoadCommand::TwoLevelHints { .. } => LC_TWOLEVEL_HINTS,
            LoadCommand::BuildVersion { .. } => LC_BUILD_VERSION,
            LoadCommand::DyldExportsTrie { .. } => LC_DYLD_EXPORTS_TRIE,
            LoadCommand::DyldChainedFixups { .. } => LC_DYLD_CHAINED_FIXUPS,
            LoadCommand::Command { cmd, .. } => cmd,
        }
    }

    pub fn name(&self) -> &'static str {
        Self::cmd_name(self.cmd())
    }

    fn cmd_name(cmd: u32) -> &'static str {
        match cmd {
            LC_SEGMENT => "LC_SEGMENT",
            LC_SYMTAB => "LC_SYMTAB",
            LC_SYMSEG => "LC_SYMSEG",
            LC_THREAD => "LC_THREAD",
            LC_UNIXTHREAD => "LC_UNIXTHREAD",
            LC_LOADFVMLIB => "LC_LOADFVMLIB",
            LC_IDFVMLIB => "LC_IDFVMLIB",
            LC_IDENT => "LC_IDENT",
            LC_FVMFILE => "LC_FVMFILE",
            LC_PREPAGE => "LC_PREPAGE",
            LC_DYSYMTAB => "LC_DYSYMTAB",
            LC_LOAD_DYLIB => "LC_LOAD_DYLIB",
            LC_ID_DYLIB => "LC_ID_DYLIB",
            LC_LOAD_DYLINKER => "LC_LOAD_DYLINKER",
            LC_ID_DYLINKER => "LC_ID_DYLINKER",
            LC_PREBOUND_DYLIB => "LC_PREBOUND_DYLIB",
            LC_ROUTINES => "LC_ROUTINES",
            LC_SUB_FRAMEWORK => "LC_SUB_FRAMEWORK",
            LC_SUB_UMBRELLA => "LC_SUB_UMBRELLA",
            LC_SUB_CLIENT => "LC_SUB_CLIENT",
            LC_SUB_LIBRARY => "LC_SUB_LIBRARY",
            LC_TWOLEVEL_HINTS => "LC_TWOLEVEL_HINTS",
            LC_PREBIND_CKSUM => "LC_PREBIND_CKSUM",
            LC_LOAD_WEAK_DYLIB => "LC_LOAD_WEAK_DYLIB",
            LC_SEGMENT_64 => "LC_SEGMENT_64",
            LC_ROUTINES_64 => "LC_ROUTINES_64",
            LC_UUID => "LC_UUID",
            LC_RPATH => "LC_RPATH",
            LC_CODE_SIGNATURE => "LC_CODE_SIGNATURE",
            LC_SEGMENT_SPLIT_INFO => "LC_SEGMENT_SPLIT_INFO",
            LC_REEXPORT_DYLIB => "LC_REEXPORT_DYLIB",
            LC_LAZY_LOAD_DYLIB => "LC_LAZY_LOAD_DYLIB",
            LC_ENCRYPTION_INFO => "LC_ENCRYPTION_INFO",
            LC_DYLD_INFO => "LC_DYLD_INFO",
            LC_DYLD_INFO_ONLY => "LC_DYLD_INFO_ONLY",
            LC_LOAD_UPWARD_DYLIB => "LC_LOAD_UPWARD_DYLIB",
            LC_VERSION_MIN_MACOSX => "LC_VERSION_MIN_MACOSX",
            LC_VERSION_MIN_IPHONEOS => "LC_VERSION_MIN_IPHONEOS",
            LC_FUNCTION_STARTS => "LC_FUNCTION_STARTS",
            LC_DYLD_ENVIRONMENT => "LC_DYLD_ENVIRONMENT",
            LC_MAIN => "LC_MAIN",
            LC_DATA_IN_CODE => "LC_DATA_IN_CODE",
            LC_SOURCE_VERSION => "LC_SOURCE_VERSION",
            LC_DYLIB_CODE_SIGN_DRS => "LC_DYLIB_CODE_SIGN_DRS",
            LC_ENCRYPTION_INFO_64 => "LC_ENCRYPTION_INFO_64",
            LC_LINKER_OPTION => "LC_LINKER_OPTION",
            LC_LINKER_OPTIMIZATION_HINT => "LC_LINKER_OPTIMIZATION_HINT",
            LC_VERSION_MIN_TVOS => "LC_VERSION_MIN_TVOS",
            LC_VERSION_MIN_WATCHOS => "LC_VERSION_MIN_WATCHOS",
            LC_NOTE => "LC_NOTE",
            LC_BUILD_VERSION => "LC_BUILD_VERSION",
            LC_DYLD_EXPORTS_TRIE => "LC_DYLD_EXPORTS_TRIE",
            LC_DYLD_CHAINED_FIXUPS => "LC_DYLD_CHAINED_FIXUPS",
            _ => "LC_COMMAND",
        }
    }
}

pub trait CursorExt<T: AsRef<[u8]>> {
    fn read_uleb128(&mut self) -> Result<usize>;

    fn read_cstr(&mut self) -> Result<String>;
}

impl<T> CursorExt<T> for Cursor<T>
where
    T: AsRef<[u8]>,
{
    fn read_uleb128(&mut self) -> Result<usize> {
        let mut v = 0;
        let mut bits = 0;

        loop {
            let b = self.read_u8()?;
            let n = usize::from(b & 0x7F);

            if bits > 63 {
                return Err(NumberOverflow);
            }

            v |= n << bits;
            bits += 7;

            if (b & 0x80) == 0 {
                break;
            }
        }

        Ok(v)
    }

    fn read_cstr(&mut self) -> Result<String> {
        let mut v = Vec::new();

        self.read_until(0, &mut v)?;

        Ok(String::from_utf8(v.split(|&b| b == 0).next().unwrap().to_vec())?)
    }
}

/// The flags field of a section structure is separated into two parts a section
/// type and section attributes.
///
/// The section types are mutually exclusive (it can only have one type)
/// but the section attributes are not (it may have more than one attribute).
///
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
pub struct SectionFlags(u32);

impl SectionFlags {
    pub fn sect_type(self) -> u32 {
        self.0 & SECTION_TYPE
    }

    pub fn sect_attrs(self) -> SectionAttributes {
        SectionAttributes::from_bits_truncate(self.0 & SECTION_ATTRIBUTES)
    }
}

impl From<SectionFlags> for u32 {
    fn from(f: SectionFlags) -> u32 {
        f.0
    }
}

/// A segment is made up of zero or more sections.
///
/// `Non-MH_OBJECT` files have all of their segments with the proper sections in each,
/// and padded to the specified segment alignment when produced by the link editor.
/// The first segment of a `MH_EXECUTE` and `MH_FVMLIB` format file contains the mach header
/// and load commands of the object file before its first section.  The zero
/// fill sections are always last in their segment (in all formats).  This
/// allows the zeroed segment padding to be mapped into memory where zero fill
/// sections might be. The gigabyte zero fill sections, those with the section
/// type `S_GB_ZEROFILL`, can only be in a segment with sections of this type.
/// These segments are then placed after all other segments.
///
/// The `MH_OBJECT` format has all of its sections in one segment for
/// compactness.  There is no padding to a specified segment boundary and the
/// mach header and load commands are not part of the segment.
///
/// Sections with the same section name, sectname, going into the same segment,
/// segname, are combined by the link editor.  The resulting section is aligned
/// to the maximum alignment of the combined sections and is the new section's
/// alignment.  The combined sections are aligned to their original alignment in
/// the combined section.  Any padded bytes to get the specified alignment are
/// zeroed.
///
/// The format of the relocation entries referenced by the reloff and nreloc
/// fields of the section structure for mach object files is described in the
/// header file <reloc.h>.
///
///
#[derive(Debug, Clone)]
pub struct Section {
    /// name of this section
    pub sectname: String,
    /// segment this section goes in
    pub segname: String,
    /// memory address of this section
    pub addr: usize,
    /// size in bytes of this section
    pub size: usize,
    /// file offset of this section
    pub offset: u32,
    /// section alignment (power of 2)
    pub align: u32,
    /// file offset of relocation entries
    pub reloff: u32,
    /// number of relocation entries
    pub nreloc: u32,
    // flags (section type and attributes)
    pub flags: SectionFlags,
    /// reserved (for offset or index)
    pub reserved1: u32,
    /// reserved (for count or sizeof)
    pub reserved2: u32,
    /// reserved
    pub reserved3: u32,
}

impl Section {
    fn parse_section<T: BufRead, O: ByteOrder>(buf: &mut T) -> Result<Section> {
        let section = Section {
            sectname: buf.read_fixed_size_string(16)?,
            segname: buf.read_fixed_size_string(16)?,
            addr: buf.read_u32::<O>()? as usize,
            size: buf.read_u32::<O>()? as usize,
            offset: buf.read_u32::<O>()?,
            align: buf.read_u32::<O>()?,
            reloff: buf.read_u32::<O>()?,
            nreloc: buf.read_u32::<O>()?,
            flags: SectionFlags(buf.read_u32::<O>()?),
            reserved1: buf.read_u32::<O>()?,
            reserved2: buf.read_u32::<O>()?,
            reserved3: 0,
        };

        Ok(section)
    }

    fn parse_section64<T: BufRead, O: ByteOrder>(buf: &mut T) -> Result<Section> {
        let section = Section {
            sectname: buf.read_fixed_size_string(16)?,
            segname: buf.read_fixed_size_string(16)?,
            addr: buf.read_u64::<O>()? as usize,
            size: buf.read_u64::<O>()? as usize,
            offset: buf.read_u32::<O>()?,
            align: buf.read_u32::<O>()?,
            reloff: buf.read_u32::<O>()?,
            nreloc: buf.read_u32::<O>()?,
            flags: SectionFlags(buf.read_u32::<O>()?),
            reserved1: buf.read_u32::<O>()?,
            reserved2: buf.read_u32::<O>()?,
            reserved3: buf.read_u32::<O>()?,
        };

        Ok(section)
    }
}

/// The `LC_DATA_IN_CODE` load commands uses a `LinkEditData`
/// to point to an array of `DataInCodeEntry` entries.
///
/// Each entry describes a range of data in a code section.
///
pub struct DataInCodeEntry {
    pub offset: u32, // from mach_header to start of data range
    pub length: u16, // number of bytes in data range
    pub kind: u16,   // a DICE_KIND_* value
}

#[cfg(test)]
pub mod tests {
    use std::io::Cursor;

    use byteorder::LittleEndian;

    use super::super::*;

    include!("testdata.rs");

    macro_rules! parse_command {
        ($buf:expr) => {{
            let header = MachHeader::default();
            let mut buf = Vec::new();

            buf.extend_from_slice(&$buf[..]);

            let mut cur = Cursor::new(buf);

            LoadCommand::parse::<LittleEndian, Vec<u8>>(&header, &mut cur).unwrap()
        }};
    }

    #[test]
    fn test_parse_segments() {
        if let (
            LoadCommand::Segment64 {
                ref segname,
                vmaddr,
                vmsize,
                fileoff,
                filesize,
                maxprot,
                initprot,
                flags,
                ref sections,
            },
            cmdsize,
        ) = parse_command!(LC_SEGMENT_64_PAGEZERO_DATA)
        {
            assert_eq!(cmdsize, 72);
            assert_eq!(segname, SEG_PAGEZERO);
            assert_eq!(vmaddr, 0);
            assert_eq!(vmsize, 0x0000000100000000);
            assert_eq!(fileoff, 0);
            assert_eq!(filesize, 0);
            assert_eq!(maxprot, 0);
            assert_eq!(initprot, 0);
            assert!(flags.is_empty());
            assert!(sections.is_empty());
        } else {
            panic!();
        }

        if let (
            LoadCommand::Segment64 {
                ref segname,
                vmaddr,
                vmsize,
                fileoff,
                filesize,
                maxprot,
                initprot,
                flags,
                ref sections,
            },
            cmdsize,
        ) = parse_command!(LC_SEGMENT_64_TEXT_DATA)
        {
            assert_eq!(cmdsize, 712);
            assert_eq!(segname, SEG_TEXT);
            assert_eq!(vmaddr, 0x0000000100000000);
            assert_eq!(vmsize, 0x00000000001e3000);
            assert_eq!(fileoff, 0);
            assert_eq!(filesize, 0x1e3000);
            assert_eq!(maxprot, 7);
            assert_eq!(initprot, 5);
            assert!(flags.is_empty());
            assert_eq!(sections.len(), 8);

            assert_eq!(
                sections
                    .iter()
                    .map(|ref sec| (*sec).sectname.clone())
                    .collect::<Vec<String>>(),
                vec![
                    SECT_TEXT,
                    "__stubs",
                    "__stub_helper",
                    "__gcc_except_tab",
                    "__const",
                    "__cstring",
                    "__unwind_info",
                    "__eh_frame",
                ]
            );
        } else {
            panic!();
        }

        if let (
            LoadCommand::Segment64 {
                ref segname,
                vmaddr,
                vmsize,
                fileoff,
                filesize,
                maxprot,
                initprot,
                flags,
                ref sections,
            },
            cmdsize,
        ) = parse_command!(LC_SEGMENT_64_DATA_DATA)
        {
            assert_eq!(cmdsize, 872);
            assert_eq!(segname, SEG_DATA);
            assert_eq!(vmaddr, 0x00000001001e3000);
            assert_eq!(vmsize, 0x0000000000013000);
            assert_eq!(fileoff, 0x1e3000);
            assert_eq!(filesize, 0x12000);
            assert_eq!(maxprot, 7);
            assert_eq!(initprot, 3);
            assert!(flags.is_empty());
            assert_eq!(sections.len(), 10);

            assert_eq!(
                sections
                    .iter()
                    .map(|ref sec| (*sec).sectname.clone())
                    .collect::<Vec<String>>(),
                vec![
                    "__nl_symbol_ptr",
                    "__got",
                    "__la_symbol_ptr",
                    "__mod_init_func",
                    "__const",
                    SECT_DATA,
                    "__thread_vars",
                    "__thread_data",
                    SECT_COMMON,
                    SECT_BSS,
                ]
            );
        } else {
            panic!();
        }

        if let (
            LoadCommand::Segment64 {
                ref segname,
                vmaddr,
                vmsize,
                fileoff,
                filesize,
                maxprot,
                initprot,
                flags,
                ref sections,
            },
            cmdsize,
        ) = parse_command!(LC_SEGMENT_64_LINKEDIT_DATA)
        {
            assert_eq!(cmdsize, 72);
            assert_eq!(segname, SEG_LINKEDIT);
            assert_eq!(vmaddr, 0x00000001001f6000);
            assert_eq!(vmsize, 0x000000000017a000);
            assert_eq!(fileoff, 0x1f5000);
            assert_eq!(filesize, 0x1790b4);
            assert_eq!(maxprot, 7);
            assert_eq!(initprot, 1);
            assert!(flags.is_empty());
            assert!(sections.is_empty());
        } else {
            panic!();
        }
    }

    #[test]
    fn test_parse_dyld_info_command() {
        if let (
            LoadCommand::DyldInfo {
                rebase_off,
                rebase_size,
                bind_off,
                bind_size,
                weak_bind_off,
                weak_bind_size,
                lazy_bind_off,
                lazy_bind_size,
                export_off,
                export_size,
            },
            cmdsize,
        ) = parse_command!(LC_DYLD_INFO_ONLY_DATA)
        {
            assert_eq!(cmdsize, 48);
            assert_eq!(rebase_off, 0x1f5000);
            assert_eq!(rebase_size, 3368);
            assert_eq!(bind_off, 0x1f5d28);
            assert_eq!(bind_size, 80);
            assert_eq!(weak_bind_off, 0x1f5d78);
            assert_eq!(weak_bind_size, 24);
            assert_eq!(lazy_bind_off, 0x1f5d90);
            assert_eq!(lazy_bind_size, 1688);
            assert_eq!(export_off, 0x1f6428);
            assert_eq!(export_size, 34856);
        } else {
            panic!();
        }
    }

    #[test]
    fn test_parse_symtab_command() {
        if let (
            LoadCommand::SymTab {
                symoff,
                nsyms,
                stroff,
                strsize,
            },
            cmdsize,
        ) = parse_command!(LC_SYMTAB_DATA)
        {
            assert_eq!(cmdsize, 24);
            assert_eq!(symoff, 0x200d88);
            assert_eq!(nsyms, 36797);
            assert_eq!(stroff, 0x290bf4);
            assert_eq!(strsize, 906432);
        } else {
            panic!();
        }
    }

    #[test]
    fn test_parse_dysymtab_command() {
        if let (
            LoadCommand::DySymTab {
                ilocalsym,
                nlocalsym,
                iextdefsym,
                nextdefsym,
                iundefsym,
                nundefsym,
                tocoff,
                ntoc,
                modtaboff,
                nmodtab,
                extrefsymoff,
                nextrefsyms,
                indirectsymoff,
                nindirectsyms,
                extreloff,
                nextrel,
                locreloff,
                nlocrel,
            },
            cmdsize,
        ) = parse_command!(LC_DYSYMTAB_DATA)
        {
            assert_eq!(cmdsize, 80);
            assert_eq!(ilocalsym, 0);
            assert_eq!(nlocalsym, 35968);
            assert_eq!(iextdefsym, 35968);
            assert_eq!(nextdefsym, 746);
            assert_eq!(iundefsym, 36714);
            assert_eq!(nundefsym, 83);
            assert_eq!(tocoff, 0);
            assert_eq!(ntoc, 0);
            assert_eq!(modtaboff, 0);
            assert_eq!(nmodtab, 0);
            assert_eq!(extrefsymoff, 0);
            assert_eq!(nextrefsyms, 0);
            assert_eq!(indirectsymoff, 2689368);
            assert_eq!(nindirectsyms, 167);
            assert_eq!(extreloff, 0);
            assert_eq!(nextrel, 0);
            assert_eq!(locreloff, 0);
            assert_eq!(nlocrel, 0);
        } else {
            panic!();
        }
    }

    #[test]
    fn test_parse_load_dylinker_command() {
        if let (LoadCommand::LoadDyLinker(LcString(off, ref name)), cmdsize) = parse_command!(LC_LOAD_DYLINKER_DATA) {
            assert_eq!(cmdsize, 32);
            assert_eq!(off, 12);
            assert_eq!(name, "/usr/lib/dyld");
        } else {
            panic!();
        }
    }

    #[test]
    fn test_parse_uuid_command() {
        if let (LoadCommand::Uuid(ref uuid), cmdsize) = parse_command!(LC_UUID_DATA) {
            assert_eq!(cmdsize, 24);
            assert_eq!(uuid.to_string(), "92e3cf1f-20da-3373-a98c-851366d353bf");
        } else {
            panic!();
        }
    }

    #[test]
    fn test_parse_min_version_command() {
        if let (LoadCommand::VersionMin { target, version, sdk }, cmdsize) = parse_command!(LC_VERSION_MIN_MACOSX_DATA)
        {
            assert_eq!(cmdsize, 16);
            assert_eq!(target, BuildTarget::MacOsX);
            assert_eq!(version.to_string(), "10.11");
            assert_eq!(sdk.to_string(), "10.11");
        } else {
            panic!();
        }
    }

    #[test]
    fn test_parse_source_version_command() {
        if let (LoadCommand::SourceVersion(version), cmdsize) = parse_command!(LC_SOURCE_VERSION_DATA) {
            assert_eq!(cmdsize, 16);
            assert_eq!(version.to_string(), "0.0");
        } else {
            panic!();
        }
    }

    #[test]
    fn test_parse_main_command() {
        if let (LoadCommand::EntryPoint { entryoff, stacksize }, cmdsize) = parse_command!(LC_MAIN_DATA) {
            assert_eq!(cmdsize, 24);
            assert_eq!(entryoff, 0x11400);
            assert_eq!(stacksize, 0);
        } else {
            panic!();
        }
    }

    #[test]
    fn test_load_dylib_command() {
        if let (LoadCommand::LoadDyLib(ref dylib), cmdsize) = parse_command!(LC_LOAD_DYLIB_DATA) {
            assert_eq!(cmdsize, 56);
            assert_eq!(dylib.name, LcString(24, String::from("/usr/lib/libSystem.B.dylib")));
            assert_eq!(dylib.timestamp, 2);
            assert_eq!(dylib.current_version.to_string(), "1226.10.1");
            assert_eq!(dylib.compatibility_version.to_string(), "1.0");
        } else {
            panic!();
        }
    }

    #[test]
    fn test_parse_link_edit_data_command() {
        if let (LoadCommand::FunctionStarts(LinkEditData { off, size }), cmdsize) =
            parse_command!(LC_FUNCTION_STARTS_DATA)
        {
            assert_eq!(cmdsize, 16);
            assert_eq!(off, 0x1fec50);
            assert_eq!(size, 8504);
        } else {
            panic!();
        }

        if let (LoadCommand::DataInCode(LinkEditData { off, size }), cmdsize) = parse_command!(LC_DATA_IN_CODE_DATA) {
            assert_eq!(cmdsize, 16);
            assert_eq!(off, 0x200d88);
            assert_eq!(size, 0);
        } else {
            panic!();
        }
    }

    #[test]
    fn test_parse_rpath_command() {
        if let (LoadCommand::Rpath(path), cmdsize) = parse_command!(LC_RPATH_DATA) {
            assert_eq!(cmdsize, 64);
            assert_eq!(path, "@executable_path/../../Library/PrivateFrameworks");
        } else {
            panic!()
        }
    }

    #[cfg(feature = "display")]
    #[test]
    fn test_build_version_command() {
        let (cmd, cmdsize) = parse_command!(LC_BUILD_VERSION);

        if let LoadCommand::BuildVersion(ref version) = cmd {
            assert_eq!(cmdsize, 32);
            assert_eq!(version.platform(), Platform::Other(8));
            assert_eq!(version.minos.to_string(), "12.0");
            assert_eq!(version.sdk.to_string(), "12.0");
            assert_eq!(
                version.build_tools,
                vec![BuildTool {
                    tool: 3,
                    version: "409.10.0".parse().unwrap()
                }]
            );
        } else {
            panic!()
        }

        assert_eq!(
            MachCommand(cmd, cmdsize).to_string(),
            "      cmd LC_BUILD_VERSION
  cmdsize 32
 platform Other(8)
    minos 12.0
      sdk 12.0
    tools
          LD 409.10
"
        );
    }
}