text: # Copyright 2021 The Chromium Authors. All rights reserved.
  # 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')
  