#!/bin/sh

# Copyright (C) 2024-2025 Free Software Foundation, Inc.
# Copyright (C) 2025  Paul Floyd
#     pjfloyd@wanadoo.fr

# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program.  If not, see <http://www.gnu.org/licenses/>.

# Print a stack trace of a running process.
# Similar to the gcore command, but instead of creating a core file,
# we simply have gdb print out the stack backtrace to the terminal.

# This script is largely based on gstack/pstack from gdb.
# There is a bit of extra filtering to remove gdb warnings,
# the shell used is /bin/sh rather than bash.
# The main functional change is that it 'uses target remote | vgdb'
# instead of 'attach'.

GDB=${GDB:-$(command -v gdb)}
GDBARGS=${GDBARGS:-}
AWK=${AWK:-}
VERSION="3.27.1"

# Find an appropriate awk interpreter if one was not specified
# via the environment.
awk_prog=""
if [ -z "$AWK" ]; then
    for prog in gawk mawk nawk awk; do
	awk_prog=$(command -v $prog)
	test -n "$awk_prog" && break
    done
    AWK="$awk_prog"
fi
if [ ! -x "$AWK" ]; then
    echo "$0: could not find usable awk interpreter" 1>&2
    exit 2
fi

print_usage() {
    echo "Usage: $0 [-h|--help] [-v|--version] PID"
}

print_try_help() {
    echo "Try '$0 --help' for more information."
}

print_help() {
    print_usage
    echo "Print a stack trace of a running program"
    echo
    echo "  -h, --help         Print this message then exit."
    echo "  -v, --version      Print version information then exit."
}

print_version() {
    echo "${0}-${VERSION}"
}

# Parse options.
while getopts hv-: OPT; do
    if [ "$OPT" = "-" ]; then
	OPT="${OPTARG%%=*}"
	OPTARG="${OPTARG#'$OPT'}"
	OPTARG="${OPTARG#=}"
    fi

    case "$OPT" in
	h | help)
	    print_help
	    exit 0
	    ;;
	v | version)
	    print_version
	    exit 0
	    ;;
	\?)
	    # getopts has already output an error message.
	    print_try_help 1>&2
	    exit 2 ;;
	*)
	    echo "$0: unrecognized option '--$OPT'" 1>&2
	    print_try_help 1>&2
	    exit 2
	    ;;
    esac
done
shift $((OPTIND-1))

# The sole remaining argument should be the PID of the process
# whose backtrace is desired.
if [ $# -ne 1 ]; then
    print_usage 1>&2
    exit 1
fi

PID=$1

awk_script=$(cat << EOF
BEGIN {
  first=1
  attach_okay=0
}

/ATTACHED/ {
  attach_okay=1
}

/^#/ {
  if (attach_okay) {
    print \$0
  }
}

/^Thread/ {
  if (attach_okay) {
    if (first == 0)
       print ""
    first=0
    print \$0
  }
}

/warning:/ {
  next
}

END {
if (attach_okay == 0)
  exit 2
}
EOF
	  )

# Run GDB and remove some unwanted noise.
"$GDB" --quiet -nx $GDBARGS <<EOF 2>&1 |
set width 0
set height 0
set pagination no
set debuginfod enabled off
define vgdb-bt
target remote | vgdb --pid=\$arg0
echo "ATTACHED"
thread apply all bt
end
vgdb-bt $PID
EOF
$AWK -- "$awk_script"
