﻿#!/usr/bin/env bash

export RUNTIME_PATH=$1
export EXECUTION_DIR=$(dirname "$0")

exitcode_list[0]="Exited Successfully"
exitcode_list[130]="SIGINT  Ctrl-C occurred. Likely tests timed out."
exitcode_list[131]="SIGQUIT Ctrl-\ occurred. Core dumped."
exitcode_list[132]="SIGILL  Illegal Instruction. Core dumped. Likely codegen issue."
exitcode_list[133]="SIGTRAP Breakpoint hit. Core dumped."
exitcode_list[134]="SIGABRT Abort. Managed or native assert, or runtime check such as heap corruption, caused call to abort(). Core dumped."
exitcode_list[135]="IGBUS  Unaligned memory access. Core dumped."
exitcode_list[136]="SIGFPE  Bad floating point arguments. Core dumped."
exitcode_list[137]="SIGKILL Killed eg by kill"
exitcode_list[139]="SIGSEGV Illegal memory access. Deref invalid pointer, overrunning buffer, stack overflow etc. Core dumped."
exitcode_list[143]="SIGTERM Terminated. Usually before SIGKILL."
exitcode_list[159]="SIGSYS  Bad System Call."

function print_info_from_core_file_using_lldb {
  local core_file_name=$1
  local executable_name=$2
  local plugin_path_name="$RUNTIME_PATH/shared/Microsoft.NETCore.App/9.9.9/libsosplugin.so"

  # check for existence of lldb on the path
  hash lldb 2>/dev/null || { echo >&2 "lldb was not found. Unable to print core file."; return; }

  # pe and clrstack are defined in libsosplugin.so
  if [ ! -f $plugin_path_name ]; then
    echo $plugin_path_name cannot be found.
    return
  fi

  echo ----- start ===============  lldb Output =====================================================
  echo Printing managed exceptions and managed call stack.
  lldb -O "settings set target.exec-search-paths $RUNTIME_PATH" -o "plugin load $plugin_path_name" -o "clrthreads -managedexception" -o "pe -nested" -o "clrstack -all -a -f" -o "quit"  --core $core_file_name $executable_name
  echo ----- end ===============  lldb Output =======================================================
}

function print_info_from_core_file_using_gdb {
  local core_file_name=$1
  local executable_name=$2

  # Check for the existence of GDB on the path
  hash gdb 2>/dev/null || { echo >&2 "GDB was not found. Unable to print core file."; return; }

  echo ----- start ===============  GDB Output =====================================================
  # Open the dump in GDB and print the stack from each thread. We can add more
  # commands here if desired.
  echo printing native stack.
  gdb --batch -ex "thread apply all bt full" -ex "quit" $executable_name $core_file_name
  echo ----- end ===============  GDB Output =======================================================
}

function print_info_from_core_file {
  local core_file_name=$1
  local executable_name=$RUNTIME_PATH/$2

  if ! [ -e $executable_name ]; then
    echo "Unable to find executable $executable_name"
    return
  elif ! [ -e $core_file_name ]; then
    echo "Unable to find core file $core_file_name"
    return
  fi
  echo "Printing info from core file $core_file_name"
  print_info_from_core_file_using_gdb $core_file_name $executable_name
  print_info_from_core_file_using_lldb $core_file_name $executable_name
}

function copy_core_file_to_temp_location {
  local core_file_name=$1

  local storage_location="/tmp/coredumps"

  # Create the directory (this shouldn't fail even if it already exists).
  mkdir -p $storage_location

  local new_location=$storage_location/core.$RANDOM

  echo "Copying core file $core_file_name to $new_location in case you need it."
  cp $core_file_name $new_location
}

if [ "$RUNTIME_PATH" == "" ]
then
echo error: RUNTIME_PATH is not defined.  Usage: $0 RUNTIME_PATH
exit -1
fi

# ========================= BEGIN Core File Setup ============================
if [ "$(uname -s)" == "Darwin" ]; then
  # On OS X, we will enable core dump generation only if there are no core 
  # files already in /cores/ at this point. This is being done to prevent
  # inadvertently flooding the CI machines with dumps.
  if [[ ! -d "/cores" || ! "$(ls -A /cores)" ]]; then
    ulimit -c unlimited
  fi
elif [ "$(uname -s)" == "Linux" ]; then
  # On Linux, we'll enable core file generation unconditionally, and if a dump
  # is generated, we will print some useful information from it and delete the
  # dump immediately.

  if [ -e /proc/self/coredump_filter ]; then
      # Include memory in private and shared file-backed mappings in the dump.
      # This ensures that we can see disassembly from our shared libraries when
      # inspecting the contents of the dump. See 'man core' for details.
      echo -n 0x3F > /proc/self/coredump_filter
  fi

  ulimit -c unlimited
fi
# ========================= END Core File Setup ==============================

# ========================= BEGIN Test Execution =============================
echo ----- start $(date +"%T") ===============  To repro directly: ===================================================== 
echo pushd $EXECUTION_DIR
[[TestRunCommandsEcho]]
echo popd
echo ===========================================================================================================
pushd $EXECUTION_DIR
[[TestRunCommands]]
test_exitcode=$?
popd
echo ----- end $(date +"%T") ----- exit code $test_exitcode ----------------------------------------------------------

if [ "${exitcode_list[$test_exitcode]}" != "" ]; then
  echo exit code $test_exitcode means ${exitcode_list[$test_exitcode]}
fi
# ========================= END Test Execution ===============================

# ======================= BEGIN Core File Inspection =========================
pushd $EXECUTION_DIR >/dev/null
if [[ "$(uname -s)" == "Linux" && $test_exitcode -ne 0 ]]; then
  echo Looking around for any Linux dump...
  # Depending on distro/configuration, the core files may either be named "core"
  # or "core.<PID>" by default. We read /proc/sys/kernel/core_uses_pid to 
  # determine which it is.
  core_name_uses_pid=0
  if [ -e /proc/sys/kernel/core_uses_pid ] && [ "1" == $(cat /proc/sys/kernel/core_uses_pid) ]; then
    core_name_uses_pid=1
  fi

  if [ $core_name_uses_pid == "1" ]; then
    # We don't know what the PID of the process was, so let's look at all core
    # files whose name matches core.NUMBER
    echo Looking for files matching core.* ...
    for f in core.*; do
      [[ $f =~ core.[0-9]+ ]] && print_info_from_core_file "$f" "dotnet" && copy_core_file_to_temp_location "$f" && rm "$f"
    done
  elif [ -f core ]; then
    echo found a dump named core in $EXECUTION_DIR !
    print_info_from_core_file "core" "dotnet"
    copy_core_file_to_temp_location "core"
    rm "core"
  else
    echo ... found no dump in $PWD
  fi
fi
popd >/dev/null
# ======================== END Core File Inspection ==========================
exit $test_exitcode
