]> git.ipfire.org Git - thirdparty/binutils-gdb.git/commitdiff
Add gstack script
authorKeith Seitz <keiths@redhat.com>
Fri, 20 Dec 2024 20:46:11 +0000 (12:46 -0800)
committerKeith Seitz <keiths@redhat.com>
Fri, 20 Dec 2024 20:46:11 +0000 (12:46 -0800)
This commit adds support for a `gstack' command which Fedora has
been carrying for many years. gstack is a natural counterpart to
the gcore command. Whereas gcore dumps a core file, gstack prints
stack traces of a running process.

There are many improvements over Fedora's version of this script.
The dependency on procfs is gone; gstack will run anywhere gdb
runs. The only runtime dependencies are bash and awk.

The script includes suggestions from gdb/32325 to include
versioning and help. [If this approach to gdb/32325 is acceptable,
I could propagate the solution to gcore/gdb-add-index.]

I've rewritten the documentation, integrating it into the User Manual.
The manpage is now output using this one source.

Example run (on x86_64 Fedora 40)

$ gstack --help
Usage: gstack [-h|--help] [-v|--version] PID
Print a stack trace of a running program

  -h, --help         Print this message then exit.
  -v, --version      Print version information then exit.
$ gstack -v
GNU gstack (GDB) 16.0.50.20241119-git
$ gstack 12345678
Process 12345678 not found.
$ gstack $(pidof emacs)
Thread 6 (Thread 0x7fd5ec1c06c0 (LWP 2491423) "pool-spawner"):
#0  0x00007fd6015ca3dd in syscall () at /lib64/libc.so.6
#1  0x00007fd60b31eccd in g_cond_wait () at /lib64/libglib-2.0.so.0
#2  0x00007fd60b28a61b in g_async_queue_pop_intern_unlocked () at /lib64/libglib-2.0.so.0
#3  0x00007fd60b2f1a03 in g_thread_pool_spawn_thread () at /lib64/libglib-2.0.so.0
#4  0x00007fd60b2f0813 in g_thread_proxy () at /lib64/libglib-2.0.so.0
#5  0x00007fd6015486d7 in start_thread () at /lib64/libc.so.6
#6  0x00007fd6015cc60c in clone3 () at /lib64/libc.so.6
#7  0x0000000000000000 in ??? ()

Thread 5 (Thread 0x7fd5eb9bf6c0 (LWP 2491424) "gmain"):
#0  0x00007fd6015be87d in poll () at /lib64/libc.so.6
#1  0x0000000000000001 in ??? ()
#2  0xffffffff00000001 in ??? ()
#3  0x0000000000000001 in ??? ()
#4  0x000000002104cfd0 in ??? ()
#5  0x00007fd5eb9be320 in ??? ()
#6  0x00007fd60b321c34 in g_main_context_iterate_unlocked.isra () at /lib64/libglib-2.0.so.0

Thread 4 (Thread 0x7fd5eb1be6c0 (LWP 2491425) "gdbus"):
#0  0x00007fd6015be87d in poll () at /lib64/libc.so.6
#1  0x0000000020f9b558 in ??? ()
#2  0xffffffff00000003 in ??? ()
#3  0x0000000000000003 in ??? ()
#4  0x00007fd5d8000b90 in ??? ()
#5  0x00007fd5eb1bd320 in ??? ()
#6  0x00007fd60b321c34 in g_main_context_iterate_unlocked.isra () at /lib64/libglib-2.0.so.0

Thread 3 (Thread 0x7fd5ea9bd6c0 (LWP 2491426) "emacs"):
#0  0x00007fd6015ca3dd in syscall () at /lib64/libc.so.6
#1  0x00007fd60b31eccd in g_cond_wait () at /lib64/libglib-2.0.so.0
#2  0x00007fd60b28a61b in g_async_queue_pop_intern_unlocked () at /lib64/libglib-2.0.so.0
#3  0x00007fd60b28a67c in g_async_queue_pop () at /lib64/libglib-2.0.so.0
#4  0x00007fd603f4d0d9 in fc_thread_func () at /lib64/libpangoft2-1.0.so.0
#5  0x00007fd60b2f0813 in g_thread_proxy () at /lib64/libglib-2.0.so.0
#6  0x00007fd6015486d7 in start_thread () at /lib64/libc.so.6
#7  0x00007fd6015cc60c in clone3 () at /lib64/libc.so.6
#8  0x0000000000000000 in ??? ()

Thread 2 (Thread 0x7fd5e9e6d6c0 (LWP 2491427) "dconf worker"):
#0  0x00007fd6015be87d in poll () at /lib64/libc.so.6
#1  0x0000000000000001 in ??? ()
#2  0xffffffff00000001 in ??? ()
#3  0x0000000000000001 in ??? ()
#4  0x00007fd5cc000b90 in ??? ()
#5  0x00007fd5e9e6c320 in ??? ()
#6  0x00007fd60b321c34 in g_main_context_iterate_unlocked.isra () at /lib64/libglib-2.0.so.0

Thread 1 (Thread 0x7fd5fcc45280 (LWP 2491417) "emacs"):
#0  0x00007fd6015c9197 in pselect () at /lib64/libc.so.6
#1  0x0000000000000000 in ??? ()

Since this is essentially a complete rewrite of the original
script and documentation, I've chosen to only keep a 2024 copyright date.

Reviewed-By: Eli Zaretskii <eliz@gnu.org>
Approved-By: Tom Tromey <tom@tromey.com>
gdb/Makefile.in
gdb/NEWS
gdb/configure
gdb/configure.ac
gdb/doc/Makefile.in
gdb/doc/gdb.texinfo
gdb/gstack-1.in [new file with mode: 0755]
gdb/testsuite/gdb.base/gstack.c [new file with mode: 0644]
gdb/testsuite/gdb.base/gstack.exp [new file with mode: 0644]

index 2bd17495dcc24606941574818f1824039bcf3ab1..6d627b61fe70e3e929d9a5fa07df89f61ffffd8c 100644 (file)
@@ -590,6 +590,7 @@ CONFIG_CLEAN = @CONFIG_CLEAN@
 CONFIG_INSTALL = @CONFIG_INSTALL@
 CONFIG_UNINSTALL = @CONFIG_UNINSTALL@
 HAVE_NATIVE_GCORE_TARGET = @HAVE_NATIVE_GCORE_TARGET@
+HAVE_GSTACK = @HAVE_GSTACK@
 
 CONFIG_SRC_SUBDIR = arch cli dwarf2 mi compile tui unittests guile python \
        target nat
@@ -1946,7 +1947,7 @@ generated_files = \
 # Flags needed to compile Python code
 PYTHON_CFLAGS = @PYTHON_CFLAGS@
 
-all: gdb$(EXEEXT) $(CONFIG_ALL) gdb-gdb.py gdb-gdb.gdb gcore
+all: gdb$(EXEEXT) $(CONFIG_ALL) gdb-gdb.py gdb-gdb.gdb gcore gstack
        @$(MAKE) $(FLAGS_TO_PASS) DO=all "DODIRS=$(SUBDIRS)" subdir_do
 
 # Rule for compiling .c files in the top-level gdb directory.
@@ -2105,6 +2106,19 @@ install-only: $(CONFIG_INSTALL)
                  $(INSTALL_SCRIPT) gcore \
                          $(DESTDIR)$(bindir)/$$transformed_name; \
        fi
+       if test "x${HAVE_GSTACK}" != x; \
+       then \
+         transformed_name=`t='$(program_transform_name)'; \
+                           echo gstack | sed -e "$$t"` ; \
+                 if test "x$$transformed_name" = x; then \
+                   transformed_name=gstack ; \
+                 else \
+                   true ; \
+                 fi ; \
+                 $(SHELL) $(srcdir)/../mkinstalldirs $(DESTDIR)$(bindir) ; \
+                 $(INSTALL_SCRIPT) gstack \
+                         $(DESTDIR)$(bindir)/$$transformed_name; \
+       fi
        transformed_name=`t='$(program_transform_name)'; \
                          echo gdb-add-index | sed -e "$$t"` ; \
        if test "x$$transformed_name" = x; then \
@@ -2149,6 +2163,17 @@ uninstall: force $(CONFIG_UNINSTALL)
                  fi ; \
                  rm -f $(DESTDIR)$(bindir)/$$transformed_name; \
        fi
+       if test "x$(HAVE_GSTACK)" != x; \
+       then \
+         transformed_name=`t='$(program_transform_name)'; \
+                           echo gstack | sed -e "$$t"` ; \
+                 if test "x$$transformed_name" = x; then \
+                   transformed_name=gstack ; \
+                 else \
+                   true ; \
+                 fi ; \
+                 rm -f $(DESTDIR)$(bindir)/$$transformed_name; \
+       fi
        transformed_name=`t='$(program_transform_name)'; \
                          echo gdb-add-index | sed -e "$$t"` ; \
        if test "x$$transformed_name" = x; then \
@@ -2260,7 +2285,7 @@ clean mostlyclean: $(CONFIG_CLEAN)
 # functionality described is if the distributed files are unmodified.
 distclean: clean
        @$(MAKE) $(FLAGS_TO_PASS) DO=distclean "DODIRS=$(CLEANDIRS)" subdir_do
-       rm -f nm.h config.status config.h stamp-h b jit-reader.h gcore stamp-nmh
+       rm -f nm.h config.status config.h stamp-h b jit-reader.h gcore gstack gstack.in stamp-nmh
        rm -f gdb-gdb.py gdb-gdb.gdb
        rm -f y.output yacc.acts yacc.tmp y.tab.h
        rm -f config.log config.cache
@@ -2321,6 +2346,15 @@ jit-reader.h: $(srcdir)/jit-reader.in config.status
 gcore: $(srcdir)/gcore.in config.status
        $(ECHO_GEN) $(SHELL) config.status $(SILENT_FLAG) $@
 
+gstack: gstack.in version.c
+       $(ECHO_GEN) \
+       vv=`grep 'version\[\] = ' version.c | grep -o '".*"' | tr -d \"`; \
+       sed -e "s,@VERSION@,$$vv," $< > $@
+       @chmod +x $@
+
+gstack.in: $(srcdir)/gstack-1.in config.status
+       $(ECHO_GEN) $(SHELL) config.status $(SILENT_FLAG) $@
+
 gdb-gdb.py: $(srcdir)/gdb-gdb.py.in config.status
        $(ECHO_GEN) $(SHELL) config.status $(SILENT_FLAG) $@
 
index 3b9cd00a9a0df9e7e9038bd942637a957f6acedf..186f91e137773e1455c5183cea4c5d037f35575c 100644 (file)
--- a/gdb/NEWS
+++ b/gdb/NEWS
@@ -62,6 +62,8 @@
 * Support for process record/replay and reverse debugging on loongarch*-linux*
   targets has been added.
 
+* New bash script gstack uses GDB to print stack traces of running processes.
+
 * Python API
 
   ** Added gdb.record.clear.  Clears the trace data of the current recording.
index de750f4fafee64900fc1a6d4a1d8aed2ead73657..1531f62f76a0e23d7d181e0781f179d9924e0f72 100755 (executable)
@@ -760,6 +760,7 @@ HAVE_NATIVE_GCORE_TARGET
 TARGET_OBS
 AMD_DBGAPI_LIBS
 AMD_DBGAPI_CFLAGS
+HAVE_GSTACK
 ENABLE_BFD_64_BIT_FALSE
 ENABLE_BFD_64_BIT_TRUE
 subdirs
@@ -11499,7 +11500,7 @@ else
   lt_dlunknown=0; lt_dlno_uscore=1; lt_dlneed_uscore=2
   lt_status=$lt_dlunknown
   cat > conftest.$ac_ext <<_LT_EOF
-#line 11502 "configure"
+#line 11503 "configure"
 #include "confdefs.h"
 
 #if HAVE_DLFCN_H
@@ -11605,7 +11606,7 @@ else
   lt_dlunknown=0; lt_dlno_uscore=1; lt_dlneed_uscore=2
   lt_status=$lt_dlunknown
   cat > conftest.$ac_ext <<_LT_EOF
-#line 11608 "configure"
+#line 11609 "configure"
 #include "confdefs.h"
 
 #if HAVE_DLFCN_H
@@ -25008,6 +25009,12 @@ if test x${all_targets} = xtrue; then
   fi
 fi
 
+HAVE_GSTACK=0
+if test $gdb_native = yes; then
+   HAVE_GSTACK=1
+fi
+
+
 # AMD debugger API support.
 
 
@@ -33761,6 +33768,8 @@ fi
 
 ac_config_files="$ac_config_files gcore"
 
+ac_config_files="$ac_config_files gstack.in:gstack-1.in"
+
 ac_config_files="$ac_config_files Makefile gdb-gdb.gdb gdb-gdb.py doc/Makefile data-directory/Makefile"
 
 
@@ -34861,6 +34870,7 @@ do
     "jit-reader.h") CONFIG_FILES="$CONFIG_FILES jit-reader.h:jit-reader.in" ;;
     "nm.h") CONFIG_LINKS="$CONFIG_LINKS nm.h:$GDB_NM_FILE" ;;
     "gcore") CONFIG_FILES="$CONFIG_FILES gcore" ;;
+    "gstack.in") CONFIG_FILES="$CONFIG_FILES gstack.in:gstack-1.in" ;;
     "Makefile") CONFIG_FILES="$CONFIG_FILES Makefile" ;;
     "gdb-gdb.gdb") CONFIG_FILES="$CONFIG_FILES gdb-gdb.gdb" ;;
     "gdb-gdb.py") CONFIG_FILES="$CONFIG_FILES gdb-gdb.py" ;;
index 230c0be79c7c51f64f748cfa5c446fc0d62b7520..e9312b1bc64d90b86c4f237c94caa1e708b71cdd 100644 (file)
@@ -261,6 +261,12 @@ if test x${all_targets} = xtrue; then
   fi
 fi
 
+HAVE_GSTACK=0
+if test $gdb_native = yes; then
+   HAVE_GSTACK=1
+fi
+AC_SUBST(HAVE_GSTACK)
+
 # AMD debugger API support.
 
 AC_ARG_WITH([amd-dbgapi],
@@ -2264,6 +2270,7 @@ GDB_AC_SELFTEST([
 GDB_AC_TRANSFORM([gdb], [GDB_TRANSFORM_NAME])
 GDB_AC_TRANSFORM([gcore], [GCORE_TRANSFORM_NAME])
 AC_CONFIG_FILES([gcore], [chmod +x gcore])
+AC_CONFIG_FILES([gstack.in:gstack-1.in])
 AC_CONFIG_FILES([Makefile gdb-gdb.gdb gdb-gdb.py doc/Makefile data-directory/Makefile])
 
 AC_OUTPUT
index 181325fd0c91b060a6aefa29d8e25269fc610440..c75714b5de284b51b8e53c2f7b8e15e7cc0047df 100644 (file)
@@ -188,7 +188,7 @@ TEXI2POD = perl $(srcdir)/../../etc/texi2pod.pl \
 POD2MAN = pod2man --center="GNU Development Tools"
 
 # List of man pages generated from gdb.texi
-MAN1S = gdb.1 gdbserver.1 gcore.1 gdb-add-index.1
+MAN1S = gdb.1 gdbserver.1 gcore.1 gstack.1 gdb-add-index.1
 MAN5S = gdbinit.5
 MANS = $(MAN1S) $(MAN5S)
 
@@ -199,6 +199,7 @@ POD_FILE_TMPS = $(patsubst %.1,%.pod,$(MAN1S)) \
 
 HAVE_NATIVE_GCORE_TARGET = @HAVE_NATIVE_GCORE_TARGET@
 HAVE_NATIVE_GCORE_HOST = @HAVE_NATIVE_GCORE_HOST@
+HAVE_GSTACK = @HAVE_GSTACK@
 
 ###
 
@@ -339,6 +340,10 @@ install-man1: $(MAN1S)
                  -a "$$p" = gcore.1; then \
            continue; \
          fi; \
+         if test "x$(HAVE_GSTACK)" = x \
+                 -a "$$p" = gstack.1; then \
+           continue; \
+         fi; \
          if test -f "$$p"; then d=; else d="$(srcdir)/"; fi; \
          f=`echo $$p | sed -e 's|^.*/||' -e '$(transform)'`; \
          echo " $(INSTALL_DATA) '$$d$$p' '$(DESTDIR)$(man1dir)/$$f'"; \
@@ -363,6 +368,10 @@ uninstall-man1:
                  -a "$$i" = gcore.1; then \
            continue; \
          fi; \
+         if test "x$(HAVE_GSTACK)" = x \
+                 -a "$$i" = gstack.1; then \
+           continue; \
+         fi; \
          echo "$$i"; \
        done | \
          sed -n '/\.1[a-z]*$$/p'; \
index 7b6000abbea59fad5989615715f66b536ada3aa2..b985399cf34039dbb416fd862d19198bf3954123 100644 (file)
@@ -50584,6 +50584,7 @@ Show the current verbosity setting.
 * gdb man::                     The GNU Debugger man page
 * gdbserver man::               Remote Server for the GNU Debugger man page
 * gcore man::                   Generate a core file of a running program
+* gstack man::                  Print a stack trace of a running program
 * gdbinit man::                 gdbinit scripts
 * gdb-add-index man::           Add index files to speed up GDB
 @end menu
@@ -51266,6 +51267,75 @@ Richard M. Stallman and Roland H. Pesch, July 1991.
 @end ifset
 @c man end
 
+@node gstack man
+@heading gstack
+
+@c man title gstack Print a stack trace of a running program
+
+@format
+@c man begin SYNOPSIS gstack
+gstack [-h | --help] [-v | --version] @var{pid}
+@c man end
+@end format
+
+@c man begin DESCRIPTION gstack
+Print a stack trace of a running program with process ID @var{pid}.  If the process
+is multi-threaded, @command{gstack} outputs backtraces for every thread which exists
+in the process.
+
+The script invokes @value{GDBN}, attaches to the given process ID, prints the stack trace,
+and detaches from the process.
+
+@command{gstack} exits with non-zero status if @code{gdb} was unable to attach to
+the given process ID for any reason, such as a non-existent process ID or
+insufficient privileges to attach to the process.
+@c man end
+
+@c man begin OPTIONS gstack
+@table @env
+@item --help
+@itemx -h
+List all options, with brief explanations.
+
+@item --version
+@itemx -v
+Print version information and then exit.
+@end table
+@c man end
+
+@c man begin ENVIRONMENT gstack
+@table @env
+@item AWK
+Full file name for an Awk interpreter to use.  If not set, @env{PATH} will be
+searched for an @code{awk} program.
+
+@item GDB
+Full file name for a @value{GDBN} executable to use to generate stack backtraces.
+If not set, @env{PATH} will be searched for a @code{gdb} program.
+
+@item GDBARGS
+Optional arguments to be passed to the @code{gdb} program.
+@end table
+@c man end
+
+@c man begin SEEALSO gstack
+@ifset man
+The full documentation for @value{GDBN} is maintained as a Texinfo manual.
+If the @code{info} and @code{gdb} programs and @value{GDBN}'s Texinfo
+documentation are properly installed at your site, the command
+
+@smallexample
+info gdb
+@end smallexample
+
+@noindent
+should give you access to the complete manual.
+
+@cite{Using GDB: A Guide to the GNU Source-Level Debugger},
+Richard M. Stallman and Roland H. Pesch, July 1991.
+@end ifset
+@c man end
+
 @node gdbinit man
 @heading gdbinit
 
diff --git a/gdb/gstack-1.in b/gdb/gstack-1.in
new file mode 100755 (executable)
index 0000000..5e41329
--- /dev/null
@@ -0,0 +1,147 @@
+#!/usr/bin/env bash
+
+# Copyright (C) 2024 Free Software Foundation, Inc.
+
+# 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.
+
+GDB=${GDB:-$(command -v gdb)}
+GDBARGS=${GDBARGS:-}
+AWK=${AWK:-}
+PKGVERSION=@PKGVERSION@
+VERSION=@VERSION@
+
+# 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
+
+function print_usage() {
+    echo "Usage: $0 [-h|--help] [-v|--version] PID"
+}
+
+function print_try_help() {
+    echo "Try '$0 --help' for more information."
+}
+
+function 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."
+}
+
+function print_version() {
+    echo "GNU gstack (${PKGVERSION}) ${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
+  }
+}
+
+END {
+if (attach_okay == 0)
+  exit 2
+}
+EOF
+         )
+
+# Run GDB and remove some unwanted noise.
+"$GDB" --quiet -nx --readnever $GDBARGS <<EOF |
+set width 0
+set height 0
+set pagination no
+set debuginfod enabled off
+define attach-bt
+attach \$arg0
+echo "ATTACHED"
+thread apply all bt
+end
+attach-bt $PID
+EOF
+$AWK -- "$awk_script"
diff --git a/gdb/testsuite/gdb.base/gstack.c b/gdb/testsuite/gdb.base/gstack.c
new file mode 100644 (file)
index 0000000..a871670
--- /dev/null
@@ -0,0 +1,32 @@
+/* This testcase is part of GDB, the GNU debugger.
+
+   Copyright 2024 Free Software Foundation, Inc.
+
+   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/>.  */
+
+#include <unistd.h>
+#include <string.h>
+
+int
+main (void)
+{
+  const char msg[] = "looping\n";
+
+  /* Output a simple string for the expect script to monitor us.  */
+  write (1, msg, strlen (msg));
+
+  for (;;) ;
+
+  return 0;
+}
diff --git a/gdb/testsuite/gdb.base/gstack.exp b/gdb/testsuite/gdb.base/gstack.exp
new file mode 100644 (file)
index 0000000..1ad1402
--- /dev/null
@@ -0,0 +1,92 @@
+# Copyright (C) 2024 Free Software Foundation, Inc.
+
+# 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/>.
+
+require !gdb_protocol_is_remote
+standard_testfile
+
+if {[prepare_for_testing "failed to prepare" $testfile $srcfile {debug}] == -1} {
+    return -1
+}
+
+set command "$binfile"
+set res [remote_spawn host $command]
+if { ![gdb_assert { ![expr {$res < 0 || $res == ""}] } "spawn inferior"] } {
+    return
+}
+
+# The spawn id of the test inferior.
+set test_spawn_id $res
+
+# Wait for the spawned program to loop.
+set test "wait for inferior to loop"
+gdb_expect {
+    -re "looping\r\n" {
+       pass $test
+    }
+    eof {
+       fail "$test (eof)"
+       return
+    }
+    timeout {
+       fail "$test (timeout)"
+       return
+    }
+}
+
+# The test case uses a very simple notification not to get caught by attach on
+# exiting the function.
+
+set test "spawn gstack"
+set pid [spawn_id_get_pid $test_spawn_id]
+set gstack_cmd [findfile $base_dir/../../gdb/gstack]
+set command "sh -c GDB=$GDB\\ GDBARGS=-data-directory\\\\\\ $GDB_DATA_DIRECTORY\\ $gstack_cmd\\ $pid\\;echo\\ GSTACK-END"
+set res [remote_spawn host $command]
+if { ![gdb_assert { ![expr {$res < 0 || $res == ""}] } $test] } {
+    return
+}
+
+set test "got backtrace"
+set saw_backtrace false
+set no_awk false
+gdb_test_multiple "" $test {
+    -i "$res" -re "#0 +(0x\[0-9a-f\]+ in )?main \(\).*\r\nGSTACK-END\r\n\$" {
+       set saw_backtrace true
+       pass $test
+       exp_continue
+    }
+
+    -i "$res" "could not find usable awk interpreter" {
+       set no_awk true
+       exp_continue
+    }
+
+    eof {
+       set result [wait -i $res]
+       verbose $result
+
+       gdb_assert { [lindex $result 2] == 0 } "gstack exits with no error"
+       gdb_assert { [lindex $result 3] == 0 } "gstack's exit status is 0"
+
+       remote_close host
+    }
+}
+if {$no_awk} {
+    unsupported "no awk interpreter found"
+} elseif {!$saw_backtrace} {
+    fail $test
+}
+
+# Kill the test inferior.
+kill_wait_spawned_process $test_spawn_id