From: "Rohr, Stephan" <stephan.rohr@intel.com>
To: Andrew Burgess <aburgess@redhat.com>
Cc: "gdb-patches@sourceware.org" <gdb-patches@sourceware.org>,
Tom Tromey <tom@tromey.com>
Subject: RE: [PATCH 1/1] gdb: set the cache information in 'get_prev_frame_maybe_check_cycle'
Date: Wed, 8 Jul 2026 08:46:51 +0000 [thread overview]
Message-ID: <DS7PR11MB62473E2BE06FB064B704321993FF2@DS7PR11MB6247.namprd11.prod.outlook.com> (raw)
In-Reply-To: <87ldbmu6py.fsf@redhat.com>
HI Andrew,
Thanks for sharing. The patch itself looks good.
I have a few comments regarding the commit message, see below.
Thanks
Stephan
> -----Original Message-----
> From: Andrew Burgess <aburgess@redhat.com>
> Sent: Tuesday, 7 July 2026 18:04
> To: Rohr, Stephan <stephan.rohr@intel.com>
> Cc: gdb-patches@sourceware.org; Tom Tromey <tom@tromey.com>
> Subject: RE: [PATCH 1/1] gdb: set the cache information in
> 'get_prev_frame_maybe_check_cycle'
>
> "Rohr, Stephan" <stephan.rohr@intel.com> writes:
>
> > Hi Andrew,
> >
> > thanks for sharing this!
> >
> > One option that could cause a frame_info_ptr w/o a set ID is
> > the one I found in 'get_prev_frame_maybe_check_cycle ()'.
> >
> > It is not obvious why this causes an assertion when reinflating the
> > pointer later. My explanation how to reproduce the bug is:
> >
> > 1. The 'up' command updates 'selected_frame' to the frame at
> > level 1.
> >
> > 2. The inferior call 'p f()' triggers a re-init of the frame cache. The
> > selected_frame pointer is nulled, but the cached id is still valid.
> >
> > 3. The 'frame' command find 'selected_frame' is null, so it rebuilds
> > by calling get_selected_frame --> lookup_selected_frame
> > --> get_prev_frame --> get_prev_frame_maybe_check_cycle.
> > A new frame_info_ptr is setup but its ID is only computed a few lines
> > later. select_frame () then copies this uncached frame_info_ptr into
> > selected frame.
> >
> > 4. The pretty-printer triggers another infcall:
> >
> > 'MytypePrinter.to_string()' --> gdb.parse_and_eval("f()")
> > --> call_function_by_hand ()
> >
> > This invalidates the pointer while printing the 'mt' argument.
> > When printing the next argument, the pointer is reinflated via
> > read_frame_arg --> ... --> get_frame_pc --> reinflate. Since level > 0
> > and m_ptr == nullptr, reinflation is done by the cached id. This is not
> > set and causes the assertion to fire.
> >
> > Does this help?
> >
> > In general, I'm in favour of your patch as it is more generic and fixes all
> > issues where we have a frame_info_ptr setup w/o an ID.
> >
> > I have a few comments, see below.
> >
> > Thanks
> > Stephan
> >
> >> -----Original Message-----
> >> From: Andrew Burgess <aburgess@redhat.com>
> >> Sent: Monday, 6 July 2026 17:07
> >> To: Rohr, Stephan <stephan.rohr@intel.com>; Tom Tromey
> >> <tom@tromey.com>
> >> Cc: gdb-patches@sourceware.org
> >> Subject: RE: [PATCH 1/1] gdb: set the cache information in
> >> 'get_prev_frame_maybe_check_cycle'
> >>
> >> "Rohr, Stephan" <stephan.rohr@intel.com> writes:
> >>
> >> > Hi Tom,
> >> >
> >> > thank you for your feedback! I will fix the typos in v2 of the patch.
> >> >
> >> > I'll try to explain the problem in more detail now:
> >> >
> >> > 1. If you run an inferior call after the up command invalidates
> >> > the selected frame.
> >> > 2. The selected frame is rebuild following the chain
> >> >
> >> > get_selected_frame () -> lookup_selected_frame ()
> >> > -> get_prev_frame_always_1 ()
> >> > -> get_prev_frame_maybe_check_cycle ()
> >> >
> >> > 3. At this point, the frame_info_ptr is constructed before the
> >> > frame-id is computed. The cache Information is not updated as
> >> > 'compute_frame_id' only updates the frame id of the internal
> >> > 'frame_info' pointer.
> >> >
> >> > 4. If we now run the "frame" command in combination with a
> >> > pretty-printer, this invokes another infcall to evaluate the
> >> > pretty-printer.
> >> >
> >> > 5. This clears the 'm_ptr' member of the frame_info_ptr. Since the cache
> >> > id is not set, a subsequent reinflate fails.
> >> >
> >> > A very simple fix for this could be:
> >> >
> >> > @@ -2332,7 +2361,7 @@ get_prev_frame_maybe_check_cycle (const
> >> frame_info_ptr &this_frame)
> >> > throw;
> >> > }
> >> >
> >> > - return prev_frame;
> >> > + return frame_info_ptr (prev_frame.get ());
> >> > }
> >> >
> >> > It constructs a new frame_info_ptr using the updated raw pointer
> >> > of 'prev_frame'. This is somewhat redundant as another frame_info_ptr
> >> > is added to the frame list in the frame_info_ptr ctor.
> >> >
> >> > The frame list entry from
> >> >
> >> > frame_info_ptr prev_frame = get_prev_frame_raw (this_frame);
> >> >
> >> > is removed again by the frame_info_ptr's dtor.
> >> >
> >> > This would add some overhead.
> >> >
> >> > I didn't find a better solution. We could modify 'get_prev_frame_raw' to
> >> > return a raw pointer instead (it is the only location where is this called at
> >> > all). But we'd need a temporary copy of the frame_info_ptr anyways,
> >> > either to pass a frame_info_ptr to 'compute_frame_id' or inside of
> >> > 'compute_frame_id' (to forward the frame id to
> >> > 'frame_unwind_find_by_frame'). Changing these functions to
> >> > accept a raw frame_info pointer is not desired in my point of view.
> >> >
> >> > I appreciate your feedback.
> >> >
> >>
> >> Hi Stephan,
> >>
> >> I ran into a similar problem myself recently, and had a different fix
> >> queued up, but it was part of a larger change. I've pulled it out and
> >> the patch is below, along with your test change. The test still passes.
> >>
> >> There's no commit message yet, I still need to think about exactly
> >> what's going on in this case a bit more, it's still not clear to me how
> >> (or where) the frame_info_ptr without the frame-id is actually created,
> >> as I thought frames (after 0) always got a frame-id before they were
> >> placed into a frame_info_ptr.
> >>
> >> I'm sharing this now just so you can take a look at the change and let
> >> me know what you think, I'll dig into this a little more and write up a
> >> commit message tomorrow, but I'd like to hear what you think of this
> >> approach.
> >>
> >> Thanks,
> >> Andrew
> >>
> >> ---
> >>
> >> diff --git a/gdb/frame.c b/gdb/frame.c
> >> index cefdde5ed1e..7a0f313f4db 100644
> >> --- a/gdb/frame.c
> >> +++ b/gdb/frame.c
> >> @@ -2190,15 +2190,15 @@ reinit_frame_cache (void)
> >> sentinel_frame = nullptr;
> >> }
> >>
> >> + for (frame_info_ptr &iter : frame_info_ptr::frame_list)
> >> + iter.invalidate ();
> >> +
> >
> > I see why we need this, but it's worth a comment that the invalidate
> > loop must run before invalidating the frame stash.
>
> Added in my next version.
>
> >
> >> frame_stash_invalidate ();
> >>
> >> /* Since we can't really be sure what the first object allocated was. */
> >> obstack_free (&frame_cache_obstack, 0);
> >> obstack_init (&frame_cache_obstack);
> >>
> >> - for (frame_info_ptr &iter : frame_info_ptr::frame_list)
> >> - iter.invalidate ();
> >> -
> >> frame_debug_printf ("generation=%d", frame_cache_generation);
> >> }
> >>
> >> @@ -3435,10 +3435,21 @@ frame_info_ptr::frame_info_ptr (struct
> >> frame_info *ptr)
> >> if (m_ptr == nullptr)
> >> return;
> >>
> >> - m_cached_level = ptr->level;
> >> + m_cached_level = m_ptr->level;
> >
> > Do we need this? At this point, we should have m_ptr == ptr?
>
> I'll revert this for the final patch.
>
> >
> >> +}
> >> +
> >> +void
> >> +frame_info_ptr::invalidate ()
> >> +{
> >> + if (m_ptr == nullptr)
> >> + return;
> >> +
> >> + gdb_assert (m_cached_level == m_ptr->level);
> >>
> >> if (m_cached_level != 0 || m_ptr->this_id.value.user_created_p)
> >> m_cached_id = m_ptr->this_id.value;
> >
> > I was wondering if we should add another guard:
> >
> > if ((m_cached_id == null_frame_id)
> > && (m_cached_level != 0 || m_ptr->this_id.value.user_created_p))
> >
> > Or is this implicitly guarded by the 'm_cached_level != 0' check?
>
> Comparing to null_frame_id always returns false, see
> frame_id::operator== for details.
>
> If we _could_ compare to null_frame_id then we could add this assert:
>
> gdb_assert (m_cached_id == null_frame_id
> || m_cached_id == m_ptr->this_id.value);
>
> This means that, if a frame_info_ptr is invalidated multiple times, then
> the m_cached_id will be updated multiple times, but it should never
> change. But, as we cannot compare to null_frame_id, I'm not sure how
> we'd write that assert. Given that we check the frame-id matches when
> we reinflate though, I'm not sure the above assert adds much.
>
> Your proposal (if we could compare to null_frame_id) would prevent the
> repeated updates, but isn't needed to prevent any invalid behaviour, for
> the same reason, the frame-id should never be different.
>
> The updated patch is below, let me know what you think.
>
> Thanks,
> Andrew
>
> ---
>
> commit d13aa44f8ac57e52324da409145f73a0e3ca19a2
> Author: Andrew Burgess <aburgess@redhat.com>
> Date: Thu Jun 25 14:58:50 2026 +0000
>
> gdb: set frame_info_ptr::m_cached_id in the destructor
I think this is misleading as it's not updated in the dtor but when the
frame_info_ptr is invalidated.
>
> Currently frame_info_ptr caches the frame_id at construction time, see
> frame_info_ptr::frame_info_ptr in frame.c. The problem with this is
> that a frame's frame-id might not be known at this point.
>
> Consider get_prev_frame_maybe_check_cycle, this calls
> get_prev_frame_raw to create the previous frame, placing the result
> into a frame_info_ptr PREV_FRAME. Then (for frames other than frame
> 0) compute_frame_id is called, however, this only computes the
> frame_id for the frame_info object pointed to by the frame_info_ptr,
> the cached frame_id within the frame_info_ptr itself is not updated.
>
> What this means is that in get_prev_frame_maybe_check_cycle, the
> PREV_FRAME local has no cached frame-id.
>
> If we consider the call stack:
>
> get_selected_frame
> lookup_selected_frame
> frame_find_by_id
> get_prev_frame
> get_prev_frame_always
> get_prev_frame_always_1
> get_prev_frame_maybe_check_cycle
>
> Then what we see is that the frame_info_ptr created in
> get_prev_frame_maybe_check_cycle, which lacks a cached frame_id, can
> be passed all the way back to lookup_selected_frame, where it will be
> stored in the SELECTED_FRAME global by a call to select_frame. The
> outer get_selected_frame call (in the above backtrace) will then
> return the SELECTED_FRAME global, which lacks a cached frame-id.
>
> If GDB ever tries to reinflate the SELECTED_FRAME frame_info_ptr (or a
> copy of it), then we will trigger the assert:
> `gdb_assert (frame_id_p (m_cached_id));` which can be found in
> `frame_info_ptr::reinflate` in frame.c.
>
> An example of how this can be triggered is included in the updated
> test case, frame #1 is selected and the frame is printed. The pretty
> printer performs an inferior call which invalidates the frame cache,
> the assertion then triggers when trying to reinflate the selected
> frame frame_info_ptr.
>
We can put a bit more detail here, e.g.:
An example of how this can be triggered is included in the updated
test case:
- The 'up' command sets the selected frame to a frame with level > 0.
- An inferior call invalidates the selected frame.
- The selected frame is rebuild following the call-stack above.
The wrapping frame_info_ptr object doesn't cache the frame-id.
- If the frame is printed with the 'frame' command, this invokes another
inferior call for the pretty printer, which flushes the frame cache.
- If the frame_info_ptr is reinflated, this hits the assertion mentioned above, e.g.,
to print the next argument.
> The problem is that frame's don't always know their frame-id when they
> are placed into a frame_info_ptr, but they always do (for frame other
> than #0) after get_prev_frame_maybe_check_cycle has finished. We
> could try to have get_prev_frame_maybe_check_cycle or
> compute_frame_id
> push the computed frame-id into the frame_info_ptr, or we can just
> defer caching the frame-id until we know we might need it, i.e. when
> the frame cache is being flushed.
>
> This second approach is actually nice in that it defers the work until
> we know we need it, and frame_info_ptr objects that are created and
> destroyed without the frame cache ever being flushed no longer need to
> cache the frame-id. This isn't going to give any noticable
> performance improvement, but still, it feels nice.
>
I think we don't need to explain the two options here. Maybe add one sentence
summarizing the change, e.g.:
Update the cache-id of the frame_info_ptr before removing flushing the frame-cache.
> The changes in this commit then are:
>
> 1. In reinit_frame_cache we call frame_info_ptr::invalidate before
> deleting all the frame_info objects (by clearing the obstacks),
> this allows us to copy the frame_id from these objects.
>
> 2. In frame_info_ptr::frame_info_ptr we still need to record every
> frame_info_ptr in the global list, and for now at least, we still
> cache the frame level, this is needed for
> frame_info_ptr::is_null, which checks the cached level.
>
> 3. In frame_info::invalidate, we can assert that the cached level
> matches the stored frame's level, this should never change, and
> it is here that we now cache the frame-id.
>
I think we don't need items 1. to 3. as part of the commit message.
> Co-Authored-By: Rohr, Stephan <stephan.rohr@intel.com>
>
> diff --git a/gdb/frame.c b/gdb/frame.c
> index cefdde5ed1e..e7974060cb8 100644
> --- a/gdb/frame.c
> +++ b/gdb/frame.c
> @@ -2190,15 +2190,18 @@ reinit_frame_cache (void)
> sentinel_frame = nullptr;
> }
>
> + /* Invalidation copies the frame-id from the managed frame_info object
> + into the frame_info_ptr, so this must run before the frame_info
> + objects are invalidated. */
> + for (frame_info_ptr &iter : frame_info_ptr::frame_list)
> + iter.invalidate ();
> +
> frame_stash_invalidate ();
>
> /* Since we can't really be sure what the first object allocated was. */
> obstack_free (&frame_cache_obstack, 0);
> obstack_init (&frame_cache_obstack);
>
> - for (frame_info_ptr &iter : frame_info_ptr::frame_list)
> - iter.invalidate ();
> -
> frame_debug_printf ("generation=%d", frame_cache_generation);
> }
>
> @@ -3436,9 +3439,23 @@ frame_info_ptr::frame_info_ptr (struct
> frame_info *ptr)
> return;
>
> m_cached_level = ptr->level;
> +}
>
> +void
> +frame_info_ptr::invalidate ()
> +{
> + if (m_ptr == nullptr)
> + return;
> +
> + gdb_assert (m_cached_level == m_ptr->level);
> +
> + /* If a frame_info_ptr is invalidated multiple times then we will end up
> + updating m_cached_id multiple times. This should be harmless as the
> + underlying frame_id should never change. */
> if (m_cached_level != 0 || m_ptr->this_id.value.user_created_p)
> m_cached_id = m_ptr->this_id.value;
> +
> + m_ptr = nullptr;
> }
>
> /* See frame-info-ptr.h. */
> diff --git a/gdb/frame.h b/gdb/frame.h
> index f6553fb7b6d..b386303d895 100644
> --- a/gdb/frame.h
> +++ b/gdb/frame.h
> @@ -327,10 +327,7 @@ class frame_info_ptr : public
> intrusive_list_node<frame_info_ptr>
> }
>
> /* Invalidate this pointer. */
> - void invalidate ()
> - {
> - m_ptr = nullptr;
> - }
> + void invalidate ();
>
> private:
> /* We sometimes need to construct frame_info_ptr objects around the
> diff --git a/gdb/testsuite/gdb.python/pretty-print-call-by-hand.exp
> b/gdb/testsuite/gdb.python/pretty-print-call-by-hand.exp
> index 52162fc9952..a2a29c4d0f8 100644
> --- a/gdb/testsuite/gdb.python/pretty-print-call-by-hand.exp
> +++ b/gdb/testsuite/gdb.python/pretty-print-call-by-hand.exp
> @@ -108,6 +108,8 @@ with_test_prefix "frame movement down" {
> with_test_prefix "frame movement up" {
> if { [start_test "TAG: final frame"] == 0 } {
> gdb_test "up" [multi_line "#1 .*in g \\(mt=mytype is .*\\,
> depth=1\\).*" ".*first frame.*"]
> + gdb_test "p f ()" " = 2"
> + gdb_test "frame" [multi_line "#1 .*in g \\(mt=mytype is .*\\,
> depth=1\\).*" ".*first frame.*"]
> }
> }
>
Intel Deutschland GmbH
Registered Address: Dornacher Strasse 1, 85622 Feldkirchen, Germany
Tel: +49 89 991 430, www.intel.de
Managing Directors: Harry Demas, Jeffrey Schneiderman, Yin Chong Sorrell
Chairperson of the Supervisory Board: Nicole Lau
Registered Seat: Munich
Commercial Register: Amtsgericht Muenchen HRB 186928
next prev parent reply other threads:[~2026-07-08 8:47 UTC|newest]
Thread overview: 17+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-06-25 14:58 [PATCH 0/1] " Stephan Rohr
2026-06-25 14:58 ` [PATCH 1/1] " Stephan Rohr
2026-06-26 15:06 ` Tom Tromey
2026-06-29 14:04 ` Rohr, Stephan
2026-07-06 15:06 ` Andrew Burgess
2026-07-07 7:57 ` Rohr, Stephan
2026-07-07 16:04 ` Andrew Burgess
2026-07-08 8:46 ` Rohr, Stephan [this message]
2026-07-08 13:59 ` Andrew Burgess
2026-07-08 15:18 ` Rohr, Stephan
2026-07-14 15:14 ` Andrew Burgess
2026-07-15 7:33 ` Rohr, Stephan
2026-07-18 11:57 ` Andrew Burgess
2026-07-20 17:39 ` Tom Tromey
2026-07-07 10:33 ` Andrew Burgess
2026-07-07 12:25 ` Rohr, Stephan
2026-07-07 15:04 ` Andrew Burgess
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=DS7PR11MB62473E2BE06FB064B704321993FF2@DS7PR11MB6247.namprd11.prod.outlook.com \
--to=stephan.rohr@intel.com \
--cc=aburgess@redhat.com \
--cc=gdb-patches@sourceware.org \
--cc=tom@tromey.com \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox