From mboxrd@z Thu Jan 1 00:00:00 1970 Return-Path: Received: from simark.ca by simark.ca with LMTP id QT7OAwDOdGol5Q0AWB0awg (envelope-from ) for ; Thu, 06 Aug 2026 14:10:08 -0400 Authentication-Results: simark.ca; dkim=pass (2048-bit key; unprotected) header.d=gmail.com header.i=@gmail.com header.a=rsa-sha256 header.s=20251104 header.b=tL/sEU7c; dkim-atps=neutral Received: by simark.ca (Postfix, from userid 112) id 0BAF31E166; Thu, 06 Aug 2026 14:10:08 -0400 (EDT) X-Spam-Checker-Version: SpamAssassin 4.0.1 (2024-03-25) on simark.ca X-Spam-Level: X-Spam-Status: No, score=-4.4 required=5.0 tests=ARC_SIGNED,ARC_VALID,BAYES_00, DKIM_SIGNED,DKIM_VALID,DKIM_VALID_AU,FORGED_GMAIL_RCVD,FREEMAIL_FROM, MAILING_LIST_MULTI,RCVD_IN_DNSWL_MED,RCVD_IN_MSPIKE_H2 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 4B0C81E09B for ; Thu, 06 Aug 2026 14:10:06 -0400 (EDT) Received: from vm01.sourceware.org (localhost [IPv6:::1]) by sourceware.org (Postfix) with ESMTP id 18E4A4BA23C7 for ; Thu, 6 Aug 2026 18:10:05 +0000 (GMT) DKIM-Filter: OpenDKIM Filter v2.11.0 sourceware.org 18E4A4BA23C7 Authentication-Results: sourceware.org; dkim=pass (2048-bit key, unprotected) header.d=gmail.com header.i=@gmail.com header.a=rsa-sha256 header.s=20251104 header.b=tL/sEU7c Received: from mail-wr1-x436.google.com (mail-wr1-x436.google.com [IPv6:2a00:1450:4864:20::436]) by sourceware.org (Postfix) with ESMTPS id 52F194BB3BD2 for ; Thu, 6 Aug 2026 18:09:30 +0000 (GMT) DMARC-Filter: OpenDMARC Filter v1.4.2 sourceware.org 52F194BB3BD2 Authentication-Results: sourceware.org; dmarc=pass (p=none dis=none) header.from=gmail.com Authentication-Results: sourceware.org; spf=pass smtp.mailfrom=gmail.com ARC-Filter: OpenARC Filter v1.0.0 sourceware.org 52F194BB3BD2 Authentication-Results: sourceware.org; arc=none smtp.remote-ip=2a00:1450:4864:20::436 ARC-Seal: i=1; a=rsa-sha256; d=sourceware.org; s=key; t=1786039770; cv=none; b=OYN6zKyX2T3hyWatiapQaKhpxL+q6w8jFA2IXBEpc0Qp6m/NDlHfaMG1pR/a8yFzOu4IwJKMhBLbMsaEPhWeuyjFagrojHjQpyBovZHTxziEcjgJifrI2DixEw8v/Pnbi4Cuf1U8Y011bDi3RuQCiKJlnZ5YdSd+RJGK9z0tFUU= ARC-Message-Signature: i=1; a=rsa-sha256; d=sourceware.org; s=key; t=1786039770; c=relaxed/simple; bh=oNe5Ktmlq671/2G/sDsiOHCAzLxqn/i13K+4zYtNFgA=; h=DKIM-Signature:From:To:Subject:Date:Message-ID:MIME-Version; b=u8/44GQu3TOVWNOBnJXF/A5EmUBxHLghlNVmwS6kN9jIhz3+o9jj0WuLTYFSpVYb9M6mcYmVVjuSnaOs0OtWyeQ6zMwW2r/ugUNiP3LgbtVdzs+rUCUBFy9OhTIOHQzEb82y3nBU1JruUBfdcV0SCbMIETnf8sNypzK1xR9r6mQ= ARC-Authentication-Results: i=1; sourceware.org; dkim=pass (2048-bit key, unprotected) header.d=gmail.com header.i=@gmail.com header.a=rsa-sha256 header.s=20251104 header.b=tL/sEU7c DKIM-Filter: OpenDKIM Filter v2.11.0 sourceware.org 52F194BB3BD2 Received: by mail-wr1-x436.google.com with SMTP id ffacd0b85a97d-47fd66a094eso1030730f8f.3 for ; Thu, 06 Aug 2026 11:09:30 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20251104; t=1786039769; x=1786644569; darn=sourceware.org; h=content-transfer-encoding:mime-version:message-id:date:subject:cc :to:from:from:to:cc:subject:date:message-id:reply-to:content-type; bh=I+xhMbDdFyf1ZXgnAI2iHBCfqrmceGUefnEJ6rQ9/7I=; b=tL/sEU7cWlTiVrICI+AGwOgwTjSR+ESsooogDflVsMq6BSxCPNOkZ/KHKq0DTkRGSN iNBcer3Wkc7RQzziaVB5pOCasj+RdHJXsRaO0F+ATf2RWPgB64ltpoL7QrMpz4wA54F2 TUOHz00Enl8Xxzs5FF5XIEG1f8Nc/+KjdlgPv8RAsNO4t61cqsvvicqftidFzxyKiW4W eU9RqCQjDG/JL2XHtPwSOo43U4OyizEYshrTQBDJYocCBKNT+dNfmQi+TGc3EzyfTj4e sPJMA3Y+XxcF4kJw4RAi0A+rmBY+yNsOrTLXXei4qf+M76y74lqfrji60pYsZ148NfOu NCfA== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20251104; t=1786039769; x=1786644569; h=content-transfer-encoding:mime-version: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=I+xhMbDdFyf1ZXgnAI2iHBCfqrmceGUefnEJ6rQ9/7I=; b=goXmFDDF6ePS4op+Lwxo0Dx0brof1OdNL+xuSIZc/LZEGjsH0pmQoDdsUmm6wxJCgd FN2oJmojR4zey3GD8Qc7lSc69QZKrs/4gdAktGx7IOHu+hAQqhitI5I91ECbD2ipY0Mz +iCPb3axvDQMVAt3WXJiaZgJF8RNyhb4X4B6W3YNP0fmbFtiQi84vasbQapgCWPMtuGM RLzpECUnug1IODK7b8CCltSXKiZui+Id4Xs/XyJgdnhneCXZ/VsBi/8i0gLnbYM5RoaX X4FGvkzMTD85KChx1Zdl/rE3Yx5G72yoT5Gi9t8ycZ3UiNCXeVpR5G0KlTIbeOA9wCmU CFJg== X-Gm-Message-State: AOJu0YzxQRVgvPVhIoR3GqS2/S50PbE1Zj6triaaiLPBKKQLU8JlTKl/ vmgBQden/ObdflpRhN89TCYqxqjZOLlIyY9KGzcBZ7AbnMI53G5M4zv/zptEVA== X-Gm-Gg: AR+sD11i8Re5FNhdLmErnn87/hxj31FGpJJjnF/oLm8+MiuH/NCgge1reP/5df4iqTX MFwoHLLL57Y80CJRh1vQvUOHD82pPPFRgjRpaDDsXazAasCxZtgiFnga2jUttDDFaidFD5UWJ4T zgo3Je+oxSEYWbnQkoD6ZnlCLHf43pqCzStcxZ+18fURXVwGaHEes1tpHJx0cN6MxPxzgfdf8Mz NBwov3ybbPGN2ZyGMqd4eYgGGfRpGUsRxPhQK9dNTtiU/tsCSatSfq+AYAXKkXqeAzb7iWZgfzl RtNNy0HvwkHkjQ8nYeKOtCOQyeCrXdh7Ws1qubdlvgI/ra4D3DgEgjjlB/rCFrbZ/eK3YHi5Zn7 5g7dhUB1NCkQKw8I+MbwHyfqJ8sxjDwMOJduYfgoljECsSNYMIM2vYpv9XXhWajNDgutWo8yHwR A2sr8BP+l62Gw+29PWG0zo5ChOs5jDU4/jhmJ/x+dsJ4iiIA9tpUfegPD4vWWusSK5uj+YZt9ZF H5cANLPLM3/5VUvVBotXmbQn8Odf7TX2eyh9A6GSZEgEsXKzkzu+38= X-Received: by 2002:a5d:5e92:0:b0:47f:f87e:6485 with SMTP id ffacd0b85a97d-47ff87e6823mr10776489f8f.15.1786039768866; Thu, 06 Aug 2026 11:09:28 -0700 (PDT) Received: from il-orgads-lp.corp.audiocodes.com ([134.231.187.64]) by smtp.gmail.com with ESMTPSA id ffacd0b85a97d-47ff7b32492sm8003630f8f.35.2026.08.06.11.09.27 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Thu, 06 Aug 2026 11:09:27 -0700 (PDT) From: Orgad Shaneh To: gdb-patches@sourceware.org Cc: "Maciej W . Rozycki" , Orgad Shaneh Subject: [PATCH] gdb/mips: unwind past post-prologue SP adjustments in syscall stubs Date: Thu, 6 Aug 2026 21:09:12 +0300 Message-ID: <20260806180912.7143-1-orgads@gmail.com> X-Mailer: git-send-email 2.53.0 MIME-Version: 1.0 Content-Transfer-Encoding: 8bit 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 MIPS o32, syscalls taking five or more arguments pass the extra arguments on the stack, so libc syscall stubs temporarily lower SP around the actual syscall instruction, after the function prologue. uClibc's __syscall_ipc is representative (musl and uClibc's syscall.S have the same shape), and none of them carry CFI: addiu sp,sp,-8 # prologue sw s0,4(sp) lw v0,24(sp) lw s0,28(sp) addiu sp,sp,-32 # stack arguments for the syscall ... syscall # <- a blocked thread's PC is here addiu sp,sp,32 mips32_scan_prologue permits at most one non-prologue instruction, so for a thread blocked in such a syscall the scan stops at the two lw's and never sees the second SP adjustment. The computed frame base is 32 bytes too low, the caller's saved-ra slot is read from within the outgoing argument area, and the backtrace degenerates into a garbage frame right after the libc stub. On core dumps of multi-threaded programs from o32 uClibc/musl systems this loses the call chain of every thread blocked in msgrcv, ppoll, futex via syscall(), etc. When computing a frame (not when skipping the prologue), continue scanning from where the prologue-end heuristic stopped up to the PC, accumulating any further SP adjustments. Branches are deliberately not followed: compilers do not move SP mid-function outside prologue and epilogue (alloca frames use a frame pointer and are excluded), so post-prologue adjustments occur in practice only around syscall instructions in libc stubs, where paired temporary adjustments that were fully crossed cancel out in a linear scan. The scan does stop at a "jr $ra", though: past one, the range crosses a complete alternate return path - e.g. the single-thread fast path of glibc's cancellable syscall wrappers, which returns before the multithreaded path saves RA and runs the syscall - and a linear sum of the remainder is no longer meaningful, so the frame is left unchanged. If the net result is positive, rescan with the entry SP moved accordingly - the same restart mechanism already used for alloca - so that both the frame base and the register save slots are computed against the true frame. The post-prologue scan gets a wider address bound than the prologue scan's 200 bytes: the syscall window can be a few hundred bytes into the function (ppoll in uClibc has it at +216). Tested on a synthetic testcase (added) under qemu-mips, and on a real core dump from a MIPS32r2 uClibc 0.9.33.2 system where garbage frames across all 58 threads went from 32 to 0. Signed-off-by: Orgad Shaneh --- gdb/mips-tdep.c | 75 ++++++++++++++++++- .../gdb.arch/mips-syscall-unwind-stub.S | 37 +++++++++ gdb/testsuite/gdb.arch/mips-syscall-unwind.c | 36 +++++++++ .../gdb.arch/mips-syscall-unwind.exp | 43 +++++++++++ 4 files changed, 190 insertions(+), 1 deletion(-) create mode 100644 gdb/testsuite/gdb.arch/mips-syscall-unwind-stub.S create mode 100644 gdb/testsuite/gdb.arch/mips-syscall-unwind.c create mode 100644 gdb/testsuite/gdb.arch/mips-syscall-unwind.exp diff --git a/gdb/mips-tdep.c b/gdb/mips-tdep.c index fe0482fe5bf..30c52abe321 100644 --- a/gdb/mips-tdep.c +++ b/gdb/mips-tdep.c @@ -3446,6 +3446,7 @@ mips32_scan_prologue (struct gdbarch *gdbarch, CORE_ADDR end_prologue_addr; int seen_sp_adjust = 0; int load_immediate_bytes = 0; + long sp_adjust_extra = 0; int in_delay_slot; int regsize_is_64_bits = (mips_abi_regsize (gdbarch) == 8); @@ -3458,6 +3459,15 @@ mips32_scan_prologue (struct gdbarch *gdbarch, else sp = 0; + /* The address up to which the post-prologue scan below may look for + further SP adjustments. Bounded, but much less tightly than the + prologue scan: libc syscall stubs adjust SP shortly before the + syscall instruction, which can be a few hundred bytes into the + function (e.g. ppoll in uClibc). */ + CORE_ADDR post_prologue_limit_pc = limit_pc; + if (post_prologue_limit_pc > start_pc + 4096) + post_prologue_limit_pc = start_pc + 4096; + if (limit_pc > start_pc + 200) limit_pc = start_pc + 200; @@ -3636,12 +3646,75 @@ mips32_scan_prologue (struct gdbarch *gdbarch, prev_pc = cur_pc; } + /* If the prologue-end heuristic above stopped the scan before the PC + while computing a frame, the function may adjust SP again further on, + with no CFI to describe it. This is how o32 libc syscall stubs build + the stack argument area around the actual syscall instruction: + + addiu sp,sp,-8 # prologue + ... + addiu sp,sp,-32 # stack arguments for the syscall + syscall # <- a blocked thread's PC is here + addiu sp,sp,32 + + Continue scanning up to the PC, accumulating any further SP + adjustments. Control flow is deliberately ignored: compilers do not + move SP mid-function outside prologue and epilogue (alloca frames use + a frame pointer and are excluded below), so post-prologue adjustments + occur in practice only around syscall instructions in libc stubs, + where paired temporary adjustments that were fully crossed cancel out + in a linear scan. A net non-positive result leaves the frame + unchanged. If SP was lowered further, rescan with the entry SP moved + accordingly, so that the frame base and the register save slots + recorded above are computed against the true frame. */ + if (this_cache != NULL && frame_reg == MIPS_SP_REGNUM + && sp_adjust_extra == 0) + { + long extra = 0; + CORE_ADDR extra_pc; + + for (extra_pc = cur_pc; extra_pc < post_prologue_limit_pc; + extra_pc += MIPS_INSN32_SIZE) + { + unsigned long inst, high_word; + long offset; + + inst = (unsigned long) mips_fetch_instruction (gdbarch, ISA_MIPS, + extra_pc, NULL); + + /* A "jr $ra" before the PC means the scanned range crosses a + complete alternate return path, e.g. the single-thread fast + path of glibc's cancellable syscall wrappers. A linear sum + is no longer meaningful there, so stop and leave the frame + unchanged. */ + if (inst == 0x03e00008) /* jr $ra */ + break; + + high_word = (inst >> 16) & 0xffff; + offset = ((inst & 0xffff) ^ 0x8000) - 0x8000; + + if (high_word == 0x27bd /* addiu $sp,$sp,i */ + || high_word == 0x23bd /* addi $sp,$sp,i */ + || high_word == 0x67bd) /* daddiu $sp,$sp,i */ + extra -= offset; + } + + if (extra > 0) + { + sp_adjust_extra = extra; + sp += extra; + reset_saved_regs (gdbarch, this_cache); + goto restart; + } + } + if (this_cache != NULL) { this_cache->base = (get_frame_register_signed (this_frame, gdbarch_num_regs (gdbarch) + frame_reg) - + frame_offset); + + frame_offset + + (frame_reg == MIPS_SP_REGNUM ? sp_adjust_extra : 0)); /* FIXME: brobecker/2004-09-15: We should be able to get rid of this assignment below, eventually. But it's still needed for now. */ diff --git a/gdb/testsuite/gdb.arch/mips-syscall-unwind-stub.S b/gdb/testsuite/gdb.arch/mips-syscall-unwind-stub.S new file mode 100644 index 00000000000..e8cd3f3c464 --- /dev/null +++ b/gdb/testsuite/gdb.arch/mips-syscall-unwind-stub.S @@ -0,0 +1,37 @@ +/* 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 . */ + +/* The shape of an o32 libc syscall stub with stack arguments: SP is + temporarily lowered again after the prologue, around the syscall + instruction, with no CFI describing it. A thread blocked in the + syscall has its PC inside that window. */ + + .text + .globl stub + .type stub, @function + .set noreorder +stub: + addiu $sp, $sp, -16 + lw $v0, 16($sp) /* Two non-prologue instructions end the */ + lw $v0, 16($sp) /* prologue scan. */ + addiu $sp, $sp, -32 /* Stack argument area for the syscall. */ + nop /* blocked */ + addiu $sp, $sp, 32 + li $v0, 1 + jr $ra + addiu $sp, $sp, 16 + .size stub, .-stub diff --git a/gdb/testsuite/gdb.arch/mips-syscall-unwind.c b/gdb/testsuite/gdb.arch/mips-syscall-unwind.c new file mode 100644 index 00000000000..1f71c19b444 --- /dev/null +++ b/gdb/testsuite/gdb.arch/mips-syscall-unwind.c @@ -0,0 +1,36 @@ +/* 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 . */ + +extern long stub (void); + +long __attribute__ ((noinline)) +f2 (void) +{ + return stub () + 1; +} + +long __attribute__ ((noinline)) +f1 (void) +{ + return f2 () + 1; +} + +int +main (void) +{ + return f1 () != 2; +} diff --git a/gdb/testsuite/gdb.arch/mips-syscall-unwind.exp b/gdb/testsuite/gdb.arch/mips-syscall-unwind.exp new file mode 100644 index 00000000000..ced21933aae --- /dev/null +++ b/gdb/testsuite/gdb.arch/mips-syscall-unwind.exp @@ -0,0 +1,43 @@ +# 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 . + +# Test unwinding past a CFI-less function that adjusts SP again after +# its prologue, the way o32 libc syscall stubs build the stack argument +# area around a syscall instruction. The prologue scanner used to miss +# the second adjustment, computing a frame base 32 bytes too low and +# breaking the backtrace right past such a function. + +require {istarget "mips*-*-*"} is_ilp32_target + +standard_testfile .c mips-syscall-unwind-stub.S + +if { [prepare_for_testing "failed to prepare" ${testfile} \ + [list $srcfile $srcfile2]] } { + return +} + +if { ![runto_main] } { + return +} + +gdb_breakpoint [gdb_get_line_number "blocked" $srcfile2] +gdb_continue_to_breakpoint "syscall window" ".*blocked.*" + +gdb_test "backtrace" \ + [multi_line "#0\[ \t\]+stub \\(\\).*" \ + "#1\[ \t\]+$hex in f2 \\(\\).*" \ + "#2\[ \t\]+$hex in f1 \\(\\).*" \ + "#3\[ \t\]+$hex in main \\(\\).*"] \ + "backtrace through post-prologue SP adjustment" -- 2.53.0