1*515bc8c1Sserge-sans-paille#!/usr/bin/env python
2525cd59fSSerge Gueltonfrom __future__ import print_function
3de01668bSGreg Clayton
495c23f66SGreg Claytonimport cmd
5de01668bSGreg Claytonimport optparse
6de01668bSGreg Claytonimport os
7de01668bSGreg Claytonimport shlex
8de01668bSGreg Claytonimport struct
9de01668bSGreg Claytonimport sys
10de01668bSGreg Clayton
11de01668bSGreg ClaytonARMAG = "!<arch>\n"
12de01668bSGreg ClaytonSARMAG = 8
13de01668bSGreg ClaytonARFMAG = "`\n"
14de01668bSGreg ClaytonAR_EFMT1 = "#1/"
15de01668bSGreg Clayton
16de01668bSGreg Clayton
17de01668bSGreg Claytondef memdump(src, bytes_per_line=16, address=0):
18de01668bSGreg Clayton    FILTER = ''.join([(len(repr(chr(x))) == 3) and chr(x) or '.'
19de01668bSGreg Clayton                     for x in range(256)])
20de01668bSGreg Clayton    for i in range(0, len(src), bytes_per_line):
21de01668bSGreg Clayton        s = src[i:i+bytes_per_line]
22de01668bSGreg Clayton        hex_bytes = ' '.join(["%02x" % (ord(x)) for x in s])
23de01668bSGreg Clayton        ascii = s.translate(FILTER)
24de01668bSGreg Clayton        print("%#08.8x: %-*s %s" % (address+i, bytes_per_line*3, hex_bytes,
25de01668bSGreg Clayton                                    ascii))
26de01668bSGreg Clayton
27de01668bSGreg Clayton
28de01668bSGreg Claytonclass Object(object):
29de01668bSGreg Clayton    def __init__(self, file):
30de01668bSGreg Clayton        def read_str(file, str_len):
31de01668bSGreg Clayton            return file.read(str_len).rstrip('\0 ')
32de01668bSGreg Clayton
33de01668bSGreg Clayton        def read_int(file, str_len, base):
34de01668bSGreg Clayton            return int(read_str(file, str_len), base)
35de01668bSGreg Clayton
36de01668bSGreg Clayton        self.offset = file.tell()
37de01668bSGreg Clayton        self.file = file
38de01668bSGreg Clayton        self.name = read_str(file, 16)
39de01668bSGreg Clayton        self.date = read_int(file, 12, 10)
40de01668bSGreg Clayton        self.uid = read_int(file, 6, 10)
41de01668bSGreg Clayton        self.gid = read_int(file, 6, 10)
42de01668bSGreg Clayton        self.mode = read_int(file, 8, 8)
43de01668bSGreg Clayton        self.size = read_int(file, 10, 10)
44de01668bSGreg Clayton        if file.read(2) != ARFMAG:
45de01668bSGreg Clayton            raise ValueError('invalid BSD object at offset %#08.8x' % (
46de01668bSGreg Clayton                             self.offset))
47de01668bSGreg Clayton        # If we have an extended name read it. Extended names start with
48de01668bSGreg Clayton        name_len = 0
49de01668bSGreg Clayton        if self.name.startswith(AR_EFMT1):
50de01668bSGreg Clayton            name_len = int(self.name[len(AR_EFMT1):], 10)
51de01668bSGreg Clayton            self.name = read_str(file, name_len)
52de01668bSGreg Clayton        self.obj_offset = file.tell()
53de01668bSGreg Clayton        self.obj_size = self.size - name_len
54de01668bSGreg Clayton        file.seek(self.obj_size, 1)
55de01668bSGreg Clayton
56de01668bSGreg Clayton    def dump(self, f=sys.stdout, flat=True):
57de01668bSGreg Clayton        if flat:
58de01668bSGreg Clayton            f.write('%#08.8x: %#08.8x %5u %5u %6o %#08.8x %s\n' % (self.offset,
59de01668bSGreg Clayton                    self.date, self.uid, self.gid, self.mode, self.size,
60de01668bSGreg Clayton                    self.name))
61de01668bSGreg Clayton        else:
62de01668bSGreg Clayton            f.write('%#08.8x: \n' % self.offset)
63de01668bSGreg Clayton            f.write(' name = "%s"\n' % self.name)
64de01668bSGreg Clayton            f.write(' date = %#08.8x\n' % self.date)
65de01668bSGreg Clayton            f.write('  uid = %i\n' % self.uid)
66de01668bSGreg Clayton            f.write('  gid = %i\n' % self.gid)
67de01668bSGreg Clayton            f.write(' mode = %o\n' % self.mode)
68de01668bSGreg Clayton            f.write(' size = %#08.8x\n' % (self.size))
69de01668bSGreg Clayton            self.file.seek(self.obj_offset, 0)
70de01668bSGreg Clayton            first_bytes = self.file.read(4)
71de01668bSGreg Clayton            f.write('bytes = ')
72de01668bSGreg Clayton            memdump(first_bytes)
73de01668bSGreg Clayton
74de01668bSGreg Clayton    def get_bytes(self):
75de01668bSGreg Clayton        saved_pos = self.file.tell()
76de01668bSGreg Clayton        self.file.seek(self.obj_offset, 0)
77de01668bSGreg Clayton        bytes = self.file.read(self.obj_size)
78de01668bSGreg Clayton        self.file.seek(saved_pos, 0)
79de01668bSGreg Clayton        return bytes
80de01668bSGreg Clayton
8195c23f66SGreg Clayton    def save(self, path=None, overwrite=False):
8295c23f66SGreg Clayton        '''
8395c23f66SGreg Clayton            Save the contents of the object to disk using 'path' argument as
8495c23f66SGreg Clayton            the path, or save it to the current working directory using the
8595c23f66SGreg Clayton            object name.
8695c23f66SGreg Clayton        '''
8795c23f66SGreg Clayton
8895c23f66SGreg Clayton        if path is None:
8995c23f66SGreg Clayton            path = self.name
9095c23f66SGreg Clayton        if not overwrite and os.path.exists(path):
9195c23f66SGreg Clayton            print('error: outfile "%s" already exists' % (path))
9295c23f66SGreg Clayton            return
9395c23f66SGreg Clayton        print('Saving "%s" to "%s"...' % (self.name, path))
9495c23f66SGreg Clayton        with open(path, 'w') as f:
9595c23f66SGreg Clayton            f.write(self.get_bytes())
9695c23f66SGreg Clayton
97de01668bSGreg Clayton
98de01668bSGreg Claytonclass StringTable(object):
99de01668bSGreg Clayton    def __init__(self, bytes):
100de01668bSGreg Clayton        self.bytes = bytes
101de01668bSGreg Clayton
102de01668bSGreg Clayton    def get_string(self, offset):
103de01668bSGreg Clayton        length = len(self.bytes)
104de01668bSGreg Clayton        if offset >= length:
105de01668bSGreg Clayton            return None
106de01668bSGreg Clayton        return self.bytes[offset:self.bytes.find('\0', offset)]
107de01668bSGreg Clayton
108de01668bSGreg Clayton
109de01668bSGreg Claytonclass Archive(object):
110de01668bSGreg Clayton    def __init__(self, path):
111de01668bSGreg Clayton        self.path = path
112de01668bSGreg Clayton        self.file = open(path, 'r')
113de01668bSGreg Clayton        self.objects = []
114de01668bSGreg Clayton        self.offset_to_object = {}
115de01668bSGreg Clayton        if self.file.read(SARMAG) != ARMAG:
116de01668bSGreg Clayton            print("error: file isn't a BSD archive")
117de01668bSGreg Clayton        while True:
118de01668bSGreg Clayton            try:
119de01668bSGreg Clayton                self.objects.append(Object(self.file))
120de01668bSGreg Clayton            except ValueError:
121de01668bSGreg Clayton                break
122de01668bSGreg Clayton
123de01668bSGreg Clayton    def get_object_at_offset(self, offset):
124de01668bSGreg Clayton        if offset in self.offset_to_object:
125de01668bSGreg Clayton            return self.offset_to_object[offset]
126de01668bSGreg Clayton        for obj in self.objects:
127de01668bSGreg Clayton            if obj.offset == offset:
128de01668bSGreg Clayton                self.offset_to_object[offset] = obj
129de01668bSGreg Clayton                return obj
130de01668bSGreg Clayton        return None
131de01668bSGreg Clayton
132de01668bSGreg Clayton    def find(self, name, mtime=None, f=sys.stdout):
133de01668bSGreg Clayton        '''
134de01668bSGreg Clayton            Find an object(s) by name with optional modification time. There
135de01668bSGreg Clayton            can be multple objects with the same name inside and possibly with
136de01668bSGreg Clayton            the same modification time within a BSD archive so clients must be
137de01668bSGreg Clayton            prepared to get multiple results.
138de01668bSGreg Clayton        '''
139de01668bSGreg Clayton        matches = []
140de01668bSGreg Clayton        for obj in self.objects:
141de01668bSGreg Clayton            if obj.name == name and (mtime is None or mtime == obj.date):
142de01668bSGreg Clayton                matches.append(obj)
143de01668bSGreg Clayton        return matches
144de01668bSGreg Clayton
145de01668bSGreg Clayton    @classmethod
146de01668bSGreg Clayton    def dump_header(self, f=sys.stdout):
147de01668bSGreg Clayton        f.write('            DATE       UID   GID   MODE   SIZE       NAME\n')
148de01668bSGreg Clayton        f.write('            ---------- ----- ----- ------ ---------- '
149de01668bSGreg Clayton                '--------------\n')
150de01668bSGreg Clayton
151de01668bSGreg Clayton    def get_symdef(self):
152de01668bSGreg Clayton        def get_uint32(file):
153de01668bSGreg Clayton            '''Extract a uint32_t from the current file position.'''
154de01668bSGreg Clayton            v, = struct.unpack('=I', file.read(4))
155de01668bSGreg Clayton            return v
156de01668bSGreg Clayton
157de01668bSGreg Clayton        for obj in self.objects:
158de01668bSGreg Clayton            symdef = []
159de01668bSGreg Clayton            if obj.name.startswith("__.SYMDEF"):
160de01668bSGreg Clayton                self.file.seek(obj.obj_offset, 0)
161de01668bSGreg Clayton                ranlib_byte_size = get_uint32(self.file)
162de01668bSGreg Clayton                num_ranlib_structs = ranlib_byte_size/8
163de01668bSGreg Clayton                str_offset_pairs = []
164de01668bSGreg Clayton                for _ in range(num_ranlib_structs):
165de01668bSGreg Clayton                    strx = get_uint32(self.file)
166de01668bSGreg Clayton                    offset = get_uint32(self.file)
167de01668bSGreg Clayton                    str_offset_pairs.append((strx, offset))
168de01668bSGreg Clayton                strtab_len = get_uint32(self.file)
169de01668bSGreg Clayton                strtab = StringTable(self.file.read(strtab_len))
170de01668bSGreg Clayton                for s in str_offset_pairs:
171de01668bSGreg Clayton                    symdef.append((strtab.get_string(s[0]), s[1]))
172de01668bSGreg Clayton            return symdef
173de01668bSGreg Clayton
174de01668bSGreg Clayton    def get_object_dicts(self):
175de01668bSGreg Clayton        '''
176de01668bSGreg Clayton            Returns an array of object dictionaries that contain they following
177de01668bSGreg Clayton            keys:
178de01668bSGreg Clayton                'object': the actual bsd.Object instance
179de01668bSGreg Clayton                'symdefs': an array of symbol names that the object contains
180de01668bSGreg Clayton                           as found in the "__.SYMDEF" item in the archive
181de01668bSGreg Clayton        '''
182de01668bSGreg Clayton        symdefs = self.get_symdef()
183de01668bSGreg Clayton        symdef_dict = {}
184de01668bSGreg Clayton        if symdefs:
185de01668bSGreg Clayton            for (name, offset) in symdefs:
186de01668bSGreg Clayton                if offset in symdef_dict:
187de01668bSGreg Clayton                    object_dict = symdef_dict[offset]
188de01668bSGreg Clayton                else:
189de01668bSGreg Clayton                    object_dict = {
190de01668bSGreg Clayton                        'object': self.get_object_at_offset(offset),
191de01668bSGreg Clayton                        'symdefs': []
192de01668bSGreg Clayton                    }
193de01668bSGreg Clayton                    symdef_dict[offset] = object_dict
194de01668bSGreg Clayton                object_dict['symdefs'].append(name)
195de01668bSGreg Clayton        object_dicts = []
196de01668bSGreg Clayton        for offset in sorted(symdef_dict):
197de01668bSGreg Clayton            object_dicts.append(symdef_dict[offset])
198de01668bSGreg Clayton        return object_dicts
199de01668bSGreg Clayton
200de01668bSGreg Clayton    def dump(self, f=sys.stdout, flat=True):
201de01668bSGreg Clayton        f.write('%s:\n' % self.path)
202de01668bSGreg Clayton        if flat:
203de01668bSGreg Clayton            self.dump_header(f=f)
204de01668bSGreg Clayton        for obj in self.objects:
205de01668bSGreg Clayton            obj.dump(f=f, flat=flat)
206de01668bSGreg Clayton
20795c23f66SGreg Claytonclass Interactive(cmd.Cmd):
20895c23f66SGreg Clayton    '''Interactive prompt for exploring contents of BSD archive files, type
20995c23f66SGreg Clayton      "help" to see a list of supported commands.'''
21095c23f66SGreg Clayton    image_option_parser = None
21195c23f66SGreg Clayton
21295c23f66SGreg Clayton    def __init__(self, archives):
21395c23f66SGreg Clayton        cmd.Cmd.__init__(self)
21495c23f66SGreg Clayton        self.use_rawinput = False
21595c23f66SGreg Clayton        self.intro = ('Interactive  BSD archive prompt, type "help" to see a '
21695c23f66SGreg Clayton                      'list of supported commands.')
21795c23f66SGreg Clayton        self.archives = archives
21895c23f66SGreg Clayton        self.prompt = '% '
21995c23f66SGreg Clayton
22095c23f66SGreg Clayton    def default(self, line):
22195c23f66SGreg Clayton        '''Catch all for unknown command, which will exit the interpreter.'''
22295c23f66SGreg Clayton        print("unknown command: %s" % line)
22395c23f66SGreg Clayton        return True
22495c23f66SGreg Clayton
22595c23f66SGreg Clayton    def do_q(self, line):
22695c23f66SGreg Clayton        '''Quit command'''
22795c23f66SGreg Clayton        return True
22895c23f66SGreg Clayton
22995c23f66SGreg Clayton    def do_quit(self, line):
23095c23f66SGreg Clayton        '''Quit command'''
23195c23f66SGreg Clayton        return True
23295c23f66SGreg Clayton
23395c23f66SGreg Clayton    def do_extract(self, line):
23495c23f66SGreg Clayton        args = shlex.split(line)
23595c23f66SGreg Clayton        if args:
23695c23f66SGreg Clayton            extracted = False
23795c23f66SGreg Clayton            for object_name in args:
23895c23f66SGreg Clayton                for archive in self.archives:
23995c23f66SGreg Clayton                    matches = archive.find(object_name)
24095c23f66SGreg Clayton                    if matches:
24195c23f66SGreg Clayton                        for object in matches:
24295c23f66SGreg Clayton                            object.save(overwrite=False)
24395c23f66SGreg Clayton                            extracted = True
24495c23f66SGreg Clayton            if not extracted:
24595c23f66SGreg Clayton                print('error: no object matches "%s" in any archives' % (
24695c23f66SGreg Clayton                        object_name))
24795c23f66SGreg Clayton        else:
24895c23f66SGreg Clayton            print('error: must specify the name of an object to extract')
24995c23f66SGreg Clayton
25095c23f66SGreg Clayton    def do_ls(self, line):
25195c23f66SGreg Clayton        args = shlex.split(line)
25295c23f66SGreg Clayton        if args:
25395c23f66SGreg Clayton            for object_name in args:
25495c23f66SGreg Clayton                for archive in self.archives:
25595c23f66SGreg Clayton                    matches = archive.find(object_name)
25695c23f66SGreg Clayton                    if matches:
25795c23f66SGreg Clayton                        for object in matches:
25895c23f66SGreg Clayton                            object.dump(flat=False)
25995c23f66SGreg Clayton                    else:
26095c23f66SGreg Clayton                        print('error: no object matches "%s" in "%s"' % (
26195c23f66SGreg Clayton                                object_name, archive.path))
26295c23f66SGreg Clayton        else:
26395c23f66SGreg Clayton            for archive in self.archives:
26495c23f66SGreg Clayton                archive.dump(flat=True)
26595c23f66SGreg Clayton                print('')
26695c23f66SGreg Clayton
26795c23f66SGreg Clayton
268de01668bSGreg Clayton
269de01668bSGreg Claytondef main():
270de01668bSGreg Clayton    parser = optparse.OptionParser(
271de01668bSGreg Clayton        prog='bsd',
272de01668bSGreg Clayton        description='Utility for BSD archives')
273de01668bSGreg Clayton    parser.add_option(
274de01668bSGreg Clayton        '--object',
275de01668bSGreg Clayton        type='string',
276de01668bSGreg Clayton        dest='object_name',
277de01668bSGreg Clayton        default=None,
278de01668bSGreg Clayton        help=('Specify the name of a object within the BSD archive to get '
279de01668bSGreg Clayton              'information on'))
280de01668bSGreg Clayton    parser.add_option(
281de01668bSGreg Clayton        '-s', '--symbol',
282de01668bSGreg Clayton        type='string',
283de01668bSGreg Clayton        dest='find_symbol',
284de01668bSGreg Clayton        default=None,
285de01668bSGreg Clayton        help=('Specify the name of a symbol within the BSD archive to get '
286de01668bSGreg Clayton              'information on from SYMDEF'))
287de01668bSGreg Clayton    parser.add_option(
288de01668bSGreg Clayton        '--symdef',
289de01668bSGreg Clayton        action='store_true',
290de01668bSGreg Clayton        dest='symdef',
291de01668bSGreg Clayton        default=False,
292de01668bSGreg Clayton        help=('Dump the information in the SYMDEF.'))
293de01668bSGreg Clayton    parser.add_option(
294de01668bSGreg Clayton        '-v', '--verbose',
295de01668bSGreg Clayton        action='store_true',
296de01668bSGreg Clayton        dest='verbose',
297de01668bSGreg Clayton        default=False,
298de01668bSGreg Clayton        help='Enable verbose output')
299de01668bSGreg Clayton    parser.add_option(
300de01668bSGreg Clayton        '-e', '--extract',
301de01668bSGreg Clayton        action='store_true',
302de01668bSGreg Clayton        dest='extract',
303de01668bSGreg Clayton        default=False,
304de01668bSGreg Clayton        help=('Specify this to extract the object specified with the --object '
305de01668bSGreg Clayton              'option. There must be only one object with a matching name or '
306de01668bSGreg Clayton              'the --mtime option must be specified to uniquely identify a '
307de01668bSGreg Clayton              'single object.'))
308de01668bSGreg Clayton    parser.add_option(
309de01668bSGreg Clayton        '-m', '--mtime',
310de01668bSGreg Clayton        type='int',
311de01668bSGreg Clayton        dest='mtime',
312de01668bSGreg Clayton        default=None,
313de01668bSGreg Clayton        help=('Specify the modification time of the object an object. This '
314de01668bSGreg Clayton              'option is used with either the --object or --extract options.'))
315de01668bSGreg Clayton    parser.add_option(
316de01668bSGreg Clayton        '-o', '--outfile',
317de01668bSGreg Clayton        type='string',
318de01668bSGreg Clayton        dest='outfile',
319de01668bSGreg Clayton        default=None,
320de01668bSGreg Clayton        help=('Specify a different name or path for the file to extract when '
321de01668bSGreg Clayton              'using the --extract option. If this option isn\'t specified, '
322de01668bSGreg Clayton              'then the extracted object file will be extracted into the '
323de01668bSGreg Clayton              'current working directory if a file doesn\'t already exist '
324de01668bSGreg Clayton              'with that name.'))
32595c23f66SGreg Clayton    parser.add_option(
32695c23f66SGreg Clayton        '-i', '--interactive',
32795c23f66SGreg Clayton        action='store_true',
32895c23f66SGreg Clayton        dest='interactive',
32995c23f66SGreg Clayton        default=False,
33095c23f66SGreg Clayton        help=('Enter an interactive shell that allows users to interactively '
33195c23f66SGreg Clayton              'explore contents of .a files.'))
332de01668bSGreg Clayton
333de01668bSGreg Clayton    (options, args) = parser.parse_args(sys.argv[1:])
334de01668bSGreg Clayton
33595c23f66SGreg Clayton    if options.interactive:
33695c23f66SGreg Clayton        archives = []
33795c23f66SGreg Clayton        for path in args:
33895c23f66SGreg Clayton            archives.append(Archive(path))
33995c23f66SGreg Clayton        interpreter = Interactive(archives)
34095c23f66SGreg Clayton        interpreter.cmdloop()
34195c23f66SGreg Clayton        return
34295c23f66SGreg Clayton
343de01668bSGreg Clayton    for path in args:
344de01668bSGreg Clayton        archive = Archive(path)
345de01668bSGreg Clayton        if options.object_name:
346de01668bSGreg Clayton            print('%s:\n' % (path))
347de01668bSGreg Clayton            matches = archive.find(options.object_name, options.mtime)
348de01668bSGreg Clayton            if matches:
349de01668bSGreg Clayton                dump_all = True
350de01668bSGreg Clayton                if options.extract:
351de01668bSGreg Clayton                    if len(matches) == 1:
352de01668bSGreg Clayton                        dump_all = False
35395c23f66SGreg Clayton                        matches[0].save(path=options.outfile, overwrite=False)
354de01668bSGreg Clayton                    else:
355de01668bSGreg Clayton                        print('error: multiple objects match "%s". Specify '
356de01668bSGreg Clayton                              'the modification time using --mtime.' % (
357de01668bSGreg Clayton                                options.object_name))
358de01668bSGreg Clayton                if dump_all:
359de01668bSGreg Clayton                    for obj in matches:
360de01668bSGreg Clayton                        obj.dump(flat=False)
361de01668bSGreg Clayton            else:
362de01668bSGreg Clayton                print('error: object "%s" not found in archive' % (
363de01668bSGreg Clayton                      options.object_name))
364de01668bSGreg Clayton        elif options.find_symbol:
365de01668bSGreg Clayton            symdefs = archive.get_symdef()
366de01668bSGreg Clayton            if symdefs:
367de01668bSGreg Clayton                success = False
368de01668bSGreg Clayton                for (name, offset) in symdefs:
369de01668bSGreg Clayton                    obj = archive.get_object_at_offset(offset)
370de01668bSGreg Clayton                    if name == options.find_symbol:
371de01668bSGreg Clayton                        print('Found "%s" in:' % (options.find_symbol))
372de01668bSGreg Clayton                        obj.dump(flat=False)
373de01668bSGreg Clayton                        success = True
374de01668bSGreg Clayton                if not success:
375de01668bSGreg Clayton                    print('Didn\'t find "%s" in any objects' % (
376de01668bSGreg Clayton                          options.find_symbol))
377de01668bSGreg Clayton            else:
378de01668bSGreg Clayton                print("error: no __.SYMDEF was found")
379de01668bSGreg Clayton        elif options.symdef:
380de01668bSGreg Clayton            object_dicts = archive.get_object_dicts()
381de01668bSGreg Clayton            for object_dict in object_dicts:
382de01668bSGreg Clayton                object_dict['object'].dump(flat=False)
383de01668bSGreg Clayton                print("symbols:")
384de01668bSGreg Clayton                for name in object_dict['symdefs']:
385de01668bSGreg Clayton                    print("  %s" % (name))
386de01668bSGreg Clayton        else:
387de01668bSGreg Clayton            archive.dump(flat=not options.verbose)
388de01668bSGreg Clayton
389de01668bSGreg Clayton
390de01668bSGreg Claytonif __name__ == '__main__':
391de01668bSGreg Clayton    main()
392de01668bSGreg Clayton
393de01668bSGreg Clayton
394de01668bSGreg Claytondef print_mtime_error(result, dmap_mtime, actual_mtime):
395525cd59fSSerge Guelton    print("error: modification time in debug map (%#08.8x) doesn't "
396de01668bSGreg Clayton                     "match the .o file modification time (%#08.8x)" % (
397525cd59fSSerge Guelton                        dmap_mtime, actual_mtime), file=result)
398de01668bSGreg Clayton
399de01668bSGreg Clayton
400de01668bSGreg Claytondef print_file_missing_error(result, path):
401525cd59fSSerge Guelton    print("error: file \"%s\" doesn't exist" % (path), file=result)
402de01668bSGreg Clayton
403de01668bSGreg Clayton
404de01668bSGreg Claytondef print_multiple_object_matches(result, object_name, mtime, matches):
405525cd59fSSerge Guelton    print("error: multiple matches for object '%s' with with "
406525cd59fSSerge Guelton                     "modification time %#08.8x:" % (object_name, mtime), file=result)
407de01668bSGreg Clayton    Archive.dump_header(f=result)
408de01668bSGreg Clayton    for match in matches:
409de01668bSGreg Clayton        match.dump(f=result, flat=True)
410de01668bSGreg Clayton
411de01668bSGreg Clayton
412de01668bSGreg Claytondef print_archive_object_error(result, object_name, mtime, archive):
413de01668bSGreg Clayton    matches = archive.find(object_name, f=result)
414de01668bSGreg Clayton    if len(matches) > 0:
415525cd59fSSerge Guelton        print("error: no objects have a modification time that "
416de01668bSGreg Clayton                         "matches %#08.8x for '%s'. Potential matches:" % (
417525cd59fSSerge Guelton                            mtime, object_name), file=result)
418de01668bSGreg Clayton        Archive.dump_header(f=result)
419de01668bSGreg Clayton        for match in matches:
420de01668bSGreg Clayton            match.dump(f=result, flat=True)
421de01668bSGreg Clayton    else:
422525cd59fSSerge Guelton        print("error: no object named \"%s\" found in archive:" % (
423525cd59fSSerge Guelton            object_name), file=result)
424de01668bSGreg Clayton        Archive.dump_header(f=result)
425de01668bSGreg Clayton        for match in archive.objects:
426de01668bSGreg Clayton            match.dump(f=result, flat=True)
427de01668bSGreg Clayton        # archive.dump(f=result, flat=True)
428de01668bSGreg Clayton
429de01668bSGreg Clayton
430de01668bSGreg Claytonclass VerifyDebugMapCommand:
431de01668bSGreg Clayton    name = "verify-debug-map-objects"
432de01668bSGreg Clayton
433de01668bSGreg Clayton    def create_options(self):
434de01668bSGreg Clayton        usage = "usage: %prog [options]"
435de01668bSGreg Clayton        description = '''This command reports any .o files that are missing
436de01668bSGreg Claytonor whose modification times don't match in the debug map of an executable.'''
437de01668bSGreg Clayton
438de01668bSGreg Clayton        self.parser = optparse.OptionParser(
439de01668bSGreg Clayton            description=description,
440de01668bSGreg Clayton            prog=self.name,
441de01668bSGreg Clayton            usage=usage,
442de01668bSGreg Clayton            add_help_option=False)
443de01668bSGreg Clayton
444de01668bSGreg Clayton        self.parser.add_option(
445de01668bSGreg Clayton            '-e', '--errors',
446de01668bSGreg Clayton            action='store_true',
447de01668bSGreg Clayton            dest='errors',
448de01668bSGreg Clayton            default=False,
449de01668bSGreg Clayton            help="Only show errors")
450de01668bSGreg Clayton
451de01668bSGreg Clayton    def get_short_help(self):
452de01668bSGreg Clayton        return "Verify debug map object files."
453de01668bSGreg Clayton
454de01668bSGreg Clayton    def get_long_help(self):
455de01668bSGreg Clayton        return self.help_string
456de01668bSGreg Clayton
457de01668bSGreg Clayton    def __init__(self, debugger, unused):
458de01668bSGreg Clayton        self.create_options()
459de01668bSGreg Clayton        self.help_string = self.parser.format_help()
460de01668bSGreg Clayton
461de01668bSGreg Clayton    def __call__(self, debugger, command, exe_ctx, result):
462de01668bSGreg Clayton        import lldb
463de01668bSGreg Clayton        # Use the Shell Lexer to properly parse up command options just like a
464de01668bSGreg Clayton        # shell would
465de01668bSGreg Clayton        command_args = shlex.split(command)
466de01668bSGreg Clayton
467de01668bSGreg Clayton        try:
468de01668bSGreg Clayton            (options, args) = self.parser.parse_args(command_args)
469de01668bSGreg Clayton        except:
470de01668bSGreg Clayton            result.SetError("option parsing failed")
471de01668bSGreg Clayton            return
472de01668bSGreg Clayton
473de01668bSGreg Clayton        # Always get program state from the SBExecutionContext passed in
474de01668bSGreg Clayton        target = exe_ctx.GetTarget()
475de01668bSGreg Clayton        if not target.IsValid():
476de01668bSGreg Clayton            result.SetError("invalid target")
477de01668bSGreg Clayton            return
478de01668bSGreg Clayton        archives = {}
479de01668bSGreg Clayton        for module_spec in args:
480de01668bSGreg Clayton            module = target.module[module_spec]
481de01668bSGreg Clayton            if not (module and module.IsValid()):
482de01668bSGreg Clayton                result.SetError('error: invalid module specification: "%s". '
483de01668bSGreg Clayton                                'Specify the full path, basename, or UUID of '
484de01668bSGreg Clayton                                'a module ' % (module_spec))
485de01668bSGreg Clayton                return
486de01668bSGreg Clayton            num_symbols = module.GetNumSymbols()
487de01668bSGreg Clayton            num_errors = 0
488de01668bSGreg Clayton            for i in range(num_symbols):
489de01668bSGreg Clayton                symbol = module.GetSymbolAtIndex(i)
490de01668bSGreg Clayton                if symbol.GetType() != lldb.eSymbolTypeObjectFile:
491de01668bSGreg Clayton                    continue
492de01668bSGreg Clayton                path = symbol.GetName()
493de01668bSGreg Clayton                if not path:
494de01668bSGreg Clayton                    continue
495de01668bSGreg Clayton                # Extract the value of the symbol by dumping the
496de01668bSGreg Clayton                # symbol. The value is the mod time.
497de01668bSGreg Clayton                dmap_mtime = int(str(symbol).split('value = ')
498de01668bSGreg Clayton                                 [1].split(',')[0], 16)
499de01668bSGreg Clayton                if not options.errors:
500525cd59fSSerge Guelton                    print('%s' % (path), file=result)
501de01668bSGreg Clayton                if os.path.exists(path):
502de01668bSGreg Clayton                    actual_mtime = int(os.stat(path).st_mtime)
503de01668bSGreg Clayton                    if dmap_mtime != actual_mtime:
504de01668bSGreg Clayton                        num_errors += 1
505de01668bSGreg Clayton                        if options.errors:
506525cd59fSSerge Guelton                            print('%s' % (path), end=' ', file=result)
507de01668bSGreg Clayton                        print_mtime_error(result, dmap_mtime,
508de01668bSGreg Clayton                                          actual_mtime)
509de01668bSGreg Clayton                elif path[-1] == ')':
510de01668bSGreg Clayton                    (archive_path, object_name) = path[0:-1].split('(')
511de01668bSGreg Clayton                    if not archive_path and not object_name:
512de01668bSGreg Clayton                        num_errors += 1
513de01668bSGreg Clayton                        if options.errors:
514525cd59fSSerge Guelton                            print('%s' % (path), end=' ', file=result)
515de01668bSGreg Clayton                        print_file_missing_error(path)
516de01668bSGreg Clayton                        continue
517de01668bSGreg Clayton                    if not os.path.exists(archive_path):
518de01668bSGreg Clayton                        num_errors += 1
519de01668bSGreg Clayton                        if options.errors:
520525cd59fSSerge Guelton                            print('%s' % (path), end=' ', file=result)
521de01668bSGreg Clayton                        print_file_missing_error(archive_path)
522de01668bSGreg Clayton                        continue
523de01668bSGreg Clayton                    if archive_path in archives:
524de01668bSGreg Clayton                        archive = archives[archive_path]
525de01668bSGreg Clayton                    else:
526de01668bSGreg Clayton                        archive = Archive(archive_path)
527de01668bSGreg Clayton                        archives[archive_path] = archive
528de01668bSGreg Clayton                    matches = archive.find(object_name, dmap_mtime)
529de01668bSGreg Clayton                    num_matches = len(matches)
530de01668bSGreg Clayton                    if num_matches == 1:
531525cd59fSSerge Guelton                        print('1 match', file=result)
532de01668bSGreg Clayton                        obj = matches[0]
533de01668bSGreg Clayton                        if obj.date != dmap_mtime:
534de01668bSGreg Clayton                            num_errors += 1
535de01668bSGreg Clayton                            if options.errors:
536525cd59fSSerge Guelton                                print('%s' % (path), end=' ', file=result)
537de01668bSGreg Clayton                            print_mtime_error(result, dmap_mtime, obj.date)
538de01668bSGreg Clayton                    elif num_matches == 0:
539de01668bSGreg Clayton                        num_errors += 1
540de01668bSGreg Clayton                        if options.errors:
541525cd59fSSerge Guelton                            print('%s' % (path), end=' ', file=result)
542de01668bSGreg Clayton                        print_archive_object_error(result, object_name,
543de01668bSGreg Clayton                                                   dmap_mtime, archive)
544de01668bSGreg Clayton                    elif num_matches > 1:
545de01668bSGreg Clayton                        num_errors += 1
546de01668bSGreg Clayton                        if options.errors:
547525cd59fSSerge Guelton                            print('%s' % (path), end=' ', file=result)
548de01668bSGreg Clayton                        print_multiple_object_matches(result,
549de01668bSGreg Clayton                                                      object_name,
550de01668bSGreg Clayton                                                      dmap_mtime, matches)
551de01668bSGreg Clayton            if num_errors > 0:
552525cd59fSSerge Guelton                print("%u errors found" % (num_errors), file=result)
553de01668bSGreg Clayton            else:
554525cd59fSSerge Guelton                print("No errors detected in debug map", file=result)
555de01668bSGreg Clayton
556de01668bSGreg Clayton
557de01668bSGreg Claytondef __lldb_init_module(debugger, dict):
558de01668bSGreg Clayton    # This initializer is being run from LLDB in the embedded command
559de01668bSGreg Clayton    # interpreter.
560de01668bSGreg Clayton    # Add any commands contained in this module to LLDB
561de01668bSGreg Clayton    debugger.HandleCommand(
562de01668bSGreg Clayton        'command script add -c %s.VerifyDebugMapCommand %s' % (
563de01668bSGreg Clayton            __name__, VerifyDebugMapCommand.name))
564de01668bSGreg Clayton    print('The "%s" command has been installed, type "help %s" for detailed '
565de01668bSGreg Clayton          'help.' % (VerifyDebugMapCommand.name, VerifyDebugMapCommand.name))
566