From patchwork Tue Jun 2 09:54:38 2026 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Andrew Burgess X-Patchwork-Id: 136287 Return-Path: X-Original-To: patchwork@sourceware.org Delivered-To: patchwork@sourceware.org Received: from vm01.sourceware.org (localhost [IPv6:::1]) by sourceware.org (Postfix) with ESMTP id 703E04BA2E25 for ; Tue, 2 Jun 2026 09:55:53 +0000 (GMT) DKIM-Filter: OpenDKIM Filter v2.11.0 sourceware.org 703E04BA2E25 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=BaSMPaGQ X-Original-To: gdb-patches@sourceware.org Delivered-To: gdb-patches@sourceware.org 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 BFA0E4BA2E25 for ; Tue, 2 Jun 2026 09:54:46 +0000 (GMT) DMARC-Filter: OpenDMARC Filter v1.4.2 sourceware.org BFA0E4BA2E25 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 BFA0E4BA2E25 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=1780394086; cv=none; b=lxBoOmF6V1ze/NvvkscrwEq4r7ZAYbaj3FAkS7mZGMQgn/A+k6SOETq2vbUmsbS6/ncbW9dCIL5SHcuVBrth4qBHCrbzQs0rtaafFvMUuC9S8WpKSjgqUYgGNXMHzPp6Ak1nDt3YAA4/PDCs0OhCYLl33XQHUEo+MhZg17K9I8Q= ARC-Message-Signature: i=1; a=rsa-sha256; d=sourceware.org; s=key; t=1780394086; c=relaxed/simple; bh=NhZVxopWaRTG1ohQIWRepMc07JCwU2MvQ8qIpiMq8Ag=; h=DKIM-Signature:From:To:Subject:Date:Message-Id:MIME-Version; b=EpgUlOetHOxgvLyZjOTC3C5noVF66lu/MpdXfa6VLEKW5HPgbLkGVuy4IASLQY8jXenBUB3/EqdQeUevy8+q0Den3oOUDFCFj7hsfZnAgZFnU9CT3OKLaTGOTUXXF1hrzsKX7VViumaHvGzX6NwMRtRik3jngDm9M7dbctyn6CQ= 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=BaSMPaGQ DKIM-Filter: OpenDKIM Filter v2.11.0 sourceware.org BFA0E4BA2E25 DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=redhat.com; s=mimecast20190719; t=1780394086; 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=hG38LSQ/F0HQoAnPAB+CGUu7UavzlmtGq2bB0MDluik=; b=BaSMPaGQbZrDhn2/MIYr1PxHI1P4KnbWG1l8Scs0j0cNMPuRdmUIllUQ0Po1l/Vuo6IEH9 MJR/8flm0XLtF/mch2JO/Ed8+y3lv+57BysQA47esW/WgM+Ms1LRBRN+hpfIxgAgxAQAD+ WKrfjMDv7U3stoy1yf6mm0sdIs+2u7M= Received: from mail-wr1-f70.google.com (mail-wr1-f70.google.com [209.85.221.70]) by relay.mimecast.com with ESMTP with STARTTLS (version=TLSv1.3, cipher=TLS_AES_256_GCM_SHA384) id us-mta-319-nUgt8NL7PhS_APmA8LLiZg-1; Tue, 02 Jun 2026 05:54:45 -0400 X-MC-Unique: nUgt8NL7PhS_APmA8LLiZg-1 X-Mimecast-MFC-AGG-ID: nUgt8NL7PhS_APmA8LLiZg_1780394084 Received: by mail-wr1-f70.google.com with SMTP id ffacd0b85a97d-45ef3aeeb41so2351610f8f.1 for ; Tue, 02 Jun 2026 02:54:44 -0700 (PDT) X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20251104; t=1780394084; x=1780998884; 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=hG38LSQ/F0HQoAnPAB+CGUu7UavzlmtGq2bB0MDluik=; b=KgSNdlTkyNzd0OPaCWFo+5AwjxoK5mKNuOhbV4HN942bZi4RRWGC/fo1HqOvNSI2T0 QZ8ZRLTVQW+DM+kvbhkh6h0JZIh0Wuua3EpEj+K4qfgEzFncGGIXVARwBIraG+GvjdBL PgqNrEXrNLfvvSjBBq1zFtvf/G2ufq79F0aQykBbAeKiIfx5dMQU5yCRxhuVB0O+FAxO 4TdcnV7Bipl+mdIM32DWhrXxW7es9Ar6vQDwoEjEJfaF/c+6oR+S740krvSZSGj5EkcD F7GmCH6AqEbSKrKNvtaHZWPlT2V8brGOt+L4qWNBYKET7qNRHCpcFz9TwbJvumRFgR5l 7ZYw== X-Gm-Message-State: AOJu0YwR7YCIgreVpaOOsMFISfXh85YGTnJQLdGR1ySDQ7x+oPfyLPLj /OASJG80Qw74X2qvAwectfzie4E43j6UprIPBUnhC7RYM2s9eTPTrluSO6tX2RFdJAG5ZEohcxn SLGfoEI9dZkrXjfwwZfQLrk0o7RMQCIcTNyJpSvV15MV/1cr0PVfA7CtEuCPwQDAbqhRGQURwsI wKM2Zne9JmXalthNa8TG9Vf6+c4wUw7aGm54Xs4dUkcBypVnk= X-Gm-Gg: Acq92OHelcORUJ5X61tRpr0SqdQQ9xbZ52tZk6EbsMnqYJKqfBT3Rr/edF/2RABcax8 WxdtWXfQPmYoIAprev3X0dpM5hXs33nNVfARsS5+bK8AMF2AYYGYYxnvSDiY5kqGS4AGsJumx8m ZJGxnpfG4BTktB4339OH11R8QaK4YK+jdV+wMO5yIQfxyrwT5bkVcYd+2H8QzVJpFr3VPWeflQa bTtbndigQaWWbrNTf1OPQ29MuKYZz6KIHdG8e7tMLK3hTL6wKzX0KGPbCzz9RCQsdCxIPbuHpyX FZJATLhtkJ5N+bZR+695fL/FnRH9PQg8GLb8ZIRss59nQ+FrQlk+6MCodtxAHk9oRjm2o2utF+T AU8jyrO8R0s56CPvM1rH1uWlJow== X-Received: by 2002:a05:600c:c092:b0:489:1ba8:5bf0 with SMTP id 5b1f17b1804b1-490a2959113mr247805855e9.21.1780394083913; Tue, 02 Jun 2026 02:54:43 -0700 (PDT) X-Received: by 2002:a05:600c:c092:b0:489:1ba8:5bf0 with SMTP id 5b1f17b1804b1-490a2959113mr247805325e9.21.1780394083416; Tue, 02 Jun 2026 02:54:43 -0700 (PDT) Received: from localhost ([213.31.44.43]) by smtp.gmail.com with ESMTPSA id 5b1f17b1804b1-490b1006988sm24235995e9.5.2026.06.02.02.54.42 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Tue, 02 Jun 2026 02:54:42 -0700 (PDT) From: Andrew Burgess To: gdb-patches@sourceware.org Cc: Andrew Burgess Subject: [PATCH 1/2] gdb/testsuite: don't overwrite test executable in py-frame.exp Date: Tue, 2 Jun 2026 10:54:38 +0100 Message-Id: <49510890858b4922f37907ddf1a0c1b449760874.1780393815.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: sp2yEhMdjK9w8RAu6QxQCfNau1u3t8ffxYaERDF_VFM_1780394084 X-Mimecast-Originator: redhat.com content-type: text/plain; charset="US-ASCII"; x-default=true X-Spam-Status: No, score=-12.0 required=5.0 tests=BAYES_00, DKIMWL_WL_HIGH, DKIM_SIGNED, DKIM_VALID, DKIM_VALID_AU, DKIM_VALID_EF, GIT_PATCH_0, RCVD_IN_DNSWL_BLOCKED, RCVD_IN_MSPIKE_H3, RCVD_IN_MSPIKE_WL, RCVD_IN_PBL, SPF_HELO_PASS, SPF_NONE, TXREP shortcircuit=no autolearn=ham autolearn_force=no version=3.4.6 X-Spam-Checker-Version: SpamAssassin 3.4.6 (2021-04-09) on sourceware.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~patchwork=sourceware.org@sourceware.org In gdb.python/py-frame.exp when we re-compile the test executable without debug information, give it a new, unique, name to make re-running the tests outside the testsuite easier. While I was in the area I removed a `gdb_exit` call that was not necessary, `prepare_for_testing` calls `clean_restart` which already does a `gdb_exit`. There should be no change in what is being tested after this commit. --- gdb/testsuite/gdb.python/py-frame.exp | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/gdb/testsuite/gdb.python/py-frame.exp b/gdb/testsuite/gdb.python/py-frame.exp index ca350cf03c2..9c425f6aa98 100644 --- a/gdb/testsuite/gdb.python/py-frame.exp +++ b/gdb/testsuite/gdb.python/py-frame.exp @@ -204,8 +204,7 @@ gdb_test "python print(gdb.selected_frame().read_register(bad_object))" \ "test Frame.read_register with bad_type object" # Compile again without debug info. -gdb_exit -if { [prepare_for_testing "failed to prepare" ${testfile} ${srcfile} {}] } { +if { [prepare_for_testing "failed to prepare" ${testfile}-nodebug ${srcfile} {}] } { return } From patchwork Tue Jun 2 09:54:39 2026 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Andrew Burgess X-Patchwork-Id: 136288 Return-Path: X-Original-To: patchwork@sourceware.org Delivered-To: patchwork@sourceware.org Received: from vm01.sourceware.org (localhost [IPv6:::1]) by sourceware.org (Postfix) with ESMTP id DC9084BA2E1B for ; Tue, 2 Jun 2026 09:56:18 +0000 (GMT) DKIM-Filter: OpenDKIM Filter v2.11.0 sourceware.org DC9084BA2E1B 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=amVHJ9bP X-Original-To: gdb-patches@sourceware.org Delivered-To: gdb-patches@sourceware.org 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 050CA4BA23C8 for ; Tue, 2 Jun 2026 09:54:51 +0000 (GMT) DMARC-Filter: OpenDMARC Filter v1.4.2 sourceware.org 050CA4BA23C8 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 050CA4BA23C8 Authentication-Results: sourceware.org; arc=none smtp.remote-ip=170.10.133.124 ARC-Seal: i=1; a=rsa-sha256; d=sourceware.org; s=key; t=1780394092; cv=none; b=H2n3d5M1qFgN+ptOIIBfIqzDLCkM7Al4zsDjeTL7T1e7wOCjn6N/N7qTWNLPLoohqb8HsWWbnbGcKvKWTgEN6SmgbwZcR/KP4Hn4Iz6CyTRgwP5eYm715vzj2AVtM+mLOiWOg4jfSvaPKZlQYQ+5Hz/KEeVzSxQYA/8dZ1ZALiE= ARC-Message-Signature: i=1; a=rsa-sha256; d=sourceware.org; s=key; t=1780394092; c=relaxed/simple; bh=RNd17yytGOQKvn78o95FtAsuhBmTbBWcbZWfaMDdZ2s=; h=DKIM-Signature:From:To:Subject:Date:Message-Id:MIME-Version; b=I46aWDJlVWPdoT5mLYPqegygWP/2l7KsIPIrSUkm4CZcT/2fevY62X+Nay4vt8dasMIfci/Zmc99Jg/rnd1J4P0duhQH2QBCrR4m9bpMxlL7z/iU2I0VtjCRplGvu6UlWfD3NJfRdsICUBs4KHA46Ngz51X8buhMCIYhv/HCWOI= 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=amVHJ9bP DKIM-Filter: OpenDKIM Filter v2.11.0 sourceware.org 050CA4BA23C8 DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=redhat.com; s=mimecast20190719; t=1780394091; 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=6fLbACmIeN5W5FtwtPR/SKCeW6SVFe2AMFfKlbOSask=; b=amVHJ9bP6twRkALLJwwh3y+r5+Pv58vtOAEHosipDcwzDJf5t/j/Ebem7zslbhBzZo8R7K jckn/TZVwTnpkH1nApZZ/To2DPCwuITGzKN22FvbFHzx5lg1eoQjkM818K1fJDvMYa3eb5 Wq1R+qsyUp3bCtcZj4N/3YqiAaT+TOk= Received: from mail-wm1-f69.google.com (mail-wm1-f69.google.com [209.85.128.69]) by relay.mimecast.com with ESMTP with STARTTLS (version=TLSv1.3, cipher=TLS_AES_256_GCM_SHA384) id us-mta-194-9WRNeoTSNNWdNFYPHEHL6A-1; Tue, 02 Jun 2026 05:54:50 -0400 X-MC-Unique: 9WRNeoTSNNWdNFYPHEHL6A-1 X-Mimecast-MFC-AGG-ID: 9WRNeoTSNNWdNFYPHEHL6A_1780394089 Received: by mail-wm1-f69.google.com with SMTP id 5b1f17b1804b1-490a763180bso21623575e9.0 for ; Tue, 02 Jun 2026 02:54:50 -0700 (PDT) X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20251104; t=1780394089; x=1780998889; 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=6fLbACmIeN5W5FtwtPR/SKCeW6SVFe2AMFfKlbOSask=; b=s1oJ4xo+aMaTtWn2IGatIcIUtAJEfn+DbUfGKdbHode3P/7qAFdx0tf40J8bRx7boK jZdQAw2Ej6Vee8KZdESlD982Y75Rnt7+/r4Uo2XfbuX1+EA958fxfMH8fD+xTaCkicU5 BabQfh0yKpx/L3k879NVQVDEhtSmuSQruaufmcxL09926XV59zJZsGgk+7KZpsHxbRCH OAGTpnagba+NEGgFawAcBDjaFc3bWPZsMdqwerrVvk07ezmHahXHugA+YxWVGfXilxyI hKC9yWUKv3LncVSmFKVnLJEAR8+iARiAVPRz3YveX2b0McBL9ZkMRJwXywxuAnkxryyn iRZQ== X-Gm-Message-State: AOJu0Ywfzm+IKeO65PxVbj8V0JcvImTvt9FMfkuFtOaWRBrp92fsFzws kSIdoK7VbQYXCjMXZPDUxmJFOq5GEQmQ7f7J7tW/lYAVbqqIhSMr/+tI6rqFik0J1PjBCMvmWo7 4x3tXiydQnCgbFBIcidBwTETiDisBEVs5QfALru4IAuemStmaLisBs+HlV7pXlf/QsN49I+fQWT 7HPJWkIAnY8mJT1dWn7fAlmRVX7IW1mdjoNnw9Vs0WGCR2Nfc= X-Gm-Gg: Acq92OFtqsUM0SEZUD/4aCxLkYov0pQ3f3zINVKltRocxZWHil8afKRrr0YnxPDNwr7 gBlEpIPe5n+G9WYeUzyScN5orFL+FBzfZ3+SEV+LZRUVNO8fAO1BwlQyfSCn4zdzV9K9FOO/JlI fNFdvPvYvTZDJFlFng1MiJJdNfgpAN0NvcDpR40k02GyvtuLRh2KyyQfFXJVJB799F7PN3Oma+e IPhfXDYTJM+pP+RbR6cQXKan0Ap/zzZ74puWYvsvJi+XK8/6bXYV6mjfXjju3cQN3/rZ6GMivUf m0/2IZmbodCRWm1ivRaPoL3KEMUBKSHXiW3H53M+uKYOqtE2AQeYgbtubXx+9cF/nkNLxqgu33Y AOcKJGmPp6Yi85cSYnsL+BqhaOw== X-Received: by 2002:a05:600c:c098:b0:489:1c32:210d with SMTP id 5b1f17b1804b1-490a2923cbbmr221522655e9.15.1780394088019; Tue, 02 Jun 2026 02:54:48 -0700 (PDT) X-Received: by 2002:a05:600c:c098:b0:489:1c32:210d with SMTP id 5b1f17b1804b1-490a2923cbbmr221521865e9.15.1780394087214; Tue, 02 Jun 2026 02:54:47 -0700 (PDT) Received: from localhost ([213.31.44.43]) by smtp.gmail.com with ESMTPSA id 5b1f17b1804b1-4909c103baesm97896965e9.17.2026.06.02.02.54.44 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Tue, 02 Jun 2026 02:54:44 -0700 (PDT) From: Andrew Burgess To: gdb-patches@sourceware.org Cc: Andrew Burgess Subject: [PATCH 2/2] gdb/python: remove frame_object::frame_id_is_next as unnecessary Date: Tue, 2 Jun 2026 10:54:39 +0100 Message-Id: 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: 1qntLD9k36A2Dr2epXFTHZO0maTyTKp5VWjH4DEdjgk_1780394089 X-Mimecast-Originator: redhat.com content-type: text/plain; charset="US-ASCII"; x-default=true X-Spam-Status: No, score=-13.3 required=5.0 tests=BAYES_00, DKIMWL_WL_HIGH, DKIM_SIGNED, DKIM_VALID, DKIM_VALID_AU, DKIM_VALID_EF, GIT_PATCH_0, KAM_SHORT, RCVD_IN_DNSWL_BLOCKED, RCVD_IN_HOSTKARMA_W, RCVD_IN_MSPIKE_H5, RCVD_IN_MSPIKE_WL, RCVD_IN_PBL, SPF_HELO_PASS, SPF_NONE, TXREP, URIBL_BLOCKED shortcircuit=no autolearn=ham autolearn_force=no version=3.4.6 X-Spam-Checker-Version: SpamAssassin 3.4.6 (2021-04-09) on sourceware.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~patchwork=sourceware.org@sourceware.org Remove the frame_object::frame_id_is_next field. This has been part of how Python handles frames since this code was first added in commit f8f6f20b6e37e6d219940fcad58b1f66124d11c1 back in 2009. The motivation for this field can be found in a couple of comments, there's this one on frame_id_is_next: /* Marks that the FRAME_ID member actually holds the ID of the frame next to this, and not this frames' ID itself. This is a hack to permit Python frame objects which represent invalid frames (i.e., the last frame_info in a corrupt stack). The problem arises from the fact that this code relies on FRAME_ID to uniquely identify a frame, which is not always true for the last "frame" in a corrupt stack (it can have a null ID, or the same ID as the previous frame). Whenever get_prev_frame returns NULL, we record the frame_id of the next frame and set FRAME_ID_IS_NEXT to 1. */ And this one in frame_info_to_frame_object: /* Try to get the previous frame, to determine if this is the last frame in a corrupt stack. If so, we need to store the frame_id of the next frame and not of this one (which is possibly invalid). */ This field is dealing with a problem that was present in older versions of GDB where not every stack frame had a valid frame-id, if the stack was corrupted in some way then the last frame might have an invalid frame-id. However, that is no longer the case. With current GDB there is a promise that every frame has a valid frame-id. I don't have a single commit to point to where this became the reality, but it is my understanding of current GDB. As an example, the frame-id of every frame (except #0) is computed as the frame is created, any frames with a duplicate frame-id, or any errors during computation of the frame-id, and the new frame is discarded. Additionally, our frame_info_ptr::reinflate mechanism relies on unique and valid frame-ids. The problem with the existing code is that the last frame in a corrupted stack will hold the frame-id of the next frame, that is, the more inner frame. This means we have two frames holding the same frame-id, and the only difference is the frame_id_is_next flag. However, the frapy_str function, which prints a string representation of the frame, doesn't take frame_id_is_next into account, so printing the last two frames in a corrupted stack, will print the same frame-id. We could fix this in frapy_str and also frapy_repr by checking the frame_id_is_next flag and then fetching the previous frame-id, but this would still assume that the previous frame has a valid-id, so we might as well just drop the frame_id_is_next flag and make everything simpler. There's a new test which exposes the incorrectly printed frame-id problem. While testing I needed to "fix" the results for two existing tests. In frame_info_to_frame_object, in order to figure out if we should use the next frame, we called get_prev_frame. This would cause GDB to always unwind 1 extra level of the stack. What this means is that, if there is an error, or some diagnostic output, when unwinding frame #1, then this will show up when trying to access frame #0 via the Python API as frame_info_to_frame_object on frame #0 would call get_prev_frame, which would then unwind frame #1. After this commit this is no longer the case. We only see output (either errors, or diagnostic output) associated with frame #1 when we actually try to unwind to frame #1. The two tests that needed updating were expecting output associated with frame #1 while printing frame #0. This is now fixed and the output for frame #1 occurs later on. I think this is an improvement. --- gdb/python/py-frame.c | 33 +--- gdb/testsuite/gdb.python/py-frame.exp | 33 ++++ gdb/testsuite/gdb.python/py-frame.py | 187 ++++++++++++++++++ .../gdb.python/py-pending-frame-level.exp | 2 +- gdb/testsuite/gdb.python/py-unwind.exp | 4 +- 5 files changed, 226 insertions(+), 33 deletions(-) create mode 100644 gdb/testsuite/gdb.python/py-frame.py diff --git a/gdb/python/py-frame.c b/gdb/python/py-frame.c index 0a1a0dffbf9..367c08917cd 100644 --- a/gdb/python/py-frame.c +++ b/gdb/python/py-frame.c @@ -32,16 +32,6 @@ struct frame_object : public PyObject { struct frame_id frame_id; struct gdbarch *gdbarch; - - /* Marks that the FRAME_ID member actually holds the ID of the frame next - to this, and not this frames' ID itself. This is a hack to permit Python - frame objects which represent invalid frames (i.e., the last frame_info - in a corrupt stack). The problem arises from the fact that this code - relies on FRAME_ID to uniquely identify a frame, which is not always true - for the last "frame" in a corrupt stack (it can have a null ID, or the same - ID as the previous frame). Whenever get_prev_frame returns NULL, we - record the frame_id of the next frame and set FRAME_ID_IS_NEXT to 1. */ - int frame_id_is_next; }; static_assert (gdb::is_python_allocatable_v); @@ -69,9 +59,6 @@ frame_object_to_frame_info (PyObject *obj) if (frame == NULL) return NULL; - if (frame_obj->frame_id_is_next) - frame = get_prev_frame (frame); - return frame; } @@ -371,22 +358,7 @@ frame_info_to_frame_object (const frame_info_ptr &frame) try { - - /* Try to get the previous frame, to determine if this is the last frame - in a corrupt stack. If so, we need to store the frame_id of the next - frame and not of this one (which is possibly invalid). */ - if (get_prev_frame (frame) == NULL - && get_frame_unwind_stop_reason (frame) != UNWIND_NO_REASON - && get_next_frame (frame) != NULL) - { - frame_obj->frame_id = get_frame_id (get_next_frame (frame)); - frame_obj->frame_id_is_next = 1; - } - else - { - frame_obj->frame_id = get_frame_id (frame); - frame_obj->frame_id_is_next = 0; - } + frame_obj->frame_id = get_frame_id (frame); frame_obj->gdbarch = get_frame_arch (frame); } catch (const gdb_exception &except) @@ -730,8 +702,7 @@ frapy_richcompare (PyObject *self, PyObject *other, int op) frame_object *self_frame = (frame_object *) self; frame_object *other_frame = (frame_object *) other; - if (self_frame->frame_id_is_next == other_frame->frame_id_is_next - && self_frame->frame_id == other_frame->frame_id) + if (self_frame->frame_id == other_frame->frame_id) result = Py_EQ; else result = Py_NE; diff --git a/gdb/testsuite/gdb.python/py-frame.exp b/gdb/testsuite/gdb.python/py-frame.exp index 9c425f6aa98..2bac1b6f5b9 100644 --- a/gdb/testsuite/gdb.python/py-frame.exp +++ b/gdb/testsuite/gdb.python/py-frame.exp @@ -203,6 +203,39 @@ gdb_test "python print(gdb.selected_frame().read_register(bad_object))" \ ".*Invalid type for register.*" \ "test Frame.read_register with bad_type object" +# Load the Python script, this includes a frame unwinder which can be +# used to "corrupt" the backtrace by introducing a cycle. +set remote_python_file [gdb_remote_download host ${srcdir}/${subdir}/${testfile}.py] +gdb_test_no_output "source ${remote_python_file}" "load python file" + +# Check that the frame-id as read by Python is correct for each frame +# in the backtrace. When STOP_AT_LEVEL is None then the stack is not +# corrupted. For other STOP_AT_LEVEL values a cycle is introduced +# into the stack after the numbered level. E.g. for STOP_AT_LEVEL 0, +# frame #1 will appear to have the same frame-id as #0, which +# terminates the unwind. +foreach_with_prefix stop_at_level { None 0 1 2 } { + gdb_test_no_output "python global_unwinder.stop_at_level = $stop_at_level" + + if { $stop_at_level eq "None" } { + set frame_limit 3 + } else { + set frame_limit [expr {$stop_at_level + 1}] + } + + for { set i 0 } { $i < $frame_limit } { incr i } { + gdb_test "frame $i" ".*" "select frame $i" + + set expected_output [capture_command_output "maint print frame-id" \ + [string_to_regexp "frame-id for frame #$i: "]] + set python_output [capture_command_output \ + "python print(gdb.selected_frame())" ""] + + gdb_assert { $python_output eq $expected_output } \ + "frame-id for frame $i" + } +} + # Compile again without debug info. if { [prepare_for_testing "failed to prepare" ${testfile}-nodebug ${srcfile} {}] } { return diff --git a/gdb/testsuite/gdb.python/py-frame.py b/gdb/testsuite/gdb.python/py-frame.py new file mode 100644 index 00000000000..320f977fb2b --- /dev/null +++ b/gdb/testsuite/gdb.python/py-frame.py @@ -0,0 +1,187 @@ +# Copyright (C) 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 . + +# A class which is a GDB frame unwinder. This frame unwinder can be +# used to corrupt the backtrace by tricking GDB into believing that +# there is a repeated frame-id in the backtrace. +# +# Sourcing this script creates a global called 'global_unwinder' and +# registers a global unwinder called 'stop-at-level'. The unwinder +# needs to be primed with the frame-ids copied from the current stack, +# this can be done by enabling the unwinder using the GDB command: +# +# (gdb) enable unwinder global stop-at-level +# +# You then need to set the level at which to corrupt the stack, e.g.: +# +# (gdb) python global_unwinder.stop_at_level = 3 +# +# This will allow the frames #0, #1, #2, and #3 to print correctly, +# but frame #4 will appear to be a repeat of frame #3, so GDB will +# terminate the backtrace. +# +# As the unwinder relies on cached frame-ids, then resuming the +# inferior for which the frame-ids were cached will cause the unwinder +# to auto-disable itself, after which you'll need to re-enable the +# next time the inferior stops. +# +# Changing the 'global_unwinder.stop_at_level' will flush the frame +# cache, so you can test breaking the stack at different levels. + + +import re +import gdb +from gdb.unwinder import Unwinder, FrameId + + +class corrupt_stack_unwinder(Unwinder): + def __init__(self): + # Is this unwinder enabled or not? Accessed via the 'enabled' + # property. + self._enabled = True + + # List of FrameId instances, one for each stack frame. This list is + # populated when this file is sourced into GDB. + self._frame_ids = [] + + # The inferior for which we cached self._frame_ids. The + # unwinder will only work within this inferior. + self._inferior = None + + # At what stack level should we break the unwind? + self._stop_at_level = None + + Unwinder.__init__(self, "stop-at-level") + + if gdb.selected_inferior().pid > 0: + self._update_frame_id_cache() + else: + self._enabled = False + + gdb.events.cont.connect(lambda ev: self._continued_event_handler(ev)) + + # Assume that the current inferior has a stack. Collect a FrameId + # object for each level of the current stack. + def _update_frame_id_cache(self): + self._frame_ids = [] + self._inferior = gdb.selected_inferior() + + frame = gdb.newest_frame() + while frame is not None: + # Get the frame-id in a verbose text form. + output = gdb.execute( + "maint print frame-id %d" % frame.level(), 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" % frame.level()) + + # Create the FrameId object. + sp_addr = int(match.group(1), 16) + pc_addr = int(match.group(2), 16) + self._frame_ids.append(FrameId(sp_addr, pc_addr)) + + frame = frame.older() + + + # Called when an inferior continues. If this is the inferior for + # which this unwinder collected the stack, then discard the + # collected FrameId objects and disable the unwinder. + def _continued_event_handler(self, event): + if gdb.selected_inferior() == self._inferior: + self._enabled = False + self._frame_ids = [] + self._inferior = None + + # To aid debugging, print the captured FrameId instances. + def print_frame_ids(self): + for level, fid in enumerate(self._frame_ids): + print( + "frame-id for frame #%s: {stack=0x%x,code=0x%x}" + % (level, fid.sp, fid.pc) + ) + + def __call__(self, pending_frame): + if self.stop_at_level is None or pending_frame.level() != self.stop_at_level: + return None + + if gdb.selected_inferior() != self._inferior: + return None + + if len(self._frame_ids) <= self.stop_at_level: + raise gdb.GdbError("not enough parsed frame-ids") + + # 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(self._frame_ids[self.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 + # unwinders. Don't add unavailable registers to avoid this. + if str(val) == "": + continue + unwinder.add_saved_register(reg, val) + return unwinder + + @property + def enabled(self): + return self._enabled + + @enabled.setter + def enabled(self, val): + if val: + self._enabled = False + self._update_frame_id_cache() + + self._enabled = val + + @property + def stop_at_level(self): + return self._stop_at_level + + @stop_at_level.setter + def stop_at_level(self, val): + self._stop_at_level = val + gdb.invalidate_cached_frames() + + +global_unwinder = corrupt_stack_unwinder() +gdb.unwinder.register_unwinder(None, global_unwinder, True) diff --git a/gdb/testsuite/gdb.python/py-pending-frame-level.exp b/gdb/testsuite/gdb.python/py-pending-frame-level.exp index 0e10b8065d1..102b1c2d740 100644 --- a/gdb/testsuite/gdb.python/py-pending-frame-level.exp +++ b/gdb/testsuite/gdb.python/py-pending-frame-level.exp @@ -51,8 +51,8 @@ gdb_test_no_output "source ${pyfile}"\ # unwinder mixed in. gdb_test_sequence "bt" "Backtrace with extra Python output" { "Func f0, Level 0" - "Func f1, Level 1" "\\r\\n#0 \[^\r\n\]* f0 \\(\\) at " + "Func f1, Level 1" "\\r\\n#1 \[^\r\n\]* f1 \\(\\) at " "Func f2, Level 2" "\\r\\n#2 \[^\r\n\]* f2 \\(\\) at " diff --git a/gdb/testsuite/gdb.python/py-unwind.exp b/gdb/testsuite/gdb.python/py-unwind.exp index af864dceedf..7cdc1e8052c 100644 --- a/gdb/testsuite/gdb.python/py-unwind.exp +++ b/gdb/testsuite/gdb.python/py-unwind.exp @@ -249,7 +249,9 @@ with_test_prefix "bad object unwinder" { gdb_test_no_output "python obj = bad_object_unwinder(\"bad-object\")" gdb_test_no_output "python gdb.unwinder.register_unwinder(None, obj, replace=True)" gdb_test "backtrace" \ - "Python Exception : an Unwinder should return gdb.UnwindInfo, not bad_object_unwinder.+Blah\\.\r\n.*" + [multi_line \ + "#0 corrupt_frame_inner \\(\\) at \[^\r\n\]+" \ + "an Unwinder should return gdb.UnwindInfo, not bad_object_unwinder.+Blah\\."] } # Gather information about every frame.