[RFC] Show the selected frame in "bt"

Message ID 20260529184503.3263361-1-tom@tromey.com
State New
Headers
Series [RFC] Show the selected frame in "bt" |

Checks

Context Check Description
linaro-tcwg-bot/tcwg_gdb_build--master-aarch64 warning Skipped because it is an RFC
linaro-tcwg-bot/tcwg_gdb_build--master-arm warning Skipped because it is an RFC

Commit Message

Tom Tromey May 29, 2026, 6:45 p.m. UTC
  I've occasionally wished that "bt" would indicate the selected frame.
This patch implements this idea.  In particular it marks the selected
frame with "*", similar to other "selected" output in gdb.  (See that
other series where emoji were allowed in the "current" column of a
table; if that is ever resurrected, I'd expect the same treatment to
be applied here.)

Now the output looks like:

(gdb) bt
  #0  0x00007ffff6e381fd in poll () from /lib64/libc.so.6
  #1  0x000000000100b1ee in gdb_wait_for_event (block=1) at ../../binutils-gdb/gdbsupport/event-loop.cc:587
  #2  0x000000000100a77f in gdb_do_one_event (mstimeout=-1) at ../../binutils-gdb/gdbsupport/event-loop.cc:263
* #3  0x00000000007d2c6a in interp::do_one_event (this=<optimized out>, mstimeout=-1) at ../../binutils-gdb/gdb/interps.h:90
  #4  start_event_loop () at ../../binutils-gdb/gdb/main.c:400
  #5  captured_command_loop () at ../../binutils-gdb/gdb/main.c:465
  #6  0x00000000007d5715 in captured_main (context=context@entry=0x7fffffffdd10) at ../../binutils-gdb/gdb/main.c:1373
  #7  gdb_main (args=args@entry=0x7fffffffdd50) at ../../binutils-gdb/gdb/main.c:1392
  #8  0x0000000000452cd5 in main (argc=1, argv=0x7fffffffdea8) at ../../binutils-gdb/gdb/gdb.c:38

I think the main downside of this patch is that it uses a little more
horizontal space for the indicator.

This patch will not regression-test cleanly.  There are many tests
that need an update to match the new output.  However, I wanted to
send this out for conceptual approval before doing that work.

Let me know what you think.
---
 gdb/extension-priv.h         |  3 ++-
 gdb/extension.c              |  8 +++++---
 gdb/extension.h              |  3 ++-
 gdb/frame.h                  |  3 ++-
 gdb/mi/mi-cmd-stack.c        |  8 ++++----
 gdb/python/py-framefilter.c  | 18 +++++++++++++----
 gdb/python/python-internal.h |  3 ++-
 gdb/stack.c                  | 39 +++++++++++++++++++++++++++---------
 8 files changed, 61 insertions(+), 24 deletions(-)
  

Comments

Simon Marchi May 29, 2026, 7:05 p.m. UTC | #1
On 5/29/26 2:45 PM, Tom Tromey wrote:
> I've occasionally wished that "bt" would indicate the selected frame.
> This patch implements this idea.  In particular it marks the selected
> frame with "*", similar to other "selected" output in gdb.  (See that
> other series where emoji were allowed in the "current" column of a
> table; if that is ever resurrected, I'd expect the same treatment to
> be applied here.)
> 
> Now the output looks like:
> 
> (gdb) bt
>   #0  0x00007ffff6e381fd in poll () from /lib64/libc.so.6
>   #1  0x000000000100b1ee in gdb_wait_for_event (block=1) at ../../binutils-gdb/gdbsupport/event-loop.cc:587
>   #2  0x000000000100a77f in gdb_do_one_event (mstimeout=-1) at ../../binutils-gdb/gdbsupport/event-loop.cc:263
> * #3  0x00000000007d2c6a in interp::do_one_event (this=<optimized out>, mstimeout=-1) at ../../binutils-gdb/gdb/interps.h:90
>   #4  start_event_loop () at ../../binutils-gdb/gdb/main.c:400
>   #5  captured_command_loop () at ../../binutils-gdb/gdb/main.c:465
>   #6  0x00000000007d5715 in captured_main (context=context@entry=0x7fffffffdd10) at ../../binutils-gdb/gdb/main.c:1373
>   #7  gdb_main (args=args@entry=0x7fffffffdd50) at ../../binutils-gdb/gdb/main.c:1392
>   #8  0x0000000000452cd5 in main (argc=1, argv=0x7fffffffdea8) at ../../binutils-gdb/gdb/gdb.c:38
> 
> I think the main downside of this patch is that it uses a little more
> horizontal space for the indicator.

I like it, it fits well in the "show user what is selected" paradigm.

If we're talking about bt formatting and horizontal space, we could
consider not showing addresses by default, I bet that most users don't
care.  I find it ugly that by default the address is shown for some
frames but not all, it makes things unaligned (you can force GDB to show
addresses with `-frame-info location-and-address` though).  I don't know
if there is currently a setting to make GDB show the function name but
not the address.

Simon
  
Schimpe, Christina June 1, 2026, 7:05 a.m. UTC | #2
> -----Original Message-----
> From: Simon Marchi <simark@simark.ca>
> Sent: Freitag, 29. Mai 2026 21:06
> To: Tom Tromey <tom@tromey.com>; gdb-patches@sourceware.org
> Subject: Re: [RFC] Show the selected frame in "bt"
> 
> On 5/29/26 2:45 PM, Tom Tromey wrote:
> > I've occasionally wished that "bt" would indicate the selected frame.
> > This patch implements this idea.  In particular it marks the selected
> > frame with "*", similar to other "selected" output in gdb.  (See that
> > other series where emoji were allowed in the "current" column of a
> > table; if that is ever resurrected, I'd expect the same treatment to
> > be applied here.)
> >
> > Now the output looks like:
> >
> > (gdb) bt
> >   #0  0x00007ffff6e381fd in poll () from /lib64/libc.so.6
> >   #1  0x000000000100b1ee in gdb_wait_for_event (block=1) at
> ../../binutils-gdb/gdbsupport/event-loop.cc:587
> >   #2  0x000000000100a77f in gdb_do_one_event (mstimeout=-1) at
> > ../../binutils-gdb/gdbsupport/event-loop.cc:263
> > * #3  0x00000000007d2c6a in interp::do_one_event (this=<optimized out>,
> mstimeout=-1) at ../../binutils-gdb/gdb/interps.h:90
> >   #4  start_event_loop () at ../../binutils-gdb/gdb/main.c:400
> >   #5  captured_command_loop () at ../../binutils-gdb/gdb/main.c:465
> >   #6  0x00000000007d5715 in captured_main
> (context=context@entry=0x7fffffffdd10) at ../../binutils-gdb/gdb/main.c:1373
> >   #7  gdb_main (args=args@entry=0x7fffffffdd50) at ../../binutils-
> gdb/gdb/main.c:1392
> >   #8  0x0000000000452cd5 in main (argc=1, argv=0x7fffffffdea8) at
> > ../../binutils-gdb/gdb/gdb.c:38
> >
> > I think the main downside of this patch is that it uses a little more
> > horizontal space for the indicator.
> 
> I like it, it fits well in the "show user what is selected" paradigm.
> 
> If we're talking about bt formatting and horizontal space, we could consider
> not showing addresses by default, I bet that most users don't care.  I find it
> ugly that by default the address is shown for some frames but not all, it
> makes things unaligned (you can force GDB to show addresses with `-frame-
> info location-and-address` though). I don't know if there is currently a setting
> to make GDB show the function name but not the address.

That the bt command only shows the address for some frames but not for all of them
used to confuse me, too. 
Hiding the address is possible with "set print address off", but I doubt that users
can find this easily since it's not part of the backtrace help text.

I personally like that we show the selected frame - and since I am working on
"bt -shadow" I wonder if it makes sense to integrate that in this output, too.

Currently for "bt -shadow" we don't have the frame #0 which is shown in the
normal backtrace - so we cannot always show the selected frame in "bt shadow".
It has also been discussed to add this frame or to simply start the "bt -shadow"
frame numbering with #1, so that the frame numbers are at least a bit more aligned.
This is one of the opens left for this series.

In any case, if we don't have frame #0 as in the normal bt, I am not sure if showing
the selected frame makes sense for "bt -shadow".

Christina


Intel Deutschland GmbH
Registered Address: Dornacher Strasse 1, 85622 Feldkirchen, Germany
Tel: +49 89 991 430, www.intel.de
Managing Directors: Harry Demas, Jeffrey Schneiderman, Yin Chong Sorrell
Chairperson of the Supervisory Board: Nicole Lau
Registered Seat: Munich
Commercial Register: Amtsgericht Muenchen HRB 186928
  

Patch

diff --git a/gdb/extension-priv.h b/gdb/extension-priv.h
index 4ef87415413..9b0c4815399 100644
--- a/gdb/extension-priv.h
+++ b/gdb/extension-priv.h
@@ -186,7 +186,8 @@  struct extension_language_ops
     (const struct extension_language_defn *,
      const frame_info_ptr &frame, frame_filter_flags flags,
      enum ext_lang_frame_args args_type,
-     struct ui_out *out, int frame_low, int frame_high);
+     struct ui_out *out, int frame_low, int frame_high,
+     const frame_info_ptr &selected_frame);
 
   /* Used for registering the ptwrite filter to the current thread.  */
   void (*apply_ptwrite_filter)
diff --git a/gdb/extension.c b/gdb/extension.c
index d8ef8123ab5..a57ad4a517a 100644
--- a/gdb/extension.c
+++ b/gdb/extension.c
@@ -521,7 +521,8 @@  apply_ext_lang_frame_filter (const frame_info_ptr &frame,
 			     frame_filter_flags flags,
 			     enum ext_lang_frame_args args_type,
 			     struct ui_out *out,
-			     int frame_low, int frame_high)
+			     int frame_low, int frame_high,
+			     const frame_info_ptr &selected_frame)
 {
   for (const struct extension_language_defn *extlang : extension_languages)
     {
@@ -531,8 +532,9 @@  apply_ext_lang_frame_filter (const frame_info_ptr &frame,
 	  || extlang->ops->apply_frame_filter == NULL)
 	continue;
       status = extlang->ops->apply_frame_filter (extlang, frame, flags,
-					       args_type, out,
-					       frame_low, frame_high);
+						 args_type, out,
+						 frame_low, frame_high,
+						 selected_frame);
       /* We use the filters from the first extension language that has
 	 applicable filters.  Also, an error is reported immediately
 	 rather than continue trying.  */
diff --git a/gdb/extension.h b/gdb/extension.h
index e351b5b672e..af535199adb 100644
--- a/gdb/extension.h
+++ b/gdb/extension.h
@@ -302,7 +302,8 @@  extern int apply_ext_lang_val_pretty_printer
 extern enum ext_lang_bt_status apply_ext_lang_frame_filter
   (const frame_info_ptr &frame, frame_filter_flags flags,
    enum ext_lang_frame_args args_type,
-   struct ui_out *out, int frame_low, int frame_high);
+   struct ui_out *out, int frame_low, int frame_high,
+   const frame_info_ptr &selected_frame);
 
 extern void apply_ext_lang_ptwrite_filter
   (struct btrace_thread_info *btinfo);
diff --git a/gdb/frame.h b/gdb/frame.h
index 00485e6d1e2..ed344991330 100644
--- a/gdb/frame.h
+++ b/gdb/frame.h
@@ -899,7 +899,8 @@  extern void print_stack_frame (const frame_info_ptr &, int print_level,
 extern void print_frame_info (const frame_print_options &fp_opts,
 			      const frame_info_ptr &, int print_level,
 			      enum print_what print_what, int args,
-			      int set_current_sal);
+			      int set_current_sal,
+			      const frame_info_ptr &selected_frame);
 
 extern frame_info_ptr block_innermost_frame (const struct block *);
 
diff --git a/gdb/mi/mi-cmd-stack.c b/gdb/mi/mi-cmd-stack.c
index 3281be2b4dc..ba75b9d92d6 100644
--- a/gdb/mi/mi-cmd-stack.c
+++ b/gdb/mi/mi-cmd-stack.c
@@ -67,7 +67,7 @@  mi_apply_ext_lang_frame_filter (const frame_info_ptr &frame,
   return apply_ext_lang_frame_filter (frame, flags,
 				      (enum ext_lang_frame_args) print_values,
 				      out,
-				      frame_low, frame_high);
+				      frame_low, frame_high, {});
 }
 
 /* Print a list of the stack frames.  Args can be none, in which case
@@ -158,7 +158,7 @@  mi_cmd_stack_list_frames (const char *command, const char *const *argv,
 
       result = apply_ext_lang_frame_filter (get_current_frame (), flags,
 					    NO_VALUES,  current_uiout,
-					    py_frame_low, frame_high);
+					    py_frame_low, frame_high, {});
     }
 
   /* Run the inbuilt backtrace if there are no filters registered, or
@@ -175,7 +175,7 @@  mi_cmd_stack_list_frames (const char *command, const char *const *argv,
 	  /* Print the location and the address always, even for level 0.
 	     If args is 0, don't print the arguments.  */
 	  print_frame_info (user_frame_print_options,
-			    fi, 1, LOC_AND_ADDRESS, 0 /* args */, 0);
+			    fi, 1, LOC_AND_ADDRESS, 0 /* args */, 0, {});
 	}
     }
 }
@@ -762,5 +762,5 @@  mi_cmd_stack_info_frame (const char *command, const char *const *argv,
     error (_("-stack-info-frame: No arguments allowed"));
 
   print_frame_info (user_frame_print_options,
-		    get_selected_frame (NULL), 1, LOC_AND_ADDRESS, 0, 1);
+		    get_selected_frame (NULL), 1, LOC_AND_ADDRESS, 0, 1, {});
 }
diff --git a/gdb/python/py-framefilter.c b/gdb/python/py-framefilter.c
index f256a26807c..11463746787 100644
--- a/gdb/python/py-framefilter.c
+++ b/gdb/python/py-framefilter.c
@@ -761,7 +761,8 @@  static enum ext_lang_bt_status
 py_print_frame (PyObject *filter, frame_filter_flags flags,
 		enum ext_lang_frame_args args_type,
 		struct ui_out *out, int indent,
-		levels_printed_hash &levels_printed)
+		levels_printed_hash &levels_printed,
+		const frame_info_ptr &selected_frame)
 {
   int has_addr = 0;
   CORE_ADDR address = 0;
@@ -865,6 +866,14 @@  py_print_frame (PyObject *filter, frame_filter_flags flags,
 			     || print_what == LOC_AND_ADDRESS
 			     || print_what == SHORT_LOCATION));
 
+  if (!selected_frame.is_null ())
+    {
+      if (frame == selected_frame)
+	out->text ("* ");
+      else
+	out->text ("  ");
+    }
+
   /* Print frame level.  MI does not require the level if
      locals/variables only are being printed.  */
   if (print_level
@@ -1068,7 +1077,7 @@  py_print_frame (PyObject *filter, frame_filter_flags flags,
 
 	      enum ext_lang_bt_status success
 		= py_print_frame (item, flags, args_type, out, indent,
-				  levels_printed);
+				  levels_printed, selected_frame);
 
 	      if (success == EXT_LANG_BT_ERROR)
 		return EXT_LANG_BT_ERROR;
@@ -1142,7 +1151,8 @@  enum ext_lang_bt_status
 gdbpy_apply_frame_filter (const struct extension_language_defn *extlang,
 			  const frame_info_ptr &frame, frame_filter_flags flags,
 			  enum ext_lang_frame_args args_type,
-			  struct ui_out *out, int frame_low, int frame_high)
+			  struct ui_out *out, int frame_low, int frame_high,
+			  const frame_info_ptr &selected_frame)
 {
   struct gdbarch *gdbarch = NULL;
   enum ext_lang_bt_status success = EXT_LANG_BT_ERROR;
@@ -1234,7 +1244,7 @@  gdbpy_apply_frame_filter (const struct extension_language_defn *extlang,
       try
 	{
 	  success = py_print_frame (item.get (), flags, args_type, out, 0,
-				    levels_printed);
+				    levels_printed, selected_frame);
 	}
       catch (const gdb_exception_error &except)
 	{
diff --git a/gdb/python/python-internal.h b/gdb/python/python-internal.h
index 10c984bad4b..9245a4e25d7 100644
--- a/gdb/python/python-internal.h
+++ b/gdb/python/python-internal.h
@@ -444,7 +444,8 @@  extern enum ext_lang_bt_status gdbpy_apply_frame_filter
   (const struct extension_language_defn *,
    const frame_info_ptr &frame, frame_filter_flags flags,
    enum ext_lang_frame_args args_type,
-   struct ui_out *out, int frame_low, int frame_high);
+   struct ui_out *out, int frame_low, int frame_high,
+   const frame_info_ptr &selected_frame);
 extern void gdbpy_preserve_values (const struct extension_language_defn *,
 				   struct objfile *objfile,
 				   copied_types_hash_t &copied_types);
diff --git a/gdb/stack.c b/gdb/stack.c
index 7329430adab..ab1fe33a72a 100644
--- a/gdb/stack.c
+++ b/gdb/stack.c
@@ -227,7 +227,8 @@  static void print_frame (struct ui_out *uiout,
 			 const frame_print_options &opts,
 			 const frame_info_ptr &frame, int print_level,
 			 enum print_what print_what,  int print_args,
-			 struct symtab_and_line sal);
+			 struct symtab_and_line sal,
+			 const frame_info_ptr &selected_frame);
 
 static frame_info_ptr find_frame_for_function (const char *);
 static frame_info_ptr find_frame_for_address (CORE_ADDR);
@@ -362,7 +363,7 @@  print_stack_frame (const frame_info_ptr &frame, int print_level,
     {
       print_frame_info (user_frame_print_options,
 			frame, print_level, print_what, 1 /* print_args */,
-			set_current_sal);
+			set_current_sal, {});
       if (set_current_sal)
 	set_current_sal_from_frame (frame);
     }
@@ -961,7 +962,7 @@  static void
 do_print_frame_info (struct ui_out *uiout, const frame_print_options &fp_opts,
 		     const frame_info_ptr &frame, int print_level,
 		     enum print_what print_what, int print_args,
-		     int set_current_sal)
+		     int set_current_sal, const frame_info_ptr &selected_frame)
 {
   struct gdbarch *gdbarch = get_frame_arch (frame);
   int source_print;
@@ -983,6 +984,14 @@  do_print_frame_info (struct ui_out *uiout, const frame_print_options &fp_opts,
       annotate_frame_begin (print_level ? frame_relative_level (frame) : 0,
 			    gdbarch, get_frame_pc (frame));
 
+      if (!selected_frame.is_null ())
+	{
+	  if (frame == selected_frame)
+	    uiout->text ("* ");
+	  else
+	    uiout->text ("  ");
+	}
+
       /* Do this regardless of SOURCE because we don't have any source
 	 to list for this frame.  */
       if (print_level)
@@ -1042,7 +1051,7 @@  do_print_frame_info (struct ui_out *uiout, const frame_print_options &fp_opts,
 		    || print_what == SHORT_LOCATION);
   if (location_print || !sal.symtab)
     print_frame (uiout, fp_opts, frame, print_level,
-		 print_what, print_args, sal);
+		 print_what, print_args, sal, selected_frame);
 
   source_print = (print_what == SRC_LINE || print_what == SRC_AND_LOC);
 
@@ -1129,11 +1138,11 @@  void
 print_frame_info (const frame_print_options &fp_opts,
 		  const frame_info_ptr &frame, int print_level,
 		  enum print_what print_what, int print_args,
-		  int set_current_sal)
+		  int set_current_sal, const frame_info_ptr &selected_frame)
 {
   do_with_buffered_output (do_print_frame_info, current_uiout,
 			   fp_opts, frame, print_level, print_what,
-			   print_args, set_current_sal);
+			   print_args, set_current_sal, selected_frame);
 }
 
 /* See stack.h.  */
@@ -1263,7 +1272,8 @@  print_frame (struct ui_out *uiout,
 	     const frame_print_options &fp_opts,
 	     const frame_info_ptr &frame, int print_level,
 	     enum print_what print_what, int print_args,
-	     struct symtab_and_line sal)
+	     struct symtab_and_line sal,
+	     const frame_info_ptr &selected_frame)
 {
   struct gdbarch *gdbarch = get_frame_arch (frame);
   enum language funlang = language_unknown;
@@ -1282,6 +1292,14 @@  print_frame (struct ui_out *uiout,
   {
     ui_out_emit_tuple tuple_emitter (uiout, "frame");
 
+    if (!selected_frame.is_null ())
+      {
+	if (frame == selected_frame)
+	  uiout->text ("* ");
+	else
+	  uiout->text ("  ");
+      }
+
     if (print_level)
       {
 	uiout->text ("#");
@@ -1930,6 +1948,8 @@  backtrace_command_1 (const frame_print_options &fp_opts,
   if (fp_opts.print_raw_frame_arguments)
     flags |= PRINT_RAW_FRAME_ARGUMENTS;
 
+  frame_info_ptr selected_frame = get_selected_frame ();
+
   if (!bt_opts.no_filters)
     {
       enum ext_lang_frame_args arg_type;
@@ -1951,7 +1971,8 @@  backtrace_command_1 (const frame_print_options &fp_opts,
 
       result = apply_ext_lang_frame_filter (get_current_frame (), flags,
 					    arg_type, current_uiout,
-					    py_start, py_end);
+					    py_start, py_end,
+					    selected_frame);
     }
 
   /* Run the inbuilt backtrace if there are no filters registered, or
@@ -1982,7 +2003,7 @@  backtrace_command_1 (const frame_print_options &fp_opts,
 	     hand, perhaps the code does or could be fixed to make sure
 	     the frame->prev field gets set to NULL in that case).  */
 
-	  print_frame_info (fp_opts, fi, 1, LOCATION, 1, 0);
+	  print_frame_info (fp_opts, fi, 1, LOCATION, 1, 0, selected_frame);
 	  if ((flags & PRINT_LOCALS) != 0)
 	    print_frame_local_vars (fi, false, NULL, NULL, 1, gdb_stdout);