From mboxrd@z Thu Jan 1 00:00:00 1970 Return-Path: Received: from simark.ca by simark.ca with LMTP id 4XCJERdqGWqBsyYAWB0awg (envelope-from ) for ; Fri, 29 May 2026 06:27:35 -0400 Authentication-Results: simark.ca; dkim=pass (1024-bit key; unprotected) header.d=redhat.com header.i=@redhat.com header.a=rsa-sha256 header.s=mimecast20190719 header.b=AgdPMF9S; dkim-atps=neutral Received: by simark.ca (Postfix, from userid 112) id 4038C1E062; Fri, 29 May 2026 06:27:35 -0400 (EDT) X-Spam-Checker-Version: SpamAssassin 4.0.1 (2024-03-25) on simark.ca X-Spam-Level: X-Spam-Status: No, score=-3.4 required=5.0 tests=ARC_SIGNED,ARC_VALID,BAYES_00, DKIMWL_WL_HIGH,DKIM_SIGNED,DKIM_VALID,DKIM_VALID_AU,MAILING_LIST_MULTI, RCVD_IN_DNSWL_MED,RCVD_IN_VALIDITY_CERTIFIED_BLOCKED, RCVD_IN_VALIDITY_RPBL_BLOCKED,RCVD_IN_VALIDITY_SAFE_BLOCKED autolearn=ham autolearn_force=no version=4.0.1 Received: from vm01.sourceware.org (vm01.sourceware.org [38.145.34.32]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange x25519 server-signature ECDSA (prime256v1) server-digest SHA256) (No client certificate requested) by simark.ca (Postfix) with ESMTPS id 6B78B1E062 for ; Fri, 29 May 2026 06:27:32 -0400 (EDT) Received: from vm01.sourceware.org (localhost [IPv6:::1]) by sourceware.org (Postfix) with ESMTP id 6B9C04BA2E2F for ; Fri, 29 May 2026 10:27:31 +0000 (GMT) DKIM-Filter: OpenDKIM Filter v2.11.0 sourceware.org 6B9C04BA2E2F Authentication-Results: sourceware.org; dkim=pass (1024-bit key, unprotected) header.d=redhat.com header.i=@redhat.com header.a=rsa-sha256 header.s=mimecast20190719 header.b=AgdPMF9S Received: from us-smtp-delivery-124.mimecast.com (us-smtp-delivery-124.mimecast.com [170.10.129.124]) by sourceware.org (Postfix) with ESMTP id 540664BA2E25 for ; Fri, 29 May 2026 10:26:53 +0000 (GMT) DMARC-Filter: OpenDMARC Filter v1.4.2 sourceware.org 540664BA2E25 Authentication-Results: sourceware.org; dmarc=pass (p=quarantine dis=none) header.from=redhat.com Authentication-Results: sourceware.org; spf=pass smtp.mailfrom=redhat.com ARC-Filter: OpenARC Filter v1.0.0 sourceware.org 540664BA2E25 Authentication-Results: sourceware.org; arc=none smtp.remote-ip=170.10.129.124 ARC-Seal: i=1; a=rsa-sha256; d=sourceware.org; s=key; t=1780050413; cv=none; b=GnhQyOKZNKgixh4tiNr0ymDkkHWDQ6KVTGCxvhWI5pXw9aW36DW0rX6hxR2Q3TKuDeU/jLuW2tdXKepNPyvjJKvB2Ajum7tPdXhqmqmFmqVBtU4do1Z1Qn/8T9KnONxDPrgiCxIs8IWzWKDnlYiZUZ/Lafwaf3DFvNuIAea/Kic= ARC-Message-Signature: i=1; a=rsa-sha256; d=sourceware.org; s=key; t=1780050413; c=relaxed/simple; bh=xZFvr7RzfIzDLKafhDJm1nueiFbVWa0u0wW2j74YXck=; h=DKIM-Signature:From:To:Subject:Date:Message-ID:MIME-Version; b=v9NqTvoLJyaVcefgBEp+E2VnJTwhNqWRrY9t/jxmFLkjpAhhdMFI0qmFJmByKp3Z8t4fHRHjqbCQUZP8TyPNq6feMVM7J5jHl+QcI4BsEiePa7KeTJ/dNeE0U1Ccr+8XdoDGqnLSreiisSzcUapyHrxdqjc6o6Tj07TVolx+LEI= ARC-Authentication-Results: i=1; sourceware.org; dkim=pass (1024-bit key, unprotected) header.d=redhat.com header.i=@redhat.com header.a=rsa-sha256 header.s=mimecast20190719 header.b=AgdPMF9S DKIM-Filter: OpenDKIM Filter v2.11.0 sourceware.org 540664BA2E25 DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=redhat.com; s=mimecast20190719; t=1780050413; h=from:from:reply-to:subject:subject:date:date:message-id:message-id: to:to:cc:cc:mime-version:mime-version:content-type:content-type: content-transfer-encoding:content-transfer-encoding: in-reply-to:in-reply-to:references:references; bh=WeRowGc2Q/7xC+0vecG1toi9pjnfljMJyDiPeUPGlt4=; b=AgdPMF9Sryari13qOxGCS6hI5RLpb1Qo9cmwrWkQj85Qj7ovmdudiVvZisN1AV2Hq8b0Ox bjvGYL8DKotlhbaje6yYRcrm4e5nRGp0pj421iZYT2EKeJ8bGGMflVS3bwcFgpMALm3Ak4 xXN3rdP+s3vscYEX1CatLLV6FnUHyf4= Received: from mail-wm1-f72.google.com (mail-wm1-f72.google.com [209.85.128.72]) by relay.mimecast.com with ESMTP with STARTTLS (version=TLSv1.3, cipher=TLS_AES_256_GCM_SHA384) id us-mta-515-_azD7XR3MyG4LOXM-Y3EWg-1; Fri, 29 May 2026 06:26:51 -0400 X-MC-Unique: _azD7XR3MyG4LOXM-Y3EWg-1 X-Mimecast-MFC-AGG-ID: _azD7XR3MyG4LOXM-Y3EWg_1780050410 Received: by mail-wm1-f72.google.com with SMTP id 5b1f17b1804b1-49045f93baeso53449295e9.1 for ; Fri, 29 May 2026 03:26:51 -0700 (PDT) X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20251104; t=1780050410; x=1780655210; h=content-transfer-encoding:mime-version:message-id:date:references :in-reply-to:subject:cc:to:from:x-gm-gg:x-gm-message-state:from:to :cc:subject:date:message-id:reply-to; bh=r2j8S9L0EI/svobkTZ5Gx0yEzHcE2y9zjFzmERF0VN4=; b=aFxuWWuVF7/kYmjcYY4WM40Kdp+lkJzpdVls8ZkGnxtfkrZAJc6PjeqdsjzX4HaJvk eV1brUU4byKkeNn5UuW3ZJQB1tgvSnPqej7BVJZnNtANH/ZmkhBcn6hqvqUC8wtpWRvV MdZF48Nm530EnCqNdPHYEeceYqkyRgIT796MDP7ShArQpKyAhKjquy6Ced1AqNWPE+zc saJn2+AQQbvcispu+u/O7V8aYgSTB5kVK3j1TlmYZDeU0WjvstI9BlP/NRTY3sWCNUh9 cxPjVoVzILlRslwhbpKKWqDWcOTOm1/Chrvg6Eb2R202D1imh4f5k93v11Aky8ytOF9v nNSA== X-Forwarded-Encrypted: i=1; AFNElJ/4Q0Bg3w6bwQ5jsv//gs96He9lVyAzmFTZQQcV0XBtIYmKBhyhTLOQliv8DTEC5Y1t2f8CeNc2arACOw==@sourceware.org X-Gm-Message-State: AOJu0YxQEOpilivB4iReP2aeJNWH8W9eprREbgtEbEQgxLne54k7YcyV 2LqSpVzZrJubwREauSNDGx/7ivsNu6EsjK4kMbX0tPhjaSTuZQGnGJ4JXJfxqqF+JJWiq3NEgPa SLuPdNRZy5mnfK63LnKrGQj88fmU4gwwbHkykADt79ENd3Kw5HmCb/FFDvvpV8BFZ0MC95cw= X-Gm-Gg: Acq92OHwbUBasNXiG+WLUKtdN/BrNX049nLVXFsF0LKQT2hwI2vwxt2NQDhxaaqRVH3 DE0VJ5QduZVU0O7JvnTMtDvZZFXXJO31hR/PSJCOH092wJfxSFfyz9fHBGS3F79vkN9WKESjCqA Awb/Fts66Vfde/ETx0cA5YX44SRStwqZa+4bvJz3HbFbhmTHVn8WfBqS9Q1KM8VnfVj+TgqkUad PGbeUGBXB3R4iH0jX9iQwgZbSn8HFeR1IeLtHYiHBpD3BgFENizJeQ112Yzy+W3BxiHKpZiyEAi M2m2ZOyzLBzp0Q9uVJQMgQD8RQABGGK+DmGZuLoijSU7o7M7r869kRT23SGl0Kufb9XxdOVueCf dMEBucAeoQ0+1s7IAxJub3JU/6A== X-Received: by 2002:a05:600c:5486:b0:490:9d1b:f07f with SMTP id 5b1f17b1804b1-4909d1bf141mr36110365e9.12.1780050408736; Fri, 29 May 2026 03:26:48 -0700 (PDT) X-Received: by 2002:a05:600c:5486:b0:490:9d1b:f07f with SMTP id 5b1f17b1804b1-4909d1bf141mr36108535e9.12.1780050407169; Fri, 29 May 2026 03:26:47 -0700 (PDT) Received: from localhost ([213.31.44.43]) by smtp.gmail.com with ESMTPSA id 5b1f17b1804b1-4909c6319ddsm37594715e9.0.2026.05.29.03.26.46 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Fri, 29 May 2026 03:26:46 -0700 (PDT) From: Andrew Burgess To: Alexandra =?utf-8?B?SMOhamtvdsOh?= , gdb-patches@sourceware.org Cc: ahajkova@redhat.com Subject: Re: [PATCH v6] gdb: Add source-tracking breakpoints feature In-Reply-To: <20260528160644.315663-1-ahajkova@redhat.com> References: <20260528160644.315663-1-ahajkova@redhat.com> Date: Fri, 29 May 2026 11:26:45 +0100 Message-ID: <8733zazf2y.fsf@redhat.com> MIME-Version: 1.0 X-Mimecast-Spam-Score: 0 X-Mimecast-MFC-PROC-ID: QY1nUbXQEzaOnUOR5CrIwq6crxZ2cOMkimaxodcGpVY_1780050410 X-Mimecast-Originator: redhat.com Content-Type: text/plain; charset=utf-8 Content-Transfer-Encoding: quoted-printable X-BeenThere: gdb-patches@sourceware.org X-Mailman-Version: 2.1.30 Precedence: list List-Id: Gdb-patches mailing list List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: gdb-patches-bounces~public-inbox=simark.ca@sourceware.org Alexandra H=C3=A1jkov=C3=A1 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 breakpoin= ts. > > 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 > --- > V6:=20 > - 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-sour= ce.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. > =20 > +* 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. > =20 > @@ -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. > =20 > +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. > =20 > 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" > =20 > /* readline defines this. */ > #undef savestring > @@ -165,6 +166,19 @@ static bool bl_address_is_meaningful (const bp_locat= ion *loc); > =20 > static int find_loc_num_by_location (const bp_location *loc); > =20 > +static bool breakpoint_source_is_tracked (const breakpoint_source *src); > + > +/* Number of source lines to capture around a breakpoint for source trac= king. > + This context is used to match and relocate breakpoints when the execu= table > + is reloaded. The window is centered on the breakpoint line, capturin= g > + lines both before and after it. */ > +#define BREAKPOINT_SRC_CTX_LINES 3 > + > +/* Multiplier for the search window when looking for relocated breakpoin= ts. > + We search in (BREAKPOINT_SRC_CTX_LINES * BREAKPOINT_SRC_SEARCH_MULTIP= LIER) > + 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, > =09 value); > } > =20 > +/* When true file and line breakpoints are created as source-tracking > + breakpoints. */ > + > +static bool source_tracking_breakpoints =3D false; > + > +/* Implement 'show breakpoint source-tracking enabled'. */ > + > +static void > +show_source_tracking_breakpoints (struct ui_file *file, int from_tty, > +=09=09=09=09 struct cmd_list_element *c, > +=09=09=09=09 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 =3D 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 =3D false; > + for (struct breakpoint &b : all_breakpoints ()) > + { > + if (breakpoint_source_is_tracked (b.bp_source.get ())) > +=09{ > +=09 any_discarded =3D true; > +=09 b.bp_source.reset (); > +=09} > + } > + if (any_discarded) > + gdb_printf ("Discarding existing source tracking information.\n"); The text here should be wrapped in `_()`. > +} > + > /* See breakpoint.h. */ > bool debug_breakpoint =3D false; > =20 > @@ -593,6 +661,179 @@ show_debug_breakpoint (struct ui_file *file, int fr= om_tty, > gdb_printf (file, _("Breakpoint location debugging is %s.\n"), value); > } > =20 > +/* Return true if the breakpoint source is being tracked. */ > + > +static bool > +breakpoint_source_is_tracked (const breakpoint_source *src) > +{ > + if (src =3D=3D 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 additiona= l > + 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 () !=3D EXPLICIT_LOCATION_SPEC) > + return false; > + > + const explicit_location_spec *explicit_loc > + =3D as_explicit_location_spec (spec); > + > + if (explicit_loc->function_name.get () !=3D nullptr > + || explicit_loc->label_name.get () !=3D nullptr > + || explicit_loc->source_filename.get () =3D=3D nullptr) > + return false; > + > + if (explicit_loc->line_offset.sign !=3D LINE_OFFSET_NONE) > + return false; > + > + return explicit_loc->line_offset.offset > 0; > +} > + > +/* Print captured source lines to stdout, marking the breakpoint line wi= th '>'. */ > + > +static void > +breakpoint_source_print (const breakpoint_source *src) > +{ > + if (!breakpoint_source_is_tracked (src)) > + return; > + > + int start_line =3D breakpoint_source_get_start_line (src); > + for (int j =3D 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 =3D start_line + j; > + char prefix; > + if (j =3D=3D src->bp_line_stored) > +=09prefix =3D '>'; > + else > +=09prefix =3D ' '; > + gdb_printf ("%c %ps %s", prefix, > +=09=09 styled_string (line_number_style.style (), > +=09=09=09=09 pulongest (line_num)), > +=09=09 src->source_lines[j].c_str ()); > + if (src->source_lines[j].empty () > +=09 || src->source_lines[j].back () !=3D '\n') > +=09gdb_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 =3D=3D NULL || *args =3D=3D '\0') This should be `nullptr` not `NULL` here. > + error (_("Breakpoint number required.")); > + > + /* Parse the breakpoint number. */ > + const char *end =3D args; > + int num =3D get_number_trailer (&end, 0); > + > + if (num <=3D 0) > + error (_("Invalid breakpoint number '%s'."), args); > + > + /* Find the breakpoint. */ > + breakpoint *b =3D nullptr; > + for (breakpoint &bp : all_breakpoints ()) > + { > + if (bp.number =3D=3D num) > +=09{ > +=09 b =3D &bp; > +=09 break; > +=09} > + } > + > + if (b =3D=3D 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 sals, > +=09=09=09 int num_of_lines) > +{ > + /* Check if we have a valid symtab - if not, we can't capture source l= ines. > + The symtab can be missing if the executable wasn't compiled with > + debugging symbols. */ > + if (sals.empty () || sals[0].symtab =3D=3D nullptr || sals[0].line <= =3D 0) > + return {}; > + > + breakpoint_source result; > + result.bp_line =3D sals[0].line; > + > + /* Calculate the starting line, centering around the breakpoint line. > + Avoid going before line 1. */ > + int lines_to_capture =3D num_of_lines; > + int start_line =3D sals[0].line - (lines_to_capture / 2); > + if (start_line < 1) > + start_line =3D 1; > + > + /* Get line offsets to check file bounds. */ > + const std::vector *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 =3D (int) offsets->size () - start_line; > + > + /* Get the BFD from the symtab. */ > + if (sals[0].symtab->compunit ()->objfile ()) > + result.source_bfd =3D sals[0].symtab->compunit ()->objfile ()->obfd; > + > + /* Capture the source lines. */ > + auto restore_styling =3D make_scoped_restore (&source_styling, false); > + for (int j =3D 0; j < lines_to_capture; j++) > + { > + std::string line; > + if (!g_source_cache.get_source_lines (sals[0].symtab, start_line += j, > +=09=09=09=09=09 start_line + j, &line)) > +=09{ > +=09 /* Failed to read source - return empty structure so this > +=09 breakpoint won't be tracked. */ > +=09 warning (_("Failed to capture source lines for source tracking.")); > +=09 return {}; > +=09} > + result.source_lines.push_back (line); > + if (start_line + j =3D=3D sals[0].line) > +=09result.bp_line_stored =3D j; > + } > + > + return result; > +} > + > /* See breakpoint.h. */ > =20 > int > @@ -838,6 +1079,8 @@ static int tracepoint_count; > =20 > 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; > =20 > /* Return whether a breakpoint is an active enabled breakpoint. */ > @@ -6834,6 +7077,16 @@ print_one_breakpoint_location (struct breakpoint *= b, > uiout->text ("\n"); > } > =20 > + if (!part_of_multiple && breakpoint_source_is_tracked (b->bp_source.ge= t ())) > + { > + uiout->text ("\tsource-tracking enabled (tracking "); > + uiout->field_signed ("tracked-lines", > +=09=09=09 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, > =09=09=09=09enabled, flags, > =09=09=09=09display_canonical); > =20 > + /* 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 locat= ion > + may have moved differently. */ > + if (source_tracking_breakpoints && sals.size () =3D=3D 1 > + && sals[0].explicit_line > + && breakpoint_locspec_suitable_for_tracking (b->locspec.get ())) > + { > + /* Capture source if we have valid symtab and line info. > +=09 This works for both "b file:line" and "b line" formats. > +=09 We capture BREAKPOINT_SRC_CTX_LINES lines to provide > +=09 context around the breakpoint location. */ > + b->bp_source =3D std::make_unique > +=09(breakpoint_source_capture (sals, BREAKPOINT_SRC_CTX_LINES)); > + } > + > + bool warn_not_tracked =3D (source_tracking_breakpoints && sals.size ()= =3D=3D 1 > +=09=09=09 && sals[0].explicit_line > +=09=09=09 && !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=20 (gdb) b 19 Breakpoint 1 at 0x4011a2: file hello.c, line 19. (gdb) r Starting program: /tmp/hello.x=20 Hello World =20 Breakpoint 1, main () at hello.c:19 19=09 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)=20 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 () =3D=3D 1 && sals[0].explicit_line && breakpoint_locspec_suitable_for_tracking (b->locspec.get ())) { /* Capture source if we have valid symtab and line info. =09 This works for both "b file:line" and "b line" formats. =09 We capture BREAKPOINT_SRC_CTX_LINES lines to provide =09 context around the breakpoint location. */ b->bp_source =3D std::make_unique =09(breakpoint_source_capture (sals, BREAKPOINT_SRC_CTX_LINES)); if (!breakpoint_source_is_tracked (b->bp_source.get ())) =09warning (_("Source file not available; breakpoint will not be " =09=09 "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 " > +=09 "source-tracked.")); > } > =20 > /* Add SALS.nelts breakpoints to the breakpoint table. For each > @@ -13200,6 +13481,162 @@ code_breakpoint::location_spec_to_sals (locatio= n_spec *locspec, > return sals; > } > =20 > +/* Match BREAKPOINT_SRC_CTX_LINES lines of the initially stored source i= n a > + BREAKPOINT_SRC_CTX_LINES * BREAKPOINT_SRC_SEARCH_MULTIPLIER lines cur= rent > + source window. > + > + Returns new breakpoint line on success or -1 on failure. */ > + > +static int > +sliding_window_match (breakpoint_source *bp_source, > +=09=09 breakpoint_source *tmp_source) > +{ > + /* The index into BP_SOURCE's lines where the breakpoint was placed. = */ > + int bp_stored =3D 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 =3D 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 =3D GDB style places the `=3D` on the next line. > + (bp_stored =3D=3D 0 > + ? empty_string : bp_source->source_lines[bp_stored - 1]); > + const std::string &bp_next =3D Again with the misplaced `=3D`. > + ((bp_stored + 1) =3D=3D bp_size > + ? empty_string : bp_source->source_lines[bp_stored + 1]); > + > + /* Now search TMP_SOURCE for the breakpoint line. */ > + size_t tmp_size =3D tmp_source->source_lines.size (); > + for (size_t i =3D 0; i < tmp_size; i++) > + { > + if (bp_source->source_lines[bp_stored] =3D=3D tmp_source->source_l= ines[i]) > +=09{ > +=09 /* Found the breakpoint line. Capture the lines before and after > +=09 the breakpoint line from TMP_SOURCE. As above, if the > +=09 breakpoint is at the start or end of TMP_SOURCE then use > +=09 EMPTY_STRING as a stand in. */ > +=09 const std::string &tmp_prev =3D > +=09 (i =3D=3D 0 ? empty_string : tmp_source->source_lines[i - 1]); > +=09 const std::string &tmp_next =3D And two more misplaced `=3D` here. > +=09 ((i + 1) =3D=3D tmp_size > +=09 ? empty_string : tmp_source->source_lines[i + 1]); > + > +=09 /* If the previous and next lines match then this is the new > +=09 location of the breakpoint, calculate and return the updated > +=09 line number. */ > +=09 if (bp_prev =3D=3D tmp_prev && bp_next =3D=3D tmp_next) > +=09 return tmp_source->bp_line + i - tmp_source->bp_line_stored; > +=09} > + } > + > + /* 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 &expanded) > +{ > + if (expanded.empty () || expanded[0].symtab =3D=3D nullptr > + || !breakpoint_source_is_tracked (bp_source.get ())) > + return; > + > + struct compunit_symtab *cust =3D expanded[0].symtab->compunit (); > + if (cust =3D=3D nullptr || cust->objfile () =3D=3D nullptr) > + return; > + > + bfd *current_bfd =3D cust->objfile ()->obfd.get (); > + if (bp_source->source_bfd.get () =3D=3D current_bfd) > + return; > + > + /* BFD changed =E2=80=94 executable was reloaded. */ > + if (expanded.size () !=3D 1) > + { > + warning (_("Breakpoint %d now has multiple locations after reload,= " > +=09=09 "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 =3D 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 =3D make_scoped_restore (&source_styling, false); > + if (!g_source_cache.get_source_lines (expanded[0].symtab, > +=09=09=09=09=09expanded[0].line, > +=09=09=09=09=09expanded[0].line, &line)) > + { > + /* Source is unreadable after reload =E2=80=94 drop tracking. */ > + bp_source.reset (); > + return; > + } > + > + if (line =3D=3D bp_source->source_lines[bp_stored]) > + { > + /* Line unchanged =E2=80=94 just refresh the capture with the new = BFD. */ > + bp_source =3D std::make_unique > +=09(breakpoint_source_capture (expanded, BREAKPOINT_SRC_CTX_LINES)); > + return; > + } > + > + breakpoint_source tmp_source > + =3D breakpoint_source_capture (expanded, > +=09=09=09=09 BREAKPOINT_SRC_CTX_LINES > +=09=09=09=09 * BREAKPOINT_SRC_SEARCH_MULTIPLIER); > + int new_bp_line =3D sliding_window_match (bp_source.get (), &tmp_sourc= e); > + if (new_bp_line =3D=3D -1) > + { > + warning (_("Breakpoint %d source code not found " > +=09=09 "after reload, keeping original location."), number); > + bp_source.reset (); > + return; > + } > + > + location_spec *spec =3D locspec.get (); > + std::string bp_string (spec->to_string ()); > + auto pos =3D bp_string.rfind (':'); > + if (pos =3D=3D std::string::npos) > + { > + warning (_("unable to update location spec for breakpoint %d, " > +=09=09 "disabling source tracking."), number); > + bp_source.reset (); > + return; > + } > + > + bp_string =3D 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 =3D bp_string.c_str (); > + locspec =3D string_to_location_spec (&new_spec_str, current_language); > + spec =3D locspec.get (); > + > + int found; > + expanded =3D location_spec_to_sals (spec, filter_pspace, &found); > + if (found && new_bp_line !=3D bp_source->bp_line) > + { > + gdb_printf (_("Breakpoint %d adjusted from line %d to line %d.\n")= , > +=09=09 number, bp_source->bp_line, new_bp_line); > + notify_breakpoint_modified (this); > + } > + > + bp_source =3D std::make_unique > + (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) > =20 > if (locspec_range_end !=3D nullptr) > =09{ > +=09 /* Ranged breakpoints are not currently tracked. */ > +=09 gdb_assert (!breakpoint_source_is_tracked (bp_source.get ())); > + > =09 std::vector sals_end > =09 =3D location_spec_to_sals (locspec_range_end.get (), > =09=09=09=09 filter_pspace, &found); > =09 if (found) > =09 expanded_end =3D std::move (sals_end); > =09} > + > + adjust_bp_for_source_tracking (filter_pspace, expanded); > } > =20 > /* Update the locations for this breakpoint. For thread-specific > @@ -15024,6 +15466,15 @@ Convenience variable \"$bpnum\" contains the num= ber of the last\n\ > breakpoint set."), > =09 &maintenanceinfolist); > =20 > + add_cmd ("source-tracking-context", class_maintenance, > +=09 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."), > +=09 &maintenanceinfolist); > + > add_basic_prefix_cmd ("catch", class_breakpoint, _("\ > Set catchpoints to catch events."), > =09=09=09&catch_cmdlist, > @@ -15342,6 +15793,27 @@ Usage: agent-printf \"format string\", ARG1, ARG= 2, 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.")); > =20 > + add_setshow_prefix_cmd ("source-tracking", class_breakpoint, > +=09=09=09 _("\ > +Source tracking breakpoint specific settings."), > +=09=09=09 _("\ > +Source tracking breakpoint specific settings."), > +=09=09=09 &source_tracking_bp_set_cmdlist, > +=09=09=09 &source_tracking_bp_show_cmdlist, > +=09=09=09 &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."), > +=09=09=09 set_breakpoint_source_tracking_enabled, > +=09=09=09 get_breakpoint_source_tracking_enabled, > +=09=09=09 show_source_tracking_breakpoints, > +=09=09=09 &source_tracking_bp_set_cmdlist, > +=09=09=09 &source_tracking_bp_show_cmdlist); > + > automatic_hardware_breakpoints =3D true; > =20 > 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 > +#include > #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" > =20 > struct block; > struct gdbpy_breakpoint_object; > @@ -615,6 +617,30 @@ using bp_location_list =3D intrusive_list; > using bp_location_iterator =3D bp_location_list::iterator; > using bp_location_range =3D iterator_range; > =20 > +/* 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 source_lines; > + > + /* The original line number where the breakpoint was set > + in the source file. */ > + int bp_line =3D 0; > + > + /* Index into source_lines vector indicating which line > + contains the breakpoint (0-based). */ > + int bp_line_stored =3D 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 > find the end of the range. */ > location_spec_up locspec_range_end; > =20 > + /* Captured source code around the breakpoint location, used to > + track source around the breakpoint to automatically adjust the brea= kpoint > + when source code changes between recompilations. */ > + std::unique_ptr 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); > =20 > + /* 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, > +=09=09=09=09 std::vector &expanded); > + > /* Find the SaL locations corresponding to the given LOCATION. > On return, FOUND will be 1 if any SaL was found, zero otherwise. *= / > =20 > 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. > =20 > +@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. > =20 > @end table > =20 > +@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 withi= n a 1k block starting > at thread local base, when using the @samp{info w32 thread-information-b= lock} > command. > =20 > +@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 an= d > +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/testsui= te/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 .= */ > + > +int > +foo (void) > +{ > + return 0; > +} > + > +int > +foo (int var) > +{ > + int i =3D 8; > + var +=3D 10; > + var +=3D i; > + > + return var; > +} > + > +int > +main (void) > +{ > + foo (); > + return foo (2); > +} > diff --git a/gdb/testsuite/gdb.base/adjust_breakpoint-3.cpp b/gdb/testsui= te/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 .= */ > + > + > +int > +foo () { > + return 0; > +} > + > +int > +foo (int var) > +{ > + int j =3D 0; > + int i =3D 8; > + > + var +=3D 10; > + var +=3D i; > + > + return var; > +} > + > +int > +main (void) > +{ > + foo (); > + return foo (2); > +} > diff --git a/gdb/testsuite/gdb.base/adjust_breakpoint-4.cpp b/gdb/testsui= te/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 .= */ > + > +int > +foo () { > + return 0; > +} > + > +int > +foo (int var) > +{ > + int i =3D 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 . > + > +# Test that source-tracking breakpoints handle missing source files corr= ectly. > +# When source tracking is enabled but the source file cannot be found, G= DB > +# 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_sou= rce_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" { > +=09-re -wrap "source-tracking enabled.*" { > +=09 fail $gdb_test_name > +=09} > +=09-re -wrap "breakpoint.*${build_srcfile}:${bp_line}.*" { > +=09 pass $gdb_test_name > +=09} > + } > 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 .= */ > + > +int > +foo (void) > +{ > + return 0; > +} > + > +int > +foo (int var) > +{ > + int i =3D 8; > + > + var +=3D 10; > + var +=3D 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 . > + > +# 1) Set the breakpoint to a certain line in $srcfile. Replace the $srcf= ile > +# with tmp-$srcfile which is exactly the same except one line is missing= , > +# which changes the line where the breakpoint was initially set and move= s > +# 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 addi= ng 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" \ > +=09"disable" > + > + gdb_test_no_output "set breakpoint source-tracking enabled on" \ > +=09"enable" > +} > + > +# Test that the breakpoint can be adjusted backward. > +set lineno [gdb_get_line_number "var +=3D 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] = =3D=3D -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 ti= me" \ > + "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] = =3D=3D -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 ti= me" \ > + "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 \ > +=09 "1\\s+breakpoint\\s+keep\\s+y\[^\r\n\]+" \ > +=09 "\\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 +=3D 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] = =3D=3D -1} { > + return > +} > + > +# When the original line is removed and cannot be found in the search wi= ndow, > +# 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 with= out 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] = =3D=3D -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 ti= me" \ > + "breakpoint not adjusted when tracking disabled" > diff --git a/gdb/testsuite/gdb.base/source-tracking-inline-1.c b/gdb/test= suite/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 .= */ > + > +volatile int global_var =3D 0; > + > +static void __attribute__ ((__always_inline__)) > +inline_func (void) > +{ > + ++global_var;=09=09/* 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 =3D foo (42) + bar (10); > + > + ++global_var; > + > + return ans - global_var; > +} > diff --git a/gdb/testsuite/gdb.base/source-tracking-inline-2.c b/gdb/test= suite/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 .= */ > +volatile int global_var =3D 0; > + > +static void __attribute__ ((__always_inline__)) > +inline_func (void) > +{ > + ++global_var;=09=09/* 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 =3D foo (42) + bar (10); > + > + ++global_var; > + > + return ans - global_var; > +} > diff --git a/gdb/testsuite/gdb.base/source-tracking-inline.exp b/gdb/test= suite/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 . > + > +# 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 locatio= ns\\)" > + > +# 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 but NOT "source-tracking enabled". > +set test "multi-location breakpoint not tracked" > +gdb_test_multiple "info breakpoints" $test { > + -re "source-tracking enabled.*$gdb_prompt $" { > +=09fail "$test (tracking incorrectly enabled)" > + } > + -re ".*$gdb_prompt $" { > +=09pass $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.*" { =09fail "$gdb_test_name (tracking incorrectly enabled)" } -re -wrap ".*" { =09pass $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 $" { > +=09fail "$test (tracking incorrectly enabled)" > + } > + -re ".*$gdb_prompt $" { > +=09pass $test > + } You can use the same tricks to clean up this gdb_test_multiple too. > +} > --=20 > 2.52.0 Thanks, Andrew