From mboxrd@z Thu Jan 1 00:00:00 1970 Return-Path: Received: from simark.ca by simark.ca with LMTP id 65lRAq0LK2pqAwIAWB0awg (envelope-from ) for ; Thu, 11 Jun 2026 15:25:33 -0400 Authentication-Results: simark.ca; dkim=pass (1024-bit key; unprotected) header.d=redhat.com header.i=@redhat.com header.a=rsa-sha256 header.s=mimecast20190719 header.b=ezrdxokL; dkim-atps=neutral Received: by simark.ca (Postfix, from userid 112) id EAB4B1E070; Thu, 11 Jun 2026 15:25:32 -0400 (EDT) X-Spam-Checker-Version: SpamAssassin 4.0.1 (2024-03-25) on simark.ca X-Spam-Level: X-Spam-Status: No, score=-6.4 required=5.0 tests=ARC_SIGNED,ARC_VALID,BAYES_00, DKIMWL_WL_HIGH,DKIM_SIGNED,DKIM_VALID,DKIM_VALID_AU,MAILING_LIST_MULTI, RCVD_IN_DNSWL_MED autolearn=ham autolearn_force=no version=4.0.1 Received: from vm01.sourceware.org (vm01.sourceware.org [IPv6:2620:52:6:3111::32]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange x25519 server-signature ECDSA (prime256v1) server-digest SHA256) (No client certificate requested) by simark.ca (Postfix) with ESMTPS id D0C3F1E070 for ; Thu, 11 Jun 2026 15:25:31 -0400 (EDT) Received: from vm01.sourceware.org (localhost [IPv6:::1]) by sourceware.org (Postfix) with ESMTP id 2BB544BA2E0A for ; Thu, 11 Jun 2026 19:25:30 +0000 (GMT) DKIM-Filter: OpenDKIM Filter v2.11.0 sourceware.org 2BB544BA2E0A Authentication-Results: sourceware.org; dkim=pass (1024-bit key, unprotected) header.d=redhat.com header.i=@redhat.com header.a=rsa-sha256 header.s=mimecast20190719 header.b=ezrdxokL Received: from us-smtp-delivery-124.mimecast.com (us-smtp-delivery-124.mimecast.com [170.10.129.124]) by sourceware.org (Postfix) with ESMTP id A40CD4BA543C for ; Thu, 11 Jun 2026 19:24:56 +0000 (GMT) DMARC-Filter: OpenDMARC Filter v1.4.2 sourceware.org A40CD4BA543C Authentication-Results: sourceware.org; dmarc=pass (p=quarantine dis=none) header.from=redhat.com Authentication-Results: sourceware.org; spf=pass smtp.mailfrom=redhat.com ARC-Filter: OpenARC Filter v1.0.0 sourceware.org A40CD4BA543C Authentication-Results: sourceware.org; arc=none smtp.remote-ip=170.10.129.124 ARC-Seal: i=1; a=rsa-sha256; d=sourceware.org; s=key; t=1781205896; cv=none; b=BGtjMnKt2V1D6G2/+H1FFnoGNah007ROPXCKQ8RQ/gjaev9AkcjbMksmotOrhsxU9oUWEi/poX9NV2+ZD4SB4/hlaeOomDypsOBQt/xYH5eSQQeY4tUGHubSD9bP27IWY2CG1qP8KK1bYoNBokCFFzTujQMHQxvuIg5DBQkeQEI= ARC-Message-Signature: i=1; a=rsa-sha256; d=sourceware.org; s=key; t=1781205896; c=relaxed/simple; bh=ycBXXsC6xehq80MQfSwEtUeJYA0S4PCLowxurwIsFcQ=; h=DKIM-Signature:Message-ID:Date:MIME-Version:Subject:To:From; b=npwDvs5AGliTmrnm6RqZlRI3HfEkisx40i1qXQf7d7RJYMdnYpErzrqm2qCZouO53hvEt51KQHqsLAZtHV6kdi706TMP7SJj62bx2IlaESBKNYHXz5ggyD878fOgzcVeqMB9MIdyYZ3jhQsWloa1CPiXkAB/2j1al4hw2VjTv/k= ARC-Authentication-Results: i=1; sourceware.org; dkim=pass (1024-bit key, unprotected) header.d=redhat.com header.i=@redhat.com header.a=rsa-sha256 header.s=mimecast20190719 header.b=ezrdxokL DKIM-Filter: OpenDKIM Filter v2.11.0 sourceware.org A40CD4BA543C DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=redhat.com; s=mimecast20190719; t=1781205896; h=from:from:reply-to:subject:subject:date:date:message-id:message-id: to:to:cc:mime-version:mime-version:content-type:content-type: content-transfer-encoding:content-transfer-encoding: in-reply-to:in-reply-to:references:references; bh=jI95Ydw8jLD8JJ/S6orWOZj6Y6o0DU03jl2210cTgYg=; b=ezrdxokLKQc6RSX5NlkF17eDq6YDBkiH4OmJ2eOFyV8m2tyFXgZaR4ONbX8Mv+nNZpOri0 u4quE6MlXhVs/HP1w+i8jGTnXMdv5uXPRWpISKUuQ0KIcWLi19KJDi0lsI47n9I2amujF1 HX4IKttsL+jvsbQl7YVZyrjhwqMRD4g= Received: from mail-dy1-f199.google.com (mail-dy1-f199.google.com [74.125.82.199]) by relay.mimecast.com with ESMTP with STARTTLS (version=TLSv1.3, cipher=TLS_AES_256_GCM_SHA384) id us-mta-107-7D_eak9yONWmyPzCcuw8dA-1; Thu, 11 Jun 2026 15:24:54 -0400 X-MC-Unique: 7D_eak9yONWmyPzCcuw8dA-1 X-Mimecast-MFC-AGG-ID: 7D_eak9yONWmyPzCcuw8dA_1781205893 Received: by mail-dy1-f199.google.com with SMTP id 5a478bee46e88-304df51ff3eso499597eec.0 for ; Thu, 11 Jun 2026 12:24:54 -0700 (PDT) X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20251104; t=1781205893; x=1781810693; h=content-transfer-encoding:in-reply-to:from:content-language :references:to:subject:user-agent:mime-version:date:message-id :x-gm-gg:x-gm-message-state:from:to:cc:subject:date:message-id :reply-to; bh=jI95Ydw8jLD8JJ/S6orWOZj6Y6o0DU03jl2210cTgYg=; b=QWWcX3b4vmYnW+lZTRwoEpqY1NvD1e8QFLJLkg/uyG7PnLi7bq09KUfJo/LostQtgU qtDLrj38PIwdeN8BmVEMQociqnOCcXmQAvSxfjTBHiSxnuzIKU22KgLSF9M1OtW8TLxA iIr2N9qRQ5JW/ITsuAROylJSu4w9EZh7vy2ljEv5DIZPrh0wYOKwpnrrFEccNmKM+wmY y8u+4sjqmIe9exy6Yzk6+djgLHrTb1yLxSTxwh4ng7asZdLu5IyTcOt6R7x7vE/k/UlZ XWym2k3crZ12okDPiQB3B8Y05O64SdxqUZpDwqRN4ofBhUm4ZNRrJOPQT4ObRhNXP8JO irWA== X-Forwarded-Encrypted: i=1; AFNElJ+kmZVjMcNrevJS/roiMhfufb2gDaAqwfKVgQLIVyB1NaVEjkhbJF+D8bRiy1OD4sjFYHZkYPwf8tzheQ==@sourceware.org X-Gm-Message-State: AOJu0YyCMBbO4fOLvwdIlAezx1kbvR+1bThRM03HxtACiOyEjOfoJAoU 4KKRmPhgpefrOVK7ex+juxFhxjhYjdNV9qoC61ZJOItAS7CNcliLgW64zUUrgn44GNyBzKxGA1f If/IXg1vZ6J7FbFnI7Yg24sjn6wc9bYFAUapVaW+QKjq0frlWpRSH4TDdp9dZsIgpTaFcsLs= X-Gm-Gg: Acq92OHiwgwiZiM0aM/UqizK8JojG0F+nL+j7pHU8BsU1zWCLLvaEvDYLb0yH/OTgki U9w6saTW7RbTSdz9dfA6PSrazM2ecgu/zwhjWk1846oEaUfVbTkE1FFEl7Ogg/Yfr6pirgoVwKc I0FHtZdPn1dit/m0Udk0VyduKDVSwuKrwnMUgULVxsFH86wBpo4wXjlLdeyaZ3FkUUQ/d82U9hO vaKOBTimPSyBMvjnOighDo9t8wxr4Cjf1spRTYzFin5vKcW4n9dV2jlRq4v46xlPKiL50a8J+qS tDHI/h2qCSIWxT9QfnYOrjyYVc0+0tWwQcq3aFgPKJNpnvX2qsbEA/ysKQaD2cswjK72oGE2mQH D9p+EClnxGdjHhEbh/pstEWMpYV0djmXzD59oNyGWS+uUN1iezYPsAA4QMuDH6x94Fjs3 X-Received: by 2002:a05:7301:1010:b0:307:3a6c:ece9 with SMTP id 5a478bee46e88-308047bf91amr3438315eec.15.1781205893167; Thu, 11 Jun 2026 12:24:53 -0700 (PDT) X-Received: by 2002:a05:7301:1010:b0:307:3a6c:ece9 with SMTP id 5a478bee46e88-308047bf91amr3438278eec.15.1781205892324; Thu, 11 Jun 2026 12:24:52 -0700 (PDT) Received: from ?IPV6:2804:14d:8084:993e:2d5d:7adb:2b6:a50e? ([2804:14d:8084:993e:2d5d:7adb:2b6:a50e]) by smtp.gmail.com with ESMTPSA id 5a478bee46e88-3081e91f878sm19572eec.17.2026.06.11.12.24.50 (version=TLS1_3 cipher=TLS_AES_128_GCM_SHA256 bits=128/128); Thu, 11 Jun 2026 12:24:51 -0700 (PDT) Message-ID: <02e967d8-a06a-4774-8618-e8fddd665c26@redhat.com> Date: Thu, 11 Jun 2026 16:24:47 -0300 MIME-Version: 1.0 User-Agent: Mozilla Thunderbird Subject: Re: [PATCH] gdb: allow 'until' to work in outermost frame To: Andrew Burgess , gdb-patches@sourceware.org References: <41fe591d58ba010fa771e80ca674b61e30feef2f.1780942441.git.aburgess@redhat.com> From: Guinevere Larsen In-Reply-To: <41fe591d58ba010fa771e80ca674b61e30feef2f.1780942441.git.aburgess@redhat.com> X-Mimecast-Spam-Score: 0 X-Mimecast-MFC-PROC-ID: zHa3D5RwB8cBg3Mi5ELyT35Hl5e7noA-iABwqUuujwU_1781205893 X-Mimecast-Originator: redhat.com Content-Language: en-US Content-Type: text/plain; charset=UTF-8; format=flowed Content-Transfer-Encoding: 7bit X-BeenThere: gdb-patches@sourceware.org X-Mailman-Version: 2.1.30 Precedence: list List-Id: Gdb-patches mailing list List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: gdb-patches-bounces~public-inbox=simark.ca@sourceware.org On 6/8/26 3:14 PM, 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. > > 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, however, when 'until' > creates the caller frame breakpoint it calls frame_unwind_caller_pc > which calls frame_unwind_caller_frame, which skips get_prev_frame and > calls get_prev_frame_always. 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 share the outer frame detection logic > between get_prev_frame and frame_unwind_caller_frame. When 'backtrace > past-entry' is off, which is the default, frame_unwind_caller_frame > will return NULL for the outermost frame. This means that a command > like 'until *ADDRESS' will no longer try to create a breakpoint in the > caller frame when used in the outermost frame. Why not simply change frame_unwind_caller_frame to call get_prev_frame instead of get_prev_frame_always? feels like it would be a better idea to not assume that the caller is always available anyway, rather than porting the bits of work-arounds back to it > > If the user turns 'backtrace past-entry' on then we assume that the > user knows best, and will try to place the breakpoint in the caller > frame. > > 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 | 37 ++++- > gdb/testsuite/gdb.base/until-in-entry-frame.c | 22 +++ > .../gdb.base/until-in-entry-frame.exp | 153 ++++++++++++++++++ > 3 files changed, 207 insertions(+), 5 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 f64f5554f8c..5561ba0f2bd 100644 > --- a/gdb/frame.c > +++ b/gdb/frame.c > @@ -74,6 +74,7 @@ set_backtrace_options user_set_backtrace_options; > static frame_info_ptr get_prev_frame_raw (const frame_info_ptr &this_frame); > static const char *frame_stop_reason_symbol_string (enum unwind_stop_reason reason); > static frame_info_ptr create_new_frame (frame_id id); > +static bool inside_entry_func (const frame_info_ptr &this_frame); > > /* Status of some values cached in the frame_info object. */ > > @@ -697,6 +698,21 @@ get_stack_frame_id (const frame_info_ptr &next_frame) > return get_frame_id (skip_artificial_frames (next_frame)); > } > > +/* If FRAME is the outermost frame, and the user has not turned on > + 'backtrace past-entry', then return true, otherwise, return false. */ > + > +static bool > +stop_unwinding_due_to_outermost_frame (const frame_info_ptr &frame) > +{ > + std::optional frame_pc = get_frame_pc_if_available (frame); > + > + return (frame->level >= 0 > + && get_frame_type (frame) == NORMAL_FRAME > + && !user_set_backtrace_options.backtrace_past_entry > + && frame_pc.has_value () > + && inside_entry_func (frame)); > +} > + > /* Helper for the various frame_unwind_caller_* functions. Unwind > INITIAL_NEXT_FRAME at least one frame, but skip any artificial frames, > that is inline or tailcall frames. > @@ -707,6 +723,21 @@ get_stack_frame_id (const frame_info_ptr &next_frame) > static frame_info_ptr > frame_unwind_caller_frame (const frame_info_ptr &initial_next_frame) > { > + /* The frames prior to the entry frame (not main, but the actual entry > + frame) are usually invalid. If INITIAL_NEXT_FRAME is the entry frame > + then just claim that there is no caller frame. This prevents things > + like 'until' from trying to place breakpoints in the invalid frame > + which GDB thinks called the entry frame. > + > + Of course, if the user has turned on 'backtrace past-entry' then we > + assume that the user knows best, and that those frames are valid, in > + which case we do unwind past the entry frame. */ > + if (stop_unwinding_due_to_outermost_frame (initial_next_frame)) > + { > + frame_debug_printf ("inside entry func"); > + return nullptr; > + } > + > frame_info_ptr this_frame = get_prev_frame_always (initial_next_frame); > if (this_frame == nullptr) > return nullptr; > @@ -2785,11 +2816,7 @@ get_prev_frame (const frame_info_ptr &this_frame) > from main returns directly to the caller of main. Since we don't > stop at main, we should at least stop at the entry point of the > application. */ > - if (this_frame->level >= 0 > - && get_frame_type (this_frame) == NORMAL_FRAME > - && !user_set_backtrace_options.backtrace_past_entry > - && frame_pc.has_value () > - && inside_entry_func (this_frame)) > + if (stop_unwinding_due_to_outermost_frame (this_frame)) > { > frame_debug_got_null_frame (this_frame, "inside entry func"); > return NULL; > diff --git a/gdb/testsuite/gdb.base/until-in-entry-frame.c b/gdb/testsuite/gdb.base/until-in-entry-frame.c > new file mode 100644 > index 00000000000..9b08ea9e1d9 > --- /dev/null > +++ b/gdb/testsuite/gdb.base/until-in-entry-frame.c > @@ -0,0 +1,22 @@ > +/* This testcase is part of GDB, the GNU debugger. > + > + Copyright 2026 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 . */ > + > +int > +main (void) > +{ > + return 0; > +} > diff --git a/gdb/testsuite/gdb.base/until-in-entry-frame.exp b/gdb/testsuite/gdb.base/until-in-entry-frame.exp > new file mode 100644 > index 00000000000..aaf117ac3aa > --- /dev/null > +++ b/gdb/testsuite/gdb.base/until-in-entry-frame.exp > @@ -0,0 +1,153 @@ > +# Copyright 2026 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 . > + > +# Check that 'until' in the outermost frame works as expected. > + > +standard_testfile > + > +if { [build_executable "failed to build" ${testfile} $srcfile \ > + { debug ldflags=-static } ] } { > + return > +} > + > +# Use 'bt 1' to get the name of the current function. Returns the > +# name of the current function, or the empty string if the current > +# function name cannot be established. > +proc current_function_name { testname } { > + set func_name "" > + gdb_test_multiple "bt 1" $testname { > + -re -wrap "#0\\s+\[^\r\n\]*\\s+in (\[^( \]+) \\(.*" { > + set func_name $expect_out(1,string) > + } > + } > + > + return $func_name > +} > + > +# Start the inferior with 'starti', then use 'until' within the > +# outermost frame. This is the real outermost frame, not 'main'. > +# > +# PAST_ENTRY should be 'on' or 'off' and is used in a 'set backtrace > +# past-entry ...' command. > +proc run_test { use_stepi past_entry } { > + clean_restart $::testfile > + > + if { [gdb_starti_cmd] < 0 } { > + untested starti > + return > + } > + > + gdb_test "" "Program stopped\\..*" "starti" > + > + set func_name [current_function_name \ > + "function name for first function"] > + > + gdb_test_no_output "set backtrace past-entry $past_entry" > + > + # On x86-64 GDB does get a valid frame-id for the very first > + # instruction, but from the second instruction onwards it gets > + # outer_frame_id. As a result an 'until' setup at the first > + # instruction doesn't match later on within the same function as > + # the frame-ids no longer match. > + # > + # Try to work around this by issuing a 'stepi' to move to the > + # second instruction. > + # > + # This is only a heuristic though. On different architectures GDB > + # might have a valid frame-id for multiple instructions within the > + # outer frame, or might not have outer_frame_id for all > + # instructions. > + # > + # Also, we need to consider that the very first instruction might > + # be a control flow instruction, so using stepi could send the > + # inferior to another function. We try to spot this case and > + # perform an early return if that happens. > + if { $use_stepi } { > + gdb_test "stepi" > + set func_name_after [current_function_name \ > + "function name after stepi"] > + if { $func_name ne $func_name_after } { > + unsupported "stepi moved to another function" > + return > + } > + } > + > + # If the 'until' doesn't trigger then stop in 'main' as a backup. > + gdb_breakpoint main > + > + # Find an address to use in the 'until' command. > + set until_address "" > + set capture_address false > + gdb_test_multiple "disassemble" "find address for until" { > + -re "^=> $::hex \[^\r\n\]+\r\n" { > + set capture_address true > + exp_continue > + } > + > + -re "^\\s+($::hex) \[^\r\n\]+\r\n" { > + if { $capture_address } { > + set until_address $expect_out(1,string) > + set capture_address false > + } > + > + exp_continue > + } > + > + -re "^$::gdb_prompt $" { > + set found_address [expr { $until_address ne "" }] > + gdb_assert { $found_address } $gdb_test_name > + if { !$found_address } { > + return > + } > + } > + > + -re "^\[^\r\n\]*\r\n" { > + exp_continue > + } > + } > + > + # Send the 'until' command and then wait for GDB to stop. See the > + # notes above about the 'stepi' for why we sometimes expect to see > + # GDB reach 'main' here. > + send_gdb "until *${until_address}\n" > + gdb_test_multiple "" "after until" { > + -re -wrap "Breakpoint $::decimal, (?:\[^\r\n\]+ in )?main \\(\\).*" { > + kfail "gdb/34245" "$gdb_test_name (skipped until breakpoint)" > + } > + > + -re -wrap "$::hex in $func_name \\(\\).*" { > + pass $gdb_test_name > + } > + > + -re -wrap "Warning:\r\nCannot insert breakpoint 0\\.\r\nCannot access memory at address $::hex.*Command aborted\\." { > + # With PAST_ENTRY 'on', GDB discovers some invalid frames > + # past the entry frame based on whatever happens to be in > + # the registers when the entry function is called. These > + # invalid frames are often non-writable, so GDB will fail > + # to insert the breakpoint when the inferior resumes. > + # > + # We still count this as a pass though; the user should > + # only turn on 'backtrace past-entry' when they know that > + # is needed, so failure here is totally expected. > + gdb_assert { $past_entry eq "on" } "$gdb_test_name (caller breakpoint failed)" > + } > + } > +} > + > +foreach_with_prefix past_entry { off on } { > + foreach_with_prefix use_stepi { true false } { > + run_test $use_stepi $past_entry > + } > +} > > base-commit: 4562eab73d375e57f3c5a67f62d2678d7730d7ab -- Cheers, Guinevere Larsen it/its she/her (deprecated)