[v6] gdb: Add source-tracking breakpoints feature
Checks
| Context |
Check |
Description |
| linaro-tcwg-bot/tcwg_gdb_build--master-aarch64 |
success
|
Build passed
|
| linaro-tcwg-bot/tcwg_gdb_build--master-arm |
success
|
Build passed
|
| linaro-tcwg-bot/tcwg_gdb_check--master-arm |
success
|
Test passed
|
| linaro-tcwg-bot/tcwg_gdb_check--master-aarch64 |
fail
|
Test failed
|
Commit Message
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
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>
---
V6:
- sliding_window_match: changed bp_stored from int to size_t to fix
signed/unsigned comparison with bp_size.
- sliding_window_match: fixed typo in comment
- adjust_bp_for_source_tracking: replaced the rfind-based locspec
string manipulation with clone() + direct update of
explicit_location_spec::line_offset
- adjust_bp_for_source_tracking: added handling for the !found case
after location_spec_to_sals
gdb/NEWS | 14 +
gdb/breakpoint.c | 472 ++++++++++++++++++
gdb/breakpoint.h | 38 ++
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 | 143 ++++++
.../gdb.base/source-tracking-inline-1.c | 50 ++
.../gdb.base/source-tracking-inline-2.c | 49 ++
.../gdb.base/source-tracking-inline.exp | 80 +++
13 files changed, 1103 insertions(+)
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
Comments
Alexandra Hájková <ahajkova@redhat.com> writes:
> When we rerun the executable after changing its source files,
> GDB would re-set all previously set breakpoints. The
> breakpoints set to the function names would remain at their initial
> locations. But the breakpoints which used filename:line notation would
> be silently shifted following the source code changes.
>
> To address this, GDB now optionally captures a small window of source
> code lines around each breakpoint set with filename:line notation,
> when it is first set. When the binary is reloaded, GDB detects the BFD
> change and tries to locate the same source context in the new file,
> and if successful, re-sets the breakpoint to the matched source code
> line.
>
> The breakpoint_source structure stores captured source code lines
> around a breakpoint location, along with a reference to the BFD
> that was current when the source was captured.
>
> When source tracking is enabled (via 'set breakpoint source-tracking
> enabled on'), GDB captures 3 lines of source context
> (BREAKPOINT_SRC_CTX_LINES) along with the current BFD when a
> breakpoint is first set. On executable reload (detected by comparing
> BFDs), it searches within a 12-line window
> (BREAKPOINT_SRC_CTX_LINES * BREAKPOINT_SRC_SEARCH_MULTIPLIER) for
> the best match and adjusts the breakpoint location if needed.
>
> If source tracking is disabled after breakpoints have been tracked,
> all existing source tracking information is discarded and a message
> is printed.
>
> Tests added:
> gdb.base/adjust_breakpoint.exp
> gdb.base/adjust_breakpoint-missing-source.exp
> gdb.base/source-tracking-inline.exp
>
> 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>
> ---
> V6:
> - sliding_window_match: changed bp_stored from int to size_t to fix
> signed/unsigned comparison with bp_size.
> - sliding_window_match: fixed typo in comment
> - adjust_bp_for_source_tracking: replaced the rfind-based locspec
> string manipulation with clone() + direct update of
> explicit_location_spec::line_offset
> - adjust_bp_for_source_tracking: added handling for the !found case
> after location_spec_to_sals
>
> gdb/NEWS | 14 +
> gdb/breakpoint.c | 472 ++++++++++++++++++
> gdb/breakpoint.h | 38 ++
> 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 | 143 ++++++
> .../gdb.base/source-tracking-inline-1.c | 50 ++
> .../gdb.base/source-tracking-inline-2.c | 49 ++
> .../gdb.base/source-tracking-inline.exp | 80 +++
> 13 files changed, 1103 insertions(+)
> 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
>
> diff --git a/gdb/NEWS b/gdb/NEWS
> index 7c8cf9af4c2..2bfcead6ce5 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.
>
> @@ -104,6 +111,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 101dc57ee6b..017412d53a4 100644
> --- a/gdb/breakpoint.c
> +++ b/gdb/breakpoint.c
> @@ -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
> @@ -165,6 +166,19 @@ static bool bl_address_is_meaningful (const bp_location *loc);
>
> static int find_loc_num_by_location (const bp_location *loc);
>
> +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 +596,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 is %s.\n"), value);
This doesn't read quite right. I think this should be either:
Source tracking breakpoints are %s.\n
or
Source tracking for breakpoint is %s.\n
whichever you prefer.
> +}
> +
> +/* 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");
The text here should be wrapped in `_()`.
> +}
> +
> /* See breakpoint.h. */
> bool debug_breakpoint = false;
>
> @@ -593,6 +661,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;
Instead of comparing the size to zero, you can write:
return !src->source_lines.empty () && src->bp_line > 0;
which seems to be more common in GDB.
> +}
> +
> +/* 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++)
It would be better to make `j` std::size_t rather than casting to `int`
in the comparison. But I guess you will still need to perform a cast
when you compute the line_num later as our line numbers are `int` within
GDB.
> + {
> + 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')
This should be `nullptr` not `NULL` here.
> + 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 +1079,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. */
> @@ -6834,6 +7077,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)
> @@ -8943,7 +9196,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 ()));
This also needs `breakpoint_locspec_suitable_for_tracking
(b->locspec.get ())` adding as a condition. To see why try this
session:
Reading symbols from /tmp/hello.x...
(gdb) set breakpoint source-tracking enabled on
(gdb) b 19
Breakpoint 1 at 0x4011a2: file hello.c, line 19.
(gdb) r
Starting program: /tmp/hello.x
Hello World
Breakpoint 1, main () at hello.c:19
19 call_me ( 0, 1, 2, 3, 4, 5, 6, 7 );
(gdb) b +1
Breakpoint 2 at 0x4011cf: file hello.c, line 20.
warning: Source file not available; breakpoint will not be source-tracked.
(gdb)
Notice the 'warning: ...' line, that's clearly incorrect as the source
file is available. The problem is that
`breakpoint_locspec_suitable_for_tracking` only allows absolute line
numbers, not relative line numbers as was used in this case.
Now we could potentially extend support for relative line numbers,
there's no reason why that couldn't work. But I can understand wanting
to keep things simple at this stage.
I'm tempted to suggest that warn_not_tracked warning should be moved
into the preceding `if` block, like this:
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."));
}
But I think this also indicates we should probably have a test for
creating a breakpoint via a relative line number. I don't think it's
necessary for these to be source tracked (at this time), but we should
be checking that there's no warnings printed in this case.
> 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
> @@ -13200,6 +13481,162 @@ 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. */
> + int bp_stored = bp_source->bp_line_stored;
If bp_line_stored is changed to std::size_t then the type of bp_stored
should be updated to match.
> + 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 ("");
You can probably drop the `("")` as the default std::string is empty.
> +
> + /* 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 =
GDB style places the `=` on the next line.
> + (bp_stored == 0
> + ? empty_string : bp_source->source_lines[bp_stored - 1]);
> + const std::string &bp_next =
Again with the misplaced `=`.
> + ((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 =
And two more misplaced `=` here.
> + ((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 bee found in TMP_SOURCE. */
Typo: "... has not BEEN found in ..."
> + 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;
The `bp_stored` here is only used in one place, further down the
function and `bp_source` is not changed between here and where
`bp_stored` is used.
I think you could inline `bp_source->bp_line_stored` where
`bp_stored` is currently used, the line length would still be fine. But
if you really like using the local `bp_stored`, then you should move
this to just before it is being used.
> + 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. */
> @@ -13230,12 +13667,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
> @@ -15024,6 +15466,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,
> @@ -15342,6 +15793,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 3e84f1c99ab..edab3704f98 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,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;
> @@ -615,6 +617,30 @@ 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;
If this is an index into source_lines, then it would be better made a
std::size_t.
> +
> + /* 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
> does set it to 0). I implemented it because I thought it would be
> @@ -863,6 +889,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 +1014,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 9de2c7a299e..8d7e0fc652c 100644
> --- a/gdb/doc/gdb.texinfo
> +++ b/gdb/doc/gdb.texinfo
> @@ -4659,6 +4659,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 at least one
> +of its immediate neighbors also matches, reducing false positives from
> +short or repeated lines. @value{GDBN} uses the first such confirmed
The "... at least one of its immediate neighbors also matches, ..." bit
is true when the breakpoint is placed at the very start, or very end of
a file, but in the general case don't you require that both neighbours
match?
I don't think every single edge case needs to be documented in this
description, the specific matching algorithm will be subject to change
over time, but if we want to say anything then I'd document the general
case, that the breakpoint line and both its neighbours need to match.
> +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
> @@ -41991,6 +42013,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.
> @@ -42859,6 +42888,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..5c8ddb8b64e
> --- /dev/null
> +++ b/gdb/testsuite/gdb.base/adjust_breakpoint.exp
> @@ -0,0 +1,143 @@
> +# 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 happends when the breakpoint line disappears.
Typo: "Test what HAPPENS when ..."
> +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"
> 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..cbc1ee993de
> --- /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 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..724bb78845a
> --- /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 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..ab9d2276b4e
> --- /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 {debug nowarnings}] } {
> + return
> +}
Is the 'nowarnings' needed here? If so why. If not, then you should
drop it, then you're left with just {debug}, which is the default, so
you should remove that too.
> +
> +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".
> +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
> + }
Instead of setting up a 'test' variable, you can place the test name on
the gdb_test_multiple line, then use $gdb_test_name within the
gdb_test_multiple. This magic variable is set to the name of the test.
Also you can use '-wrap' to drop the $gdb_prompt part, so the test
becomes:
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 {debug nowarnings}] } {
> + return
> +}
Same question as before about nowarnings.
> +
> +# 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
> + }
You can use the same tricks to clean up this gdb_test_multiple too.
> +}
> --
> 2.52.0
Thanks,
Andrew
@@ -6,6 +6,13 @@
* Support for the Common Trace Format (CTF) has been removed. GDB now
saves trace information exclusively in its own "tfile" format.
+* GDB now supports source-tracking breakpoints, which automatically
+ adjust their location when source code changes between rebuilds.
+ When enabled, file and line breakpoints capture the surrounding
+ source code context and use it to adjust the breakpoint line if the
+ source is modified. Source tracking can be enabled with 'set
+ breakpoint source-tracking enabled on'.
+
* Support for .gdb_index sections with version less than 7 has been
removed.
@@ -104,6 +111,13 @@ unset local-environment
environment. The local environment is used by "shell", "pipe", and
other commands that launch a subprocess other than an inferior.
+set breakpoint source-tracking enabled [on|off]
+show breakpoint source-tracking enabled
+ Enable or disable source-tracking for file and line breakpoints.
+ When enabled, breakpoints capture surrounding source code and
+ automatically adjust their location when the source changes between
+ recompilations.
+
save history FILENAME
Save the command history to the given file.
@@ -69,6 +69,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
@@ -165,6 +166,19 @@ static bool bl_address_is_meaningful (const bp_location *loc);
static int find_loc_num_by_location (const bp_location *loc);
+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 +596,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 is %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 +661,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 +1079,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. */
@@ -6834,6 +7077,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)
@@ -8943,7 +9196,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
@@ -13200,6 +13481,162 @@ 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. */
+ int 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 bee 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 == 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. */
@@ -13230,12 +13667,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
@@ -15024,6 +15466,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,
@@ -15342,6 +15793,27 @@ Usage: agent-printf \"format string\", ARG1, ARG2, ARG3, ..., ARGN\n\
This supports most C printf format specifications, like %s, %d, etc.\n\
This is useful for formatted output in user-defined commands."));
+ add_setshow_prefix_cmd ("source-tracking", class_breakpoint,
+ _("\
+Source tracking breakpoint specific settings."),
+ _("\
+Source tracking breakpoint specific settings."),
+ &source_tracking_bp_set_cmdlist,
+ &source_tracking_bp_show_cmdlist,
+ &breakpoint_set_cmdlist, &breakpoint_show_cmdlist);
+
+ add_setshow_boolean_cmd ("enabled", class_breakpoint, _("\
+Set whether file and line breakpoints use source tracking."), _("\
+Show whether file and line breakpoints use source tracking."), _("\
+When on, breakpoints set with file:line syntax will track the source\n\
+location and automatically adjust when the source changes and the\n\
+inferior is restarted."),
+ set_breakpoint_source_tracking_enabled,
+ get_breakpoint_source_tracking_enabled,
+ show_source_tracking_breakpoints,
+ &source_tracking_bp_set_cmdlist,
+ &source_tracking_bp_show_cmdlist);
+
automatic_hardware_breakpoints = true;
gdb::observers::about_to_proceed.attach (breakpoint_about_to_proceed,
@@ -27,6 +27,7 @@
#include "probe.h"
#include "location.h"
#include <vector>
+#include <memory>
#include "gdbsupport/array-view.h"
#include "gdbsupport/filtered-iterator.h"
#include "gdbsupport/iterator-range.h"
@@ -34,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;
@@ -615,6 +617,30 @@ 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
does set it to 0). I implemented it because I thought it would be
@@ -863,6 +889,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 +1014,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. */
@@ -4659,6 +4659,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 at least one
+of its immediate neighbors also matches, 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
@@ -41991,6 +42013,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.
@@ -42859,6 +42888,22 @@ Control whether to show all non zero areas within a 1k block starting
at thread local base, when using the @samp{info w32 thread-information-block}
command.
+@kindex set breakpoint source-tracking enabled
+@kindex show breakpoint source-tracking enabled
+@item set breakpoint source-tracking enabled @r{[}on@r{|}off@r{]}
+@itemx show breakpoint source-tracking enabled
+Control whether to enable source-tracking for breakpoints set by file and
+line number. Use @code{on} to enable, @code{off} to disable. When
+enabled, @value{GDBN} captures a window of source lines around each
+new file:line breakpoint and uses it to relocate the breakpoint if the
+source is modified and the executable is rebuilt. @xref{Breakpoints},
+for a full description of the matching algorithm and its limitations.
+The default is @code{off}. Breakpoints set by function name or address
+are not affected by this setting.
+
+If this setting is changed from @code{on} to @code{off}, then any
+existing source tracking information will be discarded.
+
@kindex maint set target-async
@kindex maint show target-async
@item maint set target-async
new file mode 100644
@@ -0,0 +1,39 @@
+/* This testcase is part of GDB, the GNU debugger.
+
+ Copyright 2026 Free Software Foundation, Inc.
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>. */
+
+int
+foo (void)
+{
+ return 0;
+}
+
+int
+foo (int var)
+{
+ int i = 8;
+ var += 10;
+ var += i;
+
+ return var;
+}
+
+int
+main (void)
+{
+ foo ();
+ return foo (2);
+}
new file mode 100644
@@ -0,0 +1,41 @@
+/* This testcase is part of GDB, the GNU debugger.
+
+ Copyright 2026 Free Software Foundation, Inc.
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>. */
+
+
+int
+foo () {
+ return 0;
+}
+
+int
+foo (int var)
+{
+ int j = 0;
+ int i = 8;
+
+ var += 10;
+ var += i;
+
+ return var;
+}
+
+int
+main (void)
+{
+ foo ();
+ return foo (2);
+}
new file mode 100644
@@ -0,0 +1,37 @@
+/* This testcase is part of GDB, the GNU debugger.
+
+ Copyright 2026 Free Software Foundation, Inc.
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>. */
+
+int
+foo () {
+ return 0;
+}
+
+int
+foo (int var)
+{
+ int i = 8;
+
+
+ return var;
+}
+
+int
+main (void)
+{
+ foo ();
+ return foo (2);
+}
new file mode 100644
@@ -0,0 +1,55 @@
+# Copyright 2026 Free Software Foundation, Inc.
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+# Test that source-tracking breakpoints handle missing source files correctly.
+# When source tracking is enabled but the source file cannot be found, GDB
+# should create a non-tracking breakpoint and not crash or fail.
+
+standard_testfile adjust_breakpoint.cpp
+set build_srcfile xxx-${testfile}.cpp
+
+set new_source_file [standard_output_file ${build_srcfile}]
+remote_exec build "cp ${srcdir}/${subdir}/adjust_breakpoint.cpp $new_source_file"
+if { [prepare_for_testing "failed to prepare" $testfile $new_source_file] } {
+ return
+}
+
+# Enable source tracking.
+gdb_test_no_output "set breakpoint source-tracking enabled on" \
+ "enable source tracking breakpoints"
+
+# Now remove the source file before setting the breakpoint.
+set bp_line [gdb_get_line_number "return 0;" $new_source_file]
+remote_exec build "rm -f $new_source_file"
+
+# Verify the source is not available.
+gdb_test "list" ".*No such file or directory.*" \
+ "verify source file is not available"
+
+# Try to set a breakpoint, this should succeed but not be tracked.
+gdb_test "break ${build_srcfile}:${bp_line}" "Breakpoint.*at.*" \
+ "create breakpoint when source file missing"
+
+# Check that the breakpoint was created but is not tracked,
+# it should not show the "source-tracking enabled" message.
+gdb_test_multiple "info breakpoints" \
+ "breakpoint not tracked when source missing" {
+ -re -wrap "source-tracking enabled.*" {
+ fail $gdb_test_name
+ }
+ -re -wrap "breakpoint.*${build_srcfile}:${bp_line}.*" {
+ pass $gdb_test_name
+ }
+ }
new file mode 100644
@@ -0,0 +1,40 @@
+/* This testcase is part of GDB, the GNU debugger.
+
+ Copyright 2026 Free Software Foundation, Inc.
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>. */
+
+int
+foo (void)
+{
+ return 0;
+}
+
+int
+foo (int var)
+{
+ int i = 8;
+
+ var += 10;
+ var += i;
+
+ return var;
+}
+
+int
+main (void)
+{
+ foo ();
+ return foo (2);
+}
new file mode 100644
@@ -0,0 +1,143 @@
+# 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 happends 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"
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,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 {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." $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".
+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/$srcfile2 $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
+ }
+}