From mboxrd@z Thu Jan 1 00:00:00 1970 Return-Path: Received: from simark.ca by simark.ca with LMTP id lXQ0LmkWe2kdLxsAWB0awg (envelope-from ) for ; Thu, 29 Jan 2026 03:12:25 -0500 Authentication-Results: simark.ca; dkim=pass (1024-bit key; unprotected) header.d=suse.de header.i=@suse.de header.a=rsa-sha256 header.s=susede2_rsa header.b=wzO1rP6N; dkim=pass header.d=suse.de header.i=@suse.de header.a=ed25519-sha256 header.s=susede2_ed25519 header.b=qrrAzbjX; dkim=pass (1024-bit key) header.d=suse.de header.i=@suse.de header.a=rsa-sha256 header.s=susede2_rsa header.b=wzO1rP6N; dkim=neutral header.d=suse.de header.i=@suse.de header.a=ed25519-sha256 header.s=susede2_ed25519 header.b=qrrAzbjX; dkim-atps=neutral Received: by simark.ca (Postfix, from userid 112) id ABE3D1E089; Thu, 29 Jan 2026 03:12:25 -0500 (EST) X-Spam-Checker-Version: SpamAssassin 4.0.1 (2024-03-25) on simark.ca X-Spam-Level: X-Spam-Status: No, score=-2.4 required=5.0 tests=ARC_SIGNED,ARC_VALID,BAYES_00, DKIM_SIGNED,DKIM_VALID,DKIM_VALID_AU,MAILING_LIST_MULTI, RCVD_IN_DNSWL_MED,RCVD_IN_VALIDITY_CERTIFIED_BLOCKED, RCVD_IN_VALIDITY_RPBL_BLOCKED,RCVD_IN_VALIDITY_SAFE_BLOCKED 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 018971E089 for ; Thu, 29 Jan 2026 03:12:23 -0500 (EST) Received: from vm01.sourceware.org (localhost [127.0.0.1]) by sourceware.org (Postfix) with ESMTP id 58CDC4BAD17D for ; Thu, 29 Jan 2026 08:12:17 +0000 (GMT) DKIM-Filter: OpenDKIM Filter v2.11.0 sourceware.org 58CDC4BAD17D Authentication-Results: sourceware.org; dkim=pass (1024-bit key, unprotected) header.d=suse.de header.i=@suse.de header.a=rsa-sha256 header.s=susede2_rsa header.b=wzO1rP6N; dkim=pass header.d=suse.de header.i=@suse.de header.a=ed25519-sha256 header.s=susede2_ed25519 header.b=qrrAzbjX; dkim=pass (1024-bit key) header.d=suse.de header.i=@suse.de header.a=rsa-sha256 header.s=susede2_rsa header.b=wzO1rP6N; dkim=neutral header.d=suse.de header.i=@suse.de header.a=ed25519-sha256 header.s=susede2_ed25519 header.b=qrrAzbjX Received: from smtp-out2.suse.de (smtp-out2.suse.de [195.135.223.131]) by sourceware.org (Postfix) with ESMTPS id B2D224BAE7F6 for ; Thu, 29 Jan 2026 08:11:31 +0000 (GMT) DMARC-Filter: OpenDMARC Filter v1.4.2 sourceware.org B2D224BAE7F6 Authentication-Results: sourceware.org; dmarc=pass (p=none dis=none) header.from=suse.de Authentication-Results: sourceware.org; spf=pass smtp.mailfrom=suse.de ARC-Filter: OpenARC Filter v1.0.0 sourceware.org B2D224BAE7F6 Authentication-Results: server2.sourceware.org; arc=none smtp.remote-ip=195.135.223.131 ARC-Seal: i=1; a=rsa-sha256; d=sourceware.org; s=key; t=1769674292; cv=none; b=j3xV+1NTSaZntI5k53Vff8UuDsnh+ns6S5c7SaaxZ8/glPBsHdBhj3RQCE5ZMPMgedSzSlwcxedGySw+pg6p6vQzRmhO5W6yd+z1JNMf2DHLr2I9M4G6X3tldfZEuzzKLNkzXkrnx4Otoabt6jX4oUMCo3VCPGwWrM3mamgbh/A= ARC-Message-Signature: i=1; a=rsa-sha256; d=sourceware.org; s=key; t=1769674292; c=relaxed/simple; bh=h79buv/2dJaLaACcCsJLJQHECdVRaZ/MrIK4JfT7K2Y=; h=DKIM-Signature:DKIM-Signature:DKIM-Signature:DKIM-Signature: Message-ID:Date:MIME-Version:Subject:To:From; b=xhZJJ8ktKYhmGb3ylWcFCo+RXgwMN3XVe8Pb4rc7QW9bwfxvB9ddVAyvWJi4JEeLheOF3pjva6Ewdc9clmydcaLTxDAxFZ9g4TAIlsL+MRpUkIoRtPJtXq8dh2GCa14keRyz2qo7VsdmJ8q1O0i+2z41eInHUS0PK48Q6o/BnvU= ARC-Authentication-Results: i=1; server2.sourceware.org DKIM-Filter: OpenDKIM Filter v2.11.0 sourceware.org B2D224BAE7F6 Received: from imap1.dmz-prg2.suse.org (imap1.dmz-prg2.suse.org [IPv6:2a07:de40:b281:104:10:150:64:97]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature RSA-PSS (4096 bits) server-digest SHA256) (No client certificate requested) by smtp-out2.suse.de (Postfix) with ESMTPS id 9EC4A5BCDC; Thu, 29 Jan 2026 08:11:30 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=suse.de; s=susede2_rsa; t=1769674290; h=from:from:reply-to: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=Ins1vMIAQEIsoI1duPBtBYJozfdSPdK/9ba6nfS/D2U=; b=wzO1rP6NVUv7LiKe53gpg0ICmPk343cOT2gguzXU+kUCKW4HVF/JCnR8DghAyu64E1yKN1 rPMtRMJBjIYb+3v6eG1CVyvDmFq0HR7Ai4SsGGeIn3NLUnv1QhvH0vnznQQDczvLiNd3P6 OIdAt+4VToViL//ZUpGTEz/frexNGMg= DKIM-Signature: v=1; a=ed25519-sha256; c=relaxed/relaxed; d=suse.de; s=susede2_ed25519; t=1769674290; h=from:from:reply-to: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=Ins1vMIAQEIsoI1duPBtBYJozfdSPdK/9ba6nfS/D2U=; b=qrrAzbjX6XxUw/sIa13XA6NKvCn+pquj2mQ/Y8Ww6FUJXiqPAAcwtku6Wq7swMrcoBWVn/ ZClu39vSzVpziHCg== Authentication-Results: smtp-out2.suse.de; dkim=pass header.d=suse.de header.s=susede2_rsa header.b=wzO1rP6N; dkim=pass header.d=suse.de header.s=susede2_ed25519 header.b=qrrAzbjX DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=suse.de; s=susede2_rsa; t=1769674290; h=from:from:reply-to: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=Ins1vMIAQEIsoI1duPBtBYJozfdSPdK/9ba6nfS/D2U=; b=wzO1rP6NVUv7LiKe53gpg0ICmPk343cOT2gguzXU+kUCKW4HVF/JCnR8DghAyu64E1yKN1 rPMtRMJBjIYb+3v6eG1CVyvDmFq0HR7Ai4SsGGeIn3NLUnv1QhvH0vnznQQDczvLiNd3P6 OIdAt+4VToViL//ZUpGTEz/frexNGMg= DKIM-Signature: v=1; a=ed25519-sha256; c=relaxed/relaxed; d=suse.de; s=susede2_ed25519; t=1769674290; h=from:from:reply-to: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=Ins1vMIAQEIsoI1duPBtBYJozfdSPdK/9ba6nfS/D2U=; b=qrrAzbjX6XxUw/sIa13XA6NKvCn+pquj2mQ/Y8Ww6FUJXiqPAAcwtku6Wq7swMrcoBWVn/ ZClu39vSzVpziHCg== Received: from imap1.dmz-prg2.suse.org (localhost [127.0.0.1]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature RSA-PSS (4096 bits) server-digest SHA256) (No client certificate requested) by imap1.dmz-prg2.suse.org (Postfix) with ESMTPS id 7E33E3EA61; Thu, 29 Jan 2026 08:11:30 +0000 (UTC) Received: from dovecot-director2.suse.de ([2a07:de40:b281:106:10:150:64:167]) by imap1.dmz-prg2.suse.org with ESMTPSA id VpVSHTIWe2ltUQAAD6G6ig (envelope-from ); Thu, 29 Jan 2026 08:11:30 +0000 Message-ID: Date: Thu, 29 Jan 2026 09:11:30 +0100 MIME-Version: 1.0 User-Agent: Mozilla Thunderbird Subject: Re: [PATCHv3] gdb: fix gdb.base/inline-frame-cycle-unwind.exp for s390x To: Andrew Burgess , gdb-patches@sourceware.org References: <20260121122543.4129049-1-tdevries@suse.de> <4f6a5869bcd8cb6da8537457535f58121918ff1c.1769629812.git.aburgess@redhat.com> Content-Language: en-US From: Tom de Vries In-Reply-To: <4f6a5869bcd8cb6da8537457535f58121918ff1c.1769629812.git.aburgess@redhat.com> Content-Type: text/plain; charset=UTF-8; format=flowed Content-Transfer-Encoding: 7bit X-Spamd-Result: default: False [-4.51 / 50.00]; BAYES_HAM(-3.00)[100.00%]; NEURAL_HAM_LONG(-1.00)[-1.000]; R_DKIM_ALLOW(-0.20)[suse.de:s=susede2_rsa,suse.de:s=susede2_ed25519]; NEURAL_HAM_SHORT(-0.20)[-1.000]; MIME_GOOD(-0.10)[text/plain]; MX_GOOD(-0.01)[]; FUZZY_RATELIMITED(0.00)[rspamd.com]; RCVD_VIA_SMTP_AUTH(0.00)[]; ARC_NA(0.00)[]; MID_RHS_MATCH_FROM(0.00)[]; MIME_TRACE(0.00)[0:+]; RCPT_COUNT_TWO(0.00)[2]; RCVD_TLS_ALL(0.00)[]; DKIM_SIGNED(0.00)[suse.de:s=susede2_rsa,suse.de:s=susede2_ed25519]; FROM_EQ_ENVFROM(0.00)[]; FROM_HAS_DN(0.00)[]; TO_DN_SOME(0.00)[]; RCVD_COUNT_TWO(0.00)[2]; TO_MATCH_ENVRCPT_ALL(0.00)[]; DBL_BLOCKED_OPENRESOLVER(0.00)[suse.de:mid,suse.de:dkim,suse.de:email]; DKIM_TRACE(0.00)[suse.de:+] X-Rspamd-Queue-Id: 9EC4A5BCDC X-Rspamd-Action: no action X-Rspamd-Server: rspamd2.dmz-prg2.suse.org 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 1/28/26 8:52 PM, Andrew Burgess wrote: > Tom, > > Here's what I propose we push to resolve this. The code changes are > basically as you posted in v2 with some tweaks to the comments. The > commit message is updated based on my feedback to your v2. > > I'll push this next week, or sooner if you give a +1. > Hi Andrew, I read through this, and other than one typo noted below, LGTM. So, +1. > Thanks, > Andrew > > --- > > With test-case gdb.base/inline-frame-cycle-unwind.exp on s390x-linux, I run > into: > > ... > (gdb) bt > #0 inline_func () at inline-frame-cycle-unwind.c:49 > #1 normal_func () at inline-frame-cycle-unwind.c:32 > #2 0x000000000100065c in inline_func () at inline-frame-cycle-unwind.c:45 > #3 normal_func () at inline-frame-cycle-unwind.c:32 > Backtrace stopped: previous frame identical to this frame (corrupt stack?) > (gdb) FAIL: $exp: bt: cycle at level 5: backtrace when the unwind is broken \ > at frame 5 > ... > > In contrast, on x86_64-linux, I get: > > ... > (gdb) bt > #0 inline_func () at inline-frame-cycle-unwind.c:49 > #1 normal_func () at inline-frame-cycle-unwind.c:32 > #2 0x0000000000401157 in inline_func () at inline-frame-cycle-unwind.c:45 > #3 normal_func () at inline-frame-cycle-unwind.c:32 > #4 0x0000000000401157 in inline_func () at inline-frame-cycle-unwind.c:45 > #5 normal_func () at inline-frame-cycle-unwind.c:32 > Backtrace stopped: previous frame identical to this frame (corrupt stack?) > (gdb) PASS: $exp: bt: cycle at level 5: backtrace when the unwind is broken \ > at frame 5 > ... > > To understand what's going wrong here, we first need to understand > what this test was trying to do. > > The test tries to create a frame-cycle using a custom Python > unwinder. A frame-cycle occurs when a frame in the backtrace has the > same frame-id as an earlier frame. As soon as GDB finds a frame with > a frame-id that it has seen before then the backtrace will be > terminated with the message: > > Backtrace stopped: previous frame identical to this frame (corrupt stack?) > > A Python frame unwinder does two jobs: > > - It provides the frame-id for a frame #n, and > > - it provides the unwound register values for the previous (#n + 1) > frame. > > For a frame not claimed by a Python frame unwinder, GDB will compute > the frame-id based off of the register values. Particularly, the $pc > and $sp, or $fp registers (or the architecture's equivalent). > > In this test then, our frame unwinder does something a little > strange. When we want to stop a frame #5, the frame unwinder claims a -> at Thanks, - Tom > frame #5. The frame unwinder then tries to give frame #5 its "normal" > frame-id, but, instead of unwinding the register values, we provide > frame #6 with all of the register values from frame #5 unmodified. > > The frame unwinder does not claim frame #6, instead GDB uses its > "normal" logic to compute the frame-id. As the registers in frame #6 > are identical to the values in frame #5, GDB computes the same > frame-id for frame #6 as we supplied for frame #5, and thus a frame > cycle is created. > > Notice I said that we try to give frame #5 its "normal" frame-id. > > When this test was originally written there was no easy way to access > the frame-id for a frame. So instead, the test was written to try and > recreate the frame-id based on the frame stack pointers, and the frame > size (difference between subsequent frames). And this worked fine for > a bunch of common architectures, like x86-64 and AArch64. > > But unfortunately this frame-id computation doesn't work on s390. > > For this explanation we only care about two parts of the frame-id, the > code address, and the stack address. The code address part is usually > the start of the function for a particular frame. Our computation of > this was fine. > > The stack address in the frame-id isn't the actual $sp value. > Instead, this is usually the Call Frame Address (CFA) as defined in > the DWARF. How this is calculated really depends on the DWARF, but is > often influenced by the architectures ABI. > > On x86-64 and AArch64 the CFA is usually the $sp immediately on entry > to a frame, before any stack adjustment is performed. Thus, if we > take the frame size (difference between $sp in two frames), and add > this to the current $sp value, we successfully calculate the CFA, > which we can use in the frame-id. > > On s390 though, this is not the case, the calculation of the CFA is > more complex as the s390 ABI requires that caller frames allocate a > 160 byte Register Save Area below the stack pointer. Because of this, > when the Python unwinder ties to calculate the real frame-id for a > frame, it gets the CFA wrong, and inadvertently ends up calculating a > frame-id which matches an earlier frame-id. In the example above, > frame #5 ends up matching frame #3. Because frame #4 is an inline > frame GDB ends up terminating the backtrace after frame #3. > > The fix for this is actually pretty simple. Since this test was > originally written GDB has gained the 'maint print frame-id' command. > So instead of trying to calculate the frame-id we can just capture the > frame-ids that GDB generates, and then, once the Python frame unwinder > is in use, we can repeat the previously captured frame-id back to > GDB. > > This fixes the issues seen on s390, and doesn't impact testing on the > other architectures. > > Tested on x86_64-linux and s390x-linux. > > Co-Authored-By: Tom de Vries > --- > .../gdb.base/inline-frame-cycle-unwind.exp | 3 + > .../gdb.base/inline-frame-cycle-unwind.py | 74 ++++++++++++++----- > 2 files changed, 59 insertions(+), 18 deletions(-) > > diff --git a/gdb/testsuite/gdb.base/inline-frame-cycle-unwind.exp b/gdb/testsuite/gdb.base/inline-frame-cycle-unwind.exp > index cc9585ee325..fd9cba78f9a 100644 > --- a/gdb/testsuite/gdb.base/inline-frame-cycle-unwind.exp > +++ b/gdb/testsuite/gdb.base/inline-frame-cycle-unwind.exp > @@ -72,6 +72,9 @@ gdb_continue_to_breakpoint "stop at test breakpoint" > gdb_test_no_output "source ${pyfile}"\ > "import python scripts" > > +# Print the captured frame IDs. > +gdb_test "python print_frame_ids()" > + > # Test with and without filters. > foreach bt_cmd { "bt" "bt -no-filters" } { > with_test_prefix "$bt_cmd" { > diff --git a/gdb/testsuite/gdb.base/inline-frame-cycle-unwind.py b/gdb/testsuite/gdb.base/inline-frame-cycle-unwind.py > index 80cfb864f21..1a3fc5517ad 100644 > --- a/gdb/testsuite/gdb.base/inline-frame-cycle-unwind.py > +++ b/gdb/testsuite/gdb.base/inline-frame-cycle-unwind.py > @@ -13,6 +13,8 @@ > # You should have received a copy of the GNU General Public License > # along with this program. If not, see . > > +import re > + > import gdb > from gdb.unwinder import Unwinder > > @@ -21,10 +23,17 @@ from gdb.unwinder import Unwinder > # was written for. > stop_at_level = None > > -# Set this to the stack frame size of frames 1, 3, and 5. These > -# frames will all have the same stack frame size as they are the same > -# function called recursively. > -stack_adjust = None > +# List of FrameId instances, one for each stack frame. This list is > +# populated when this file is sourced into GDB. > +frame_ids = [] > + > + > +# To aid debugging, print the captured FrameId instances. > +def print_frame_ids(): > + for level, fid in enumerate(frame_ids): > + print( > + "frame-id for frame #%s: {stack=0x%x,code=0x%x}" % (level, fid.sp, fid.pc) > + ) > > > class FrameId(object): > @@ -49,17 +58,39 @@ class TestUnwinder(Unwinder): > if stop_at_level is None or pending_frame.level() != stop_at_level: > return None > > - if stack_adjust is None: > - raise gdb.GdbError("invalid stack_adjust") > + if len(frame_ids) < stop_at_level: > + raise gdb.GdbError("not enough parsed frame-ids") > > if stop_at_level not in [1, 3, 5]: > raise gdb.GdbError("invalid stop_at_level") > > - sp_desc = pending_frame.architecture().registers().find("sp") > - sp = pending_frame.read_register(sp_desc) + stack_adjust > - pc = (gdb.lookup_symbol("normal_func"))[0].value().address > - unwinder = pending_frame.create_unwind_info(FrameId(sp, pc)) > + # Set the frame-id for this frame to its actual, expected > + # frame-id, which we captured in the FRAME_IDS list. > + unwinder = pending_frame.create_unwind_info(frame_ids[stop_at_level]) > > + # Provide the register values for the caller frame, that is, > + # the frame at 'STOP_AT_LEVEL + 1'. > + # > + # We forward all of the register values unchanged from this > + # frame. > + # > + # What this means is that, as far as GDB is concerned, the > + # caller frame will appear to be identical to this frame. Of > + # particular importance, we send $pc and $sp unchanged to the > + # caller frame. > + # > + # Because the caller frame has the same $pc and $sp as this > + # frame, GDB will compute the same frame-id for the caller > + # frame as we just supplied for this frame (above). This > + # creates the artificial frame cycle which is the whole point > + # of this test. > + # > + # NOTE: Forwarding all registers unchanged like this to the > + # caller frame is not how you'd normally write a frame > + # unwinder. Some registers might indeed be unmodified between > + # frames, but we'd usually expect the $sp and/or the $pc to > + # change. This test is deliberately doing something weird in > + # order to force a cycle, and so test GDB. > for reg in pending_frame.architecture().registers("general"): > val = pending_frame.read_register(reg) > # Having unavailable registers leads to a fall back to the standard > @@ -76,11 +107,18 @@ gdb.unwinder.register_unwinder(None, TestUnwinder(), True) > # > # main -> normal_func -> inline_func -> normal_func -> inline_func -> normal_func -> inline_func > # > -# Compute the stack frame size of normal_func, which has inline_func > -# inlined within it. > -f0 = gdb.newest_frame() > -f1 = f0.older() > -f2 = f1.older() > -f0_sp = f0.read_register("sp") > -f2_sp = f2.read_register("sp") > -stack_adjust = f2_sp - f0_sp > +# Iterate through frames 0 to 6, parse their frame-id and store it > +# into the global FRAME_IDS list. > +for i in range(7): > + # Get the frame-id in a verbose text form. > + output = gdb.execute("maint print frame-id %d" % i, to_string=True) > + > + # Parse the frame-id in OUTPUT, find the stack and code addresses. > + match = re.search(r"stack=(0x[0-9a-fA-F]+).*?code=(0x[0-9a-fA-F]+)", output) > + if not match: > + raise gdb.GdbError("Could not parse frame-id for frame #%d" % i) > + > + # Create the FrameId object. > + sp_addr = int(match.group(1), 16) > + pc_addr = int(match.group(2), 16) > + frame_ids.append(FrameId(sp_addr, pc_addr)) > > base-commit: c02c23175e47f29907a0a3af3f6e6a404bc99719