From mboxrd@z Thu Jan 1 00:00:00 1970 Return-Path: Received: from simark.ca by simark.ca with LMTP id a693OoJpeml6VhoAWB0awg (envelope-from ) for ; Wed, 28 Jan 2026 14:54:42 -0500 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=GJBNVzUp; dkim-atps=neutral Received: by simark.ca (Postfix, from userid 112) id DFBAD1E0DD; Wed, 28 Jan 2026 14:54:42 -0500 (EST) X-Spam-Checker-Version: SpamAssassin 4.0.1 (2024-03-25) on simark.ca X-Spam-Level: X-Spam-Status: No, score=-3.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,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 CCDA11E08D for ; Wed, 28 Jan 2026 14:54:41 -0500 (EST) Received: from vm01.sourceware.org (localhost [127.0.0.1]) by sourceware.org (Postfix) with ESMTP id 372C94BA903D for ; Wed, 28 Jan 2026 19:54:41 +0000 (GMT) DKIM-Filter: OpenDKIM Filter v2.11.0 sourceware.org 372C94BA903D 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=GJBNVzUp Received: from us-smtp-delivery-124.mimecast.com (us-smtp-delivery-124.mimecast.com [170.10.133.124]) by sourceware.org (Postfix) with ESMTP id D16A64BAD168 for ; Wed, 28 Jan 2026 19:52:22 +0000 (GMT) DMARC-Filter: OpenDMARC Filter v1.4.2 sourceware.org D16A64BAD168 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 D16A64BAD168 Authentication-Results: server2.sourceware.org; arc=none smtp.remote-ip=170.10.133.124 ARC-Seal: i=1; a=rsa-sha256; d=sourceware.org; s=key; t=1769629942; cv=none; b=jtK4NCFlIcGwBMiSyM8OuJUYSSwYHCQX0uMYrPRvqDfDtTXyIkKcckxPg6avVKzlzbV8lLTlZ7mOSMqkLTVZotY2Uh/43f1/9yg8TZDMofvOhxjqKU1sO6QeW+ZGBjw+S09lYDl6X8996MoYqqls+TS++foI0zlaZJYXJNDy43w= ARC-Message-Signature: i=1; a=rsa-sha256; d=sourceware.org; s=key; t=1769629942; c=relaxed/simple; bh=Br/mpQVGvNrCrTxmsYilJehvJ+KkxcM4vMC6wCcf8QQ=; h=DKIM-Signature:From:To:Subject:Date:Message-Id:MIME-Version; b=eIOTjLpLfcXjF6DerkOSawpu92/MN0uhzmWdXENexeHOrUC+Qwt5766CY7pnsFW1mWPcfq2FxIcOupWqW4gx/A55q2l/26JASRefLmGG0cEBR7P4UdWZEq7C4UOk0E7aaRB14R4ygEJJd2C5tv3Gx63x1XgL2wWe5ndMCOXdQI8= ARC-Authentication-Results: i=1; server2.sourceware.org DKIM-Filter: OpenDKIM Filter v2.11.0 sourceware.org D16A64BAD168 DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=redhat.com; s=mimecast20190719; t=1769629942; 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=GtYW94pgiRMXZEvqMRmoqQJEnWMILuYX7tznARyTKcw=; b=GJBNVzUp4jWRlts942UTXTxNpgwz4Mjs3uM3mBRX9ZyfGINj5mfp9ijvL0VOBBneGoJQNw EI3gnPlnb4hhC24dqWvqT6JhMDN6ZcCQm+ZH8jZ4szA5UylBtv2PWLCffkzVufCOBqxPDP +Cm7sqhD2O/JrlWQWu4ggnFLyzGZoNo= Received: from mail-wm1-f70.google.com (mail-wm1-f70.google.com [209.85.128.70]) by relay.mimecast.com with ESMTP with STARTTLS (version=TLSv1.3, cipher=TLS_AES_256_GCM_SHA384) id us-mta-584-y_Ne70F7Mpa7pOO3oMXtnA-1; Wed, 28 Jan 2026 14:52:21 -0500 X-MC-Unique: y_Ne70F7Mpa7pOO3oMXtnA-1 X-Mimecast-MFC-AGG-ID: y_Ne70F7Mpa7pOO3oMXtnA_1769629940 Received: by mail-wm1-f70.google.com with SMTP id 5b1f17b1804b1-4801d21c280so1571505e9.1 for ; Wed, 28 Jan 2026 11:52:20 -0800 (PST) X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20230601; t=1769629939; x=1770234739; 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; bh=GtYW94pgiRMXZEvqMRmoqQJEnWMILuYX7tznARyTKcw=; b=B2tYZ0aEkQhvgtfPeo9EyNIlVgd0xPnFvf24PtdyLAnBFjEyxRXSF+54CKUv3ny1EI ThIkJqTjMI7TugUdPv9qZ7z0aygBpTWbEHa/FjeXDwiBwZNBZKy05SmFqGl2mfO1vUw9 iz8qGHM3SxEmAffpp6/2KkQARedZ0Ev6dXnODRRUWwc1YVIkbQHuY5t2OxKuoLy1/NDc 9sFyuB4dT4fXBj797GClnE/h644Zy533kZL7h1mzbjnmT9bWun7V4FPgELfdwroiTIE7 uDzyjwp+s57uOSrrUq63MG077uF5uIl+UbGHTRvkRzi3uhE8cjTgMeL0cI42cTpGIZXN 86Tg== X-Gm-Message-State: AOJu0YwVr+CePD+RrfIrrN03o2OA/7Y0WZm8GxtfifrhQSu4CcFLBPoC UkwxD5rejQmdKsBrHJr3QLdz3bDX2rGaUiYkOwykPPkcD/Pgg0k9eltlNzzwTRgoErh0CkDWtek rn8L1ERTi0918G4RhcGKMI+efnK2gHHnWKXjclZWWnHTp3Hc/u+amUBOrWmSG4nW/ya7D1q4YNx lhq6ttYT8u6ee/AmWV8ii9hEJDLJB6nOYH1eJCzepGqymgpbM= X-Gm-Gg: AZuq6aIMtTJdN8OdvoYQHN0K/0UXQs9MaNtqhYnQWcWcxHJRfrXd+1gvRxehAQn/qdg 6DEoow6YdgUKyY66axX+hsWhrG1/4yaTndB6K5P6VF4QVuS5u2vrsaFZEfNNqDheLHRmazaqGVV Owy3zCvx3CjH+arFwV1yu/id1Z5Ixy27N3z9FSbqav9Tm4+MFra6nkFkq3Yld31QuG8z59wWg68 BMQu47xSnQ3xr2ri9lE92tyogy5v3sncdQosobRkWzVgCYzOLNqBX2AoJxInmpkqCIECnkusV2I ltNej1qDW08fVLQ1sCg0fjLL6tB1vhpaOLnyg0gvWMh8RUDnDPehEj76k6GhCZE6lv9XFpfJnSO HIRZBApvXJCWu909Zyy43yvY247S1 X-Received: by 2002:a05:600c:4e8b:b0:480:4a4f:c366 with SMTP id 5b1f17b1804b1-48069c486e6mr71099825e9.20.1769629938973; Wed, 28 Jan 2026 11:52:18 -0800 (PST) X-Received: by 2002:a05:600c:4e8b:b0:480:4a4f:c366 with SMTP id 5b1f17b1804b1-48069c486e6mr71099515e9.20.1769629938329; Wed, 28 Jan 2026 11:52:18 -0800 (PST) Received: from localhost (92.40.184.2.threembb.co.uk. [92.40.184.2]) by smtp.gmail.com with ESMTPSA id 5b1f17b1804b1-48066beeaf9sm159840585e9.6.2026.01.28.11.52.17 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Wed, 28 Jan 2026 11:52:18 -0800 (PST) From: Andrew Burgess To: gdb-patches@sourceware.org Cc: Andrew Burgess , Tom de Vries Subject: [PATCHv3] gdb: fix gdb.base/inline-frame-cycle-unwind.exp for s390x Date: Wed, 28 Jan 2026 19:52:13 +0000 Message-Id: <4f6a5869bcd8cb6da8537457535f58121918ff1c.1769629812.git.aburgess@redhat.com> X-Mailer: git-send-email 2.25.4 In-Reply-To: <20260121122543.4129049-1-tdevries@suse.de> References: <20260121122543.4129049-1-tdevries@suse.de> MIME-Version: 1.0 X-Mimecast-Spam-Score: 0 X-Mimecast-MFC-PROC-ID: ntJZMYwUYFarGXCpw_DtxlHcn-Fu6T0ADIaBjeKL-SE_1769629940 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 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. 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 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 -- 2.25.4