diff --git a/archive-cores/action.yaml b/archive-cores/action.yaml new file mode 100644 index 0000000..63476d4 --- /dev/null +++ b/archive-cores/action.yaml @@ -0,0 +1,31 @@ +name: "archive cores" +description: 'Archive all cores corresponding to a given set of core_patterns' + +inputs: + dest: + description: | + Destination directory to archive cores to. A subdirectory + 'cores' will be created under that destination directory + required: true + + core_patterns: + description: | + List of paths where the cores might be searched. Glob paths + are supported to filter core files from the others. + required: true + + ignore_filters: + description: | + Filters to ignore a found core (space separated, globs supported) applied + against the full backtrace. + + This can for example be the function name that will always be present + in the relevant call stack. + default: "" + +runs: + using: "composite" + steps: + - name: Archive found cores + shell: bash + run: ${GITHUB_ACTION_PATH}/archive-cores.sh "${{ inputs.dest }}" "${{ inputs.ignore_filters }}" ${{ inputs.core_patterns }} diff --git a/archive-cores/archive-cores.sh b/archive-cores/archive-cores.sh new file mode 100755 index 0000000..7bf027d --- /dev/null +++ b/archive-cores/archive-cores.sh @@ -0,0 +1,134 @@ +#!/bin/bash +set -euo pipefail + +# Archive all cores corresponding to a given set of core_patterns to +# $dest_directory/cores. +# +# When possible, the binary that generated the core is also archived, and +# backtraces are generated and archived. +# +# Ignore filters are applied against each found backtrace to determine if the +# core should be archived or not. +# +# Example: +# ./archive_cores.sh $ARTIFACT_DIR "daemontest" ./core.* /tmp/core.* + +if [ "$#" -lt 2 ]; then + echo "usage: $0 " + exit 1 +fi + +DEST="$1" +IGNORE_FILTERS="$2" +shift +shift +CORE_PATTERNS="$@" + +OUTPUT_DIR="${DEST}/cores" + +# Piping into xargs is sometime necessary when globs are single quoted +# by the caller / environment. +CORES="$(echo $CORE_PATTERNS | xargs ls -dUN || /bin/true 2> /dev/null)" + +is_expected_crash() +{ + local bt_full="$1" + local bin="$2" + + for match in $FILTER_PATTERNS; do + if grep -q "$match" "$bt_full"; then + echo "Known and expected crash of $bin: $match, skipping" + return 0 + fi + done + + # Ignores externally sent SIGSEGV signals. This might be used to test behavior + # of the process when encountering this exception. + if grep '^#3 ' -A2 "$bt_full" | grep -q '^#4 .*\(epoll_wait\|libpthread\)'; then + echo "Known and expected crash of $bin: daemon externally killed with SIGSEGV, skipping" + return 0 + fi + + return 1 +} + +# Retrieve the path of the binary from a core +get_bin_path() +{ + local core="$1" + local bin= + + # First try: get the core name with file + bin="$(file "$core" | grep 'execfn: ' | sed "s/.*execfn: '\([^']\+\)'.*/\1/")" + test -n "$bin" && bin="$(command -v "$bin")" + + # This may fail if there are too many section headers in the core, newer + # versions of `file` can deal with this + if [ ! -e "$bin" ]; then + bin="$(file -Pelf_phnum=20000 "$core" 2>/dev/null \ + | grep 'execfn: ' \ + | sed "s/.*execfn: '\([^']\+\)'.*/\1/")" + test -n "$bin" && bin="$(command -v "$bin")" + fi + + # Last try with gdb + if [ ! -e "$bin" ]; then + bin="$(gdb -n --batch "$core" 2>/dev/null \ + | grep "^Core was generated by" \ + | sed "s/^Core was generated by \`\([^ \`']\+\).*/\1/")" + test -n "$bin" && bin="$(command -v "$bin")" + fi + + if [ -e "$bin" ]; then + echo "$bin" + return 0 + else + return 1 + fi +} + +if [ -z "$CORES" ]; then + echo "no core to collect" + exit 0 +fi + +mkdir -p "${OUTPUT_DIR}" +for core in $CORES; do + if ! file "$core" | grep -q 'core file'; then + echo "$core is not a core file" + file "$core" + continue + fi + + core_name="$(basename "$core")" + bin="$(get_bin_path "$core")" + + echo "archive core '$core' for binary '$bin' to $OUTPUT_DIR" + + if [ ! -e "$bin" ]; then + echo "failed to parse binary path or does not exist, got: '$bin'" + file "$core" + else + # Generate backtraces + core_name="$(basename "$bin")-$(basename "$core")" + prefix="${OUTPUT_DIR}/$core_name-backtrace" + bt_full="$prefix-full.txt" + bt_all="$prefix-all.txt" + + gdb -batch -ex 'bt full' "$bin" "$core" > "$bt_full" + + # Known and expected crashes + if is_expected_crash "$bt_full" "$bin"; then + rm "$bt_full" + continue + fi + + gdb -batch -ex 'thread apply all bt' "$bin" "$core" > "$bt_all" + + # Archive binary + cp "$bin" "$OUTPUT_DIR" + fi + + # Archive core + tar -czSf "${OUTPUT_DIR}/$core_name.tar.gz" "$core" +done