From: Andrew Burgess <aburgess@redhat.com>
To: gdb-patches@sourceware.org
Cc: Andrew Burgess <aburgess@redhat.com>
Subject: [PATCH 3/4] gdb/python: don't allow FinishBreakpoints for inline frames
Date: Sat, 24 Jan 2026 11:29:13 +0000 [thread overview]
Message-ID: <37a173304a79718a39b3ca5d4e3fa51dab6fa835.1769253423.git.aburgess@redhat.com> (raw)
In-Reply-To: <cover.1769253423.git.aburgess@redhat.com>
Creating a Python gdb.FinishBreakpoint for an inline frame doesn't
work.
If we look at the 'finish' command, in the finish_command
function (infcmd.c) then we see that GDB handles inline frames very
different to non-inline frames.
For non-inline frames GDB creates a temporary breakpoint and then
resumes the inferior until the breakpoint is hit.
But for inline frames, GDB steps forward until we have left the inline
frame.
When it comes to gdb.FinishBreakpoint we only have the "create a
temporary breakpoint" mechanism; that is, after all, what the
FinishBreakpoint is, it's a temporary breakpoint placed at the
return address in the caller.
Currently, when a FinishBreakpoint is created within an inline frame,
GDB ends up creating the breakpoint at the current $pc. As a result
the breakpoint will not be hit before the current function
exits (unless there's a loop going on, but that's not the point).
We could imagine what a solution to this problem would look like, GDB
would need to figure out the set of addresses for all possible exit
points from the inline function, and place a breakpoint at each of
these locations. I don't propose doing that in this commit.
Instead, I plan to update the docs to note that creating a
FinishBreakpoint within an inline frame is not allowed, and I will
catch this case within bpfinishpy_init (python/py-finishbreakpoint.c)
and throw an error.
Though the error is new, all I'm doing is raising an error for a case
that never worked.
There's a new test to cover this case.
---
gdb/doc/python.texi | 3 +
gdb/python/py-finishbreakpoint.c | 8 +-
.../gdb.python/py-finish-breakpoint-inline.c | 58 +++++++
.../py-finish-breakpoint-inline.exp | 147 ++++++++++++++++++
.../gdb.python/py-finish-breakpoint-inline.py | 27 ++++
5 files changed, 242 insertions(+), 1 deletion(-)
create mode 100644 gdb/testsuite/gdb.python/py-finish-breakpoint-inline.c
create mode 100644 gdb/testsuite/gdb.python/py-finish-breakpoint-inline.exp
create mode 100644 gdb/testsuite/gdb.python/py-finish-breakpoint-inline.py
diff --git a/gdb/doc/python.texi b/gdb/doc/python.texi
index 1f88ea7e9ad..eccb7e523c7 100644
--- a/gdb/doc/python.texi
+++ b/gdb/doc/python.texi
@@ -7221,6 +7221,9 @@ Finish Breakpoints in Python
Finish breakpoints are thread specific and must be create with the right
thread selected.
+It is not possible to create a @code{gdb.FinishBreakpoint} for an
+inline frame (@pxref{Inline Functions}).
+
@defun FinishBreakpoint.__init__ (@r{[}frame@r{]} @r{[}, internal@r{]})
Create a finish breakpoint at the return address of the @code{gdb.Frame}
object @var{frame}. If @var{frame} is not provided, this defaults to the
diff --git a/gdb/python/py-finishbreakpoint.c b/gdb/python/py-finishbreakpoint.c
index 84d2abeb433..84d55c470a2 100644
--- a/gdb/python/py-finishbreakpoint.c
+++ b/gdb/python/py-finishbreakpoint.c
@@ -192,10 +192,16 @@ bpfinishpy_init (PyObject *self, PyObject *args, PyObject *kwargs)
PyErr_SetString (PyExc_ValueError,
_("Invalid ID for the `frame' object."));
}
+ else if (get_frame_type (frame) == INLINE_FRAME)
+ {
+ PyErr_SetString
+ (PyExc_ValueError,
+ _("Unable to create FinishBreakpoint for inline frame."));
+ }
else
{
prev_frame = get_prev_frame (frame);
- if (prev_frame == 0)
+ if (prev_frame == nullptr)
{
PyErr_SetString (PyExc_ValueError,
_("\"FinishBreakpoint\" not "
diff --git a/gdb/testsuite/gdb.python/py-finish-breakpoint-inline.c b/gdb/testsuite/gdb.python/py-finish-breakpoint-inline.c
new file mode 100644
index 00000000000..c955917a873
--- /dev/null
+++ b/gdb/testsuite/gdb.python/py-finish-breakpoint-inline.c
@@ -0,0 +1,58 @@
+/* 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 <http://www.gnu.org/licenses/>. */
+
+/* Global used to create filler code within functions. */
+volatile int global_var = 1;
+
+static int __attribute__ ((noinline, noclone))
+baz (int arg)
+{
+ arg += global_var;
+ return arg;
+}
+
+static inline int __attribute__ ((__always_inline__))
+bar (int arg)
+{
+ arg += global_var;
+ arg = baz (arg); /* Finish location. */
+ arg -= global_var;
+ return arg;
+}
+
+static inline int __attribute__ ((__always_inline__))
+foo (int arg)
+{
+ arg += global_var;
+ arg = bar (arg);
+ arg -= global_var;
+ return arg;
+}
+
+int
+main (void)
+{
+ int ans;
+
+ ++global_var;
+ ++global_var;
+ ans = foo (42);
+ ++global_var;
+ ++global_var;
+ ans += global_var;
+ return ans; /* Final breakpoint. */
+}
diff --git a/gdb/testsuite/gdb.python/py-finish-breakpoint-inline.exp b/gdb/testsuite/gdb.python/py-finish-breakpoint-inline.exp
new file mode 100644
index 00000000000..d1c3f3de8f0
--- /dev/null
+++ b/gdb/testsuite/gdb.python/py-finish-breakpoint-inline.exp
@@ -0,0 +1,147 @@
+# 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 <http://www.gnu.org/licenses/>.
+
+# Check that attempting to create a FinishBreakpoint within an inline
+# function will fail.
+#
+# For inline functions the 'finish' command steps forward until we are
+# outside the inline function.
+#
+# For FinishBreakpoints though we need to pick an address an place a
+# breakpoint there. Currently GDB doesn't know where to place such a
+# breakpoint for an inline function, so our solution is to prevent
+# creation of FinishBreakpoints for inline frames.
+
+load_lib gdb-python.exp
+
+require allow_python_tests
+
+standard_testfile
+
+if {[prepare_for_testing "failed to prepare" $testfile $srcfile]} {
+ return
+}
+
+if {![runto_main]} {
+ return
+}
+
+# Source the Python script.
+set pyfile [gdb_remote_download host ${srcdir}/${subdir}/${testfile}.py]
+gdb_test "source $pyfile" "Python script imported" "import python scripts"
+
+# Breakpoint locations needed for this test.
+gdb_breakpoint foo
+gdb_breakpoint bar
+gdb_breakpoint baz
+set final_lineno [gdb_get_line_number "Final breakpoint."]
+gdb_breakpoint $final_lineno
+
+# Depending on how the code is compiled, and exactly where the finish
+# breakpoint is placed, the breakpoint could potentially be reported
+# on either of these lines.
+set lineno_1 [gdb_get_line_number "Finish location."]
+set lineno_2 [expr {$lineno_1 + 1}]
+set lineno_re "(?:$lineno_1|$lineno_2)"
+
+# Run to 'foo', which is an inline function called from a normal
+# function, and try to create a MyFinishBreakpoint. This should fail.
+gdb_continue_to_breakpoint "breakpoint in foo"
+gdb_test "python MyFinishBreakpoint(gdb.selected_frame())" \
+ "Error occurred in Python: Unable to create FinishBreakpoint for inline frame\\." \
+ "try to create FinishBreakpoint for inline frame, caller is a normal frame"
+
+# Continue to 'bar', which is an inline function called from another
+# inline function, and try to create a MyFinishBreakpoint. This
+# should fail.
+gdb_continue_to_breakpoint "breakpoint in bar"
+gdb_test "python MyFinishBreakpoint(gdb.selected_frame())" \
+ "Error occurred in Python: Unable to create FinishBreakpoint for inline frame\\." \
+ "try to create FinishBreakpoint for inline frame, caller is an inline frame"
+
+# Continue to 'baz', which is a normal function called from an inline
+# function, and create a MyFinishBreakpoint, which we expect to succeed.
+gdb_continue_to_breakpoint "breakpoint in baz"
+gdb_test "python MyFinishBreakpoint(gdb.selected_frame())" \
+ "Temporary breakpoint $decimal at $hex: file \[^\r\n\]+/$srcfile, line $lineno_re\\." \
+ "create FinishBreakpoint normal function, caller is an inline frame"
+
+# Continue and make sure we hit the MyFinishBreakpoint.
+set saw_finish_breakpoint false
+set saw_return_value false
+set saw_breakpoint_location false
+set saw_source_line false
+gdb_test_multiple "continue" "continue to finish breakpoint" {
+ -re "^Stopped at MyFinishBreakpoint\r\n" {
+ set saw_finish_breakpoint true
+ exp_continue
+ }
+ -re "^Return value is 51\r\n" {
+ set saw_return_value true
+ exp_continue
+ }
+ -re "^Breakpoint $decimal, ($hex in )?bar \\(arg=$decimal\\) at \[^\r\n\]+/$srcfile:$lineno_re\r\n" {
+ set saw_breakpoint_location true
+ exp_continue
+ }
+ -re "^$lineno_re\\s+\[^\r\n\]+\r\n" {
+ set saw_source_line true
+ exp_continue
+ }
+ -re "^$gdb_prompt $" {
+ gdb_assert {$saw_finish_breakpoint \
+ && $saw_return_value \
+ && $saw_breakpoint_location \
+ && $saw_source_line } $gdb_test_name
+ }
+ -re "^\[^\r\n\]*\r\n" {
+ exp_continue
+ }
+}
+
+# Continue to the final breakpoint location. We don't expect to see
+# any of the MyFinishBreakpoint output here. If we do then we've hit
+# an unexpected FinishBreakpoint.
+set saw_finish_breakpoint false
+set saw_return_value false
+set saw_breakpoint_location false
+set saw_source_line false
+gdb_test_multiple "continue" "continue to final breakpoint" {
+ -re "^Stopped at MyFinishBreakpoint\r\n" {
+ set saw_finish_breakpoint true
+ exp_continue
+ }
+ -re "^Return value is 51\r\n" {
+ set saw_return_value true
+ exp_continue
+ }
+ -re "^Breakpoint $decimal, main \\(\\) at \[^\r\n\]+/$srcfile:$final_lineno\r\n" {
+ set saw_breakpoint_location true
+ exp_continue
+ }
+ -re "^$final_lineno\\s+\[^\r\n\]+\r\n" {
+ set saw_source_line true
+ exp_continue
+ }
+ -re "^$gdb_prompt $" {
+ gdb_assert {!$saw_finish_breakpoint \
+ && !$saw_return_value \
+ && $saw_breakpoint_location \
+ && $saw_source_line } $gdb_test_name
+ }
+ -re "^\[^\r\n\]*\r\n" {
+ exp_continue
+ }
+}
diff --git a/gdb/testsuite/gdb.python/py-finish-breakpoint-inline.py b/gdb/testsuite/gdb.python/py-finish-breakpoint-inline.py
new file mode 100644
index 00000000000..e493dfa4017
--- /dev/null
+++ b/gdb/testsuite/gdb.python/py-finish-breakpoint-inline.py
@@ -0,0 +1,27 @@
+# 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 <http://www.gnu.org/licenses/>.
+
+
+import gdb
+
+
+class MyFinishBreakpoint(gdb.FinishBreakpoint):
+ def stop(self):
+ print("Stopped at MyFinishBreakpoint")
+ print("Return value is {}".format(self.return_value))
+ return True
+
+
+print("Python script imported")
--
2.25.4
next prev parent reply other threads:[~2026-01-24 11:31 UTC|newest]
Thread overview: 14+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-01-24 11:29 [PATCH 0/4] Fixes for tail call, until, and FinishBreakpoints Andrew Burgess
2026-01-24 11:29 ` [PATCH 1/4] gdb: fix frame_unwind_caller_WHAT functions for inline and tail calls Andrew Burgess
2026-01-27 16:37 ` Andrew Burgess
2026-01-24 11:29 ` [PATCH 2/4] gdb/python: fix FinishBreakpoint.return_value for tail call functions Andrew Burgess
2026-01-24 11:29 ` Andrew Burgess [this message]
2026-01-24 12:23 ` [PATCH 3/4] gdb/python: don't allow FinishBreakpoints for inline frames Eli Zaretskii
2026-01-24 11:29 ` [PATCH 4/4] gdb/python: fix gdb.FinishBreakpoint returning to a tail call frame Andrew Burgess
2026-03-05 13:37 ` [PATCHv2 0/4] Fixes for tail call, until, and FinishBreakpoints Andrew Burgess
2026-03-05 13:37 ` [PATCHv2 1/4] gdb: fix frame_unwind_caller_WHAT functions for inline and tail calls Andrew Burgess
2026-03-05 13:37 ` [PATCHv2 2/4] gdb/python: fix FinishBreakpoint.return_value for tail call functions Andrew Burgess
2026-03-05 13:37 ` [PATCHv2 3/4] gdb/python: don't allow FinishBreakpoints for inline frames Andrew Burgess
2026-03-05 13:37 ` [PATCHv2 4/4] gdb/python: fix gdb.FinishBreakpoint returning to a tail call frame Andrew Burgess
2026-03-05 15:59 ` [PATCHv2 0/4] Fixes for tail call, until, and FinishBreakpoints Tom Tromey
2026-03-05 17:49 ` Andrew Burgess
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=37a173304a79718a39b3ca5d4e3fa51dab6fa835.1769253423.git.aburgess@redhat.com \
--to=aburgess@redhat.com \
--cc=gdb-patches@sourceware.org \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox