gdb: stepping between inline functions with multiple ranges

I (Andrew) have split this small change from a larger patch which was
posted here:

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

And I have written the stand alone test for this issue.  The original
patch included this paragraph to explain this change (I've fixed one
typo in this text replacing 'program' with 'function'):

  ... 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 function 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.

The important thing that happens here is that both the outer function
and the inline function must both have multiple ranges.  When the
inferior is within the inline function and moves from one range to
another it is critical that the address we stop at is the start of a
range in both the outer function and the inline function.

The diagram below represents how the functions are split and aligned:

                           (A)       (B)
  bar:         |------------|         |---|
  foo:   |------------------|         |--------|

The inferior is stepping through 'bar' and eventually reaches
point (A) at which point control passes to point (B).

Currently, when the inferior stops, GDB notices that both 'foo' and
'bar' start at address (B), and so GDB uses the inline frame mechanism
to skip 'bar' and tells the user that the inferior is in 'foo'.

However, as we were in 'bar' before the step then it makes sense that
we should be in 'bar' after the step, and this is what the patch does.

There are two tests using the DWARF assembler, the first checks the
above situation and ensures that GDB reports 'bar' after the step.

The second test is similar, but after the step we enter a new range
where a different inline function starts, something like this:

                           (A)       (B)
  bar:         |------------|
  baz:                                |---|
  foo:   |------------------|         |--------|

In this case as we step at (A) and land at (B) we leave 'bar' and
expect to stop in 'foo', GDB shouldn't automatically enter 'baz' as
that is a completely different inline function.  And this is, indeed,
what we see.

Co-Authored-By: Andrew Burgess <aburgess@redhat.com>
This commit is contained in:
Bernd Edlinger
2024-10-15 18:14:12 +01:00
committed by Andrew Burgess
parent b9de07a5ff
commit 5d9887ffa2
5 changed files with 644 additions and 0 deletions

View File

@@ -8114,6 +8114,34 @@ process_event_stop_test (struct execution_control_state *ecs)
return;
}
/* Handle the case when subroutines have multiple ranges. When we step
from one part to the next part of the same subroutine, all subroutine
levels are skipped again which begin here. Compensate for this by
removing all skipped subroutines, which were already executing from
the user's perspective. */
if (get_stack_frame_id (frame)
== ecs->event_thread->control.step_stack_frame_id
&& inline_skipped_frames (ecs->event_thread) > 0
&& ecs->event_thread->control.step_frame_id.artificial_depth > 0
&& ecs->event_thread->control.step_frame_id.code_addr_p)
{
int depth = 0;
const struct block *prev
= block_for_pc (ecs->event_thread->control.step_frame_id.code_addr);
const struct block *curr = block_for_pc (ecs->event_thread->stop_pc ());
while (curr != nullptr && !curr->contains (prev))
{
if (curr->inlined_p ())
depth++;
else if (curr->function () != nullptr)
break;
curr = curr->superblock ();
}
while (inline_skipped_frames (ecs->event_thread) > depth)
step_into_inline_frame (ecs->event_thread);
}
/* Look for "calls" to inlined functions, part one. If the inline
frame machinery detected some skipped call sites, we have entered
a new inline function. */