@@ -3,6 +3,15 @@
*** Changes since GDB 17
+* Source-tracking breakpoints
+
+ 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.
@@ -80,6 +89,12 @@ unset local-environment
Analogs of the existing "environment" commands that affect GDB's own
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,7 @@
#include "cli/cli-style.h"
#include "cli/cli-decode.h"
#include "break-cond-parse.h"
+#include "source-cache.h"
/* readline defines this. */
#undef savestring
@@ -574,6 +575,10 @@ show_automatic_hardware_breakpoints (struct ui_file *file, int from_tty,
processing user input. */
static bool always_inserted_mode = false;
+/* Control whether file & line breakpoints are created as source-tracking
+ breakpoints. */
+static bool source_tracking_breakpoints = false;
+
static void
show_always_inserted_mode (struct ui_file *file, int from_tty,
struct cmd_list_element *c, const char *value)
@@ -593,6 +598,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.size () > 0 && 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 (int j = 0; j < (int) src->source_lines.size (); j++)
+ {
+ int line_num = start_line + 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 == NULL || *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;
+
+ /* 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 +1016,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. */
@@ -6821,6 +7001,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)
@@ -8930,7 +9120,35 @@ 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));
+ }
+
+ bool warn_not_tracked = (source_tracking_breakpoints && sals.size () == 1
+ && sals[0].explicit_line
+ && !breakpoint_source_is_tracked (b->bp_source.get ()));
install_breakpoint (internal, std::move (b), 0);
+ if (warn_not_tracked)
+ warning (_("Source file not available; breakpoint will not be "
+ "source-tracked."));
}
/* Add SALS.nelts breakpoints to the breakpoint table. For each
@@ -9213,7 +9431,6 @@ breakpoint_ops_for_location_spec (const location_spec *locspec,
}
/* See breakpoint.h. */
-
int
create_breakpoint (struct gdbarch *gdbarch,
location_spec *locspec,
@@ -13186,6 +13403,157 @@ 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)
+{
+ int bp_stored = bp_source->bp_line_stored;
+ int tmp_size = (int) tmp_source->source_lines.size ();
+ int bp_size = (int) bp_source->source_lines.size ();
+
+ for (int i = 0; i < tmp_size; i++)
+ {
+ /* Look for the initial breakpoint line in a stored window. */
+ if (bp_source->source_lines[bp_stored] == tmp_source->source_lines[i])
+ {
+ /* Check if the stored lines before and after the breakpoint line
+ also match, to reduce false positives. */
+ if (i > 0 && bp_stored > 0
+ && (i + 1) < tmp_size
+ && (bp_stored + 1) < bp_size)
+ {
+ if ((bp_source->source_lines[bp_stored - 1] == tmp_source->source_lines[i - 1])
+ && (bp_source->source_lines[bp_stored + 1] == tmp_source->source_lines[i + 1]))
+ {
+ return tmp_source->bp_line + i - tmp_source->bp_line_stored;
+ }
+ }
+ else if (i == 0 && (i + 1) < tmp_size
+ && (bp_stored + 1) < bp_size)
+ {
+ if (bp_source->source_lines[bp_stored + 1] == tmp_source->source_lines[i + 1])
+ {
+ return tmp_source->bp_line + i - tmp_source->bp_line_stored;
+ }
+ }
+ else if ((i + 1) == tmp_size && i > 0
+ && bp_stored > 0)
+ {
+ if (bp_source->source_lines[bp_stored - 1] == tmp_source->source_lines[i - 1])
+ {
+ return tmp_source->bp_line + i - tmp_source->bp_line_stored;
+ }
+ }
+ }
+ }
+
+ 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 == nullptr || 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 ()));
+
+ int bp_stored = bp_source->bp_line_stored;
+ 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_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;
+ }
+
+ location_spec *spec = locspec.get ();
+ std::string bp_string (spec->to_string ());
+ auto pos = bp_string.rfind (':');
+ if (pos == std::string::npos)
+ {
+ warning (_("unable to update location spec for breakpoint %d, "
+ "disabling source tracking."), number);
+ bp_source.reset ();
+ return;
+ }
+
+ bp_string = bp_string.substr (0, pos + 1);
+ bp_string.append (std::to_string (new_bp_line));
+
+ /* set_string only updates the cached display string, not the parsed
+ spec_string that location_spec_to_sals actually uses. Replace the
+ location spec entirely by re-parsing the updated string so the new
+ line number takes effect. */
+ const char *new_spec_str = bp_string.c_str ();
+ locspec = string_to_location_spec (&new_spec_str, current_language);
+ spec = locspec.get ();
+
+ int found;
+ expanded = location_spec_to_sals (spec, filter_pspace, &found);
+ if (found && 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. */
@@ -13216,6 +13584,9 @@ 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);
@@ -13224,6 +13595,8 @@ code_breakpoint::re_set_default (struct program_space *filter_pspace)
}
}
+ adjust_bp_for_source_tracking (filter_pspace, expanded);
+
/* Update the locations for this breakpoint. For thread-specific
breakpoints this will remove any old locations that are for the wrong
program space -- this can happen if the user changes the thread of a
@@ -15007,6 +15380,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 +15707,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,
+ &source_tracking_breakpoints, _("\
+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."),
+ NULL,
+ NULL,
+ &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,6 +35,7 @@
#include "gdbsupport/safe-iterator.h"
#include "cli/cli-script.h"
#include "target/waitstatus.h"
+#include "gdb_bfd.h"
struct block;
struct gdbpy_breakpoint_object;
@@ -82,6 +84,17 @@ enum remove_bp_reason
architecture. */
#define BREAKPOINT_MAX 16
+
+/* 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
/* Type of breakpoint. */
@@ -614,6 +627,28 @@ extern bool target_exact_watchpoints;
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>;
+/* 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). */
+ int bp_line_stored = 0;
+
+ /* BFD when source was captured. */
+ gdb_bfd_ref_ptr source_bfd;
+};
/* 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
@@ -863,6 +898,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 +1023,14 @@ 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. */
@@ -4659,6 +4659,17 @@ program.
On some systems, you can set breakpoints in shared libraries before
the executable is run.
+@cindex source-tracking breakpoints
+@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 the
+surrounding source code lines. If the source file is modified and the
+executable is rebuilt, @value{GDBN} attempts to relocate the breakpoint
+by searching for the captured source code.
+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
@@ -41980,6 +41991,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{Set Breaks}), 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.
@@ -42848,6 +42866,19 @@ 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 the surrounding source code lines when creating a
+file:line breakpoint. When the executable is reloaded, @value{GDBN}
+attempts to relocate the breakpoint by searching for the captured source
+code, allowing breakpoints to automatically adjust their location when source
+files are modified between compilations. The default is @code{off}.
+Breakpoints set by function name or address are not affected by this setting.
+
@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 1992-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 2025 Free Software Foundation, Inc.
+
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <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 -1
+}
+
+# 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 - 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
+# (should not show "source-tracking enabled" message)
+set test "breakpoint not tracked when source missing"
+gdb_test_multiple "info breakpoints" $test {
+ -re "source-tracking enabled.*$gdb_prompt $" {
+ fail $test
+ }
+ -re "breakpoint.*${build_srcfile}:${bp_line}.*$gdb_prompt $" {
+ pass $test
+ }
+}
new file mode 100644
@@ -0,0 +1,40 @@
+/* This testcase is part of GDB, the GNU debugger.
+
+ Copyright 1992-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,118 @@
+# Copyright 2025 Free Software Foundation, Inc.
+
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <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"
+
+# part 1) move the breakpoint 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 "r" "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"
+
+# part 2) move the breakpoint 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 "r" "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"
+
+# part 3) 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 "r" "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"
+
+# part 4) source tracking disabled - breakpoint should not adjust
+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 "r" "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"
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 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 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,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 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,77 @@
+# 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/>.
+
+standard_testfile .c -1.c -2.c
+
+set build_srcfile [standard_output_file $srcfile]
+
+remote_exec build "cp $srcdir/$subdir/$srcfile2 $build_srcfile"
+if { [prepare_for_testing "failed to prepare" $testfile $build_srcfile {debug nowarnings}] } {
+ 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."]
+
+# The breakpoint is on an inline function called from two places,
+# so it resolves to 2 locations.
+gdb_test "break $srcfile:$lineno" \
+ "^Breakpoint $decimal at $hex: $srcfile:$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".
+set test "multi-location breakpoint not tracked"
+gdb_test_multiple "info breakpoints" $test {
+ -re "source-tracking enabled.*$gdb_prompt $" {
+ fail "$test (tracking incorrectly enabled)"
+ }
+ -re "<MULTIPLE>.*$gdb_prompt $" {
+ pass $test
+ }
+}
+
+sleep 1
+remote_exec build "cp $srcdir/$subdir/$srcfile3 $build_srcfile"
+if { [build_executable "failed to build" $testfile $build_srcfile {debug nowarnings}] } {
+ 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.
+set test "multi-location breakpoint still not tracked after reload"
+gdb_test_multiple "info breakpoints" $test {
+ -re "source-tracking enabled.*$gdb_prompt $" {
+ fail "$test (tracking incorrectly enabled)"
+ }
+ -re "<MULTIPLE>.*$gdb_prompt $" {
+ pass $test
+ }
+}
+
+gdb_test "continue"