16 def memdump(src
, bytes_per_line
=16, address
=0):
17 FILTER
= "".join([(len(repr(chr(x
))) == 3) and chr(x
) or "." for x
in range(256)])
18 for i
in range(0, len(src
), bytes_per_line
):
19 s
= src
[i
: i
+ bytes_per_line
]
20 hex_bytes
= " ".join(["%02x" % (ord(x
)) for x
in s
])
21 ascii
= s
.translate(FILTER
)
22 print("%#08.8x: %-*s %s" % (address
+ i
, bytes_per_line
* 3, hex_bytes
, ascii
))
26 def __init__(self
, file):
27 def read_str(file, str_len
):
28 return file.read(str_len
).rstrip("\0 ")
30 def read_int(file, str_len
, base
):
31 return int(read_str(file, str_len
), base
)
33 self
.offset
= file.tell()
35 self
.name
= read_str(file, 16)
36 self
.date
= read_int(file, 12, 10)
37 self
.uid
= read_int(file, 6, 10)
38 self
.gid
= read_int(file, 6, 10)
39 self
.mode
= read_int(file, 8, 8)
40 self
.size
= read_int(file, 10, 10)
41 if file.read(2) != ARFMAG
:
42 raise ValueError("invalid BSD object at offset %#08.8x" % (self
.offset
))
43 # If we have an extended name read it. Extended names start with
45 if self
.name
.startswith(AR_EFMT1
):
46 name_len
= int(self
.name
[len(AR_EFMT1
) :], 10)
47 self
.name
= read_str(file, name_len
)
48 self
.obj_offset
= file.tell()
49 self
.obj_size
= self
.size
- name_len
50 file.seek(self
.obj_size
, 1)
52 def dump(self
, f
=sys
.stdout
, flat
=True):
55 "%#08.8x: %#08.8x %5u %5u %6o %#08.8x %s\n"
67 f
.write("%#08.8x: \n" % self
.offset
)
68 f
.write(' name = "%s"\n' % self
.name
)
69 f
.write(" date = %#08.8x\n" % self
.date
)
70 f
.write(" uid = %i\n" % self
.uid
)
71 f
.write(" gid = %i\n" % self
.gid
)
72 f
.write(" mode = %o\n" % self
.mode
)
73 f
.write(" size = %#08.8x\n" % (self
.size
))
74 self
.file.seek(self
.obj_offset
, 0)
75 first_bytes
= self
.file.read(4)
80 saved_pos
= self
.file.tell()
81 self
.file.seek(self
.obj_offset
, 0)
82 bytes
= self
.file.read(self
.obj_size
)
83 self
.file.seek(saved_pos
, 0)
86 def save(self
, path
=None, overwrite
=False):
88 Save the contents of the object to disk using 'path' argument as
89 the path, or save it to the current working directory using the
95 if not overwrite
and os
.path
.exists(path
):
96 print('error: outfile "%s" already exists' % (path
))
98 print('Saving "%s" to "%s"...' % (self
.name
, path
))
99 with
open(path
, "w") as f
:
100 f
.write(self
.get_bytes())
103 class StringTable(object):
104 def __init__(self
, bytes
):
107 def get_string(self
, offset
):
108 length
= len(self
.bytes
)
111 return self
.bytes
[offset
: self
.bytes
.find("\0", offset
)]
114 class Archive(object):
115 def __init__(self
, path
):
117 self
.file = open(path
, "r")
119 self
.offset_to_object
= {}
120 if self
.file.read(SARMAG
) != ARMAG
:
121 print("error: file isn't a BSD archive")
124 self
.objects
.append(Object(self
.file))
128 def get_object_at_offset(self
, offset
):
129 if offset
in self
.offset_to_object
:
130 return self
.offset_to_object
[offset
]
131 for obj
in self
.objects
:
132 if obj
.offset
== offset
:
133 self
.offset_to_object
[offset
] = obj
137 def find(self
, name
, mtime
=None, f
=sys
.stdout
):
139 Find an object(s) by name with optional modification time. There
140 can be multple objects with the same name inside and possibly with
141 the same modification time within a BSD archive so clients must be
142 prepared to get multiple results.
145 for obj
in self
.objects
:
146 if obj
.name
== name
and (mtime
is None or mtime
== obj
.date
):
151 def dump_header(self
, f
=sys
.stdout
):
152 f
.write(" DATE UID GID MODE SIZE NAME\n")
154 " ---------- ----- ----- ------ ---------- " "--------------\n"
157 def get_symdef(self
):
158 def get_uint32(file):
159 """Extract a uint32_t from the current file position."""
160 (v
,) = struct
.unpack("=I", file.read(4))
163 for obj
in self
.objects
:
165 if obj
.name
.startswith("__.SYMDEF"):
166 self
.file.seek(obj
.obj_offset
, 0)
167 ranlib_byte_size
= get_uint32(self
.file)
168 num_ranlib_structs
= ranlib_byte_size
/ 8
169 str_offset_pairs
= []
170 for _
in range(num_ranlib_structs
):
171 strx
= get_uint32(self
.file)
172 offset
= get_uint32(self
.file)
173 str_offset_pairs
.append((strx
, offset
))
174 strtab_len
= get_uint32(self
.file)
175 strtab
= StringTable(self
.file.read(strtab_len
))
176 for s
in str_offset_pairs
:
177 symdef
.append((strtab
.get_string(s
[0]), s
[1]))
180 def get_object_dicts(self
):
182 Returns an array of object dictionaries that contain they following
184 'object': the actual bsd.Object instance
185 'symdefs': an array of symbol names that the object contains
186 as found in the "__.SYMDEF" item in the archive
188 symdefs
= self
.get_symdef()
191 for name
, offset
in symdefs
:
192 if offset
in symdef_dict
:
193 object_dict
= symdef_dict
[offset
]
196 "object": self
.get_object_at_offset(offset
),
199 symdef_dict
[offset
] = object_dict
200 object_dict
["symdefs"].append(name
)
202 for offset
in sorted(symdef_dict
):
203 object_dicts
.append(symdef_dict
[offset
])
206 def dump(self
, f
=sys
.stdout
, flat
=True):
207 f
.write("%s:\n" % self
.path
)
209 self
.dump_header(f
=f
)
210 for obj
in self
.objects
:
211 obj
.dump(f
=f
, flat
=flat
)
214 class Interactive(cmd
.Cmd
):
215 """Interactive prompt for exploring contents of BSD archive files, type
216 "help" to see a list of supported commands."""
218 image_option_parser
= None
220 def __init__(self
, archives
):
221 cmd
.Cmd
.__init
__(self
)
222 self
.use_rawinput
= False
224 'Interactive BSD archive prompt, type "help" to see a '
225 "list of supported commands."
227 self
.archives
= archives
230 def default(self
, line
):
231 """Catch all for unknown command, which will exit the interpreter."""
232 print("unknown command: %s" % line
)
235 def do_q(self
, line
):
239 def do_quit(self
, line
):
243 def do_extract(self
, line
):
244 args
= shlex
.split(line
)
247 for object_name
in args
:
248 for archive
in self
.archives
:
249 matches
= archive
.find(object_name
)
251 for object in matches
:
252 object.save(overwrite
=False)
255 print('error: no object matches "%s" in any archives' % (object_name
))
257 print("error: must specify the name of an object to extract")
259 def do_ls(self
, line
):
260 args
= shlex
.split(line
)
262 for object_name
in args
:
263 for archive
in self
.archives
:
264 matches
= archive
.find(object_name
)
266 for object in matches
:
267 object.dump(flat
=False)
270 'error: no object matches "%s" in "%s"'
271 % (object_name
, archive
.path
)
274 for archive
in self
.archives
:
275 archive
.dump(flat
=True)
280 parser
= optparse
.OptionParser(prog
="bsd", description
="Utility for BSD archives")
287 "Specify the name of a object within the BSD archive to get "
298 "Specify the name of a symbol within the BSD archive to get "
299 "information on from SYMDEF"
307 help=("Dump the information in the SYMDEF."),
315 help="Enable verbose output",
324 "Specify this to extract the object specified with the --object "
325 "option. There must be only one object with a matching name or "
326 "the --mtime option must be specified to uniquely identify a "
337 "Specify the modification time of the object an object. This "
338 "option is used with either the --object or --extract options."
348 "Specify a different name or path for the file to extract when "
349 "using the --extract option. If this option isn't specified, "
350 "then the extracted object file will be extracted into the "
351 "current working directory if a file doesn't already exist "
362 "Enter an interactive shell that allows users to interactively "
363 "explore contents of .a files."
367 (options
, args
) = parser
.parse_args(sys
.argv
[1:])
369 if options
.interactive
:
372 archives
.append(Archive(path
))
373 interpreter
= Interactive(archives
)
374 interpreter
.cmdloop()
378 archive
= Archive(path
)
379 if options
.object_name
:
380 print("%s:\n" % (path
))
381 matches
= archive
.find(options
.object_name
, options
.mtime
)
385 if len(matches
) == 1:
387 matches
[0].save(path
=options
.outfile
, overwrite
=False)
390 'error: multiple objects match "%s". Specify '
391 "the modification time using --mtime."
392 % (options
.object_name
)
398 print('error: object "%s" not found in archive' % (options
.object_name
))
399 elif options
.find_symbol
:
400 symdefs
= archive
.get_symdef()
403 for name
, offset
in symdefs
:
404 obj
= archive
.get_object_at_offset(offset
)
405 if name
== options
.find_symbol
:
406 print('Found "%s" in:' % (options
.find_symbol
))
410 print('Didn\'t find "%s" in any objects' % (options
.find_symbol
))
412 print("error: no __.SYMDEF was found")
414 object_dicts
= archive
.get_object_dicts()
415 for object_dict
in object_dicts
:
416 object_dict
["object"].dump(flat
=False)
418 for name
in object_dict
["symdefs"]:
419 print(" %s" % (name
))
421 archive
.dump(flat
=not options
.verbose
)
424 if __name__
== "__main__":
428 def print_mtime_error(result
, dmap_mtime
, actual_mtime
):
430 "error: modification time in debug map (%#08.8x) doesn't "
431 "match the .o file modification time (%#08.8x)" % (dmap_mtime
, actual_mtime
),
436 def print_file_missing_error(result
, path
):
437 print('error: file "%s" doesn\'t exist' % (path
), file=result
)
440 def print_multiple_object_matches(result
, object_name
, mtime
, matches
):
442 "error: multiple matches for object '%s' with with "
443 "modification time %#08.8x:" % (object_name
, mtime
),
446 Archive
.dump_header(f
=result
)
447 for match
in matches
:
448 match
.dump(f
=result
, flat
=True)
451 def print_archive_object_error(result
, object_name
, mtime
, archive
):
452 matches
= archive
.find(object_name
, f
=result
)
455 "error: no objects have a modification time that "
456 "matches %#08.8x for '%s'. Potential matches:" % (mtime
, object_name
),
459 Archive
.dump_header(f
=result
)
460 for match
in matches
:
461 match
.dump(f
=result
, flat
=True)
464 'error: no object named "%s" found in archive:' % (object_name
), file=result
466 Archive
.dump_header(f
=result
)
467 for match
in archive
.objects
:
468 match
.dump(f
=result
, flat
=True)
469 # archive.dump(f=result, flat=True)
472 class VerifyDebugMapCommand
:
473 name
= "verify-debug-map-objects"
475 def create_options(self
):
476 usage
= "usage: %prog [options]"
477 description
= """This command reports any .o files that are missing
478 or whose modification times don't match in the debug map of an executable."""
480 self
.parser
= optparse
.OptionParser(
481 description
=description
, prog
=self
.name
, usage
=usage
, add_help_option
=False
484 self
.parser
.add_option(
490 help="Only show errors",
493 def get_short_help(self
):
494 return "Verify debug map object files."
496 def get_long_help(self
):
497 return self
.help_string
499 def __init__(self
, debugger
, unused
):
500 self
.create_options()
501 self
.help_string
= self
.parser
.format_help()
503 def __call__(self
, debugger
, command
, exe_ctx
, result
):
506 # Use the Shell Lexer to properly parse up command options just like a
508 command_args
= shlex
.split(command
)
511 (options
, args
) = self
.parser
.parse_args(command_args
)
513 result
.SetError("option parsing failed")
516 # Always get program state from the SBExecutionContext passed in
517 target
= exe_ctx
.GetTarget()
518 if not target
.IsValid():
519 result
.SetError("invalid target")
522 for module_spec
in args
:
523 module
= target
.module
[module_spec
]
524 if not (module
and module
.IsValid()):
526 'error: invalid module specification: "%s". '
527 "Specify the full path, basename, or UUID of "
528 "a module " % (module_spec
)
531 num_symbols
= module
.GetNumSymbols()
533 for i
in range(num_symbols
):
534 symbol
= module
.GetSymbolAtIndex(i
)
535 if symbol
.GetType() != lldb
.eSymbolTypeObjectFile
:
537 path
= symbol
.GetName()
540 # Extract the value of the symbol by dumping the
541 # symbol. The value is the mod time.
542 dmap_mtime
= int(str(symbol
).split("value = ")[1].split(",")[0], 16)
543 if not options
.errors
:
544 print("%s" % (path
), file=result
)
545 if os
.path
.exists(path
):
546 actual_mtime
= int(os
.stat(path
).st_mtime
)
547 if dmap_mtime
!= actual_mtime
:
550 print("%s" % (path
), end
=" ", file=result
)
551 print_mtime_error(result
, dmap_mtime
, actual_mtime
)
552 elif path
[-1] == ")":
553 (archive_path
, object_name
) = path
[0:-1].split("(")
554 if not archive_path
and not object_name
:
557 print("%s" % (path
), end
=" ", file=result
)
558 print_file_missing_error(path
)
560 if not os
.path
.exists(archive_path
):
563 print("%s" % (path
), end
=" ", file=result
)
564 print_file_missing_error(archive_path
)
566 if archive_path
in archives
:
567 archive
= archives
[archive_path
]
569 archive
= Archive(archive_path
)
570 archives
[archive_path
] = archive
571 matches
= archive
.find(object_name
, dmap_mtime
)
572 num_matches
= len(matches
)
574 print("1 match", file=result
)
576 if obj
.date
!= dmap_mtime
:
579 print("%s" % (path
), end
=" ", file=result
)
580 print_mtime_error(result
, dmap_mtime
, obj
.date
)
581 elif num_matches
== 0:
584 print("%s" % (path
), end
=" ", file=result
)
585 print_archive_object_error(
586 result
, object_name
, dmap_mtime
, archive
588 elif num_matches
> 1:
591 print("%s" % (path
), end
=" ", file=result
)
592 print_multiple_object_matches(
593 result
, object_name
, dmap_mtime
, matches
596 print("%u errors found" % (num_errors
), file=result
)
598 print("No errors detected in debug map", file=result
)
601 def __lldb_init_module(debugger
, dict):
602 # This initializer is being run from LLDB in the embedded command
604 # Add any commands contained in this module to LLDB
605 debugger
.HandleCommand(
606 "command script add -o -c %s.VerifyDebugMapCommand %s"
607 % (__name__
, VerifyDebugMapCommand
.name
)
610 'The "%s" command has been installed, type "help %s" for detailed '
611 "help." % (VerifyDebugMapCommand
.name
, VerifyDebugMapCommand
.name
)