From mboxrd@z Thu Jan 1 00:00:00 1970 Return-Path: Received: from simark.ca by simark.ca with LMTP id uPKVAH29Y2gYnCcAWB0awg (envelope-from ) for ; Tue, 01 Jul 2025 06:50:37 -0400 Authentication-Results: simark.ca; dkim=pass (2048-bit key; unprotected) header.d=intel.com header.i=@intel.com header.a=rsa-sha256 header.s=Intel header.b=Opbt3Izc; dkim-atps=neutral Received: by simark.ca (Postfix, from userid 112) id F30451E11E; Tue, 1 Jul 2025 06:50:36 -0400 (EDT) X-Spam-Checker-Version: SpamAssassin 4.0.1 (2024-03-25) on simark.ca X-Spam-Level: X-Spam-Status: No, score=-10.1 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, RCVD_IN_VALIDITY_RPBL,RCVD_IN_VALIDITY_SAFE autolearn=ham autolearn_force=no version=4.0.1 Received: from server2.sourceware.org (server2.sourceware.org [8.43.85.97]) (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 010501E102 for ; Tue, 1 Jul 2025 06:50:36 -0400 (EDT) Received: from server2.sourceware.org (localhost [IPv6:::1]) by sourceware.org (Postfix) with ESMTP id A201B3854AA5 for ; Tue, 1 Jul 2025 10:50:35 +0000 (GMT) DKIM-Filter: OpenDKIM Filter v2.11.0 sourceware.org A201B3854AA5 Authentication-Results: sourceware.org; dkim=pass (2048-bit key, unprotected) header.d=intel.com header.i=@intel.com header.a=rsa-sha256 header.s=Intel header.b=Opbt3Izc Received: from mgamail.intel.com (mgamail.intel.com [198.175.65.15]) by sourceware.org (Postfix) with ESMTPS id 0C94C385B507 for ; Tue, 1 Jul 2025 10:48:55 +0000 (GMT) DMARC-Filter: OpenDMARC Filter v1.4.2 sourceware.org 0C94C385B507 Authentication-Results: sourceware.org; dmarc=pass (p=none dis=none) header.from=intel.com Authentication-Results: sourceware.org; spf=pass smtp.mailfrom=intel.com ARC-Filter: OpenARC Filter v1.0.0 sourceware.org 0C94C385B507 Authentication-Results: server2.sourceware.org; arc=none smtp.remote-ip=198.175.65.15 ARC-Seal: i=1; a=rsa-sha256; d=sourceware.org; s=key; t=1751366936; cv=none; b=IqWztnWVJyMFko4vw8SlVD6hJuLkMWXeZCMs6/zUDc0vjyb0AsPndJ0eD9Le35VO5wkBuqSP4JaOAoF0RTklBzR71/EdUSCV4cou92y37ES4kgbf88i1K1xqG3nlXasvqI163LYBXGmCro/bR818bbPXWukINm+sSegbpPi6dqA= ARC-Message-Signature: i=1; a=rsa-sha256; d=sourceware.org; s=key; t=1751366936; c=relaxed/simple; bh=ddWRDISISEkP9B6v1ShpjlMZ+05q1lJRk9HlODqrbmc=; h=DKIM-Signature:From:To:Subject:Date:Message-Id:MIME-Version; b=ibwGozmIsRpnSrDJdI1INf5tB88QGw6afyQJJSeNofLdqTHnJnoPczH7avlkN4hsf+akZff4DOF8E4NkMTc/Do+bgHGp3TZpDVTQtNB72xYB+2cu4bztSAUiab697lDPRCp9WtAyKjqwMWkCyHF9tAp2SXTboPy9MhstmOZVkZw= ARC-Authentication-Results: i=1; server2.sourceware.org DKIM-Filter: OpenDKIM Filter v2.11.0 sourceware.org 0C94C385B507 DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=intel.com; i=@intel.com; q=dns/txt; s=Intel; t=1751366936; x=1782902936; h=from:to:subject:date:message-id:in-reply-to:references: mime-version:content-transfer-encoding; bh=ddWRDISISEkP9B6v1ShpjlMZ+05q1lJRk9HlODqrbmc=; b=Opbt3IzcL4l0XKDKdtL672xmMRraag3QO9ipn3sX8zOE3s5+YStieeSs E+Z8VQoxXPxLBBcRHU+psHjsMKUAoZcLTOeACSQf+HvK86si54e/V71Uk ebLlFAsQMfWhIdFQ+7OzIfAvR48gAt2zSYq2ib2cHr5+Kpe6MHOZdNp3D rGjHmoq766iLN6GM1C31+NyL8I19QT7Ldb7H4rV5P4JiCWG2vPCS/KsoE D/S3r0m7QNfMgsyt7hixbN5/Z3zXPw4vI3psoRdRX0su0lGuFv9XrmsFf A3W0008R38Pu74zBEXGXww5GknoMYacnOfoaH7HNeFDdkPgYpG+ei+1lh Q==; X-CSE-ConnectionGUID: L8g3FRUvQwq4qbokrek32Q== X-CSE-MsgGUID: 7xbYfpVnQcaA6q/3yFpZGA== X-IronPort-AV: E=McAfee;i="6800,10657,11480"; a="57306636" X-IronPort-AV: E=Sophos;i="6.16,279,1744095600"; d="scan'208";a="57306636" Received: from orviesa010.jf.intel.com ([10.64.159.150]) by orvoesa107.jf.intel.com with ESMTP/TLS/ECDHE-RSA-AES256-GCM-SHA384; 01 Jul 2025 03:48:54 -0700 X-CSE-ConnectionGUID: RBO7CwnLTjOj4wn411StMA== X-CSE-MsgGUID: mUMMFhcEQ++z0jYEI0qIbQ== X-ExtLoop1: 1 X-IronPort-AV: E=Sophos;i="6.16,279,1744095600"; d="scan'208";a="153135531" Received: from gkldtt-dev-003.igk.intel.com (HELO localhost) ([10.123.221.128]) by orviesa010-auth.jf.intel.com with ESMTP/TLS/ECDHE-RSA-AES256-GCM-SHA384; 01 Jul 2025 03:48:54 -0700 From: Pawel Kupczak To: gdb-patches@sourceware.org Subject: [PATCH 1/3] gdb, amd64: extend the amd64 prologue analyzer to skip register pushes Date: Tue, 1 Jul 2025 10:47:57 +0000 Message-Id: <20250701104759.52595-2-pawel.kupczak@intel.com> X-Mailer: git-send-email 2.34.1 In-Reply-To: <20250701104759.52595-1-pawel.kupczak@intel.com> References: <20250701104759.52595-1-pawel.kupczak@intel.com> MIME-Version: 1.0 Content-Type: text/plain; charset="us-ascii" 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 A typical function's prologue can consist of setting up a frame pointer, pushing registers onto the stack and allocating space on the stack. Current amd64 prologue analyzer would stop after the frame setup. This patch allows GDB to skip past register pushes, while also improving unwinding pushed registers, for functions with a frame pointer, without debug info and .cfi directives found in .eh_frame section that are used for unwinding. Skipping register pushes was also present for i386 targets before - the proposed changes are based on i386 implementation. It also improves the unwinding even if .cfi directives are present, because GDB can only unwind a register if it has reached a corresponding .cfi directive, which won't be there before the pushes. Additionally, at least gcc 11.4 and later by default doesn't emit necessary debug info, which GDB would try to use to find prologue's end. In that case, extended prologue analyzer would take effect. Using C source listed below as an example, compiled with gcc 11.4.0: ``` int __attribute__ ((noinline)) bar (int a) { return a + a; } int __attribute__ ((noinline)) foo (int a, int b, int c, int d, int e) { int x = bar (a) + bar (b) + bar (c) + bar (d) + bar (e); return x; } int main (int argc, char **argv) { return foo (1, 2, 3, 4, 5); } ``` Compiling with "gcc -O1 -fno-omit-frame-pointer -fno-asynchronous-unwind-tables", we get: ``` (gdb) b foo Breakpoint 1 at 0x1139 (gdb) r ... Breakpoint 1, 0x0000555555555139 in foo () (gdb) disassemble Dump of assembler code for function foo: 0x0000555555555131 <+0>: endbr64 0x0000555555555135 <+4>: push %rbp 0x0000555555555136 <+5>: mov %rsp,%rbp => 0x0000555555555139 <+8>: push %r15 0x000055555555513b <+10>: push %r14 0x000055555555513d <+12>: push %r13 0x000055555555513f <+14>: push %r12 0x0000555555555141 <+16>: push %rbx 0x0000555555555142 <+17>: sub $0x8,%rsp 0x0000555555555146 <+21>: mov %esi,%r15d ... (gdb) ni 0x000055555555513b in foo () (gdb) p $r15 $1 = 140737354125376 (gdb) p $r15=1234 $2 = 1234 (gdb) p $r15 $3 = 1234 (gdb) up #1 0x00005555555551b7 in main () (gdb) p $r15 $4 = 1234 ``` With the proposed changes, breakpoint gets past those register pushes: ``` (gdb) b foo Breakpoint 1 at 0x1142 (gdb) r ... Breakpoint 1, 0x0000555555555142 in foo () (gdb) disassemble Dump of assembler code for function foo: 0x0000555555555131 <+0>: endbr64 0x0000555555555135 <+4>: push %rbp 0x0000555555555136 <+5>: mov %rsp,%rbp 0x0000555555555139 <+8>: push %r15 0x000055555555513b <+10>: push %r14 0x000055555555513d <+12>: push %r13 0x000055555555513f <+14>: push %r12 0x0000555555555141 <+16>: push %rbx => 0x0000555555555142 <+17>: sub $0x8,%rsp 0x0000555555555146 <+21>: mov %esi,%r15d ... ``` Also, unwinding pushed registers now works: ``` ... Breakpoint 1, 0x0000555555555142 in foo () (gdb) disassemble Dump of assembler code for function foo: 0x0000555555555131 <+0>: endbr64 0x0000555555555135 <+4>: push %rbp 0x0000555555555136 <+5>: mov %rsp,%rbp 0x0000555555555139 <+8>: push %r15 0x0000555555555139 <+8>: push %r15 0x000055555555513b <+10>: push %r14 0x000055555555513d <+12>: push %r13 0x000055555555513f <+14>: push %r12 0x0000555555555141 <+16>: push %rbx => 0x0000555555555142 <+17>: sub $0x8,%rsp 0x0000555555555146 <+21>: mov %esi,%r15d ... (gdb) p $r15 $1 = 140737354125376 (gdb) p $r15=1234 $2 = 1234 (gdb) p $r15 $3 = 1234 (gdb) up #1 0x00005555555551b7 in main () (gdb) p $r15 $4 = 140737354125376 ``` Additionally a new test was added to verify this behavior. --- gdb/amd64-tdep.c | 53 +++++++++++- .../amd64-extended-prologue-analysis.c | 49 +++++++++++ .../amd64-extended-prologue-analysis.exp | 86 +++++++++++++++++++ 3 files changed, 187 insertions(+), 1 deletion(-) mode change 100644 => 100755 gdb/amd64-tdep.c create mode 100644 gdb/testsuite/gdb.arch/amd64-extended-prologue-analysis.c create mode 100644 gdb/testsuite/gdb.arch/amd64-extended-prologue-analysis.exp diff --git a/gdb/amd64-tdep.c b/gdb/amd64-tdep.c old mode 100644 new mode 100755 index 82dd1e07cf3..863b29a8c27 --- a/gdb/amd64-tdep.c +++ b/gdb/amd64-tdep.c @@ -2553,6 +2553,56 @@ amd64_analyze_frame_setup (gdbarch *gdbarch, CORE_ADDR pc, return pc; } +/* Check whether PC points at code pushing registers onto the stack. If so, + update CACHE and return pc after those pushes or CURRENT_PC, whichever is + smaller. Otherwise, return PC passed to this function. */ + +static CORE_ADDR +amd64_analyze_register_saves (CORE_ADDR pc, CORE_ADDR current_pc, + amd64_frame_cache *cache) +{ + gdb_byte op; + int offset = 0; + + /* There are at most 16 registers that would be pushed in the prologue. */ + for (int i = 0; i < 16 && pc < current_pc; i++) + { + int reg = 0; + int pc_offset = 0; + + if (target_read_code (pc, &op, 1) == -1) + return pc; + + /* %r8 - %r15 prefix. */ + if (op == 0x41) + { + reg += 8; + pc_offset = 1; + + if (target_read_code (pc + 1, &op, 1) == -1) + return pc; + } + + /* push %rax|%rcx|%rdx|%rbx|%rsp|%rbp|%rsi|%rdi + + or with 0x41 prefix: + push %r8|%r9|%r10|%r11|%r12|%r13|%r14|%r15. */ + if (op < 0x50 || op > 0x57) + break; + + reg += op - 0x50; + offset -= 8; + + int regnum = amd64_arch_reg_to_regnum (reg); + cache->saved_regs[regnum] = offset; + cache->sp_offset += 8; + + pc += 1 + pc_offset; + } + + return pc; +} + /* Do a limited analysis of the prologue at PC and update CACHE accordingly. Bail out early if CURRENT_PC is reached. Return the address where the analysis stopped. @@ -2594,7 +2644,8 @@ amd64_analyze_prologue (gdbarch *gdbarch, CORE_ADDR pc, CORE_ADDR current_pc, if (current_pc <= pc) return current_pc; - return amd64_analyze_frame_setup (gdbarch, pc, current_pc, cache); + pc = amd64_analyze_frame_setup (gdbarch, pc, current_pc, cache); + return amd64_analyze_register_saves (pc, current_pc, cache); } /* Work around false termination of prologue - GCC PR debug/48827. diff --git a/gdb/testsuite/gdb.arch/amd64-extended-prologue-analysis.c b/gdb/testsuite/gdb.arch/amd64-extended-prologue-analysis.c new file mode 100644 index 00000000000..7d777d23236 --- /dev/null +++ b/gdb/testsuite/gdb.arch/amd64-extended-prologue-analysis.c @@ -0,0 +1,49 @@ +/* This testcase is part of GDB, the GNU debugger. + + Copyright 2025 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 __attribute__ ((noinline)) +bar (int x) +{ + return x + x; +} + +/* This function should generate a prologue in shape of: + push %rbp + .cfi_def_cfa_offset 16 + .cfi_offset %rbp, -16 + mov %rsp, %rbp + .cfi_def_cfa_register %rbp + push %reg1 + push %reg2 + .cfi_offset %reg2, 32 + .cfi_offset %reg1, 24 + + So to be able to unwind a register, GDB needs to skip prologue past + register pushes (to access .cfi directives). */ +int __attribute__ ((noinline)) +foo (int a, int b, int c, int d) +{ + a += bar (a) + bar (b) + bar (c); + return a; +} + +int +main (int argc, char **argv) +{ + int a = foo (argc, argc + 1, argc + 2, argc * 2); + return a; +} diff --git a/gdb/testsuite/gdb.arch/amd64-extended-prologue-analysis.exp b/gdb/testsuite/gdb.arch/amd64-extended-prologue-analysis.exp new file mode 100644 index 00000000000..49e03357995 --- /dev/null +++ b/gdb/testsuite/gdb.arch/amd64-extended-prologue-analysis.exp @@ -0,0 +1,86 @@ +# Copyright 2025 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 . +# +# This test verifies that when placing a breakpoint on a function with a frame +# pointer, instructions that push callee-saved registers in the prologue are +# skipped, without debug info. When stopped on such breakpoint, the pushed +# registers should be able to be immediately unwound. With debug info present, +# GDB would try to use prologue-end markers found in the line table to +# determine where the prologue ends. +# +# It is also tested both with and without .eh_frame's .cfi directives - with +# them, GDB can only unwind a register once stopped after .cfi directive for +# that register's push. + +require is_x86_64_m64_target +standard_testfile .c + +# Place a breakpoint at foo, go to it and test if %r12 can be unwound. +proc test_run {} { + gdb_breakpoint "foo" + gdb_continue_to_breakpoint "Continue to foo" + + gdb_test "backtrace" "#0 $::hex in foo\[^s\]+#1 $::hex in main\[^s\]+" \ + "Verify backtrace output in foo" + + set main_r12 [get_integer_valueof "\$r12" "null"] + gdb_assert { $main_r12 ne "null" } + + set foo_r12 [expr "$main_r12 + 2"] + gdb_test "print \$r12=$foo_r12" "$foo_r12" "Set foo's %r12=$foo_r12" + gdb_test "up" ".*main.*" "Go up a frame from foo" + gdb_test "print \$r12" "$main_r12" "foo's %r12 unwound" + gdb_test "down" ".*foo.*" "Go back down a frame to foo" + gdb_test "print \$r12" "$foo_r12" "foo's %r12 unwound back" +} + +with_test_prefix "w/o .cfi directives" { + # -fno-asynchronous-unwind-tables is needed to get rid of .cfi directives in + # .eh_frame section. + if { [gdb_can_simple_compile fno-asynchronous-unwind-tables \ + { void foo () { } } object -fno-asynchronous-unwind-tables] != 0 } { + + # For at least gcc 11.4/clang 14.0.0 and later, -O1 makes them more + # eager to use registers in the prologue. + # + # At least gcc 11.4 and later by default does not generate full debug + # info, "nodebug" is there for other compilers. + if { [prepare_for_testing "failed to prepare" $testfile $srcfile \ + { optimize=-O1 nodebug + additional_flags=-fno-asynchronous-unwind-tables + additional_flags=-fno-omit-frame-pointer}] } { + return -1 + } + + if { ![runto_main] } { + return -1 + } + + test_run + } +} + +with_test_prefix "with .cfi directives" { + if { [prepare_for_testing "failed to prepare" $testfile $srcfile \ + { optimize=-O1 nodebug additional_flags=-fno-omit-frame-pointer}] } { + return -1 + } + + if { ![runto_main] } { + return -1 + } + + test_run +} -- 2.34.1 --------------------------------------------------------------------- Intel Technology Poland sp. z o.o. ul. Slowackiego 173 | 80-298 Gdansk | Sad Rejonowy Gdansk Polnoc | VII Wydzial Gospodarczy Krajowego Rejestru Sadowego - KRS 101882 | NIP 957-07-52-316 | Kapital zakladowy 200.000 PLN. Spolka oswiadcza, ze posiada status duzego przedsiebiorcy w rozumieniu ustawy z dnia 8 marca 2013 r. o przeciwdzialaniu nadmiernym opoznieniom w transakcjach handlowych. Ta wiadomosc wraz z zalacznikami jest przeznaczona dla okreslonego adresata i moze zawierac informacje poufne. W razie przypadkowego otrzymania tej wiadomosci, prosimy o powiadomienie nadawcy oraz trwale jej usuniecie; jakiekolwiek przegladanie lub rozpowszechnianie jest zabronione. This e-mail and any attachments may contain confidential material for the sole use of the intended recipient(s). If you are not the intended recipient, please contact the sender and delete all copies; any review or distribution by others is strictly prohibited.