From mboxrd@z Thu Jan 1 00:00:00 1970 Return-Path: Received: from simark.ca by simark.ca with LMTP id KS5sBhdwjGpIyTwAWB0awg (envelope-from ) for ; Mon, 24 Aug 2026 12:23:51 -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=EwMPq/sF; dkim-atps=neutral Received: by simark.ca (Postfix, from userid 112) id 142041E0A3; Mon, 24 Aug 2026 12:23:51 -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 [38.145.34.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 B43B01E033 for ; Mon, 24 Aug 2026 12:23:47 -0400 (EDT) Received: from vm01.sourceware.org (localhost [IPv6:::1]) by sourceware.org (Postfix) with ESMTP id 819F34BAE7CD for ; Mon, 24 Aug 2026 16:23:46 +0000 (GMT) DKIM-Filter: OpenDKIM Filter v2.11.0 sourceware.org 819F34BAE7CD 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=EwMPq/sF 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 8BF4C4BAE7CF for ; Mon, 24 Aug 2026 16:22:32 +0000 (GMT) DMARC-Filter: OpenDMARC Filter v1.4.2 sourceware.org 8BF4C4BAE7CF 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 8BF4C4BAE7CF 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=1787588552; cv=none; b=Itr1Cvy2pVWmV39c2cTrpF3iaIcJ+cU1Xa8/cMrG6AI/sYGcKUwiFNiDGxhOkw6On+j7qvAOAQma3LDIFcqEGuNRxOKDiptZVlICykYg6TCH5gnYxcVPg3GySNyb5q3/X3wrFHuDRiwJeBugX+Fdlf7qgFQyOZK9aEyLH5ZxUFI= ARC-Message-Signature: i=1; a=rsa-sha256; d=sourceware.org; s=key; t=1787588552; c=relaxed/simple; bh=jwcjY2ALsphaDfOOlRCKz5BsT710Bav8zHCl/c0K/pQ=; h=DKIM-Signature:From:To:Subject:Date:Message-Id:MIME-Version; b=JG0XJURUkAjyS0+smU6DJAmZB5wmM5lhd9NUi/rAJIXjCta9UgAIA6AvGS3v8MgpaQnmi28ECjJ5bdA+OqJr0RMlIceUuUnvwXga9wWnru0tg3zpc/nxkEcT2VznSy1W1VIqit4CjAQ7U+XJ4ImKLHJP6VKUunXiDG6ApEg+Uy8= 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=EwMPq/sF DKIM-Filter: OpenDKIM Filter v2.11.0 sourceware.org 8BF4C4BAE7CF DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=redhat.com; s=mimecast20190719; t=1787588552; h=from:from:reply-to:subject:subject:date:date:message-id:message-id: to:to:cc:cc:mime-version:mime-version:content-type:content-type: content-transfer-encoding:content-transfer-encoding: in-reply-to:in-reply-to:references:references; bh=bW4TCrjTSuWr9ot6d6QUHk43ECxZByIP8euSTMHtPUo=; b=EwMPq/sFuv/GmEpRMOGof3B6W1BawU4Aootu/YVrIHGTvCedhrMZQZRSViXt+9PGbO9nHJ zxIiJMkVp4Cr7okI9c9Z6nHDPt9ctU6zbuzCW06w3zZFhvSZvEcUUyAAiINAcHmyP76XWE UY7bvmERDSCVZ2Gq7d8apmQEV+bHAmw= Received: from mail-wm1-f71.google.com (mail-wm1-f71.google.com [209.85.128.71]) by relay.mimecast.com with ESMTP with STARTTLS (version=TLSv1.3, cipher=TLS_AES_256_GCM_SHA384) id us-mta-447-V_f6F0NUMKKYkFG43dXYwA-1; Mon, 24 Aug 2026 12:22:30 -0400 X-MC-Unique: V_f6F0NUMKKYkFG43dXYwA-1 X-Mimecast-MFC-AGG-ID: V_f6F0NUMKKYkFG43dXYwA_1787588549 Received: by mail-wm1-f71.google.com with SMTP id 5b1f17b1804b1-496b6f97676so13716575e9.2 for ; Mon, 24 Aug 2026 09:22:30 -0700 (PDT) X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20251104; t=1787588549; x=1788193349; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:x-gm-gg:x-gm-message-state:from :to:cc:subject:date:message-id:reply-to:content-type; bh=bW4TCrjTSuWr9ot6d6QUHk43ECxZByIP8euSTMHtPUo=; b=WNeu7K+YCifmkm4s5UUcicZV68eb0+YnGXyfuewfCSp7D/Xoq3sfPrQDDIuzedqidO Uj5KZZrWsi2hlYjHJ/vo6xlON7Tjk6JTxXhh0fIsw83AXC/eSS7dQZEq/uCk+xpdB1iK l2xcAFAs9y5rAwIaI8fIDjMoaeTkNYzYe9PIfutiYguwsUypFmjvGTqcaRz01Qvdac2w PFcvMQP3QqQ5bC4a+rj4l/GWtoAHBbvm2Rqw/OLyLAX9FuZnmjsvt1hEVxHHmvuVx7W3 /T8fKRR+ikaIEnhuvk1BA5WzkVQr/8RxRuLyJGzoDBBd1F80o2mO5yV31SW/s4MVtMv7 03Ew== X-Gm-Message-State: AFuF++nIv1MvBQn42BGNshgtG4kRXUP6RhYctUYkxvxTYBWx3cHitDjB 6B6wCLQYtBtiWG7V2FalPVu2rDgo05HDEpt1M9pDOcT+4/FPxXc5Tl1U7e3EhcpOXLy2dPlCuK/ ct/1tBmp702Xe6/1AqfsBYhEvWEYvUAtj5F21UYilqpGXotpXAapTFF4XEtWIRYD2IcTx/DrzUQ 85Q2sOgra1+0n6UgRd0lHNRu0JF4YFu/v0sxUHTz3ZiITFIHA= X-Gm-Gg: AR+sD1222BxUCXTIrGZMyidNbNJxRDpTn3fz4V7AXqzQhGRFVEbnxZO3zCmlGn4jw8M y6t1LvT3otFsSomyBa3T9KQryJAp/SgdGWoruAR5SAB5OcPnbA6jdq/WIrrgFo74P+uZk8jEhpz K0NeF8I45G8XJb4rGT7cM6XKcvaBVur0Wj6ftF/RzBB9NaCksa4HjH94nwCBX+mmjI6YjG4PZlh 48RRWsan76gSz6vm9OmeBQ3w7t0c4A/b7tz4+h4K7d2ovMPQ7PF3RliOzOzP/fT7RT46MvL8G8Z uiJbCjHQSEdjAHdfL4D4/9kJqDVWZCsd3nf4PCsNLsrjxInRsRHB0ydWFxT8Ui3sU8XqtkPVgz4 WmvPPAAMPng16VBpamzH4OO0gdP0= X-Received: by 2002:a05:600c:8b57:b0:499:d639:8475 with SMTP id 5b1f17b1804b1-499d63985bdmr6005125e9.6.1787588549092; Mon, 24 Aug 2026 09:22:29 -0700 (PDT) X-Received: by 2002:a05:600c:8b57:b0:499:d639:8475 with SMTP id 5b1f17b1804b1-499d63985bdmr6003015e9.6.1787588548332; Mon, 24 Aug 2026 09:22:28 -0700 (PDT) Received: from localhost (128.223.159.143.dyn.plus.net. [143.159.223.128]) by smtp.gmail.com with ESMTPSA id 5b1f17b1804b1-499d5d72d36sm1589905e9.3.2026.08.24.09.22.26 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Mon, 24 Aug 2026 09:22:27 -0700 (PDT) From: Andrew Burgess To: gdb-patches@sourceware.org Cc: Simon Marchi , Andrew Burgess Subject: [PATCHv9 3/4] gdb: allow 'until' to work in outermost frame Date: Mon, 24 Aug 2026 17:22:18 +0100 Message-Id: <41fc9ac7b20577ba647a4be79b50b8f19811de9a.1787588386.git.aburgess@redhat.com> X-Mailer: git-send-email 2.25.4 In-Reply-To: References: MIME-Version: 1.0 X-Mimecast-Spam-Score: 0 X-Mimecast-MFC-PROC-ID: CtkW2P38XellK95EwKG37dsHN7NJ0-a9b5oVOeWvucg_1787588549 X-Mimecast-Originator: redhat.com Content-Transfer-Encoding: 8bit content-type: text/plain; charset="US-ASCII"; x-default=true 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 The 'until' command with an argument, e.g. 'until *ADDRESS', is implemented by 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 frame_unwind_caller_frame, which calls 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 add an entry frame check into frame_unwind_caller_frame path, similar to the one found in get_prev_frame. To achieve this I create get_prev_frame_with_entry_frame_check, a new function that checks if a frame is an entry frame, and returns NULL if it is. Otherwise, get_prev_frame_with_entry_frame_check just calls get_prev_frame_always. Then in get_prev_frame and frame_unwind_caller_frame, instead of calling get_prev_frame_always directly, we can call the new get_prev_frame_with_entry_frame_check. Finally, the existing entry frame check can be removed from get_prev_frame. Notice that in moving the entry frame check from get_prev_frame I have removed the 'frame_pc.has_value ()' check. Previously inside_entry_func called get_frame_func which would throw if the frame's $pc was not available. I have updated inside_entry_func to call get_frame_func_if_available, which doesn't throw. As a result the `frame_pc.has_value ()` check is no longer needed. An earlier version of this patch tried moving the inside_entry_frame check into get_prev_frame_always, however, if get_prev_frame_always is unable to unwind a frame then it must set the stop reason on the last frame, this can be seen in `get_frame_unwind_stop_reason` where the assert `gdb_assert (frame->prev_p);` will trigger if the previous frame is not setup correctly. Setting the stop reason means the decision about whether there's a previous frame or not is permanent (at least until the next frame cache flush), but we need commands like 'bt -past-entry' to work, which means the choice for whether there's a previous frame or not needs to remain dynamic. 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. This was true for both the executable entry frame and the inferior entry frame. I created bug PR gdb/34245 for this issue. Bug: https://sourceware.org/bugzilla/show_bug.cgi?id=34245 --- gdb/frame.c | 96 ++++---- gdb/testsuite/gdb.base/until-in-entry-frame.c | 22 ++ .../gdb.base/until-in-entry-frame.exp | 215 ++++++++++++++++++ 3 files changed, 281 insertions(+), 52 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 e61bfd8d1f7..db09171b08d 100644 --- a/gdb/frame.c +++ b/gdb/frame.c @@ -734,6 +734,45 @@ get_stack_frame_id (const frame_info_ptr &next_frame) return get_frame_id (skip_artificial_frames (next_frame)); } +/* Return true if THIS_FRAME is either of the two possible entry frames; + the entry frame for the whole inferior, or the entry frame of just the + executable. */ + +static bool +inside_entry_func (const frame_info_ptr &this_frame) +{ + CORE_ADDR frame_func_addr; + if (!get_frame_func_if_available (this_frame, &frame_func_addr)) + return false; + + const entry_point_info &ep_info + = current_program_space->get_entry_point_info (); + + /* Check both entry frames. */ + return (ep_info.exec_entry_address () == frame_func_addr + || ep_info.inferior_entry_address () == frame_func_addr); +} + +/* Check if THIS_FRAME is an entry frame. If it is, return NULL. + Otherwise, call get_prev_frame_always and return its result. */ + +static frame_info_ptr +get_prev_frame_with_entry_frame_check (const frame_info_ptr &this_frame) +{ + FRAME_SCOPED_DEBUG_START_END ("fi=%d", this_frame->level); + + if (this_frame->level >= 0 + && get_frame_type (this_frame) == NORMAL_FRAME + && !user_set_backtrace_options.backtrace_past_entry + && inside_entry_func (this_frame)) + { + frame_debug_printf ("is inside an entry frame"); + return nullptr; + } + + return get_prev_frame_always (this_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. @@ -744,7 +783,8 @@ 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) { - frame_info_ptr this_frame = get_prev_frame_always (initial_next_frame); + frame_info_ptr this_frame + = get_prev_frame_with_entry_frame_check (initial_next_frame); if (this_frame == nullptr) return nullptr; @@ -754,11 +794,8 @@ frame_unwind_caller_frame (const frame_info_ptr &initial_next_frame) struct frame_id frame_unwind_caller_id (const frame_info_ptr &initial_next_frame) { - /* Use get_prev_frame_always, and not get_prev_frame. The latter - will truncate the frame chain, leading to this function - unintentionally returning a null_frame_id (e.g., when a caller - requests the frame ID of "main()"s caller. */ - + /* The frame_unwind_caller_frame call can return NULL if we are + initially in an entry frame. */ frame_info_ptr this_frame = frame_unwind_caller_frame (initial_next_frame); if (this_frame == nullptr) return null_frame_id; @@ -2698,19 +2735,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 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 @@ -2767,38 +2791,6 @@ get_prev_frame (const frame_info_ptr &this_frame) return NULL; } - /* If we're already inside the entry function for the main objfile, - then it isn't valid. Don't apply this test to a dummy frame - - dummy frame PCs typically land in the entry func. Don't apply - this test to the sentinel frame. Sentinel frames should always - be allowed to unwind. */ - /* NOTE: cagney/2003-07-07: Fixed a bug in inside_main_func() - - wasn't checking for "main" in the minimal symbols. With that - fixed asm-source tests now stop in "main" instead of halting the - backtrace in weird and wonderful ways somewhere inside the entry - file. Suspect that tests for inside the entry file/func were - added to work around that (now fixed) case. */ - /* NOTE: cagney/2003-07-15: danielj (if I'm reading it right) - suggested having the inside_entry_func test use the - inside_main_func() msymbol trick (along with exec_entry_point_address() - I guess) to determine the address range of the start function. - That should provide a far better stopper than the current - heuristics. */ - /* NOTE: tausq/2004-10-09: this is needed if, for example, the compiler - applied tail-call optimizations to main so that a function called - 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)) - { - frame_debug_got_null_frame (this_frame, "inside entry func"); - return NULL; - } - /* Assume that the only way to get a zero PC is through something like a SIGSEGV or a dummy frame, and hence that NORMAL frames will never unwind a zero PC. */ @@ -2812,7 +2804,7 @@ get_prev_frame (const frame_info_ptr &this_frame) return NULL; } - return get_prev_frame_always (this_frame); + return get_prev_frame_with_entry_frame_check (this_frame); } CORE_ADDR 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..6a0e311ef41 --- /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..b380107cc42 --- /dev/null +++ b/gdb/testsuite/gdb.base/until-in-entry-frame.exp @@ -0,0 +1,215 @@ +# 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] } { + return +} + +set testfile_static ${testfile}-static +if { [build_executable "failed to build" $testfile_static $srcfile \ + { debug additional_flags=-static } ] } { + return +} + +# Use 'frame' 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 "frame" $testname { + -re -wrap "#0\\s+(?:$::hex in )?(\[^( \]+) \\(.*" { + set func_name $expect_out(1,string) + } + + -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. +# +# When AT_EXEC_ENTRY is true, continue to the executable entry frame +# rather than running the test in the inferior entry frame. If the +# inferior entry frame is the same as the executable entry frame then +# this proc will return early. +proc run_test { use_stepi past_entry at_exec_entry testfile } { + clean_restart $testfile + + if {$::use_gdb_stub && [target_info exists gdb,do_reload_on_run]} { + # This is the path taken by the 'native-gdbserver' board. + # Start gdbserver and connect, this should leave the inferior + # at the first instruction. + if { [gdb_reload] != 0 } { + return + } + pass "connected to gdbserver" + } else { + # Start inferior with 'starti' and then wait for a prompt. + if { [gdb_starti_cmd] < 0 } { + untested starti + return + } + gdb_test "" "Program stopped.*" "prompt after starti" + } + + set func_name [current_function_name \ + "function name for first function"] + + set inferior_entry_address "" + set executable_entry_address "" + gdb_test_multiple "maintenance info entry-address" "" -lbl { + -re "\r\nInferior entry address: ($::hex)(?=\r\n)" { + set inferior_entry_address $expect_out(1,string) + exp_continue + } + -re "\r\nExecutable entry address: ($::hex)(?=\r\n)" { + set executable_entry_address $expect_out(1,string) + exp_continue + } + -re "\r\n$::gdb_prompt $" { + gdb_assert { $inferior_entry_address ne "" \ + && $executable_entry_address ne "" } \ + $gdb_test_name + } + } + + if { $at_exec_entry } { + # If we are being asked to run the test at the executable + # entry frame, but the inferior entry frame is the same as the + # executable entry frame, which will be the current frame, + # then there is nothing more to test here. + # + # This proc will be called again with AT_EXEC_ENTRY false, in + # which case the tests in this proc will be run. + if { $inferior_entry_address == $executable_entry_address } { + return + } + + # Move forward to the executable entry frame. + gdb_breakpoint "*$executable_entry_address" + gdb_continue_to_breakpoint "executable entry frame" + } + + 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" -lbl { + -re "\r\n=> $::hex \[^\r\n\]+(?=\r\n)" { + set capture_address true + exp_continue + } + + -re "\r\n\\s+($::hex) \[^\r\n\]+(?=\r\n)" { + if { $capture_address } { + set until_address $expect_out(1,string) + set capture_address false + } + + exp_continue + } + + -re "\r\n$::gdb_prompt $" { + set found_address [expr { $until_address ne "" }] + if { !$found_address } { + unsupported "$gdb_test_name (no instruction found)" + return + } + pass $gdb_test_name + } + } + + # 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 "\r\n(?:$::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 } { + foreach_with_prefix at_exec_entry { false true } { + run_test $use_stepi $past_entry $at_exec_entry $testfile + + with_test_prefix "static" { + run_test $use_stepi $past_entry $at_exec_entry $testfile_static + } + } + } +} -- 2.25.4