1*9c5db199SXin Li# Copyright (c) 2011 The Chromium OS Authors. All rights reserved. 2*9c5db199SXin Li# Use of this source code is governed by a BSD-style license that can be 3*9c5db199SXin Li# found in the LICENSE file. 4*9c5db199SXin Li 5*9c5db199SXin Liimport logging 6*9c5db199SXin Liimport os 7*9c5db199SXin Liimport re 8*9c5db199SXin Liimport shutil 9*9c5db199SXin Lifrom autotest_lib.client.common_lib import utils as client_utils 10*9c5db199SXin Lifrom autotest_lib.client.common_lib.cros import dev_server 11*9c5db199SXin Lifrom autotest_lib.client.common_lib.cros import retry 12*9c5db199SXin Lifrom autotest_lib.client.cros import constants 13*9c5db199SXin Lifrom autotest_lib.server.cros.dynamic_suite.constants import JOB_BUILD_KEY 14*9c5db199SXin Lifrom autotest_lib.server.crashcollect import collect_log_file 15*9c5db199SXin Lifrom autotest_lib.server import utils 16*9c5db199SXin Li 17*9c5db199SXin Litry: 18*9c5db199SXin Li from autotest_lib.utils.frozen_chromite.lib import metrics 19*9c5db199SXin Liexcept ImportError: 20*9c5db199SXin Li metrics = client_utils.metrics_mock 21*9c5db199SXin Li 22*9c5db199SXin Li 23*9c5db199SXin Lidef generate_minidump_stacktrace(minidump_path): 24*9c5db199SXin Li """ 25*9c5db199SXin Li Generates a stacktrace for the specified minidump. 26*9c5db199SXin Li 27*9c5db199SXin Li This function expects the debug symbols to reside under: 28*9c5db199SXin Li /build/<board>/usr/lib/debug 29*9c5db199SXin Li 30*9c5db199SXin Li @param minidump_path: absolute path to minidump to by symbolicated. 31*9c5db199SXin Li @raise client_utils.error.CmdError if minidump_stackwalk return code != 0. 32*9c5db199SXin Li """ 33*9c5db199SXin Li symbol_dir = '%s/../../../lib/debug' % utils.get_server_dir() 34*9c5db199SXin Li logging.info('symbol_dir: %s', symbol_dir) 35*9c5db199SXin Li client_utils.run('minidump_stackwalk "%s" "%s" > "%s.txt"' % 36*9c5db199SXin Li (minidump_path, symbol_dir, minidump_path)) 37*9c5db199SXin Li 38*9c5db199SXin Li 39*9c5db199SXin Lidef _resolve_crashserver(): 40*9c5db199SXin Li """ 41*9c5db199SXin Li Attempts to find a devserver / crashserver that has capacity to 42*9c5db199SXin Li symbolicate a crashdump. 43*9c5db199SXin Li 44*9c5db199SXin Li @raises DevServerException if no server with capacity could be found. 45*9c5db199SXin Li @returns Hostname of resolved server, if found. 46*9c5db199SXin Li """ 47*9c5db199SXin Li crashserver_name = dev_server.get_least_loaded_devserver( 48*9c5db199SXin Li devserver_type=dev_server.CrashServer) 49*9c5db199SXin Li if not crashserver_name: 50*9c5db199SXin Li metrics.Counter('chromeos/autotest/crashcollect/could_not_resolve' 51*9c5db199SXin Li ).increment() 52*9c5db199SXin Li raise dev_server.DevServerException( 53*9c5db199SXin Li 'No crash server has the capacity to symbolicate the dump.') 54*9c5db199SXin Li else: 55*9c5db199SXin Li metrics.Counter('chromeos/autotest/crashcollect/resolved' 56*9c5db199SXin Li ).increment(fields={'crash_server': crashserver_name}) 57*9c5db199SXin Li return crashserver_name 58*9c5db199SXin Li 59*9c5db199SXin Li 60*9c5db199SXin Lidef _symbolicate_minidump_with_devserver(minidump_path, resultdir, 61*9c5db199SXin Li crashserver_name): 62*9c5db199SXin Li """ 63*9c5db199SXin Li Generates a stack trace for the specified minidump by consulting devserver. 64*9c5db199SXin Li 65*9c5db199SXin Li This function assumes the debug symbols have been staged on the devserver. 66*9c5db199SXin Li 67*9c5db199SXin Li @param minidump_path: absolute path to minidump to by symbolicated. 68*9c5db199SXin Li @param resultdir: server job's result directory. 69*9c5db199SXin Li @param crashserver_name: Name of crashserver to attempt to symbolicate with. 70*9c5db199SXin Li @raise DevServerException upon failure, HTTP or otherwise. 71*9c5db199SXin Li """ 72*9c5db199SXin Li # First, look up what build we tested. If we can't find this, we can't 73*9c5db199SXin Li # get the right debug symbols, so we might as well give up right now. 74*9c5db199SXin Li keyvals = client_utils.read_keyval(resultdir) 75*9c5db199SXin Li if JOB_BUILD_KEY not in keyvals: 76*9c5db199SXin Li raise dev_server.DevServerException( 77*9c5db199SXin Li 'Cannot determine build being tested.') 78*9c5db199SXin Li 79*9c5db199SXin Li devserver = dev_server.CrashServer(crashserver_name) 80*9c5db199SXin Li 81*9c5db199SXin Li with metrics.SecondsTimer( 82*9c5db199SXin Li 'chromeos/autotest/crashcollect/symbolicate_duration', 83*9c5db199SXin Li fields={'crash_server': crashserver_name}): 84*9c5db199SXin Li trace_text = devserver.symbolicate_dump(minidump_path, 85*9c5db199SXin Li keyvals[JOB_BUILD_KEY]) 86*9c5db199SXin Li 87*9c5db199SXin Li if not trace_text: 88*9c5db199SXin Li raise dev_server.DevServerException('Unknown error!!') 89*9c5db199SXin Li with open(minidump_path + '.txt', 'w') as trace_file: 90*9c5db199SXin Li trace_file.write(trace_text) 91*9c5db199SXin Li 92*9c5db199SXin Lidef generate_stacktrace_for_file(minidump, host_resultdir): 93*9c5db199SXin Li """ 94*9c5db199SXin Li Tries to generate a stack trace for the file located at |minidump|. 95*9c5db199SXin Li @param minidump: path to minidump file to generate the stacktrace for. 96*9c5db199SXin Li @param host_resultdir: server job's result directory. 97*9c5db199SXin Li """ 98*9c5db199SXin Li # First, try to symbolicate locally. 99*9c5db199SXin Li try: 100*9c5db199SXin Li logging.info('Trying to generate stack trace locally for %s', minidump) 101*9c5db199SXin Li generate_minidump_stacktrace(minidump) 102*9c5db199SXin Li logging.info('Generated stack trace for dump %s', minidump) 103*9c5db199SXin Li return 104*9c5db199SXin Li except client_utils.error.CmdError as err: 105*9c5db199SXin Li logging.info('Failed to generate stack trace locally for ' 106*9c5db199SXin Li 'dump %s (rc=%d):\n%r', 107*9c5db199SXin Li minidump, err.result_obj.exit_status, err) 108*9c5db199SXin Li 109*9c5db199SXin Li # If that did not succeed, try to symbolicate using the dev server. 110*9c5db199SXin Li try: 111*9c5db199SXin Li logging.info('Generating stack trace using devserver for %s', minidump) 112*9c5db199SXin Li crashserver_name = _resolve_crashserver() 113*9c5db199SXin Li args = (minidump, host_resultdir, crashserver_name) 114*9c5db199SXin Li is_timeout, _ = retry.timeout(_symbolicate_minidump_with_devserver, 115*9c5db199SXin Li args=args, 116*9c5db199SXin Li timeout_sec=600) 117*9c5db199SXin Li if is_timeout: 118*9c5db199SXin Li logging.info('Generating stack trace timed out for dump %s', 119*9c5db199SXin Li minidump) 120*9c5db199SXin Li metrics.Counter( 121*9c5db199SXin Li 'chromeos/autotest/crashcollect/symbolicate_timed_out' 122*9c5db199SXin Li ).increment(fields={'crash_server': crashserver_name}) 123*9c5db199SXin Li else: 124*9c5db199SXin Li logging.info('Generated stack trace for dump %s', minidump) 125*9c5db199SXin Li return 126*9c5db199SXin Li except dev_server.DevServerException as e: 127*9c5db199SXin Li logging.info('Failed to generate stack trace on devserver for dump ' 128*9c5db199SXin Li '%s:\n%r', minidump, e) 129*9c5db199SXin Li 130*9c5db199SXin Li # Symbolicating failed. 131*9c5db199SXin Li logging.warning('Failed to generate stack trace for %s (see info logs)', 132*9c5db199SXin Li minidump) 133*9c5db199SXin Li 134*9c5db199SXin Lidef find_and_generate_minidump_stacktraces(host_resultdir): 135*9c5db199SXin Li """ 136*9c5db199SXin Li Finds all minidump files and generates a stack trace for each. 137*9c5db199SXin Li 138*9c5db199SXin Li Enumerates all files under the test results directory (recursively) 139*9c5db199SXin Li and generates a stack trace file for the minidumps. Minidump files are 140*9c5db199SXin Li identified as files with .dmp extension. The stack trace filename is 141*9c5db199SXin Li composed by appending the .txt extension to the minidump filename. 142*9c5db199SXin Li 143*9c5db199SXin Li @param host_resultdir: Directory to walk looking for dmp files. 144*9c5db199SXin Li 145*9c5db199SXin Li @returns The list of all found minidump files. Each dump may or may not have 146*9c5db199SXin Li been symbolized. 147*9c5db199SXin Li """ 148*9c5db199SXin Li minidumps = [] 149*9c5db199SXin Li for file in _find_crashdumps(host_resultdir): 150*9c5db199SXin Li generate_stacktrace_for_file(file, host_resultdir) 151*9c5db199SXin Li minidumps.append(file) 152*9c5db199SXin Li return minidumps 153*9c5db199SXin Li 154*9c5db199SXin Li 155*9c5db199SXin Lidef _find_crashdumps(host_resultdir): 156*9c5db199SXin Li """Find crashdumps. 157*9c5db199SXin Li 158*9c5db199SXin Li @param host_resultdir The result directory for this host for this test run. 159*9c5db199SXin Li """ 160*9c5db199SXin Li for dir, subdirs, files in os.walk(host_resultdir): 161*9c5db199SXin Li for file in files: 162*9c5db199SXin Li if file.endswith('.dmp'): 163*9c5db199SXin Li yield os.path.join(dir, file) 164*9c5db199SXin Li 165*9c5db199SXin Li 166*9c5db199SXin Lidef _find_orphaned_crashdumps(host): 167*9c5db199SXin Li """Return file paths of crashdumps on host. 168*9c5db199SXin Li 169*9c5db199SXin Li @param host A host object of the device. 170*9c5db199SXin Li """ 171*9c5db199SXin Li return host.list_files_glob(os.path.join(constants.CRASH_DIR, '*')) 172*9c5db199SXin Li 173*9c5db199SXin Li 174*9c5db199SXin Lidef report_crashdumps(host): 175*9c5db199SXin Li """Report on crashdumps for host. 176*9c5db199SXin Li 177*9c5db199SXin Li This is run when no tests failed. We don't process crashdumps in this 178*9c5db199SXin Li case because of devserver load, but they should still be reported. 179*9c5db199SXin Li 180*9c5db199SXin Li @param host A host object of the device we're to pull crashes from. 181*9c5db199SXin Li """ 182*9c5db199SXin Li for crashfile in _find_orphaned_crashdumps(host): 183*9c5db199SXin Li logging.warning('Host crashdump exists: %s', crashfile) 184*9c5db199SXin Li host.job.record('INFO', None, None, 185*9c5db199SXin Li 'Host crashdump exists: %s' % (crashfile,)) 186*9c5db199SXin Li 187*9c5db199SXin Li host_resultdir = _get_host_resultdir(host) 188*9c5db199SXin Li for crashfile in _find_crashdumps(host_resultdir): 189*9c5db199SXin Li logging.warning('Local crashdump exists: %s', crashfile) 190*9c5db199SXin Li host.job.record('INFO', None, None, 191*9c5db199SXin Li 'Local crashdump exists: %s' % (crashfile,)) 192*9c5db199SXin Li 193*9c5db199SXin Li 194*9c5db199SXin Lidef fetch_orphaned_crashdumps(host, infodir): 195*9c5db199SXin Li """ 196*9c5db199SXin Li Copy all of the crashes in the crash directory over to the results folder. 197*9c5db199SXin Li 198*9c5db199SXin Li @param host A host object of the device we're to pull crashes from. 199*9c5db199SXin Li @param infodir The directory to fetch crashdumps into. 200*9c5db199SXin Li @return The list of minidumps that we pulled back from the host. 201*9c5db199SXin Li """ 202*9c5db199SXin Li if not os.path.exists(infodir): 203*9c5db199SXin Li os.mkdir(infodir) 204*9c5db199SXin Li orphans = [] 205*9c5db199SXin Li 206*9c5db199SXin Li if not host.check_cached_up_status(): 207*9c5db199SXin Li logging.warning('Host %s did not answer to ping, skip fetching ' 208*9c5db199SXin Li 'orphaned crashdumps.', host.hostname) 209*9c5db199SXin Li return orphans 210*9c5db199SXin Li 211*9c5db199SXin Li try: 212*9c5db199SXin Li for file in _find_orphaned_crashdumps(host): 213*9c5db199SXin Li logging.info('Collecting %s...', file) 214*9c5db199SXin Li collect_log_file(host, file, infodir, clean=False) 215*9c5db199SXin Li orphans.append(file) 216*9c5db199SXin Li except Exception as e: 217*9c5db199SXin Li logging.warning('Collection of orphaned crash dumps failed %s', e) 218*9c5db199SXin Li finally: 219*9c5db199SXin Li # Delete infodir if we have no orphans 220*9c5db199SXin Li if not orphans: 221*9c5db199SXin Li logging.info('There are no orphaned crashes; deleting %s', infodir) 222*9c5db199SXin Li os.rmdir(infodir) 223*9c5db199SXin Li return orphans 224*9c5db199SXin Li 225*9c5db199SXin Li 226*9c5db199SXin Lidef _copy_to_debug_dir(host_resultdir, filename): 227*9c5db199SXin Li """ 228*9c5db199SXin Li Copies a file to the debug dir under host_resultdir. 229*9c5db199SXin Li 230*9c5db199SXin Li @param host_resultdir The result directory for this host for this test run. 231*9c5db199SXin Li @param filename The full path of the file to copy to the debug folder. 232*9c5db199SXin Li """ 233*9c5db199SXin Li debugdir = os.path.join(host_resultdir, 'debug') 234*9c5db199SXin Li src = filename 235*9c5db199SXin Li dst = os.path.join(debugdir, os.path.basename(filename)) 236*9c5db199SXin Li 237*9c5db199SXin Li try: 238*9c5db199SXin Li shutil.copyfile(src, dst) 239*9c5db199SXin Li logging.info('Copied %s to %s', src, dst) 240*9c5db199SXin Li except IOError: 241*9c5db199SXin Li logging.warning('Failed to copy %s to %s', src, dst) 242*9c5db199SXin Li 243*9c5db199SXin Li 244*9c5db199SXin Lidef _get_host_resultdir(host): 245*9c5db199SXin Li """Get resultdir for host. 246*9c5db199SXin Li 247*9c5db199SXin Li @param host A host object of the device we're to pull crashes from. 248*9c5db199SXin Li """ 249*9c5db199SXin Li return getattr(getattr(host, 'job', None), 'resultdir', None) 250*9c5db199SXin Li 251*9c5db199SXin Li 252*9c5db199SXin Lidef get_host_infodir(host): 253*9c5db199SXin Li """Get infodir for host. 254*9c5db199SXin Li 255*9c5db199SXin Li @param host A host object of the device we're to pull crashes from. 256*9c5db199SXin Li """ 257*9c5db199SXin Li host_resultdir = _get_host_resultdir(host) 258*9c5db199SXin Li return os.path.join(host_resultdir, 'crashinfo.%s' % host.hostname) 259*9c5db199SXin Li 260*9c5db199SXin Li 261*9c5db199SXin Lidef get_site_crashdumps(host, test_start_time): 262*9c5db199SXin Li """ 263*9c5db199SXin Li Copy all of the crashdumps from a host to the results directory. 264*9c5db199SXin Li 265*9c5db199SXin Li @param host The host object from which to pull crashes 266*9c5db199SXin Li @param test_start_time When the test we just ran started. 267*9c5db199SXin Li @return A list of all the minidumps 268*9c5db199SXin Li """ 269*9c5db199SXin Li host_resultdir = _get_host_resultdir(host) 270*9c5db199SXin Li infodir = get_host_infodir(host) 271*9c5db199SXin Li 272*9c5db199SXin Li orphans = fetch_orphaned_crashdumps(host, infodir) 273*9c5db199SXin Li minidumps = find_and_generate_minidump_stacktraces(host_resultdir) 274*9c5db199SXin Li 275*9c5db199SXin Li # Record all crashdumps in status.log of the job: 276*9c5db199SXin Li # - If one server job runs several client jobs we will only record 277*9c5db199SXin Li # crashdumps in the status.log of the high level server job. 278*9c5db199SXin Li # - We will record these crashdumps whether or not we successfully 279*9c5db199SXin Li # symbolicate them. 280*9c5db199SXin Li if host.job and minidumps or orphans: 281*9c5db199SXin Li host.job.record('INFO', None, None, 'Start crashcollection record') 282*9c5db199SXin Li for minidump in minidumps: 283*9c5db199SXin Li host.job.record('INFO', None, 'New Crash Dump', minidump) 284*9c5db199SXin Li for orphan in orphans: 285*9c5db199SXin Li host.job.record('INFO', None, 'Orphaned Crash Dump', orphan) 286*9c5db199SXin Li host.job.record('INFO', None, None, 'End crashcollection record') 287*9c5db199SXin Li 288*9c5db199SXin Li orphans.extend(minidumps) 289*9c5db199SXin Li 290*9c5db199SXin Li for minidump in orphans: 291*9c5db199SXin Li report_bug_from_crash(host, minidump) 292*9c5db199SXin Li 293*9c5db199SXin Li # We copy Chrome crash information to the debug dir to assist debugging. 294*9c5db199SXin Li # Since orphans occurred on a previous run, they are most likely not 295*9c5db199SXin Li # relevant to the current failure, so we don't copy them. 296*9c5db199SXin Li for minidump in minidumps: 297*9c5db199SXin Li minidump_no_ext = os.path.splitext(minidump)[0] 298*9c5db199SXin Li _copy_to_debug_dir(host_resultdir, minidump_no_ext + '.dmp.txt') 299*9c5db199SXin Li _copy_to_debug_dir(host_resultdir, minidump_no_ext + '.log') 300*9c5db199SXin Li 301*9c5db199SXin Li return orphans 302*9c5db199SXin Li 303*9c5db199SXin Li 304*9c5db199SXin Lidef find_package_of(host, exec_name): 305*9c5db199SXin Li """ 306*9c5db199SXin Li Find the package that an executable came from. 307*9c5db199SXin Li 308*9c5db199SXin Li @param host A host object that has the executable. 309*9c5db199SXin Li @param exec_name Name of or path to executable. 310*9c5db199SXin Li @return The name of the package that installed the executable. 311*9c5db199SXin Li """ 312*9c5db199SXin Li # Run "portageq owners" on "host" to determine which package owns 313*9c5db199SXin Li # "exec_name." Portageq queue output consists of package names followed 314*9c5db199SXin Li # tab-prefixed path names. For example, owners of "python:" 315*9c5db199SXin Li # 316*9c5db199SXin Li # sys-devel/gdb-7.7.1-r2 317*9c5db199SXin Li # /usr/share/gdb/python 318*9c5db199SXin Li # chromeos-base/dev-install-0.0.1-r711 319*9c5db199SXin Li # /usr/bin/python 320*9c5db199SXin Li # dev-lang/python-2.7.3-r7 321*9c5db199SXin Li # /etc/env.d/python 322*9c5db199SXin Li # 323*9c5db199SXin Li # This gets piped into "xargs stat" to annotate each line with 324*9c5db199SXin Li # information about the path, so we later can consider only packages 325*9c5db199SXin Li # with executable files. After annotation the above looks like: 326*9c5db199SXin Li # 327*9c5db199SXin Li # stat: cannot stat '@@@ sys-devel/gdb-7.7.1-r2 @@@': ... 328*9c5db199SXin Li # stat: cannot stat '/usr/share/gdb/python': ... 329*9c5db199SXin Li # stat: cannot stat '@@@ chromeos-base/dev-install-0.0.1-r711 @@@': ... 330*9c5db199SXin Li # 755 -rwxr-xr-x /usr/bin/python 331*9c5db199SXin Li # stat: cannot stat '@@@ dev-lang/python-2.7.3-r7 @@@': ... 332*9c5db199SXin Li # 755 drwxr-xr-x /etc/env.d/python 333*9c5db199SXin Li # 334*9c5db199SXin Li # Package names are surrounded by "@@@" to facilitate parsing. Lines 335*9c5db199SXin Li # starting with an octal number were successfully annotated, because 336*9c5db199SXin Li # the path existed on "host." 337*9c5db199SXin Li # The above is then parsed to find packages which contain executable files 338*9c5db199SXin Li # (not directories), in this case "chromeos-base/dev-install-0.0.1-r711." 339*9c5db199SXin Li # 340*9c5db199SXin Li # TODO(milleral): portageq can show scary looking error messages 341*9c5db199SXin Li # in the debug logs via stderr. We only look at stdout, so those 342*9c5db199SXin Li # get filtered, but it would be good to silence them. 343*9c5db199SXin Li cmd = ('portageq owners / ' + exec_name + 344*9c5db199SXin Li r'| sed -e "s/^[^\t].*/@@@ & @@@/" -e "s/^\t//"' 345*9c5db199SXin Li r'| tr \\n \\0' 346*9c5db199SXin Li ' | xargs -0 -r stat -L -c "%a %A %n" 2>&1') 347*9c5db199SXin Li portageq = host.run(cmd, ignore_status=True) 348*9c5db199SXin Li 349*9c5db199SXin Li # Parse into a set of names of packages containing an executable file. 350*9c5db199SXin Li packages = set() 351*9c5db199SXin Li pkg = '' 352*9c5db199SXin Li pkg_re = re.compile('@@@ (.*) @@@') 353*9c5db199SXin Li path_re = re.compile('^([0-7]{3,}) (.)') 354*9c5db199SXin Li for line in portageq.stdout.splitlines(): 355*9c5db199SXin Li match = pkg_re.search(line) 356*9c5db199SXin Li if match: 357*9c5db199SXin Li pkg = match.group(1) 358*9c5db199SXin Li continue 359*9c5db199SXin Li match = path_re.match(line) 360*9c5db199SXin Li if match: 361*9c5db199SXin Li isexec = int(match.group(1), 8) & 0o111 362*9c5db199SXin Li isfile = match.group(2) == '-' 363*9c5db199SXin Li if pkg and isexec and isfile: 364*9c5db199SXin Li packages.add(pkg) 365*9c5db199SXin Li 366*9c5db199SXin Li # If exactly one package found it must be the one we want, return it. 367*9c5db199SXin Li if len(packages) == 1: 368*9c5db199SXin Li return packages.pop() 369*9c5db199SXin Li 370*9c5db199SXin Li # TODO(milleral): Decide if it really is an error if not exactly one 371*9c5db199SXin Li # package is found. 372*9c5db199SXin Li # It is highly questionable as to if this should be left in the 373*9c5db199SXin Li # production version of this code or not. 374*9c5db199SXin Li if len(packages) == 0: 375*9c5db199SXin Li logging.warning('find_package_of() found no packages for "%s"', 376*9c5db199SXin Li exec_name) 377*9c5db199SXin Li else: 378*9c5db199SXin Li logging.warning('find_package_of() found multiple packages for "%s": ' 379*9c5db199SXin Li '%s', exec_name, ', '.join(packages)) 380*9c5db199SXin Li return '' 381*9c5db199SXin Li 382*9c5db199SXin Li 383*9c5db199SXin Lidef report_bug_from_crash(host, minidump_path): 384*9c5db199SXin Li """ 385*9c5db199SXin Li Given a host to query and a minidump, file a bug about the crash. 386*9c5db199SXin Li 387*9c5db199SXin Li @param host A host object that is where the dump came from 388*9c5db199SXin Li @param minidump_path The path to the dump file that should be reported. 389*9c5db199SXin Li """ 390*9c5db199SXin Li # TODO(milleral): Once this has actually been tested, remove the 391*9c5db199SXin Li # try/except. In the meantime, let's make sure nothing dies because of 392*9c5db199SXin Li # the fact that this code isn't very heavily tested. 393*9c5db199SXin Li try: 394*9c5db199SXin Li meta_path = os.path.splitext(minidump_path)[0] + '.meta' 395*9c5db199SXin Li with open(meta_path, 'r') as f: 396*9c5db199SXin Li for line in f.readlines(): 397*9c5db199SXin Li parts = line.split('=') 398*9c5db199SXin Li if parts[0] == 'exec_name': 399*9c5db199SXin Li package = find_package_of(host, parts[1].strip()) 400*9c5db199SXin Li if not package: 401*9c5db199SXin Li package = '<unknown package>' 402*9c5db199SXin Li logging.info('Would report crash on %s.', package) 403*9c5db199SXin Li break 404*9c5db199SXin Li except Exception as e: 405*9c5db199SXin Li logging.warning('Crash detection failed with: %s', e) 406