From: Kevin Buettner <kevinb@redhat.com>
To: gdb-patches@sourceware.org
Cc: "Alexandra Hájková" <ahajkova@redhat.com>
Subject: Re: [PATCH v7] gdb: Add source-tracking breakpoints feature
Date: Sat, 4 Jul 2026 19:59:24 -0700 [thread overview]
Message-ID: <20260704195924.7e97257d@f44-mesa-1> (raw)
In-Reply-To: <20260613201437.18964-1-ahajkova@redhat.com>
Hi Sasha,
See my review, below. I've trimmed away much of the patch, leaving
ample context for my remarks.
On Sat, 13 Jun 2026 21:57:12 +0200
Alexandra Hajkova <ahajkova@redhat.com> wrote:
[...]
> +/* 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;
I think you might have a fencepost (off-by-one) error here. I think
that lines_to_capture should actually be:
lines_to_capture = (int) offsets->size () - start_line + 1;
The example that my AI helper came up with is:
Breakpoint is at line 3 of a 3-line file.
num_of_lines is 3, so start_line = 3 - (3/2) = 2.
start_line + lines_to_capture = 2 + 3 = 5, which is greater than
offsets->size() (which is 3).
So lines_to_capture is clipped to 3 - 2 = 1.
The loop runs only for j = 0. It captures line start_line + 0 = 2.
The check if (start_line + j == sals[0].line) becomes if (2 == 3),
which is false.
Thus, result.bp_line_stored is never set and remains 0.
Before fixing this, you might write a test case which demonstrates
this failure. I.e. it should FAIL with the current code, but then
PASS once the off-by-one problems is addressed.
> +
> + /* Get the BFD from the symtab. */
> + if (sals[0].symtab->compunit ()->objfile ())
> + result.source_bfd = sals[0].symtab->compunit ()->objfile ()->obfd;
Here's the first case of API drift that my AI helper found.
symtab::compunit() now returns a 'compunit_symtab &', a reference,
instead of a pointer. You should use the dot operator '.' instead
of the arrow operator '->' when calling objfile() on it. I.e.:
if (sals[0].symtab->compunit ().objfile ())
result.source_bfd = sals[0].symtab->compunit ().objfile ()->obfd;
[...]
> +/* 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;
There's been some API drift since you posted this patch:
symtab::compunit() now returns a 'compunit_symtab &' (reference)
instead of a pointer. Because it returns a reference, taking its
address and checking for 'nullptr' is dead code and will likely
trigger compiler warnings (e.g., -Waddress).
You can adjust it like this to use a reference instead:
struct compunit_symtab &cust = expanded[0].symtab->compunit ();
if (cust.objfile () == nullptr)
return;
> +
> + bfd *current_bfd = cust->objfile ()->obfd.get ();
> + if (bp_source->source_bfd.get () == current_bfd)
> + return;
> +
> + /* BFD changed ___ executable was reloaded. */
> + if (expanded.size () != 1)
> + {
> + warning (_("Breakpoint %d now has multiple locations after reload, "
> + "disabling source tracking."), number);
> + bp_source.reset ();
> + return;
> + }
> +
> + /* If this fails then the location spec has changed since the
> + breakpoint's source tracking was initially setup. */
> + gdb_assert (breakpoint_locspec_suitable_for_tracking (locspec.get ()));
> +
> + std::string line;
> + auto restore_styling = make_scoped_restore (&source_styling, false);
> + if (!g_source_cache.get_source_lines (expanded[0].symtab,
> + expanded[0].line,
> + expanded[0].line, &line))
> + {
> + /* Source is unreadable after reload ___ drop tracking. */
> + bp_source.reset ();
> + return;
> + }
> +
> + if (line == bp_source->source_lines[bp_source->bp_line_stored])
> + {
> + /* Line unchanged ___ just refresh the capture with the new BFD. */
> + bp_source = std::make_unique<breakpoint_source>
> + (breakpoint_source_capture (expanded, BREAKPOINT_SRC_CTX_LINES));
> + return;
> + }
> +
> + breakpoint_source tmp_source
> + = breakpoint_source_capture (expanded,
> + BREAKPOINT_SRC_CTX_LINES
> + * BREAKPOINT_SRC_SEARCH_MULTIPLIER);
> + int new_bp_line = sliding_window_match (bp_source.get (), &tmp_source);
> + if (new_bp_line == -1)
> + {
> + warning (_("Breakpoint %d source code not found "
> + "after reload, keeping original location."), number);
> + bp_source.reset ();
> + return;
> + }
> +
> + auto *explicit_loc = as_explicit_location_spec (locspec.get ());
> + location_spec_up new_locspec = explicit_loc->clone ();
> + auto *new_explicit = as_explicit_location_spec (new_locspec.get ());
> + new_explicit->line_offset.offset = new_bp_line;
> + new_explicit->line_offset.sign = LINE_OFFSET_NONE;
> + new_explicit->set_string (""); // invalidate the cached display string
Nit: I think that we still generally prefer C-style comments. That
said, after looking at the GDB C/C++ coding standard, I don't see a
prohibition against C++ style comments. But, a search of the GDB
sources show very few uses of //, so it seems to me that /* */ is
still preferred.
> + locspec = std::move (new_locspec);
You are setting locspec here (above)...
> +
> + location_spec *spec = locspec.get ();
> +
> + int found;
> + expanded = location_spec_to_sals (spec, filter_pspace, &found);
> + if (!found)
> + {
...but then, here, if the spec isn't found, the locspec member variable
retains the new_locspec setting. I recommend moving that assignment
to locspec after the end of this block. (Some other slight adjustments
are needed too.)
> + warning (_("Breakpoint %d adjusted to line %d but location could not "
> + "be resolved; keeping original location."), number, new_bp_line);
> + bp_source.reset ();
> + return;
> + }
> + if (new_bp_line != bp_source->bp_line)
> + {
> + gdb_printf (_("Breakpoint %d adjusted from line %d to line %d.\n"),
> + number, bp_source->bp_line, new_bp_line);
> + notify_breakpoint_modified (this);
> + }
> +
> + bp_source = std::make_unique<breakpoint_source>
> + (breakpoint_source_capture (expanded, BREAKPOINT_SRC_CTX_LINES));
> +}
[...]
> diff --git a/gdb/breakpoint.h b/gdb/breakpoint.h
> index 7149211a055..4a7952b042a 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). */
> + size_t bp_line_stored = 0;
> +
> + /* BFD when source was captured. */
> + gdb_bfd_ref_ptr source_bfd;
> +};
> +
Consider whether this struct might be moved to gdb/breakpoint.c. If
so, you'd replace it with a forward declaration:
struct breakpoint_source;
With that done, you'd no longer need the #include of gdb_bfd.h, earlier.
That #include would then be moved to breakpoint.c.
> /* 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. */
prev parent reply other threads:[~2026-07-05 3:00 UTC|newest]
Thread overview: 2+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-06-13 19:57 Alexandra Hájková
2026-07-05 2:59 ` Kevin Buettner [this message]
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=20260704195924.7e97257d@f44-mesa-1 \
--to=kevinb@redhat.com \
--cc=ahajkova@redhat.com \
--cc=gdb-patches@sourceware.org \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox