| # Copyright 2021 The Chromium Authors |
| # Use of this source code is governed by a BSD-style license that can be |
| # found in the LICENSE file. |
| """Uses dump_syms to extract breakpad symbol files.""" |
| |
| import logging |
| import os |
| import subprocess |
| import sys |
| |
| import flag_utils |
| import rename_breakpad |
| |
| sys.path.insert( |
| 0, |
| os.path.join(os.path.dirname(__file__), os.pardir, os.pardir, 'third_party', |
| 'catapult', 'common', 'py_utils')) |
| from py_utils import tempfile_ext |
| |
| |
| def ExtractBreakpadFiles(dump_syms_path, |
| build_dir, |
| breakpad_output_dir, |
| search_unstripped=True, |
| module_ids=None): |
| """Uses dump_syms to extract breakpad files. |
| |
| Args: |
| dump_syms_path: The path to the dump_syms binary that should be run. |
| build_dir: The path to the input directory containing the binaries that |
| dump_syms will use. |
| breakpad_output_dir: The output directory for the breakpad symbol files |
| produced. |
| search_unstripped: Boolean flag for whether to search for 'lib.unstripped' |
| subdirectory or not. If specified and '|build_dir|/lib.unstripped' exists, |
| dump_syms is run on this directory instead. If not specified, dump_syms is |
| run on |build_dir|. |
| module_ids: A set of module IDs needed to symbolize the trace. Only extracts |
| breakpad on symbol binaries with a module ID in this set. Extracts all |
| symbols if |module_ids| is None. |
| |
| Returns: |
| True if at least one breakpad file could be extracted from |build_dir|; |
| False, otherwise |
| |
| Raises: |
| Exception: If the dump_syms binary or the input and output |
| directories cannot be found. |
| """ |
| # Check to see if |dump_syms_path| is a file. |
| dump_syms_path = EnsureDumpSymsBinary(dump_syms_path, build_dir) |
| |
| # Check if |build_dir| and |breakpad_base_dir| are directories. |
| if not os.path.isdir(build_dir): |
| raise Exception('Invalid build directory.') |
| if not os.path.isdir(breakpad_output_dir): |
| raise Exception('Invalid breakpad output directory.') |
| |
| # If on Android, lib.unstripped will hold symbols for binaries. |
| symbol_dir = build_dir |
| if search_unstripped and os.path.isdir( |
| os.path.join(build_dir, 'lib.unstripped')): |
| symbol_dir = os.path.join(build_dir, 'lib.unstripped') |
| |
| breakpad_file_count = 0 |
| for file_iter in os.listdir(symbol_dir): |
| input_file_path = os.path.join(symbol_dir, file_iter) |
| if os.path.isfile(input_file_path) and IsValidBinaryPath(input_file_path): |
| if not IsModuleNeededForSymbolization(dump_syms_path, module_ids, |
| input_file_path): |
| continue |
| # Construct absolute file paths for input and output files. |
| output_file_path = os.path.join(breakpad_output_dir, |
| file_iter + '.breakpad') |
| |
| flag_utils.GetTracingLogger().debug('Extracting breakpad file from: %s', |
| input_file_path) |
| if _RunDumpSyms(dump_syms_path, input_file_path, output_file_path): |
| flag_utils.GetTracingLogger().debug('Extracted breakpad to: %s', |
| output_file_path) |
| breakpad_file_count += 1 |
| |
| # Extracting breakpad symbols should be successful with at least one file. |
| return breakpad_file_count > 0 |
| |
| |
| def ExtractBreakpadOnSubtree(symbols_root, metadata, dump_syms_path): |
| """Converts symbol files in the given subtree into breakpad files. |
| |
| Args: |
| symbols_root: root of subtree containing symbol files to convert to breakpad |
| format. |
| metadata: trace metadata to extract module ids from. |
| dump_syms_path: local path to dump_syms binary. |
| |
| Raises: |
| Exception: if path to dump_syms binary not passed or no breakpad files |
| could be extracted from subtree. |
| """ |
| flag_utils.GetTracingLogger().debug('Converting symbols to breakpad format.') |
| if dump_syms_path is None: |
| raise Exception('Path to dump_syms binary is required for symbolizing ' |
| 'official Android traces. You can build dump_syms from ' |
| 'your local build directory with the right architecture ' |
| 'with: autoninja -C out_<arch>/Release dump_syms.') |
| |
| # Set of module IDs we need to symbolize. |
| module_ids = GetModuleIdsToSymbolize(metadata) |
| |
| did_extract = False |
| for root_dir, _, _ in os.walk(symbols_root, topdown=True): |
| root_path = os.path.abspath(root_dir) |
| did_extract |= ExtractBreakpadFiles(dump_syms_path, |
| root_path, |
| root_path, |
| search_unstripped=False, |
| module_ids=module_ids) |
| |
| if not did_extract: |
| raise Exception( |
| 'No breakpad symbols could be extracted from files in the subtree: ' + |
| symbols_root) |
| |
| |
| def GetModuleIdsToSymbolize(metadata): |
| """Returns module IDs needed for symbolization and logs breakpad message. |
| |
| We log the message before calling |ExtractBreakpadFiles| because otherwise |
| we will repeatedly log when |ExtractBreakpadOnSubtree| recursively runs |
| |ExtractBreakpadFiles|. |
| |
| Args: |
| metadata: metadata extracted from the trace. |
| """ |
| module_ids = metadata.GetModuleIds() |
| |
| if module_ids is None: |
| flag_utils.GetTracingLogger().info( |
| 'No specified modules to extract. Converting all symbol ' |
| 'binaries to breakpad.') |
| else: |
| flag_utils.GetTracingLogger().debug('Module IDs to symbolize: %s', |
| (module_ids)) |
| |
| return module_ids |
| |
| |
| def IsModuleNeededForSymbolization(dump_syms_path, module_ids, symbol_binary): |
| """Determines if we should extract breakpad from symbol binary. |
| |
| If module_ids is None, then we extract breakpad on all symbol binaries. |
| Otherwise, we only extract breakpad on binaries with a module ID needed to |
| symbolize the trace. |
| |
| Args: |
| dump_syms_path: The path to the dump_syms binary that should be run. |
| module_ids: A set of module IDs needed to symbolize the trace. Only extracts |
| breakpad on symbol binaries with a module ID in this set. Extracts all |
| symbols if |module_ids| is None. |
| symbol_binary: Symbol binary file to symbolize trace. |
| |
| Returns: |
| True if symbols should be extracted to breakpad; false, otherwise. |
| """ |
| if module_ids is None: |
| return True |
| |
| # Only convert breakpad if binary has module ID we need to symbolize. |
| module_id = _GetModuleIDFromBinary(dump_syms_path, symbol_binary) |
| if module_id is None or module_id not in module_ids: |
| flag_utils.GetTracingLogger().debug( |
| 'Skipping breakpad extraction for module (%s, %s) ' |
| 'since trace has no frames with this ID.', module_id, symbol_binary) |
| return False |
| return True |
| |
| |
| def _GetModuleIDFromBinary(dump_syms_path, symbol_binary): |
| """Gets module ID of symbol binary. |
| |
| Args: |
| dump_syms_path: The path to the dump_syms binary that should be run. |
| symbol_binary: path to symbol binary. |
| |
| Returns: |
| Module ID from symbol binary, or None if fails to extract. |
| """ |
| # Creates temp file because |_RunDumpSyms| pipes result into a file. |
| # After extracting the module ID, we do not need this output file. |
| with tempfile_ext.NamedTemporaryFile(mode='w+') as output_file: |
| output_file.close() # RunDumpsyms opens the file again. |
| if not _RunDumpSyms(dump_syms_path, |
| symbol_binary, |
| output_file.name, |
| only_module_header=True): |
| return None |
| return rename_breakpad.ExtractModuleIdIfValidBreakpad(output_file.name) |
| |
| |
| def _RunDumpSyms(dump_syms_binary, |
| input_file_path, |
| output_file_path, |
| only_module_header=False): |
| """Runs the dump_syms binary on a file and outputs the resulting breakpad. |
| |
| symbols to the specified file. |
| Args: |
| dump_syms_binary: The path to the dump_syms binary that should be run. |
| input_file_path: Input file path to run dump_syms on. |
| output_file_path: Output file path to store result. |
| only_module_header: Only extracts the module header, if specified. |
| |
| Returns: |
| True if the command succeeded and false otherwise. |
| """ |
| cmd = [dump_syms_binary] |
| if only_module_header: |
| cmd.append('-i') |
| cmd.append(input_file_path) |
| |
| with open(output_file_path, 'w') as f: |
| proc = subprocess.Popen(cmd, stdout=f, stderr=subprocess.PIPE) |
| stderr = proc.communicate()[1] |
| if proc.returncode != 0: |
| flag_utils.GetTracingLogger().info( |
| 'Dump_syms failed to extract information from symbol binary: %s. ' |
| 'Error: %s', input_file_path, str(stderr)) |
| return False |
| return True |
| |
| |
| def EnsureDumpSymsBinary(dump_syms_path, build_dir): |
| """Checks to see if dump_syms can be found. |
| |
| Args: |
| dump_syms_path: The given path to dump_syms. |
| build_dir: The path to a directory. |
| |
| Returns: |
| The path to the dump_syms binary or raises exception. |
| """ |
| if dump_syms_path is not None and os.path.isfile( |
| dump_syms_path) and 'dump_syms' in dump_syms_path: |
| return dump_syms_path |
| |
| if build_dir is not None: |
| path_to_dump_syms = os.path.join(build_dir, 'dump_syms') |
| if os.path.isfile(path_to_dump_syms): |
| return path_to_dump_syms |
| |
| if not build_dir: |
| build_dir = 'out/android' # For error message. |
| raise Exception( |
| 'dump_syms binary not found. Build a binary with ' |
| 'autoninja -C {build_dir} dump_syms and try again with ' |
| '--dump_syms={build_dir}/dump_syms'.format(build_dir=build_dir)) |
| |
| |
| def IsValidBinaryPath(path): |
| # Get the file name from the full file path. |
| file_name = os.path.basename(path) |
| if file_name.endswith('partition.so') or file_name.endswith( |
| '.dwp') or file_name.endswith('.dwo') or '_combined' in file_name: |
| return False |
| return file_name == 'chrome' or file_name.endswith( |
| '.so') or file_name.endswith('.exe') |