[2/2] gdb: add 'maint info blocks' command

Message ID ef6f7f8eab7e0f82e290cff6e20cf1ae4a295420.1721659206.git.aburgess@redhat.com
State New
Headers
Series New inline-frames and blocks maintenance commands |

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

Andrew Burgess July 22, 2024, 2:42 p.m. UTC
  While reviewing a patch I wanted to understand which blocks existed at
a given address.

The 'maint print symbols' command does provide some of this
information, but that command displays all blocks within a given
symtab.  If I want to know which blocks are at a given address I have
to figure that out for myself based on the output of 'maint print
symbols' ... and I'm too lazy for that!

So this command lists just those blocks at a given address, along with
information about the blocks type.  This new command doesn't list the
symbols within each block, for that my expectation is that you'd cross
reference the output with that of 'maint print symbols'.

The new command format is:

  maintenance info blocks
  maintenance info blocks ADDRESS

This lists the blocks at ADDRESS, or at the current $pc if ADDRESS is
not given.  Blocks are listed starting at the global block, then the
static block, and then the progressively narrower scoped blocks.

For each block we list the internal block pointer (which allows easy
cross referencing with 'maint print symbols'), the inferior address
range, along with other useful information.
---
 gdb/NEWS                                      |   6 +
 gdb/block.c                                   | 132 ++++++++++++++++++
 gdb/doc/gdb.texinfo                           |  50 +++++++
 .../maint-info-inline-frames-and-blocks.exp   |  65 ++++++++-
 4 files changed, 252 insertions(+), 1 deletion(-)
  

Comments

Eli Zaretskii July 22, 2024, 3:12 p.m. UTC | #1
> From: Andrew Burgess <aburgess@redhat.com>
> Cc: Andrew Burgess <aburgess@redhat.com>
> Date: Mon, 22 Jul 2024 15:42:09 +0100
> 
>  gdb/NEWS                                      |   6 +
>  gdb/block.c                                   | 132 ++++++++++++++++++
>  gdb/doc/gdb.texinfo                           |  50 +++++++
>  .../maint-info-inline-frames-and-blocks.exp   |  65 ++++++++-
>  4 files changed, 252 insertions(+), 1 deletion(-)

Thanks.

> --- a/gdb/NEWS
> +++ b/gdb/NEWS
> @@ -20,6 +20,12 @@ maintenance info inline-frames [ADDRESS]
>    current address, or for ADDRESS if specified.  The output identifies
>    inlined frames which start at the specified address.
>  
> +maintenance info blocks [ADDRESS]
> +  New command which displays information about all of the blocks at
> +  ADDRESS, or at the current address if ADDRESS is not given.  Blocks
> +  are listed starting at the inner global block out to the most inner
> +  block.
> +

This part is okay.

> +Here is an example of the command's output:
> +@smallexample
> +(@value{GDBP}) maintenance info blocks
> +Blocks at 0x401137:
> +  from objfile: [(objfile *) 0x50507d0] /tmp/inline_func_demo
> +
> +[(block *) 0x504da90] 0x401106..0x40119a
> +  entry pc: 0x401106
> +  is global block
> +  symbol count: 2
> +  is contiguous
> +[(block *) 0x504d9f0] 0x401106..0x40119a
> +  entry pc: 0x401106
> +  is static block
> +  symbol count: 1
> +  is contiguous
> +[(block *) 0x504d9a0] 0x401106..0x40119a
> +  entry pc: 0x401106
> +  function: main
> +  is contiguous
> +[(block *) 0x504d900] 0x401137..0x401166
> +  entry pc: 0x401137
> +  inline function: foo
> +  symbol count: 1
> +  is contiguous
> +[(block *) 0x504d860] 0x401137..0x401165
> +  entry pc: 0x401137
> +  inline function: bar
> +  symbol count: 1
> +  is contiguous
> +@end smallexample

This example should probably be wrapped in "@group..@end group", or at
least each part of it that describes a block should be wrapped like
that, to prevent them from being broken across pages.

> +The @command{maint info blocks} command lists the symbol count for

This should say

  The command @kbd{maint info blocks} lists the symbol...

@command is used for shell commands, which is not the case here.

> +each block but doesn't print the symbols themselves.  The symbol names
> +can be found using @command{maint print symbols} (@pxref{maint print
                      ^^^^^^^^
Likewise here: use @kbd or @code.

The documentation parts are okay with these nits fixed.

Reviewed-By: Eli Zaretskii <eliz@gnu.org>
  

Patch

diff --git a/gdb/NEWS b/gdb/NEWS
index 1cbc05c5a18..1673671744d 100644
--- a/gdb/NEWS
+++ b/gdb/NEWS
@@ -20,6 +20,12 @@  maintenance info inline-frames [ADDRESS]
   current address, or for ADDRESS if specified.  The output identifies
   inlined frames which start at the specified address.
 
+maintenance info blocks [ADDRESS]
+  New command which displays information about all of the blocks at
+  ADDRESS, or at the current address if ADDRESS is not given.  Blocks
+  are listed starting at the inner global block out to the most inner
+  block.
+
 *** Changes in GDB 15
 
 * The MPX commands "show/set mpx bound" have been deprecated, as Intel
diff --git a/gdb/block.c b/gdb/block.c
index 511689c9738..3cb6c28dc1e 100644
--- a/gdb/block.c
+++ b/gdb/block.c
@@ -25,6 +25,8 @@ 
 #include "addrmap.h"
 #include "gdbtypes.h"
 #include "objfiles.h"
+#include "cli/cli-cmds.h"
+#include "inferior.h"
 
 /* This is used by struct block to store namespace-related info for
    C++ files, namely using declarations and the current namespace in
@@ -834,3 +836,133 @@  make_blockranges (struct objfile *objfile,
   return blr;
 }
 
+/* Implement 'maint info blocks' command.  If passed an argument then
+   print a list of all blocks at the given address.  With no arguments
+   then list all blocks at the current address of the current inferior.  */
+
+static void
+maintenance_info_blocks (const char *arg, int from_tty)
+{
+  CORE_ADDR address;
+
+  /* With no argument use the program counter of the current thread.  If
+     there is an argument then use this as the address to examine.  */
+  if (arg == nullptr)
+    {
+      if (inferior_ptid == null_ptid)
+	error (_("no inferior thread"));
+
+      struct regcache *regcache = get_thread_regcache (inferior_thread ());
+      address = regcache_read_pc (regcache);
+    }
+  else
+    address = parse_and_eval_address (arg);
+
+  /* Find the inner most block for ADDRESS.  */
+  const struct block *cur_block = block_for_pc (address);
+  if (cur_block == nullptr)
+    {
+      gdb_printf (_("No blocks at %s\n"), core_addr_to_string_nz (address));
+      return;
+    }
+
+  gdb_printf (_("Blocks at %s:\n"), core_addr_to_string_nz (address));
+
+  const struct objfile *toplevel_objfile = cur_block->objfile ();
+  if (toplevel_objfile != nullptr)
+    gdb_printf (_("  from objfile: [(objfile *) %s] %s\n"),
+		host_address_to_string (toplevel_objfile),
+		objfile_name (toplevel_objfile));
+
+  gdb_printf ("\n");
+
+  /* List the blocks backwards; global block (widest scope) first, down to
+     the smallest scoped block last.  To do this we need to build the list
+     of blocks starting from the inner block, then print that list
+     backwards.  */
+  std::vector<const struct block *> blocks;
+  while (cur_block != nullptr)
+    {
+      blocks.emplace_back (cur_block);
+      cur_block = cur_block->superblock ();
+    }
+
+  for (auto it = blocks.rbegin (); it != blocks.rend (); ++it)
+    {
+      cur_block = *it;
+
+      gdb_printf (_("[(block *) %s] %s..%s\n"),
+		  host_address_to_string (cur_block),
+		  core_addr_to_string_nz (cur_block->start ()),
+		  core_addr_to_string_nz (cur_block->end ()));
+      gdb_printf (_("  entry pc: %s\n"),
+		  core_addr_to_string_nz (cur_block->entry_pc ()));
+      if (cur_block->is_static_block ())
+	gdb_printf (_("  is static block\n"));
+      if (cur_block->is_global_block ())
+	gdb_printf (_("  is global block\n"));
+      if (cur_block->function () != nullptr)
+	{
+	  if (cur_block->inlined_p ())
+	    gdb_printf (_("  inline function: %s\n"),
+			cur_block->function ()->print_name ());
+	  else
+	    gdb_printf (_("  function: %s\n"),
+			cur_block->function ()->print_name ());
+	}
+      else if (cur_block->inlined_p ())
+	gdb_printf (_("  is inline block, not has no function symbol\n"));
+      if (cur_block->scope () != nullptr
+	  && *cur_block->scope () != '\0')
+	gdb_printf (_("  scope: %s\n"), cur_block->scope ());
+      int symbol_count = mdict_size (cur_block->multidict ());
+      if (symbol_count > 0)
+	gdb_printf (_("  symbol count: %d\n"), symbol_count);
+
+      /* Check out block::objfile() to see why it might be the case that a
+	 nested block could have a different objfile than its containing
+	 block.  */
+      if (cur_block->objfile () != toplevel_objfile)
+	{
+	  if (cur_block->objfile () == nullptr)
+	    gdb_printf (_("  from objfile: [(objfile *) %s]\n"),
+			host_address_to_string (cur_block->objfile ()));
+	  else
+	    gdb_printf (_("  from objfile: [(objfile *) %s] %s\n"),
+			host_address_to_string (cur_block->objfile ()),
+			objfile_name (cur_block->objfile ()));
+	}
+
+      if (cur_block->is_contiguous ())
+	gdb_printf (_("  is contiguous\n"));
+      else
+	{
+	  gdb_printf (_("  address ranges:\n"));
+	  for (const blockrange &rng : cur_block->ranges ())
+	    gdb_printf (_("    %s..%s\n"),
+			core_addr_to_string_nz (rng.start ()),
+			core_addr_to_string_nz (rng.end ()));
+	}
+    }
+}
+
+
+
+void _initialize_block ();
+void
+_initialize_block ()
+{
+  add_cmd ("blocks", class_maintenance, maintenance_info_blocks,
+	   _("\
+Display block information for current thread.\n\
+\n\
+Usage:\n\
+\n\
+  maintenance info blocks [ADDRESS]\n\
+\n\
+With no ADDRESS show all blocks at the current address, starting with the\n\
+global block and working down to the inner most block.\n\
+\n\
+When ADDRESS is given, list the blocks at ADDRESS."),
+	   &maintenanceinfolist);
+}
diff --git a/gdb/doc/gdb.texinfo b/gdb/doc/gdb.texinfo
index 0f710cf9289..aeeb9f94dea 100644
--- a/gdb/doc/gdb.texinfo
+++ b/gdb/doc/gdb.texinfo
@@ -20412,6 +20412,7 @@ 
 Show whether messages will be printed when a @value{GDBN} command
 entered from the keyboard causes symbol information to be loaded.
 
+@anchor{maint print symbols}
 @kindex maint print symbols
 @cindex symbol dump
 @kindex maint print psymbols
@@ -41947,6 +41948,55 @@ 
   main
 @end smallexample
 
+@kindex maint info blocks
+@item maint info blocks
+@itemx maint info blocks @var{address}
+Print information about all blocks at @var{address}, or at the current
+@code{$pc} if @var{address} is not given.
+
+For information about what blocks are in @value{GDBN} see @ref{Blocks
+In Python}.
+
+Blocks are listed starting from the global block, then the static
+block, and then proceeding through progressively narrower scopes.
+
+Here is an example of the command's output:
+@smallexample
+(@value{GDBP}) maintenance info blocks
+Blocks at 0x401137:
+  from objfile: [(objfile *) 0x50507d0] /tmp/inline_func_demo
+
+[(block *) 0x504da90] 0x401106..0x40119a
+  entry pc: 0x401106
+  is global block
+  symbol count: 2
+  is contiguous
+[(block *) 0x504d9f0] 0x401106..0x40119a
+  entry pc: 0x401106
+  is static block
+  symbol count: 1
+  is contiguous
+[(block *) 0x504d9a0] 0x401106..0x40119a
+  entry pc: 0x401106
+  function: main
+  is contiguous
+[(block *) 0x504d900] 0x401137..0x401166
+  entry pc: 0x401137
+  inline function: foo
+  symbol count: 1
+  is contiguous
+[(block *) 0x504d860] 0x401137..0x401165
+  entry pc: 0x401137
+  inline function: bar
+  symbol count: 1
+  is contiguous
+@end smallexample
+
+The @command{maint info blocks} command lists the symbol count for
+each block but doesn't print the symbols themselves.  The symbol names
+can be found using @command{maint print symbols} (@pxref{maint print
+symbols}).
+
 @kindex maint print registers
 @kindex maint print raw-registers
 @kindex maint print cooked-registers
diff --git a/gdb/testsuite/gdb.base/maint-info-inline-frames-and-blocks.exp b/gdb/testsuite/gdb.base/maint-info-inline-frames-and-blocks.exp
index f5c5fd2623e..4be8167895a 100644
--- a/gdb/testsuite/gdb.base/maint-info-inline-frames-and-blocks.exp
+++ b/gdb/testsuite/gdb.base/maint-info-inline-frames-and-blocks.exp
@@ -13,7 +13,8 @@ 
 # You should have received a copy of the GNU General Public License
 # along with this program.  If not, see <http://www.gnu.org/licenses/>.
 
-# Check the 'maint info inline-frames' command.
+# Check the 'maint info inline-frames' and 'maint info blocks'
+# commands.
 
 standard_testfile
 
@@ -26,6 +27,45 @@  if {![runto normal_func]} {
     return 0
 }
 
+# Make a pattern to match 'maint info blocks' output.  ARGS is the
+# list of function names we expect to see.  If the function name
+# starts with 'inline_func' then we expect to see an inline block,
+# otherwise blocks are not expected to be inline.
+proc make_blocks_result { args } {
+    set result \
+	[list \
+	     "Blocks at $::hex:" \
+	     "  from objfile: \\\[\\(objfile \\*\\) $::hex\\\] [string_to_regexp $::binfile]" \
+	     ""\
+	     "\\\[\\(block \\*\\) $::hex\\\] $::hex\\.\\.$::hex" \
+	     "  entry pc: $::hex" \
+	     "  is global block" \
+	     ".*" \
+	     "\\\[\\(block \\*\\) $::hex\\\] $::hex\\.\\.$::hex" \
+	     "  entry pc: $::hex" \
+	     "  is static block" \
+	     ".*" ]
+
+    foreach func $args {
+	lappend result \
+	    "\\\[\\(block \\*\\) $::hex\\\] $::hex\\.\\.$::hex" \
+	    "  entry pc: $::hex"
+
+	if { [string range $func 0 10] eq "inline_func" } {
+	    lappend result"  inline function: $func"
+	} else {
+	    lappend result"  function: $func"
+	}
+
+	lappend result ".*"
+    }
+
+    return [multi_line {*}$result]
+}
+
+gdb_test "maint info blocks" [make_blocks_result normal_func] \
+    "maint info blocks in normal_func only"
+
 # Next forward until we find the call to inline_func_a().  The hope is
 # that when we see the 'inline_func_a()' line this will be the start of
 # the inlined function.  This might not be the case on all
@@ -54,6 +94,10 @@  gdb_test_multiple "next" "next forward to inline_func_a" {
     }
 }
 
+gdb_test "maint info blocks" [make_blocks_result normal_func \
+				  inline_func_a inline_func_b] \
+    "maint info blocks when all blocks visible"
+
 # View the inline frame information.  This should display that we are
 # at the start of inline_func_a() within normal_func().
 gdb_test "maint info inline-frames" \
@@ -105,6 +149,10 @@  gdb_test "maint info inline-frames" \
 	 "  normal_func"] \
     "check inline-frames state when just entered inline_func_b"
 
+gdb_test "maint info blocks" [make_blocks_result normal_func \
+				  inline_func_a inline_func_b] \
+    "maint info blocks when all blocks still visible"
+
 gdb_test "step" ".*" \
     "step into the body of inline_func_b"
 
@@ -117,6 +165,10 @@  gdb_test "maint info inline-frames" \
 	 "> inline_func_b"] \
     "check inline-frames state when within inline_func_b"
 
+gdb_test "maint info blocks" [make_blocks_result normal_func \
+				  inline_func_a inline_func_b] \
+    "maint info blocks within inline function, all blocks still visible"
+
 # Use the recorded $pc value to check inline frames.
 gdb_test "maint info inline-frames $pc" \
     [multi_line \
@@ -127,6 +179,10 @@  gdb_test "maint info inline-frames $pc" \
 	 "> normal_func"] \
     "check inline-frames state at recorded \$pc"
 
+gdb_test "maint info blocks" [make_blocks_result normal_func \
+				  inline_func_a inline_func_b] \
+    "maint info blocks using stored \$pc, inferior still running"
+
 clean_restart $binfile
 
 # Use the recorded $pc value to check inline frames when the inferior
@@ -140,8 +196,15 @@  gdb_test "maint info inline-frames $pc" \
 	 "> normal_func"] \
     "check inline-frames state at recorded \$pc before execution starts"
 
+gdb_test "maint info blocks $pc" [make_blocks_result normal_func \
+				      inline_func_a inline_func_b] \
+    "maint info blocks using stored \$pc, inferior not running"
+
 # Trying to read the $pc from the current thread should fail if the
 # inferior is not yet running.
 gdb_test "maint info inline-frames" \
     "^no inferior thread" \
     "check inline-frames state of current thread before execution starts"
+
+gdb_test "maint info blocks" "^no inferior thread" \
+    "maint info blocks with no \$pc and inferior not running"