#!/bin/bash
# Copyright (c) 2010 The Chromium OS Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.

# Script to generate minidump symbols in the format required by
# minidump_stackwalk to dump stack information.
#
# NOTE: This script must be run from the chromeos build chroot environment.
#

# --- BEGIN COMMON.SH BOILERPLATE ---
# Load common CrOS utilities.  Inside the chroot this file is installed in
# /usr/lib/crosutils.  Outside the chroot we find it relative to the script's
# location.
find_common_sh() {
  local common_paths=(/usr/lib/crosutils $(dirname "$(readlink -f "$0")"))
  local path

  SCRIPT_ROOT=
  for path in "${common_paths[@]}"; do
    if [ -r "${path}/common.sh" ]; then
      SCRIPT_ROOT=${path}
      break
    fi
  done
}

find_common_sh
. "${SCRIPT_ROOT}/common.sh" || { echo "Unable to load common.sh"; exit 1; }
# --- END COMMON.SH BOILERPLATE ---

# Script must be run inside the chroot
restart_in_chroot_if_needed "$@"

get_default_board

# Flags
DEFINE_string board "$DEFAULT_BOARD" "The board to build packages for."
DEFINE_string minidump_symbol_root "" \
  "Symbol root (defaults to /usr/lib/debug/breakpad for board)"
DEFINE_boolean verbose ${FLAGS_FALSE} "Be verbose."

DUMP_SYMS="dump_syms"

CUMULATIVE_SIZE=0
ANY_ERRORS=0

SYM_FILE=$(mktemp "/tmp/sym.XXXX")
ERR_FILE=$(mktemp "/tmp/err.XXXX")

function cleanup() {
  rm -f "${SYM_FILE}" "${ERR_FILE}"
}

# Given path to a debug file, return its text file
function get_text_for_debug() {
  local debug_file=$1
  local text_dir=$(dirname "${debug_file#$DEBUG_ROOT}")
  local text_path=${SYSROOT}${text_dir}/$(basename "${debug_file}" .debug)
  echo ${text_path}
}

# Given path to a text file, return its debug file
function get_debug_for_text() {
  local text_file=$1
  local text_path=${text_file#${SYSROOT}}
  local debug_path=${DEBUG_ROOT}${text_path}.debug
  echo ${debug_path}
}

# Verify the file given is not a 64-bit ELF file.  For now all targets
# are 32-bit, we'll need to determine the correct bit automatically
# once we release 64-bit versions.  Allow files in /usr/lib64 to exist
# on the image and only give warnings.
function verify_not_64b_elf() {
  local elf="$1"
  if file "${elf}" | grep -q "ELF 64-bit"; then
    # Allow with a warning if in /usr/lib64
    if echo "${elf}" | grep -q /usr/lib64; then
      warn "64-bit usr/lib64 file ${elf} ignored."
    else
      error "File ${elf} is a 64b executable"
      ANY_ERRORS=1
    fi
    return 1
  fi
  return 0
}

# Dump given debug and text file.  Returns 1 if any errors, even
# if they can be ignored, but only sets ANY_ERRORS if the error should not
# be ignored (and we should not proceed to upload).
function dump_file() {
  local debug_file="$1"
  local text_file="$2"
  local debug_directory="$(dirname "${debug_file}")"
  # 64b ELF files may be installed on the target in PERL directories
  verify_not_64b_elf "${debug_file}" || return 1
  verify_not_64b_elf "${text_file}" || return 1
  # Dump symbols as root in order to read all files.
  if ! sudo "${DUMP_SYMS}" "${text_file}" "${debug_directory}" > "${SYM_FILE}" \
       2> "${ERR_FILE}"; then
    # A lot of files (like kernel files) contain no debug information, do
    # not consider such occurrences as errors.
    if grep -q "file contains no debugging information" "${ERR_FILE}"; then
      warn "No symbols found for ${text_file}"
      return 1
    fi
    error "Unable to dump symbols for ${text_file}:"
    cat "${ERR_FILE}"
    ANY_ERRORS=1
    return 1
  fi
  local file_id=$(head -1 ${SYM_FILE} | cut -d' ' -f4)
  local module_name=$(head -1 ${SYM_FILE} | cut -d' ' -f5)
  if [ ${FLAGS_verbose} -eq ${FLAGS_TRUE} ]; then
    # Show file upload success and symbol info for easier lookup
    info "Dumped symbols from ${text_file} for ${module_name}|${file_id}."
  fi
  # Sanity check: if we've created the same named file in the /usr/lib/debug
  # directory during the src_compile stage of an ebuild, verify our sym file
  # is the same.
  local installed_sym="${DEBUG_ROOT}"/$(basename "${text_file}").sym
  if [ -e "${installed_sym}" ]; then
    if ! diff "${installed_sym}" "${SYM_FILE}"; then
      error "${installed_sym} differ from current sym file:"
      diff "${installed_sym}" "${SYM_FILE}"
      ANY_ERRORS=1
      return 1
    fi
  fi
  size=$(wc -c "${SYM_FILE}" | cut -d' ' -f1)
  CUMULATIVE_SIZE=$((CUMULATIVE_SIZE + $size))

  local container_dir="${FLAGS_minidump_symbol_root}/${module_name}/${file_id}"
  sudo mkdir -p "${container_dir}"
  sudo mv "${SYM_FILE}" "${container_dir}/${module_name}.sym"
  return 0
}

# Convert the given debug file.  No return value.
function process_file() {
  local debug_file="$1"
  local text_file="$(get_text_for_debug ${debug_file})"
  if [ "${text_file##*.}" == "ko" ]; then
    # Skip kernel objects.  We can't use their symbols and they sometimes
    # have objects with empty text sections which trigger errors in dump_sym.
    if [ ${FLAGS_verbose} -eq ${FLAGS_TRUE} ]; then
      info "Skipping kernel object: ${text_file}"
    fi
    return 0
  fi
  if [ "${text_file#${AUTOTEST_ROOT}}" != "${text_file}" ]; then
    # Skip autotest files, they are not part of the image to debug
    # and some cause trouble to dump_syms because they are built
    # externally (with different build options).
    if [ ${FLAGS_verbose} -eq ${FLAGS_TRUE} ]; then
      info "Skipping autotest file: ${text_file}"
    fi
    return 0
  fi
  if [ ! -f "${text_file}" ]; then
    # Allow files to not exist, for instance if they are in the INSTALL_MASK.
    warn "Binary does not exist: ${text_file}"
    return 0
  fi

  dump_file "${debug_file}" "${text_file}"
}

function main() {
  trap cleanup EXIT

  # Parse command line
  FLAGS_HELP="usage: $0 [flags] [<files...>]"
  FLAGS "$@" || exit 1
  eval set -- "${FLAGS_ARGV}"

  set -e

  [ -n "$FLAGS_board" ] ||  die "--board is required."

  SYSROOT="/build/${FLAGS_board}"

  if [[ -z "${FLAGS_minidump_symbol_root}" ]]; then
    FLAGS_minidump_symbol_root="${SYSROOT}/usr/lib/debug/breakpad"
  fi

  info "Writing minidump symbols to ${FLAGS_minidump_symbol_root}"

  DEBUG_ROOT="${SYSROOT}/usr/lib/debug"
  AUTOTEST_ROOT="${SYSROOT}/usr/local/autotest"
  CUMULATIVE_SIZE=0

  if [ -z "${FLAGS_ARGV}" ]; then
    for debug_file in $(find "${DEBUG_ROOT}" -name \*.debug); do
      ! process_file "${debug_file}"
    done
  else
    for either_file in ${FLAGS_ARGV}; do
      either_file=${either_file#\'}
      either_file=${either_file%\'}
      if [ ! -f "${either_file}" ]; then
        error "Specified file ${either_file} does not exist"
        ANY_ERRORS=1
        continue
      fi
      if [ "${either_file##*.}" == "debug" ]; then
        debug_file="${either_file}"
      else
        debug_file="$(get_debug_for_text ${either_file})"
      fi
      ! process_file "${debug_file}"
    done
  fi

  info "Generated ${CUMULATIVE_SIZE}B of debug information"

  [ ${ANY_ERRORS} -ne 0 ] && die "Encountered problems"
  return 0
}

main "$@"
