Compare commits

...

5 Commits

Author SHA1 Message Date
Bernd Edlinger
cc9be6af06 [wip] Fix range end handling of inlined subroutines
Currently there is a problem when debugging
optimized code when the inferior stops at an inline
sub-range end PC.  It is unclear if that location
is from the inline function or from the calling
function.  Therefore the call stack is often
wrong.

This patch detects the "weak" line table entries
which are likely part of the previous inline block,
and if we have such a location, it assumes the
location belongs to the previous block.

Additionally it may happen that the infrun machinery
steps from one inline range to another inline range
of the same inline function.  That can look like
jumping back and forth from the calling program
to the inline function, while really the inline
function just jumps from a hot to a cold section
of the code, i.e. error handling.

Additionally it may happen that one inline sub-range
is empty or the inline is completely empty.  But
filtering that information away is not the right
solution, since although there is no actual code
from the inline, it is still possible that variables
from an inline function can be inspected here.

The issue with the empty ranges is also discussed here:
https://gcc.gnu.org/bugzilla/show_bug.cgi?id=94474

Conceptually this patch uses a heuristic to work around
a deficiency in the dwarf-4 and dwarf-5 rnglists structure.
There should be a location view number for each inline
sub-range begin PC and end PC, similar to the DW_AT_GNU_entry_view
which is the location view for the inline entry point.
2024-12-04 14:03:44 +00:00
Bernd Edlinger
215ba4f398 [wip] Introduce a new line table flag is_weak
This introduces a new line table flag is_weak.
The line entries at the end of a subroutine range,
use this to indicate that they may possibly
be part of the previous subroutine.

When there is a sequence of line entries at the
same address where an inline range ends, and the
last item has is_stmt = 0, we force all previous
items to have is_weak = 1.

Additionally this adds a "fake" end sequence to the
record_line function, that is line number -1.
That will be used in the next patch.

Finally this adds a handling for empty ranges to
record_block_range.  Currently this function is
not called with empty ranges, but that will be used
in the next patch.

There should be no functional changes after this commit.

Well, except the new is-weak flag in the line table of course.
As an example consider the following test code:

$ cat test.c
inline int test ()
{
  asm ("nop");
  return 0;
}

int main ()
{
  int x = test ();
  return x;
}
$ gcc -g -O2 test.c

This will receive the following line table:

(gdb) maintenance info line-table
INDEX  LINE   REL-ADDRESS        UNREL-ADDRESS      IS-STMT IS-WEAK PROLOGUE-END EPILOGUE-BEGIN
0      8      0x0000555555555040 0x0000000000001040 Y
1      9      0x0000555555555040 0x0000000000001040 Y
2      1      0x0000555555555040 0x0000000000001040 Y
3      3      0x0000555555555040 0x0000000000001040 Y
4      4      0x0000555555555041 0x0000000000001041 Y       Y
5      4      0x0000555555555041 0x0000000000001041         Y  <---+ set is_weak
6      10     0x0000555555555041 0x0000000000001041 Y       Y      ^
7      11     0x0000555555555041 0x0000000000001041           <----+ no is-stmt
8      END    0x0000555555555044 0x0000000000001044 Y
2024-12-04 14:03:44 +00:00
Andrew Burgess
94a1302434 [wip] gdb/testsuite: all the optimised code test cases
All the test cases pulled from this series:

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

Rebased on top of current HEAD.  Some of these tests are probably
duplicates of tests that have been upstreamed into other locations,
e.g. see recent additions to gdb.opt/

Also, I moved these tests into their own commit to make it easier for
me to move them between branches for testing, but when it comes to
upstreaming, these tests should be merged into the appropriate GDB
related commit, and not left as a commit on their own.
2024-12-04 14:03:44 +00:00
Andrew Burgess
8a2fb168c9 gdb: improve line number lookup around inline functions
This commit aims to fix an issue where GDB would report the wrong line
for frames other than #0 if a previous frame had just left an inline
function.

Consider this example which is compiled at -Og:

  volatile int global = 0;

  static inline int bar (void) { asm (""); return 1; }

  static void foo (int count)
  { global += count; }

  int main (void)
  {
    foo (bar ());
    return 0;
  }

Used in this GDB session:

  (gdb) break foo
  Breakpoint 1 at 0x401106: file test.c, line 6.
  (gdb) run
  Starting program: /tmp/inline-bt/test.x

  Breakpoint 1, foo (count=count@entry=1) at test.c:6
  6	{ global += count; }
  (gdb) frame 1
  #1  0x0000000000401121 in main () at test.c:3
  3	static inline int bar (void) { asm (""); return 1; }

Notice that GDB incorrectly reports frame #1 as being at line 3 when
it should really be reporting this line:

  foo (bar ());

The cause of this problem is in find_pc_sect_line (symtab.c).  This
function is passed a PC for which GDB must find the symtab_and_line
information.  The function can be called in two modes based on the
NOTCURRENT argument.

When NOTCURRENT is false then we are looking for information about the
current PC, i.e. the PC at which the inferior is currently stopped
at.

When NOTCURRENT is true we are looking for information about a PC that
it not the current PC, but is instead the PC for a previous frame.
The interesting thing in this case is that the PC passed in will be
the address after the address we actually want to lookup information
for, this is because as we unwind the program counter from frame #0
what we get is the return address in frame #1.  The return address is
often (or sometimes) on the line after the calling line, and so in
find_pc_sect_line, when NOTCURRENT is true, we subtract 1 from PC and
then proceed as normal looking for information about this new PC
value.

Now lets look at the x86-64 disassembly for 'main' from the above
example.  The location marker (=>) represents the return address in
'main' after calling 'foo':

  (gdb) run
  Starting program: /tmp/inline-bt/test.x

  Breakpoint 1, foo (count=count@entry=1) at test.c:6
  6	{ global += count; }
  #0  foo (count=count@entry=1) at test.c:6
  #1  0x000000000040111f in main () at test.c:3
  (gdb) up
  #1  0x000000000040111f in main () at test.c:3
  3	static inline int bar (void) { asm (""); return 1; }
  (gdb) disassemble
  Dump of assembler code for function main:
     0x0000000000401115 <+0>:	mov    $0x1,%edi
     0x000000000040111a <+5>:	call   0x401106 <foo>
  => 0x000000000040111f <+10>:	mov    $0x0,%eax
     0x0000000000401124 <+15>:	ret
  End of assembler dump.

And the corresponding line table:

  (gdb) maintenance info line-table
  objfile: /tmp/inline-bt/test.x ((struct objfile *) 0x59405a0)
  compunit_symtab: test.c ((struct compunit_symtab *) 0x53ad320)
  symtab: /tmp/inline-bt/test.c ((struct symtab *) 0x53ad3a0)
  linetable: ((struct linetable *) 0x53adc90):
  INDEX  LINE   REL-ADDRESS        UNREL-ADDRESS      IS-STMT PROLOGUE-END EPILOGUE-BEGIN
  0      6      0x0000000000401106 0x0000000000401106 Y
  1      6      0x0000000000401106 0x0000000000401106 Y
  2      6      0x0000000000401106 0x0000000000401106
  3      6      0x0000000000401114 0x0000000000401114
  4      9      0x0000000000401115 0x0000000000401115 Y
  5      10     0x0000000000401115 0x0000000000401115 Y
  6      3      0x0000000000401115 0x0000000000401115 Y
  7      3      0x0000000000401115 0x0000000000401115 Y
  8      3      0x0000000000401115 0x0000000000401115 Y
  9      10     0x0000000000401115 0x0000000000401115
  10     11     0x000000000040111f 0x000000000040111f Y
  11     12     0x000000000040111f 0x000000000040111f
  12     END    0x0000000000401125 0x0000000000401125 Y

When looking for the line information of frame #1 we start with the
return address 0x40111f, however, as this is not the current program
counter value we subtract one and look for line information for
0x40111e.

We will find the entry at index 9, this is the last entry with an
address less than the address we're looking for, the next entry has an
address greater than the one we're looking for.  The entry at index 9
is for line 10 which is the correct line, but GDB reports line 3, so
what's going on?

Having found a matching entry GDB checks to see if the entry is marked
as is-stmt (is statement).  In our case index 9 (line 10) is not a
statement, and so GDB looks backwards for entries at the same address,
if any of these are marked is-stmt then GDB will use the last of these
instead.  In our case the previous entry at index 8 is marked is-stmt,
and so GDB uses that.  The entry at index 8 is for line 3, and that is
why GDB reports the wrong line.  So why perform the backward is-stmt
check?

When NOTCURRENT is false (not our case) the backward scan makes
sense.  If the inferior has just stopped at some new location, and we
want to report that location to the user, then it is better (I think)
to select an is-stmt entry.  In this way we will report a line number
for a line which the inferior is just about to start executing, and
non of the side effects of that line have yet taken place.  The line
GDB prints will correspond with the reported line, and if the user
queries the inferior state, the inferior should (assuming the compiler
emitted correct is-stmt markers) correspond to the line in question
having not yet been started.

However, in our case NOTCURRENT is true.  We're looking back to
previous frames that are currently in-progress.  If upon return to the
previous frame we are about to execute the next line then (is seems to
me) that this indicates we must be performing the very last action
from the previous line.  As such, looking back through the line table
in order to report a line that has not yet started is the wrong thing
to do.  We really want to report the very last line table entry for
the previous address as this is (I think) most likely to represent the
previous line that is just about to complete.

Further, in the NOTCURRENT case, we should care less about reporting
an is-stmt line.  When a user looks back to a previous frame I don't
think they expect the line being reported to have not yet started.  In
fact I think the expectation is the reverse ... after all, the
previous line must have executed enough to call the current frame.

So my proposal is that the backward scan of the line table looking for
an is-stmt entry should not be performed when NOTCURRENT is true.  In
the case above this means we will report the entry at index 9, which
is for line 10, which is correct.

For testing this commit I have:

 1. Extended the existing gdb.opt/inline-bt.exp test.  I've extended
 the source code to include a test similar to the example above.  I
 have also extended the script so that the test is compiled at a
 variety of optimisation levels (O0, Og, O1, O2).

 2. Added a new DWARF assembler test which hard codes a line table
 similar to the example given above.  My hope is that even if test
 case (1) changes (due to compiler changes) this test will continue to
 test the specific case I'm interested in.

I have tested the gdb.opt/inline-bt.exp test with gcc versions 8.4.0,
9.3.1, 10.5.0, 11.5.0, 12.2.0, and 14.2.0, in each case the test will
fail (with the expected error) without this patch applied, and will
pass with this patch applied.

I was inspired to write this patch while reviewing these patches:

  https://inbox.sourceware.org/gdb-patches/AS8P193MB1285C58F6F09502252CEC16FE4DF2@AS8P193MB1285.EURP193.PROD.OUTLOOK.COM
  https://inbox.sourceware.org/gdb-patches/AS8P193MB12855708DFF59A5309F5B19EE4DF2@AS8P193MB1285.EURP193.PROD.OUTLOOK.COM

though this patch only covers one of the issues addressed by these
patches, and the approach taken is quite different.  Still, those
patches are worth reading for the history of this fix.

Bug: https://sourceware.org/bugzilla/show_bug.cgi?id=25987
2024-12-04 14:03:25 +00:00
Andrew Burgess
bade3fecaf gdb: handle empty ranges for inline subroutines
The work in this patch is based on changes found in this series:

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

That series has the fixes here merged along with other changes, and
takes a different approach for how to handle the issue addressed here.

Credit for identifying the original issue belongs with Bernd, the
author of the original patch, who I have included as a co-author on
this patch.  A brief description of how the approach taken in this
patch differs from the approach Bernd took can be found at the end of
this commit message.

When compiling with optimisation, it can often happen that gcc will
emit an inline function instance with an empty range associated.  This
can happen in two ways.  The inline function might have a DW_AT_low_pc
and DW_AT_high_pc, where the high-pc is an offset from the low-pc, but
the high-pc offset is given as 0 by gcc.

Alternatively, the inline function might have a DW_AT_ranges, and one
of the sub-ranges might be empty, though usually in this case, other
ranges will be non-empty.

The second case is made worse in that sometimes gcc will specify a
DW_AT_entry_pc value which points to the address of the empty
sub-range.

My understanding of the DWARF spec is that empty ranges as seen in
these examples indicate that no instructions are associated with the
inline function, and indeed, this is how GDB handles these cases,
rejecting blocks and sub-ranges which are empty.

  DWARF-5, 2.17.2, Contiguous Address Range:
    The value of the DW_AT_low_pc attribute is the address of the
    first instruction associated with the entity. If the value of the
    DW_AT_high_pc is of class address, it is the address of the first
    location past the last instruction associated with the entity...

  DWARF-5, 2.17.3, Non-Contiguous Address Ranges:
    A bounded range entry whose beginning and ending address offsets
    are equal (including zero) indicates an empty range and may be
    ignored.

As a consequence, an attempt by the user to place a breakpoint on an
inline function with an empty low/high address range will trigger
GDB's pending breakpoint message:

  (gdb) b foo
  Function "foo" not defined.
  Make breakpoint pending on future shared library load? (y or [n]) n

While, having the entry-pc point at an empty range forces GDB to
ignore the given entry-pc and select a suitable alternative.

If instead of ignoring these empty ranges, we instead teach GDB to
treat these as non-empty, what we find is that, in all the cases I've
seen, the debug experience is improved.

As a minimum, in the low/high case, GDB now knows about the inline
function, and can place breakpoints that will be hit.  Further, in
most cases, local variables from the inline function can be accessed.

If we do start treating empty address ranges as non-empty then we are
deviating from the DWARF spec.  It is not clear if we are working
around a gcc bug (I suspect so), or if gcc actually considers the
inline function gone, and we're just getting lucky that the debug
experience seems improved.

My proposed strategy for handling these empty address ranges is to
only perform this work around if the compiler is gcc, so far I've not
seen this issue with Clang (the only other compiler I've tested),
though extending this to other compilers in the future would be
trivial.

Additionally, I only apply the work around for
DW_TAG_inlined_subroutine DIEs, as I've only seen the issue for
inline functions.

If we find a suitable empty address range then the fix-up is to give
the address range a length of 1 byte.

Now clearly, in most cases, 1 byte isn't even going to cover a single
instruction, but so far this doesn't seem to be a problem. An
alternative to using a 1-byte range would be to try and disassemble
the code at the given address, calculate the instruction length, and
use that, the length of one instruction.  But this means that the
DWARF parser now needs to make use of the disassembler, which feels
like a big change that I'd rather avoid if possible.

The other alternative is to allow blocks to be created with zero
length address ranges and then change the rest of GDB to allow for
lookup of zero sized blocks to succeed.  This is the approach taken by
the original patch series that I linked above.

The results achieved by the original patch are impressive, and Bernd,
the original patch author, makes a good argument that at least some of
the problems relating to empty ranges are a result of deficiencies in
the DWARF specification rather than issues with gcc.

However, I remain unconvinced.  But even if I accept that the issue is
with DWARF itself rather than gcc, the question still remains; should
we fix the problem by synthesising new DWARF attributes and/or accept
non-standard DWARF during the dwarf2/read.c phase, and then update GDB
to handle the new reality, or should we modify the incoming DWARF as
we read it to make it fit GDB's existing algorithms.

The original patch, I believe, took the former approach, while I
favour the later, and so, for now, I propose that the single byte
range proposal is good enough, at least until we find counter examples
where this doesn't work.

This leaves just one question: what about the remaining work in the
original patch.  That work deals with problems around the end address
of non-empty ranges.  The original patch handled that case using the
same algorithm changes, which is neat, but I think there are
alternative solutions that should be investigated.  If the
alternatives don't end up working out, then it's trivial to revert
this patch in the future and adopt the original proposal.

For testing I have two approaches, C/C++ test compiled with
optimisation that show the problems discussed.  These are good because
they show that these issues do crop up in compiled code.  But they are
bad in that the next compiler version might change the way the test is
optimised such that the problem no longer shows.

And so I've backed up the real code tests with DWARF assembler tests
which reproduce each issue.

The DWARF assembler tests are not really impacted by which gcc version
is used, but I've run all of these tests using gcc versions 8.4.0,
9.5.0, 10.5.0, 11.5.0, 12.2.0, and 14.2.0.  I see failures in all of
the new tests when using an unpatched GDB, and no failures when using
a patched GDB.

Bug: https://sourceware.org/bugzilla/show_bug.cgi?id=25987
Co-Authored-By: Bernd Edlinger <bernd.edlinger@hotmail.de>
2024-12-04 14:03:25 +00:00
33 changed files with 1823 additions and 271 deletions

View File

@@ -196,7 +196,20 @@ blockvector_for_pc_sect (CORE_ADDR pc, struct obj_section *section,
return NULL;
if (pblock)
*pblock = b;
{
struct symtab_and_line sal = find_pc_sect_line (pc, section, 0);
if (sal.line != 0 && sal.pc == pc && sal.is_weak)
{
const struct block *b2 = find_block_in_blockvector (bl, pc - 1);
const struct block *b0 = b;
while (b0->superblock () && !b0->function ())
b0 = b0->superblock ();
if (b0->contains (b2))
b = b2;
}
*pblock = b;
}
return bl;
}

View File

@@ -413,6 +413,16 @@ buildsym_compunit::record_block_range (struct block *block,
|| end_inclusive + 1 != block->end ())
m_pending_addrmap_interesting = true;
if (block->inlined_p ())
{
m_inline_end_vector.push_back (end_inclusive + 1);
if (end_inclusive + 1 == start)
{
end_inclusive = start;
m_pending_addrmap_interesting = true;
}
}
m_pending_addrmap.set_empty (start, end_inclusive, block);
}
@@ -627,19 +637,16 @@ buildsym_compunit::record_line (struct subfile *subfile, int line,
{
m_have_line_numbers = true;
/* Normally, we treat lines as unsorted. But the end of sequence
marker is special. We sort line markers at the same PC by line
number, so end of sequence markers (which have line == 0) appear
first. This is right if the marker ends the previous function,
and there is no padding before the next function. But it is
wrong if the previous line was empty and we are now marking a
switch to a different subfile. We must leave the end of sequence
marker at the end of this group of lines, not sort the empty line
to after the marker. The easiest way to accomplish this is to
delete any empty lines from our table, if they are followed by
end of sequence markers. All we lose is the ability to set
breakpoints at some lines which contain no instructions
anyway. */
/* The end of sequence marker is special. We need to delete any
previous lines at the same PC, otherwise these lines may cause
problems since they might be at the same address as the following
function. For instance suppose a function calls abort there is no
reason to emit a ret after that point (no joke).
So the label may be at the same address where the following
function begins. There is also a fake end of sequence marker (-1)
that we emit internally when switching between different CUs
In this case, duplicate line table entries shall not be deleted.
We simply set the is_weak marker in this case. */
if (line == 0)
{
std::optional<int> last_line;
@@ -659,15 +666,84 @@ buildsym_compunit::record_line (struct subfile *subfile, int line,
if (!last_line.has_value () || *last_line == 0)
return;
}
else if (line == -1)
{
line = 0;
auto e = subfile->line_vector_entries.end ();
while (e > subfile->line_vector_entries.begin ())
{
e--;
if (e->unrelocated_pc () != pc)
break;
e->is_weak = 1;
}
}
linetable_entry &e = subfile->line_vector_entries.emplace_back ();
e.line = line;
e.is_stmt = (flags & LEF_IS_STMT) != 0;
e.is_weak = false;
e.set_unrelocated_pc (pc);
e.prologue_end = (flags & LEF_PROLOGUE_END) != 0;
e.epilogue_begin = (flags & LEF_EPILOGUE_BEGIN) != 0;
}
/* Patch the is_stmt bits at the given inline end address.
The line table has to be already sorted. */
static void
patch_inline_end_pos (struct subfile *subfile, struct objfile *objfile,
CORE_ADDR end)
{
std::vector<linetable_entry> &items = subfile->line_vector_entries;
int a = 2, b = items.size () - 1;
/* We need at least two items with pc = end in the table.
The lowest usable items are at pos 0 and 1, the highest
usable items are at pos b - 2 and b - 1. */
if (a > b
|| end < items[1].pc (objfile)
|| end > items[b - 2].pc (objfile))
return;
/* Look for the first item with pc > end in the range [a,b].
The previous element has pc = end or there is no match.
We set a = 2, since we need at least two consecutive elements
with pc = end to do anything useful.
We set b = items.size () - 1, since we are not interested
in the last element which should be an end of sequence
marker with line = 0 and is_stmt = true. */
while (a < b)
{
int c = (a + b) / 2;
if (end < items[c].pc (objfile))
b = c;
else
a = c + 1;
}
a--;
if (items[a].pc (objfile) != end || items[a].is_stmt)
return;
/* When there is a sequence of line entries at the same address
where an inline range ends, and the last item has is_stmt = 0,
we force all previous items to have is_weak = true as well. */
do
{
/* We stop at the first line entry with a different address,
or when we see an end of sequence marker. */
a--;
if (items[a].pc (objfile) != end || items[a].line == 0)
break;
items[a].is_weak = true;
}
while (a > 0);
}
/* Subroutine of end_compunit_symtab to simplify it. Look for a subfile that
matches the main source file's basename. If there is only one, and
@@ -892,6 +968,10 @@ buildsym_compunit::end_compunit_symtab_with_blockvector
relationships, this is why std::stable_sort is used. */
std::stable_sort (subfile->line_vector_entries.begin (),
subfile->line_vector_entries.end ());
for (int i = 0; i < m_inline_end_vector.size (); i++)
patch_inline_end_pos (subfile, m_objfile,
m_inline_end_vector[i]);
}
/* Allocate a symbol table if necessary. */

View File

@@ -446,6 +446,9 @@ private:
/* Pending symbols that are local to the lexical context. */
struct pending *m_local_symbols = nullptr;
/* Pending inline end range addresses. */
std::vector<CORE_ADDR> m_inline_end_vector;
};

View File

@@ -10688,6 +10688,17 @@ read_variable (struct die_info *die, struct dwarf2_cu *cu)
}
}
/* Return true if an empty range associated with an entry of type TAG in
CU should be "fixed", that is, converted to a single byte, non-empty
range. */
static bool
dwarf_fixup_empty_range (struct dwarf2_cu *cu, dwarf_tag tag)
{
return (tag == DW_TAG_inlined_subroutine
&& producer_is_gcc (cu->producer, nullptr, nullptr));
}
/* Call CALLBACK from DW_AT_ranges attribute value OFFSET
reading .debug_rnglists.
Callback's type should be:
@@ -10850,7 +10861,12 @@ dwarf2_rnglists_process (unsigned offset, struct dwarf2_cu *cu,
/* Empty range entries have no effect. */
if (range_beginning == range_end)
continue;
{
if (dwarf_fixup_empty_range (cu, tag))
range_end = (unrelocated_addr) ((CORE_ADDR) range_end + 1);
else
continue;
}
/* Only DW_RLE_offset_pair needs the base address added. */
if (rlet == DW_RLE_offset_pair)
@@ -10972,7 +10988,12 @@ dwarf2_ranges_process (unsigned offset, struct dwarf2_cu *cu, dwarf_tag tag,
/* Empty range entries have no effect. */
if (range_beginning == range_end)
continue;
{
if (dwarf_fixup_empty_range (cu, tag))
range_end = (unrelocated_addr) ((CORE_ADDR) range_end + 1);
else
continue;
}
range_beginning = (unrelocated_addr) ((CORE_ADDR) range_beginning
+ (CORE_ADDR) *base);
@@ -11195,9 +11216,24 @@ dwarf2_get_pc_bounds (struct die_info *die, unrelocated_addr *lowpc,
if (ret == PC_BOUNDS_NOT_PRESENT || ret == PC_BOUNDS_INVALID)
return ret;
/* partial_die_info::read has also the strict LOW < HIGH requirement. */
/* These LOW and HIGH values will be used to create a block. A block's
high address is the first address after the block's address range, so
if 'high <= low' then the block has no code associated with it. */
if (high <= low)
return PC_BOUNDS_INVALID;
{
/* In some cases though, when the blocks LOW / HIGH were defined with
the DW_AT_low_pc and DW_AT_high_pc, we see some compilers create
an empty block when we can provide a better debug experience by
having a non-empty block. We do this by "fixing" the block to be
a single byte in length. See dwarf_fixup_empty_range for when
this fixup is performed. */
if (high == low
&& ret == PC_BOUNDS_HIGH_LOW
&& dwarf_fixup_empty_range (cu, die->tag))
high = (unrelocated_addr) (((ULONGEST) low) + 1);
else
return PC_BOUNDS_INVALID;
}
/* When using the GNU linker, .gnu.linkonce. sections are used to
eliminate duplicate copies of functions and vtables and such.
@@ -11465,7 +11501,31 @@ dwarf2_record_block_ranges (struct die_info *die, struct block *block,
CORE_ADDR low = per_objfile->relocate (unrel_low);
CORE_ADDR high = per_objfile->relocate (unrel_high);
cu->get_builder ()->record_block_range (block, low, high - 1);
/* Blocks where 'high < low' should be rejected earlier in the
process, e.g. see dwarf2_get_pc_bounds. */
gdb_assert (high >= low);
/* The value of HIGH is the first address past the end, but
GDB stores ranges with the high value as last inclusive
address, so in most cases we need to decrement HIGH here.
Blocks where 'high == low' represent an empty block (i.e. a
block with no associated code).
When 'high == low' and dwarf_fixup_empty_range returns true we
"fix" the empty range into a single byte range, which we can
do by leaving HIGH untouched. Otherwise we decrement HIGH,
which might result in 'high < low'. */
if (high > low || !dwarf_fixup_empty_range (cu, die->tag))
high -= 1;
/* If the above decrement resulted in 'high < low' then this
represents an empty range. There's little point storing this
in GDB's internal structures, it's just more to search
through, and it will never match any address. */
if (high >= low)
cu->get_builder ()->record_block_range (block, low, high);
}
}
@@ -18352,21 +18412,9 @@ private:
/* Additional bits of state we need to track. */
/* The last file that we called dwarf2_start_subfile for.
This is only used for TLLs. */
unsigned int m_last_file = 0;
/* The last file a line number was recorded for. */
struct subfile *m_last_subfile = NULL;
/* The address of the last line entry. */
unrelocated_addr m_last_address;
/* Set to true when a previous line at the same address (using
m_last_address) had LEF_IS_STMT set in m_flags. This is reset to false
when a line entry at a new address (m_address different to
m_last_address) is processed. */
bool m_stmt_at_address = false;
/* When true, record the lines we decode. */
bool m_currently_recording_lines = true;
@@ -18524,7 +18572,8 @@ dwarf_record_line_1 (struct gdbarch *gdbarch, struct subfile *subfile,
static void
dwarf_finish_line (struct gdbarch *gdbarch, struct subfile *subfile,
unrelocated_addr address, struct dwarf2_cu *cu)
unrelocated_addr address, struct dwarf2_cu *cu,
bool end_sequence)
{
if (subfile == NULL)
return;
@@ -18537,7 +18586,8 @@ dwarf_finish_line (struct gdbarch *gdbarch, struct subfile *subfile,
paddress (gdbarch, (CORE_ADDR) address));
}
dwarf_record_line_1 (gdbarch, subfile, 0, address, LEF_IS_STMT, cu);
dwarf_record_line_1 (gdbarch, subfile, end_sequence ? 0 : -1, address,
LEF_IS_STMT, cu);
}
void
@@ -18565,38 +18615,17 @@ lnp_state_machine::record_line (bool end_sequence)
/* For now we ignore lines not starting on an instruction boundary.
But not when processing end_sequence for compatibility with the
previous version of the code. */
else if (m_op_index == 0 || end_sequence)
else if ((m_op_index == 0 && m_line != 0) || end_sequence)
{
/* When we switch files we insert an end maker in the first file,
switch to the second file and add a new line entry. The
problem is that the end marker inserted in the first file will
discard any previous line entries at the same address. If the
line entries in the first file are marked as is-stmt, while
the new line in the second file is non-stmt, then this means
the end marker will discard is-stmt lines so we can have a
non-stmt line. This means that there are less addresses at
which the user can insert a breakpoint.
To improve this we track the last address in m_last_address,
and whether we have seen an is-stmt at this address. Then
when switching files, if we have seen a stmt at the current
address, and we are switching to create a non-stmt line, then
discard the new line. */
bool file_changed
= m_last_subfile != m_cu->get_builder ()->get_current_subfile ();
bool ignore_this_line
= ((file_changed && !end_sequence && m_last_address == m_address
&& ((m_flags & LEF_IS_STMT) == 0)
&& m_stmt_at_address)
|| (!end_sequence && m_line == 0));
if ((file_changed && !ignore_this_line) || end_sequence)
if (m_last_subfile != m_cu->get_builder ()->get_current_subfile ()
|| end_sequence)
{
dwarf_finish_line (m_gdbarch, m_last_subfile, m_address,
m_currently_recording_lines ? m_cu : nullptr);
m_currently_recording_lines ? m_cu : nullptr,
end_sequence || (m_flags & LEF_IS_STMT) != 0);
}
if (!end_sequence && !ignore_this_line)
if (!end_sequence)
{
linetable_entry_flags lte_flags = m_flags;
if (producer_is_codewarrior (m_cu))
@@ -18616,15 +18645,6 @@ lnp_state_machine::record_line (bool end_sequence)
m_last_line = m_line;
}
}
/* Track whether we have seen any IS_STMT true at m_address in case we
have multiple line table entries all at m_address. */
if (m_last_address != m_address)
{
m_stmt_at_address = false;
m_last_address = m_address;
}
m_stmt_at_address |= (m_flags & LEF_IS_STMT) != 0;
}
lnp_state_machine::lnp_state_machine (struct dwarf2_cu *cu, gdbarch *arch,
@@ -18638,8 +18658,7 @@ lnp_state_machine::lnp_state_machine (struct dwarf2_cu *cu, gdbarch *arch,
This is currently used by MIPS code,
cf. `mips_adjust_dwarf2_line'. */
m_address ((unrelocated_addr) gdbarch_adjust_dwarf2_line (arch, 0, 0)),
m_flags (lh->default_is_stmt ? LEF_IS_STMT : (linetable_entry_flags) 0),
m_last_address (m_address)
m_flags (lh->default_is_stmt ? LEF_IS_STMT : (linetable_entry_flags) 0)
{
}

View File

@@ -996,7 +996,8 @@ prepare_one_step (thread_info *tp, struct step_command_fsm *sm)
if (sym->aclass () == LOC_BLOCK)
{
const block *block = sym->value_block ();
if (block->end () < tp->control.step_range_end)
if (block->end () < tp->control.step_range_end
&& block->end () > tp->control.step_range_start)
tp->control.step_range_end = block->end ();
}
}

View File

@@ -8200,6 +8200,8 @@ process_event_stop_test (struct execution_control_state *ecs)
infrun_debug_printf ("stepping through inlined function");
if (ecs->event_thread->control.step_over_calls == STEP_OVER_ALL
|| ecs->event_thread->stop_pc () != stop_pc_sal.pc
|| !stop_pc_sal.is_stmt
|| inline_frame_is_marked_for_skip (false, ecs->event_thread))
keep_going (ecs);
else
@@ -8248,7 +8250,8 @@ process_event_stop_test (struct execution_control_state *ecs)
end_stepping_range (ecs);
return;
}
else if (*curr_frame_id == original_frame_id)
else if (get_stack_frame_id (frame)
== ecs->event_thread->control.step_stack_frame_id)
{
/* We are not at the start of a statement, and we have not changed
frame.

View File

@@ -497,6 +497,7 @@ jit_symtab_line_mapping_add_impl (struct gdb_symbol_callbacks *cb,
(unrelocated_addr (map[i].pc));
stab->linetable->item[i].line = map[i].line;
stab->linetable->item[i].is_stmt = true;
stab->linetable->item[i].is_weak = false;
}
}

View File

@@ -265,6 +265,8 @@ dump_symtab_1 (struct symtab *symtab, struct ui_file *outfile)
gdb_puts (paddress (gdbarch, l->item[i].pc (objfile)), outfile);
if (l->item[i].is_stmt)
gdb_printf (outfile, "\t(stmt)");
if (l->item[i].is_weak)
gdb_printf (outfile, "\t(weak)");
gdb_printf (outfile, "\n");
}
}
@@ -981,12 +983,13 @@ maintenance_print_one_line_table (struct symtab *symtab, void *data)
/* Leave space for 6 digits of index and line number. After that the
tables will just not format as well. */
struct ui_out *uiout = current_uiout;
ui_out_emit_table table_emitter (uiout, 7, -1, "line-table");
ui_out_emit_table table_emitter (uiout, 8, -1, "line-table");
uiout->table_header (6, ui_left, "index", _("INDEX"));
uiout->table_header (6, ui_left, "line", _("LINE"));
uiout->table_header (18, ui_left, "rel-address", _("REL-ADDRESS"));
uiout->table_header (18, ui_left, "unrel-address", _("UNREL-ADDRESS"));
uiout->table_header (7, ui_left, "is-stmt", _("IS-STMT"));
uiout->table_header (7, ui_left, "is-weak", _("IS-WEAK"));
uiout->table_header (12, ui_left, "prologue-end", _("PROLOGUE-END"));
uiout->table_header (14, ui_left, "epilogue-begin", _("EPILOGUE-BEGIN"));
uiout->table_body ();
@@ -1008,6 +1011,7 @@ maintenance_print_one_line_table (struct symtab *symtab, void *data)
uiout->field_core_addr ("unrel-address", objfile->arch (),
CORE_ADDR (item->unrelocated_pc ()));
uiout->field_string ("is-stmt", item->is_stmt ? "Y" : "");
uiout->field_string ("is-weak", item->is_weak ? "Y" : "");
uiout->field_string ("prologue-end", item->prologue_end ? "Y" : "");
uiout->field_string ("epilogue-begin", item->epilogue_begin ? "Y" : "");
uiout->text ("\n");

View File

@@ -3294,26 +3294,38 @@ find_pc_sect_line (CORE_ADDR pc, struct obj_section *section, int notcurrent)
0) instead of a real line. */
if (prev && prev->line
&& (!best || prev->unrelocated_pc () > best->unrelocated_pc ()))
&& (!best || prev->unrelocated_pc () > best->unrelocated_pc ()
|| (prev->unrelocated_pc () == best->unrelocated_pc ()
&& (best->pc (objfile) == pc
? !best->is_stmt : best->is_weak))))
{
best = prev;
best_symtab = iter_s;
/* If during the binary search we land on a non-statement entry,
scan backward through entries at the same address to see if
there is an entry marked as is-statement. In theory this
duplication should have been removed from the line table
during construction, this is just a double check. If the line
table has had the duplication removed then this should be
pretty cheap. */
if (!best->is_stmt)
/* If NOTCURRENT is false then the address we are looking for is
the address the inferior is currently stopped at. In this
case our preference is to report a stop at a line marked as
is_stmt. If BEST is not marked as a statement then scan
backwards through entries at this address looking for one that
is marked as a statement; if one is found then use that.
If NOTCURRENT is true then the address we're looking for is
not the inferior's current address, but is an address from a
previous stack frame (i.e. frames 1, 2, 3, ... etc). In this
case scanning backwards for an is_stmt line table entry is not
the desired behaviour. If an inline function terminated at
this address then the last is_stmt line will be within the
inline function, while the following non-statement line will
be for the outer function. When looking up the stack we
expect to see the outer function. */
if (!best->is_stmt && !notcurrent)
{
const linetable_entry *tmp = best;
while (tmp > first
&& (tmp - 1)->unrelocated_pc () == tmp->unrelocated_pc ()
&& (tmp - 1)->line != 0 && !tmp->is_stmt)
--tmp;
if (tmp->is_stmt)
if (tmp->is_stmt && (tmp->pc (objfile) == pc || !tmp->is_weak))
best = tmp;
}
@@ -3337,18 +3349,14 @@ find_pc_sect_line (CORE_ADDR pc, struct obj_section *section, int notcurrent)
We used to return alt->line - 1 here, but that could be
anywhere; if we don't have line number info for this PC,
don't make some up. */
val.pc = pc;
}
else if (best->line == 0)
{
/* If our best fit is in a range of PC's for which no line
number info is available (line number is zero) then we didn't
find any valid line information. */
if (notcurrent)
pc++;
val.pc = pc;
}
else
{
val.is_stmt = best->is_stmt;
val.is_weak = best->is_weak;
val.symtab = best_symtab;
val.line = best->line;
val.pc = best->pc (objfile);

View File

@@ -1647,6 +1647,9 @@ struct linetable_entry
/* True if this PC is a good location to place a breakpoint for LINE. */
bool is_stmt : 1;
/* True if this PC is at a subroutine range end. */
bool is_weak : 1;
/* True if this location is a good location to place a breakpoint after a
function prologue. */
bool prologue_end : 1;
@@ -2403,6 +2406,8 @@ struct symtab_and_line
/* If the line number information is valid, then this indicates if this
line table entry had the is-stmt flag set or not. */
bool is_stmt = false;
/* True if this PC is at a subroutine range end. */
bool is_weak = false;
/* The probe associated with this symtab_and_line. */
probe *prob = NULL;

View File

@@ -0,0 +1,39 @@
/* 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/>. */
static int test0 (void)
{
asm (""); /* line 20 */
return 1; /* line 21 */
}
int __attribute__((noinline, noclone))
#ifdef __CET__
__attribute__((nocf_check))
#endif
test1 (int x)
{
asm ("");
return x+1; /* line 31 */
}
int
main()
{ test1 (test0 ()); /* line 36 */
test1 (test0 ()); /* line 37 */
return 0; /* line 38 */
}

View File

@@ -0,0 +1,51 @@
# 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/>.
standard_testfile .c
if { ![test_compiler_info gcc*] || ![supports_statement_frontiers] } {
untested "this test needs gcc with statement frontiers"
return -1
}
global srcfile testfile
set options {debug nowarnings optimize=-O2}
lappend options additional_flags=-gstatement-frontiers
if { [prepare_for_testing "failed to prepare" $binfile \
$srcfile $options] } {
return -1
}
if ![runto_main] {
return
}
gdb_test "frame 0" "\\s*\\#0\\s+main.*${srcfile}:36.*" "in main"
gdb_test_multiple "step" "step into test0" {
-re ".*test0.*${srcfile}:20.*$::gdb_prompt $" {
gdb_test "step" ".*line 21.*" $gdb_test_name
}
-re ".*test0.*${srcfile}:21.*$::gdb_prompt $" {
pass $gdb_test_name
}
}
gdb_test "frame 1" "\\s*\\#1\\s+main.*${srcfile}:36.*" "frame1"
gdb_test "step" ".*test1.*${srcfile}:31.*" "step into test1"
gdb_test "frame 1" "\\s*\\#1.*in main.*${srcfile}:36.*" "frame2"
gdb_test "step" ".*main.*${srcfile}:37.*" "step back to main"
gdb_test "next" ".*return 0;.*" "step over test0+1"
gdb_test "frame 0" "\\s*\\#0\\s+main.*${srcfile}:38.*" "in main again"

View File

@@ -0,0 +1,39 @@
/* 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/>. */
volatile int global = 0;
__attribute__((noinline, noclone)) void
foo (int arg)
{
global += arg;
}
inline __attribute__((always_inline)) int
bar (int val)
{
if (__builtin_expect(global == val, 0))
return 1;
foo (1);
return 1;
}
int
main (void)
{
if ((__builtin_expect(global, 0) && bar (1)) || bar (2))
return 1;
return 0;
}

View File

@@ -0,0 +1,43 @@
# 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/>.
standard_testfile .c
if { [test_compiler_info gcc*] && ![supports_statement_frontiers] } {
untested "this test needs gcc with statement frontiers"
return -1
}
global srcfile testfile
set options {debug nowarnings optimize=-O2}
if { [supports_statement_frontiers] } {
lappend options additional_flags=-gstatement-frontiers
}
if { [prepare_for_testing "failed to prepare" $binfile \
$srcfile $options] } {
return -1
}
if ![runto_main] {
return
}
gdb_test "break bar" ".*Breakpoint 2 at .*" "break at bar"
gdb_test "break foo" ".*Breakpoint 3 at .*" "break at foo"
gdb_test "continue" ".*Breakpoint .* bar .*" "continue to bar"
gdb_test "continue" ".*Breakpoint .* foo .*" "continue to foo"
gdb_test "continue" ".* exited .*" "continue to exit"

View File

@@ -0,0 +1,33 @@
/* 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/>. */
/* PR 25987 */
struct MyClass;
struct ptr {
MyClass* get() { return t; } /* line 21 */
MyClass* t;
};
struct MyClass { void call(); };
void MyClass::call() {
*(volatile char*)-1 = 1; /* line 26 */
}
static void intermediate(ptr p) {
p.get()->call(); /* line 29 */
}
int main() {
intermediate(ptr{new MyClass});
}

View File

@@ -0,0 +1,51 @@
# 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/>.
# PR 25987
standard_testfile .cc
if { ![test_compiler_info gcc*] || ![supports_statement_frontiers] } {
untested "this test needs gcc with statement frontiers"
return -1
}
set options {c++ debug nowarnings optimize=-Og}
lappend options additional_flags=-gstatement-frontiers
if { [prepare_for_testing "failed to prepare" $testfile \
$srcfile $options] } {
return -1
}
if ![runto_main] {
return
}
gdb_test "bt" "\\s*\\#0\\s+main.*" "in main"
#break at the empty inline function ptr::get
gdb_test "b get" ".*" "break at get"
gdb_test "c" ".*" "continue to get"
#call frame 1 is at line 29
gdb_test "bt" [multi_line "\\s*\\#0\\s+ptr::get\[^\r\]*${srcfile}:21" \
"\\s*\\#1\\s+intermediate\[^\r\]*${srcfile}:29" \
".*"] \
"at get"
#print a local value here
gdb_test "p t" ".*(\\\$1 = \\(MyClass \\*\\) 0x|value has been optimized out).*" "print t"
gdb_test "c" ".*SIGSEGV.*" "continue to SIGSEGV"
#call frame 1 is at line 29
gdb_test "bt" [multi_line "\\s*\\#0\\s+\[^\r\]*MyClass::call\[^\r\]*${srcfile}:26" \
"\\s*\\#1\\s+0x\[^\r\]*intermediate\[^\r\]*${srcfile}:29" \
".*"] \
"at call"

View File

@@ -47,8 +47,7 @@ tree_check (tree *t, int i)
int __attribute__((noinline, noclone))
get_alias_set (tree *t)
{
if (t != NULL
{ if (t != NULL
&& TREE_TYPE (t).z != 1
&& TREE_TYPE (t).z != 2
&& TREE_TYPE (t).z != 3)
@@ -60,7 +59,6 @@ tree xx;
int
main()
{
get_alias_set (&xx); /* Beginning of main */
{ get_alias_set (&xx);
return 0;
} // main

View File

@@ -15,7 +15,8 @@
standard_testfile .cc
if {[test_compiler_info gcc*] && ![supports_statement_frontiers] } {
if { ![test_compiler_info gcc*] || ![supports_statement_frontiers] } {
untested "this test needs gcc with statement frontiers"
return -1
}
@@ -24,17 +25,8 @@ 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
}
lappend options additional_flags=-gstatement-frontiers
if { $use_header } {
lappend options additional_flags=-DUSE_NEXT_INLINE_H
set executable "$testfile-with-header"
@@ -53,8 +45,6 @@ proc do_test { use_header } {
with_test_prefix $prefix {
set main_location [gdb_get_line_number "Beginning of main" $srcfile]
if {![runto_main]} {
return
}
@@ -81,129 +71,28 @@ proc do_test { use_header } {
clean_restart $executable
if ![runto $main_location qualified] {
if ![runto_main] {
return
}
gdb_test "bt" "\\s*\\#0\\s+main.*" "in main"
set line1 {\t\{}
set line2 {\t if \(t != NULL}
gdb_test_multiple "step" "step into get_alias_set" {
-re -wrap $line1 {
gdb_test "next" $line2 $gdb_test_name
}
-re -wrap $line2 {
pass $gdb_test_name
}
}
gdb_test "step" ".*" "step into get_alias_set"
gdb_test "bt" "\\s*\\#0\\s+get_alias_set\[^\r\]*${srcfile}:.*" \
"not in inline 1"
# It's possible that this first failure (when not using a header
# file) is GCC's fault, though the remaining failures would best
# be fixed by adding location views support (though it could be
# that some easier heuristic could be figured out). Still, it is
# not certain that the first failure wouldn't also be fixed by
# having location view support, so for now it is tagged as such.
set have_kfail [expr [test_compiler_info gcc*] && !$use_header]
set ok 1
gdb_test_multiple "next" "next step 1" {
-re -wrap "if \\(t->x != i\\)" {
set ok 0
send_gdb "next\n"
exp_continue
}
-re -wrap ".*TREE_TYPE.* != 1" {
if { $ok } {
pass $gdb_test_name
} else {
if { $have_kfail } {
setup_kfail "*-*-*" symtab/25507
}
fail $gdb_test_name
}
}
}
gdb_test "next" ".*TREE_TYPE.*" "next step 1"
gdb_test "bt" "\\s*\\#0\\s+get_alias_set\[^\r\]*${srcfile}:.*" \
"not in inline 2"
set ok 1
gdb_test_multiple "next" "next step 2" {
-re -wrap "return x;" {
set ok 0
send_gdb "next\n"
exp_continue
}
-re -wrap ".*TREE_TYPE.* != 2" {
if { $ok } {
pass $gdb_test_name
} else {
if { $have_kfail } {
setup_kfail "*-*-*" symtab/25507
}
fail $gdb_test_name
}
}
}
gdb_test "next" ".*TREE_TYPE.*" "next step 2"
gdb_test "bt" "\\s*\\#0\\s+get_alias_set\[^\r\]*${srcfile}:.*" \
"not in inline 3"
set ok 1
gdb_test_multiple "next" "next step 3" {
-re -wrap "return x;" {
set ok 0
send_gdb "next\n"
exp_continue
}
-re -wrap ".*TREE_TYPE.* != 3\\)" {
if { $ok } {
pass $gdb_test_name
} else {
if { $have_kfail } {
setup_kfail "*-*-*" symtab/25507
}
fail $gdb_test_name
}
}
}
gdb_test "next" ".*TREE_TYPE.*" "next step 3"
gdb_test "bt" "\\s*\\#0\\s+get_alias_set\[^\r\]*${srcfile}:.*" \
"not in inline 4"
set ok 1
gdb_test_multiple "next" "next step 4" {
-re -wrap "(if \\(t != NULL|\} // get_alias_set)" {
send_gdb "next\n"
exp_continue
}
-re -wrap "return x;" {
set ok 0
send_gdb "next\n"
exp_continue
}
-re -wrap "return 0.*" {
if { $ok } {
pass $gdb_test_name
} else {
if { $have_kfail } {
setup_kfail "*-*-*" symtab/25507
}
fail $gdb_test_name
}
}
}
gdb_test "next" "return 0.*" "next step 4"
gdb_test "bt" \
"\\s*\\#0\\s+(main|get_alias_set)\[^\r\]*${srcfile}:.*" \
"not in inline 5"
if {!$use_header} {
# With the debug from GCC 10.x (and earlier) GDB is currently
# unable to successfully complete the following tests when we
# are not using a header file.
kfail symtab/25507 "stepping tests"
return
}
clean_restart ${executable}
if ![runto_main] {
@@ -220,22 +109,84 @@ proc do_test { use_header } {
gdb_test "step" ".*if \\(t->x != i\\).*" "step 2"
gdb_test "bt" "\\s*\\#0\\s+\[^\r\]*tree_check\[^\r\]*${hdrfile}:.*" \
"in inline 1 pass 2"
gdb_test "step" ".*TREE_TYPE.*" "step 3"
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"
gdb_test "step" ".*if \\(t->x != i\\).*" "step 4"
gdb_test "step" ".*if \\(t->x != i\\).*" "step 5"
gdb_test "bt" "\\s*\\#0\\s+\[^\r\]*tree_check\[^\r\]*${hdrfile}:.*" \
"in inline 2 pass 2"
gdb_test "step" ".*TREE_TYPE.*" "step 5"
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"
gdb_test "step" ".*if \\(t->x != i\\).*" "step 6"
gdb_test "step" ".*if \\(t->x != i\\).*" "step 8"
gdb_test "bt" "\\s*\\#0\\s+\[^\r\]*tree_check\[^\r\]*${hdrfile}:.*" \
"in inline 3 pass 2"
gdb_test "step" "return 0.*" "step 7"
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}:.*" \
"not in inline 4 pass 2"
clean_restart ${executable}
if ![runto_main] {
return
}
gdb_test "bt" "\\s*\\#0\\s+main.*" "in main pass 3"
gdb_test "step" ".*" "step into get_alias_set pass 3"
gdb_test "bt" "\\s*\\#0\\s+get_alias_set\[^\r\]*${srcfile}:.*" \
"in get_alias_set pass 3"
gdb_test "step" ".*TREE_TYPE.*" "step 1 pass 3"
gdb_test "bt" "\\s*\\#0\\s+get_alias_set\[^\r\]*${srcfile}:.*" \
"not in inline 1 pass 3"
gdb_test "step" ".*if \\(t->x != i\\).*" "step 2 pass 3"
gdb_test "bt" "\\s*\\#0\\s+\[^\r\]*tree_check\[^\r\]*${hdrfile}:.*" \
"in inline 1 pass 3"
gdb_test_multiple "p t->x = 2" "change value pass 3" {
-re ".*value has been optimized out.*$::gdb_prompt $" {
gdb_test "p xx.x = 2" ".* = 2.*" $gdb_test_name
}
-re ".* = 2.*$::gdb_prompt $" {
pass $gdb_test_name
}
}
gdb_test "step" ".*abort.*" "step 3, pass 3"
gdb_test "bt" "\\s*\\#0\\s+\[^\r\]*tree_check\[^\r\]*${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"
}
}

View File

@@ -0,0 +1,39 @@
/* 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/>. */
volatile int global_var = 0;
int
main (void)
{ /* main decl line */
asm ("main_label: .globl main_label");
++global_var;
asm ("main_0: .globl main_0");
++global_var;
asm ("main_1: .globl main_1");
++global_var; /* foo call line */
asm ("main_2: .globl main_2");
++global_var;
asm ("main_3: .globl main_3");
++global_var;
return 0;
}

View File

@@ -0,0 +1,128 @@
# 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/>.
# Define an inline function `foo` within the function `main`. The
# function `foo` uses DW_AT_low_pc and DW_AT_high_pc to define its
# range, except that DW_AT_high_pc is the constant 0.
#
# This should indicate that there is no code associated with `foo`,
# however, with gcc versions at least between 8.x and 14.x (latest at
# the time of writing this comment), it is observed that when these
# empty inline functions are created, if GDB stops at the address
# given in DW_AT_low_pc, then locals associated with the inline
# function can usually be read.
#
# At the very least, stopping at the location of the inline function
# means that the user can place a breakpoint on the inline function
# and have GDB stop in a suitable location, that alone is helpful.
#
# This test defines an inline function, places a breakpoint, and then
# runs and expects GDB to stop, and report the stop as being inside
# the inline function.
#
# We then check that the next outer frame is `main` as expected, and
# that the block for `foo` has been extended to a single byte, which
# is how GDB gives the previously empty block some range.
load_lib dwarf.exp
require dwarf2_support
standard_testfile .c .S
# Lines we reference in the generated DWARF.
set main_decl_line [gdb_get_line_number "main decl line"]
set foo_call_line [gdb_get_line_number "foo call line"]
get_func_info main
set asm_file [standard_output_file $srcfile2]
Dwarf::assemble $asm_file {
upvar entry_label entry_label
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 0 data4}
}
}
}
}
lines {version 2} lines_table {
include_dir "$::srcdir/$::subdir"
file_name "$::srcfile" 1
}
}
if {[prepare_for_testing "failed to prepare" $testfile \
[list $srcfile $asm_file] {nodebug}]} {
return
}
if {![runto_main]} {
return
}
gdb_breakpoint foo
gdb_test "continue" \
"Breakpoint $decimal, $hex in foo \\(\\)" \
"continue to b/p in foo"
set foo_start [get_hexadecimal_valueof "&main_1" "*UNKNOWN*" \
"get address of foo start"]
set foo_end [get_hexadecimal_valueof "&main_1 + 1" "*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 some content"
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"

View File

@@ -0,0 +1,54 @@
/* 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/>. */
volatile int global_var = 0;
int
main (void)
{ /* main decl line */
asm ("main_label: .globl main_label");
++global_var;
asm ("main_0: .globl main_0");
++global_var;
asm ("main_1: .globl main_1");
++global_var; /* foo call line */
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_9");
++global_var;
return 0;
}

View File

@@ -0,0 +1,260 @@
# 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/>.
# Define an inline function `foo` within the function `main`. The
# function `foo` uses DW_AT_ranges to define its ranges. One of the
# sub-ranges for foo will be empty.
#
# An empty sub-rnage should indicate that there is no code associated
# with `foo` at that address, however, with gcc versions at least
# between 8.x and 14.x (latest at the time of writing this comment),
# it is observed that when these empty sub-ranges are created for an
# inline function, if GDB treats the sub-range as non-empty, and stops
# at that location, then this generally gives a better debug
# experience. It is often still possible to read local variables at
# that address.
#
# This function defines an inline function, places a breakpoint on its
# entry-pc, and then runs and expects GDB to stop, and report the stop
# as being inside the inline function.
#
# We then check that the next outer frame is `main` as expected, and
# that the block for `foo` has the expected sub-ranges.
#
# We compile a variety of different configurations, broadly there are
# two variables, the location of the empty sub-range, and whether the
# entry-pc points at the empty sub-range or not.
#
# The the empty sub-range location, the empty sub-range can be the
# sub-range at the lowest address, highest address, or can be
# somewhere between a blocks low and high addresses.
load_lib dwarf.exp
require dwarf2_support
standard_testfile .c .S
# Lines we reference in the generated DWARF.
set main_decl_line [gdb_get_line_number "main decl line"]
set foo_call_line [gdb_get_line_number "foo call line"]
get_func_info main
# Compile the source file and load the executable into GDB so we can
# extract some addresses needed for creating the DWARF.
if { [prepare_for_testing "failed to prepare" ${testfile} \
[list ${srcfile}]] } {
return -1
}
if {![runto_main]} {
return -1
}
# Some addresses that we need when generating the DWARF.
for { set i 0 } { $i < 9 } { incr i } {
set main_$i [get_hexadecimal_valueof "&main_$i" "UNKNOWN" \
"get address for main_$i"]
}
# Create the DWARF assembler file into ASM_FILE. Using DWARF_VERSION
# to define which style of ranges to create. FUNC_RANGES is a list of
# 6 entries, each of which is an address, used to create the ranges
# for the inline function DIE. The ENTRY_PC is also an address and is
# used for the DW_AT_entry_pc of the inlined function.
proc write_asm_file { asm_file dwarf_version func_ranges entry_pc } {
Dwarf::assemble $asm_file {
upvar entry_label entry_label
upvar dwarf_version dwarf_version
upvar func_ranges func_ranges
upvar entry_pc entry_pc
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}
{entry_pc $entry_pc addr}
{ranges $ranges_label DW_FORM_sec_offset}
}
}
}
}
lines {version 2} lines_table {
include_dir "$::srcdir/$::subdir"
file_name "$::srcfile" 1
}
if { $dwarf_version == 5 } {
rnglists {} {
table {} {
ranges_label: list_ {
start_end [lindex $func_ranges 0] [lindex $func_ranges 1]
start_end [lindex $func_ranges 2] [lindex $func_ranges 3]
start_end [lindex $func_ranges 4] [lindex $func_ranges 5]
}
}
}
} else {
ranges { } {
ranges_label: sequence {
range [lindex $func_ranges 0] [lindex $func_ranges 1]
range [lindex $func_ranges 2] [lindex $func_ranges 3]
range [lindex $func_ranges 4] [lindex $func_ranges 5]
}
}
}
}
}
# Gobal used to give each generated binary a unique name.
set test_id 0
proc run_test { dwarf_version empty_loc entry_pc_type } {
incr ::test_id
set this_testfile $::testfile-$::test_id
set asm_file [standard_output_file $this_testfile.S]
if { $empty_loc eq "start" } {
set ranges [list \
$::main_1 $::main_1 \
$::main_3 $::main_4 \
$::main_6 $::main_7]
set entry_pc_choices [list $::main_1 $::main_3]
} elseif { $empty_loc eq "middle" } {
set ranges [list \
$::main_1 $::main_2 \
$::main_4 $::main_4 \
$::main_6 $::main_7]
set entry_pc_choices [list $::main_4 $::main_1]
} elseif { $empty_loc eq "end" } {
set ranges [list \
$::main_1 $::main_2 \
$::main_4 $::main_5 \
$::main_7 $::main_7]
set entry_pc_choices [list $::main_7 $::main_1]
} else {
error "unknown location for empty range '$empty_loc'"
}
if { $entry_pc_type eq "empty" } {
set entry_pc [lindex $entry_pc_choices 0]
} elseif { $entry_pc_type eq "non_empty" } {
set entry_pc [lindex $entry_pc_choices 1]
} else {
error "unknown entry-pc type '$entry_pc_type'"
}
write_asm_file $asm_file $dwarf_version $ranges $entry_pc
if {[prepare_for_testing "failed to prepare" $this_testfile \
[list $::srcfile $asm_file] {nodebug}]} {
return
}
if {![runto_main]} {
return
}
# Continue until we stop in 'foo'.
gdb_breakpoint foo
gdb_test "continue" \
"Breakpoint $::decimal, $::hex in foo \\(\\)" \
"continue to b/p in foo"
# Check we stopped at the entry-pc.
set pc [get_hexadecimal_valueof "\$pc" "*UNKNOWN*" \
"get \$pc at breakpoint"]
gdb_assert { $pc == $entry_pc } "stopped at entry-pc"
# The block's expected overall low/high addresses.
set block_start [lindex $ranges 0]
set block_end [lindex $ranges 5]
# Setup variables r{0,1,2}s, r{0,1,2}e, to represent ranges start
# and end addresses. These are extracted from the RANGES
# variable. However, RANGES includes the empty ranges, so spot
# the empty ranges and update the end address as GDB does.
#
# Also, if the empty range is at the end of the block, then the
# block's overall end address also needs adjusting.
for { set i 0 } { $i < 3 } { incr i } {
set start [lindex $ranges [expr $i * 2]]
set end [lindex $ranges [expr $i * 2 + 1]]
if { $start == $end } {
set end [format "0x%x" [expr $end + 1]]
}
if { $block_end == $start } {
set block_end $end
}
set r${i}s $start
set r${i}e $end
}
# Check the block 'foo' has the expected ranges.
gdb_test "maintenance info blocks" \
[multi_line \
"\\\[\\(block \\*\\) $::hex\\\] $block_start\\.\\.$block_end" \
" entry pc: $entry_pc" \
" inline function: foo" \
" symbol count: $::decimal" \
" address ranges:" \
" $r0s\\.\\.$r0e" \
" $r1s\\.\\.$r1e" \
" $r2s\\.\\.$r2e"] \
"block for foo has some content"
# Check the outer frame is 'main' as expected.
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"
}
foreach_with_prefix dwarf_version { 4 5 } {
foreach_with_prefix empty_loc { start middle end } {
foreach_with_prefix entry_pc_type { empty non_empty } {
run_test $dwarf_version $empty_loc $entry_pc_type
}
}
}

View File

@@ -0,0 +1,79 @@
/* 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/>. */
/* Used to insert labels with which we can build a fake line table. */
#define LL(N) asm ("line_label_" #N ": .globl line_label_" #N)
/* The following non-compiled code exists for the generated line table to
point at. */
#if 0
volatile int global = 0;
__attribute__((noinline, noclone)) void
foo (int arg)
{ /* foo prologue */
asm ("");
global += arg;
}
inline __attribute__((always_inline)) int
bar (void)
{
return 1; /* bar body */
}
int
main (void)
{ /* main prologue */
foo (bar ()); /* call line */
return 0;
}
#endif /* 0 */
volatile int var;
/* Generate some code to take up some space. */
#define FILLER do { \
var = 99; \
} while (0)
void
func (void)
{
asm ("func_label: .globl func_label");
FILLER;
LL (1);
FILLER;
LL (2);
return;
}
int
main (void)
{
asm ("main_label: .globl main_label");
FILLER;
LL (4);
FILLER;
LL (5);
func ();
FILLER;
LL (6);
FILLER;
return 0;
}

View File

@@ -0,0 +1,227 @@
# 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/>.
# Setup a line table where:
#
# | | | | Func | Func | Func |
# | Addr | Line | Stmt | main | foo | bar |
# |------|------|------|------|------|------|
# | 1 | 28 | Y | | X | |
# | 2 | 30 | Y | | X | |
# | 3 | 31 | N | | X | |
# | 4 | 41 | Y | X | | |
# | 5 | 42 | Y | X | | |
# | 5 | 36 | Y | X | | X |
# | 5 | 42 | N | X | | |
# | 6 | 43 | Y | X | | |
# | 7 | END | Y | X | | |
# |------|------|------|------|------|------|
#
#
# The function 'bar' is inline within 'main' while 'foo' is not
# inline. Function 'foo' is called from 'main' immediately after the
# inlined call to bar. The C code can be found within a '#if 0' block
# inside the test's .c file. The line table is similar to that
# generated by compiling the source code at optimisation level -Og.
#
# Place a breakpoint in 'foo', run to the breakpoint, and then examine
# frame #1, that is, the frame for 'main'. At one point, bugs in GDB
# meant that the user would be shown the inline line from 'bar' rather
# than the line from 'main'. In the example above the user expects to
# see line 42 from 'main', but instead would be shown line '36'.
#
# The cause of the bug is this: to find the line for frame #1 GDB
# first finds an address in frame #1 by unwinding frame #0. This
# provides the return address in frame #1. GDB subtracts 1 from this
# address and looks for a line matching this address. In this case
# that would be line 42.
#
# However, buggy GDB would then scan backward through the line table
# looking for a line table entry that is marked as is-stmt. In this
# case, the first matching entry is that for line 36, and so that is
# what is reported. This backward scan makes sense for frame #0, but
# not for outer frames.
#
# This has now been fixed to prevent the backward scan for frames
# other than frame #0.
load_lib dwarf.exp
# This test can only be run on targets which support DWARF-2 and use
# gas.
require dwarf2_support
standard_testfile .c .S
# Lines in the source code that we need to reference.
set call_line [gdb_get_line_number "call line" $srcfile]
set foo_prologue [gdb_get_line_number "foo prologue" $srcfile]
set main_prologue [gdb_get_line_number "main prologue" $srcfile]
set bar_body [gdb_get_line_number "bar body" $srcfile]
# We need the return address in 'main' after the call to 'func' so
# that we can build the line table. Compile the .c file with debug,
# and figure out the address. This works so long as the only
# difference in build flags between this compile and the later compile
# is that this is debug on, and the later compile is debug off.
if { [prepare_for_testing "failed to prepare" $testfile $srcfile] } {
return
}
if {![runto func]} {
return
}
set func_call_line [gdb_get_line_number "func ();"]
gdb_test "up" \
[multi_line \
"#1\\s*$hex in main \\(\\) at \[^\r\n\]+" \
"$func_call_line\\s+ func \\(\\);"] \
"move up from func to main"
set return_addr_in_main [get_hexadecimal_valueof "\$pc" "*UNKNOWN*" \
"get pc after return from func"]
# Prepare and run the test. Placed into a proc in case we ever want
# to parameterise this test in the future.
proc do_test { } {
set build_options {nodebug}
set asm_file [standard_output_file $::srcfile2]
Dwarf::assemble $asm_file {
upvar build_options build_options
declare_labels lines_label foo_label bar_label
get_func_info main $build_options
get_func_info func $build_options
cu {} {
compile_unit {
{producer "gcc" }
{language @DW_LANG_C}
{name $::srcfile}
{low_pc 0 addr}
{stmt_list ${lines_label} DW_FORM_sec_offset}
} {
foo_label: subprogram {
{external 1 flag}
{name foo}
{low_pc $func_start addr}
{high_pc "$func_start + $func_len" addr}
}
bar_label: subprogram {
{external 1 flag}
{name bar}
{inline 3 data1}
}
subprogram {
{external 1 flag}
{name main}
{low_pc $main_start addr}
{high_pc "$main_start + $main_len" addr}
} {
inlined_subroutine {
{abstract_origin %$bar_label}
{low_pc line_label_4 addr}
{high_pc line_label_5 addr}
{call_file 1 data1}
{call_line $::call_line data1}
}
}
}
}
lines {version 2 default_is_stmt 1} lines_label {
include_dir "${::srcdir}/${::subdir}"
file_name "$::srcfile" 1
program {
DW_LNE_set_address func
line $::foo_prologue
DW_LNS_copy
DW_LNE_set_address line_label_1
DW_LNS_advance_line 2
DW_LNS_copy
DW_LNE_set_address line_label_2
DW_LNS_advance_line 1
DW_LNS_negate_stmt
DW_LNS_copy
DW_LNE_set_address main
DW_LNS_advance_line [expr $::main_prologue - $::foo_prologue - 3]
DW_LNS_negate_stmt
DW_LNS_copy
DW_LNE_set_address line_label_4
DW_LNS_advance_line 1
DW_LNS_copy
DW_LNE_set_address line_label_4
line $::bar_body
DW_LNS_copy
DW_LNE_set_address line_label_4
line $::call_line
DW_LNS_negate_stmt
DW_LNS_copy
# Skip line_label_5, this is used as the end of `bar`
# the inline function.
DW_LNE_set_address $::return_addr_in_main
DW_LNS_advance_line 1
DW_LNS_negate_stmt
DW_LNS_copy
DW_LNE_set_address "$main_start + $main_len"
DW_LNE_end_sequence
}
}
}
if { [prepare_for_testing "failed to prepare" $::testfile \
[list $::srcfile $asm_file] $build_options] } {
return
}
if ![runto foo] {
return
}
# For this backtrace we don't really care which line number in foo
# is reported. We might get different line numbers depending on
# how the architectures skip prologue function works. This test
# is all about how frame #1 is reported.
set foo_body_1 [expr $::foo_prologue + 1]
set foo_body_2 [expr $::foo_prologue + 2]
gdb_test "bt" \
[multi_line \
"^#0\\s+foo \\(\\) at \[^\r\n\]+$::srcfile:(?:$::foo_prologue|$foo_body_1|$foo_body_2)" \
"#1\\s+$::hex in main \\(\\) at \[^\r\n\]+$::srcfile:$::call_line"] \
"backtrace show correct line number in main"
gdb_test "frame 1" \
[multi_line \
"^#1\\s+$::hex in main \\(\\) at \[^\r\n\]+$::srcfile:$::call_line" \
"$::call_line\\s+foo \\(bar \\(\\)\\);\[^\r\n\]+"] \
"correct lines are shown for frame 1"
}
# Run the test.
do_test

View File

@@ -168,7 +168,7 @@ gdb_test_multiple "maint info line-table gdb.dwarf2/dw2-ranges-base.c" \
-re ".*linetable: \\(\\(struct linetable \\*\\) 0x0\\):\r\nNo line table.\r\n" {
exp_continue
}
-re ".*linetable: \\(\\(struct linetable \\*\\) $hex\\):\r\nINDEX\[ \t\]+LINE\[ \t\]+REL-ADDRESS\[ \t\]+UNREL-ADDRESS\[ \t\]+IS-STMT\[ \t\]PROLOGUE-END\[ \t\]EPILOGUE-BEGIN *\r\n" {
-re ".*linetable: \\(\\(struct linetable \\*\\) $hex\\):\r\nINDEX\[ \t\]+LINE\[ \t\]+REL-ADDRESS\[ \t\]+UNREL-ADDRESS\[ \t\]+IS-STMT\[ \t\]IS-WEAK\[ \t\]PROLOGUE-END\[ \t\]EPILOGUE-BEGIN *\r\n" {
exp_continue
}
}

View File

@@ -56,6 +56,9 @@ foreach foo {foo_1 foo_2 foo_3 foo_4 foo_5 foo_6} {
"get address for $foo label"]
}
set foo_3_end [get_hexadecimal_valueof "&foo_3 + 1" "UNKNOWN" \
"get address for 'foo_3 + 1'"]
# Some line numbers needed in the generated DWARF.
set foo_decl_line [gdb_get_line_number "foo decl line"]
set bar_call_line [gdb_get_line_number "bar call line"]
@@ -85,24 +88,40 @@ if [is_ilp32_target] {
# generated which covers some parts of the inlined function. This
# makes most sense when being tested with the 'foo_6' label, as that
# label is all about handling the end of the inline function case.
#
# The PRODUCER is the string used to control the DW_AT_producer string
# in the CU. When PRODUCER is 'gcc' then a string is used that
# represents the gcc compiler. When PRODUCER is 'other' then a string
# that will not be interpreted as gcc is used. The gcc compiler will
# sometimes generate empty ranges for inline functions (from at least
# gcc 8.x through to the currently latest release 14.x), and so GDB
# has code in place to convert empty ranges to non-empty. This fix is
# not applied to other compilers at this time.
proc run_test { entry_label dwarf_version with_line_table } {
set dw_testname "${::testfile}-${dwarf_version}-${entry_label}"
proc run_test { producer entry_label dwarf_version with_line_table } {
set dw_testname "${::testfile}-${producer}-${dwarf_version}-${entry_label}"
if { $with_line_table } {
set dw_testname ${dw_testname}-lt
}
if { $producer eq "other" } {
set producer_str "ACME C 1.0.0"
} else {
set producer_str "GNU C 10.0.0"
}
set asm_file [standard_output_file "${dw_testname}.S"]
Dwarf::assemble $asm_file {
upvar dwarf_version dwarf_version
upvar entry_label entry_label
upvar producer_str producer_str
declare_labels lines_table inline_func ranges_label
cu { version $dwarf_version } {
compile_unit {
{producer "gcc"}
{producer $producer_str}
{language @DW_LANG_C}
{name $::srcfile}
{comp_dir /tmp}
@@ -157,6 +176,10 @@ proc run_test { entry_label dwarf_version with_line_table } {
line 2
DW_LNS_copy
DW_LNE_set_address foo_3
line 3
DW_LNS_copy
DW_LNE_set_address foo_6
line 10
DW_LNS_copy
@@ -206,6 +229,16 @@ proc run_test { entry_label dwarf_version with_line_table } {
return false
}
if { $producer eq "gcc" } {
set entry_pc $::foo_3
set empty_range_re "\r\n $::foo_3\\.\\.$::foo_3_end"
set line_num 3
} else {
set entry_pc $::foo_1
set empty_range_re ""
set line_num 1
}
# Place a breakpoint on `bar` and run to the breakpoint. Use
# gdb_test as we want full pattern matching against the stop
# location.
@@ -215,8 +248,8 @@ proc run_test { entry_label dwarf_version with_line_table } {
if { $with_line_table } {
set re \
[multi_line \
"Breakpoint $::decimal, bar \\(\\) at \[^\r\n\]+/$::srcfile:1" \
"1\\s+\[^\r\n\]+"]
"Breakpoint $::decimal, bar \\(\\) at \[^\r\n\]+/$::srcfile:$line_num" \
"$line_num\\s+\[^\r\n\]+"]
} else {
set re "Breakpoint $::decimal, $::hex in bar \\(\\)"
}
@@ -230,21 +263,23 @@ proc run_test { entry_label dwarf_version with_line_table } {
gdb_test "maint info blocks" \
[multi_line \
"\\\[\\(block \\*\\) $::hex\\\] $::foo_1\\.\\.$::foo_6" \
" entry pc: $::foo_1" \
" entry pc: $entry_pc" \
" inline function: bar" \
" symbol count: $::decimal" \
" address ranges:" \
" address ranges:$empty_range_re" \
" $::foo_1\\.\\.$::foo_2" \
" $::foo_5\\.\\.$::foo_6"]
}
foreach_with_prefix dwarf_version { 4 5 } {
# Test various labels without any line table present.
foreach_with_prefix entry_label { foo_3 foo_4 foo_2 foo_6 } {
run_test $entry_label $dwarf_version false
}
foreach_with_prefix producer { other gcc } {
foreach_with_prefix dwarf_version { 4 5 } {
# Test various labels without any line table present.
foreach_with_prefix entry_label { foo_3 foo_4 foo_2 foo_6 } {
run_test $producer $entry_label $dwarf_version false
}
# Now test what happens if we use the end address of the block,
# but also supply a line table. Does GDB do anything different?
run_test foo_6 $dwarf_version true
# Now test what happens if we use the end address of the block,
# but also supply a line table. Does GDB do anything different?
run_test $producer foo_6 $dwarf_version true
}
}

View File

@@ -0,0 +1,65 @@
/* 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 "attributes.h"
/* A global to do some work on. This being volatile is important. Without
this the compiler might optimise the whole program away. */
volatile int global = 0;
__attribute__((noinline)) ATTRIBUTE_NOCLONE void
breakpt ()
{
/* Some filler work. */
global++;
}
struct MyClass;
struct ptr
{
/* The following line is a single line to aid matching in the test
script. Sometimes the DWARF will point GDB at the '{' and sometimes
at the body of the function. We don't really care for this test, so
placing everything on one line removes this variability. */
MyClass* get_myclass () { return t; }
MyClass* t;
};
struct MyClass
{
void call();
};
void
MyClass::call ()
{
breakpt (); /* Final breakpoint. */
}
static void
intermediate (ptr p)
{
p.get_myclass ()->call ();
}
int
main ()
{
intermediate (ptr {new MyClass});
}

View File

@@ -0,0 +1,95 @@
# 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/>.
standard_testfile .cc
require {expr ![test_compiler_info gcc* c++] \
|| [supports_statement_frontiers] }
set options {c++ debug optimize=-Og}
lappend_include_file options $srcdir/lib/attributes.h
if {[supports_statement_frontiers]} {
lappend options additional_flags=-gstatement-frontiers
}
# Some line numbers we need for the test.
set get_myclass_line [gdb_get_line_number "MyClass* get_myclass ()"]
set call_get_line [gdb_get_line_number "p.get_myclass ()"]
set final_bp_line [gdb_get_line_number "Final breakpoint"]
# Build the test executable adding "-OPT_LEVEL" to the compilation
# flags. The break on the small function which is likely to have been
# inlined, check we stop where we expect, and that the backtrace looks
# correct.
#
# Then return from the inline function and call to another function,
# check the backtrace from this second function also looks good,
# specifically, we're checking that the backtrace doesn't incorrectly
# place frame #1 on the line for the inline function.
proc run_test { opt_level } {
set opts $::options
lappend opts "additional_flags=-${opt_level}"
if { [prepare_for_testing "failed to prepare" "$::testfile-$opt_level" \
$::srcfile $opts] } {
return
}
if { ![runto_main] } {
return
}
gdb_test "bt" "#0\\s+main \\(\\) \[^\r\n\]+/$::srcfile:$::decimal" \
"backtrace in main"
# Break at the empty inline function ptr::get_myclass.
gdb_breakpoint get_myclass
gdb_continue_to_breakpoint "continue to get_myclass" \
[multi_line \
".*/$::srcfile:$::get_myclass_line" \
"$::get_myclass_line\\s+MyClass\\* get_myclass \\(\\) \[^\r\n\]+"]
# Backtrace.
gdb_test "bt" \
[multi_line \
"#0\\s+ptr::get_myclass\[^\r\n\]+/$::srcfile:$::get_myclass_line" \
"#1\\s+intermediate\[^\r\n\]+/$::srcfile:$::call_get_line" \
"#2\\s+\[^\r\n\]+main \\(\\) \[^\r\n\]+/$::srcfile:$::decimal"] \
"at get_myclass"
# Print a class member variable, this should be in scope, but is often
# reported as optimised out.
gdb_test "p t" \
"(?:\\\$1 = \\(MyClass \\*\\) $::hex|value has been optimized out)" \
"print ptr::t"
gdb_breakpoint $::srcfile:$::final_bp_line
gdb_continue_to_breakpoint "continue to final breakpoint"
# Backtrace. Check frame #1 looks right. Bug gdb/25987 would report
# frame #1 as being the correct function, but would report the line for
# ptr::get_myclass(), which is not correct.
gdb_test "bt" \
[multi_line \
"#0\\s+MyClass::call\[^\r\n\]+/$::srcfile:$::final_bp_line" \
"#1\\s+\[^\r\n\]+ intermediate\[^\r\n\]+/$::srcfile:$::call_get_line" \
"#2\\s+\[^\r\n\]+ main \\(\\) \[^\r\n\]+/$::srcfile:$::decimal"] \
"at call"
}
foreach_with_prefix opt_level { Og O0 O1 O2 } {
run_test ${opt_level}
}

View File

@@ -0,0 +1,40 @@
/* 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 "attributes.h"
static int
test0 (void)
{
asm (""); /* First line of test0. */
return 1; /* Second line of test0. */
}
int __attribute__((noinline)) ATTRIBUTE_NOCLONE
test1 (int x)
{
asm ("");
return x + 1; /* Second line of test1. */
}
int
main (void)
{
test1 (test0 ()); /* First line of main. */
test1 (test0 ()); /* Second line of main. */
return 0; /* Third line of main. */
}

View File

@@ -0,0 +1,110 @@
# 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/>.
standard_testfile
require {expr ![test_compiler_info gcc* c++] \
|| [supports_statement_frontiers] }
set options {debug nowarnings optimize=-O2}
lappend_include_file options $srcdir/lib/attributes.h
if {[supports_statement_frontiers]} {
lappend options additional_flags=-gstatement-frontiers
}
# Some line numbers we need.
set lineno_main_1 [gdb_get_line_number "First line of main"]
set lineno_main_2 [gdb_get_line_number "Second line of main"]
set lineno_main_3 [gdb_get_line_number "Third line of main"]
set lineno_test0_1 [gdb_get_line_number "First line of test0"]
set lineno_test0_2 [gdb_get_line_number "Second line of test0"]
set lineno_test1_2 [gdb_get_line_number "Second line of test1"]
# ...
proc run_test { opt_level } {
set opts $::options
lappend opts "additional_flags=-${opt_level}"
if { [prepare_for_testing "failed to prepare" "$::testfile-$opt_level" \
$::srcfile $opts] } {
return
}
if { ![runto_main] } {
return
}
gdb_test "frame 0" \
[multi_line \
"#0\\s+main \[^\r\n\]+/$::srcfile:$::lineno_main_1" \
"$::lineno_main_1\\s+\[^\r\n\]+"] \
"frame 0 while in main"
gdb_test_multiple "step" "step into test0" {
-re -wrap ".*test0.*$::srcfile:$::lineno_test0_1.*" {
gdb_test "step" ".*line $::lineno_test0_2.*" $gdb_test_name
}
-re -wrap ".*test0.*$::srcfile:$::lineno_test0_2.*" {
pass $gdb_test_name
}
}
gdb_test "frame 1" \
[multi_line \
"#1\\s+main \[^\r\n\]+/$::srcfile:$::lineno_main_1" \
"$::lineno_main_1\\s+\[^\r\n\]+"] \
"inspect frame 1, main"
# Step into test1() function.
gdb_test "step" \
[multi_line \
"test1 \\(\[^)\]+\\) at \[^\r\n\]+/$::srcfile:$::lineno_test1_2" \
"$::lineno_test1_2\\s+\[^\r\n\]+"] \
"step into test1"
# Check frame #1 looks right. Bug gdb/25987 would report frame #1 as
# being the correct function, but would report the line for a nearby
# inlined function.
gdb_test "frame 1" \
[multi_line \
"#1\\s+\[^\r\n\]*main \\(\\) \[^\r\n\]+/$::srcfile:$::lineno_main_1" \
"$::lineno_main_1\\s+\[^\r\n\]+"] \
"inspect frame 1 again, still main"
# Step from the last line of test1 back into main.
gdb_test "step" \
[multi_line \
"main \\(\\) at \[^\r\n\]+/$::srcfile:$::lineno_main_2" \
"$::lineno_main_2\\s+\[^\r\n\]+"] \
"step back to main"
# Use next to step to the last line of main. This skips over the inline
# call to test0, and the non-inline call to test1.
gdb_test "next" \
"$::lineno_main_3\\s+return 0;\\s+\[^\r\n\]+" \
"step over test0+1"
# Sanity check that we are in main like we expect.
gdb_test "frame 0" \
[multi_line \
"#0\\s+main \[^\r\n\]+/$::srcfile:$::lineno_main_3" \
"$::lineno_main_3\\s+return 0;\\s+\[^\r\n\]+"] \
"confirm expected frame in main"
}
foreach_with_prefix opt_level { Og O0 O1 O2 } {
run_test ${opt_level}
}

View File

@@ -13,6 +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/>. */
#include "attributes.h"
/* This is only ever run if it is compiled with a new-enough GCC, but
we don't want the compilation to fail if compiled by some other
compiler. */
@@ -39,6 +41,30 @@ inline ATTR int func2(void)
return x * func1 (1);
}
inline ATTR int
return_one (void)
{
/* The following empty asm() statement prevents older (< 11.x) versions
of gcc from completely optimising away this function. And for newer
versions of gcc (>= 11.x) this ensures that we have two line table
entries in main for the inline call to this function, with the second
of these lines being a non-statement, which is critical for this
test. These two behaviours have been checked for versions of gcc
between 8.4.0 and 14.2.0. */
asm ("");
return 1;
}
volatile int global = 0;
__attribute__((noinline)) ATTRIBUTE_NOCLONE void
not_inline_func (int count)
{
global += count;
global += count; /* b/p in not_inline_func */
global += count;
}
int main (void)
{
int val;
@@ -53,5 +79,7 @@ int main (void)
val = func2 ();
result = val;
not_inline_func (return_one ()); /* bt line in main */
return 0;
}

View File

@@ -15,9 +15,11 @@
standard_testfile .c inline-markers.c
set opts {debug additional_flags=-Winline}
lappend_include_file opts $srcdir/lib/attributes.h
if {[prepare_for_testing "failed to prepare" $testfile \
[list $srcfile $srcfile2] \
{debug additional_flags=-Winline}]} {
[list $srcfile $srcfile2] $opts]} {
return -1
}
@@ -29,40 +31,87 @@ if { [skip_inline_frame_tests] } {
return
}
set line1 [gdb_get_line_number "set breakpoint 1 here" ${srcfile2}]
gdb_breakpoint $srcfile2:$line1
# Run inline function backtrace tests, compile with binary with OPT_LEVEL
# optimisation level. OPT_LEVEL should be a string like 'O0', 'O1', etc.
# No leading '-' is needed on OPT_LEVEL, that is added in this proc.
proc run_test { opt_level } {
gdb_test "continue" ".*set breakpoint 1 here.*" "continue to bar, 1"
gdb_test "backtrace" "#0 bar.*#1 .*main.*" "backtrace from bar, 1"
gdb_test "info frame" ".*called by frame.*" "bar not inlined"
set local_opts $::opts
lappend local_opts "additional_flags=-$opt_level"
gdb_test "continue" ".*set breakpoint 1 here.*" "continue to bar, 2"
gdb_test "backtrace" "#0 bar.*#1 .*func1.*#2 .*main.*" \
"backtrace from bar, 2"
gdb_test "up" "#1 .*func1.*" "up from bar, 2"
gdb_test "info frame" ".*inlined into frame.*" "func1 inlined, 2"
if {[prepare_for_testing "failed to prepare" ${::testfile}-${opt_level} \
[list $::srcfile $::srcfile2] $local_opts]} {
return
}
gdb_test "continue" ".*set breakpoint 1 here.*" "continue to bar, 3"
gdb_test "backtrace" "#0 bar.*#1 .*func1.*#2 .*func2.*#3 .*main.*" \
"backtrace from bar, 3"
gdb_test "up" "#1 .*func1.*" "up from bar, 3"
gdb_test "info frame" ".*inlined into frame.*" "func1 inlined, 3"
gdb_test "up" "#2 .*func2.*" "up from func1, 3"
gdb_test "info frame" ".*inlined into frame.*" "func2 inlined, 3"
runto_main
# A regression test for having a backtrace limit that forces unwinding
# to stop after an inline frame. GDB needs to compute the frame_id of
# the inline frame, which requires unwinding past all the inline
# frames to the real stack frame, even if that means bypassing the
# user visible backtrace limit. See PR backtrace/15558.
#
# Set a backtrace limit that forces an unwind stop after an inline
# function.
gdb_test_no_output "set backtrace limit 2"
# Force flushing the frame cache.
gdb_test "maint flush register-cache" "Register cache flushed."
gdb_test "up" "#1 .*func1.*" "up from bar, 4"
gdb_test "info frame" ".*in func1.*" "info frame still works"
# Verify the user visible limit works as expected.
gdb_test "up" "Initial frame selected; you cannot go up." "up hits limit"
gdb_test "backtrace" "#0 bar.*#1 .*func1.*" "backtrace hits limit"
set line1 [gdb_get_line_number "set breakpoint 1 here" ${::srcfile2}]
gdb_breakpoint $::srcfile2:$line1
with_test_prefix "first stop at bar" {
gdb_continue_to_breakpoint "continue to bar" \
".*set breakpoint 1 here.*"
gdb_test "backtrace" "#0 bar.*#1 .*main.*" "backtrace from bar"
gdb_test "info frame" ".*called by frame.*" "bar not inlined"
}
with_test_prefix "second stop at bar" {
gdb_continue_to_breakpoint "continue to bar" \
".*set breakpoint 1 here.*"
gdb_test "backtrace" "#0 bar.*#1 .*func1.*#2 .*main.*" \
"backtrace from bar"
gdb_test "up" "#1 .*func1.*" "up from bar"
gdb_test "info frame" ".*inlined into frame.*" "func1 inlined"
}
with_test_prefix "third stop at bar" {
gdb_continue_to_breakpoint "continue to bar" \
".*set breakpoint 1 here.*"
gdb_test "backtrace" "#0 bar.*#1 .*func1.*#2 .*func2.*#3 .*main.*" \
"backtrace from bar"
gdb_test "up" "#1 .*func1.*" "up from bar"
gdb_test "info frame" ".*inlined into frame.*" "func1 inlined"
gdb_test "up" "#2 .*func2.*" "up from func1"
gdb_test "info frame" ".*inlined into frame.*" "func2 inlined"
}
# A regression test for having a backtrace limit that forces unwinding
# to stop after an inline frame. GDB needs to compute the frame_id of
# the inline frame, which requires unwinding past all the inline
# frames to the real stack frame, even if that means bypassing the
# user visible backtrace limit. See PR backtrace/15558.
#
# Set a backtrace limit that forces an unwind stop after an inline
# function.
gdb_test_no_output "set backtrace limit 2"
# Force flushing the frame cache.
gdb_test "maint flush register-cache" "Register cache flushed."
gdb_test "up" "#1 .*func1.*" "up from bar"
gdb_test "info frame" ".*in func1.*" "info frame still works"
# Verify the user visible limit works as expected.
gdb_test "up" "Initial frame selected; you cannot go up." "up hits limit"
gdb_test "backtrace" "#0 bar.*#1 .*func1.*" "backtrace hits limit"
set line2 [gdb_get_line_number "b/p in not_inline_func" $::srcfile]
set line3 [gdb_get_line_number "bt line in main" $::srcfile]
gdb_breakpoint $::srcfile:$line2
gdb_continue_to_breakpoint "stop in not_inline_func" \
".*b/p in not_inline_func.*"
gdb_test "bt" \
[multi_line \
"^#0\\s+not_inline_func \\(\[^)\]+\\) at \[^\r\n\]+$::srcfile:$line2" \
"#1\\s+$::hex in main \\(\\) at \[^\r\n\]+$::srcfile:$line3"] \
"bt from not_inline_func to main"
gdb_test "frame 1" \
[multi_line \
"^#1\\s+$::hex in main \\(\\) at \[^\r\n\]+$::srcfile:$line3" \
"$line3\\s+not_inline_func \\(return_one \\(\\)\\);\[^\r\n\]+"] \
"select frame for main from not_inline_func"
}
foreach_with_prefix opt_level { O0 Og O1 O2 } {
run_test $opt_level
}

View File

@@ -431,6 +431,7 @@ arrange_linetable (std::vector<linetable_entry> &old_linetable)
linetable_entry &e = fentries.emplace_back ();
e.line = ii;
e.is_stmt = true;
e.is_weak = false;
e.set_unrelocated_pc (old_linetable[ii].unrelocated_pc ());
}
}