Thieu Le | c16253b | 2011-03-03 11:13:54 -0800 | [diff] [blame] | 1 | # Copyright (c) 2011 The Chromium OS 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 | |
| 5 | import logging |
| 6 | import os |
| 7 | from autotest_lib.client.common_lib import utils as client_utils |
Alex Miller | 24c27c1 | 2012-08-09 10:24:24 -0700 | [diff] [blame] | 8 | from autotest_lib.client.common_lib import error |
Chris Masone | bafbbb0 | 2012-05-16 13:41:36 -0700 | [diff] [blame] | 9 | from autotest_lib.client.common_lib.cros import dev_server |
Chris Masone | d931e8c | 2011-11-09 13:17:16 -0800 | [diff] [blame] | 10 | from autotest_lib.client.cros import constants |
Chris Masone | 44e4d6c | 2012-08-15 14:25:53 -0700 | [diff] [blame] | 11 | from autotest_lib.server.cros.dynamic_suite.constants import JOB_BUILD_KEY |
Thieu Le | c16253b | 2011-03-03 11:13:54 -0800 | [diff] [blame] | 12 | from autotest_lib.server import utils |
| 13 | |
Chris Sosa | accb5ce | 2012-08-30 17:29:15 -0700 | [diff] [blame] | 14 | |
Thieu Le | c16253b | 2011-03-03 11:13:54 -0800 | [diff] [blame] | 15 | def generate_minidump_stacktrace(minidump_path): |
| 16 | """ |
| 17 | Generates a stacktrace for the specified minidump. |
| 18 | |
| 19 | This function expects the debug symbols to reside under: |
| 20 | /build/<board>/usr/lib/debug |
Chris Masone | bafbbb0 | 2012-05-16 13:41:36 -0700 | [diff] [blame] | 21 | |
| 22 | @param minidump_path: absolute path to minidump to by symbolicated. |
| 23 | @raise client_utils.error.CmdError if minidump_stackwalk return code != 0. |
Thieu Le | c16253b | 2011-03-03 11:13:54 -0800 | [diff] [blame] | 24 | """ |
| 25 | symbol_dir = '%s/../../../lib/debug' % utils.get_server_dir() |
Alex Miller | 24c27c1 | 2012-08-09 10:24:24 -0700 | [diff] [blame] | 26 | logging.info('symbol_dir: %s', symbol_dir) |
Michael Krebs | 3005870 | 2012-09-25 15:37:04 -0700 | [diff] [blame] | 27 | client_utils.run('minidump_stackwalk "%s" "%s" > "%s.txt"' % |
Chris Masone | bafbbb0 | 2012-05-16 13:41:36 -0700 | [diff] [blame] | 28 | (minidump_path, symbol_dir, minidump_path)) |
| 29 | |
| 30 | |
| 31 | def symbolicate_minidump_with_devserver(minidump_path, resultdir): |
| 32 | """ |
| 33 | Generates a stack trace for the specified minidump by consulting devserver. |
| 34 | |
| 35 | This function assumes the debug symbols have been staged on the devserver. |
| 36 | |
| 37 | @param minidump_path: absolute path to minidump to by symbolicated. |
| 38 | @param resultdir: server job's result directory. |
| 39 | @raise DevServerException upon failure, HTTP or otherwise. |
| 40 | """ |
| 41 | # First, look up what build we tested. If we can't find this, we can't |
| 42 | # get the right debug symbols, so we might as well give up right now. |
| 43 | keyvals = client_utils.read_keyval(resultdir) |
Chris Masone | 44e4d6c | 2012-08-15 14:25:53 -0700 | [diff] [blame] | 44 | if JOB_BUILD_KEY not in keyvals: |
Chris Masone | bafbbb0 | 2012-05-16 13:41:36 -0700 | [diff] [blame] | 45 | raise dev_server.DevServerException( |
| 46 | 'Cannot determine build being tested.') |
| 47 | |
Chris Sosa | accb5ce | 2012-08-30 17:29:15 -0700 | [diff] [blame] | 48 | devserver = dev_server.CrashServer.resolve(keyvals[JOB_BUILD_KEY]) |
Chris Masone | aa10f8e | 2012-05-15 13:34:21 -0700 | [diff] [blame] | 49 | trace_text = devserver.symbolicate_dump( |
Chris Masone | 44e4d6c | 2012-08-15 14:25:53 -0700 | [diff] [blame] | 50 | minidump_path, keyvals[JOB_BUILD_KEY]) |
Chris Masone | bafbbb0 | 2012-05-16 13:41:36 -0700 | [diff] [blame] | 51 | if not trace_text: |
| 52 | raise dev_server.DevServerException('Unknown error!!') |
| 53 | with open(minidump_path + '.txt', 'w') as trace_file: |
| 54 | trace_file.write(trace_text) |
Thieu Le | c16253b | 2011-03-03 11:13:54 -0800 | [diff] [blame] | 55 | |
| 56 | |
Chris Masone | d931e8c | 2011-11-09 13:17:16 -0800 | [diff] [blame] | 57 | def find_and_generate_minidump_stacktraces(host_resultdir): |
Thieu Le | c16253b | 2011-03-03 11:13:54 -0800 | [diff] [blame] | 58 | """ |
| 59 | Finds all minidump files and generates a stack trace for each. |
| 60 | |
| 61 | Enumerates all files under the test results directory (recursively) |
| 62 | and generates a stack trace file for the minidumps. Minidump files are |
| 63 | identified as files with .dmp extension. The stack trace filename is |
| 64 | composed by appending the .txt extension to the minidump filename. |
Alex Miller | 24c27c1 | 2012-08-09 10:24:24 -0700 | [diff] [blame] | 65 | |
beeps | 71bf47c | 2013-11-14 20:44:30 -0800 | [diff] [blame] | 66 | @param host_resultdir: Directory to walk looking for dmp files. |
| 67 | |
Alex Miller | 24c27c1 | 2012-08-09 10:24:24 -0700 | [diff] [blame] | 68 | @returns The list of generated minidumps. |
Thieu Le | c16253b | 2011-03-03 11:13:54 -0800 | [diff] [blame] | 69 | """ |
Alex Miller | 24c27c1 | 2012-08-09 10:24:24 -0700 | [diff] [blame] | 70 | minidumps = [] |
Thieu Le | c16253b | 2011-03-03 11:13:54 -0800 | [diff] [blame] | 71 | for dir, subdirs, files in os.walk(host_resultdir): |
| 72 | for file in files: |
| 73 | if not file.endswith('.dmp'): |
| 74 | continue |
| 75 | minidump = os.path.join(dir, file) |
Chris Masone | bafbbb0 | 2012-05-16 13:41:36 -0700 | [diff] [blame] | 76 | |
| 77 | # First, try to symbolicate locally. |
| 78 | try: |
| 79 | generate_minidump_stacktrace(minidump) |
Chris Masone | 4245a73 | 2012-04-30 14:52:10 -0700 | [diff] [blame] | 80 | logging.info('Generated stack trace for dump %s', minidump) |
Alex Miller | 24c27c1 | 2012-08-09 10:24:24 -0700 | [diff] [blame] | 81 | minidumps.append(minidump) |
Chris Masone | bafbbb0 | 2012-05-16 13:41:36 -0700 | [diff] [blame] | 82 | continue |
| 83 | except client_utils.error.CmdError as err: |
Ilja H. Friedel | 04be2bd | 2014-05-07 21:29:59 -0700 | [diff] [blame] | 84 | logging.warning('Failed to generate stack trace locally for ' |
Chris Masone | bafbbb0 | 2012-05-16 13:41:36 -0700 | [diff] [blame] | 85 | 'dump %s (rc=%d):\n%r', |
| 86 | minidump, err.result_obj.exit_status, err) |
| 87 | |
| 88 | # If that did not succeed, try to symbolicate using the dev server. |
| 89 | try: |
Alex Miller | 24c27c1 | 2012-08-09 10:24:24 -0700 | [diff] [blame] | 90 | minidumps.append(minidump) |
Chris Masone | bafbbb0 | 2012-05-16 13:41:36 -0700 | [diff] [blame] | 91 | symbolicate_minidump_with_devserver(minidump, host_resultdir) |
| 92 | logging.info('Generated stack trace for dump %s', minidump) |
| 93 | continue |
| 94 | except dev_server.DevServerException as e: |
Ilja H. Friedel | 04be2bd | 2014-05-07 21:29:59 -0700 | [diff] [blame] | 95 | logging.warning('Failed to generate stack trace on devserver for ' |
Chris Masone | bafbbb0 | 2012-05-16 13:41:36 -0700 | [diff] [blame] | 96 | 'dump %s:\n%r', minidump, e) |
Alex Miller | 24c27c1 | 2012-08-09 10:24:24 -0700 | [diff] [blame] | 97 | return minidumps |
Thieu Le | c16253b | 2011-03-03 11:13:54 -0800 | [diff] [blame] | 98 | |
| 99 | |
Chris Masone | d931e8c | 2011-11-09 13:17:16 -0800 | [diff] [blame] | 100 | def fetch_orphaned_crashdumps(host, host_resultdir): |
Alex Miller | 24c27c1 | 2012-08-09 10:24:24 -0700 | [diff] [blame] | 101 | """ |
| 102 | Copy all of the crashes in the crash directory over to the results folder. |
| 103 | |
| 104 | @param host A host object of the device we're to pull crashes from. |
| 105 | @param host_resultdir The result directory for this host for this test run. |
| 106 | @return The list of minidumps that we pulled back from the host. |
| 107 | """ |
| 108 | minidumps = [] |
Chris Masone | d931e8c | 2011-11-09 13:17:16 -0800 | [diff] [blame] | 109 | for file in host.list_files_glob(os.path.join(constants.CRASH_DIR, '*')): |
Alex Miller | 24c27c1 | 2012-08-09 10:24:24 -0700 | [diff] [blame] | 110 | logging.info('Collecting %s...', file) |
beeps | 54dfb0c | 2013-09-11 11:53:38 -0700 | [diff] [blame] | 111 | host.get_file(file, host_resultdir, preserve_perm=False) |
| 112 | minidumps.append(file) |
Alex Miller | 24c27c1 | 2012-08-09 10:24:24 -0700 | [diff] [blame] | 113 | return minidumps |
Chris Masone | d931e8c | 2011-11-09 13:17:16 -0800 | [diff] [blame] | 114 | |
| 115 | |
Thieu Le | c16253b | 2011-03-03 11:13:54 -0800 | [diff] [blame] | 116 | def get_site_crashdumps(host, test_start_time): |
Alex Miller | 24c27c1 | 2012-08-09 10:24:24 -0700 | [diff] [blame] | 117 | """ |
| 118 | Copy all of the crashdumps from a host to the results directory. |
| 119 | |
| 120 | @param host The host object from which to pull crashes |
| 121 | @param test_start_time When the test we just ran started. |
| 122 | @return A list of all the minidumps |
| 123 | """ |
| 124 | host_resultdir = getattr(getattr(host, 'job', None), 'resultdir', None) |
| 125 | infodir = os.path.join(host_resultdir, 'crashinfo.%s' % host.hostname) |
Chris Masone | d931e8c | 2011-11-09 13:17:16 -0800 | [diff] [blame] | 126 | if not os.path.exists(infodir): |
| 127 | os.mkdir(infodir) |
Alex Miller | 24c27c1 | 2012-08-09 10:24:24 -0700 | [diff] [blame] | 128 | |
| 129 | # TODO(milleral): handle orphans differently. crosbug.com/38202 |
beeps | 54dfb0c | 2013-09-11 11:53:38 -0700 | [diff] [blame] | 130 | try: |
| 131 | orphans = fetch_orphaned_crashdumps(host, infodir) |
| 132 | except Exception as e: |
| 133 | orphans = [] |
| 134 | logging.warning('Collection of orphaned crash dumps failed %s', e) |
| 135 | |
Alex Miller | 24c27c1 | 2012-08-09 10:24:24 -0700 | [diff] [blame] | 136 | minidumps = find_and_generate_minidump_stacktraces(host_resultdir) |
beeps | 71bf47c | 2013-11-14 20:44:30 -0800 | [diff] [blame] | 137 | |
| 138 | # Record all crashdumps in status.log of the job: |
| 139 | # - If one server job runs several client jobs we will only record |
| 140 | # crashdumps in the status.log of the high level server job. |
| 141 | # - We will record these crashdumps whether or not we successfully |
| 142 | # symbolicate them. |
| 143 | if host.job and minidumps or orphans: |
| 144 | host.job.record('INFO', None, None, 'Start crashcollection record') |
| 145 | for minidump in minidumps: |
| 146 | host.job.record('INFO', None, 'New Crash Dump', minidump) |
| 147 | for orphan in orphans: |
| 148 | host.job.record('INFO', None, 'Orphaned Crash Dump', orphan) |
| 149 | host.job.record('INFO', None, None, 'End crashcollection record') |
| 150 | |
Alex Miller | 24c27c1 | 2012-08-09 10:24:24 -0700 | [diff] [blame] | 151 | orphans.extend(minidumps) |
| 152 | |
| 153 | for minidump in orphans: |
| 154 | report_bug_from_crash(host, minidump) |
| 155 | |
| 156 | return orphans |
| 157 | |
| 158 | |
| 159 | def find_packages_of(host, exec_name): |
| 160 | """ |
| 161 | Find the package that an executable came from. |
| 162 | |
| 163 | @param host A host object that has the executable. |
| 164 | @param exec_name The name of the executable. |
| 165 | @return The name of the package that installed the executable. |
| 166 | """ |
| 167 | packages = [] |
| 168 | |
| 169 | # TODO(milleral): It would be significantly faster to iterate through |
| 170 | # $PATH and run this than to point it at all of / |
| 171 | find = host.run('find / -executable -type f -name %s' % exec_name) |
| 172 | for full_path in find.stdout.splitlines(): |
| 173 | # TODO(milleral): This currently shows scary looking error messages |
| 174 | # in the debug logs via stderr. We only look at stdout, so those |
| 175 | # get filtered, but it would be good to silence them. |
| 176 | portageq = host.run('portageq owners / %s' % full_path) |
| 177 | if portageq.stdout: |
| 178 | packages.append(portageq.stdout.splitlines()[0].strip()) |
| 179 | |
| 180 | # TODO(milleral): This chunk of code is here to verify that mapping |
| 181 | # executable name to package gives you one and only one package. |
| 182 | # It is highly questionable as to if this should be left in the |
| 183 | # production version of this code or not. |
| 184 | if len(packages) == 0: |
| 185 | raise error.NoUniquePackageFound('no package for %s' % exec_name) |
| 186 | if len(packages) > 1: |
| 187 | # Running through all of /usr/bin in the chroot showed this should |
| 188 | # never happen, but still maybe possible? |
beeps | cb6f1e2 | 2013-06-28 19:14:10 -0700 | [diff] [blame] | 189 | raise error.NoUniquePackageFound('Crash detection found more than one' |
Alex Miller | 24c27c1 | 2012-08-09 10:24:24 -0700 | [diff] [blame] | 190 | 'package for %s: %s' % exec_name, packages) |
| 191 | |
| 192 | # |len(packages) == 1| at this point, as it should be anyway |
| 193 | return packages[0] |
| 194 | |
| 195 | |
| 196 | def report_bug_from_crash(host, minidump_path): |
| 197 | """ |
| 198 | Given a host to query and a minidump, file a bug about the crash. |
| 199 | |
| 200 | @param host A host object that is where the dump came from |
| 201 | @param minidump_path The path to the dump file that should be reported. |
| 202 | """ |
| 203 | # TODO(milleral): Once this has actually been tested, remove the |
| 204 | # try/except. In the meantime, let's make sure nothing dies because of |
| 205 | # the fact that this code isn't very heavily tested. |
| 206 | try: |
| 207 | meta_path = os.path.splitext(minidump_path)[0] + '.meta' |
| 208 | with open(meta_path, 'r') as f: |
| 209 | for line in f.readlines(): |
| 210 | parts = line.split('=') |
| 211 | if parts[0] == 'exec_name': |
| 212 | packages = find_packages_of(host, parts[1].strip()) |
beeps | cb6f1e2 | 2013-06-28 19:14:10 -0700 | [diff] [blame] | 213 | logging.info('Would report crash on %s.', packages) |
Alex Miller | 24c27c1 | 2012-08-09 10:24:24 -0700 | [diff] [blame] | 214 | break |
| 215 | except Exception as e: |
beeps | cb6f1e2 | 2013-06-28 19:14:10 -0700 | [diff] [blame] | 216 | logging.warning('Crash detection failed with: %s', e) |