Hi Alexandra,
I took a look through this and have a small number of very minor nits
left to clean up. Thanks for continuing to work on this project.
Alexandra Hájková <ahajkova@redhat.com> writes:
> When we rerun the executable after changing its source files,
> GDB would re-set all previously set breakpoints. The
> breakpoints set to the function names would remain at their initial
> locations. But the breakpoints which used filename:line notation would
> be silently shifted following the source code changes.
>
> To address this, GDB now optionally captures a small window of source
> code lines around each breakpoint set with filename:line notation,
> when it is first set. When the binary is reloaded, GDB detects the BFD
> change and tries to locate the same source context in the new file,
> and if successful, re-sets the breakpoint to the matched source code
> line.
>
> The breakpoint_source structure stores captured source code lines
> around a breakpoint location, along with a reference to the BFD
> that was current when the source was captured.
>
> When source tracking is enabled (via 'set breakpoint source-tracking
> enabled on'), GDB captures 3 lines of source context
> (BREAKPOINT_SRC_CTX_LINES) along with the current BFD when a
> breakpoint is first set. On executable reload (detected by comparing
> BFDs), it searches within a 12-line window
> (BREAKPOINT_SRC_CTX_LINES * BREAKPOINT_SRC_SEARCH_MULTIPLIER) for
> the best match and adjusts the breakpoint location if needed.
>
> If source tracking is disabled after breakpoints have been tracked,
> all existing source tracking information is discarded and a message
> is printed.
>
> Tests added:
> gdb.base/adjust_breakpoint.exp
> gdb.base/adjust_breakpoint-missing-source.exp
> gdb.base/source-tracking-inline.exp
> gdb.base/test_source_tracking.exp
>
> adjust_breakpoint.exp covers four scenarios:
> - adjust the breakpoint when lines are deleted
> - adjust the breakpoint when lines are inserted
> - the tracked line disappears entirely
> - verify the tracking can be disabled
>
> adjust_breakpoint-missing-source.exp covers the edge case where source
> files are unavailable, verifying GDB falls back to non-tracking breakpoints.
>
> source-tracking-inline.exp covers source tracking with inline functions.
You list 4 new tests and then describe 3 of them. Adding a sentence for
the new test would be great:
test_source_tracking.exp verifies that source context is
correctly captured when the breakpoint is on the last line
of the file.
>
> Add maintenance command to print tracked source code.
> Add documentation for the new source-tracking breakpoints feature.
>
> Limitations of the current implementation:
>
> Source tracking is not enabled for pending breakpoints that become
> non-pending. When a breakpoint is created pending (e.g. with 'set
> breakpoint pending on'), source context is not captured at creation
> time since no symtab is available yet. When the breakpoint later
> resolves to a location, re_set_default() only updates existing tracked
> breakpoints and does not initiate tracking for newly resolved ones.
> This could be fixed in the future by initiating source tracking in
> re_set_default() when a breakpoint transitions from pending to
> non-pending.
>
> Source tracking for ranged breakpoints is not currently supported.
> Ranged breakpoints have a start and end location spec, and tracking
> both independently raises questions about whether to preserve the
> range length or track each end separately. For now, ranged
> breakpoints will never be source-tracked.
>
> Reviewed-By: Eli Zaretskii <eliz@gnu.org>
> ---
> v8:
> - Fix off-by-one error in breakpoint_source_capture and
> add gdb.base/test_source_tracking.exp to test this corner case
> - Fix API drift: symtab::compunit() now returns compunit_symtab &
> - Fix locspec assignment ordering in adjust_bp_for_source_tracking
> - Move the breakpoint_source struct definition from breakpoint.h to breakpoint.c
>
> gdb/NEWS | 14 +
> gdb/breakpoint.c | 487 ++++++++++++++++++
> gdb/breakpoint.h | 16 +-
> gdb/doc/gdb.texinfo | 45 ++
> .../gdb.base/adjust_breakpoint-2.cpp | 39 ++
> .../gdb.base/adjust_breakpoint-3.cpp | 41 ++
> .../gdb.base/adjust_breakpoint-4.cpp | 37 ++
> .../adjust_breakpoint-missing-source.exp | 55 ++
> gdb/testsuite/gdb.base/adjust_breakpoint.cpp | 40 ++
> gdb/testsuite/gdb.base/adjust_breakpoint.exp | 168 ++++++
> .../gdb.base/source-tracking-inline-1.c | 50 ++
> .../gdb.base/source-tracking-inline-2.c | 49 ++
> .../gdb.base/source-tracking-inline.exp | 80 +++
> gdb/testsuite/gdb.base/test_source_tracking.c | 18 +
> .../gdb.base/test_source_tracking.exp | 43 ++
> 15 files changed, 1181 insertions(+), 1 deletion(-)
> create mode 100644 gdb/testsuite/gdb.base/adjust_breakpoint-2.cpp
> create mode 100644 gdb/testsuite/gdb.base/adjust_breakpoint-3.cpp
> create mode 100644 gdb/testsuite/gdb.base/adjust_breakpoint-4.cpp
> create mode 100644 gdb/testsuite/gdb.base/adjust_breakpoint-missing-source.exp
> create mode 100644 gdb/testsuite/gdb.base/adjust_breakpoint.cpp
> create mode 100644 gdb/testsuite/gdb.base/adjust_breakpoint.exp
> create mode 100644 gdb/testsuite/gdb.base/source-tracking-inline-1.c
> create mode 100644 gdb/testsuite/gdb.base/source-tracking-inline-2.c
> create mode 100644 gdb/testsuite/gdb.base/source-tracking-inline.exp
> create mode 100644 gdb/testsuite/gdb.base/test_source_tracking.c
> create mode 100644 gdb/testsuite/gdb.base/test_source_tracking.exp
>
> diff --git a/gdb/NEWS b/gdb/NEWS
> index ec9b5a33787..553d8c49ff6 100644
> --- a/gdb/NEWS
> +++ b/gdb/NEWS
> @@ -6,6 +6,13 @@
> * Support for the Common Trace Format (CTF) has been removed. GDB now
> saves trace information exclusively in its own "tfile" format.
>
> +* GDB now supports source-tracking breakpoints, which automatically
> + adjust their location when source code changes between rebuilds.
> + When enabled, file and line breakpoints capture the surrounding
> + source code context and use it to adjust the breakpoint line if the
> + source is modified. Source tracking can be enabled with 'set
> + breakpoint source-tracking enabled on'.
> +
> * Support for .gdb_index sections with version less than 7 has been
> removed.
>
> @@ -141,6 +148,13 @@ unset local-environment
> environment. The local environment is used by "shell", "pipe", and
> other commands that launch a subprocess other than an inferior.
>
> +set breakpoint source-tracking enabled [on|off]
> +show breakpoint source-tracking enabled
> + Enable or disable source-tracking for file and line breakpoints.
> + When enabled, breakpoints capture surrounding source code and
> + automatically adjust their location when the source changes between
> + recompilations.
> +
The following new command needs adding to the NEWS file:
maint info source-tracking-context
...
> save history FILENAME
> Save the command history to the given file.
>
> diff --git a/gdb/breakpoint.c b/gdb/breakpoint.c
> index e4df4df04a7..0d116a217cf 100644
> --- a/gdb/breakpoint.c
> +++ b/gdb/breakpoint.c
> +
> +/* Print captured source lines to stdout, marking the breakpoint line with '>'. */
> +
> +static void
> +breakpoint_source_print (const breakpoint_source *src)
> +{
> + if (!breakpoint_source_is_tracked (src))
> + return;
> +
> + int start_line = breakpoint_source_get_start_line (src);
> + for (std::size_t j = 0; j < src->source_lines.size (); j++)
> + {
> + int line_num = start_line + (int) j;
> + char prefix;
> + if (j == src->bp_line_stored)
> + prefix = '>';
> + else
> + prefix = ' ';
> + gdb_printf ("%c %ps %s", prefix,
> + styled_string (line_number_style.style (),
> + pulongest (line_num)),
As LINE_NUM is signed, `plongest` would be a better choice here.
There's an unfortunate mix of calls throughout GDB with some using
pulongest and some plongest. But if we consider the
ui_out::field_signed calls, I think plongest is the most common, and
feels like the correct choice for signed values.
> + src->source_lines[j].c_str ());
> + if (src->source_lines[j].empty ()
> + || src->source_lines[j].back () != '\n')
> + gdb_putc ('\n');
> + }
> +}
> +
> +/* Implement the "maintenance info source-tracking-context" command. */
> +
> +static void
> +maintenance_info_source_tracking_context (const char *args, int from_tty)
> +{
> + if (args == nullptr || *args == '\0')
> + error (_("Breakpoint number required."));
> +
> + /* Parse the breakpoint number. */
> + const char *end = args;
> + int num = get_number_trailer (&end, 0);
> +
> + if (num <= 0)
> + error (_("Invalid breakpoint number '%s'."), args);
> +
> + /* Find the breakpoint. */
> + breakpoint *b = nullptr;
> + for (breakpoint &bp : all_breakpoints ())
> + {
> + if (bp.number == num)
> + {
> + b = &bp;
> + break;
> + }
> + }
> +
> + if (b == nullptr)
> + error (_("No breakpoint number %d."), num);
> +
> + /* Check if source tracking is enabled for this breakpoint. */
> + if (!breakpoint_source_is_tracked (b->bp_source.get ()))
> + {
> + gdb_printf (_("Breakpoint %d does not have source tracking enabled.\n"), num);
> + return;
The documentation for this command says:
For source-tracking breakpoints (*note Breakpoints::), print the
tracked source code context for breakpoint NUM. If breakpoint NUM
is not source tracked, or NUM is not a valid breakpoint number,
then the command gives an error.
Except for non-tracked breakpoints you don't give an error, you print a
message and return. You should either update the documentation to
match the implementation, or the implementation to match the docs, I
think either choice would be fine. I've left a comment below where the
docs are inconsistent.
> @@ -8926,6 +9204,32 @@ create_breakpoint_sal (struct gdbarch *gdbarch,
> enabled, flags,
> display_canonical);
>
> + /* Only capture source lines for file:line breakpoints when source
> + tracking is enabled. We check explicit_line to ensure the user
> + explicitly specified a line number (e.g., "break file.c:23" or
> + "break 23"), as opposed to "break function_name" or temporary
> + breakpoints set by commands like "start".
> +
> + We also only track single-location breakpoints. Multi-location
> + breakpoints (e.g., breakpoints on inline functions that are inlined
> + in multiple places) are too complex to track reliably as each location
> + may have moved differently. */
> + if (source_tracking_breakpoints && sals.size () == 1
> + && sals[0].explicit_line
> + && breakpoint_locspec_suitable_for_tracking (b->locspec.get ()))
> + {
> + /* Capture source if we have valid symtab and line info.
> + This works for both "b file:line" and "b line" formats.
> + We capture BREAKPOINT_SRC_CTX_LINES lines to provide
> + context around the breakpoint location. */
> + b->bp_source = std::make_unique<breakpoint_source>
> + (breakpoint_source_capture (sals, BREAKPOINT_SRC_CTX_LINES));
> +
> + if (!breakpoint_source_is_tracked (b->bp_source.get ()))
> + warning (_("Source file not available; breakpoint will not be "
> + "source-tracked."));
Optional: you could change this to:
if (!breakpoint_source_is_tracked (b->bp_source.get ()))
{
warning (_("Source file not available; breakpoint will not be "
"source-tracked."));
b->bp_source.reset ();
}
this would discard the empty breakpoint_source object, saving a small
amount of memory. Not discarding it is harmless, it just uses a little
memory, but it's not a leak, and an empty breakpoint_source still
returns false for breakpoint_source_is_tracked, so not deleting is
functionally correct, this is really up to you.
> + }
> +
> install_breakpoint (internal, std::move (b), 0);
> }
>
> @@ -13183,6 +13487,154 @@ code_breakpoint::location_spec_to_sals (location_spec *locspec,
> return sals;
> }
>
> +/* Match BREAKPOINT_SRC_CTX_LINES lines of the initially stored source in a
> + BREAKPOINT_SRC_CTX_LINES * BREAKPOINT_SRC_SEARCH_MULTIPLIER lines current
> + source window.
> +
> + Returns new breakpoint line on success or -1 on failure. */
> +
> +static int
> +sliding_window_match (breakpoint_source *bp_source,
> + breakpoint_source *tmp_source)
> +{
> + /* The index into BP_SOURCE's lines where the breakpoint was placed. */
> + size_t bp_stored = bp_source->bp_line_stored;
> + size_t bp_size = bp_source->source_lines.size ();
> +
> + /* An empty string, used if the breakpoint line is at the start or end of
> + the context window. */
> + static std::string empty_string;
This could also be const, so `static const std::string emptry_string`.
> +
> +/* See breakpoint.h. */
> +
> +void
> +code_breakpoint::adjust_bp_for_source_tracking
> + (program_space *filter_pspace,
> + std::vector<symtab_and_line> &expanded)
> +{
> + if (expanded.empty () || expanded[0].symtab == nullptr
> + || !breakpoint_source_is_tracked (bp_source.get ()))
> + return;
> +
> + struct compunit_symtab &cust = expanded[0].symtab->compunit ();
> + if (cust.objfile () == nullptr)
> + return;
> +
> + bfd *current_bfd = cust.objfile ()->obfd.get ();
> + if (bp_source->source_bfd.get () == current_bfd)
> + return;
> +
> + /* BFD changed — executable was reloaded. */
This is a non ASCII dash character. Can you replace it with a standard
ASCII dash please. Ideally comments should be complete sentences, so
rewriting to remove the need for a dash would be best.
> + if (expanded.size () != 1)
> + {
> + warning (_("Breakpoint %d now has multiple locations after reload, "
> + "disabling source tracking."), number);
> + bp_source.reset ();
> + return;
> + }
> +
> + /* If this fails then the location spec has changed since the
> + breakpoint's source tracking was initially setup. */
> + gdb_assert (breakpoint_locspec_suitable_for_tracking (locspec.get ()));
> +
> + std::string line;
> + auto restore_styling = make_scoped_restore (&source_styling, false);
> + if (!g_source_cache.get_source_lines (expanded[0].symtab,
> + expanded[0].line,
> + expanded[0].line, &line))
> + {
> + /* Source is unreadable after reload — drop tracking. */
Non ASCII dash again.
> + bp_source.reset ();
> + return;
> + }
> +
> + if (line == bp_source->source_lines[bp_source->bp_line_stored])
> + {
> + /* Line unchanged — just refresh the capture with the new BFD. */
Non ASCII dash again.
> + bp_source = std::make_unique<breakpoint_source>
> + (breakpoint_source_capture (expanded, BREAKPOINT_SRC_CTX_LINES));
> + return;
> + }
> +
> + breakpoint_source tmp_source
> + = breakpoint_source_capture (expanded,
> + BREAKPOINT_SRC_CTX_LINES
> + * BREAKPOINT_SRC_SEARCH_MULTIPLIER);
> + int new_bp_line = sliding_window_match (bp_source.get (), &tmp_source);
> + if (new_bp_line == -1)
> + {
> + warning (_("Breakpoint %d source code not found "
> + "after reload, keeping original location."), number);
> + bp_source.reset ();
> + return;
> + }
> +
> + auto *explicit_loc = as_explicit_location_spec (locspec.get ());
> + location_spec_up new_locspec = explicit_loc->clone ();
> + auto *new_explicit = as_explicit_location_spec (new_locspec.get ());
> + new_explicit->line_offset.offset = new_bp_line;
> + new_explicit->line_offset.sign = LINE_OFFSET_NONE;
> + new_explicit->set_string (""); /* Invalidate the cached display string. */
Sorry to be a pain, but GDB style is to place comments before the source
line, not trailing at the end. Can you move this comment to a line of
its own please.
> +
> + int found;
> + expanded = location_spec_to_sals (new_locspec.get (), filter_pspace, &found);
> + if (!found)
> + {
> + warning (_("Breakpoint %d adjusted to line %d but location could not "
> + "be resolved; keeping original location."), number, new_bp_line);
This line needs indenting with tabs. If you use 'git check' it should
point out these white space issues for you.
> diff --git a/gdb/breakpoint.h b/gdb/breakpoint.h
> index 722d75390fa..2698fc4d441 100644
> --- a/gdb/breakpoint.h
> +++ b/gdb/breakpoint.h
> @@ -27,6 +27,7 @@
> #include "probe.h"
> #include "location.h"
> #include <vector>
> +#include <memory>
> #include "gdbsupport/array-view.h"
> #include "gdbsupport/filtered-iterator.h"
> #include "gdbsupport/iterator-range.h"
> @@ -34,7 +35,6 @@
> #include "gdbsupport/safe-iterator.h"
> #include "cli/cli-script.h"
> #include "target/waitstatus.h"
> -
Unnecessary white space change, please revert.
> diff --git a/gdb/doc/gdb.texinfo b/gdb/doc/gdb.texinfo
> index a698b2b8451..d304f885def 100644
> --- a/gdb/doc/gdb.texinfo
> +++ b/gdb/doc/gdb.texinfo
> @@ -4661,6 +4661,28 @@ program.
> On some systems, you can set breakpoints in shared libraries before
> the executable is run.
>
> +@cindex source-tracking breakpoints
> +@cindex breakpoints, automatic adjustment when source changes
> +@value{GDBN} supports @dfn{source-tracking breakpoints}, which
> +automatically adjust their location when source code changes between
> +recompilations. When enabled with @code{set breakpoint source-tracking
> +enabled on}, breakpoints set by file and line number capture a small
> +window of surrounding source lines: the line immediately before the
> +breakpoint, the breakpoint line itself, and the line immediately after
> +it. If the source file is modified and the executable is rebuilt,
> +@value{GDBN} searches a window of approximately 12 lines centered on the
> +breakpoint's original position for a match. A candidate line is
> +accepted when it matches the captured breakpoint line and both of
> +its immediate neighbors also match, reducing false positives from
> +short or repeated lines. @value{GDBN} uses the first such confirmed
> +match found, scanning from the top of the search window. If the same
> +code sequence appears more than once within the search window, the
> +earliest occurrence is chosen; code outside the search window is not
> +considered. If no match is found, @value{GDBN} issues a warning and
> +keeps the breakpoint at its original location, disabling source tracking
> +for that breakpoint. Note that breakpoints set by function name or
> +address are not affected by source tracking. @xref{Set Breaks}.
> +
> @cindex watchpoints
> @cindex data breakpoints
> @cindex memory tracing
> @@ -42017,6 +42039,13 @@ Shared library events.
>
> @end table
>
> +@kindex maint info source-tracking-context
> +@item maint info source-tracking-context @var{num}
> +For source-tracking breakpoints (@pxref{Breakpoints}), print the
> +tracked source code context for breakpoint @var{num}. If breakpoint
> +@var{num} is not source tracked, or @var{num} is not a valid
> +breakpoint number, then the command gives an error.
Here is the documentation inconsistency that I mentioned earlier.
Currently GDB doesn't give an error for non-source tracked breakpoints,
it just prints a message and returns.
> diff --git a/gdb/testsuite/gdb.base/adjust_breakpoint.exp b/gdb/testsuite/gdb.base/adjust_breakpoint.exp
> new file mode 100644
> index 00000000000..123c03d322d
> --- /dev/null
> +++ b/gdb/testsuite/gdb.base/adjust_breakpoint.exp
> @@ -0,0 +1,168 @@
> +# 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/>.
> +
> +# 1) Set the breakpoint to a certain line in $srcfile. Replace the $srcfile
> +# with tmp-$srcfile which is exactly the same except one line is missing,
> +# which changes the line where the breakpoint was initially set and moves
> +# the breakpoint one line backwards.
> +# Check if GDB adjusted the line correctly.
> +#
> +# 2) Do all the same but move the breakpoint a few lines forward by adding an
> +# additional line to the tmp2-$srcfile.
> +
> +standard_testfile .cpp -2.cpp -3.cpp -4.cpp
> +set build_srcfile ${testfile}-xxx.cpp
> +
> +set new_source_file [standard_output_file ${build_srcfile}]
> +remote_exec build "cp ${srcdir}/${subdir}/${srcfile} $new_source_file"
> +if { [prepare_for_testing "failed to prepare" $testfile $new_source_file] } {
> + return
> +}
> +
> +# Enable source tracking for breakpoints.
> +gdb_test_no_output "set breakpoint source-tracking enabled on" \
> + "enable source tracking breakpoints"
> +
> +# If a breakpoint is being source tracked, then turning source
> +# tracking off discards the tracking information and prints a message.
> +# If no breakpoints are being source tracked then disabling source
> +# tracking should be silent.
> +with_test_prefix "check disabling is silent" {
> + gdb_test_no_output "set breakpoint source-tracking enabled off" \
> + "disable"
> +
> + gdb_test_no_output "set breakpoint source-tracking enabled on" \
> + "enable"
> +}
> +
> +# Test that the breakpoint can be adjusted backward.
> +set lineno [gdb_get_line_number "var += i;" $new_source_file]
> +gdb_breakpoint ${build_srcfile}:$lineno
> +
> +# Sleep to ensure timestamp changes when we rebuild.
> +sleep 1
> +remote_exec build "cp ${srcdir}/${subdir}/${srcfile2} $new_source_file"
> +if {[build_executable "failed to prepare" $testfile $new_source_file] == -1} {
> + return
> +}
> +
> +set lineno [expr {$lineno - 1}]
> +gdb_test "run" "Breakpoint 1,.*$build_srcfile:$lineno\r\n$lineno\t.*" \
> + "run stops at adjusted breakpoint location"
> +gdb_test "info breakpoints" \
> + "breakpoint.*keep.*y.*$hex.*$build_srcfile:$lineno.*already hit 1 time" \
> + "info breakpoints show the breakpoint was adjusted one line backward"
> +
> +# Test that the breakpoint can be adjusted forward.
> +clean_restart ${testfile}
> +gdb_test_no_output "set breakpoint source-tracking enabled on" \
> + "enable source tracking breakpoints for part 2"
> +gdb_breakpoint ${build_srcfile}:$lineno
> +
> +# Sleep to ensure timestamp changes when we rebuild.
> +sleep 1
> +remote_exec build "cp ${srcdir}/${subdir}/${srcfile3} $new_source_file"
> +if {[build_executable "failed to prepare" $testfile $new_source_file] == -1} {
> + return
> +}
> +
> +set lineno [expr {$lineno + 2}]
> +gdb_test "run" "Breakpoint 1,.*$build_srcfile:$lineno\r\n$lineno\t.*" \
> + "run for the second time stops at adjusted breakpoint location"
> +gdb_test "info breakpoints" \
> + "breakpoint.*keep.*y.*$hex.*$build_srcfile:$lineno.*already hit 1 time" \
> + "info breakpoints show the breakpoint was adjusted forward"
> +
> +# Disable source tracking breakpoints, the existing tracking
> +# information is discarded.
> +gdb_test "set breakpoint source-tracking enabled off" \
> + "^Discarding existing source tracking information\\." \
> + "disable source tracking, existing tracking is discarded"
> +
> +gdb_test "info breakpoints" \
> + [multi_line \
> + "1\\s+breakpoint\\s+keep\\s+y\[^\r\n\]+" \
> + "\\s+breakpoint already hit \[^\r\n\]+"] \
> + "info breakpoints breakpoint no longer tracked"
> +
> +# Test what happens when the breakpoint line disappears.
> +clean_restart ${testfile}
> +gdb_test_no_output "set breakpoint source-tracking enabled on" \
> + "enable source tracking breakpoints for part 3"
> +set lineno [gdb_get_line_number "var += 10;" $new_source_file]
> +gdb_breakpoint ${build_srcfile}:$lineno
> +
> +# Sleep to ensure timestamp changes when we rebuild.
> +sleep 1
> +remote_exec build "cp ${srcdir}/${subdir}/${srcfile4} $new_source_file"
> +if {[build_executable "failed to prepare" $testfile $new_source_file] == -1} {
> + return
> +}
> +
> +# When the original line is removed and cannot be found in the search window,
> +# the breakpoint stays at the symbol-resolved location. Line 10 becomes blank
> +# in tmp3, so GDB resolves it to line 11 (return var;) or stays at line 10.
> +# We test that it doesn't move beyond the reasonable range.
> +set lineno_re "(?:$lineno|[expr {$lineno + 1}])"
> +gdb_test "run" "Breakpoint 1,.*$build_srcfile:$lineno_re\r\n$lineno_re\t.*" \
> + "run for the third time stops near original location"
> +gdb_test "info breakpoints" \
> + "breakpoint.*keep.*y.*$hex.*$build_srcfile:$lineno_re.*already hit 1 time" \
> + "the breakpoint stays near original location when line disappears"
> +
> +# Test that with source tracking disabled the breakpoint should not be
> +# adjusted.
> +clean_restart ${testfile}
> +# Don't enable source tracking - test that breakpoints don't adjust without it
> +set lineno [gdb_get_line_number "return var;" $new_source_file]
> +gdb_breakpoint ${build_srcfile}:$lineno
> +
> +# Sleep to ensure timestamp changes when we rebuild.
> +sleep 1
> +remote_exec build "cp ${srcdir}/${subdir}/${srcfile2} $new_source_file"
> +if {[build_executable "failed to prepare" $testfile $new_source_file] == -1} {
> + return
> +}
> +
> +gdb_test "run" "Breakpoint 1,.*$build_srcfile:$lineno\r\n$lineno\t.*" \
> + "run for the fourth time stops at unadjusted location"
> +gdb_test "info breakpoints" \
> + "breakpoint.*keep.*y.*$hex.*$build_srcfile:$lineno.*already hit 1 time" \
> + "breakpoint not adjusted when tracking disabled"
> +
> +# Test that relative line breakpoints (e.g., "b +1") with source tracking
> +# enabled do not produce a spurious warning. Relative line offsets are not
> +# supported for source tracking, so the breakpoint should be created silently
> +# without any warning message.
> +clean_restart ${testfile}
> +gdb_test_no_output "set breakpoint source-tracking enabled on" \
> + "enable source tracking for relative line test"
> +
> +# Run to main so we have a current line context for relative breakpoints.
> +if {![runto_main]} {
> + return
> +}
> +
> +# "b +1" is a relative offset breakpoint — not trackable, but should be
Non ASCII dash again.
> +# created silently with no warning.
> +gdb_test_multiple "break +1" "relative line breakpoint creates no warning" {
> + -re -wrap "warning:.*source-track.*" {
> + fail "$gdb_test_name (unexpected warning)"
> + }
> + -re -wrap "Breakpoint $decimal at $hex.*" {
> + pass $gdb_test_name
> + }
> +}
> +
Blank line at end of file, please remove.
> diff --git a/gdb/testsuite/gdb.base/test_source_tracking.exp b/gdb/testsuite/gdb.base/test_source_tracking.exp
> new file mode 100644
> index 00000000000..597447aaa73
> --- /dev/null
> +++ b/gdb/testsuite/gdb.base/test_source_tracking.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 <http://www.gnu.org/licenses/>.
> +
> +# Test source-tracking correctly captures context when bp is on the last line.
> +# This covers an off-by-one in breakpoint_source_capture where the clipping
> +# formula excluded the last line of the file from the capture window.
> +
> +standard_testfile .c
> +
> +if { [prepare_for_testing "failed to prepare" $testfile $srcfile] } {
> + return
> +}
> +
> +gdb_test_no_output "set breakpoint source-tracking enabled on"
> +
> +set lineno [gdb_get_line_number "BPLastLine" $srcfile]
> +gdb_breakpoint ${srcfile}:$lineno
> +
> +# The '>' marker should be on the actual breakpoint line, not the line before it.
> +# Use gdb_test_multiple so the pattern can match the '>' line anywhere in the
> +# multi-line output (gdb_test's -wrap prevents matching across newlines).
> +gdb_test_multiple "maint info source-tracking-context 1" \
> + "breakpoint line is correctly marked in captured context" {
> + -re "> \[0-9\]+ \[^\r\n\]*BPLastLine\[^\r\n\]*\r\n$gdb_prompt $" {
> + pass $gdb_test_name
> + }
> + -re "$gdb_prompt $" {
> + fail $gdb_test_name
> + }
> +}
> +
Blank line at end of the file, please remove.
Thanks,
Andrew
@@ -6,6 +6,13 @@
* Support for the Common Trace Format (CTF) has been removed. GDB now
saves trace information exclusively in its own "tfile" format.
+* GDB now supports source-tracking breakpoints, which automatically
+ adjust their location when source code changes between rebuilds.
+ When enabled, file and line breakpoints capture the surrounding
+ source code context and use it to adjust the breakpoint line if the
+ source is modified. Source tracking can be enabled with 'set
+ breakpoint source-tracking enabled on'.
+
* Support for .gdb_index sections with version less than 7 has been
removed.
@@ -141,6 +148,13 @@ unset local-environment
environment. The local environment is used by "shell", "pipe", and
other commands that launch a subprocess other than an inferior.
+set breakpoint source-tracking enabled [on|off]
+show breakpoint source-tracking enabled
+ Enable or disable source-tracking for file and line breakpoints.
+ When enabled, breakpoints capture surrounding source code and
+ automatically adjust their location when the source changes between
+ recompilations.
+
save history FILENAME
Save the command history to the given file.
@@ -69,6 +69,8 @@
#include "cli/cli-style.h"
#include "cli/cli-decode.h"
#include "break-cond-parse.h"
+#include "source-cache.h"
+#include "gdb_bfd.h"
/* readline defines this. */
#undef savestring
@@ -165,6 +167,43 @@ static bool bl_address_is_meaningful (const bp_location *loc);
static int find_loc_num_by_location (const bp_location *loc);
+/* Captured source code around a breakpoint location, used for
+ source-tracking breakpoints. When source tracking is enabled,
+ this structure stores the original source lines around a breakpoint
+ so the breakpoint can be automatically adjusted if the source code
+ changes when the executable is reloaded. */
+
+struct breakpoint_source
+{
+ /* The captured source lines as strings. The number of captured lines
+ is 'source_lines.size ()'. */
+ std::vector<std::string> source_lines;
+
+ /* The original line number where the breakpoint was set
+ in the source file. */
+ int bp_line = 0;
+
+ /* Index into source_lines vector indicating which line
+ contains the breakpoint (0-based). */
+ size_t bp_line_stored = 0;
+
+ /* BFD when source was captured. */
+ gdb_bfd_ref_ptr source_bfd;
+};
+
+static bool breakpoint_source_is_tracked (const breakpoint_source *src);
+
+/* Number of source lines to capture around a breakpoint for source tracking.
+ This context is used to match and relocate breakpoints when the executable
+ is reloaded. The window is centered on the breakpoint line, capturing
+ lines both before and after it. */
+#define BREAKPOINT_SRC_CTX_LINES 3
+
+/* Multiplier for the search window when looking for relocated breakpoints.
+ We search in (BREAKPOINT_SRC_CTX_LINES * BREAKPOINT_SRC_SEARCH_MULTIPLIER)
+ lines to find code that may have moved. */
+#define BREAKPOINT_SRC_SEARCH_MULTIPLIER 4
+
/* update_global_location_list's modes of operation wrt to whether to
insert locations now. */
enum ugll_insert_mode
@@ -582,6 +621,60 @@ show_always_inserted_mode (struct ui_file *file, int from_tty,
value);
}
+/* When true file and line breakpoints are created as source-tracking
+ breakpoints. */
+
+static bool source_tracking_breakpoints = false;
+
+/* Implement 'show breakpoint source-tracking enabled'. */
+
+static void
+show_source_tracking_breakpoints (struct ui_file *file, int from_tty,
+ struct cmd_list_element *c,
+ const char *value)
+{
+ gdb_printf (file, _("Source tracking breakpoints are %s.\n"), value);
+}
+
+/* Get the value of 'breakpoint source-tracking enabled' setting. */
+
+static bool
+get_breakpoint_source_tracking_enabled ()
+{
+ return source_tracking_breakpoints;
+}
+
+/* Set the value of 'breakpoint source-tracking enabled' setting. When
+ this setting is changed to 'off' then any existing source-tracked
+ breakpoints have their source tracking information removed. */
+
+static void
+set_breakpoint_source_tracking_enabled (bool value)
+{
+ /* Store the new value. */
+ source_tracking_breakpoints = value;
+
+ /* When turning source tracking breakpoints on we don't start tracking
+ existing breakpoints, so we're done. */
+ if (source_tracking_breakpoints)
+ return;
+
+ /* Source tracking has been turned off. Discard any existing source
+ tracking information. Print a message if some information was
+ actually discarded. */
+ bool any_discarded = false;
+ for (struct breakpoint &b : all_breakpoints ())
+ {
+ if (breakpoint_source_is_tracked (b.bp_source.get ()))
+ {
+ any_discarded = true;
+ b.bp_source.reset ();
+ }
+ }
+ if (any_discarded)
+ gdb_printf (_("Discarding existing source tracking information.\n"));
+}
+
/* See breakpoint.h. */
bool debug_breakpoint = false;
@@ -593,6 +686,179 @@ show_debug_breakpoint (struct ui_file *file, int from_tty,
gdb_printf (file, _("Breakpoint location debugging is %s.\n"), value);
}
+/* Return true if the breakpoint source is being tracked. */
+
+static bool
+breakpoint_source_is_tracked (const breakpoint_source *src)
+{
+ if (src == nullptr)
+ return false;
+ return !src->source_lines.empty () && src->bp_line > 0;
+}
+
+/* Calculate the starting line number for captured source. */
+
+static int
+breakpoint_source_get_start_line (const breakpoint_source *src)
+{
+ if (!breakpoint_source_is_tracked (src))
+ return 0;
+ return src->bp_line - src->bp_line_stored;
+}
+
+/* Return true if SPEC is suitable for source tracking, otherwise false. A
+ location spec is suitable for tracking if it is an explicit location
+ spec, and the line offset is an absolute line number. We also don't
+ allow for SPEC to be function or label based. Most of these
+ restrictions could be lifted, but this would likely require additional
+ work to support these changes, especially when updating the location
+ spec. */
+
+static bool
+breakpoint_locspec_suitable_for_tracking (const location_spec *spec)
+{
+ if (spec->type () != EXPLICIT_LOCATION_SPEC)
+ return false;
+
+ const explicit_location_spec *explicit_loc
+ = as_explicit_location_spec (spec);
+
+ if (explicit_loc->function_name.get () != nullptr
+ || explicit_loc->label_name.get () != nullptr
+ || explicit_loc->source_filename.get () == nullptr)
+ return false;
+
+ if (explicit_loc->line_offset.sign != LINE_OFFSET_NONE)
+ return false;
+
+ return explicit_loc->line_offset.offset > 0;
+}
+
+/* Print captured source lines to stdout, marking the breakpoint line with '>'. */
+
+static void
+breakpoint_source_print (const breakpoint_source *src)
+{
+ if (!breakpoint_source_is_tracked (src))
+ return;
+
+ int start_line = breakpoint_source_get_start_line (src);
+ for (std::size_t j = 0; j < src->source_lines.size (); j++)
+ {
+ int line_num = start_line + (int) j;
+ char prefix;
+ if (j == src->bp_line_stored)
+ prefix = '>';
+ else
+ prefix = ' ';
+ gdb_printf ("%c %ps %s", prefix,
+ styled_string (line_number_style.style (),
+ pulongest (line_num)),
+ src->source_lines[j].c_str ());
+ if (src->source_lines[j].empty ()
+ || src->source_lines[j].back () != '\n')
+ gdb_putc ('\n');
+ }
+}
+
+/* Implement the "maintenance info source-tracking-context" command. */
+
+static void
+maintenance_info_source_tracking_context (const char *args, int from_tty)
+{
+ if (args == nullptr || *args == '\0')
+ error (_("Breakpoint number required."));
+
+ /* Parse the breakpoint number. */
+ const char *end = args;
+ int num = get_number_trailer (&end, 0);
+
+ if (num <= 0)
+ error (_("Invalid breakpoint number '%s'."), args);
+
+ /* Find the breakpoint. */
+ breakpoint *b = nullptr;
+ for (breakpoint &bp : all_breakpoints ())
+ {
+ if (bp.number == num)
+ {
+ b = &bp;
+ break;
+ }
+ }
+
+ if (b == nullptr)
+ error (_("No breakpoint number %d."), num);
+
+ /* Check if source tracking is enabled for this breakpoint. */
+ if (!breakpoint_source_is_tracked (b->bp_source.get ()))
+ {
+ gdb_printf (_("Breakpoint %d does not have source tracking enabled.\n"), num);
+ return;
+ }
+
+ /* Print the source tracking information. */
+ breakpoint_source_print (b->bp_source.get ());
+}
+
+/* Capture source lines around a breakpoint location for source tracking.
+ Returns a breakpoint_source structure with the captured lines, or an
+ empty structure if capture fails. Does not print the lines. */
+
+static breakpoint_source
+breakpoint_source_capture (gdb::array_view<const symtab_and_line> sals,
+ int num_of_lines)
+{
+ /* Check if we have a valid symtab - if not, we can't capture source lines.
+ The symtab can be missing if the executable wasn't compiled with
+ debugging symbols. */
+ if (sals.empty () || sals[0].symtab == nullptr || sals[0].line <= 0)
+ return {};
+
+ breakpoint_source result;
+ result.bp_line = sals[0].line;
+
+ /* Calculate the starting line, centering around the breakpoint line.
+ Avoid going before line 1. */
+ int lines_to_capture = num_of_lines;
+ int start_line = sals[0].line - (lines_to_capture / 2);
+ if (start_line < 1)
+ start_line = 1;
+
+ /* Get line offsets to check file bounds. */
+ const std::vector<off_t> *offsets;
+ if (!g_source_cache.get_line_charpos (sals[0].symtab, &offsets))
+ return {};
+
+ /* Adjust number of lines if we'd run past the end of the file. */
+ if (start_line + lines_to_capture > (int) offsets->size ())
+ lines_to_capture = (int) offsets->size () - start_line + 1;
+
+ /* Get the BFD from the symtab. */
+ if (sals[0].symtab->compunit ().objfile ())
+ result.source_bfd = sals[0].symtab->compunit ().objfile ()->obfd;
+
+ /* Capture the source lines. */
+ auto restore_styling = make_scoped_restore (&source_styling, false);
+ for (int j = 0; j < lines_to_capture; j++)
+ {
+ std::string line;
+ if (!g_source_cache.get_source_lines (sals[0].symtab, start_line + j,
+ start_line + j, &line))
+ {
+ /* Failed to read source - return empty structure so this
+ breakpoint won't be tracked. */
+ warning (_("Failed to capture source lines for source tracking."));
+ return {};
+ }
+ result.source_lines.push_back (line);
+ if (start_line + j == sals[0].line)
+ result.bp_line_stored = j;
+ }
+
+ return result;
+}
+
/* See breakpoint.h. */
int
@@ -838,6 +1104,8 @@ static int tracepoint_count;
static struct cmd_list_element *breakpoint_set_cmdlist;
static struct cmd_list_element *breakpoint_show_cmdlist;
+static struct cmd_list_element *source_tracking_bp_set_cmdlist;
+static struct cmd_list_element *source_tracking_bp_show_cmdlist;
struct cmd_list_element *save_cmdlist;
/* Return whether a breakpoint is an active enabled breakpoint. */
@@ -6817,6 +7085,16 @@ print_one_breakpoint_location (struct breakpoint *b,
uiout->text ("\n");
}
+ if (!part_of_multiple && breakpoint_source_is_tracked (b->bp_source.get ()))
+ {
+ uiout->text ("\tsource-tracking enabled (tracking ");
+ uiout->field_signed ("tracked-lines",
+ b->bp_source->source_lines.size ());
+ uiout->text (" lines around line ");
+ uiout->field_signed ("original-line", b->bp_source->bp_line);
+ uiout->text (")\n");
+ }
+
if (!part_of_multiple)
{
if (b->hit_count)
@@ -8926,6 +9204,32 @@ create_breakpoint_sal (struct gdbarch *gdbarch,
enabled, flags,
display_canonical);
+ /* Only capture source lines for file:line breakpoints when source
+ tracking is enabled. We check explicit_line to ensure the user
+ explicitly specified a line number (e.g., "break file.c:23" or
+ "break 23"), as opposed to "break function_name" or temporary
+ breakpoints set by commands like "start".
+
+ We also only track single-location breakpoints. Multi-location
+ breakpoints (e.g., breakpoints on inline functions that are inlined
+ in multiple places) are too complex to track reliably as each location
+ may have moved differently. */
+ if (source_tracking_breakpoints && sals.size () == 1
+ && sals[0].explicit_line
+ && breakpoint_locspec_suitable_for_tracking (b->locspec.get ()))
+ {
+ /* Capture source if we have valid symtab and line info.
+ This works for both "b file:line" and "b line" formats.
+ We capture BREAKPOINT_SRC_CTX_LINES lines to provide
+ context around the breakpoint location. */
+ b->bp_source = std::make_unique<breakpoint_source>
+ (breakpoint_source_capture (sals, BREAKPOINT_SRC_CTX_LINES));
+
+ if (!breakpoint_source_is_tracked (b->bp_source.get ()))
+ warning (_("Source file not available; breakpoint will not be "
+ "source-tracked."));
+ }
+
install_breakpoint (internal, std::move (b), 0);
}
@@ -13183,6 +13487,154 @@ code_breakpoint::location_spec_to_sals (location_spec *locspec,
return sals;
}
+/* Match BREAKPOINT_SRC_CTX_LINES lines of the initially stored source in a
+ BREAKPOINT_SRC_CTX_LINES * BREAKPOINT_SRC_SEARCH_MULTIPLIER lines current
+ source window.
+
+ Returns new breakpoint line on success or -1 on failure. */
+
+static int
+sliding_window_match (breakpoint_source *bp_source,
+ breakpoint_source *tmp_source)
+{
+ /* The index into BP_SOURCE's lines where the breakpoint was placed. */
+ size_t bp_stored = bp_source->bp_line_stored;
+ size_t bp_size = bp_source->source_lines.size ();
+
+ /* An empty string, used if the breakpoint line is at the start or end of
+ the context window. */
+ static std::string empty_string;
+
+ /* The lines immediately before and after the breakpoint in BP_SOURCE.
+ If the breakpoint is the first or last line in BP_SOURCE then use
+ EMPTY_STRING as a stand in. */
+ const std::string &bp_prev
+ = (bp_stored == 0
+ ? empty_string : bp_source->source_lines[bp_stored - 1]);
+ const std::string &bp_next
+ = ((bp_stored + 1) == bp_size
+ ? empty_string : bp_source->source_lines[bp_stored + 1]);
+
+ /* Now search TMP_SOURCE for the breakpoint line. */
+ size_t tmp_size = tmp_source->source_lines.size ();
+ for (size_t i = 0; i < tmp_size; i++)
+ {
+ if (bp_source->source_lines[bp_stored] == tmp_source->source_lines[i])
+ {
+ /* Found the breakpoint line. Capture the lines before and after
+ the breakpoint line from TMP_SOURCE. As above, if the
+ breakpoint is at the start or end of TMP_SOURCE then use
+ EMPTY_STRING as a stand in. */
+ const std::string &tmp_prev
+ = (i == 0 ? empty_string : tmp_source->source_lines[i - 1]);
+ const std::string &tmp_next
+ = ((i + 1) == tmp_size
+ ? empty_string : tmp_source->source_lines[i + 1]);
+
+ /* If the previous and next lines match then this is the new
+ location of the breakpoint, calculate and return the updated
+ line number. */
+ if (bp_prev == tmp_prev && bp_next == tmp_next)
+ return tmp_source->bp_line + i - tmp_source->bp_line_stored;
+ }
+ }
+
+ /* The updated breakpoint location has not been found in TMP_SOURCE. */
+ return -1;
+}
+
+/* See breakpoint.h. */
+
+void
+code_breakpoint::adjust_bp_for_source_tracking
+ (program_space *filter_pspace,
+ std::vector<symtab_and_line> &expanded)
+{
+ if (expanded.empty () || expanded[0].symtab == nullptr
+ || !breakpoint_source_is_tracked (bp_source.get ()))
+ return;
+
+ struct compunit_symtab &cust = expanded[0].symtab->compunit ();
+ if (cust.objfile () == nullptr)
+ return;
+
+ bfd *current_bfd = cust.objfile ()->obfd.get ();
+ if (bp_source->source_bfd.get () == current_bfd)
+ return;
+
+ /* BFD changed — executable was reloaded. */
+ if (expanded.size () != 1)
+ {
+ warning (_("Breakpoint %d now has multiple locations after reload, "
+ "disabling source tracking."), number);
+ bp_source.reset ();
+ return;
+ }
+
+ /* If this fails then the location spec has changed since the
+ breakpoint's source tracking was initially setup. */
+ gdb_assert (breakpoint_locspec_suitable_for_tracking (locspec.get ()));
+
+ std::string line;
+ auto restore_styling = make_scoped_restore (&source_styling, false);
+ if (!g_source_cache.get_source_lines (expanded[0].symtab,
+ expanded[0].line,
+ expanded[0].line, &line))
+ {
+ /* Source is unreadable after reload — drop tracking. */
+ bp_source.reset ();
+ return;
+ }
+
+ if (line == bp_source->source_lines[bp_source->bp_line_stored])
+ {
+ /* Line unchanged — just refresh the capture with the new BFD. */
+ bp_source = std::make_unique<breakpoint_source>
+ (breakpoint_source_capture (expanded, BREAKPOINT_SRC_CTX_LINES));
+ return;
+ }
+
+ breakpoint_source tmp_source
+ = breakpoint_source_capture (expanded,
+ BREAKPOINT_SRC_CTX_LINES
+ * BREAKPOINT_SRC_SEARCH_MULTIPLIER);
+ int new_bp_line = sliding_window_match (bp_source.get (), &tmp_source);
+ if (new_bp_line == -1)
+ {
+ warning (_("Breakpoint %d source code not found "
+ "after reload, keeping original location."), number);
+ bp_source.reset ();
+ return;
+ }
+
+ auto *explicit_loc = as_explicit_location_spec (locspec.get ());
+ location_spec_up new_locspec = explicit_loc->clone ();
+ auto *new_explicit = as_explicit_location_spec (new_locspec.get ());
+ new_explicit->line_offset.offset = new_bp_line;
+ new_explicit->line_offset.sign = LINE_OFFSET_NONE;
+ new_explicit->set_string (""); /* Invalidate the cached display string. */
+
+ int found;
+ expanded = location_spec_to_sals (new_locspec.get (), filter_pspace, &found);
+ if (!found)
+ {
+ warning (_("Breakpoint %d adjusted to line %d but location could not "
+ "be resolved; keeping original location."), number, new_bp_line);
+ bp_source.reset ();
+ return;
+ }
+ locspec = std::move (new_locspec);
+ if (new_bp_line != bp_source->bp_line)
+ {
+ gdb_printf (_("Breakpoint %d adjusted from line %d to line %d.\n"),
+ number, bp_source->bp_line, new_bp_line);
+ notify_breakpoint_modified (this);
+ }
+
+ bp_source = std::make_unique<breakpoint_source>
+ (breakpoint_source_capture (expanded, BREAKPOINT_SRC_CTX_LINES));
+}
+
/* The default re_set method, for typical hardware or software
breakpoints. Reevaluate the breakpoint and recreate its
locations. */
@@ -13213,12 +13665,17 @@ code_breakpoint::re_set_default (struct program_space *filter_pspace)
if (locspec_range_end != nullptr)
{
+ /* Ranged breakpoints are not currently tracked. */
+ gdb_assert (!breakpoint_source_is_tracked (bp_source.get ()));
+
std::vector<symtab_and_line> sals_end
= location_spec_to_sals (locspec_range_end.get (),
filter_pspace, &found);
if (found)
expanded_end = std::move (sals_end);
}
+
+ adjust_bp_for_source_tracking (filter_pspace, expanded);
}
/* Update the locations for this breakpoint. For thread-specific
@@ -15007,6 +15464,15 @@ Convenience variable \"$bpnum\" contains the number of the last\n\
breakpoint set."),
&maintenanceinfolist);
+ add_cmd ("source-tracking-context", class_maintenance,
+ maintenance_info_source_tracking_context, _("\
+Print source tracking context for a breakpoint.\n\
+Usage: maintenance info source-tracking-context BPNUM\n\
+\n\
+Displays the captured source code lines used to track\n\
+and automatically adjust the breakpoint when source code changes."),
+ &maintenanceinfolist);
+
add_basic_prefix_cmd ("catch", class_breakpoint, _("\
Set catchpoints to catch events."),
&catch_cmdlist,
@@ -15325,6 +15791,27 @@ Usage: agent-printf \"format string\", ARG1, ARG2, ARG3, ..., ARGN\n\
This supports most C printf format specifications, like %s, %d, etc.\n\
This is useful for formatted output in user-defined commands."));
+ add_setshow_prefix_cmd ("source-tracking", class_breakpoint,
+ _("\
+Source tracking breakpoint specific settings."),
+ _("\
+Source tracking breakpoint specific settings."),
+ &source_tracking_bp_set_cmdlist,
+ &source_tracking_bp_show_cmdlist,
+ &breakpoint_set_cmdlist, &breakpoint_show_cmdlist);
+
+ add_setshow_boolean_cmd ("enabled", class_breakpoint, _("\
+Set whether file and line breakpoints use source tracking."), _("\
+Show whether file and line breakpoints use source tracking."), _("\
+When on, breakpoints set with file:line syntax will track the source\n\
+location and automatically adjust when the source changes and the\n\
+inferior is restarted."),
+ set_breakpoint_source_tracking_enabled,
+ get_breakpoint_source_tracking_enabled,
+ show_source_tracking_breakpoints,
+ &source_tracking_bp_set_cmdlist,
+ &source_tracking_bp_show_cmdlist);
+
automatic_hardware_breakpoints = true;
gdb::observers::about_to_proceed.attach (breakpoint_about_to_proceed,
@@ -27,6 +27,7 @@
#include "probe.h"
#include "location.h"
#include <vector>
+#include <memory>
#include "gdbsupport/array-view.h"
#include "gdbsupport/filtered-iterator.h"
#include "gdbsupport/iterator-range.h"
@@ -34,7 +35,6 @@
#include "gdbsupport/safe-iterator.h"
#include "cli/cli-script.h"
#include "target/waitstatus.h"
-
struct block;
struct gdbpy_breakpoint_object;
struct gdbscm_breakpoint_object;
@@ -615,6 +615,8 @@ using bp_location_list = intrusive_list<bp_location>;
using bp_location_iterator = bp_location_list::iterator;
using bp_location_range = iterator_range<bp_location_iterator>;
+struct breakpoint_source;
+
/* Note that the ->silent field is not currently used by any commands
(though the code is in there if it was to be, and set_raw_breakpoint
does set it to 0). I implemented it because I thought it would be
@@ -863,6 +865,11 @@ struct breakpoint : public intrusive_list_node<breakpoint>
find the end of the range. */
location_spec_up locspec_range_end;
+ /* Captured source code around the breakpoint location, used to
+ track source around the breakpoint to automatically adjust the breakpoint
+ when source code changes between recompilations. */
+ std::unique_ptr<breakpoint_source> bp_source;
+
/* Architecture we used to set the breakpoint. */
struct gdbarch *gdbarch;
/* Language we used to set the breakpoint. */
@@ -983,6 +990,13 @@ struct code_breakpoint : public breakpoint
/* Helper method that does the basic work of re_set. */
void re_set_default (program_space *pspace);
+ /* Helper method for re_set_default. Checks if the executable was
+ reloaded and if so, attempts to adjust the breakpoint location
+ using source tracking. EXPANDED may be updated if the location
+ is adjusted. */
+ void adjust_bp_for_source_tracking (program_space *filter_pspace,
+ std::vector<symtab_and_line> &expanded);
+
/* Find the SaL locations corresponding to the given LOCATION.
On return, FOUND will be 1 if any SaL was found, zero otherwise. */
@@ -4661,6 +4661,28 @@ program.
On some systems, you can set breakpoints in shared libraries before
the executable is run.
+@cindex source-tracking breakpoints
+@cindex breakpoints, automatic adjustment when source changes
+@value{GDBN} supports @dfn{source-tracking breakpoints}, which
+automatically adjust their location when source code changes between
+recompilations. When enabled with @code{set breakpoint source-tracking
+enabled on}, breakpoints set by file and line number capture a small
+window of surrounding source lines: the line immediately before the
+breakpoint, the breakpoint line itself, and the line immediately after
+it. If the source file is modified and the executable is rebuilt,
+@value{GDBN} searches a window of approximately 12 lines centered on the
+breakpoint's original position for a match. A candidate line is
+accepted when it matches the captured breakpoint line and both of
+its immediate neighbors also match, reducing false positives from
+short or repeated lines. @value{GDBN} uses the first such confirmed
+match found, scanning from the top of the search window. If the same
+code sequence appears more than once within the search window, the
+earliest occurrence is chosen; code outside the search window is not
+considered. If no match is found, @value{GDBN} issues a warning and
+keeps the breakpoint at its original location, disabling source tracking
+for that breakpoint. Note that breakpoints set by function name or
+address are not affected by source tracking. @xref{Set Breaks}.
+
@cindex watchpoints
@cindex data breakpoints
@cindex memory tracing
@@ -42017,6 +42039,13 @@ Shared library events.
@end table
+@kindex maint info source-tracking-context
+@item maint info source-tracking-context @var{num}
+For source-tracking breakpoints (@pxref{Breakpoints}), print the
+tracked source code context for breakpoint @var{num}. If breakpoint
+@var{num} is not source tracked, or @var{num} is not a valid
+breakpoint number, then the command gives an error.
+
@kindex maint info btrace
@item maint info btrace
Pint information about raw branch tracing data.
@@ -42885,6 +42914,22 @@ Control whether to show all non zero areas within a 1k block starting
at thread local base, when using the @samp{info w32 thread-information-block}
command.
+@kindex set breakpoint source-tracking enabled
+@kindex show breakpoint source-tracking enabled
+@item set breakpoint source-tracking enabled @r{[}on@r{|}off@r{]}
+@itemx show breakpoint source-tracking enabled
+Control whether to enable source-tracking for breakpoints set by file and
+line number. Use @code{on} to enable, @code{off} to disable. When
+enabled, @value{GDBN} captures a window of source lines around each
+new file:line breakpoint and uses it to relocate the breakpoint if the
+source is modified and the executable is rebuilt. @xref{Breakpoints},
+for a full description of the matching algorithm and its limitations.
+The default is @code{off}. Breakpoints set by function name or address
+are not affected by this setting.
+
+If this setting is changed from @code{on} to @code{off}, then any
+existing source tracking information will be discarded.
+
@kindex maint set target-async
@kindex maint show target-async
@item maint set target-async
new file mode 100644
@@ -0,0 +1,39 @@
+/* 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/>. */
+
+int
+foo (void)
+{
+ return 0;
+}
+
+int
+foo (int var)
+{
+ int i = 8;
+ var += 10;
+ var += i;
+
+ return var;
+}
+
+int
+main (void)
+{
+ foo ();
+ return foo (2);
+}
new file mode 100644
@@ -0,0 +1,41 @@
+/* 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/>. */
+
+
+int
+foo () {
+ return 0;
+}
+
+int
+foo (int var)
+{
+ int j = 0;
+ int i = 8;
+
+ var += 10;
+ var += i;
+
+ return var;
+}
+
+int
+main (void)
+{
+ foo ();
+ return foo (2);
+}
new file mode 100644
@@ -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 <http://www.gnu.org/licenses/>. */
+
+int
+foo () {
+ return 0;
+}
+
+int
+foo (int var)
+{
+ int i = 8;
+
+
+ return var;
+}
+
+int
+main (void)
+{
+ foo ();
+ return foo (2);
+}
new file mode 100644
@@ -0,0 +1,55 @@
+# 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/>.
+
+# Test that source-tracking breakpoints handle missing source files correctly.
+# When source tracking is enabled but the source file cannot be found, GDB
+# should create a non-tracking breakpoint and not crash or fail.
+
+standard_testfile adjust_breakpoint.cpp
+set build_srcfile xxx-${testfile}.cpp
+
+set new_source_file [standard_output_file ${build_srcfile}]
+remote_exec build "cp ${srcdir}/${subdir}/adjust_breakpoint.cpp $new_source_file"
+if { [prepare_for_testing "failed to prepare" $testfile $new_source_file] } {
+ return
+}
+
+# Enable source tracking.
+gdb_test_no_output "set breakpoint source-tracking enabled on" \
+ "enable source tracking breakpoints"
+
+# Now remove the source file before setting the breakpoint.
+set bp_line [gdb_get_line_number "return 0;" $new_source_file]
+remote_exec build "rm -f $new_source_file"
+
+# Verify the source is not available.
+gdb_test "list" ".*No such file or directory.*" \
+ "verify source file is not available"
+
+# Try to set a breakpoint, this should succeed but not be tracked.
+gdb_test "break ${build_srcfile}:${bp_line}" "Breakpoint.*at.*" \
+ "create breakpoint when source file missing"
+
+# Check that the breakpoint was created but is not tracked,
+# it should not show the "source-tracking enabled" message.
+gdb_test_multiple "info breakpoints" \
+ "breakpoint not tracked when source missing" {
+ -re -wrap "source-tracking enabled.*" {
+ fail $gdb_test_name
+ }
+ -re -wrap "breakpoint.*${build_srcfile}:${bp_line}.*" {
+ pass $gdb_test_name
+ }
+ }
new file mode 100644
@@ -0,0 +1,40 @@
+/* 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/>. */
+
+int
+foo (void)
+{
+ return 0;
+}
+
+int
+foo (int var)
+{
+ int i = 8;
+
+ var += 10;
+ var += i;
+
+ return var;
+}
+
+int
+main (void)
+{
+ foo ();
+ return foo (2);
+}
new file mode 100644
@@ -0,0 +1,168 @@
+# 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/>.
+
+# 1) Set the breakpoint to a certain line in $srcfile. Replace the $srcfile
+# with tmp-$srcfile which is exactly the same except one line is missing,
+# which changes the line where the breakpoint was initially set and moves
+# the breakpoint one line backwards.
+# Check if GDB adjusted the line correctly.
+#
+# 2) Do all the same but move the breakpoint a few lines forward by adding an
+# additional line to the tmp2-$srcfile.
+
+standard_testfile .cpp -2.cpp -3.cpp -4.cpp
+set build_srcfile ${testfile}-xxx.cpp
+
+set new_source_file [standard_output_file ${build_srcfile}]
+remote_exec build "cp ${srcdir}/${subdir}/${srcfile} $new_source_file"
+if { [prepare_for_testing "failed to prepare" $testfile $new_source_file] } {
+ return
+}
+
+# Enable source tracking for breakpoints.
+gdb_test_no_output "set breakpoint source-tracking enabled on" \
+ "enable source tracking breakpoints"
+
+# If a breakpoint is being source tracked, then turning source
+# tracking off discards the tracking information and prints a message.
+# If no breakpoints are being source tracked then disabling source
+# tracking should be silent.
+with_test_prefix "check disabling is silent" {
+ gdb_test_no_output "set breakpoint source-tracking enabled off" \
+ "disable"
+
+ gdb_test_no_output "set breakpoint source-tracking enabled on" \
+ "enable"
+}
+
+# Test that the breakpoint can be adjusted backward.
+set lineno [gdb_get_line_number "var += i;" $new_source_file]
+gdb_breakpoint ${build_srcfile}:$lineno
+
+# Sleep to ensure timestamp changes when we rebuild.
+sleep 1
+remote_exec build "cp ${srcdir}/${subdir}/${srcfile2} $new_source_file"
+if {[build_executable "failed to prepare" $testfile $new_source_file] == -1} {
+ return
+}
+
+set lineno [expr {$lineno - 1}]
+gdb_test "run" "Breakpoint 1,.*$build_srcfile:$lineno\r\n$lineno\t.*" \
+ "run stops at adjusted breakpoint location"
+gdb_test "info breakpoints" \
+ "breakpoint.*keep.*y.*$hex.*$build_srcfile:$lineno.*already hit 1 time" \
+ "info breakpoints show the breakpoint was adjusted one line backward"
+
+# Test that the breakpoint can be adjusted forward.
+clean_restart ${testfile}
+gdb_test_no_output "set breakpoint source-tracking enabled on" \
+ "enable source tracking breakpoints for part 2"
+gdb_breakpoint ${build_srcfile}:$lineno
+
+# Sleep to ensure timestamp changes when we rebuild.
+sleep 1
+remote_exec build "cp ${srcdir}/${subdir}/${srcfile3} $new_source_file"
+if {[build_executable "failed to prepare" $testfile $new_source_file] == -1} {
+ return
+}
+
+set lineno [expr {$lineno + 2}]
+gdb_test "run" "Breakpoint 1,.*$build_srcfile:$lineno\r\n$lineno\t.*" \
+ "run for the second time stops at adjusted breakpoint location"
+gdb_test "info breakpoints" \
+ "breakpoint.*keep.*y.*$hex.*$build_srcfile:$lineno.*already hit 1 time" \
+ "info breakpoints show the breakpoint was adjusted forward"
+
+# Disable source tracking breakpoints, the existing tracking
+# information is discarded.
+gdb_test "set breakpoint source-tracking enabled off" \
+ "^Discarding existing source tracking information\\." \
+ "disable source tracking, existing tracking is discarded"
+
+gdb_test "info breakpoints" \
+ [multi_line \
+ "1\\s+breakpoint\\s+keep\\s+y\[^\r\n\]+" \
+ "\\s+breakpoint already hit \[^\r\n\]+"] \
+ "info breakpoints breakpoint no longer tracked"
+
+# Test what happens when the breakpoint line disappears.
+clean_restart ${testfile}
+gdb_test_no_output "set breakpoint source-tracking enabled on" \
+ "enable source tracking breakpoints for part 3"
+set lineno [gdb_get_line_number "var += 10;" $new_source_file]
+gdb_breakpoint ${build_srcfile}:$lineno
+
+# Sleep to ensure timestamp changes when we rebuild.
+sleep 1
+remote_exec build "cp ${srcdir}/${subdir}/${srcfile4} $new_source_file"
+if {[build_executable "failed to prepare" $testfile $new_source_file] == -1} {
+ return
+}
+
+# When the original line is removed and cannot be found in the search window,
+# the breakpoint stays at the symbol-resolved location. Line 10 becomes blank
+# in tmp3, so GDB resolves it to line 11 (return var;) or stays at line 10.
+# We test that it doesn't move beyond the reasonable range.
+set lineno_re "(?:$lineno|[expr {$lineno + 1}])"
+gdb_test "run" "Breakpoint 1,.*$build_srcfile:$lineno_re\r\n$lineno_re\t.*" \
+ "run for the third time stops near original location"
+gdb_test "info breakpoints" \
+ "breakpoint.*keep.*y.*$hex.*$build_srcfile:$lineno_re.*already hit 1 time" \
+ "the breakpoint stays near original location when line disappears"
+
+# Test that with source tracking disabled the breakpoint should not be
+# adjusted.
+clean_restart ${testfile}
+# Don't enable source tracking - test that breakpoints don't adjust without it
+set lineno [gdb_get_line_number "return var;" $new_source_file]
+gdb_breakpoint ${build_srcfile}:$lineno
+
+# Sleep to ensure timestamp changes when we rebuild.
+sleep 1
+remote_exec build "cp ${srcdir}/${subdir}/${srcfile2} $new_source_file"
+if {[build_executable "failed to prepare" $testfile $new_source_file] == -1} {
+ return
+}
+
+gdb_test "run" "Breakpoint 1,.*$build_srcfile:$lineno\r\n$lineno\t.*" \
+ "run for the fourth time stops at unadjusted location"
+gdb_test "info breakpoints" \
+ "breakpoint.*keep.*y.*$hex.*$build_srcfile:$lineno.*already hit 1 time" \
+ "breakpoint not adjusted when tracking disabled"
+
+# Test that relative line breakpoints (e.g., "b +1") with source tracking
+# enabled do not produce a spurious warning. Relative line offsets are not
+# supported for source tracking, so the breakpoint should be created silently
+# without any warning message.
+clean_restart ${testfile}
+gdb_test_no_output "set breakpoint source-tracking enabled on" \
+ "enable source tracking for relative line test"
+
+# Run to main so we have a current line context for relative breakpoints.
+if {![runto_main]} {
+ return
+}
+
+# "b +1" is a relative offset breakpoint — not trackable, but should be
+# created silently with no warning.
+gdb_test_multiple "break +1" "relative line breakpoint creates no warning" {
+ -re -wrap "warning:.*source-track.*" {
+ fail "$gdb_test_name (unexpected warning)"
+ }
+ -re -wrap "Breakpoint $decimal at $hex.*" {
+ pass $gdb_test_name
+ }
+}
+
new file mode 100644
@@ -0,0 +1,50 @@
+/* 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/>. */
+
+volatile int global_var = 0;
+
+static inline void __attribute__ ((__always_inline__))
+inline_func (void)
+{
+ ++global_var; /* Breakpoint here. */
+}
+
+int __attribute__ ((noinline, noclone))
+foo (int x)
+{
+ inline_func ();
+ return x + global_var;
+}
+
+int __attribute__ ((noinline, noclone))
+bar (int x)
+{
+ inline_func ();
+ return x - global_var;
+}
+
+int
+main (void)
+{
+ ++global_var;
+
+ int ans = foo (42) + bar (10);
+
+ ++global_var;
+
+ return ans - global_var;
+}
new file mode 100644
@@ -0,0 +1,49 @@
+/* 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/>. */
+volatile int global_var = 0;
+
+static inline void __attribute__ ((__always_inline__))
+inline_func (void)
+{
+ ++global_var; /* Breakpoint here. */
+}
+
+int __attribute__ ((noinline, noclone))
+foo (int x)
+{
+ inline_func ();
+ return x + global_var;
+}
+
+int __attribute__ ((noinline, noclone))
+bar (int x)
+{
+ inline_func ();
+ return x - global_var;
+}
+
+int
+main (void)
+{
+ ++global_var;
+
+ int ans = foo (42) + bar (10);
+
+ ++global_var;
+
+ return ans - global_var;
+}
new file mode 100644
@@ -0,0 +1,80 @@
+# 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/>.
+
+# Test of the source tracking breakpoint feature when trying to place
+# a breakpoint on inline functions. As the breakpoint resolves to
+# multiple locations we don't currently source track these
+# breakpoints.
+
+standard_testfile -1.c -2.c
+
+set build_srcfile [standard_output_file ${testfile}.c]
+
+remote_exec build "cp $srcdir/$subdir/$srcfile $build_srcfile"
+if { [prepare_for_testing "failed to prepare" $testfile $build_srcfile] } {
+ return
+}
+
+if {![runto_main]} {
+ return
+}
+
+gdb_test_no_output "set breakpoint source-tracking enabled on" \
+ "enable source tracking breakpoints"
+
+set lineno [gdb_get_line_number "Breakpoint here." $build_srcfile]
+
+# The breakpoint is on an inline function called from two places,
+# so it resolves to 2 locations.
+gdb_test "break ${testfile}.c:$lineno" \
+ "^Breakpoint $decimal at $hex: ${testfile}.c:$lineno\\. \\(2 locations\\)"
+
+# Check that we are tracking the expected number of lines.
+#
+# With the multi-location fix, this breakpoint should NOT be tracked
+# because it has 2 locations (inline function called from two places).
+# The output should show <MULTIPLE> but NOT "source-tracking enabled".
+gdb_test_multiple "info breakpoints" \
+ "multi-location breakpoint not tracked" {
+ -re -wrap "source-tracking enabled.*" {
+ fail "$gdb_test_name (tracking incorrectly enabled)"
+ }
+ -re -wrap "<MULTIPLE>.*" {
+ pass $gdb_test_name
+ }
+}
+
+sleep 1
+remote_exec build "cp $srcdir/$subdir/$srcfile2 $build_srcfile"
+if { [build_executable "failed to build" $testfile $build_srcfile] } {
+ return
+}
+
+# Reload the executable. 'start' internally uses 'tbreak main', which
+# is not a file:line breakpoint, so it will not be source-tracked.
+gdb_test "with confirm off -- start"
+
+# After reload, the breakpoint should still NOT be tracked because it
+# still has 2 locations. This verifies we don't try to track
+# multi-location breakpoints even after reload.
+gdb_test_multiple "info breakpoints" \
+ "multi-location breakpoint still not tracked after reload" {
+ -re -wrap "source-tracking enabled.*" {
+ fail "$gdb_test_name (tracking incorrectly enabled)"
+ }
+ -re -wrap "<MULTIPLE>.*" {
+ pass $gdb_test_name
+ }
+}
new file mode 100644
@@ -0,0 +1,18 @@
+/* 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/>. */
+
+int main (void) { int x = 0xCAFE; return x; /* BPLastLine. */ }
new file mode 100644
@@ -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 <http://www.gnu.org/licenses/>.
+
+# Test source-tracking correctly captures context when bp is on the last line.
+# This covers an off-by-one in breakpoint_source_capture where the clipping
+# formula excluded the last line of the file from the capture window.
+
+standard_testfile .c
+
+if { [prepare_for_testing "failed to prepare" $testfile $srcfile] } {
+ return
+}
+
+gdb_test_no_output "set breakpoint source-tracking enabled on"
+
+set lineno [gdb_get_line_number "BPLastLine" $srcfile]
+gdb_breakpoint ${srcfile}:$lineno
+
+# The '>' marker should be on the actual breakpoint line, not the line before it.
+# Use gdb_test_multiple so the pattern can match the '>' line anywhere in the
+# multi-line output (gdb_test's -wrap prevents matching across newlines).
+gdb_test_multiple "maint info source-tracking-context 1" \
+ "breakpoint line is correctly marked in captured context" {
+ -re "> \[0-9\]+ \[^\r\n\]*BPLastLine\[^\r\n\]*\r\n$gdb_prompt $" {
+ pass $gdb_test_name
+ }
+ -re "$gdb_prompt $" {
+ fail $gdb_test_name
+ }
+}
+