gdb: limit updating the address spaces to the current target

For the `update_address_spaces` function, there is this comment:

   It is assumed that there are no bound inferiors yet, otherwise,
   they'd be left with stale referenced to released aspaces.

The function indeed iterates over all inferiors and resets their
address spaces.  The function has only one caller:
remote_target::start_remote_1.  This means, if we already have an
inferior and then start a remote target in a second inferior, we may
alter the address space of the first inferior.  It seems the current
code is a left-over from pre-multi-target days.  To prevent bad things
from happening, update the address spaces for a given target only.

A scenario where unwanted behavior occurs is as follows: Start a
multi-threaded program in non-stop mode.  Hit a breakpoint with a
thread.  Create a second inferior on a remote target.  Switch back to
inferior 1.  Continuing repeatedly hits the breakpoint.  See below.

  $ gdb /tmp/multi-thread
  Reading symbols from /tmp/multi-thread...
  (gdb) set non-stop on
  (gdb) break 30
  Breakpoint 1 at 0x151e: file multi-thread.cpp, line 30.
  (gdb) run
  Starting program: /tmp/multi-thread
  [Thread debugging using libthread_db enabled]
  Using host libthread_db library "/lib/x86_64-linux-gnu/libthread_db.so.1".
  [New Thread 0x7ffff7a4f640 (LWP 3413070)]
  [New Thread 0x7ffff724e640 (LWP 3413071)]
  [New Thread 0x7ffff6a4d640 (LWP 3413072)]
  [New Thread 0x7ffff624c640 (LWP 3413073)]

  Thread 1 "multi-thread" hit Breakpoint 1, main (argc=1, argv=0x7fffffffdec8) at multi-thread.cpp:30
  30        int x = 42;
  (gdb) add-inferior -no-connection
  [New inferior 2]
  Added inferior 2
  (gdb) inferior 2
  [Switching to inferior 2 [<null>] (<noexec>)]
  (gdb) target extended-remote | gdbserver --multi -
  Remote debugging using | gdbserver --multi -
  Remote debugging using stdio
  (gdb) inferior 1
  [Switching to inferior 1 [process 3413067] (/tmp/multi-thread)]
  [Switching to thread 1.1 (Thread 0x7ffff7a50740 (LWP 3413067))]
  #0  main (argc=1, argv=0x7fffffffdec8) at multi-thread.cpp:30
  30        int x = 42;
  (gdb) continue
  Continuing.

  Thread 1.1 "multi-thread" hit Breakpoint 1, main (argc=1, argv=0x7fffffffdec8) at multi-thread.cpp:30
  30        int x = 42;
  (gdb) continue
  Continuing.

  Thread 1.1 "multi-thread" hit Breakpoint 1, main (argc=1, argv=0x7fffffffdec8) at multi-thread.cpp:30
  30        int x = 42;
  (gdb) continue
  Continuing.

  Thread 1.1 "multi-thread" hit Breakpoint 1, main (argc=1, argv=0x7fffffffdec8) at multi-thread.cpp:30
  30        int x = 42;
  (gdb)
  ...

It was not possible to simplify the bug reproducer.  For some reason,
simplifications make observable problems go away.  Adding the case
above as a testcase.

Regression-tested in Linux x86-64 with the default, native-gdbserver,
and native-extended-gdbserver board files.

Approved-By: Simon Marchi <simon.marchi@efficios.com>
This commit is contained in:
Tankut Baris Aktemur 2026-02-25 18:59:57 +01:00
parent 36974d77e9
commit adbc0c5542
5 changed files with 157 additions and 26 deletions

View file

@ -445,36 +445,36 @@ maintenance_info_program_spaces_command (const char *args, int from_tty)
print_program_space (current_uiout, requested);
}
/* Update all program spaces matching to address spaces. The user may
have created several program spaces, and loaded executables into
them before connecting to the target interface that will create the
inferiors. All that happens before GDB has a chance to know if the
inferiors will share an address space or not. Call this after
having connected to the target interface and having fetched the
target description, to fixup the program/address spaces mappings.
It is assumed that there are no bound inferiors yet, otherwise,
they'd be left with stale referenced to released aspaces. */
/* See progspace.h. */
void
update_address_spaces (void)
update_address_spaces (process_stratum_target *target,
gdbarch *gdbarch)
{
int shared_aspace
= gdbarch_has_shared_address_space (current_inferior ()->arch ());
gdb_assert (target != nullptr);
gdb_assert (gdbarch != nullptr);
int shared_aspace = gdbarch_has_shared_address_space (gdbarch);
/* Find the program spaces that are being used by inferiors of the
current target. We shouldn't alter inferiors of other targets. */
gdb::unordered_set<program_space *> pspaces_to_update;
for (inferior *inf : all_inferiors (target))
pspaces_to_update.insert (inf->pspace);
if (shared_aspace)
{
address_space_ref_ptr aspace = new_address_space ();
for (struct program_space *pspace : program_spaces)
for (program_space *pspace : pspaces_to_update)
pspace->aspace = aspace;
}
else
for (struct program_space *pspace : program_spaces)
for (program_space *pspace : pspaces_to_update)
pspace->aspace = new_address_space ();
for (inferior *inf : all_inferiors ())
if (gdbarch_has_global_solist (current_inferior ()->arch ()))
for (inferior *inf : all_inferiors (target))
if (gdbarch_has_global_solist (gdbarch))
inf->aspace = maybe_new_address_space ();
else
inf->aspace = inf->pspace->aspace;

View file

@ -463,14 +463,19 @@ private:
share an address space. */
extern address_space_ref_ptr maybe_new_address_space ();
/* Update all program spaces matching to address spaces. The user may
have created several program spaces, and loaded executables into
them before connecting to the target interface that will create the
inferiors. All that happens before GDB has a chance to know if the
inferiors will share an address space or not. Call this after
having connected to the target interface and having fetched the
target description, to fixup the program/address spaces
/* Update all program spaces matching to address spaces. This is done
for the inferiors that have TARGET as their process stratum target
and using GDBARCH to determine if there is a shared address space
and if solibs are global.
The user may have created several program spaces, and loaded
executables into them before connecting to the target interface
that will create the inferiors. All that happens before GDB has a
chance to know if the inferiors will share an address space or not.
Call this after having connected to the target interface and having
fetched the target description, to fixup the program/address spaces
mappings. */
extern void update_address_spaces (void);
extern void update_address_spaces (process_stratum_target *target,
gdbarch *gdbarch);
#endif /* GDB_PROGSPACE_H */

View file

@ -5614,7 +5614,7 @@ remote_target::start_remote_1 (int from_tty, int extended_p)
/* Next, now that we know something about the target, update the
address spaces in the program spaces. */
update_address_spaces ();
update_address_spaces (this, current_inferior ()->arch ());
/* On OSs where the list of libraries is global to all
processes, we fetch them early. */

View file

@ -0,0 +1,66 @@
/* 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/>. */
#include <unistd.h>
#include <pthread.h>
static volatile int spin = 1;
static void *
thread_func (void *arg)
{
/* Unleash the main thread. */
spin = 0;
for (;;)
sleep (1);
return NULL;
}
static void
breakpoint_1 (void)
{
/* Do nothing. */
}
static void
breakpoint_2 (void)
{
/* Do nothing. */
}
int
main ()
{
pthread_t thread;
pthread_create (&thread, NULL, thread_func, NULL);
alarm (30);
/* Make sure the thread is up an running. */
while (spin)
sleep (1);
breakpoint_1 ();
int a = 42;
breakpoint_2 ();
pthread_join (thread, NULL);
return 0;
}

View file

@ -0,0 +1,60 @@
# 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/>.
# When a remote target is set up in a new inferior, the address space
# of the other inferiors would be reset. This is a regression test
# for that scenario.
require allow_multi_inferior_tests
load_lib gdbserver-support.exp
require allow_gdbserver_tests
standard_testfile
if {[build_executable "failed to prepare" $testfile $srcfile \
{debug pthreads}]} {
return -1
}
# Problematic scenario is seen in non-stop mode.
save_vars {GDBFLAGS} {
append GDBFLAGS { -ex "set non-stop on"}
clean_restart $testfile
}
if {![runto "breakpoint_1"]} {
return -1
}
# Start an empty gdbserver in a new inferior.
gdb_test "add-inferior -no-connection" "Added inferior 2"
gdb_test "inferior 2" "Switching to inferior 2.*"
set res [gdbserver_start "--multi" ""]
set gdbserver_gdbport [lindex $res 1]
if {[gdb_target_cmd "extended-remote" $gdbserver_gdbport]} {
return -1
}
# Go back to inferior 1. Continuing should hit the next breakpoint.
# Buggy GDB was hitting the first breakpoint.
gdb_test "inferior 1" "Switching to inferior 1.*"
gdb_breakpoint "breakpoint_2"
gdb_test "continue" " breakpoint_2 \\(\\).*" "hit the next bp"
# Clean up gdbserver.
gdb_test "inferior 2" "Switching to inferior 2.*" "back to inferior 2"
gdbserver_exit 0