From: Alexandra Petlanova Hajkova <ahajkova@redhat.com>
To: Simon Marchi <simon.marchi@efficios.com>
Cc: gdb-patches@sourceware.org
Subject: Re: [PATCH 2/2] gdb: give sentinel for user frames distinct IDs, register sentinel frames to the frame cache
Date: Tue, 7 Feb 2023 18:02:34 +0100 [thread overview]
Message-ID: <CAJVr-EOvFGgVbeEM3kK0fuiLxmPYYMNYRF04iyDS4onxX2Q7-A@mail.gmail.com> (raw)
In-Reply-To: <20230130200249.131155-3-simon.marchi@efficios.com>
[-- Attachment #1: Type: text/plain, Size: 18732 bytes --]
On Mon, Jan 30, 2023 at 9:04 PM Simon Marchi via Gdb-patches <
gdb-patches@sourceware.org> wrote:
> The test gdb.base/frame-view.exp fails like this on AArch64:
>
> frame^M
> #0 baz (z1=hahaha, /home/simark/src/binutils-gdb/gdb/value.c:4056:
> internal-error: value_fetch_lazy_register: Assertion `next_frame != NULL'
> failed.^M
> A problem internal to GDB has been detected,^M
> further debugging may prove unreliable.^M
> FAIL: gdb.base/frame-view.exp: with_pretty_printer=true: frame (GDB
> internal error)
>
> The sequence of events leading to this is the following:
>
> - When we create the user frame (the "select-frame view" command), we
> create a sentinel frame just for our user-created frame, in
> create_new_frame. This sentinel frame has the same id as the regular
> sentinel frame.
>
> - When printing the frame, after doing the "select-frame view" command,
> the argument's pretty printer is invoked, which does an inferior
> function call (this is the point of the test). This clears the frame
> cache, including the "real" sentinel frame, which sets the
> sentinel_frame global to nullptr.
>
> - Later in the frame-printing process (when printing the second
> argument), the auto-reinflation mechanism re-creates the user frame
> by calling create_new_frame again, creating its own special sentinel
> frame again. However, note that the "real" sentinel frame, the
> sentinel_frame global, is still nullptr. If the selected frame had
> been a regular frame, we would have called get_current_frame at some
> point during the reinflation, which would have re-created the "real"
> sentinel frame. But it's not the case when reinflating a user frame.
>
> - Deep down the stack, something wants to fill in the unwind stop
> reason for frame 0, which requires trying to unwind frame 1. This
> leads us to trying to unwind the PC of frame 1:
>
> #0 gdbarch_unwind_pc (gdbarch=0xffff8d010080, next_frame=...) at
> /home/simark/src/binutils-gdb/gdb/gdbarch.c:2955
> #1 0x000000000134569c in dwarf2_tailcall_sniffer_first
> (this_frame=..., tailcall_cachep=0xffff773fcae0,
> entry_cfa_sp_offsetp=0xfffff7f7d450)
> at /home/simark/src/binutils-gdb/gdb/dwarf2/frame-tailcall.c:390
> #2 0x0000000001355d84 in dwarf2_frame_cache (this_frame=...,
> this_cache=0xffff773fc928) at
> /home/simark/src/binutils-gdb/gdb/dwarf2/frame.c:1089
> #3 0x00000000013562b0 in dwarf2_frame_unwind_stop_reason
> (this_frame=..., this_cache=0xffff773fc928) at
> /home/simark/src/binutils-gdb/gdb/dwarf2/frame.c:1101
> #4 0x0000000001990f64 in get_prev_frame_always_1 (this_frame=...) at
> /home/simark/src/binutils-gdb/gdb/frame.c:2281
> #5 0x0000000001993034 in get_prev_frame_always (this_frame=...) at
> /home/simark/src/binutils-gdb/gdb/frame.c:2376
> #6 0x000000000199b814 in get_frame_unwind_stop_reason (frame=...) at
> /home/simark/src/binutils-gdb/gdb/frame.c:3051
> #7 0x0000000001359cd8 in dwarf2_frame_cfa (this_frame=...) at
> /home/simark/src/binutils-gdb/gdb/dwarf2/frame.c:1356
> #8 0x000000000132122c in dwarf_expr_context::execute_stack_op
> (this=0xfffff7f80170, op_ptr=0xffff8d8883ee "\217\002",
> op_end=0xffff8d8883ee "\217\002")
> at /home/simark/src/binutils-gdb/gdb/dwarf2/expr.c:2110
> #9 0x0000000001317b30 in dwarf_expr_context::eval
> (this=0xfffff7f80170, addr=0xffff8d8883ed "\234\217\002", len=1) at
> /home/simark/src/binutils-gdb/gdb/dwarf2/expr.c:1239
> #10 0x000000000131d68c in dwarf_expr_context::execute_stack_op
> (this=0xfffff7f80170, op_ptr=0xffff8d88840e "", op_end=0xffff8d88840e "")
> at /home/simark/src/binutils-gdb/gdb/dwarf2/expr.c:1811
> #11 0x0000000001317b30 in dwarf_expr_context::eval
> (this=0xfffff7f80170, addr=0xffff8d88840c "\221p", len=2) at
> /home/simark/src/binutils-gdb/gdb/dwarf2/expr.c:1239
> #12 0x0000000001314c3c in dwarf_expr_context::evaluate
> (this=0xfffff7f80170, addr=0xffff8d88840c "\221p", len=2, as_lval=true,
> per_cu=0xffff90b03700, frame=..., addr_info=0x0,
> type=0xffff8f6c8400, subobj_type=0xffff8f6c8400, subobj_offset=0)
> at /home/simark/src/binutils-gdb/gdb/dwarf2/expr.c:1078
> #13 0x000000000149f9e0 in dwarf2_evaluate_loc_desc_full
> (type=0xffff8f6c8400, frame=..., data=0xffff8d88840c "\221p", size=2,
> per_cu=0xffff90b03700, per_objfile=0xffff9070b980,
> subobj_type=0xffff8f6c8400, subobj_byte_offset=0, as_lval=true)
> at /home/simark/src/binutils-gdb/gdb/dwarf2/loc.c:1513
> #14 0x00000000014a0100 in dwarf2_evaluate_loc_desc
> (type=0xffff8f6c8400, frame=..., data=0xffff8d88840c "\221p", size=2,
> per_cu=0xffff90b03700, per_objfile=0xffff9070b980, as_lval=true)
> at /home/simark/src/binutils-gdb/gdb/dwarf2/loc.c:1557
> #15 0x00000000014aa584 in locexpr_read_variable
> (symbol=0xffff8f6cd770, frame=...) at
> /home/simark/src/binutils-gdb/gdb/dwarf2/loc.c:3052
>
> - AArch64 defines a special "prev register" function,
> aarch64_dwarf2_prev_register, to handle unwinding the PC. This
> function does
>
> frame_unwind_register_unsigned (this_frame, AARCH64_LR_REGNUM);
>
> - frame_unwind_register_unsigned ultimately creates a lazy register
> value, saving the frame id of this_frame->next. this_frame is the
> user-created frame, to this_frame->next is the special sentinel frame
> we created for it. So the saved ID is the sentinel frame ID.
>
> - When time comes to un-lazify the value, value_fetch_lazy_register
> calls frame_find_by_id, to find the frame with the ID we saved.
>
> - frame_find_by_id sees it's the sentinel frame ID, so returns the
> sentinel_frame global, which is, if you remember, nullptr.
>
> - We hit the `gdb_assert (next_frame != NULL)` assertion in
> value_fetch_lazy_register.
>
> The issues I see here are:
>
> - The ID of the sentinel frame created for the user-created frame is
> not distinguishable from the ID of the regular sentinel frame. So
> there's no way frame_find_by_id could find the right frame, in
> value_fetch_lazy_register.
> - Even if they had distinguishable IDs, sentinel frames created for
> user frames are not registered anywhere, so there's no easy way
> frame_find_by_id could find it.
>
> This patch addresses these two issues:
>
> - Give sentinel frames created for user frames their own distinct IDs
> - Register sentinel frames in the frame cache, so they can be found
> with frame_find_by_id.
>
> I initially had this split in two patches, but I then found that it was
> easier to explain as a single patch.
>
> Rergarding the first part of the change: with this patch, the sentinel
> frames created for user frames (in create_new_frame) still have
> stack_status == FID_STACK_SENTINEL, but their code_addr and stack_addr
> fields are now filled with the addresses used to create the user frame.
> This ensures this sentinel frame ID is different from the "target"
> sentinel frame ID, as well as any other "user" sentinel frame ID. If
> the user tries to create the same frame, with the same addresses,
> multiple times, create_sentinel_frame just reuses the existing frame.
> So we won't end up with multiple user sentinels with the same ID.
>
> Regular "target" sentinel frames remain with code_addr and stack_addr
> unset.
>
> The concrete changes for that part are:
>
> - Remove the sentinel_frame_id constant, since there isn't one
> "sentinel frame ID" now. Add the frame_id_build_sentinel function
> for building sentinel frame IDs and a is_sentinel_frame_id function
> to check if a frame id represents a sentinel frame.
> - Replace the sentinel_frame_id check in frame_find_by_id with a
> comparison to `frame_id_build_sentinel (0, 0)`. The sentinel_frame
> global is meant to contain a reference to the "target" sentinel, so
> the one with addresses (0, 0).
> - Add stack and code address parameters to create_sentinel_frame, to be
> able to create the various types of sentinel frames.
> - Adjust get_current_frame to create the regular "target" sentinel.
> - Adjust create_new_frame to create a sentinel with the ID specific to
> the created user frame.
> - Adjust sentinel_frame_prev_register to get the sentinel frame ID from
> the frame_info object, since there isn't a single "sentinel frame ID"
> now.
> - Change get_next_frame_sentinel_okay to check for a
> sentinel-frame-id-like frame ID, rather than for sentinel_frame
> specifically, since this function could be called with another
> sentinel frame (and we would want the assert to catch it).
>
> The rest of the change is about registering the sentinel frame in the
> frame cache:
>
> - Change frame_stash_add's assertion to allow sentinel frame levels
> (-1).
> - Make create_sentinel_frame add the frame to the frame cache.
> - Change the "sentinel_frame != NULL" check in reinit_frame_cache for a
> check that the frame stash is not empty. The idea is that if we only
> have some user-created frames in the cache when reinit_frame_cache is
> called, we probably want to emit the frames invalid annotation. The
> goal of that check is to avoid unnecessary repeated annotations, I
> suppose, so the "frame cache not empty" check should achieve that.
>
> After this change, I think we could theoritically get rid of the
> sentienl_frame global. That sentinel frame could always be found by
> looking up `frame_id_build_sentinel (0, 0)` in the frame cache.
> However, I left the global there to avoid slowing the typical case down
> for nothing. I however, noted in its comment that it is an
> optimization.
>
> With this fix applied, the gdb.base/frame-view.exp now passes for me on
> AArch64. value_of_register_lazy now saves the special sentinel frame ID
> in the value, and value_fetch_lazy_register is able to find that
> sentinel frame after the frame cache reinit and after the user-created
> frame was reinflated.
>
> Change-Id: I8b77b3448822c8aab3e1c3dda76ec434eb62704f
> ---
> gdb/frame-id.h | 10 ++++---
> gdb/frame.c | 63 ++++++++++++++++++++++++++++++++++----------
> gdb/frame.h | 10 +++++++
> gdb/sentinel-frame.c | 5 +++-
> 4 files changed, 70 insertions(+), 18 deletions(-)
>
> diff --git a/gdb/frame-id.h b/gdb/frame-id.h
> index 5978d99bba8c..8ddf7d114082 100644
> --- a/gdb/frame-id.h
> +++ b/gdb/frame-id.h
> @@ -128,12 +128,16 @@ struct frame_id
> /* For convenience. All fields are zero. This means "there is no
> frame". */
> extern const struct frame_id null_frame_id;
>
> -/* Sentinel frame. */
> -extern const struct frame_id sentinel_frame_id;
> -
> /* This means "there is no frame ID, but there is a frame". It should be
> replaced by best-effort frame IDs for the outermost frame, somehow.
> The implementation is only special_addr_p set. */
> extern const struct frame_id outer_frame_id;
>
> +/* Return true if ID represents a sentinel frame. */
> +static inline bool
> +is_sentinel_frame_id (frame_id id)
> +{
> + return id.stack_status == FID_STACK_SENTINEL;
> +}
> +
> #endif /* ifdef GDB_FRAME_ID_H */
> diff --git a/gdb/frame.c b/gdb/frame.c
> index fed961b2a8df..9235a2ceb38c 100644
> --- a/gdb/frame.c
> +++ b/gdb/frame.c
> @@ -49,7 +49,10 @@
> innermost frame.
>
> The current frame, which is the innermost frame, can be found at
> - sentinel_frame->prev. */
> + sentinel_frame->prev.
> +
> + This is an optimization to be able to find the sentinel frame quickly,
> + it could otherwise be found in the frame cache. */
>
> static frame_info *sentinel_frame;
>
> @@ -294,8 +297,8 @@ frame_stash_create (void)
> static bool
> frame_stash_add (frame_info *frame)
> {
> - /* Do not try to stash the sentinel frame. */
> - gdb_assert (frame->level >= 0);
> + /* Valid frame levels are -1 (sentinel frames) and above. */
> + gdb_assert (frame->level >= -1);
>
> frame_info **slot = (frame_info **) htab_find_slot (frame_stash,
> frame, INSERT);
> @@ -681,7 +684,6 @@ frame_unwind_caller_id (frame_info_ptr next_frame)
> }
>
> const struct frame_id null_frame_id = { 0 }; /* All zeros. */
> -const struct frame_id sentinel_frame_id = { 0, 0, 0, FID_STACK_SENTINEL,
> 0, 1, 0 };
> const struct frame_id outer_frame_id = { 0, 0, 0, FID_STACK_OUTER, 0, 1,
> 0 };
>
> struct frame_id
> @@ -750,6 +752,29 @@ frame_id_build_wild (CORE_ADDR stack_addr)
> return id;
> }
>
> +/* See frame.h. */
> +
> +frame_id
> +frame_id_build_sentinel (CORE_ADDR stack_addr, CORE_ADDR code_addr)
> +{
> + frame_id id = null_frame_id;
> +
> + id.stack_status = FID_STACK_SENTINEL;
> + id.special_addr_p = 1;
> +
> + if (stack_addr != 0 || code_addr != 0)
> + {
> + /* The purpose of saving these in the sentinel frame ID is to be
> able to
> + differentiate the IDs of several sentinel frames that could exist
> + simultaneously in the frame cache. */
> + id.stack_addr = stack_addr;
> + id.code_addr = code_addr;
> + id.code_addr_p = 1;
> + }
> +
> + return id;
> +}
> +
> bool
> frame_id_p (frame_id l)
> {
> @@ -896,7 +921,7 @@ frame_find_by_id (struct frame_id id)
> return NULL;
>
> /* Check for the sentinel frame. */
> - if (id == sentinel_frame_id)
> + if (id == frame_id_build_sentinel (0, 0))
> return frame_info_ptr (sentinel_frame);
>
> /* Try using the frame stash first. Finding it there removes the need
> @@ -1587,10 +1612,14 @@ put_frame_register_bytes (frame_info_ptr frame,
> int regnum,
> }
> }
>
> -/* Create a sentinel frame. */
> +/* Create a sentinel frame.
>
> -static frame_info *
> -create_sentinel_frame (struct program_space *pspace, struct regcache
> *regcache)
> + See frame_id_build_sentinel for the description of STACK_ADDR and
> + CODE_ADDR. */
> +
> +static frame_info_ptr
> +create_sentinel_frame (struct program_space *pspace, struct regcache
> *regcache,
> + CORE_ADDR stack_addr, CORE_ADDR code_addr)
> {
> frame_info *frame = FRAME_OBSTACK_ZALLOC (struct frame_info);
>
> @@ -1608,11 +1637,14 @@ create_sentinel_frame (struct program_space
> *pspace, struct regcache *regcache)
> frame->next = frame;
> /* The sentinel frame has a special ID. */
> frame->this_id.p = frame_id_status::COMPUTED;
> - frame->this_id.value = sentinel_frame_id;
> + frame->this_id.value = frame_id_build_sentinel (stack_addr, code_addr);
> +
> + bool added = frame_stash_add (frame);
> + gdb_assert (added);
>
> frame_debug_printf (" -> %s", frame->to_string ().c_str ());
>
> - return frame;
> + return frame_info_ptr (frame);
> }
>
> /* Cache for frame addresses already read by gdb. Valid only while
> @@ -1654,7 +1686,8 @@ get_current_frame (void)
>
> if (sentinel_frame == NULL)
> sentinel_frame =
> - create_sentinel_frame (current_program_space, get_current_regcache
> ());
> + create_sentinel_frame (current_program_space, get_current_regcache
> (),
> + 0, 0).get ();
>
> /* Set the current frame before computing the frame id, to avoid
> recursion inside compute_frame_id, in case the frame's
> @@ -1980,7 +2013,8 @@ create_new_frame (frame_id id)
> frame_info *fi = FRAME_OBSTACK_ZALLOC (struct frame_info);
>
> fi->next = create_sentinel_frame (current_program_space,
> - get_current_regcache ());
> + get_current_regcache (),
> + id.stack_addr, id.code_addr).get ();
>
> /* Set/update this frame's cached PC value, found in the next frame.
> Do this before looking for this frame's unwinder. A sniffer is
> @@ -2044,7 +2078,8 @@ get_next_frame_sentinel_okay (frame_info_ptr
> this_frame)
> is the sentinel frame. But we disallow it here anyway because
> calling get_next_frame_sentinel_okay() on the sentinel frame
> is likely a coding error. */
> - gdb_assert (this_frame != sentinel_frame);
> + if (this_frame->this_id.p == frame_id_status::COMPUTED)
> + gdb_assert (!is_sentinel_frame_id (this_frame->this_id.value));
>
> return frame_info_ptr (this_frame->next);
> }
> @@ -2064,7 +2099,7 @@ reinit_frame_cache (void)
> {
> ++frame_cache_generation;
>
> - if (sentinel_frame != NULL)
> + if (htab_elements (frame_stash) > 0)
> annotate_frames_invalid ();
>
> invalidate_selected_frame ();
> diff --git a/gdb/frame.h b/gdb/frame.h
> index c4f7c121391b..4a99bd660dfc 100644
> --- a/gdb/frame.h
> +++ b/gdb/frame.h
> @@ -159,6 +159,16 @@ extern struct frame_id
> as the special identifier address are set to indicate wild cards. */
> extern struct frame_id frame_id_build_wild (CORE_ADDR stack_addr);
>
> +/* Construct a frame ID for a sentinel frame.
> +
> + If either STACK_ADDR or CODE_ADDR is not 0, the ID represents a
> sentinel
> + frame for a user-created frame. STACK_ADDR and CODE_ADDR are the
> addresses
> + used to create the frame.
> +
> + If STACK_ADDR and CODE_ADDR are both 0, the ID represents a regular
> sentinel
> + frame (i.e. the "next" frame of the target's current frame). */
> +extern frame_id frame_id_build_sentinel (CORE_ADDR stack_addr, CORE_ADDR
> code_addr);
> +
> /* Returns true when L is a valid frame. */
> extern bool frame_id_p (frame_id l);
>
> diff --git a/gdb/sentinel-frame.c b/gdb/sentinel-frame.c
> index f323de3bb5d3..3e5b9be2edf6 100644
> --- a/gdb/sentinel-frame.c
> +++ b/gdb/sentinel-frame.c
> @@ -50,8 +50,11 @@ sentinel_frame_prev_register (frame_info_ptr this_frame,
> = (struct frame_unwind_cache *) *this_prologue_cache;
> struct value *value;
>
> + frame_id this_frame_id = get_frame_id (this_frame);
> + gdb_assert (is_sentinel_frame_id (this_frame_id));
> +
> value = cache->regcache->cooked_read_value (regnum);
> - VALUE_NEXT_FRAME_ID (value) = sentinel_frame_id;
> + VALUE_NEXT_FRAME_ID (value) = this_frame_id;
>
> return value;
> }
> --
> 2.39.1
>
> The commit message is awesome, very informative, I really enjoyed reading
it.
I can see the patch set fixes gdb.base/frame-view.exp on aarch64, I've seen
this failure before the patch which does not occur after applying it.
frame
#0 baz (z1=hahaha, ../../binutils-gdb/gdb/value.c:4056: internal-error: v
alue_fetch_lazy_register: Assertion `next_frame != NULL' failed.
A problem internal to GDB has been detected,
further debugging may prove unreliable.
----- Backtrace -----
FAIL: gdb.base/frame-view.exp: with_pretty_printer=true: frame (GDB
internal error)
Resyncing due to internal error.
0x5a6aaf gdb_internal_backtrace_1
../../binutils-gdb/gdb/bt-utils.c:122
next prev parent reply other threads:[~2023-02-07 17:02 UTC|newest]
Thread overview: 10+ messages / expand[flat|nested] mbox.gz Atom feed top
2023-01-30 20:02 [PATCH 0/2] Fix gdb.base/frame-view.exp on AArch64 Simon Marchi
2023-01-30 20:02 ` [PATCH 1/2] gdb: call frame unwinders' dealloc_cache methods through destroying the frame cache Simon Marchi
2023-02-09 7:40 ` Tom de Vries
2023-02-09 12:42 ` Tom de Vries
2023-02-09 19:53 ` Simon Marchi
2023-01-30 20:02 ` [PATCH 2/2] gdb: give sentinel for user frames distinct IDs, register sentinel frames to " Simon Marchi
2023-02-07 17:02 ` Alexandra Petlanova Hajkova [this message]
2023-02-08 21:37 ` Simon Marchi
2023-02-08 17:05 ` Luis Machado
2023-02-08 21:38 ` Simon Marchi
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=CAJVr-EOvFGgVbeEM3kK0fuiLxmPYYMNYRF04iyDS4onxX2Q7-A@mail.gmail.com \
--to=ahajkova@redhat.com \
--cc=gdb-patches@sourceware.org \
--cc=simon.marchi@efficios.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;
as well as URLs for read-only IMAP folder(s) and NNTP newsgroup(s).