From: Simon Marchi <simark@simark.ca>
To: Andrew Burgess <aburgess@redhat.com>, gdb-patches@sourceware.org
Subject: Re: [PATCHv6 3/4] gdb: allow 'until' to work in outermost frame
Date: Fri, 10 Jul 2026 13:00:02 -0400 [thread overview]
Message-ID: <848a1bb5-78d6-4bc3-93b1-ec195b8615c9@simark.ca> (raw)
In-Reply-To: <2a4ac1c0763d6ea9c3f97855b19ef91c07d35f19.1783693321.git.aburgess@redhat.com>
On 7/10/26 10:24 AM, Andrew Burgess wrote:
> The 'until' command with an argument, e.g. 'until *ADDRESS', is
> implemented by the until_break_command in breakpoint.c.
>
> The most important thing this function does is convert the location
> argument '*ADDRESS' in my example, into a vector of symtab_and_line
> objects. Each of these symtab_and_line objects is then used to create
> a temporary bp_until breakpoint.
>
> The other thing that until_break_command does is create a breakpoint
> in the caller frame. This breakpoint is there to ensure the inferior
> stops upon exiting the frame in which the 'until' command was issued,
> if the until breakpoint was not hit.
>
> When compiling an assembler file into a static test program, if I use
> 'starti' to stop the inferior within the outermost frame, and then try
> to use 'until *ADDRESS' I see the following error:
>
> (gdb) starti
> Starting program: /tmp/hello
>
> Program stopped.
> 0x0000000000401000 in _start ()
> (gdb) bt
> #0 0x0000000000401000 in _start ()
> (gdb) until *0x0000000000401015
> Warning:
> Cannot insert breakpoint 0.
> Cannot access memory at address 0x1
>
> Command aborted.
> (gdb)
>
> The problem here is the breakpoint that 'until' tries to create in the
> caller frame. Though the 'bt' in the above example indicates that
> there is only a single frame, the outermost frame, this is only
> because GDB has specific code in get_prev_frame to stop the backtrace
> at the outermost frame. If we turn this off and try 'bt' again:
>
> (gdb) set backtrace past-entry on
> (gdb) bt
> #0 0x0000000000401000 in _start ()
> #1 0x0000000000000001 in ?? ()
> #2 0x00007fffffffac73 in ?? ()
> #3 0x0000000000000000 in ?? ()
> (gdb)
>
> What we are seeing here is the garbage values that happen to be in the
> registers tricking GDB into thinking there are frames before _start.
>
> GDB's code to handle this is in get_prev_frame, where we call
> inside_entry_func. This checks if a frame is one of the two possible
> entry frames, the inferior entry frame or the executable entry frame.
> See the previous commit for more details. The important thing is that
> the inferior entry frame is the absolute outer frame, the very first
> frame that the inferior executed when starting, while the executable
> entry frame is just the first frame within the main executable.
>
> There are a number of user configurable filters in get_prev_frame, the
> backtrace past-main filter, the backtrace frame limit filter, and the
> backtrace past-entry filter that we are discussing here.
>
> When creating the caller frame breakpoint, the 'until' command doesn't
> use get_prev_frame, it uses get_prev_frame_always. This is so that
> the user configurable filters don't prevent the caller frame
> breakpoint from being created. But this means that when creating the
> breakpoint, we skip the entry frame check. As a result, the 'until'
> command will try to place a breakpoint in the bogus frame #1 shown
> above. As the frame is at address 0x1, which is non-writable, we see
> an error when trying to insert the breakpoint.
>
> In this commit I propose that we split the inside_entry_func handling
> in to two parts. In get_prev_frame we will retain the check for the
> executable entry frame. In theory it is possible that there are
> frames between the executable entry frame and the inferior entry
> frame. These are the frames the 'backtrace past-entry' can hide or
> reveal (if the frames can be discovered).
>
> The check for the inferior entry frame I propose moving into
> get_prev_frame_always. I think this is a better place for it because
> any frames before the inferior entry frame are almost certainly
> bogus. As such we want to elide those frames even for "inner" use
> cases, like the caller frame breakpoint of the 'until' command.
>
> The test for this fix ran into an issue where the frame-id for the
> outermost frame would change between the first instruction and later
> instructions in the frame. I created bug PR gdb/34245 for this issue.
>
> Bug: https://sourceware.org/bugzilla/show_bug.cgi?id=34245
> ---
> gdb/frame.c | 103 +++++++----
> gdb/testsuite/gdb.base/until-in-entry-frame.c | 22 +++
> .../gdb.base/until-in-entry-frame.exp | 166 ++++++++++++++++++
> 3 files changed, 259 insertions(+), 32 deletions(-)
> create mode 100644 gdb/testsuite/gdb.base/until-in-entry-frame.c
> create mode 100644 gdb/testsuite/gdb.base/until-in-entry-frame.exp
>
> diff --git a/gdb/frame.c b/gdb/frame.c
> index 782ca33abd8..a84cca5b81c 100644
> --- a/gdb/frame.c
> +++ b/gdb/frame.c
> @@ -2232,6 +2232,63 @@ frame_register_unwind_location (const frame_info_ptr &initial_this_frame,
> }
> }
>
> +/* When checking if a frame is an entry frame, there are two different
> + entry frames to consider. This enum is used to choose between them. */
> +
> +enum class entry_address_type
> +{
> + /* The executable's entry frame. This is the first frame within the main
> + executable. */
> + executable,
> +
> + /* The inferior's entry frame. This is the first frame within the
> + inferior, this can be outside the main executable, e.g. for a
> + dynamically linked executable, this will be the first frame in the
> + dynamic linker. */
> + inferior,
> +};
> +
> +/* Test whether THIS_FRAME is inside the TYPE process entry point
> + function. */
> +
> +static bool
> +inside_entry_func (const frame_info_ptr &this_frame,
> + entry_address_type type)
> +{
> + const program_space::entry_point_info &ep_info
> + = current_program_space->get_entry_point_info ();
> +
> + CORE_ADDR frame_func_addr;
> + if (!get_frame_func_if_available (this_frame, &frame_func_addr))
> + return false;
> +
> + switch (type)
> + {
> + case entry_address_type::executable:
> + return ep_info.exec_entry_address () == frame_func_addr;
> +
> + case entry_address_type::inferior:
> + return ep_info.inferior_entry_address () == frame_func_addr;
> + }
> +
> + gdb_assert_not_reached ("unknown entry address type: %d", ((int) type));
> +}
Given that this code path allows reading just one of the two entry
address types, it would perhaps be good to by pass
program_spaceget_entry_point_info (which returns both), and just go
fetch the one that is requested. The other one will inevitably be
wasted. Not a big deal if both branches are reasonably fast to fetch,
but still it shouldn't be difficult to fetch just the right one.
> +
> +/* Debug routine to print a NULL frame being returned. */
> +
> +static void
> +frame_debug_got_null_frame (const frame_info_ptr &this_frame,
> + const char *reason)
> +{
> + if (frame_debug)
> + {
> + if (this_frame != NULL)
> + frame_debug_printf ("this_frame=%d -> %s", this_frame->level, reason);
> + else
> + frame_debug_printf ("this_frame=nullptr -> %s", reason);
> + }
> +}
> +
> /* Get the previous raw frame, and check that it is not identical to
> same other frame frame already in the chain. If it is, there is
> most likely a stack cycle, so we discard it, and mark THIS_FRAME as
> @@ -2538,6 +2595,17 @@ get_prev_frame_always_1 (const frame_info_ptr &this_frame)
> frame_info_ptr
> get_prev_frame_always (const frame_info_ptr &this_frame)
> {
> + if (this_frame->level >= 0
> + && get_frame_type (this_frame) == NORMAL_FRAME
> + && !user_set_backtrace_options.backtrace_past_entry
> + && inside_entry_func (this_frame, entry_address_type::inferior))
get_prev_frame_always is called quite often, inside_entry_func is going
to be called quite often (the other parts of the conjunction are often
going to be true), inside_entry_func calls
program_space::get_entry_point_info, which calls
solib_ops::inferior_entry_point_address.
svr4_solib_ops::inferior_entry_point_address is somewhat heavy (ELF
parsing and all). I wonder if the inferior entry point should be
cached, as it's not going to change often.
I put a printf in svr4_solib_ops::inferior_entry_point_address, attached
to gnome-calculator (with debuginfod, so debug info for all the libs)
and ran a backtrace. For 10 frames,
svr4_solib_ops::inferior_entry_point_address was called 242 times.
Perhaps it's premature optimization, but it just feels wrong.
> + {
> + this_frame->prev_p = true;
> + this_frame->stop_reason = UNWIND_OUTERMOST;
> + frame_debug_got_null_frame (this_frame, "inside inferior entry func");
> + return nullptr;
> + }
I notice that this makes get_prev_frame_always modify a persistent
state (stop_reason) of `this_frame` based on a setting that could change
from one call to another (options.backtrace_past_entry). For instance,
what happens to "bt -past-entry" after this runs?
> static bool
> @@ -2689,19 +2742,6 @@ inside_main_func (const frame_info_ptr &this_frame)
> return sym_addr == get_frame_func (this_frame);
> }
>
> -/* Test whether THIS_FRAME is inside the process entry point function. */
> -
> -static bool
> -inside_entry_func (const frame_info_ptr &this_frame)
> -{
> - const program_space::entry_point_info &ep_info
> - = current_program_space->get_entry_point_info ();
> -
> - CORE_ADDR frame_func_addr = get_frame_func (this_frame);
> - return (ep_info.exec_entry_address () == frame_func_addr
> - || ep_info.inferior_entry_address () == frame_func_addr);
> -}
> -
> /* Return a structure containing various interesting information about
> the frame that called THIS_FRAME. Returns NULL if there is either
> no such frame or the frame fails any of a set of target-independent
> @@ -2783,11 +2823,10 @@ get_prev_frame (const frame_info_ptr &this_frame)
> if (this_frame->level >= 0
> && get_frame_type (this_frame) == NORMAL_FRAME
> && !user_set_backtrace_options.backtrace_past_entry
> - && frame_pc.has_value ()
Can you explain the removal of this frame_pc.has_value() line? Probably
fine, but I want to be sure.
Simon
next prev parent reply other threads:[~2026-07-10 17:00 UTC|newest]
Thread overview: 54+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-06-08 18:14 [PATCH] " Andrew Burgess
2026-06-11 19:24 ` Guinevere Larsen
2026-06-11 20:40 ` Andrew Burgess
2026-06-12 12:49 ` Guinevere Larsen
2026-06-11 21:59 ` [PATCHv2 0/3] " Andrew Burgess
2026-06-11 21:59 ` [PATCHv2 1/3] gdb: rename program_space::entry_point_address* functions Andrew Burgess
2026-06-12 15:41 ` Pedro Alves
2026-06-11 21:59 ` [PATCHv2 2/3] gdb: introduce program_space::get_entry_point_info function Andrew Burgess
2026-06-12 6:07 ` Eli Zaretskii
2026-06-15 10:14 ` Andrew Burgess
2026-06-15 12:01 ` Eli Zaretskii
2026-06-12 15:41 ` Pedro Alves
2026-06-15 10:29 ` Andrew Burgess
2026-06-16 18:36 ` Pedro Alves
2026-06-23 9:46 ` Andrew Burgess
2026-06-23 10:20 ` Pedro Alves
2026-06-11 21:59 ` [PATCHv2 3/3] gdb: allow 'until' to work in outermost frame Andrew Burgess
2026-06-12 15:57 ` Pedro Alves
2026-06-16 19:47 ` [PATCHv3 0/4] " Andrew Burgess
2026-06-16 19:47 ` [PATCHv3 1/4] gdb: rename program_space::entry_point_address* functions Andrew Burgess
2026-06-16 19:47 ` [PATCHv3 2/4] gdb: introduce program_space::get_entry_point_info function Andrew Burgess
2026-06-17 11:52 ` Eli Zaretskii
2026-06-16 19:47 ` [PATCHv3 3/4] gdb: allow 'until' to work in outermost frame Andrew Burgess
2026-06-16 19:47 ` [PATCHv3 4/4] gdb: cache program space entry point information Andrew Burgess
2026-06-23 10:47 ` [PATCHv4 0/4] gdb: allow 'until' to work in outermost frame Andrew Burgess
2026-06-23 10:47 ` [PATCHv4 1/4] gdb: rename program_space::entry_point_address* functions Andrew Burgess
2026-06-23 10:47 ` [PATCHv4 2/4] gdb: introduce program_space::get_entry_point_info function Andrew Burgess
2026-06-23 10:47 ` [PATCHv4 3/4] gdb: allow 'until' to work in outermost frame Andrew Burgess
2026-06-23 10:47 ` [PATCHv4 4/4] gdb: cache program space entry point information Andrew Burgess
2026-06-25 15:26 ` [PATCHv5 0/4] gdb: allow 'until' to work in outermost frame Andrew Burgess
2026-06-25 15:26 ` [PATCHv5 1/4] gdb: rename program_space::entry_point_address* functions Andrew Burgess
2026-06-25 15:26 ` [PATCHv5 2/4] gdb: introduce program_space::get_entry_point_info function Andrew Burgess
2026-06-25 15:26 ` [PATCHv5 3/4] gdb: allow 'until' to work in outermost frame Andrew Burgess
2026-06-25 15:26 ` [PATCHv5 4/4] gdb: cache program space entry point information Andrew Burgess
2026-07-10 14:24 ` [PATCHv6 0/4] gdb: allow 'until' to work in outermost frame Andrew Burgess
2026-07-10 14:24 ` [PATCHv6 1/4] gdb: rename program_space::entry_point_address* functions Andrew Burgess
2026-07-10 14:24 ` [PATCHv6 2/4] gdb: introduce program_space::get_entry_point_info function Andrew Burgess
2026-07-10 15:39 ` Simon Marchi
2026-07-10 14:24 ` [PATCHv6 3/4] gdb: allow 'until' to work in outermost frame Andrew Burgess
2026-07-10 17:00 ` Simon Marchi [this message]
2026-07-10 17:01 ` Simon Marchi
2026-07-16 15:06 ` Andrew Burgess
2026-07-10 14:24 ` [PATCHv6 4/4] gdb: cache program space entry point information Andrew Burgess
2026-07-10 17:07 ` Simon Marchi
2026-07-18 13:11 ` [PATCHv7 0/4] gdb: allow 'until' to work in outermost frame Andrew Burgess
2026-07-18 13:11 ` [PATCHv7 1/4] gdb: rename program_space::entry_point_address* functions Andrew Burgess
2026-07-18 13:11 ` [PATCHv7 2/4] gdb: introduce program_space::get_entry_point_info function Andrew Burgess
2026-07-18 13:11 ` [PATCHv7 3/4] gdb: allow 'until' to work in outermost frame Andrew Burgess
2026-07-18 13:11 ` [PATCHv7 4/4] gdb: cache program space entry point information Andrew Burgess
2026-07-20 9:52 ` [PATCHv8 0/4] gdb: allow 'until' to work in outermost frame Andrew Burgess
2026-07-20 9:52 ` [PATCHv8 1/4] gdb: rename program_space::entry_point_address* functions Andrew Burgess
2026-07-20 9:52 ` [PATCHv8 2/4] gdb: introduce program_space::get_entry_point_info function Andrew Burgess
2026-07-20 9:52 ` [PATCHv8 3/4] gdb: allow 'until' to work in outermost frame Andrew Burgess
2026-07-20 9:52 ` [PATCHv8 4/4] gdb: cache program space entry point information 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=848a1bb5-78d6-4bc3-93b1-ec195b8615c9@simark.ca \
--to=simark@simark.ca \
--cc=aburgess@redhat.com \
--cc=gdb-patches@sourceware.org \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox