[v2] Add gstack script

Message ID 08a19a3debe4a1e3c6072a807e79566277a7c0dc.1734022384.git.keiths@redhat.com
State New
Headers
Series [v2] Add gstack script |

Checks

Context Check Description
linaro-tcwg-bot/tcwg_gdb_build--master-aarch64 success Build passed
linaro-tcwg-bot/tcwg_gdb_build--master-arm success Build passed
linaro-tcwg-bot/tcwg_gdb_check--master-arm success Test passed
linaro-tcwg-bot/tcwg_gdb_check--master-aarch64 success Test passed

Commit Message

Keith Seitz Dec. 12, 2024, 9:07 p.m. UTC
  --- Changes in v2
- Review feedback from Andrew Burgess
  o Don't chmod gstack.in in configure.ac.
  o Fix testsuite issues which could mislead users into thinking
    that tests succeeded when they did not.
---

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 0x7f382a8006c0 (LWP 1265626) "pool-spawner"):
#0  0x00007f383ffca3dd in syscall () at /lib64/libc.so.6
#1  0x00007f3849cc0ccd in g_cond_wait () at /lib64/libglib-2.0.so.0
#2  0x00007f3849c2c61b in g_async_queue_pop_intern_unlocked () at /lib64/libglib-2.0.so.0
#3  0x00007f3849c93a03 in g_thread_pool_spawn_thread () at /lib64/libglib-2.0.so.0
#4  0x00007f3849c92813 in g_thread_proxy () at /lib64/libglib-2.0.so.0
#5  0x00007f383ff486d7 in start_thread () at /lib64/libc.so.6
#6  0x00007f383ffcc60c in clone3 () at /lib64/libc.so.6

Thread 5 (Thread 0x7f3829e006c0 (LWP 1265627) "gmain"):
#0  0x00007f383ffbe87d in poll () at /lib64/libc.so.6
#1  0x00007f3849cc3c34 in g_main_context_iterate_unlocked.isra () at /lib64/libglib-2.0.so.0
#2  0x00007f3849c63383 in g_main_context_iteration () at /lib64/libglib-2.0.so.0
#3  0x00007f3849c633e1 in glib_worker_main () at /lib64/libglib-2.0.so.0
#4  0x00007f3849c92813 in g_thread_proxy () at /lib64/libglib-2.0.so.0
#5  0x00007f383ff486d7 in start_thread () at /lib64/libc.so.6
#6  0x00007f383ffcc60c in clone3 () at /lib64/libc.so.6

Thread 4 (Thread 0x7f38294006c0 (LWP 1265628) "gdbus"):
#0  0x00007f383ffbe87d in poll () at /lib64/libc.so.6
#1  0x00007f3849cc3c34 in g_main_context_iterate_unlocked.isra () at /lib64/libglib-2.0.so.0
#2  0x00007f3849c67f37 in g_main_loop_run () at /lib64/libglib-2.0.so.0
#3  0x00007f3849ec7682 in gdbus_shared_thread_func.lto_priv () at /lib64/libgio-2.0.so.0
#4  0x00007f3849c92813 in g_thread_proxy () at /lib64/libglib-2.0.so.0
#5  0x00007f383ff486d7 in start_thread () at /lib64/libc.so.6
#6  0x00007f383ffcc60c in clone3 () at /lib64/libc.so.6

Thread 3 (Thread 0x7f3828a006c0 (LWP 1265629) "emacs"):
#0  0x00007f383ffca3dd in syscall () at /lib64/libc.so.6
#1  0x00007f3849cc0ccd in g_cond_wait () at /lib64/libglib-2.0.so.0
#2  0x00007f3849c2c61b in g_async_queue_pop_intern_unlocked () at /lib64/libglib-2.0.so.0
#3  0x00007f3849c2c67c in g_async_queue_pop () at /lib64/libglib-2.0.so.0
#4  0x00007f3842c340d9 in fc_thread_func () at /lib64/libpangoft2-1.0.so.0
#5  0x00007f3849c92813 in g_thread_proxy () at /lib64/libglib-2.0.so.0
#6  0x00007f383ff486d7 in start_thread () at /lib64/libc.so.6
#7  0x00007f383ffcc60c in clone3 () at /lib64/libc.so.6

Thread 2 (Thread 0x7f3823e006c0 (LWP 1265630) "dconf worker"):
#0  0x00007f383ffbe87d in poll () at /lib64/libc.so.6
#1  0x00007f3849cc3c34 in g_main_context_iterate_unlocked.isra () at /lib64/libglib-2.0.so.0
#2  0x00007f3849c63383 in g_main_context_iteration () at /lib64/libglib-2.0.so.0
#3  0x00007f382a845705 in dconf_gdbus_worker_thread () at /usr/lib64/gio/modules/libdconfsettings.so
#4  0x00007f3849c92813 in g_thread_proxy () at /lib64/libglib-2.0.so.0
#5  0x00007f383ff486d7 in start_thread () at /lib64/libc.so.6
#6  0x00007f383ffcc60c in clone3 () at /lib64/libc.so.6

Thread 1 (Thread 0x7f383b642280 (LWP 1265621) "emacs"):
#0  0x00007f383ffc9197 in pselect () at /lib64/libc.so.6
#1  0x00000000006b9e01 in really_call_select.lto_priv ()
#2  0x00000000006e1979 in xg_select ()
#3  0x00000000006899d1 in wait_reading_process_output ()
#4  0x000000000057e91c in kbd_buffer_get_event ()
#5  0x0000000000580bc6 in read_char ()
#6  0x0000000000589c03 in read_key_sequence.lto_priv ()
#7  0x0000000000576135 in command_loop_1.lto_priv ()
#8  0x00000000006131ce in internal_condition_case ()
#9  0x000000000057569e in command_loop_2 ()
#10 0x0000000000613127 in internal_catch ()
#11 0x0000000000575af3 in command_loop ()
#12 0x0000000000575bef in recursive_edit_1 ()
#13 0x0000000000575ddd in Frecursive_edit ()
#14 0x00000000004795a9 in main ()

Since this is essentially a complete rewrite of the original
script and documentation, I've chosen to only keep a 2024 copyright date.
---
 gdb/Makefile.in                   |  38 ++++++++-
 gdb/NEWS                          |   3 +
 gdb/configure                     |  14 +++-
 gdb/configure.ac                  |   7 ++
 gdb/doc/Makefile.in               |  11 ++-
 gdb/doc/gdb.texinfo               |  48 ++++++++++++
 gdb/gstack-1.in                   | 126 ++++++++++++++++++++++++++++++
 gdb/testsuite/gdb.base/gstack.c   |  32 ++++++++
 gdb/testsuite/gdb.base/gstack.exp |  85 ++++++++++++++++++++
 9 files changed, 359 insertions(+), 5 deletions(-)
 create mode 100755 gdb/gstack-1.in
 create mode 100644 gdb/testsuite/gdb.base/gstack.c
 create mode 100644 gdb/testsuite/gdb.base/gstack.exp


base-commit: a5939d229614a4913daf3c687ec54fdf3020d496
  

Comments

Eli Zaretskii Dec. 13, 2024, 7:36 a.m. UTC | #1
> From: Keith Seitz <keiths@redhat.com>
> Date: Thu, 12 Dec 2024 13:07:41 -0800
> 
> --- Changes in v2
> - Review feedback from Andrew Burgess
>   o Don't chmod gstack.in in configure.ac.
>   o Fix testsuite issues which could mislead users into thinking
>     that tests succeeded when they did not.
> ---

Thanks.

> --- a/gdb/NEWS
> +++ b/gdb/NEWS
> @@ -62,6 +62,9 @@
>  * Support for process record/replay and reverse debugging on loongarch*-linux*
>    targets has been added.
>  
> +* Newly installed $prefix/bin/gstack uses GDB to print stack traces of
> +  running processes.

This is okay, but please say that gstack is a Bash shell script.

Also, what did you want to say by the "newly installed" part?

> +@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.
> +@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 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

Is that all we want to tell in the manual about the script?  Even for
a man page this is quite terse.  Can we expand on this some more?  At
the very least I think we should tell that the script invokes G|DB to
attach to the program and produce the backtraces, then detaches from
the program.  Also, perhaps tell what happens if PID identifies a
non-existent process?

> +awk -- "$awk_script"

What if the system has only gawk or nawk or mawk?  Should the literal
"awk" be a variable?

Reviewed-By: Eli Zaretskii <eliz@gnu.org>
  
Keith Seitz Dec. 13, 2024, 6:21 p.m. UTC | #2
Hi,

On 12/12/24 11:36 PM, Eli Zaretskii wrote:

>> --- a/gdb/NEWS
>> +++ b/gdb/NEWS
>> @@ -62,6 +62,9 @@
>>   * Support for process record/replay and reverse debugging on loongarch*-linux*
>>     targets has been added.
>>   
>> +* Newly installed $prefix/bin/gstack uses GDB to print stack traces of
>> +  running processes.
> 
> This is okay, but please say that gstack is a Bash shell script.

Sure.

> Also, what did you want to say by the "newly installed" part?

Simply to point out that this is a new script which is installed.
How about:

* New bash script gstack which uses GDB to print stack traces of
   running processes.

>> +@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.
>> +@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 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
> 
> Is that all we want to tell in the manual about the script?  Even for
> a man page this is quite terse.  Can we expand on this some more?  At
> the very least I think we should tell that the script invokes G|DB to
> attach to the program and produce the backtraces, then detaches from
> the program.  Also, perhaps tell what happens if PID identifies a
> non-existent process?

You should see the manpage I rewrote. :-)

Nonetheless, your point is taken. I will elaborate a little bit more.
I've also dug into a few other GNU man pages and note that they
commonly list any environment variables influencing the behavior of
programs. I've completely forgotten to do that, so I will add those,
too.

>> +awk -- "$awk_script"
> 
> What if the system has only gawk or nawk or mawk?  Should the literal
> "awk" be a variable?

Oh, awk. This is definitely my bad. It was lost on me the difference
between building/installing GDB on a single machine and doing the
same on different machines. The environments are not exactly correct.
Since I live in a distro-centric world, these two cases reduce for me.

I've read through the documentation on AC_PROG_AWK, and I think it
best to follow the advice there, including "Limitations of Usual
Tools" bits on awk. I think there is one questionable construct in
my awk script.

I agree -- an AWK environment variable would be a valuable addition,
including borrowing some bits from AC_PROG_AWK to make sure we get
the preferred awk program.

Thank you for your prompt review! I'll send a v3 after awaiting any
additional feedback.

Keith
  
Eli Zaretskii Dec. 13, 2024, 7:04 p.m. UTC | #3
> Date: Fri, 13 Dec 2024 10:21:48 -0800
> Cc: gdb-patches@sourceware.org
> From: Keith Seitz <keiths@redhat.com>
> 
> > Also, what did you want to say by the "newly installed" part?
> 
> Simply to point out that this is a new script which is installed.
> How about:
> 
> * New bash script gstack which uses GDB to print stack traces of
>    running processes.

OK.

> Thank you for your prompt review! I'll send a v3 after awaiting any
> additional feedback.

TIA
  
Tom Tromey Dec. 13, 2024, 7:45 p.m. UTC | #4
>>>>> "Keith" == Keith Seitz <keiths@redhat.com> writes:

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

This would be super.

Keith> +function print_try_help() {
Keith> +    echo "Try '$0 --help' for more information."

This should probably print to stderr.

Keith> +function print_help() {
Keith> +    print_usage

Here, print_usage should print to stdout.  So maybe this later spot:

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

... should redirect to stderr.

Keith> +define attach-bt
Keith> +attach \$arg0
Keith> +echo "ATTACHED"
Keith> +thread apply all bt
Keith> +end
Keith> +attach-bt $PID

I'm curious to understand why this is wrapped in a 'define'.

Tom
  
Keith Seitz Dec. 13, 2024, 7:57 p.m. UTC | #5
On 12/13/24 11:45 AM, Tom Tromey wrote:
>>>>>> "Keith" == Keith Seitz <keiths@redhat.com> writes:
> 
> Keith> The script includes suggestions from gdb/32325 to include
> Keith> versioning and help. [If this approach to gdb/32325 is acceptable,
> Keith> I could propagate the solution to gcore/gdb-add-index.]
> 
> This would be super.

I will get that on my TODO list, then.

> 
> Keith> +function print_try_help() {
> Keith> +    echo "Try '$0 --help' for more information."
> 
> This should probably print to stderr.
> 
> Keith> +function print_help() {
> Keith> +    print_usage
> 
> Here, print_usage should print to stdout.  So maybe this later spot:
> 
> Keith> +# The sole remaining argument should be the PID of the process
> Keith> +# whose backtrace is desired.
> Keith> +if [ $# -ne 1 ]; then
> Keith> +    print_usage
> 
> ... should redirect to stderr.

Yeah, I will audit/adjust this.

> 
> Keith> +define attach-bt
> Keith> +attach \$arg0
> Keith> +echo "ATTACHED"
> Keith> +thread apply all bt
> Keith> +end
> Keith> +attach-bt $PID
> 
> I'm curious to understand why this is wrapped in a 'define'.

By wrapping in a command like this, we can abort doing the backtrace
if there was a problem attaching to a PID. Otherwise, if the attach
fails, using a series of "-ex" options won't abort, and the terminal
will be "spammed" with not-too-helpful error messages. Example:

$ gdb -batch -ex "attach 1234" -ex "bt"
ptrace: No such process.
No stack.

Admittedly, using "thread apply all bt full" does mitigate some of
this, but IIRC, I ran into other use cases where we would see
a additional, unhelpful error messages like demonstrated above.

Motivation:
Fedora's original version of gstack checked /proc/$PID and output an
error message if that didn't exist. This, of course, only works on
systems that support procfs. This is an attempt to include more than
just those systems.

Furthermore, (again with Fedora's original implementation) if gdb could
not attach to a valid PID (e.g., 1), Fedora's gstack simply exited
silently. This should fix that use case, too.

It's almost certainly not going to work everywhere, but it works pretty
well for Linux, and I figure that's a start.

Keith
  

Patch

diff --git a/gdb/Makefile.in b/gdb/Makefile.in
index 52950759711..aae7a8b4790 100644
--- a/gdb/Makefile.in
+++ b/gdb/Makefile.in
@@ -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
@@ -1945,7 +1946,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.
@@ -2104,6 +2105,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 \
@@ -2148,6 +2162,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 \
@@ -2259,7 +2284,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
@@ -2320,6 +2345,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) $@
 
diff --git a/gdb/NEWS b/gdb/NEWS
index 7f0bd7edfc8..465217b388c 100644
--- a/gdb/NEWS
+++ b/gdb/NEWS
@@ -62,6 +62,9 @@ 
 * Support for process record/replay and reverse debugging on loongarch*-linux*
   targets has been added.
 
+* Newly installed $prefix/bin/gstack uses GDB to print stack traces of
+  running processes.
+
 * Python API
 
   ** Added gdb.record.clear.  Clears the trace data of the current recording.
diff --git a/gdb/configure b/gdb/configure
index de750f4fafe..1531f62f76a 100755
--- a/gdb/configure
+++ b/gdb/configure
@@ -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" ;;
diff --git a/gdb/configure.ac b/gdb/configure.ac
index 230c0be79c7..e9312b1bc64 100644
--- a/gdb/configure.ac
+++ b/gdb/configure.ac
@@ -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
diff --git a/gdb/doc/Makefile.in b/gdb/doc/Makefile.in
index 181325fd0c9..c75714b5de2 100644
--- a/gdb/doc/Makefile.in
+++ b/gdb/doc/Makefile.in
@@ -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'; \
diff --git a/gdb/doc/gdb.texinfo b/gdb/doc/gdb.texinfo
index 85ac3d9aab6..3cae61a8e5b 100644
--- a/gdb/doc/gdb.texinfo
+++ b/gdb/doc/gdb.texinfo
@@ -50553,6 +50553,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
@@ -51235,6 +51236,53 @@  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.
+@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 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
index 00000000000..53249f01399
--- /dev/null
+++ b/gdb/gstack-1.in
@@ -0,0 +1,126 @@ 
+#!/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:-}
+PKGVERSION=@PKGVERSION@
+VERSION=@VERSION@
+
+function print_usage() {
+    echo "Usage: $0 [-h|--help] [-v|--version] PID" 1>&2
+}
+
+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
+	    exit 2 ;;
+	*)
+	    echo "$0: unrecognized option '--$OPT'" >&2
+	    print_try_help
+	    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
+    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)
+       print ""
+    first=0
+    print \$0
+  }
+}
+EOF
+	  )
+
+# Run GDB and remove some unwanted noise.
+$GDB --quiet -nx $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
index 00000000000..639e9a7ea93
--- /dev/null
+++ b/gdb/testsuite/gdb.base/gstack.c
@@ -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 (;;) ;  /* forever  */
+
+  return 0;
+}
diff --git a/gdb/testsuite/gdb.base/gstack.exp b/gdb/testsuite/gdb.base/gstack.exp
new file mode 100644
index 00000000000..66ca233fa5b
--- /dev/null
+++ b/gdb/testsuite/gdb.base/gstack.exp
@@ -0,0 +1,85 @@ 
+# 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 lineno [gdb_get_line_number "forever"]
+gdb_test_multiple "" $test {
+    -i "$res" -re "#0 +(0x\[0-9a-f\]+ in )?main \(\).*${srcfile}:$lineno.*\r\nGSTACK-END\r\n\$" {
+	set saw_backtrace true
+	pass $test
+	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 {!$saw_backtrace} {
+    fail $test
+}
+
+# Kill the test inferior.
+kill_wait_spawned_process $test_spawn_id