1# Copyright 2013 The Chromium Authors. All rights reserved. 2# Use of this source code is governed by a BSD-style license that can be 3# found in the LICENSE file. 4 5import logging 6import optparse 7import os 8import re 9 10from lib.bucket import BucketSet 11from lib.dump import Dump, DumpList 12from lib.symbol import SymbolDataSources, SymbolMappingCache, SymbolFinder 13from lib.symbol import procfs 14from lib.symbol import FUNCTION_SYMBOLS, SOURCEFILE_SYMBOLS, TYPEINFO_SYMBOLS 15 16 17LOGGER = logging.getLogger('dmprof') 18 19BASE_PATH = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) 20CHROME_SRC_PATH = os.path.join(BASE_PATH, os.pardir, os.pardir) 21 22 23class SubCommand(object): 24 """Subclasses are a subcommand for this executable. 25 26 See COMMANDS in main() in dmprof.py. 27 """ 28 _DEVICE_BINDIRS = ['/data/data/', '/data/app-lib/', '/data/local/tmp'] 29 30 def __init__(self, usage): 31 self._parser = optparse.OptionParser(usage) 32 33 @staticmethod 34 def load_basic_files( 35 dump_path, multiple, no_dump=False, alternative_dirs=None): 36 prefix = SubCommand._find_prefix(dump_path) 37 # If the target process is estimated to be working on Android, converts 38 # a path in the Android device to a path estimated to be corresponding in 39 # the host. Use --alternative-dirs to specify the conversion manually. 40 if not alternative_dirs: 41 alternative_dirs = SubCommand._estimate_alternative_dirs(prefix) 42 if alternative_dirs: 43 for device, host in alternative_dirs.iteritems(): 44 LOGGER.info('Assuming %s on device as %s on host' % (device, host)) 45 symbol_data_sources = SymbolDataSources(prefix, alternative_dirs) 46 symbol_data_sources.prepare() 47 bucket_set = BucketSet() 48 bucket_set.load(prefix) 49 if not no_dump: 50 if multiple: 51 dump_list = DumpList.load(SubCommand._find_all_dumps(dump_path)) 52 else: 53 dump = Dump.load(dump_path) 54 symbol_mapping_cache = SymbolMappingCache() 55 with open(prefix + '.cache.function', 'a+') as cache_f: 56 symbol_mapping_cache.update( 57 FUNCTION_SYMBOLS, bucket_set, 58 SymbolFinder(FUNCTION_SYMBOLS, symbol_data_sources), cache_f) 59 with open(prefix + '.cache.typeinfo', 'a+') as cache_f: 60 symbol_mapping_cache.update( 61 TYPEINFO_SYMBOLS, bucket_set, 62 SymbolFinder(TYPEINFO_SYMBOLS, symbol_data_sources), cache_f) 63 with open(prefix + '.cache.sourcefile', 'a+') as cache_f: 64 symbol_mapping_cache.update( 65 SOURCEFILE_SYMBOLS, bucket_set, 66 SymbolFinder(SOURCEFILE_SYMBOLS, symbol_data_sources), cache_f) 67 bucket_set.symbolize(symbol_mapping_cache) 68 if no_dump: 69 return bucket_set 70 elif multiple: 71 return (bucket_set, dump_list) 72 else: 73 return (bucket_set, dump) 74 75 @staticmethod 76 def _find_prefix(path): 77 return re.sub('\.[0-9][0-9][0-9][0-9]\.heap', '', path) 78 79 @staticmethod 80 def _estimate_alternative_dirs(prefix): 81 """Estimates a path in host from a corresponding path in target device. 82 83 For Android, dmprof.py should find symbol information from binaries in 84 the host instead of the Android device because dmprof.py doesn't run on 85 the Android device. This method estimates a path in the host 86 corresponding to a path in the Android device. 87 88 Returns: 89 A dict that maps a path in the Android device to a path in the host. 90 If a file in SubCommand._DEVICE_BINDIRS is found in /proc/maps, it 91 assumes the process was running on Android and maps the path to 92 "out/Debug/lib" in the Chromium directory. An empty dict is returned 93 unless Android. 94 """ 95 device_lib_path_candidates = set() 96 97 with open(prefix + '.maps') as maps_f: 98 maps = procfs.ProcMaps.load_file(maps_f) 99 for entry in maps: 100 name = entry.as_dict()['name'] 101 if any([base_dir in name for base_dir in SubCommand._DEVICE_BINDIRS]): 102 device_lib_path_candidates.add(os.path.dirname(name)) 103 104 if len(device_lib_path_candidates) == 1: 105 return {device_lib_path_candidates.pop(): os.path.join( 106 CHROME_SRC_PATH, 'out', 'Debug', 'lib')} 107 else: 108 return {} 109 110 @staticmethod 111 def _find_all_dumps(dump_path): 112 prefix = SubCommand._find_prefix(dump_path) 113 dump_path_list = [dump_path] 114 115 n = int(dump_path[len(dump_path) - 9 : len(dump_path) - 5]) 116 n += 1 117 skipped = 0 118 while True: 119 p = '%s.%04d.heap' % (prefix, n) 120 if os.path.exists(p) and os.stat(p).st_size: 121 dump_path_list.append(p) 122 else: 123 if skipped > 10: 124 break 125 skipped += 1 126 n += 1 127 128 return dump_path_list 129 130 @staticmethod 131 def _find_all_buckets(dump_path): 132 prefix = SubCommand._find_prefix(dump_path) 133 bucket_path_list = [] 134 135 n = 0 136 while True: 137 path = '%s.%04d.buckets' % (prefix, n) 138 if not os.path.exists(path): 139 if n > 10: 140 break 141 n += 1 142 continue 143 bucket_path_list.append(path) 144 n += 1 145 146 return bucket_path_list 147 148 def _parse_args(self, sys_argv, required): 149 options, args = self._parser.parse_args(sys_argv) 150 if len(args) < required + 1: 151 self._parser.error('needs %d argument(s).\n' % required) 152 return None 153 return (options, args) 154 155 @staticmethod 156 def _parse_policy_list(options_policy): 157 if options_policy: 158 return options_policy.split(',') 159 else: 160 return None 161