#! /bin/bash
# set -xv
#=========================================================================
# Copyright (C) GemTalk Systems 1986-2024.  All Rights Reserved.
#
# Name - pstack
#
# Purpose - Generate C and Smalltalk call stacks for GemStone processes.
#
# Full C and Smalltalk stack printing, including args and temps of each
# stack frame:
#   pstack <pid>
# Brief stack, one line per frame:
#   pstack -b <pid>
# C full stacks only, omit smalltalk stack:
#   pstack -c <pid>
# Brief C stacks only:
#   pstack -C <pid>
# Print the help screen and exit:
#   pstack -h
#
# gdb/lldb must be in the user's PATH, and $GEMSTONE must be set.
#=========================================================================

usage(){
    echo "Usage: " >&2
    echo "   pstack [ -h | -b | -c | -C ] pidOfProcess" >&2
    echo "Where:" >&2
    echo "   -b   Brief mode.  Print brief C and Smalltalk stacks." >&2
    echo "   -c   Print C stacks only.  Smalltalk stacks are not printed." >&2
    echo "   -C   Same as -c except brief C stacks are printed." >&2
    echo "   -h   Print this help screen and exit." >&2
}

badArgs(){
    echo "[Error]: Missing or invalid argument(s) detected." >&2
    usage
    exit 1
}    

printHelp(){
    usage
    exit 0
}

if [ "$1" = "-h" ]; then
    printHelp
fi

OS=`uname -s`
if [ "$OS" = "Linux" ]; then
    DBG="gdb"
elif [ "$OS" = "Darwin" ]; then
    DBG="lldb"
else
    echo "Unexpected operating system $OS"
    exit 1
fi

# Brief mode
if [ "$1" = "-b" ]; then
    if [ $# -ne 2 ] ; then
	badArgs
    fi
    pid=$2
    gcmd="$GEMSTONE/bin/pstackbrief.${DBG}"
elif [ "$1" = "-c" ]; then
    if [ $# -ne 2 ] ; then
	badArgs
    fi
    # Full C stacks only  
    pid=$2
    gcmd="$GEMSTONE/bin/pstack_c_only.${DBG}"  
elif [ "$1" = "-C" ]; then
    if [ $# -ne 2 ] ; then
	badArgs
    fi
    # *brief* C stacks only  
    pid=$2
    gcmd="$GEMSTONE/bin/pstack_brief_c_only.${DBG}"
else
    # Normal mode    
    if [ $# -ne 1 ] ; then
	badArgs
    fi
    pid=$1
    gcmd="$GEMSTONE/bin/pstack.${DBG}"
fi

DEBUGGER=${DBG}
type $DEBUGGER
errcode=$?
if [ $errcode -ne 0 ] ; then
    echo "[Error]: Cannot find $DEBUGGER in path $PATH"
    exit $errcode
fi

# Make sure $pid is a positive integer
case $pid in
    ''|*[!0-9]*) pidOK=0 ;;
    *) pidOK=1 ;;
esac

if [ $pidOK -eq 0 ]; then
    echo "[Error]: The given process ID '$pid' is not a positive integer." >&2
    badArgs
fi

if [ "$OS" = "Linux" ]; then
    # Wait for parent process to call prctl(PR_SET_PTRACER, thisPid ...)
    # in case kernel.yama.ptrace_scope=1  in linux kernel .
    sleep 5 


    ## Invoke GDB, filter output
    # --nw prevents GUI interface to GDB, if installed
    # --nx prevents user's .gdbinit from being run
    # --batch suppresses version/copyright header and terminates GDB when
    #   input file exhausted
    # --pid attaches to the given process
    # read commands from stdin redirected to file $gcmd
    echo "===--- start gdb stacks using $gcmd "
    date
    $DEBUGGER --nw --nx --pid=$pid < $gcmd 
    #  grep -Ev '^\(gdb\)|^Reading|^Loaded|No such file or directory\.$'
    #  "No such file or directory" regularly appears on Mac, not a real problem
    echo "===--- end gdb stacks"

else
    echo "===--- start lldb stacks using $gcmd "
    date
    $DEBUGGER --no-lldbinit --attach-pid $pid < $gcmd 
    echo "===--- end lldb stacks"
fi
