Mirror of the gdb-patches mailing list
 help / color / mirror / Atom feed
From: Andrew Burgess <aburgess@redhat.com>
To: gdb-patches@sourceware.org
Cc: Andrew Burgess <aburgess@redhat.com>,
	Bernd Edlinger <bernd.edlinger@hotmail.de>
Subject: [PATCH 7/7] gdb: fix-up truncated inline function block ranges
Date: Sun, 20 Jul 2025 11:20:46 +0100	[thread overview]
Message-ID: <a8712b4642d015db3df759f8bdec71ba4c867d6d.1753006060.git.aburgess@redhat.com> (raw)
In-Reply-To: <cover.1753006060.git.aburgess@redhat.com>

This commit aims to improve GDB's handling of inline functions.  There
are two mechanisms which can tell GDB, or the user of GDB, that the
inferior is within an inline function, these are the block range
associated with an inline instance of a function, and also the line
table, which associates addresses with source lines in the program.

Currently, gcc truncates the address range for, at least some, inline
function blocks, such that a given address is considered outside the
inline function.  However, the line table maps that same address to a
line within the inline function.

A consequence of this, is that, when using 'next' to move the inferior
forward, GDB will often stop the inferior believing that the inferior
has left an inline function, and indeed, GDB will claim that the
inferior is in the outer, non-inline function, but GDB will then
display a source line from the inline function as the current location.

An example of this problem can be seen with the test
gdb.cp/step-and-next-inline.exp.  Using the
step-and-next-inline-no-header binary that is built as part of the
test:

  (gdb) file ./gdb/testsuite/outputs/gdb.cp/step-and-next-inline/step-and-next-inline-no-header
  Reading symbols from ./gdb/testsuite/outputs/gdb.cp/step-and-next-inline/step-and-next-inline-no-header...
  (gdb) break get_alias_set
  Breakpoint 1 at 0x401160: file /tmp/build/gdb/testsuite/../../../src/gdb/testsuite/gdb.cp/step-and-next-inline.cc, line 51.
  (gdb) run
  Starting program: /tmp/build/gdb/testsuite/outputs/gdb.cp/step-and-next-inline/step-and-next-inline-no-header

  Breakpoint 1, get_alias_set (t=t@entry=0x404038 <xx>)
      at /tmp/build/gdb/testsuite/../../../src/gdb/testsuite/gdb.cp/step-and-next-inline.cc:51
  51	  if (t != NULL
  (gdb) next
  52	      && TREE_TYPE (t).z != 1
  (gdb) next
  43	  return x;	<------------- Problem line.
  (gdb) bt
  #0  get_alias_set (t=t@entry=0x404038 <xx>) at /tmp/build/gdb/testsuite/../../../src/gdb/testsuite/gdb.cp/step-and-next-inline.cc:43
  #1  0x000000000040105e in main () at /tmp/build/gdb/testsuite/../../../src/gdb/testsuite/gdb.cp/step-and-next-inline.cc:64
  (gdb)

I've labelled the issue as 'Problem line'.  After the second 'next'
GDB stopped thinking it was in get_alias_set, but printed a line from
the inline function behind the TREE_TYPE macro.  The 'Problem line'
should have been line 53, not line 43.

The $pc at which GDB stopped is 0x40116f.  If we then use 'objdump
--dwarf=decodedline' to view the line table for the executable, this
is what we see:

  File name                        Line number    Starting address    View    Stmt
  ...
  step-and-next-inline.cc                   38            0x401165               x
  step-and-next-inline.cc                   40            0x401165       1       x
  step-and-next-inline.cc                   40            0x401165       2
  step-and-next-inline.cc                   40            0x401167
  step-and-next-inline.cc                   42            0x40116f               x
  step-and-next-inline.cc                   43            0x40116f       1       x
  step-and-next-inline.cc                   43            0x40116f       2
  step-and-next-inline.cc                   52            0x40116f       3
  step-and-next-inline.cc                   52            0x401172
  step-and-next-inline.cc                   38            0x401177               x
  step-and-next-inline.cc                   40            0x401177       1       x
  step-and-next-inline.cc                   40            0x401177       2
  ...

NOTE: I use objdump to view the line table, not 'maintenance info
line-table' as GDB drops some line table entries that it sees as
irrelevant.  Using objdump give a complete view of the line table.

We can see that address 0x40116f is associated with three line
numbers, 42, and 43 are both within the inline function, and 52 is the
line from which the inline function was called.  Notice too that 52 is
a non-statement line.

If we now look at the block structure for the previous $pc
value 0x40116e (i.e. $pc - 1), then we see this:

  (gdb) maintenance info blocks 0x40116e
  ...
  [(block *) 0x3c62900] 0x401040..0x4011a6
    entry pc: 0x401160
    function: get_alias_set(tree*)
    symbol count: 1
    address ranges:
      0x401160..0x4011a6
      0x401040..0x401046
  [(block *) 0x3c61260] 0x401041..0x40116f
    entry pc: 0x401165
    inline function: tree_check(tree*, int)
    symbol count: 4
    address ranges:
      0x401165..0x40116f
      0x401041..0x401046
  (gdb)

Here we see 'tree_check', the inline function that backs the TREE_TYPE
macro, this is the inline function we have just stepped out of.  This
makes sense as the end-address for the tree_check block is 0x40116f,
and as the block's end address is not inclusive, that means that
0x40116f is the first address outside the block, which, is the current
$pc value.

And so, we can see what's going on.  When the 'next' starts GDB is in
get_alias_set, GDB steps forward, entering tree_check.  GDB then uses
the extent of the block to figure out where the inline function ends,
and steps forward to that address (0x40116f).  At this point, GDB
looks up the current line in the line table (43), and reports a stop
at this line.

In this commit, the fix I propose is to look for the line table
pattern seen above, a sequence of line table entries, that end with a
non-statement entry for the calling line of an inline function,
located at the exact end address of an inline function block.

When such a pattern is found, then we can extend the inline function's
address range to the next line table address, so long as doing so does
not extend the inline function beyond the extent of the containing,
non-inline, function.

In the above example, the block for the tree_check function would be
extended to end at 0x401172.  With this fix in place, and with the
same test binary, GDB now behaves like this:

  (gdb) break get_alias_set
  Breakpoint 1 at 0x401160: file /tmp/build/gdb/testsuite/../../../src/gdb/testsuite/gdb.cp/step-and-next-inline.cc, line 51.
  (gdb) run
  Starting program: /tmp/build/gdb/testsuite/outputs/gdb.cp/step-and-next-inline/step-and-next-inline-no-header

  Breakpoint 1, get_alias_set (t=t@entry=0x404038 <xx>)
      at /tmp/build/gdb/testsuite/../../../src/gdb/testsuite/gdb.cp/step-and-next-inline.cc:51
  51	  if (t != NULL
  (gdb) next
  52	      && TREE_TYPE (t).z != 1
  (gdb) next
  53	      && TREE_TYPE (t).z != 2
  (gdb)

The block for the inline function has been updated, like this:

(gdb) maintenance info blocks 0x40116e
...
[(block *) 0x4965530] 0x401040..0x4011a6
  entry pc: 0x401160
  function: get_alias_set(tree*)
  symbol count: 1
  address ranges:
    0x401160..0x4011a6
    0x401040..0x401046
[(block *) 0x49640b0] 0x401040..0x401172
  entry pc: 0x401165
  inline function: tree_check(tree*, int)
  symbol count: 4
  address ranges:
    0x401165..0x401172
    0x401040..0x401041
    0x401041..0x401046

This is my alternative to the patch presented here:

  https://inbox.sourceware.org/gdb-patches/AS1PR01MB946510286FBF2497A6F03E83E4922@AS1PR01MB9465.eurprd01.prod.exchangelabs.com/

This original patch series from Bernd Edlinger contains a number of
different fixes, some have already been split out and merged into GDB,
but the core idea for how to improve inline function handling by
extending the inline block range is the same, however, the mechanism
Bernd uses is significantly different.

In the above series, the approach taken is to mark the line table at
the end address of an inline function, and a few addresses beyond
that (see the is-weak flag in the above series).  Then, when looking
up the block for a given address, if the address is within this marked
region, then we actually return the previous (inline function) block.

I believe that the above is a fair high-level summary of how the above
patch solves the inline function range problem.  Any differences are
down to my misunderstanding the above patch, for which I apologise.

My problem with the above patch is that it breaks what I think should
be an invariant of GDB, that when looking up a block for a given
address, the block returned must contain the address within its
ranges.  I feel that, if we start to break this invariant, then we
risk introducing bugs within, e.g. the stepping control code.

In contrast, my approach solves this problem during the DWARF parsing,
where problem cases are identified, and the DWARF "fixed" by extending
the block ranges.  After this, no additional changes are needed in
GDB, the address to block mapping can work as normal, and the stepping
logic can continue to work just as it always has.

The test changes for gdb.cp/step-and-next-inline.exp have been taken
from Bernd's original patch series, and I've added a Co-Author tag for
Bernd to reflect this, as well as for the inspiration that I took from
his original series when creating this alternative proposal.

If/when this patch is merged, I plan to follow up with some cleanup to
the test case gdb.cp/step-and-next-inline.exp.  I think this test
should really be moved to gdb.opt/, it's really testing optimisation
debug, not C++ features, but also the structure of the test file is a
bit of a mess.  I think with some restructuring we could make the test
more readable, and also, maybe, test some additional compiler
flags (e.g. more optimisation levels).  I've not done the refactoring
in this patch in order to make it clearer what new tests I've added,
and also, I want to leave the test similar to what's in Bernd's
original series, to make comparison easier.

The gdb.cp/step-and-next-inline.exp test was originally added by me
back in 2019, so the problems with it are of my own making.

For testing I've obviously run the entire test suite, but of
particular interest are these tests:

  gdb.cp/step-and-next-inline.exp
  gdb.dwarf2/dw2-inline-bt.exp
  gdb.opt/empty-inline-cxx.exp
  gdb.opt/empty-inline.exp
  gdb.opt/inline-bt.exp

I've run these tests with a range of different gcc versions:  9.5.0,
10.5.0, 11.5.0, 12.2.0, 13.3.0, 14.2.0, 15.1.0.  These tests all
relate to optimised debug of inline functions, and all passed with all
compiler versions listed here.

Co-Authored-By: Bernd Edlinger <bernd.edlinger@hotmail.de>
---
 gdb/dwarf2/read.c                             |  80 +++
 gdb/testsuite/gdb.cp/step-and-next-inline.exp |  38 +-
 .../gdb.dwarf2/dw2-extend-inline-block.c      |  78 +++
 .../gdb.dwarf2/dw2-extend-inline-block.exp    | 574 ++++++++++++++++++
 4 files changed, 763 insertions(+), 7 deletions(-)
 create mode 100644 gdb/testsuite/gdb.dwarf2/dw2-extend-inline-block.c
 create mode 100644 gdb/testsuite/gdb.dwarf2/dw2-extend-inline-block.exp

diff --git a/gdb/dwarf2/read.c b/gdb/dwarf2/read.c
index 992cf008da0..89e3efe5013 100644
--- a/gdb/dwarf2/read.c
+++ b/gdb/dwarf2/read.c
@@ -16237,6 +16237,79 @@ dwarf_finish_line (struct gdbarch *gdbarch, struct subfile *subfile,
   dwarf_record_line_1 (gdbarch, subfile, 0, address, LEF_IS_STMT, cu);
 }
 
+/* Look for an inline block that finishes at ORIGINAL_ADDRESS.  If a block
+   is found, then search up the block hierarchy looking for a suitable
+   inline block to extend, a suitable block will be called from LINE.  If a
+   block is found then update its end address to EXTENDED_ADDRESS.  */
+
+static void
+dwarf_find_and_extend_inline_block_range (dwarf2_cu *cu,
+					  unrelocated_addr original_address,
+					  unrelocated_addr extended_address,
+					  unsigned int line)
+{
+  /* Is there an inline block that ends at ORIGINAL_ADDRESS?  */
+  auto it = cu->inline_block_ends.find (original_address);
+  if (it == cu->inline_block_ends.end ())
+    return;
+
+  /* Walk back up the block structure until we find the first inline block
+     that occurs after a non-inline block.  This is our candidate for
+     extending.  */
+  struct block *block = nullptr;
+  for (const struct block *b = it->second;
+       b != nullptr;
+       b = b->superblock ())
+    {
+      if (b->function () != nullptr && b->inlined_p ())
+	{
+	  if (b->superblock () != nullptr
+	      && b->superblock ()->function () != nullptr
+	      && !b->superblock ()->inlined_p ())
+	    {
+	      block = const_cast<struct block *> (b);
+	      break;
+	    }
+	}
+    }
+
+  /* If we didn't find a block, of the line table doesn't indicate that the
+     block should be extended, then we're done.  Maybe we should try harder
+     to look for the block that matches LINE, but this would require us to
+     possibly extended more blocks, adding more complexity.  Currently,
+     this works enough for simple cases, we can possibly improve the logic
+     here later on.  */
+  if (block == nullptr || block->function ()->line () != line)
+    return;
+
+  /* Sanity check.  We should have an inline block, which should have a
+     valid super block.  */
+  gdb_assert (block->inlined_p ());
+  gdb_assert (block->superblock () != nullptr);
+
+  CORE_ADDR extended_end = cu->per_objfile->relocate (extended_address);
+
+  /* The proposed new end of BLOCK is outside of the ranges of BLOCK's
+     superblock.  If we tried to extend BLOCK then this would create an
+     invalid block structure; BLOCK would no longer be fully nested within
+     its superblock.  Don't do that.  */
+  if (extended_end > block->superblock ()->end ())
+    return;
+
+  CORE_ADDR original_end = cu->per_objfile->relocate (original_address);
+
+  /* Now find the part of BLOCK that ends at ORIGINAL_END, and extend it
+     out to EXTENDED_END.  */
+  for (blockrange &br : block->ranges ())
+    {
+      if (br.end () == original_end)
+	br.set_end (extended_end);
+    }
+
+  if (block->end () == original_end)
+    block->set_end (extended_end);
+}
+
 void
 lnp_state_machine::record_line (bool end_sequence)
 {
@@ -16255,6 +16328,13 @@ lnp_state_machine::record_line (bool end_sequence)
 		  (end_sequence ? "\t(end sequence)" : ""));
     }
 
+  if (m_address != m_last_address
+      && m_stmt_at_address
+      && m_cu->producer_is_gcc ()
+      && (m_flags & LEF_IS_STMT) == 0)
+    dwarf_find_and_extend_inline_block_range (m_cu, m_last_address,
+					      m_address, m_line);
+
   file_entry *fe = current_file ();
 
   if (fe == NULL)
diff --git a/gdb/testsuite/gdb.cp/step-and-next-inline.exp b/gdb/testsuite/gdb.cp/step-and-next-inline.exp
index effff2721fa..4ed82deda32 100644
--- a/gdb/testsuite/gdb.cp/step-and-next-inline.exp
+++ b/gdb/testsuite/gdb.cp/step-and-next-inline.exp
@@ -24,13 +24,6 @@ if {[test_compiler_info gcc*] && ![supports_statement_frontiers] } {
 proc do_test { use_header } {
     global srcfile testfile
 
-    if { $use_header } {
-	# This test will not pass due to poor debug information
-	# generated by GCC (at least up to 10.x).  See
-	# https://gcc.gnu.org/bugzilla/show_bug.cgi?id=94474
-	return
-    }
-
     set options {c++ debug nowarnings optimize=-O2}
     if { [supports_statement_frontiers] } {
 	lappend options additional_flags=-gstatement-frontiers
@@ -198,6 +191,8 @@ proc do_test { use_header } {
 	gdb_test "bt" "\\s*\\#0\\s+\[^\r\]*tree_check\[^\r\]*${hdrfile}:.*" \
 	    "in inline 1 pass 2"
 	gdb_test "step" ".*return x.*" "step 3"
+	gdb_test "bt" "\\s*\\#0\\s+\[^\r\]*tree_check\[^\r\]*${hdrfile}:.*" \
+	    "return from inline 1 pass 2"
 	gdb_test "step" ".*TREE_TYPE.*" "step 4"
 	gdb_test "bt" "\\s*\\#0\\s+get_alias_set\[^\r\]*${srcfile}:.*" \
 	    "not in inline 2 pass 2"
@@ -205,6 +200,8 @@ proc do_test { use_header } {
 	gdb_test "bt" "\\s*\\#0\\s+\[^\r\]*tree_check\[^\r\]*${hdrfile}:.*" \
 	    "in inline 2 pass 2"
 	gdb_test "step" ".*return x.*" "step 6"
+	gdb_test "bt" "\\s*\\#0\\s+\[^\r\]*tree_check\[^\r\]*${hdrfile}:.*" \
+	    "return from inline 2 pass 2"
 	gdb_test "step" ".*TREE_TYPE.*" "step 7"
 	gdb_test "bt" "\\s*\\#0\\s+get_alias_set\[^\r\]*${srcfile}:.*" \
 	    "not in inline 3 pass 2"
@@ -212,6 +209,8 @@ proc do_test { use_header } {
 	gdb_test "bt" "\\s*\\#0\\s+\[^\r\]*tree_check\[^\r\]*${hdrfile}:.*" \
 	    "in inline 3 pass 2"
 	gdb_test "step" "return x.*" "step 9"
+	gdb_test "bt" "\\s*\\#0\\s+\[^\r\]*tree_check\[^\r\]*${hdrfile}:.*" \
+	    "return from inline 3 pass 2"
 	gdb_test "step" "return 0.*" "step 10"
 	gdb_test "bt" \
 	    "\\s*\\#0\\s+(main|get_alias_set)\[^\r\]*${srcfile}:.*" \
@@ -255,6 +254,31 @@ proc do_test { use_header } {
 	gdb_test "bt" "#0\\s+\[^\r\n\]*tree_check\[^\r\n\]*${hdrfile}:.*" \
 	    "abort from inline 1 pass 3"
     }
+
+    clean_restart ${executable}
+
+    if ![runto_main] {
+	return
+    }
+
+    gdb_test "bt" "\\s*\\#0\\s+main.*" "in main pass 4"
+    gdb_test "skip tree_check" ".*" "skip tree_check pass 4"
+    gdb_test "step" ".*" "step into get_alias_set pass 4"
+    gdb_test "bt" "\\s*\\#0\\s+get_alias_set\[^\r\]*${srcfile}:.*" \
+	"in get_alias_set pass 4"
+    gdb_test "step" ".*TREE_TYPE.*" "step 1 pass 4"
+    gdb_test "bt" "\\s*\\#0\\s+get_alias_set\[^\r\]*${srcfile}:.*" \
+	"not in inline 1 pass 4"
+    gdb_test "step" ".*TREE_TYPE.*" "step 2 pass 4"
+    gdb_test "bt" "\\s*\\#0\\s+get_alias_set\[^\r\]*${srcfile}:.*" \
+	"not in inline 2 pass 4"
+    gdb_test "step" ".*TREE_TYPE.*" "step 3 pass 4"
+    gdb_test "bt" "\\s*\\#0\\s+get_alias_set\[^\r\]*${srcfile}:.*" \
+	"not in inline 3 pass 4"
+    gdb_test "step" "return 0.*" "step 4 pass 4"
+    gdb_test "bt" \
+	"\\s*\\#0\\s+(main|get_alias_set)\[^\r\]*${srcfile}:.*" \
+	"not in inline 4 pass 4"
     }
 }
 
diff --git a/gdb/testsuite/gdb.dwarf2/dw2-extend-inline-block.c b/gdb/testsuite/gdb.dwarf2/dw2-extend-inline-block.c
new file mode 100644
index 00000000000..d6becf5d66b
--- /dev/null
+++ b/gdb/testsuite/gdb.dwarf2/dw2-extend-inline-block.c
@@ -0,0 +1,78 @@
+/* This testcase is part of GDB, the GNU debugger.
+
+   Copyright 2025 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/>.  */
+
+volatile int global_var = 0;
+
+/* The follow code exists only to be referenced from the generated line
+   table.  */
+#if 0
+static inline void
+foo (void)
+{
+  /* foo:1 */
+  /* foo:2 */
+  /* foo:3 */
+}
+
+int
+main (void)
+{						/* main decl line */
+  /* main:1 */
+  /* main:2 */
+  /* main:3 */ foo ();				/* foo call line */
+  /* main:4 */
+  /* main:5 */
+  /* main:6 */
+}
+#endif
+
+
+int
+main (void)
+{
+  asm ("main_label: .globl main_label");
+  ++global_var;
+
+  asm ("main_0: .globl main_0");
+  ++global_var;
+
+  asm ("main_1: .globl main_1");
+  ++global_var;
+
+  asm ("main_2: .globl main_2");
+  ++global_var;
+
+  asm ("main_3: .globl main_3");
+  ++global_var;
+
+  asm ("main_4: .globl main_4");
+  ++global_var;
+
+  asm ("main_5: .globl main_5");
+  ++global_var;
+
+  asm ("main_6: .globl main_6");
+  ++global_var;
+
+  asm ("main_7: .globl main_7");
+  ++global_var;
+
+  asm ("main_8: .globl main_8");
+  ++global_var;
+
+  return 0;
+}
diff --git a/gdb/testsuite/gdb.dwarf2/dw2-extend-inline-block.exp b/gdb/testsuite/gdb.dwarf2/dw2-extend-inline-block.exp
new file mode 100644
index 00000000000..963410ce057
--- /dev/null
+++ b/gdb/testsuite/gdb.dwarf2/dw2-extend-inline-block.exp
@@ -0,0 +1,574 @@
+# Copyright 2025 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/>.
+
+# When compiling optimised code, GCC will sometimes truncate the address
+# range of an inline function, usually by a single instruction.
+#
+# It is possible to detect when this has happened by looking at the line
+# table, GCC will create two non-statement line table entries associated
+# with the call-line of the inline function, but the end address of the
+# inline function will be set to be the address of the first of these line
+# table entries.
+#
+# The problem here is that block end addresses are not inclusive, which
+# means the block ends before either of these line table entries.
+#
+# What we find is that we get a better debug experience if we extend the
+# inline function to actually end at the second line table entry, that is
+# the first line table entry becomes part of the inline function, while the
+# second entry remains outside the inline function.
+#
+# This test tries to create this situation using the DWARF assembler, and
+# then checks that GDB correctly extends the inline function to include the
+# first line table entry.
+
+load_lib dwarf.exp
+
+require dwarf2_support
+
+standard_testfile .c
+
+# Lines numbers we reference in the generated DWARF.
+set main_decl_line [gdb_get_line_number "main decl line"]
+set main_line_1 [gdb_get_line_number "main:1"]
+set main_line_4 [gdb_get_line_number "main:4"]
+set foo_call_line [gdb_get_line_number "foo call line"]
+set foo_line_1 [gdb_get_line_number "foo:1"]
+
+get_func_info main
+
+# Create DWARF for the test.  In this case, inline function 'foo' is created
+# with a contiguous address range that needs extending.
+
+proc build_dwarf_for_contiguous_block { asm_file } {
+    Dwarf::assemble $asm_file {
+	declare_labels lines_table inline_func
+
+	cu { } {
+	    compile_unit {
+		{producer "GNU C 14.1.0"}
+		{language @DW_LANG_C}
+		{name $::srcfile}
+		{comp_dir /tmp}
+		{low_pc 0 addr}
+		{DW_AT_stmt_list $lines_table DW_FORM_sec_offset}
+	    } {
+		inline_func: subprogram {
+		    {name foo}
+		    {inline @DW_INL_declared_inlined}
+		}
+		subprogram {
+		    {name main}
+		    {decl_file 1 data1}
+		    {decl_line $::main_decl_line data1}
+		    {decl_column 1 data1}
+		    {low_pc $::main_start addr}
+		    {high_pc $::main_len data4}
+		    {external 1 flag}
+		} {
+		    inlined_subroutine {
+			{abstract_origin %$inline_func}
+			{call_file 1 data1}
+			{call_line $::foo_call_line data1}
+			{low_pc main_1 addr}
+			{high_pc main_3 addr}
+		    }
+		}
+	    }
+	}
+
+	lines {version 2 default_is_stmt 1} lines_table {
+	    include_dir "$::srcdir/$::subdir"
+	    file_name "$::srcfile" 1
+
+	    program {
+		DW_LNE_set_address main
+		line $::main_line_1
+		DW_LNS_copy
+
+		DW_LNE_set_address main_0
+		DW_LNS_advance_line 1
+		DW_LNS_copy
+
+		DW_LNE_set_address main_1
+		line $::foo_line_1
+		DW_LNS_copy
+
+		DW_LNE_set_address main_2
+		DW_LNS_advance_line 1
+		DW_LNS_copy
+
+		DW_LNE_set_address main_3
+		DW_LNS_advance_line 1
+		DW_LNS_copy
+
+		DW_LNE_set_address main_3
+		line $::foo_call_line
+		DW_LNS_negate_stmt
+		DW_LNS_copy
+
+		DW_LNE_set_address main_4
+		DW_LNS_copy
+
+		DW_LNE_set_address main_5
+		DW_LNS_advance_line 1
+		DW_LNS_negate_stmt
+		DW_LNS_copy
+
+		DW_LNE_set_address main_6
+		DW_LNS_advance_line 1
+		DW_LNS_copy
+
+		DW_LNE_set_address main_7
+		DW_LNS_advance_line 1
+		DW_LNS_copy
+
+		DW_LNE_set_address main_8
+		DW_LNS_advance_line 1
+		DW_LNS_copy
+
+		DW_LNE_set_address "$::main_start + $::main_len"
+		DW_LNE_end_sequence
+	    }
+	}
+    }
+}
+
+# Assuming GDB is stopped at the entry $pc for 'foo', use 'maint info
+# blocks' to check the block for 'foo' is correct.  This function checks
+# 'foo' created by 'build_dwarf_for_contiguous_block'.
+
+proc check_contiguous_block {} {
+    set foo_start [get_hexadecimal_valueof "&main_1" "*UNKNOWN*" \
+		       "get address of foo start"]
+    set foo_end [get_hexadecimal_valueof "&main_4" "*UNKNOWN*" \
+		     "get address of foo end"]
+
+    gdb_test "maintenance info blocks" \
+	[multi_line \
+	     "\\\[\\(block \\*\\) $::hex\\\] $foo_start\\.\\.$foo_end" \
+	     "  entry pc: $foo_start" \
+	     "  inline function: foo" \
+	     "  symbol count: $::decimal" \
+	     "  is contiguous"] \
+	"block for foo has expected content"
+}
+
+# Create DWARF for the test.  In this case, inline function 'foo' is created
+# with two ranges, and it is the first range that needs extending.
+
+proc build_dwarf_for_first_block_range { asm_file dwarf_version } {
+    Dwarf::assemble $asm_file {
+	upvar dwarf_version dwarf_version
+	declare_labels lines_table inline_func ranges_label
+
+	cu { version $dwarf_version } {
+	    compile_unit {
+		{producer "GNU C 14.1.0"}
+		{language @DW_LANG_C}
+		{name $::srcfile}
+		{comp_dir /tmp}
+		{low_pc 0 addr}
+		{DW_AT_stmt_list $lines_table DW_FORM_sec_offset}
+	    } {
+		inline_func: subprogram {
+		    {name foo}
+		    {inline @DW_INL_declared_inlined}
+		}
+		subprogram {
+		    {name main}
+		    {decl_file 1 data1}
+		    {decl_line $::main_decl_line data1}
+		    {decl_column 1 data1}
+		    {low_pc $::main_start addr}
+		    {high_pc $::main_len data4}
+		    {external 1 flag}
+		} {
+		    inlined_subroutine {
+			{abstract_origin %$inline_func}
+			{call_file 1 data1}
+			{call_line $::foo_call_line data1}
+			{ranges $ranges_label DW_FORM_sec_offset}
+		    }
+		}
+	    }
+	}
+
+	lines {version 2 default_is_stmt 1} lines_table {
+	    include_dir "$::srcdir/$::subdir"
+	    file_name "$::srcfile" 1
+
+	    program {
+		DW_LNE_set_address main
+		line $::main_line_1
+		DW_LNS_copy
+
+		DW_LNE_set_address main_0
+		DW_LNS_advance_line 1
+		DW_LNS_copy
+
+		DW_LNE_set_address main_1
+		line $::foo_line_1
+		DW_LNS_copy
+
+		DW_LNE_set_address main_2
+		DW_LNS_advance_line 1
+		DW_LNS_copy
+
+		DW_LNE_set_address main_3
+		DW_LNS_advance_line 1
+		DW_LNS_copy
+
+		DW_LNE_set_address main_3
+		line $::foo_call_line
+		DW_LNS_negate_stmt
+		DW_LNS_copy
+
+		DW_LNE_set_address main_4
+		DW_LNS_copy
+
+		DW_LNE_set_address main_5
+		DW_LNS_advance_line 1
+		DW_LNS_negate_stmt
+		DW_LNS_copy
+
+		DW_LNE_set_address main_6
+		DW_LNS_advance_line 1
+		DW_LNS_copy
+
+		DW_LNE_set_address main_7
+		DW_LNS_advance_line 1
+		DW_LNS_copy
+
+		DW_LNE_set_address main_8
+		DW_LNS_advance_line 1
+		DW_LNS_copy
+
+		DW_LNE_set_address "$::main_start + $::main_len"
+		DW_LNE_end_sequence
+	    }
+	}
+
+	if { $dwarf_version == 5 } {
+	    rnglists {} {
+		table {} {
+		    ranges_label: list_ {
+			start_end main_1 main_3
+			start_end main_7 main_8
+		    }
+		}
+	    }
+	} else {
+	    ranges { } {
+		ranges_label: sequence {
+		    range main_1 main_3
+		    range main_7 main_8
+		}
+	    }
+	}
+    }
+}
+
+# Wrapper around 'build_dwarf_for_first_block_range', creates DWARF 4 range
+# information.
+
+proc build_dwarf_for_first_block_range_4 { asm_file } {
+    build_dwarf_for_first_block_range $asm_file 4
+}
+
+# Wrapper around 'build_dwarf_for_first_block_range', creates DWARF 5 range
+# information.
+
+proc build_dwarf_for_first_block_range_5 { asm_file } {
+    build_dwarf_for_first_block_range $asm_file 5
+}
+
+# Assuming GDB is stopped at the entry $pc for 'foo', use 'maint info
+# blocks' to check the block for 'foo' is correct.  This function checks
+# 'foo' created by 'build_dwarf_for_first_block_range'.
+
+proc check_for_block_ranges_1 {} {
+
+    set foo_start [get_hexadecimal_valueof "&main_1" "*UNKNOWN*" \
+		       "get address of foo start"]
+    set foo_end [get_hexadecimal_valueof "&main_8" "*UNKNOWN*" \
+		     "get address of foo end"]
+
+    set main_4 [get_hexadecimal_valueof "&main_4" "*UNKNOWN*" \
+		     "get address of main_4 label"]
+    set main_7 [get_hexadecimal_valueof "&main_7" "*UNKNOWN*" \
+		     "get address of main_7 label"]
+
+    gdb_test "maintenance info blocks" \
+	[multi_line \
+	     "\\\[\\(block \\*\\) $::hex\\\] $foo_start\\.\\.$foo_end" \
+	     "  entry pc: $foo_start" \
+	     "  inline function: foo" \
+	     "  symbol count: $::decimal" \
+	     "  address ranges:" \
+	     "    $foo_start\\.\\.$main_4" \
+	     "    $main_7\\.\\.$foo_end"] \
+	"block for foo has expected content"
+}
+
+# Create DWARF for the test.  In this case, inline function 'foo' is created
+# with two ranges, and it is the second range that needs extending.
+
+proc build_dwarf_for_last_block_range { asm_file dwarf_version } {
+    Dwarf::assemble $asm_file {
+	upvar dwarf_version dwarf_version
+	declare_labels lines_table inline_func ranges_label
+
+	cu { version $dwarf_version } {
+	    compile_unit {
+		{producer "GNU C 14.1.0"}
+		{language @DW_LANG_C}
+		{name $::srcfile}
+		{comp_dir /tmp}
+		{low_pc 0 addr}
+		{DW_AT_stmt_list $lines_table DW_FORM_sec_offset}
+	    } {
+		inline_func: subprogram {
+		    {name foo}
+		    {inline @DW_INL_declared_inlined}
+		}
+		subprogram {
+		    {name main}
+		    {decl_file 1 data1}
+		    {decl_line $::main_decl_line data1}
+		    {decl_column 1 data1}
+		    {low_pc $::main_start addr}
+		    {high_pc $::main_len data4}
+		    {external 1 flag}
+		} {
+		    inlined_subroutine {
+			{abstract_origin %$inline_func}
+			{call_file 1 data1}
+			{call_line $::foo_call_line data1}
+			{ranges $ranges_label DW_FORM_sec_offset}
+			{entry_pc main_1 addr}
+		    }
+		}
+	    }
+	}
+
+	lines {version 2 default_is_stmt 1} lines_table {
+	    include_dir "$::srcdir/$::subdir"
+	    file_name "$::srcfile" 1
+
+	    program {
+		DW_LNE_set_address main
+		line $::main_line_1
+		DW_LNS_copy
+
+		DW_LNE_set_address main_0
+		DW_LNS_advance_line 1
+		DW_LNS_copy
+
+		DW_LNE_set_address main_1
+		line $::foo_line_1
+		DW_LNS_copy
+
+		DW_LNE_set_address main_2
+		DW_LNS_advance_line 1
+		DW_LNS_copy
+
+		DW_LNE_set_address main_3
+		DW_LNS_advance_line 1
+		DW_LNS_copy
+
+		DW_LNE_set_address main_3
+		line $::foo_call_line
+		DW_LNS_negate_stmt
+		DW_LNS_copy
+
+		DW_LNE_set_address main_4
+		DW_LNS_copy
+
+		DW_LNE_set_address main_5
+		DW_LNS_advance_line 1
+		DW_LNS_negate_stmt
+		DW_LNS_copy
+
+		DW_LNE_set_address main_6
+		DW_LNS_advance_line 1
+		DW_LNS_copy
+
+		DW_LNE_set_address main_7
+		DW_LNS_advance_line 1
+		DW_LNS_copy
+
+		DW_LNE_set_address main_8
+		DW_LNS_advance_line 1
+		DW_LNS_copy
+
+		DW_LNE_set_address "$::main_start + $::main_len"
+		DW_LNE_end_sequence
+	    }
+	}
+
+	if { $dwarf_version == 5 } {
+	    rnglists {} {
+		table {} {
+		    ranges_label: list_ {
+			start_end main_7 main_8
+			start_end main_1 main_3
+		    }
+		}
+	    }
+	} else {
+	    ranges { } {
+		ranges_label: sequence {
+		    range main_7 main_8
+		    range main_1 main_3
+		}
+	    }
+	}
+    }
+}
+
+# Wrapper around 'build_dwarf_for_last_block_range', creates DWARF 4 range
+# information.
+
+proc build_dwarf_for_last_block_range_4 { asm_file } {
+    build_dwarf_for_last_block_range $asm_file 4
+}
+
+# Wrapper around 'build_dwarf_for_last_block_range', creates DWARF 5 range
+# information.
+
+proc build_dwarf_for_last_block_range_5 { asm_file } {
+    build_dwarf_for_last_block_range $asm_file 5
+}
+
+# Assuming GDB is stopped at the entry $pc for 'foo', use 'maint info
+# blocks' to check the block for 'foo' is correct.  This function checks
+# 'foo' created by 'build_dwarf_for_last_block_range'.
+
+proc check_for_block_ranges_2 {} {
+
+    set foo_start [get_hexadecimal_valueof "&main_1" "*UNKNOWN*" \
+		       "get address of foo start"]
+    set foo_end [get_hexadecimal_valueof "&main_8" "*UNKNOWN*" \
+		     "get address of foo end"]
+
+    set main_4 [get_hexadecimal_valueof "&main_4" "*UNKNOWN*" \
+		     "get address of main_4 label"]
+    set main_7 [get_hexadecimal_valueof "&main_7" "*UNKNOWN*" \
+		     "get address of main_7 label"]
+
+    gdb_test "maintenance info blocks" \
+	[multi_line \
+	     "\\\[\\(block \\*\\) $::hex\\\] $foo_start\\.\\.$foo_end" \
+	     "  entry pc: $foo_start" \
+	     "  inline function: foo" \
+	     "  symbol count: $::decimal" \
+	     "  address ranges:" \
+	     "    $main_7\\.\\.$foo_end" \
+	     "    $foo_start\\.\\.$main_4"] \
+	"block for foo has expected content"
+}
+
+# Buidl ASM_FILE, along with the global SRCFILE into an executable called
+# TESTFILE.  Place a breakpoint in 'foo', run to the breakpoint, and use
+# BLOCK_CHECK_FUNC to ensure the block for 'foo' is correct.
+#
+# Then step through 'foo' and back into 'main'.
+
+proc run_test { asm_file testfile block_check_func } {
+    if {[prepare_for_testing "failed to prepare" $testfile \
+	     [list $::srcfile $asm_file] {nodebug}]} {
+	return
+    }
+
+    if {![runto_main]} {
+	return
+    }
+
+    gdb_breakpoint foo
+    gdb_test "continue" \
+	[multi_line \
+	     "Breakpoint $::decimal, foo \\(\\) \[^\r\n\]+:$::foo_line_1" \
+	     "$::foo_line_1\\s+/\\* foo:1 \\*/"] \
+	"continue to b/p in foo"
+
+    # Check that the block for `foo` has been extended.
+    $block_check_func
+
+    gdb_test "frame 1" \
+	[multi_line \
+	     "#1  main \\(\\) at \[^\r\n\]+/$::srcfile:$::foo_call_line" \
+	     "$::foo_call_line\\s+\[^\r\n\]+/\\* foo call line \\*/"] \
+	"frame 1 is for main"
+
+    gdb_test "step" \
+	"^[expr $::foo_line_1 + 1]\\s+/\\* foo:2 \\*/" \
+	"step to second line of foo"
+
+    gdb_test "step" \
+	"^[expr $::foo_line_1 + 2]\\s+/\\* foo:3 \\*/" \
+	"step to third line of foo"
+
+    gdb_test "step" \
+	[multi_line \
+	     "^main \\(\\) at \[^\r\n\]+:$::main_line_4" \
+	     "$::main_line_4\\s+/\\* main:4 \\*/"] \
+	"set back to main"
+
+    gdb_test "step" \
+	"^[expr $::main_line_4 + 1]\\s+/\\* main:5 \\*/" \
+	"step again in main"
+}
+
+# Test specifications, items are:
+# 1. Prefix string used to describe the test.
+# 2. Proc to call that builds the DWARF.
+# 3. Proc to call that runs 'maint info blocks' when stopped at the entry
+#    $pc for 'foo' (the inline function), and checks that the block details
+#    for 'foo' are correct.
+set test_list \
+    [list \
+	 [list "block with ranges, extend first range, dwarf 4" \
+	      build_dwarf_for_first_block_range_4 \
+	      check_for_block_ranges_1] \
+	 [list "block with ranges, extend first range, dwarf 5" \
+	      build_dwarf_for_first_block_range_5 \
+	      check_for_block_ranges_1] \
+	 [list "block with ranges, extend last range, dwarf 4" \
+	      build_dwarf_for_last_block_range_4 \
+	      check_for_block_ranges_2] \
+	 [list "block with ranges, extend last range, dwarf 5" \
+	      build_dwarf_for_last_block_range_4 \
+	      check_for_block_ranges_2] \
+	 [list "contiguous block" \
+	      build_dwarf_for_contiguous_block \
+	      check_contiguous_block] \
+	]
+
+# Run all the tests.
+set suffix 0
+foreach test_spec $test_list {
+    incr suffix
+
+    set prefix [lindex $test_spec 0]
+    set build_dwarf_func [lindex $test_spec 1]
+    set check_block_func [lindex $test_spec 2]
+
+    with_test_prefix $prefix {
+	set asm_file [standard_output_file ${testfile}-${suffix}.S]
+	$build_dwarf_func $asm_file
+	run_test $asm_file ${testfile}-${suffix} $check_block_func
+    }
+}
-- 
2.47.1


  parent reply	other threads:[~2025-07-20 10:26 UTC|newest]

Thread overview: 37+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2025-07-20 10:20 [PATCH 0/7] Inline Function Optimised Code Debug Improvements Andrew Burgess
2025-07-20 10:20 ` [PATCH 1/7] gdb: improve line number lookup around inline functions Andrew Burgess
2025-07-20 10:20 ` [PATCH 2/7] gdb: handle empty ranges for inline subroutines Andrew Burgess
2025-07-20 10:20 ` [PATCH 3/7] gdb: split dwarf line table parsing in two Andrew Burgess
2025-07-20 10:20 ` [PATCH 4/7] gdb: move block range recording into its own function Andrew Burgess
2025-07-20 10:20 ` [PATCH 5/7] gdb: create address map after parsing all DIE Andrew Burgess
2025-07-20 10:20 ` [PATCH 6/7] gdb: record block end addresses while parsing DIEs Andrew Burgess
2025-07-20 10:20 ` Andrew Burgess [this message]
2025-08-01  8:58 ` [PATCHv2 0/7] Inline Function Optimised Code Debug Improvements Andrew Burgess
2025-08-01  8:58   ` [PATCHv2 1/7] gdb: improve line number lookup around inline functions Andrew Burgess
2025-08-01  8:58   ` [PATCHv2 2/7] gdb: handle empty ranges for inline subroutines Andrew Burgess
2025-08-01  8:58   ` [PATCHv2 3/7] gdb: split dwarf line table parsing in two Andrew Burgess
2025-08-01  8:58   ` [PATCHv2 4/7] gdb: move block range recording into its own function Andrew Burgess
2025-08-01  8:58   ` [PATCHv2 5/7] gdb: create address map after parsing all DIE Andrew Burgess
2025-08-01  8:58   ` [PATCHv2 6/7] gdb: record block end addresses while parsing DIEs Andrew Burgess
2025-08-01  8:58   ` [PATCHv2 7/7] gdb: fix-up truncated inline function block ranges Andrew Burgess
2025-10-16 17:49   ` [PATCHv3 0/7] Inline Function Optimised Code Debug Improvements Andrew Burgess
2025-10-16 17:49     ` [PATCHv3 1/7] gdb: improve line number lookup around inline functions Andrew Burgess
2025-10-27 22:22       ` Tom Tromey
2025-12-17 14:32         ` Andrew Burgess
2025-12-17 14:48           ` Tom de Vries
2025-12-18 14:46             ` Andrew Burgess
2025-10-16 17:49     ` [PATCHv3 2/7] gdb: handle empty ranges for inline subroutines Andrew Burgess
2025-10-16 17:49     ` [PATCHv3 3/7] gdb: split dwarf line table parsing in two Andrew Burgess
2025-10-16 17:49     ` [PATCHv3 4/7] gdb: move block range recording into its own function Andrew Burgess
2025-10-27 22:45       ` Tom Tromey
2025-10-16 17:49     ` [PATCHv3 5/7] gdb: create address map after parsing all DIE Andrew Burgess
2025-10-27 22:56       ` Tom Tromey
2026-01-02 16:36         ` Andrew Burgess
2026-01-05 20:03           ` Tom Tromey
2026-01-05 21:37             ` Andrew Burgess
2026-01-06  0:53               ` Tom Tromey
2025-10-16 17:49     ` [PATCHv3 6/7] gdb: record block end addresses while parsing DIEs Andrew Burgess
2025-10-27 23:00       ` Tom Tromey
2025-10-16 17:49     ` [PATCHv3 7/7] gdb: fix-up truncated inline function block ranges Andrew Burgess
2026-02-04 10:43     ` [PATCHv3 0/7] Inline Function Optimised Code Debug Improvements Andrew Burgess
2025-08-01 15:41 ` [PATCH " Sam James

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=a8712b4642d015db3df759f8bdec71ba4c867d6d.1753006060.git.aburgess@redhat.com \
    --to=aburgess@redhat.com \
    --cc=bernd.edlinger@hotmail.de \
    --cc=gdb-patches@sourceware.org \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox