xref: /aosp_15_r20/external/cronet/build/android/pylib/symbols/stack_symbolizer.py (revision 6777b5387eb2ff775bb5750e3f5d96f37fb7352b)
1# Copyright 2017 The Chromium Authors
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 os
7import re
8import tempfile
9import time
10
11from devil.utils import cmd_helper
12from pylib import constants
13from pylib.constants import host_paths
14from .expensive_line_transformer import ExpensiveLineTransformer
15from .expensive_line_transformer import ExpensiveLineTransformerPool
16
17_STACK_TOOL = os.path.join(host_paths.ANDROID_PLATFORM_DEVELOPMENT_SCRIPTS_PATH,
18                           'stack')
19_MINIMUM_TIMEOUT = 10.0
20_PER_LINE_TIMEOUT = .005  # Should be able to process 200 lines per second.
21_PROCESS_START_TIMEOUT = 20.0
22_MAX_RESTARTS = 4  # Should be plenty unless tool is crashing on start-up.
23_POOL_SIZE = 1
24_PASSTHROUH_ON_FAILURE = True
25ABI_REG = re.compile('ABI: \'(.+?)\'')
26
27
28def _DeviceAbiToArch(device_abi):
29  # The order of this list is significant to find the more specific match
30  # (e.g., arm64) before the less specific (e.g., arm).
31  arches = ['arm64', 'arm', 'x86_64', 'x86_64', 'x86', 'mips']
32  for arch in arches:
33    if arch in device_abi:
34      return arch
35  raise RuntimeError('Unknown device ABI: %s' % device_abi)
36
37
38class Symbolizer:
39  """A helper class to symbolize stack."""
40
41  def __init__(self, apk_under_test=None):
42    self._apk_under_test = apk_under_test
43    self._time_spent_symbolizing = 0
44
45
46  def __del__(self):
47    self.CleanUp()
48
49
50  def CleanUp(self):
51    """Clean up the temporary directory of apk libs."""
52    if self._time_spent_symbolizing > 0:
53      logging.info(
54          'Total time spent symbolizing: %.2fs', self._time_spent_symbolizing)
55
56
57  def ExtractAndResolveNativeStackTraces(self, data_to_symbolize,
58                                         device_abi, include_stack=True):
59    """Run the stack tool for given input.
60
61    Args:
62      data_to_symbolize: a list of strings to symbolize.
63      include_stack: boolean whether to include stack data in output.
64      device_abi: the default ABI of the device which generated the tombstone.
65
66    Yields:
67      A string for each line of resolved stack output.
68    """
69    if not os.path.exists(_STACK_TOOL):
70      logging.warning('%s missing. Unable to resolve native stack traces.',
71                      _STACK_TOOL)
72      return
73
74    arch = _DeviceAbiToArch(device_abi)
75    if not arch:
76      logging.warning('No device_abi can be found.')
77      return
78
79    cmd = [_STACK_TOOL, '--arch', arch, '--output-directory',
80           constants.GetOutDirectory(), '--more-info']
81    env = dict(os.environ)
82    env['PYTHONDONTWRITEBYTECODE'] = '1'
83    with tempfile.NamedTemporaryFile(mode='w') as f:
84      f.write('\n'.join(data_to_symbolize))
85      f.flush()
86      start = time.time()
87      try:
88        _, output = cmd_helper.GetCmdStatusAndOutput(cmd + [f.name], env=env)
89      finally:
90        self._time_spent_symbolizing += time.time() - start
91    for line in output.splitlines():
92      if not include_stack and 'Stack Data:' in line:
93        break
94      yield line
95
96
97class PassThroughSymbolizer(ExpensiveLineTransformer):
98  def __init__(self, device_abi):
99    self._command = None
100    super().__init__(_PROCESS_START_TIMEOUT, _MINIMUM_TIMEOUT,
101                     _PER_LINE_TIMEOUT)
102    if not os.path.exists(_STACK_TOOL):
103      logging.warning('%s: %s missing. Unable to resolve native stack traces.',
104                      PassThroughSymbolizer.name, _STACK_TOOL)
105      return
106    arch = _DeviceAbiToArch(device_abi)
107    if not arch:
108      logging.warning('%s: No device_abi can be found.',
109                      PassThroughSymbolizer.name)
110      return
111    self._command = [
112        _STACK_TOOL, '--arch', arch, '--output-directory',
113        constants.GetOutDirectory(), '--more-info', '--pass-through', '--flush',
114        '--quiet', '-'
115    ]
116    self.start()
117
118  @property
119  def name(self):
120    return "symbolizer"
121
122  @property
123  def command(self):
124    return self._command
125
126
127class PassThroughSymbolizerPool(ExpensiveLineTransformerPool):
128  def __init__(self, device_abi):
129    self._device_abi = device_abi
130    super().__init__(_MAX_RESTARTS, _POOL_SIZE, _PASSTHROUH_ON_FAILURE)
131
132  def CreateTransformer(self):
133    return PassThroughSymbolizer(self._device_abi)
134
135  @property
136  def name(self):
137    return "symbolizer-pool"
138