xref: /llvm-project/lldb/examples/python/mach_o.py (revision 2238dcc39358353cac21df75c3c3286ab20b8f53)
1#!/usr/bin/env python
2
3import cmd
4import dict_utils
5import file_extract
6import optparse
7import re
8import struct
9import string
10import io
11import sys
12import uuid
13
14# Mach header "magic" constants
15MH_MAGIC = 0xFEEDFACE
16MH_CIGAM = 0xCEFAEDFE
17MH_MAGIC_64 = 0xFEEDFACF
18MH_CIGAM_64 = 0xCFFAEDFE
19FAT_MAGIC = 0xCAFEBABE
20FAT_CIGAM = 0xBEBAFECA
21
22# Mach haeder "filetype" constants
23MH_OBJECT = 0x00000001
24MH_EXECUTE = 0x00000002
25MH_FVMLIB = 0x00000003
26MH_CORE = 0x00000004
27MH_PRELOAD = 0x00000005
28MH_DYLIB = 0x00000006
29MH_DYLINKER = 0x00000007
30MH_BUNDLE = 0x00000008
31MH_DYLIB_STUB = 0x00000009
32MH_DSYM = 0x0000000A
33MH_KEXT_BUNDLE = 0x0000000B
34
35# Mach haeder "flag" constant bits
36MH_NOUNDEFS = 0x00000001
37MH_INCRLINK = 0x00000002
38MH_DYLDLINK = 0x00000004
39MH_BINDATLOAD = 0x00000008
40MH_PREBOUND = 0x00000010
41MH_SPLIT_SEGS = 0x00000020
42MH_LAZY_INIT = 0x00000040
43MH_TWOLEVEL = 0x00000080
44MH_FORCE_FLAT = 0x00000100
45MH_NOMULTIDEFS = 0x00000200
46MH_NOFIXPREBINDING = 0x00000400
47MH_PREBINDABLE = 0x00000800
48MH_ALLMODSBOUND = 0x00001000
49MH_SUBSECTIONS_VIA_SYMBOLS = 0x00002000
50MH_CANONICAL = 0x00004000
51MH_WEAK_DEFINES = 0x00008000
52MH_BINDS_TO_WEAK = 0x00010000
53MH_ALLOW_STACK_EXECUTION = 0x00020000
54MH_ROOT_SAFE = 0x00040000
55MH_SETUID_SAFE = 0x00080000
56MH_NO_REEXPORTED_DYLIBS = 0x00100000
57MH_PIE = 0x00200000
58MH_DEAD_STRIPPABLE_DYLIB = 0x00400000
59MH_HAS_TLV_DESCRIPTORS = 0x00800000
60MH_NO_HEAP_EXECUTION = 0x01000000
61
62# Mach load command constants
63LC_REQ_DYLD = 0x80000000
64LC_SEGMENT = 0x00000001
65LC_SYMTAB = 0x00000002
66LC_SYMSEG = 0x00000003
67LC_THREAD = 0x00000004
68LC_UNIXTHREAD = 0x00000005
69LC_LOADFVMLIB = 0x00000006
70LC_IDFVMLIB = 0x00000007
71LC_IDENT = 0x00000008
72LC_FVMFILE = 0x00000009
73LC_PREPAGE = 0x0000000A
74LC_DYSYMTAB = 0x0000000B
75LC_LOAD_DYLIB = 0x0000000C
76LC_ID_DYLIB = 0x0000000D
77LC_LOAD_DYLINKER = 0x0000000E
78LC_ID_DYLINKER = 0x0000000F
79LC_PREBOUND_DYLIB = 0x00000010
80LC_ROUTINES = 0x00000011
81LC_SUB_FRAMEWORK = 0x00000012
82LC_SUB_UMBRELLA = 0x00000013
83LC_SUB_CLIENT = 0x00000014
84LC_SUB_LIBRARY = 0x00000015
85LC_TWOLEVEL_HINTS = 0x00000016
86LC_PREBIND_CKSUM = 0x00000017
87LC_LOAD_WEAK_DYLIB = 0x00000018 | LC_REQ_DYLD
88LC_SEGMENT_64 = 0x00000019
89LC_ROUTINES_64 = 0x0000001A
90LC_UUID = 0x0000001B
91LC_RPATH = 0x0000001C | LC_REQ_DYLD
92LC_CODE_SIGNATURE = 0x0000001D
93LC_SEGMENT_SPLIT_INFO = 0x0000001E
94LC_REEXPORT_DYLIB = 0x0000001F | LC_REQ_DYLD
95LC_LAZY_LOAD_DYLIB = 0x00000020
96LC_ENCRYPTION_INFO = 0x00000021
97LC_DYLD_INFO = 0x00000022
98LC_DYLD_INFO_ONLY = 0x00000022 | LC_REQ_DYLD
99LC_LOAD_UPWARD_DYLIB = 0x00000023 | LC_REQ_DYLD
100LC_VERSION_MIN_MACOSX = 0x00000024
101LC_VERSION_MIN_IPHONEOS = 0x00000025
102LC_FUNCTION_STARTS = 0x00000026
103LC_DYLD_ENVIRONMENT = 0x00000027
104
105# Mach CPU constants
106CPU_ARCH_MASK = 0xFF000000
107CPU_ARCH_ABI64 = 0x01000000
108CPU_TYPE_ANY = 0xFFFFFFFF
109CPU_TYPE_VAX = 1
110CPU_TYPE_MC680x0 = 6
111CPU_TYPE_I386 = 7
112CPU_TYPE_X86_64 = CPU_TYPE_I386 | CPU_ARCH_ABI64
113CPU_TYPE_MIPS = 8
114CPU_TYPE_MC98000 = 10
115CPU_TYPE_HPPA = 11
116CPU_TYPE_ARM = 12
117CPU_TYPE_MC88000 = 13
118CPU_TYPE_SPARC = 14
119CPU_TYPE_I860 = 15
120CPU_TYPE_ALPHA = 16
121CPU_TYPE_POWERPC = 18
122CPU_TYPE_POWERPC64 = CPU_TYPE_POWERPC | CPU_ARCH_ABI64
123
124# VM protection constants
125VM_PROT_READ = 1
126VM_PROT_WRITE = 2
127VM_PROT_EXECUTE = 4
128
129# VM protection constants
130N_STAB = 0xE0
131N_PEXT = 0x10
132N_TYPE = 0x0E
133N_EXT = 0x01
134
135# Values for nlist N_TYPE bits of the "Mach.NList.type" field.
136N_UNDF = 0x0
137N_ABS = 0x2
138N_SECT = 0xE
139N_PBUD = 0xC
140N_INDR = 0xA
141
142# Section indexes for the "Mach.NList.sect_idx" fields
143NO_SECT = 0
144MAX_SECT = 255
145
146# Stab defines
147N_GSYM = 0x20
148N_FNAME = 0x22
149N_FUN = 0x24
150N_STSYM = 0x26
151N_LCSYM = 0x28
152N_BNSYM = 0x2E
153N_OPT = 0x3C
154N_RSYM = 0x40
155N_SLINE = 0x44
156N_ENSYM = 0x4E
157N_SSYM = 0x60
158N_SO = 0x64
159N_OSO = 0x66
160N_LSYM = 0x80
161N_BINCL = 0x82
162N_SOL = 0x84
163N_PARAMS = 0x86
164N_VERSION = 0x88
165N_OLEVEL = 0x8A
166N_PSYM = 0xA0
167N_EINCL = 0xA2
168N_ENTRY = 0xA4
169N_LBRAC = 0xC0
170N_EXCL = 0xC2
171N_RBRAC = 0xE0
172N_BCOMM = 0xE2
173N_ECOMM = 0xE4
174N_ECOML = 0xE8
175N_LENG = 0xFE
176
177vm_prot_names = ["---", "r--", "-w-", "rw-", "--x", "r-x", "-wx", "rwx"]
178
179
180def dump_memory(base_addr, data, hex_bytes_len, num_per_line):
181    hex_bytes = data.encode("hex")
182    if hex_bytes_len == -1:
183        hex_bytes_len = len(hex_bytes)
184    addr = base_addr
185    ascii_str = ""
186    i = 0
187    while i < hex_bytes_len:
188        if ((i / 2) % num_per_line) == 0:
189            if i > 0:
190                print(" %s" % (ascii_str))
191                ascii_str = ""
192            print("0x%8.8x:" % (addr + i), end=" ")
193        hex_byte = hex_bytes[i : i + 2]
194        print(hex_byte, end=" ")
195        int_byte = int(hex_byte, 16)
196        ascii_char = "%c" % (int_byte)
197        if int_byte >= 32 and int_byte < 127:
198            ascii_str += ascii_char
199        else:
200            ascii_str += "."
201        i = i + 2
202    if ascii_str:
203        if (i / 2) % num_per_line:
204            padding = num_per_line - ((i / 2) % num_per_line)
205        else:
206            padding = 0
207        print("%*s%s" % (padding * 3 + 1, "", ascii_str))
208    print()
209
210
211class TerminalColors:
212    """Simple terminal colors class"""
213
214    def __init__(self, enabled=True):
215        # TODO: discover terminal type from "file" and disable if
216        # it can't handle the color codes
217        self.enabled = enabled
218
219    def reset(self):
220        """Reset all terminal colors and formatting."""
221        if self.enabled:
222            return "\x1b[0m"
223        return ""
224
225    def bold(self, on=True):
226        """Enable or disable bold depending on the "on" parameter."""
227        if self.enabled:
228            if on:
229                return "\x1b[1m"
230            else:
231                return "\x1b[22m"
232        return ""
233
234    def italics(self, on=True):
235        """Enable or disable italics depending on the "on" parameter."""
236        if self.enabled:
237            if on:
238                return "\x1b[3m"
239            else:
240                return "\x1b[23m"
241        return ""
242
243    def underline(self, on=True):
244        """Enable or disable underline depending on the "on" parameter."""
245        if self.enabled:
246            if on:
247                return "\x1b[4m"
248            else:
249                return "\x1b[24m"
250        return ""
251
252    def inverse(self, on=True):
253        """Enable or disable inverse depending on the "on" parameter."""
254        if self.enabled:
255            if on:
256                return "\x1b[7m"
257            else:
258                return "\x1b[27m"
259        return ""
260
261    def strike(self, on=True):
262        """Enable or disable strike through depending on the "on" parameter."""
263        if self.enabled:
264            if on:
265                return "\x1b[9m"
266            else:
267                return "\x1b[29m"
268        return ""
269
270    def black(self, fg=True):
271        """Set the foreground or background color to black.
272        The foreground color will be set if "fg" tests True. The background color will be set if "fg" tests False.
273        """
274        if self.enabled:
275            if fg:
276                return "\x1b[30m"
277            else:
278                return "\x1b[40m"
279        return ""
280
281    def red(self, fg=True):
282        """Set the foreground or background color to red.
283        The foreground color will be set if "fg" tests True. The background color will be set if "fg" tests False.
284        """
285        if self.enabled:
286            if fg:
287                return "\x1b[31m"
288            else:
289                return "\x1b[41m"
290        return ""
291
292    def green(self, fg=True):
293        """Set the foreground or background color to green.
294        The foreground color will be set if "fg" tests True. The background color will be set if "fg" tests False.
295        """
296        if self.enabled:
297            if fg:
298                return "\x1b[32m"
299            else:
300                return "\x1b[42m"
301        return ""
302
303    def yellow(self, fg=True):
304        """Set the foreground or background color to yellow.
305        The foreground color will be set if "fg" tests True. The background color will be set if "fg" tests False.
306        """
307        if self.enabled:
308            if fg:
309                return "\x1b[43m"
310            else:
311                return "\x1b[33m"
312        return ""
313
314    def blue(self, fg=True):
315        """Set the foreground or background color to blue.
316        The foreground color will be set if "fg" tests True. The background color will be set if "fg" tests False.
317        """
318        if self.enabled:
319            if fg:
320                return "\x1b[34m"
321            else:
322                return "\x1b[44m"
323        return ""
324
325    def magenta(self, fg=True):
326        """Set the foreground or background color to magenta.
327        The foreground color will be set if "fg" tests True. The background color will be set if "fg" tests False.
328        """
329        if self.enabled:
330            if fg:
331                return "\x1b[35m"
332            else:
333                return "\x1b[45m"
334        return ""
335
336    def cyan(self, fg=True):
337        """Set the foreground or background color to cyan.
338        The foreground color will be set if "fg" tests True. The background color will be set if "fg" tests False.
339        """
340        if self.enabled:
341            if fg:
342                return "\x1b[36m"
343            else:
344                return "\x1b[46m"
345        return ""
346
347    def white(self, fg=True):
348        """Set the foreground or background color to white.
349        The foreground color will be set if "fg" tests True. The background color will be set if "fg" tests False.
350        """
351        if self.enabled:
352            if fg:
353                return "\x1b[37m"
354            else:
355                return "\x1b[47m"
356        return ""
357
358    def default(self, fg=True):
359        """Set the foreground or background color to the default.
360        The foreground color will be set if "fg" tests True. The background color will be set if "fg" tests False.
361        """
362        if self.enabled:
363            if fg:
364                return "\x1b[39m"
365            else:
366                return "\x1b[49m"
367        return ""
368
369
370def swap_unpack_char():
371    """Returns the unpack prefix that will for non-native endian-ness."""
372    if struct.pack("H", 1).startswith("\x00"):
373        return "<"
374    return ">"
375
376
377def dump_hex_bytes(addr, s, bytes_per_line=16):
378    i = 0
379    line = ""
380    for ch in s:
381        if (i % bytes_per_line) == 0:
382            if line:
383                print(line)
384            line = "%#8.8x: " % (addr + i)
385        line += "%02X " % ord(ch)
386        i += 1
387    print(line)
388
389
390def dump_hex_byte_string_diff(addr, a, b, bytes_per_line=16):
391    i = 0
392    line = ""
393    a_len = len(a)
394    b_len = len(b)
395    if a_len < b_len:
396        max_len = b_len
397    else:
398        max_len = a_len
399    tty_colors = TerminalColors(True)
400    for i in range(max_len):
401        ch = None
402        if i < a_len:
403            ch_a = a[i]
404            ch = ch_a
405        else:
406            ch_a = None
407        if i < b_len:
408            ch_b = b[i]
409            if not ch:
410                ch = ch_b
411        else:
412            ch_b = None
413        mismatch = ch_a != ch_b
414        if (i % bytes_per_line) == 0:
415            if line:
416                print(line)
417            line = "%#8.8x: " % (addr + i)
418        if mismatch:
419            line += tty_colors.red()
420        line += "%02X " % ord(ch)
421        if mismatch:
422            line += tty_colors.default()
423        i += 1
424
425    print(line)
426
427
428class Mach:
429    """Class that does everything mach-o related"""
430
431    class Arch:
432        """Class that implements mach-o architectures"""
433
434        def __init__(self, c=0, s=0):
435            self.cpu = c
436            self.sub = s
437
438        def set_cpu_type(self, c):
439            self.cpu = c
440
441        def set_cpu_subtype(self, s):
442            self.sub = s
443
444        def set_arch(self, c, s):
445            self.cpu = c
446            self.sub = s
447
448        def is_64_bit(self):
449            return (self.cpu & CPU_ARCH_ABI64) != 0
450
451        cpu_infos = [
452            ["arm", CPU_TYPE_ARM, CPU_TYPE_ANY],
453            ["arm", CPU_TYPE_ARM, 0],
454            ["armv4", CPU_TYPE_ARM, 5],
455            ["armv6", CPU_TYPE_ARM, 6],
456            ["armv5", CPU_TYPE_ARM, 7],
457            ["xscale", CPU_TYPE_ARM, 8],
458            ["armv7", CPU_TYPE_ARM, 9],
459            ["armv7f", CPU_TYPE_ARM, 10],
460            ["armv7s", CPU_TYPE_ARM, 11],
461            ["armv7k", CPU_TYPE_ARM, 12],
462            ["armv7m", CPU_TYPE_ARM, 15],
463            ["armv7em", CPU_TYPE_ARM, 16],
464            ["ppc", CPU_TYPE_POWERPC, CPU_TYPE_ANY],
465            ["ppc", CPU_TYPE_POWERPC, 0],
466            ["ppc601", CPU_TYPE_POWERPC, 1],
467            ["ppc602", CPU_TYPE_POWERPC, 2],
468            ["ppc603", CPU_TYPE_POWERPC, 3],
469            ["ppc603e", CPU_TYPE_POWERPC, 4],
470            ["ppc603ev", CPU_TYPE_POWERPC, 5],
471            ["ppc604", CPU_TYPE_POWERPC, 6],
472            ["ppc604e", CPU_TYPE_POWERPC, 7],
473            ["ppc620", CPU_TYPE_POWERPC, 8],
474            ["ppc750", CPU_TYPE_POWERPC, 9],
475            ["ppc7400", CPU_TYPE_POWERPC, 10],
476            ["ppc7450", CPU_TYPE_POWERPC, 11],
477            ["ppc970", CPU_TYPE_POWERPC, 100],
478            ["ppc64", CPU_TYPE_POWERPC64, 0],
479            ["ppc970-64", CPU_TYPE_POWERPC64, 100],
480            ["i386", CPU_TYPE_I386, 3],
481            ["i486", CPU_TYPE_I386, 4],
482            ["i486sx", CPU_TYPE_I386, 0x84],
483            ["i386", CPU_TYPE_I386, CPU_TYPE_ANY],
484            ["x86_64", CPU_TYPE_X86_64, 3],
485            ["x86_64", CPU_TYPE_X86_64, CPU_TYPE_ANY],
486        ]
487
488        def __str__(self):
489            for info in self.cpu_infos:
490                if self.cpu == info[1] and (self.sub & 0x00FFFFFF) == info[2]:
491                    return info[0]
492            return "{0}.{1}".format(self.cpu, self.sub)
493
494    class Magic(dict_utils.Enum):
495        enum = {
496            "MH_MAGIC": MH_MAGIC,
497            "MH_CIGAM": MH_CIGAM,
498            "MH_MAGIC_64": MH_MAGIC_64,
499            "MH_CIGAM_64": MH_CIGAM_64,
500            "FAT_MAGIC": FAT_MAGIC,
501            "FAT_CIGAM": FAT_CIGAM,
502        }
503
504        def __init__(self, initial_value=0):
505            dict_utils.Enum.__init__(self, initial_value, self.enum)
506
507        def is_skinny_mach_file(self):
508            return (
509                self.value == MH_MAGIC
510                or self.value == MH_CIGAM
511                or self.value == MH_MAGIC_64
512                or self.value == MH_CIGAM_64
513            )
514
515        def is_universal_mach_file(self):
516            return self.value == FAT_MAGIC or self.value == FAT_CIGAM
517
518        def unpack(self, data):
519            data.set_byte_order("native")
520            self.value = data.get_uint32()
521
522        def get_byte_order(self):
523            if (
524                self.value == MH_CIGAM
525                or self.value == MH_CIGAM_64
526                or self.value == FAT_CIGAM
527            ):
528                return swap_unpack_char()
529            else:
530                return "="
531
532        def is_64_bit(self):
533            return self.value == MH_MAGIC_64 or self.value == MH_CIGAM_64
534
535    def __init__(self):
536        self.magic = Mach.Magic()
537        self.content = None
538        self.path = None
539
540    def extract(self, path, extractor):
541        self.path = path
542        self.unpack(extractor)
543
544    def parse(self, path):
545        self.path = path
546        try:
547            f = open(self.path)
548            file_extractor = file_extract.FileExtract(f, "=")
549            self.unpack(file_extractor)
550            # f.close()
551        except IOError as xxx_todo_changeme:
552            (errno, strerror) = xxx_todo_changeme.args
553            print("I/O error({0}): {1}".format(errno, strerror))
554        except ValueError:
555            print("Could not convert data to an integer.")
556        except:
557            print("Unexpected error:", sys.exc_info()[0])
558            raise
559
560    def compare(self, rhs):
561        self.content.compare(rhs.content)
562
563    def dump(self, options=None):
564        self.content.dump(options)
565
566    def dump_header(self, dump_description=True, options=None):
567        self.content.dump_header(dump_description, options)
568
569    def dump_load_commands(self, dump_description=True, options=None):
570        self.content.dump_load_commands(dump_description, options)
571
572    def dump_sections(self, dump_description=True, options=None):
573        self.content.dump_sections(dump_description, options)
574
575    def dump_section_contents(self, options):
576        self.content.dump_section_contents(options)
577
578    def dump_symtab(self, dump_description=True, options=None):
579        self.content.dump_symtab(dump_description, options)
580
581    def dump_symbol_names_matching_regex(self, regex, file=None):
582        self.content.dump_symbol_names_matching_regex(regex, file)
583
584    def description(self):
585        return self.content.description()
586
587    def unpack(self, data):
588        self.magic.unpack(data)
589        if self.magic.is_skinny_mach_file():
590            self.content = Mach.Skinny(self.path)
591        elif self.magic.is_universal_mach_file():
592            self.content = Mach.Universal(self.path)
593        else:
594            self.content = None
595
596        if self.content is not None:
597            self.content.unpack(data, self.magic)
598
599    def is_valid(self):
600        return self.content is not None
601
602    class Universal:
603        def __init__(self, path):
604            self.path = path
605            self.type = "universal"
606            self.file_off = 0
607            self.magic = None
608            self.nfat_arch = 0
609            self.archs = list()
610
611        def description(self):
612            s = "%#8.8x: %s (" % (self.file_off, self.path)
613            archs_string = ""
614            for arch in self.archs:
615                if len(archs_string):
616                    archs_string += ", "
617                archs_string += "%s" % arch.arch
618            s += archs_string
619            s += ")"
620            return s
621
622        def unpack(self, data, magic=None):
623            self.file_off = data.tell()
624            if magic is None:
625                self.magic = Mach.Magic()
626                self.magic.unpack(data)
627            else:
628                self.magic = magic
629                self.file_off = self.file_off - 4
630            # Universal headers are always in big endian
631            data.set_byte_order("big")
632            self.nfat_arch = data.get_uint32()
633            for i in range(self.nfat_arch):
634                self.archs.append(Mach.Universal.ArchInfo())
635                self.archs[i].unpack(data)
636            for i in range(self.nfat_arch):
637                self.archs[i].mach = Mach.Skinny(self.path)
638                data.seek(self.archs[i].offset, 0)
639                skinny_magic = Mach.Magic()
640                skinny_magic.unpack(data)
641                self.archs[i].mach.unpack(data, skinny_magic)
642
643        def compare(self, rhs):
644            print("error: comparing two universal files is not supported yet")
645            return False
646
647        def dump(self, options):
648            if options.dump_header:
649                print()
650                print(
651                    "Universal Mach File: magic = %s, nfat_arch = %u"
652                    % (self.magic, self.nfat_arch)
653                )
654                print()
655            if self.nfat_arch > 0:
656                if options.dump_header:
657                    self.archs[0].dump_header(True, options)
658                    for i in range(self.nfat_arch):
659                        self.archs[i].dump_flat(options)
660                if options.dump_header:
661                    print()
662                for i in range(self.nfat_arch):
663                    self.archs[i].mach.dump(options)
664
665        def dump_header(self, dump_description=True, options=None):
666            if dump_description:
667                print(self.description())
668            for i in range(self.nfat_arch):
669                self.archs[i].mach.dump_header(True, options)
670                print()
671
672        def dump_load_commands(self, dump_description=True, options=None):
673            if dump_description:
674                print(self.description())
675            for i in range(self.nfat_arch):
676                self.archs[i].mach.dump_load_commands(True, options)
677                print()
678
679        def dump_sections(self, dump_description=True, options=None):
680            if dump_description:
681                print(self.description())
682            for i in range(self.nfat_arch):
683                self.archs[i].mach.dump_sections(True, options)
684                print()
685
686        def dump_section_contents(self, options):
687            for i in range(self.nfat_arch):
688                self.archs[i].mach.dump_section_contents(options)
689                print()
690
691        def dump_symtab(self, dump_description=True, options=None):
692            if dump_description:
693                print(self.description())
694            for i in range(self.nfat_arch):
695                self.archs[i].mach.dump_symtab(True, options)
696                print()
697
698        def dump_symbol_names_matching_regex(self, regex, file=None):
699            for i in range(self.nfat_arch):
700                self.archs[i].mach.dump_symbol_names_matching_regex(regex, file)
701
702        class ArchInfo:
703            def __init__(self):
704                self.arch = Mach.Arch(0, 0)
705                self.offset = 0
706                self.size = 0
707                self.align = 0
708                self.mach = None
709
710            def unpack(self, data):
711                # Universal headers are always in big endian
712                data.set_byte_order("big")
713                (
714                    self.arch.cpu,
715                    self.arch.sub,
716                    self.offset,
717                    self.size,
718                    self.align,
719                ) = data.get_n_uint32(5)
720
721            def dump_header(self, dump_description=True, options=None):
722                if options.verbose:
723                    print("CPU        SUBTYPE    OFFSET     SIZE       ALIGN")
724                    print("---------- ---------- ---------- ---------- ----------")
725                else:
726                    print("ARCH       FILEOFFSET FILESIZE   ALIGN")
727                    print("---------- ---------- ---------- ----------")
728
729            def dump_flat(self, options):
730                if options.verbose:
731                    print(
732                        "%#8.8x %#8.8x %#8.8x %#8.8x %#8.8x"
733                        % (
734                            self.arch.cpu,
735                            self.arch.sub,
736                            self.offset,
737                            self.size,
738                            self.align,
739                        )
740                    )
741                else:
742                    print(
743                        "%-10s %#8.8x %#8.8x %#8.8x"
744                        % (self.arch, self.offset, self.size, self.align)
745                    )
746
747            def dump(self):
748                print("   cputype: %#8.8x" % self.arch.cpu)
749                print("cpusubtype: %#8.8x" % self.arch.sub)
750                print("    offset: %#8.8x" % self.offset)
751                print("      size: %#8.8x" % self.size)
752                print("     align: %#8.8x" % self.align)
753
754            def __str__(self):
755                return "Mach.Universal.ArchInfo: %#8.8x %#8.8x %#8.8x %#8.8x %#8.8x" % (
756                    self.arch.cpu,
757                    self.arch.sub,
758                    self.offset,
759                    self.size,
760                    self.align,
761                )
762
763            def __repr__(self):
764                return "Mach.Universal.ArchInfo: %#8.8x %#8.8x %#8.8x %#8.8x %#8.8x" % (
765                    self.arch.cpu,
766                    self.arch.sub,
767                    self.offset,
768                    self.size,
769                    self.align,
770                )
771
772    class Flags:
773        def __init__(self, b):
774            self.bits = b
775
776        def __str__(self):
777            s = ""
778            if self.bits & MH_NOUNDEFS:
779                s += "MH_NOUNDEFS | "
780            if self.bits & MH_INCRLINK:
781                s += "MH_INCRLINK | "
782            if self.bits & MH_DYLDLINK:
783                s += "MH_DYLDLINK | "
784            if self.bits & MH_BINDATLOAD:
785                s += "MH_BINDATLOAD | "
786            if self.bits & MH_PREBOUND:
787                s += "MH_PREBOUND | "
788            if self.bits & MH_SPLIT_SEGS:
789                s += "MH_SPLIT_SEGS | "
790            if self.bits & MH_LAZY_INIT:
791                s += "MH_LAZY_INIT | "
792            if self.bits & MH_TWOLEVEL:
793                s += "MH_TWOLEVEL | "
794            if self.bits & MH_FORCE_FLAT:
795                s += "MH_FORCE_FLAT | "
796            if self.bits & MH_NOMULTIDEFS:
797                s += "MH_NOMULTIDEFS | "
798            if self.bits & MH_NOFIXPREBINDING:
799                s += "MH_NOFIXPREBINDING | "
800            if self.bits & MH_PREBINDABLE:
801                s += "MH_PREBINDABLE | "
802            if self.bits & MH_ALLMODSBOUND:
803                s += "MH_ALLMODSBOUND | "
804            if self.bits & MH_SUBSECTIONS_VIA_SYMBOLS:
805                s += "MH_SUBSECTIONS_VIA_SYMBOLS | "
806            if self.bits & MH_CANONICAL:
807                s += "MH_CANONICAL | "
808            if self.bits & MH_WEAK_DEFINES:
809                s += "MH_WEAK_DEFINES | "
810            if self.bits & MH_BINDS_TO_WEAK:
811                s += "MH_BINDS_TO_WEAK | "
812            if self.bits & MH_ALLOW_STACK_EXECUTION:
813                s += "MH_ALLOW_STACK_EXECUTION | "
814            if self.bits & MH_ROOT_SAFE:
815                s += "MH_ROOT_SAFE | "
816            if self.bits & MH_SETUID_SAFE:
817                s += "MH_SETUID_SAFE | "
818            if self.bits & MH_NO_REEXPORTED_DYLIBS:
819                s += "MH_NO_REEXPORTED_DYLIBS | "
820            if self.bits & MH_PIE:
821                s += "MH_PIE | "
822            if self.bits & MH_DEAD_STRIPPABLE_DYLIB:
823                s += "MH_DEAD_STRIPPABLE_DYLIB | "
824            if self.bits & MH_HAS_TLV_DESCRIPTORS:
825                s += "MH_HAS_TLV_DESCRIPTORS | "
826            if self.bits & MH_NO_HEAP_EXECUTION:
827                s += "MH_NO_HEAP_EXECUTION | "
828            # Strip the trailing " |" if we have any flags
829            if len(s) > 0:
830                s = s[0:-2]
831            return s
832
833    class FileType(dict_utils.Enum):
834        enum = {
835            "MH_OBJECT": MH_OBJECT,
836            "MH_EXECUTE": MH_EXECUTE,
837            "MH_FVMLIB": MH_FVMLIB,
838            "MH_CORE": MH_CORE,
839            "MH_PRELOAD": MH_PRELOAD,
840            "MH_DYLIB": MH_DYLIB,
841            "MH_DYLINKER": MH_DYLINKER,
842            "MH_BUNDLE": MH_BUNDLE,
843            "MH_DYLIB_STUB": MH_DYLIB_STUB,
844            "MH_DSYM": MH_DSYM,
845            "MH_KEXT_BUNDLE": MH_KEXT_BUNDLE,
846        }
847
848        def __init__(self, initial_value=0):
849            dict_utils.Enum.__init__(self, initial_value, self.enum)
850
851    class Skinny:
852        def __init__(self, path):
853            self.path = path
854            self.type = "skinny"
855            self.data = None
856            self.file_off = 0
857            self.magic = 0
858            self.arch = Mach.Arch(0, 0)
859            self.filetype = Mach.FileType(0)
860            self.ncmds = 0
861            self.sizeofcmds = 0
862            self.flags = Mach.Flags(0)
863            self.uuid = None
864            self.commands = list()
865            self.segments = list()
866            self.sections = list()
867            self.symbols = list()
868            self.sections.append(Mach.Section())
869
870        def description(self):
871            return "%#8.8x: %s (%s)" % (self.file_off, self.path, self.arch)
872
873        def unpack(self, data, magic=None):
874            self.data = data
875            self.file_off = data.tell()
876            if magic is None:
877                self.magic = Mach.Magic()
878                self.magic.unpack(data)
879            else:
880                self.magic = magic
881                self.file_off = self.file_off - 4
882            data.set_byte_order(self.magic.get_byte_order())
883            (
884                self.arch.cpu,
885                self.arch.sub,
886                self.filetype.value,
887                self.ncmds,
888                self.sizeofcmds,
889                bits,
890            ) = data.get_n_uint32(6)
891            self.flags.bits = bits
892
893            if self.is_64_bit():
894                data.get_uint32()  # Skip reserved word in mach_header_64
895
896            for i in range(0, self.ncmds):
897                lc = self.unpack_load_command(data)
898                self.commands.append(lc)
899
900        def get_data(self):
901            if self.data:
902                self.data.set_byte_order(self.magic.get_byte_order())
903                return self.data
904            return None
905
906        def unpack_load_command(self, data):
907            lc = Mach.LoadCommand()
908            lc.unpack(self, data)
909            lc_command = lc.command.get_enum_value()
910            if lc_command == LC_SEGMENT or lc_command == LC_SEGMENT_64:
911                lc = Mach.SegmentLoadCommand(lc)
912                lc.unpack(self, data)
913            elif (
914                lc_command == LC_LOAD_DYLIB
915                or lc_command == LC_ID_DYLIB
916                or lc_command == LC_LOAD_WEAK_DYLIB
917                or lc_command == LC_REEXPORT_DYLIB
918            ):
919                lc = Mach.DylibLoadCommand(lc)
920                lc.unpack(self, data)
921            elif (
922                lc_command == LC_LOAD_DYLINKER
923                or lc_command == LC_SUB_FRAMEWORK
924                or lc_command == LC_SUB_CLIENT
925                or lc_command == LC_SUB_UMBRELLA
926                or lc_command == LC_SUB_LIBRARY
927                or lc_command == LC_ID_DYLINKER
928                or lc_command == LC_RPATH
929            ):
930                lc = Mach.LoadDYLDLoadCommand(lc)
931                lc.unpack(self, data)
932            elif lc_command == LC_DYLD_INFO_ONLY:
933                lc = Mach.DYLDInfoOnlyLoadCommand(lc)
934                lc.unpack(self, data)
935            elif lc_command == LC_SYMTAB:
936                lc = Mach.SymtabLoadCommand(lc)
937                lc.unpack(self, data)
938            elif lc_command == LC_DYSYMTAB:
939                lc = Mach.DYLDSymtabLoadCommand(lc)
940                lc.unpack(self, data)
941            elif lc_command == LC_UUID:
942                lc = Mach.UUIDLoadCommand(lc)
943                lc.unpack(self, data)
944            elif (
945                lc_command == LC_CODE_SIGNATURE
946                or lc_command == LC_SEGMENT_SPLIT_INFO
947                or lc_command == LC_FUNCTION_STARTS
948            ):
949                lc = Mach.DataBlobLoadCommand(lc)
950                lc.unpack(self, data)
951            elif lc_command == LC_UNIXTHREAD:
952                lc = Mach.UnixThreadLoadCommand(lc)
953                lc.unpack(self, data)
954            elif lc_command == LC_ENCRYPTION_INFO:
955                lc = Mach.EncryptionInfoLoadCommand(lc)
956                lc.unpack(self, data)
957            lc.skip(data)
958            return lc
959
960        def compare(self, rhs):
961            print("\nComparing:")
962            print("a) %s %s" % (self.arch, self.path))
963            print("b) %s %s" % (rhs.arch, rhs.path))
964            result = True
965            if self.type == rhs.type:
966                for lhs_section in self.sections[1:]:
967                    rhs_section = rhs.get_section_by_section(lhs_section)
968                    if rhs_section:
969                        print(
970                            "comparing %s.%s..."
971                            % (lhs_section.segname, lhs_section.sectname),
972                            end=" ",
973                        )
974                        sys.stdout.flush()
975                        lhs_data = lhs_section.get_contents(self)
976                        rhs_data = rhs_section.get_contents(rhs)
977                        if lhs_data and rhs_data:
978                            if lhs_data == rhs_data:
979                                print("ok")
980                            else:
981                                lhs_data_len = len(lhs_data)
982                                rhs_data_len = len(rhs_data)
983                                # if lhs_data_len < rhs_data_len:
984                                #     if lhs_data == rhs_data[0:lhs_data_len]:
985                                #         print 'section data for %s matches the first %u bytes' % (lhs_section.sectname, lhs_data_len)
986                                #     else:
987                                #         # TODO: check padding
988                                #         result = False
989                                # elif lhs_data_len > rhs_data_len:
990                                #     if lhs_data[0:rhs_data_len] == rhs_data:
991                                #         print 'section data for %s matches the first %u bytes' % (lhs_section.sectname, lhs_data_len)
992                                #     else:
993                                #         # TODO: check padding
994                                #         result = False
995                                # else:
996                                result = False
997                                print("error: sections differ")
998                                # print 'a) %s' % (lhs_section)
999                                # dump_hex_byte_string_diff(0, lhs_data, rhs_data)
1000                                # print 'b) %s' % (rhs_section)
1001                                # dump_hex_byte_string_diff(0, rhs_data, lhs_data)
1002                        elif lhs_data and not rhs_data:
1003                            print("error: section data missing from b:")
1004                            print("a) %s" % (lhs_section))
1005                            print("b) %s" % (rhs_section))
1006                            result = False
1007                        elif not lhs_data and rhs_data:
1008                            print("error: section data missing from a:")
1009                            print("a) %s" % (lhs_section))
1010                            print("b) %s" % (rhs_section))
1011                            result = False
1012                        elif lhs_section.offset or rhs_section.offset:
1013                            print("error: section data missing for both a and b:")
1014                            print("a) %s" % (lhs_section))
1015                            print("b) %s" % (rhs_section))
1016                            result = False
1017                        else:
1018                            print("ok")
1019                    else:
1020                        result = False
1021                        print(
1022                            "error: section %s is missing in %s"
1023                            % (lhs_section.sectname, rhs.path)
1024                        )
1025            else:
1026                print(
1027                    "error: comparing a %s mach-o file with a %s mach-o file is not supported"
1028                    % (self.type, rhs.type)
1029                )
1030                result = False
1031            if not result:
1032                print("error: mach files differ")
1033            return result
1034
1035        def dump_header(self, dump_description=True, options=None):
1036            if options.verbose:
1037                print(
1038                    "MAGIC      CPU        SUBTYPE    FILETYPE   NUM CMDS SIZE CMDS  FLAGS"
1039                )
1040                print(
1041                    "---------- ---------- ---------- ---------- -------- ---------- ----------"
1042                )
1043            else:
1044                print(
1045                    "MAGIC        ARCH       FILETYPE       NUM CMDS SIZE CMDS  FLAGS"
1046                )
1047                print(
1048                    "------------ ---------- -------------- -------- ---------- ----------"
1049                )
1050
1051        def dump_flat(self, options):
1052            if options.verbose:
1053                print(
1054                    "%#8.8x %#8.8x %#8.8x %#8.8x %#8u %#8.8x %#8.8x"
1055                    % (
1056                        self.magic,
1057                        self.arch.cpu,
1058                        self.arch.sub,
1059                        self.filetype.value,
1060                        self.ncmds,
1061                        self.sizeofcmds,
1062                        self.flags.bits,
1063                    )
1064                )
1065            else:
1066                print(
1067                    "%-12s %-10s %-14s %#8u %#8.8x %s"
1068                    % (
1069                        self.magic,
1070                        self.arch,
1071                        self.filetype,
1072                        self.ncmds,
1073                        self.sizeofcmds,
1074                        self.flags,
1075                    )
1076                )
1077
1078        def dump(self, options):
1079            if options.dump_header:
1080                self.dump_header(True, options)
1081            if options.dump_load_commands:
1082                self.dump_load_commands(False, options)
1083            if options.dump_sections:
1084                self.dump_sections(False, options)
1085            if options.section_names:
1086                self.dump_section_contents(options)
1087            if options.dump_symtab:
1088                self.get_symtab()
1089                if len(self.symbols):
1090                    self.dump_sections(False, options)
1091                else:
1092                    print("No symbols")
1093            if options.find_mangled:
1094                self.dump_symbol_names_matching_regex(re.compile("^_?_Z"))
1095
1096        def dump_header(self, dump_description=True, options=None):
1097            if dump_description:
1098                print(self.description())
1099            print("Mach Header")
1100            print("       magic: %#8.8x %s" % (self.magic.value, self.magic))
1101            print("     cputype: %#8.8x %s" % (self.arch.cpu, self.arch))
1102            print("  cpusubtype: %#8.8x" % self.arch.sub)
1103            print(
1104                "    filetype: %#8.8x %s"
1105                % (self.filetype.get_enum_value(), self.filetype.get_enum_name())
1106            )
1107            print("       ncmds: %#8.8x %u" % (self.ncmds, self.ncmds))
1108            print("  sizeofcmds: %#8.8x" % self.sizeofcmds)
1109            print("       flags: %#8.8x %s" % (self.flags.bits, self.flags))
1110
1111        def dump_load_commands(self, dump_description=True, options=None):
1112            if dump_description:
1113                print(self.description())
1114            for lc in self.commands:
1115                print(lc)
1116
1117        def get_section_by_name(self, name):
1118            for section in self.sections:
1119                if section.sectname and section.sectname == name:
1120                    return section
1121            return None
1122
1123        def get_section_by_section(self, other_section):
1124            for section in self.sections:
1125                if (
1126                    section.sectname == other_section.sectname
1127                    and section.segname == other_section.segname
1128                ):
1129                    return section
1130            return None
1131
1132        def dump_sections(self, dump_description=True, options=None):
1133            if dump_description:
1134                print(self.description())
1135            num_sections = len(self.sections)
1136            if num_sections > 1:
1137                self.sections[1].dump_header()
1138                for sect_idx in range(1, num_sections):
1139                    print("%s" % self.sections[sect_idx])
1140
1141        def dump_section_contents(self, options):
1142            saved_section_to_disk = False
1143            for sectname in options.section_names:
1144                section = self.get_section_by_name(sectname)
1145                if section:
1146                    sect_bytes = section.get_contents(self)
1147                    if options.outfile:
1148                        if not saved_section_to_disk:
1149                            outfile = open(options.outfile, "w")
1150                            if options.extract_modules:
1151                                # print "Extracting modules from mach file..."
1152                                data = file_extract.FileExtract(
1153                                    io.BytesIO(sect_bytes), self.data.byte_order
1154                                )
1155                                version = data.get_uint32()
1156                                num_modules = data.get_uint32()
1157                                # print "version = %u, num_modules = %u" %
1158                                # (version, num_modules)
1159                                for i in range(num_modules):
1160                                    data_offset = data.get_uint64()
1161                                    data_size = data.get_uint64()
1162                                    name_offset = data.get_uint32()
1163                                    language = data.get_uint32()
1164                                    flags = data.get_uint32()
1165                                    data.seek(name_offset)
1166                                    module_name = data.get_c_string()
1167                                    # print "module[%u] data_offset = %#16.16x,
1168                                    # data_size = %#16.16x, name_offset =
1169                                    # %#16.16x (%s), language = %u, flags =
1170                                    # %#x" % (i, data_offset, data_size,
1171                                    # name_offset, module_name, language,
1172                                    # flags)
1173                                    data.seek(data_offset)
1174                                    outfile.write(data.read_size(data_size))
1175                            else:
1176                                print(
1177                                    "Saving section %s to '%s'"
1178                                    % (sectname, options.outfile)
1179                                )
1180                                outfile.write(sect_bytes)
1181                            outfile.close()
1182                            saved_section_to_disk = True
1183                        else:
1184                            print(
1185                                "error: you can only save a single section to disk at a time, skipping section '%s'"
1186                                % (sectname)
1187                            )
1188                    else:
1189                        print("section %s:\n" % (sectname))
1190                        section.dump_header()
1191                        print("%s\n" % (section))
1192                        dump_memory(0, sect_bytes, options.max_count, 16)
1193                else:
1194                    print('error: no section named "%s" was found' % (sectname))
1195
1196        def get_segment(self, segname):
1197            if len(self.segments) == 1 and self.segments[0].segname == "":
1198                return self.segments[0]
1199            for segment in self.segments:
1200                if segment.segname == segname:
1201                    return segment
1202            return None
1203
1204        def get_first_load_command(self, lc_enum_value):
1205            for lc in self.commands:
1206                if lc.command.value == lc_enum_value:
1207                    return lc
1208            return None
1209
1210        def get_symtab(self):
1211            if self.data and not self.symbols:
1212                lc_symtab = self.get_first_load_command(LC_SYMTAB)
1213                if lc_symtab:
1214                    symtab_offset = self.file_off
1215                    if self.data.is_in_memory():
1216                        linkedit_segment = self.get_segment("__LINKEDIT")
1217                        if linkedit_segment:
1218                            linkedit_vmaddr = linkedit_segment.vmaddr
1219                            linkedit_fileoff = linkedit_segment.fileoff
1220                            symtab_offset = (
1221                                linkedit_vmaddr + lc_symtab.symoff - linkedit_fileoff
1222                            )
1223                            symtab_offset = (
1224                                linkedit_vmaddr + lc_symtab.stroff - linkedit_fileoff
1225                            )
1226                    else:
1227                        symtab_offset += lc_symtab.symoff
1228
1229                    self.data.seek(symtab_offset)
1230                    is_64 = self.is_64_bit()
1231                    for i in range(lc_symtab.nsyms):
1232                        nlist = Mach.NList()
1233                        nlist.unpack(self, self.data, lc_symtab)
1234                        self.symbols.append(nlist)
1235                else:
1236                    print("no LC_SYMTAB")
1237
1238        def dump_symtab(self, dump_description=True, options=None):
1239            self.get_symtab()
1240            if dump_description:
1241                print(self.description())
1242            for i, symbol in enumerate(self.symbols):
1243                print("[%5u] %s" % (i, symbol))
1244
1245        def dump_symbol_names_matching_regex(self, regex, file=None):
1246            self.get_symtab()
1247            for symbol in self.symbols:
1248                if symbol.name and regex.search(symbol.name):
1249                    print(symbol.name)
1250                    if file:
1251                        file.write("%s\n" % (symbol.name))
1252
1253        def is_64_bit(self):
1254            return self.magic.is_64_bit()
1255
1256    class LoadCommand:
1257        class Command(dict_utils.Enum):
1258            enum = {
1259                "LC_SEGMENT": LC_SEGMENT,
1260                "LC_SYMTAB": LC_SYMTAB,
1261                "LC_SYMSEG": LC_SYMSEG,
1262                "LC_THREAD": LC_THREAD,
1263                "LC_UNIXTHREAD": LC_UNIXTHREAD,
1264                "LC_LOADFVMLIB": LC_LOADFVMLIB,
1265                "LC_IDFVMLIB": LC_IDFVMLIB,
1266                "LC_IDENT": LC_IDENT,
1267                "LC_FVMFILE": LC_FVMFILE,
1268                "LC_PREPAGE": LC_PREPAGE,
1269                "LC_DYSYMTAB": LC_DYSYMTAB,
1270                "LC_LOAD_DYLIB": LC_LOAD_DYLIB,
1271                "LC_ID_DYLIB": LC_ID_DYLIB,
1272                "LC_LOAD_DYLINKER": LC_LOAD_DYLINKER,
1273                "LC_ID_DYLINKER": LC_ID_DYLINKER,
1274                "LC_PREBOUND_DYLIB": LC_PREBOUND_DYLIB,
1275                "LC_ROUTINES": LC_ROUTINES,
1276                "LC_SUB_FRAMEWORK": LC_SUB_FRAMEWORK,
1277                "LC_SUB_UMBRELLA": LC_SUB_UMBRELLA,
1278                "LC_SUB_CLIENT": LC_SUB_CLIENT,
1279                "LC_SUB_LIBRARY": LC_SUB_LIBRARY,
1280                "LC_TWOLEVEL_HINTS": LC_TWOLEVEL_HINTS,
1281                "LC_PREBIND_CKSUM": LC_PREBIND_CKSUM,
1282                "LC_LOAD_WEAK_DYLIB": LC_LOAD_WEAK_DYLIB,
1283                "LC_SEGMENT_64": LC_SEGMENT_64,
1284                "LC_ROUTINES_64": LC_ROUTINES_64,
1285                "LC_UUID": LC_UUID,
1286                "LC_RPATH": LC_RPATH,
1287                "LC_CODE_SIGNATURE": LC_CODE_SIGNATURE,
1288                "LC_SEGMENT_SPLIT_INFO": LC_SEGMENT_SPLIT_INFO,
1289                "LC_REEXPORT_DYLIB": LC_REEXPORT_DYLIB,
1290                "LC_LAZY_LOAD_DYLIB": LC_LAZY_LOAD_DYLIB,
1291                "LC_ENCRYPTION_INFO": LC_ENCRYPTION_INFO,
1292                "LC_DYLD_INFO": LC_DYLD_INFO,
1293                "LC_DYLD_INFO_ONLY": LC_DYLD_INFO_ONLY,
1294                "LC_LOAD_UPWARD_DYLIB": LC_LOAD_UPWARD_DYLIB,
1295                "LC_VERSION_MIN_MACOSX": LC_VERSION_MIN_MACOSX,
1296                "LC_VERSION_MIN_IPHONEOS": LC_VERSION_MIN_IPHONEOS,
1297                "LC_FUNCTION_STARTS": LC_FUNCTION_STARTS,
1298                "LC_DYLD_ENVIRONMENT": LC_DYLD_ENVIRONMENT,
1299            }
1300
1301            def __init__(self, initial_value=0):
1302                dict_utils.Enum.__init__(self, initial_value, self.enum)
1303
1304        def __init__(self, c=None, l=0, o=0):
1305            if c is not None:
1306                self.command = c
1307            else:
1308                self.command = Mach.LoadCommand.Command(0)
1309            self.length = l
1310            self.file_off = o
1311
1312        def unpack(self, mach_file, data):
1313            self.file_off = data.tell()
1314            self.command.value, self.length = data.get_n_uint32(2)
1315
1316        def skip(self, data):
1317            data.seek(self.file_off + self.length, 0)
1318
1319        def __str__(self):
1320            lc_name = self.command.get_enum_name()
1321            return "%#8.8x: <%#4.4x> %-24s" % (self.file_off, self.length, lc_name)
1322
1323    class Section:
1324        def __init__(self):
1325            self.index = 0
1326            self.is_64 = False
1327            self.sectname = None
1328            self.segname = None
1329            self.addr = 0
1330            self.size = 0
1331            self.offset = 0
1332            self.align = 0
1333            self.reloff = 0
1334            self.nreloc = 0
1335            self.flags = 0
1336            self.reserved1 = 0
1337            self.reserved2 = 0
1338            self.reserved3 = 0
1339
1340        def unpack(self, is_64, data):
1341            self.is_64 = is_64
1342            self.sectname = data.get_fixed_length_c_string(16, "", True)
1343            self.segname = data.get_fixed_length_c_string(16, "", True)
1344            if self.is_64:
1345                self.addr, self.size = data.get_n_uint64(2)
1346                (
1347                    self.offset,
1348                    self.align,
1349                    self.reloff,
1350                    self.nreloc,
1351                    self.flags,
1352                    self.reserved1,
1353                    self.reserved2,
1354                    self.reserved3,
1355                ) = data.get_n_uint32(8)
1356            else:
1357                self.addr, self.size = data.get_n_uint32(2)
1358                (
1359                    self.offset,
1360                    self.align,
1361                    self.reloff,
1362                    self.nreloc,
1363                    self.flags,
1364                    self.reserved1,
1365                    self.reserved2,
1366                ) = data.get_n_uint32(7)
1367
1368        def dump_header(self):
1369            if self.is_64:
1370                print(
1371                    "INDEX ADDRESS            SIZE               OFFSET     ALIGN      RELOFF     NRELOC     FLAGS      RESERVED1  RESERVED2  RESERVED3  NAME"
1372                )
1373                print(
1374                    "===== ------------------ ------------------ ---------- ---------- ---------- ---------- ---------- ---------- ---------- ---------- ----------------------"
1375                )
1376            else:
1377                print(
1378                    "INDEX ADDRESS    SIZE       OFFSET     ALIGN      RELOFF     NRELOC     FLAGS      RESERVED1  RESERVED2  NAME"
1379                )
1380                print(
1381                    "===== ---------- ---------- ---------- ---------- ---------- ---------- ---------- ---------- ---------- ----------------------"
1382                )
1383
1384        def __str__(self):
1385            if self.is_64:
1386                return (
1387                    "[%3u] %#16.16x %#16.16x %#8.8x %#8.8x %#8.8x %#8.8x %#8.8x %#8.8x %#8.8x %#8.8x %s.%s"
1388                    % (
1389                        self.index,
1390                        self.addr,
1391                        self.size,
1392                        self.offset,
1393                        self.align,
1394                        self.reloff,
1395                        self.nreloc,
1396                        self.flags,
1397                        self.reserved1,
1398                        self.reserved2,
1399                        self.reserved3,
1400                        self.segname,
1401                        self.sectname,
1402                    )
1403                )
1404            else:
1405                return (
1406                    "[%3u] %#8.8x %#8.8x %#8.8x %#8.8x %#8.8x %#8.8x %#8.8x %#8.8x %#8.8x %s.%s"
1407                    % (
1408                        self.index,
1409                        self.addr,
1410                        self.size,
1411                        self.offset,
1412                        self.align,
1413                        self.reloff,
1414                        self.nreloc,
1415                        self.flags,
1416                        self.reserved1,
1417                        self.reserved2,
1418                        self.segname,
1419                        self.sectname,
1420                    )
1421                )
1422
1423        def get_contents(self, mach_file):
1424            """Get the section contents as a python string"""
1425            if self.size > 0 and mach_file.get_segment(self.segname).filesize > 0:
1426                data = mach_file.get_data()
1427                if data:
1428                    section_data_offset = mach_file.file_off + self.offset
1429                    # print '%s.%s is at offset 0x%x with size 0x%x' %
1430                    # (self.segname, self.sectname, section_data_offset,
1431                    # self.size)
1432                    data.push_offset_and_seek(section_data_offset)
1433                    bytes = data.read_size(self.size)
1434                    data.pop_offset_and_seek()
1435                    return bytes
1436            return None
1437
1438    class DylibLoadCommand(LoadCommand):
1439        def __init__(self, lc):
1440            Mach.LoadCommand.__init__(self, lc.command, lc.length, lc.file_off)
1441            self.name = None
1442            self.timestamp = 0
1443            self.current_version = 0
1444            self.compatibility_version = 0
1445
1446        def unpack(self, mach_file, data):
1447            byte_order_char = mach_file.magic.get_byte_order()
1448            (
1449                name_offset,
1450                self.timestamp,
1451                self.current_version,
1452                self.compatibility_version,
1453            ) = data.get_n_uint32(4)
1454            data.seek(self.file_off + name_offset, 0)
1455            self.name = data.get_fixed_length_c_string(self.length - 24)
1456
1457        def __str__(self):
1458            s = Mach.LoadCommand.__str__(self)
1459            s += "%#8.8x %#8.8x %#8.8x " % (
1460                self.timestamp,
1461                self.current_version,
1462                self.compatibility_version,
1463            )
1464            s += self.name
1465            return s
1466
1467    class LoadDYLDLoadCommand(LoadCommand):
1468        def __init__(self, lc):
1469            Mach.LoadCommand.__init__(self, lc.command, lc.length, lc.file_off)
1470            self.name = None
1471
1472        def unpack(self, mach_file, data):
1473            data.get_uint32()
1474            self.name = data.get_fixed_length_c_string(self.length - 12)
1475
1476        def __str__(self):
1477            s = Mach.LoadCommand.__str__(self)
1478            s += "%s" % self.name
1479            return s
1480
1481    class UnixThreadLoadCommand(LoadCommand):
1482        class ThreadState:
1483            def __init__(self):
1484                self.flavor = 0
1485                self.count = 0
1486                self.register_values = list()
1487
1488            def unpack(self, data):
1489                self.flavor, self.count = data.get_n_uint32(2)
1490                self.register_values = data.get_n_uint32(self.count)
1491
1492            def __str__(self):
1493                s = "flavor = %u, count = %u, regs =" % (self.flavor, self.count)
1494                i = 0
1495                for register_value in self.register_values:
1496                    if i % 8 == 0:
1497                        s += "\n                                            "
1498                    s += " %#8.8x" % register_value
1499                    i += 1
1500                return s
1501
1502        def __init__(self, lc):
1503            Mach.LoadCommand.__init__(self, lc.command, lc.length, lc.file_off)
1504            self.reg_sets = list()
1505
1506        def unpack(self, mach_file, data):
1507            reg_set = Mach.UnixThreadLoadCommand.ThreadState()
1508            reg_set.unpack(data)
1509            self.reg_sets.append(reg_set)
1510
1511        def __str__(self):
1512            s = Mach.LoadCommand.__str__(self)
1513            for reg_set in self.reg_sets:
1514                s += "%s" % reg_set
1515            return s
1516
1517    class DYLDInfoOnlyLoadCommand(LoadCommand):
1518        def __init__(self, lc):
1519            Mach.LoadCommand.__init__(self, lc.command, lc.length, lc.file_off)
1520            self.rebase_off = 0
1521            self.rebase_size = 0
1522            self.bind_off = 0
1523            self.bind_size = 0
1524            self.weak_bind_off = 0
1525            self.weak_bind_size = 0
1526            self.lazy_bind_off = 0
1527            self.lazy_bind_size = 0
1528            self.export_off = 0
1529            self.export_size = 0
1530
1531        def unpack(self, mach_file, data):
1532            byte_order_char = mach_file.magic.get_byte_order()
1533            (
1534                self.rebase_off,
1535                self.rebase_size,
1536                self.bind_off,
1537                self.bind_size,
1538                self.weak_bind_off,
1539                self.weak_bind_size,
1540                self.lazy_bind_off,
1541                self.lazy_bind_size,
1542                self.export_off,
1543                self.export_size,
1544            ) = data.get_n_uint32(10)
1545
1546        def __str__(self):
1547            s = Mach.LoadCommand.__str__(self)
1548            s += "rebase_off = %#8.8x, rebase_size = %u, " % (
1549                self.rebase_off,
1550                self.rebase_size,
1551            )
1552            s += "bind_off = %#8.8x, bind_size = %u, " % (self.bind_off, self.bind_size)
1553            s += "weak_bind_off = %#8.8x, weak_bind_size = %u, " % (
1554                self.weak_bind_off,
1555                self.weak_bind_size,
1556            )
1557            s += "lazy_bind_off = %#8.8x, lazy_bind_size = %u, " % (
1558                self.lazy_bind_off,
1559                self.lazy_bind_size,
1560            )
1561            s += "export_off = %#8.8x, export_size = %u, " % (
1562                self.export_off,
1563                self.export_size,
1564            )
1565            return s
1566
1567    class DYLDSymtabLoadCommand(LoadCommand):
1568        def __init__(self, lc):
1569            Mach.LoadCommand.__init__(self, lc.command, lc.length, lc.file_off)
1570            self.ilocalsym = 0
1571            self.nlocalsym = 0
1572            self.iextdefsym = 0
1573            self.nextdefsym = 0
1574            self.iundefsym = 0
1575            self.nundefsym = 0
1576            self.tocoff = 0
1577            self.ntoc = 0
1578            self.modtaboff = 0
1579            self.nmodtab = 0
1580            self.extrefsymoff = 0
1581            self.nextrefsyms = 0
1582            self.indirectsymoff = 0
1583            self.nindirectsyms = 0
1584            self.extreloff = 0
1585            self.nextrel = 0
1586            self.locreloff = 0
1587            self.nlocrel = 0
1588
1589        def unpack(self, mach_file, data):
1590            byte_order_char = mach_file.magic.get_byte_order()
1591            (
1592                self.ilocalsym,
1593                self.nlocalsym,
1594                self.iextdefsym,
1595                self.nextdefsym,
1596                self.iundefsym,
1597                self.nundefsym,
1598                self.tocoff,
1599                self.ntoc,
1600                self.modtaboff,
1601                self.nmodtab,
1602                self.extrefsymoff,
1603                self.nextrefsyms,
1604                self.indirectsymoff,
1605                self.nindirectsyms,
1606                self.extreloff,
1607                self.nextrel,
1608                self.locreloff,
1609                self.nlocrel,
1610            ) = data.get_n_uint32(18)
1611
1612        def __str__(self):
1613            s = Mach.LoadCommand.__str__(self)
1614            # s += "ilocalsym = %u, nlocalsym = %u, " % (self.ilocalsym, self.nlocalsym)
1615            # s += "iextdefsym = %u, nextdefsym = %u, " % (self.iextdefsym, self.nextdefsym)
1616            # s += "iundefsym %u, nundefsym = %u, " % (self.iundefsym, self.nundefsym)
1617            # s += "tocoff = %#8.8x, ntoc = %u, " % (self.tocoff, self.ntoc)
1618            # s += "modtaboff = %#8.8x, nmodtab = %u, " % (self.modtaboff, self.nmodtab)
1619            # s += "extrefsymoff = %#8.8x, nextrefsyms = %u, " % (self.extrefsymoff, self.nextrefsyms)
1620            # s += "indirectsymoff = %#8.8x, nindirectsyms = %u, " % (self.indirectsymoff, self.nindirectsyms)
1621            # s += "extreloff = %#8.8x, nextrel = %u, " % (self.extreloff, self.nextrel)
1622            # s += "locreloff = %#8.8x, nlocrel = %u" % (self.locreloff,
1623            # self.nlocrel)
1624            s += "ilocalsym      = %-10u, nlocalsym     = %u\n" % (
1625                self.ilocalsym,
1626                self.nlocalsym,
1627            )
1628            s += (
1629                "                                             iextdefsym     = %-10u, nextdefsym    = %u\n"
1630                % (self.iextdefsym, self.nextdefsym)
1631            )
1632            s += (
1633                "                                             iundefsym      = %-10u, nundefsym     = %u\n"
1634                % (self.iundefsym, self.nundefsym)
1635            )
1636            s += (
1637                "                                             tocoff         = %#8.8x, ntoc          = %u\n"
1638                % (self.tocoff, self.ntoc)
1639            )
1640            s += (
1641                "                                             modtaboff      = %#8.8x, nmodtab       = %u\n"
1642                % (self.modtaboff, self.nmodtab)
1643            )
1644            s += (
1645                "                                             extrefsymoff   = %#8.8x, nextrefsyms   = %u\n"
1646                % (self.extrefsymoff, self.nextrefsyms)
1647            )
1648            s += (
1649                "                                             indirectsymoff = %#8.8x, nindirectsyms = %u\n"
1650                % (self.indirectsymoff, self.nindirectsyms)
1651            )
1652            s += (
1653                "                                             extreloff      = %#8.8x, nextrel       = %u\n"
1654                % (self.extreloff, self.nextrel)
1655            )
1656            s += (
1657                "                                             locreloff      = %#8.8x, nlocrel       = %u"
1658                % (self.locreloff, self.nlocrel)
1659            )
1660            return s
1661
1662    class SymtabLoadCommand(LoadCommand):
1663        def __init__(self, lc):
1664            Mach.LoadCommand.__init__(self, lc.command, lc.length, lc.file_off)
1665            self.symoff = 0
1666            self.nsyms = 0
1667            self.stroff = 0
1668            self.strsize = 0
1669
1670        def unpack(self, mach_file, data):
1671            byte_order_char = mach_file.magic.get_byte_order()
1672            self.symoff, self.nsyms, self.stroff, self.strsize = data.get_n_uint32(4)
1673
1674        def __str__(self):
1675            s = Mach.LoadCommand.__str__(self)
1676            s += "symoff = %#8.8x, nsyms = %u, stroff = %#8.8x, strsize = %u" % (
1677                self.symoff,
1678                self.nsyms,
1679                self.stroff,
1680                self.strsize,
1681            )
1682            return s
1683
1684    class UUIDLoadCommand(LoadCommand):
1685        def __init__(self, lc):
1686            Mach.LoadCommand.__init__(self, lc.command, lc.length, lc.file_off)
1687            self.uuid = None
1688
1689        def unpack(self, mach_file, data):
1690            uuid_data = data.get_n_uint8(16)
1691            uuid_str = ""
1692            for byte in uuid_data:
1693                uuid_str += "%2.2x" % byte
1694            self.uuid = uuid.UUID(uuid_str)
1695            mach_file.uuid = self.uuid
1696
1697        def __str__(self):
1698            s = Mach.LoadCommand.__str__(self)
1699            s += self.uuid.__str__()
1700            return s
1701
1702    class DataBlobLoadCommand(LoadCommand):
1703        def __init__(self, lc):
1704            Mach.LoadCommand.__init__(self, lc.command, lc.length, lc.file_off)
1705            self.dataoff = 0
1706            self.datasize = 0
1707
1708        def unpack(self, mach_file, data):
1709            byte_order_char = mach_file.magic.get_byte_order()
1710            self.dataoff, self.datasize = data.get_n_uint32(2)
1711
1712        def __str__(self):
1713            s = Mach.LoadCommand.__str__(self)
1714            s += "dataoff = %#8.8x, datasize = %u" % (self.dataoff, self.datasize)
1715            return s
1716
1717    class EncryptionInfoLoadCommand(LoadCommand):
1718        def __init__(self, lc):
1719            Mach.LoadCommand.__init__(self, lc.command, lc.length, lc.file_off)
1720            self.cryptoff = 0
1721            self.cryptsize = 0
1722            self.cryptid = 0
1723
1724        def unpack(self, mach_file, data):
1725            byte_order_char = mach_file.magic.get_byte_order()
1726            self.cryptoff, self.cryptsize, self.cryptid = data.get_n_uint32(3)
1727
1728        def __str__(self):
1729            s = Mach.LoadCommand.__str__(self)
1730            s += "file-range = [%#8.8x - %#8.8x), cryptsize = %u, cryptid = %u" % (
1731                self.cryptoff,
1732                self.cryptoff + self.cryptsize,
1733                self.cryptsize,
1734                self.cryptid,
1735            )
1736            return s
1737
1738    class SegmentLoadCommand(LoadCommand):
1739        def __init__(self, lc):
1740            Mach.LoadCommand.__init__(self, lc.command, lc.length, lc.file_off)
1741            self.segname = None
1742            self.vmaddr = 0
1743            self.vmsize = 0
1744            self.fileoff = 0
1745            self.filesize = 0
1746            self.maxprot = 0
1747            self.initprot = 0
1748            self.nsects = 0
1749            self.flags = 0
1750
1751        def unpack(self, mach_file, data):
1752            is_64 = self.command.get_enum_value() == LC_SEGMENT_64
1753            self.segname = data.get_fixed_length_c_string(16, "", True)
1754            if is_64:
1755                (
1756                    self.vmaddr,
1757                    self.vmsize,
1758                    self.fileoff,
1759                    self.filesize,
1760                ) = data.get_n_uint64(4)
1761            else:
1762                (
1763                    self.vmaddr,
1764                    self.vmsize,
1765                    self.fileoff,
1766                    self.filesize,
1767                ) = data.get_n_uint32(4)
1768            self.maxprot, self.initprot, self.nsects, self.flags = data.get_n_uint32(4)
1769            mach_file.segments.append(self)
1770            for i in range(self.nsects):
1771                section = Mach.Section()
1772                section.unpack(is_64, data)
1773                section.index = len(mach_file.sections)
1774                mach_file.sections.append(section)
1775
1776        def __str__(self):
1777            s = Mach.LoadCommand.__str__(self)
1778            if self.command.get_enum_value() == LC_SEGMENT:
1779                s += "%#8.8x %#8.8x %#8.8x %#8.8x " % (
1780                    self.vmaddr,
1781                    self.vmsize,
1782                    self.fileoff,
1783                    self.filesize,
1784                )
1785            else:
1786                s += "%#16.16x %#16.16x %#16.16x %#16.16x " % (
1787                    self.vmaddr,
1788                    self.vmsize,
1789                    self.fileoff,
1790                    self.filesize,
1791                )
1792            s += "%s %s %3u %#8.8x" % (
1793                vm_prot_names[self.maxprot],
1794                vm_prot_names[self.initprot],
1795                self.nsects,
1796                self.flags,
1797            )
1798            s += " " + self.segname
1799            return s
1800
1801    class NList:
1802        class Type:
1803            class Stab(dict_utils.Enum):
1804                enum = {
1805                    "N_GSYM": N_GSYM,
1806                    "N_FNAME": N_FNAME,
1807                    "N_FUN": N_FUN,
1808                    "N_STSYM": N_STSYM,
1809                    "N_LCSYM": N_LCSYM,
1810                    "N_BNSYM": N_BNSYM,
1811                    "N_OPT": N_OPT,
1812                    "N_RSYM": N_RSYM,
1813                    "N_SLINE": N_SLINE,
1814                    "N_ENSYM": N_ENSYM,
1815                    "N_SSYM": N_SSYM,
1816                    "N_SO": N_SO,
1817                    "N_OSO": N_OSO,
1818                    "N_LSYM": N_LSYM,
1819                    "N_BINCL": N_BINCL,
1820                    "N_SOL": N_SOL,
1821                    "N_PARAMS": N_PARAMS,
1822                    "N_VERSION": N_VERSION,
1823                    "N_OLEVEL": N_OLEVEL,
1824                    "N_PSYM": N_PSYM,
1825                    "N_EINCL": N_EINCL,
1826                    "N_ENTRY": N_ENTRY,
1827                    "N_LBRAC": N_LBRAC,
1828                    "N_EXCL": N_EXCL,
1829                    "N_RBRAC": N_RBRAC,
1830                    "N_BCOMM": N_BCOMM,
1831                    "N_ECOMM": N_ECOMM,
1832                    "N_ECOML": N_ECOML,
1833                    "N_LENG": N_LENG,
1834                }
1835
1836                def __init__(self, magic=0):
1837                    dict_utils.Enum.__init__(self, magic, self.enum)
1838
1839            def __init__(self, t=0):
1840                self.value = t
1841
1842            def __str__(self):
1843                n_type = self.value
1844                if n_type & N_STAB:
1845                    stab = Mach.NList.Type.Stab(self.value)
1846                    return "%s" % stab
1847                else:
1848                    type = self.value & N_TYPE
1849                    type_str = ""
1850                    if type == N_UNDF:
1851                        type_str = "N_UNDF"
1852                    elif type == N_ABS:
1853                        type_str = "N_ABS "
1854                    elif type == N_SECT:
1855                        type_str = "N_SECT"
1856                    elif type == N_PBUD:
1857                        type_str = "N_PBUD"
1858                    elif type == N_INDR:
1859                        type_str = "N_INDR"
1860                    else:
1861                        type_str = "??? (%#2.2x)" % type
1862                    if n_type & N_PEXT:
1863                        type_str += " | PEXT"
1864                    if n_type & N_EXT:
1865                        type_str += " | EXT "
1866                    return type_str
1867
1868        def __init__(self):
1869            self.index = 0
1870            self.name_offset = 0
1871            self.name = 0
1872            self.type = Mach.NList.Type()
1873            self.sect_idx = 0
1874            self.desc = 0
1875            self.value = 0
1876
1877        def unpack(self, mach_file, data, symtab_lc):
1878            self.index = len(mach_file.symbols)
1879            self.name_offset = data.get_uint32()
1880            self.type.value, self.sect_idx = data.get_n_uint8(2)
1881            self.desc = data.get_uint16()
1882            if mach_file.is_64_bit():
1883                self.value = data.get_uint64()
1884            else:
1885                self.value = data.get_uint32()
1886            data.push_offset_and_seek(
1887                mach_file.file_off + symtab_lc.stroff + self.name_offset
1888            )
1889            # print "get string for symbol[%u]" % self.index
1890            self.name = data.get_c_string()
1891            data.pop_offset_and_seek()
1892
1893        def __str__(self):
1894            name_display = ""
1895            if len(self.name):
1896                name_display = ' "%s"' % self.name
1897            return "%#8.8x %#2.2x (%-20s) %#2.2x %#4.4x %16.16x%s" % (
1898                self.name_offset,
1899                self.type.value,
1900                self.type,
1901                self.sect_idx,
1902                self.desc,
1903                self.value,
1904                name_display,
1905            )
1906
1907    class Interactive(cmd.Cmd):
1908        """Interactive command interpreter to mach-o files."""
1909
1910        def __init__(self, mach, options):
1911            cmd.Cmd.__init__(self)
1912            self.intro = "Interactive mach-o command interpreter"
1913            self.prompt = "mach-o: %s %% " % mach.path
1914            self.mach = mach
1915            self.options = options
1916
1917        def default(self, line):
1918            """Catch all for unknown command, which will exit the interpreter."""
1919            print("uknown command: %s" % line)
1920            return True
1921
1922        def do_q(self, line):
1923            """Quit command"""
1924            return True
1925
1926        def do_quit(self, line):
1927            """Quit command"""
1928            return True
1929
1930        def do_header(self, line):
1931            """Dump mach-o file headers"""
1932            self.mach.dump_header(True, self.options)
1933            return False
1934
1935        def do_load(self, line):
1936            """Dump all mach-o load commands"""
1937            self.mach.dump_load_commands(True, self.options)
1938            return False
1939
1940        def do_sections(self, line):
1941            """Dump all mach-o sections"""
1942            self.mach.dump_sections(True, self.options)
1943            return False
1944
1945        def do_symtab(self, line):
1946            """Dump all mach-o symbols in the symbol table"""
1947            self.mach.dump_symtab(True, self.options)
1948            return False
1949
1950
1951if __name__ == "__main__":
1952    parser = optparse.OptionParser(
1953        description="A script that parses skinny and universal mach-o files."
1954    )
1955    parser.add_option(
1956        "--arch",
1957        "-a",
1958        type="string",
1959        metavar="arch",
1960        dest="archs",
1961        action="append",
1962        help="specify one or more architectures by name",
1963    )
1964    parser.add_option(
1965        "-v",
1966        "--verbose",
1967        action="store_true",
1968        dest="verbose",
1969        help="display verbose debug info",
1970        default=False,
1971    )
1972    parser.add_option(
1973        "-H",
1974        "--header",
1975        action="store_true",
1976        dest="dump_header",
1977        help="dump the mach-o file header",
1978        default=False,
1979    )
1980    parser.add_option(
1981        "-l",
1982        "--load-commands",
1983        action="store_true",
1984        dest="dump_load_commands",
1985        help="dump the mach-o load commands",
1986        default=False,
1987    )
1988    parser.add_option(
1989        "-s",
1990        "--symtab",
1991        action="store_true",
1992        dest="dump_symtab",
1993        help="dump the mach-o symbol table",
1994        default=False,
1995    )
1996    parser.add_option(
1997        "-S",
1998        "--sections",
1999        action="store_true",
2000        dest="dump_sections",
2001        help="dump the mach-o sections",
2002        default=False,
2003    )
2004    parser.add_option(
2005        "--section",
2006        type="string",
2007        metavar="sectname",
2008        dest="section_names",
2009        action="append",
2010        help="Specify one or more section names to dump",
2011        default=[],
2012    )
2013    parser.add_option(
2014        "-o",
2015        "--out",
2016        type="string",
2017        dest="outfile",
2018        help="Used in conjunction with the --section=NAME option to save a single section's data to disk.",
2019        default=False,
2020    )
2021    parser.add_option(
2022        "-i",
2023        "--interactive",
2024        action="store_true",
2025        dest="interactive",
2026        help="enable interactive mode",
2027        default=False,
2028    )
2029    parser.add_option(
2030        "-m",
2031        "--mangled",
2032        action="store_true",
2033        dest="find_mangled",
2034        help="dump all mangled names in a mach file",
2035        default=False,
2036    )
2037    parser.add_option(
2038        "-c",
2039        "--compare",
2040        action="store_true",
2041        dest="compare",
2042        help="compare two mach files",
2043        default=False,
2044    )
2045    parser.add_option(
2046        "-M",
2047        "--extract-modules",
2048        action="store_true",
2049        dest="extract_modules",
2050        help="Extract modules from file",
2051        default=False,
2052    )
2053    parser.add_option(
2054        "-C",
2055        "--count",
2056        type="int",
2057        dest="max_count",
2058        help="Sets the max byte count when dumping section data",
2059        default=-1,
2060    )
2061
2062    (options, mach_files) = parser.parse_args()
2063    if options.extract_modules:
2064        if options.section_names:
2065            print("error: can't use --section option with the --extract-modules option")
2066            exit(1)
2067        if not options.outfile:
2068            print(
2069                "error: the --output=FILE option must be specified with the --extract-modules option"
2070            )
2071            exit(1)
2072        options.section_names.append("__apple_ast")
2073    if options.compare:
2074        if len(mach_files) == 2:
2075            mach_a = Mach()
2076            mach_b = Mach()
2077            mach_a.parse(mach_files[0])
2078            mach_b.parse(mach_files[1])
2079            mach_a.compare(mach_b)
2080        else:
2081            print("error: --compare takes two mach files as arguments")
2082    else:
2083        if not (
2084            options.dump_header
2085            or options.dump_load_commands
2086            or options.dump_symtab
2087            or options.dump_sections
2088            or options.find_mangled
2089            or options.section_names
2090        ):
2091            options.dump_header = True
2092            options.dump_load_commands = True
2093        if options.verbose:
2094            print("options", options)
2095            print("mach_files", mach_files)
2096        for path in mach_files:
2097            mach = Mach()
2098            mach.parse(path)
2099            if options.interactive:
2100                interpreter = Mach.Interactive(mach, options)
2101                interpreter.cmdloop()
2102            else:
2103                mach.dump(options)
2104