From adbc0c55421dd5e31bb1903512dfb8f5211ee5b0 Mon Sep 17 00:00:00 2001 From: Tankut Baris Aktemur Date: Wed, 25 Feb 2026 18:59:57 +0100 Subject: [PATCH] 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 [] ()] (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 --- gdb/progspace.c | 34 +++++----- gdb/progspace.h | 21 +++--- gdb/remote.c | 2 +- gdb/testsuite/gdb.multi/address-space-reset.c | 66 +++++++++++++++++++ .../gdb.multi/address-space-reset.exp | 60 +++++++++++++++++ 5 files changed, 157 insertions(+), 26 deletions(-) create mode 100644 gdb/testsuite/gdb.multi/address-space-reset.c create mode 100644 gdb/testsuite/gdb.multi/address-space-reset.exp diff --git a/gdb/progspace.c b/gdb/progspace.c index cf12939328a..484fd085b1f 100644 --- a/gdb/progspace.c +++ b/gdb/progspace.c @@ -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 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; diff --git a/gdb/progspace.h b/gdb/progspace.h index 36469eeebb4..e9261ff8590 100644 --- a/gdb/progspace.h +++ b/gdb/progspace.h @@ -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 */ diff --git a/gdb/remote.c b/gdb/remote.c index 4ad595bda68..22f584f0c57 100644 --- a/gdb/remote.c +++ b/gdb/remote.c @@ -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. */ diff --git a/gdb/testsuite/gdb.multi/address-space-reset.c b/gdb/testsuite/gdb.multi/address-space-reset.c new file mode 100644 index 00000000000..556aca0295e --- /dev/null +++ b/gdb/testsuite/gdb.multi/address-space-reset.c @@ -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 . */ + +#include +#include + +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; +} diff --git a/gdb/testsuite/gdb.multi/address-space-reset.exp b/gdb/testsuite/gdb.multi/address-space-reset.exp new file mode 100644 index 00000000000..659889719bb --- /dev/null +++ b/gdb/testsuite/gdb.multi/address-space-reset.exp @@ -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 . + +# 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