def symbolize(self, dsym_path, image_vmaddr, image_addr, instruction_addr, cpu_name, symbolize_inlined=False): """Symbolizes a single frame based on the information provided. If the symbolication fails a `SymbolicationError` is raised. `dsym_path` is the path to the dsym file on the file system. `image_vmaddr` is the slide of the image. For most situations this can just be set to `0`. If it's zero or unset we will attempt to find the slide from the dsym file. `image_addr` is the canonical image address as loaded. `instruction_addr` is the address where the error happened. `cpu_name` is the CPU name. It follows general apple conventions and is used to special case certain behavior and look up the right symbols. Common names are `armv7` and `arm64`. Additionally if `symbolize_inlined` is set to `True` then a list of frames is returned instead which might contain inlined frames. In that case the return value might be an empty list instead. """ if self._closed: raise RuntimeError('Symbolizer is closed') dsym_path = normalize_dsym_path(dsym_path) image_vmaddr = parse_addr(image_vmaddr) if not image_vmaddr: di = self._symbolizer.get_debug_info(dsym_path) if di is not None: variant = di.get_variant(cpu_name) if variant is not None: image_vmaddr = variant.vmaddr image_addr = parse_addr(image_addr) instruction_addr = parse_addr(instruction_addr) if not is_valid_cpu_name(cpu_name): raise SymbolicationError('"%s" is not a valid cpu name' % cpu_name) addr = image_vmaddr + instruction_addr - image_addr with self._lock: with timedsection('symbolize'): if symbolize_inlined: return self._symbolizer.symbolize_inlined( dsym_path, addr, cpu_name) return self._symbolizer.symbolize(dsym_path, addr, cpu_name)
def symbolize(self, dsym_path, image_vmaddr, image_addr, instruction_addr, cpu_name, uuid=None, silent=True, demangle=True): if self._closed: raise RuntimeError('Symbolizer is closed') if not is_valid_cpu_name(cpu_name): raise ValueError('"%s" is not a valid cpu name' % cpu_name) dsym_path = normalize_dsym_path(dsym_path) image_vmaddr = parse_addr(image_vmaddr) if not image_vmaddr: image_vmaddr = get_macho_vmaddr(dsym_path, cpu_name) or 0 image_addr = parse_addr(image_addr) instruction_addr = parse_addr(instruction_addr) addr = image_vmaddr + instruction_addr - image_addr try: with self._lock: with timedsection('symbolize'): sym = self.symbolizer.symbolize(dsym_path, addr, cpu_name) if sym[0] is None: raise SymbolicationError('Symbolizer could not find symbol') except SymbolicationError: if not silent: raise sym = (None, None, 0, 0) symbol_name = sym[0] if demangle: symbol_name = demangle_symbol(symbol_name) return { 'symbol_name': symbol_name, 'filename': sym[1], 'line': sym[2], 'column': sym[3], 'uuid': uuid, }
def symbolize(self, dsym_path, image_vmaddr, image_addr, instruction_addr, cpu_name, uuid=None, silent=True): if self._closed: raise RuntimeError('Symbolizer is closed') if not is_valid_cpu_name(cpu_name): raise ValueError('"%s" is not a valid cpu name' % cpu_name) dsym_path = normalize_dsym_path(dsym_path) image_vmaddr = parse_addr(image_vmaddr) if not image_vmaddr: image_vmaddr = get_macho_vmaddr(dsym_path, cpu_name) or 0 image_addr = parse_addr(image_addr) instruction_addr = parse_addr(instruction_addr) addr = image_vmaddr + instruction_addr - image_addr try: with self._lock: with timedsection('symbolize'): sym = self.symbolizer.symbolize(dsym_path, addr, cpu_name) if sym[0] is None: raise SymbolicationError('Symbolizer could not find symbol') except SymbolicationError: if not silent: raise sym = (None, None, 0, 0) return { 'symbol_name': demangle_symbol(sym[0]), 'filename': sym[1], 'line': sym[2], 'column': sym[3], 'uuid': uuid, }
def __init__(self, driver, dsym_paths, binary_images): if isinstance(dsym_paths, string_types): dsym_paths = [dsym_paths] self.driver = driver with timedsection('findimages'): self.images = find_debug_images(dsym_paths, binary_images)
def find_debug_images(dsym_paths, binary_images): images_to_load = set() with timedsection('iterimages0'): for image in binary_images: cpu_name = get_cpu_name(image['cpu_type'], image['cpu_subtype']) if cpu_name is not None: images_to_load.add(image['uuid'].lower()) images = {} # Step one: load images that are named by their UUID with timedsection('loadimages-fast'): for uuid in list(images_to_load): for dsym_path in dsym_paths: fn = os.path.join(dsym_path, uuid) if os.path.isfile(fn): images[uuid] = fn images_to_load.discard(uuid) break # Otherwise fall back to loading images from the dsym bundle. Because # this loading strategy is pretty slow we do't actually want to use it # unless we have a path that looks like a bundle. As a result we # find all the paths which are bundles and then only process those. if images_to_load: slow_paths = [] for dsym_path in dsym_paths: if os.path.isdir(os.path.join(dsym_path, 'Contents')): slow_paths.append(dsym_path) with timedsection('loadimages-slow'): for dsym_path in slow_paths: dwarf_base = os.path.join(dsym_path, 'Contents', 'Resources', 'DWARF') if os.path.isdir(dwarf_base): for fn in os.listdir(dwarf_base): # Looks like a UUID we loaded, skip it if fn in images: continue full_fn = os.path.join(dwarf_base, fn) uuids = get_macho_uuids(full_fn) for _, uuid in uuids: if uuid in images_to_load: images[uuid] = full_fn images_to_load.discard(uuid) rv = {} # Now resolve all the images. with timedsection('resolveimages'): for image in binary_images: cpu_name = get_cpu_name(image['cpu_type'], image['cpu_subtype']) if cpu_name is None: continue uid = image['uuid'].lower() if uid not in images: continue rv[image['image_addr']] = { 'uuid': uid, 'image_addr': image['image_addr'], 'dsym_path': images[uid], 'image_vmaddr': image['image_vmaddr'], 'cpu_name': cpu_name, } return rv
def find_debug_images(dsym_paths, binary_images): """Given a list of paths and a list of binary images this returns a dictionary of image addresses to the locations on the file system for all found images. """ images_to_load = set() with timedsection('iterimages0'): for image in binary_images: if get_image_cpu_name(image) is not None: images_to_load.add(image['uuid'].lower()) images = {} # Step one: load images that are named by their UUID with timedsection('loadimages-fast'): for uuid in list(images_to_load): for dsym_path in dsym_paths: fn = os.path.join(dsym_path, uuid) if os.path.isfile(fn): images[uuid] = fn images_to_load.discard(uuid) break # Otherwise fall back to loading images from the dsym bundle. Because # this loading strategy is pretty slow we do't actually want to use it # unless we have a path that looks like a bundle. As a result we # find all the paths which are bundles and then only process those. if images_to_load: slow_paths = [] for dsym_path in dsym_paths: if os.path.isdir(os.path.join(dsym_path, 'Contents')): slow_paths.append(dsym_path) with timedsection('loadimages-slow'): for dsym_path in slow_paths: dwarf_base = os.path.join(dsym_path, 'Contents', 'Resources', 'DWARF') if os.path.isdir(dwarf_base): for fn in os.listdir(dwarf_base): # Looks like a UUID we loaded, skip it if fn in images: continue full_fn = os.path.join(dwarf_base, fn) try: di = DebugInfo.open_path(full_fn) except DebugInfoError: continue for variant in di.get_variants(): uuid = str(variant.uuid) if uuid in images_to_load: images[uuid] = full_fn images_to_load.discard(uuid) rv = {} # Now resolve all the images. with timedsection('resolveimages'): for image in binary_images: cpu_name = get_image_cpu_name(image) if cpu_name is None: continue uid = image['uuid'].lower() if uid not in images: continue rv[parse_addr(image['image_addr'])] = images[uid] return rv