From: "Alexandra Hájková" <ahajkova@redhat.com>
To: gdb-patches@sourceware.org
Cc: ahajkova@redhat.com
Subject: [PATCH v8] gdb: Add source-tracking breakpoints feature
Date: Tue, 14 Jul 2026 11:40:52 +0200 [thread overview]
Message-ID: <20260714094241.178097-1-ahajkova@redhat.com> (raw)
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.
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.
+
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
@@ -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,
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"
-
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. */
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.
+
@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
diff --git a/gdb/testsuite/gdb.base/adjust_breakpoint-2.cpp b/gdb/testsuite/gdb.base/adjust_breakpoint-2.cpp
new file mode 100644
index 00000000000..0fb29c4ddac
--- /dev/null
+++ b/gdb/testsuite/gdb.base/adjust_breakpoint-2.cpp
@@ -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);
+}
diff --git a/gdb/testsuite/gdb.base/adjust_breakpoint-3.cpp b/gdb/testsuite/gdb.base/adjust_breakpoint-3.cpp
new file mode 100644
index 00000000000..cca12c5e170
--- /dev/null
+++ b/gdb/testsuite/gdb.base/adjust_breakpoint-3.cpp
@@ -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);
+}
diff --git a/gdb/testsuite/gdb.base/adjust_breakpoint-4.cpp b/gdb/testsuite/gdb.base/adjust_breakpoint-4.cpp
new file mode 100644
index 00000000000..24daadae1fe
--- /dev/null
+++ b/gdb/testsuite/gdb.base/adjust_breakpoint-4.cpp
@@ -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);
+}
diff --git a/gdb/testsuite/gdb.base/adjust_breakpoint-missing-source.exp b/gdb/testsuite/gdb.base/adjust_breakpoint-missing-source.exp
new file mode 100644
index 00000000000..5490ed40f78
--- /dev/null
+++ b/gdb/testsuite/gdb.base/adjust_breakpoint-missing-source.exp
@@ -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
+ }
+ }
diff --git a/gdb/testsuite/gdb.base/adjust_breakpoint.cpp b/gdb/testsuite/gdb.base/adjust_breakpoint.cpp
new file mode 100644
index 00000000000..4d42adb3011
--- /dev/null
+++ b/gdb/testsuite/gdb.base/adjust_breakpoint.cpp
@@ -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);
+}
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
+# 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
+ }
+}
+
diff --git a/gdb/testsuite/gdb.base/source-tracking-inline-1.c b/gdb/testsuite/gdb.base/source-tracking-inline-1.c
new file mode 100644
index 00000000000..aad234ffbcc
--- /dev/null
+++ b/gdb/testsuite/gdb.base/source-tracking-inline-1.c
@@ -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;
+}
diff --git a/gdb/testsuite/gdb.base/source-tracking-inline-2.c b/gdb/testsuite/gdb.base/source-tracking-inline-2.c
new file mode 100644
index 00000000000..ab95a312c6a
--- /dev/null
+++ b/gdb/testsuite/gdb.base/source-tracking-inline-2.c
@@ -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;
+}
diff --git a/gdb/testsuite/gdb.base/source-tracking-inline.exp b/gdb/testsuite/gdb.base/source-tracking-inline.exp
new file mode 100644
index 00000000000..96849dd1bb6
--- /dev/null
+++ b/gdb/testsuite/gdb.base/source-tracking-inline.exp
@@ -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
+ }
+}
diff --git a/gdb/testsuite/gdb.base/test_source_tracking.c b/gdb/testsuite/gdb.base/test_source_tracking.c
new file mode 100644
index 00000000000..46a682b56c5
--- /dev/null
+++ b/gdb/testsuite/gdb.base/test_source_tracking.c
@@ -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. */ }
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
+ }
+}
+
--
2.52.0
next reply other threads:[~2026-07-14 9:57 UTC|newest]
Thread overview: 4+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-07-14 9:40 Alexandra Hájková [this message]
2026-07-14 11:23 ` Andrew Burgess
2026-07-14 12:22 ` Eli Zaretskii
2026-07-18 3:31 ` Kevin Buettner
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=20260714094241.178097-1-ahajkova@redhat.com \
--to=ahajkova@redhat.com \
--cc=gdb-patches@sourceware.org \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox