+2021-07-01 Pedro Alves <pedro@palves.net>
+
+ PR mi/15729
+ PR gdb/13463
+ * linux-nat.c (linux_nat_target::detach): Close the
+ /proc/<pid>/mem file if it was open for this process.
+ (linux_handle_extended_wait) <PTRACE_EVENT_EXEC>: Close the
+ /proc/<pid>/mem file if it was open for this process.
+ (linux_nat_target::mourn_inferior): Close the /proc/<pid>/mem file
+ if it was open for this process.
+ (linux_nat_target::xfer_partial): Adjust. Do not fall back to
+ inf_ptrace_target::xfer_partial for memory accesses.
+ (last_proc_mem_file): New.
+ (maybe_close_proc_mem_file): New.
+ (linux_proc_xfer_memory_partial_pid): New, with bits factored out
+ from linux_proc_xfer_partial.
+ (linux_proc_xfer_partial): Delete.
+ (linux_proc_xfer_memory_partial): New.
+
2021-06-29 Simon Marchi <simon.marchi@polymtl.ca>
* frame.h (FRAME_SCOPED_DEBUG_ENTER_EXIT): New.
static void save_stop_reason (struct lwp_info *lp);
+static void maybe_close_proc_mem_file (pid_t pid);
+
\f
/* LWP accessors. */
detach_success (inf);
}
+
+ maybe_close_proc_mem_file (pid);
}
/* Resume execution of the inferior process. If STEP is nonzero,
{
linux_nat_debug_printf ("Got exec event from LWP %ld", lp->ptid.lwp ());
+ /* Close the /proc/<pid>/mem file if it was open for this
+ inferior. */
+ maybe_close_proc_mem_file (lp->ptid.pid ());
+
ourstatus->kind = TARGET_WAITKIND_EXECD;
ourstatus->value.execd_pathname
= xstrdup (linux_proc_pid_to_exec_file (pid));
purge_lwp_list (pid);
+ /* Close the /proc/<pid>/mem file if it was open for this
+ inferior. */
+ maybe_close_proc_mem_file (pid);
+
if (! forks_exist_p ())
/* Normal case, no other forks available. */
inf_ptrace_target::mourn_inferior ();
ULONGEST *xfered_len);
static enum target_xfer_status
-linux_proc_xfer_partial (enum target_object object,
- const char *annex, gdb_byte *readbuf,
- const gdb_byte *writebuf,
- ULONGEST offset, LONGEST len, ULONGEST *xfered_len);
+linux_proc_xfer_memory_partial (gdb_byte *readbuf, const gdb_byte *writebuf,
+ ULONGEST offset, LONGEST len, ULONGEST *xfered_len);
enum target_xfer_status
linux_nat_target::xfer_partial (enum target_object object,
const gdb_byte *writebuf,
ULONGEST offset, ULONGEST len, ULONGEST *xfered_len)
{
- enum target_xfer_status xfer;
-
if (object == TARGET_OBJECT_SIGNAL_INFO)
return linux_xfer_siginfo (object, annex, readbuf, writebuf,
offset, len, xfered_len);
return linux_nat_xfer_osdata (object, annex, readbuf, writebuf,
offset, len, xfered_len);
- /* GDB calculates all addresses in the largest possible address
- width.
- The address width must be masked before its final use - either by
- linux_proc_xfer_partial or inf_ptrace_target::xfer_partial.
-
- Compare ADDR_BIT first to avoid a compiler warning on shift overflow. */
-
if (object == TARGET_OBJECT_MEMORY)
{
+ /* GDB calculates all addresses in the largest possible address
+ width. The address width must be masked before its final use
+ by linux_proc_xfer_partial.
+
+ Compare ADDR_BIT first to avoid a compiler warning on shift overflow. */
int addr_bit = gdbarch_addr_bit (target_gdbarch ());
if (addr_bit < (sizeof (ULONGEST) * HOST_CHAR_BIT))
offset &= ((ULONGEST) 1 << addr_bit) - 1;
- }
- xfer = linux_proc_xfer_partial (object, annex, readbuf, writebuf,
- offset, len, xfered_len);
- if (xfer != TARGET_XFER_EOF)
- return xfer;
+ return linux_proc_xfer_memory_partial (readbuf, writebuf,
+ offset, len, xfered_len);
+ }
return inf_ptrace_target::xfer_partial (object, annex, readbuf, writebuf,
offset, len, xfered_len);
return linux_proc_pid_to_exec_file (pid);
}
-/* Implement the to_xfer_partial target method using /proc/<pid>/mem.
- Because we can use a single read/write call, this can be much more
- efficient than banging away at PTRACE_PEEKTEXT. */
+/* Keep the /proc/<pid>/mem file open between memory accesses, as a
+ cache to avoid constantly closing/opening the file in the common
+ case of multiple memory reads/writes from/to the same inferior.
+ Note we don't keep a file open per inferior to avoid keeping too
+ many file descriptors open, which can run into resource limits. */
+static struct
+{
+ /* The LWP this open file is for. Note that after opening the file,
+ even if the thread subsequently exits, the open file is still
+ usable for accessing memory. It's only when the whole process
+ exits or execs that the file becomes invalid (at which point
+ reads/writes return EOF). */
+ ptid_t ptid;
-static enum target_xfer_status
-linux_proc_xfer_partial (enum target_object object,
- const char *annex, gdb_byte *readbuf,
- const gdb_byte *writebuf,
- ULONGEST offset, LONGEST len, ULONGEST *xfered_len)
+ /* The file descriptor. -1 if file is not open. */
+ int fd = -1;
+
+ /* Close FD and clear it to -1. */
+ void close ()
+ {
+ linux_nat_debug_printf ("closing fd %d for /proc/%d/task/%ld/mem\n",
+ fd, ptid.pid (), ptid.lwp ());
+ ::close (fd);
+ fd = -1;
+ }
+} last_proc_mem_file;
+
+/* Close the /proc/<pid>/mem file if its LWP matches PTID. */
+
+static void
+maybe_close_proc_mem_file (pid_t pid)
{
- LONGEST ret;
- int fd;
- char filename[64];
+ if (last_proc_mem_file.ptid.pid () == pid)
+ last_proc_mem_file.close ();
+}
- if (object != TARGET_OBJECT_MEMORY)
- return TARGET_XFER_EOF;
+/* Helper for linux_proc_xfer_memory_partial. Accesses /proc via
+ PTID. Returns -1 on error, with errno set. Returns number of
+ read/written bytes otherwise. Returns 0 on EOF, which indicates
+ the address space is gone (because the process exited or
+ execed). */
- /* Don't bother for one word. */
- if (len < 3 * sizeof (long))
- return TARGET_XFER_EOF;
+static ssize_t
+linux_proc_xfer_memory_partial_pid (ptid_t ptid,
+ gdb_byte *readbuf, const gdb_byte *writebuf,
+ ULONGEST offset, LONGEST len)
+{
+ ssize_t ret;
- /* We could keep this file open and cache it - possibly one per
- thread. That requires some juggling, but is even faster. */
- xsnprintf (filename, sizeof filename, "/proc/%ld/mem",
- inferior_ptid.lwp ());
- fd = gdb_open_cloexec (filename, ((readbuf ? O_RDONLY : O_WRONLY)
- | O_LARGEFILE), 0);
- if (fd == -1)
- return TARGET_XFER_EOF;
+ /* As long as we're hitting the same inferior, the previously open
+ file is good, even if the thread it was open for exits. */
+ if (last_proc_mem_file.fd != -1
+ && last_proc_mem_file.ptid.pid () != ptid.pid ())
+ last_proc_mem_file.close ();
+
+ if (last_proc_mem_file.fd == -1)
+ {
+ /* Actually use /proc/<pid>/task/<lwp>/mem instead of
+ /proc/<lwp>/mem to avoid PID-reuse races, as we may be trying
+ to read memory via a thread which we've already reaped.
+ /proc/<lwp>/mem could open a file for the wrong process. If
+ the LWPID is reused for the same process it's OK, we can read
+ memory through it just fine. If the LWPID is reused for a
+ different process, then the open will fail because the path
+ won't exist. */
+ char filename[64];
+ xsnprintf (filename, sizeof filename,
+ "/proc/%d/task/%ld/mem", ptid.pid (), ptid.lwp ());
+
+ last_proc_mem_file.fd
+ = gdb_open_cloexec (filename, O_RDWR | O_LARGEFILE, 0);
+
+ if (last_proc_mem_file.fd == -1)
+ {
+ linux_nat_debug_printf ("opening %s failed: %s (%d)\n",
+ filename, safe_strerror (errno), errno);
+ return -1;
+ }
+ last_proc_mem_file.ptid = ptid;
+
+ linux_nat_debug_printf ("opened fd %d for %s\n",
+ last_proc_mem_file.fd, filename);
+ }
+
+ int fd = last_proc_mem_file.fd;
/* Use pread64/pwrite64 if available, since they save a syscall and can
handle 64-bit offsets even on 32-bit platforms (for instance, SPARC
: write (fd, writebuf, len));
#endif
- close (fd);
+ if (ret == -1)
+ {
+ linux_nat_debug_printf ("accessing fd %d for pid %ld failed: %s (%d)\n",
+ fd, ptid.lwp (),
+ safe_strerror (errno), errno);
+ }
+ else if (ret == 0)
+ {
+ linux_nat_debug_printf ("accessing fd %d for pid %ld got EOF\n",
+ fd, ptid.lwp ());
+ }
+
+ return ret;
+}
- if (ret == -1 || ret == 0)
- return TARGET_XFER_EOF;
+/* Implement the to_xfer_partial target method using /proc/<pid>/mem.
+ Because we can use a single read/write call, this can be much more
+ efficient than banging away at PTRACE_PEEKTEXT. Also, unlike
+ PTRACE_PEEKTEXT/PTRACE_POKETEXT, this works with running
+ threads. */
+
+static enum target_xfer_status
+linux_proc_xfer_memory_partial (gdb_byte *readbuf, const gdb_byte *writebuf,
+ ULONGEST offset, LONGEST len,
+ ULONGEST *xfered_len)
+{
+ /* Unlike PTRACE_PEEKTEXT/PTRACE_POKETEXT, reading/writing from/to
+ /proc/<pid>/mem works with running threads, and even exited
+ threads if the file was already open. If we need to open or
+ reopen the /proc file though, we may get an EACCES error
+ ("Permission denied"), meaning the thread is gone but its exit
+ status isn't reaped yet, or ENOENT if the thread is gone and
+ already reaped. In that case, just try opening the file for
+ another thread in the process. If all threads fail, then it must
+ mean the whole process exited, in which case there's nothing else
+ to do and we just fail the memory access.
+
+ Note we don't simply always access via the leader thread because
+ the leader may exit without exiting the whole process. See
+ gdb.threads/leader-exit.exp, for example. */
+
+ /* It's frequently the case that the selected thread is stopped, and
+ is thus not likely to exit (unless something kills the process
+ outside our control, with e.g., SIGKILL). Give that one a try
+ first.
+
+ Also, inferior_ptid may actually point at an LWP not in lwp_list.
+ This happens when we're detaching from a fork child that we don't
+ want to debug ("set detach-on-fork on"), and the breakpoints
+ module uninstalls breakpoints from the fork child. Which process
+ to access is given by inferior_ptid. */
+ int res = linux_proc_xfer_memory_partial_pid (inferior_ptid,
+ readbuf, writebuf,
+ offset, len);
+ if (res == 0)
+ {
+ /* EOF means the address space is gone, the whole
+ process exited or execed. */
+ return TARGET_XFER_EOF;
+ }
+ else if (res != -1)
+ {
+ *xfered_len = res;
+ return TARGET_XFER_OK;
+ }
else
{
- *xfered_len = ret;
+ /* If we simply raced with the thread exiting (EACCES), or the
+ current thread is THREAD_EXITED (ENOENT), try some other
+ thread. It's easier to handle an ENOENT failure than check
+ for THREAD_EXIT upfront because this function is called
+ before a thread for inferior_ptid is added to the thread
+ list. */
+ if (errno != EACCES && errno != ENOENT)
+ return TARGET_XFER_EOF;
+ }
+
+ int cur_pid = current_inferior ()->pid;
+
+ if (inferior_ptid.pid () != cur_pid)
+ {
+ /* We're accessing a fork child, and the access above failed.
+ Don't bother iterating the LWP list, since there's no other
+ LWP for this process. */
+ return TARGET_XFER_EOF;
+ }
+
+ /* Iterate over LWPs of the current inferior, trying to access
+ memory through one of them. */
+ for (lwp_info *lp = lwp_list; lp != nullptr; lp = lp->next)
+ {
+ if (lp->ptid.pid () != cur_pid)
+ continue;
+
+ res = linux_proc_xfer_memory_partial_pid (lp->ptid,
+ readbuf, writebuf,
+ offset, len);
+
+ if (res == 0)
+ {
+ /* EOF means the address space is gone, the whole process
+ exited or execed. */
+ return TARGET_XFER_EOF;
+ }
+ else if (res == -1)
+ {
+ if (errno == EACCES)
+ {
+ /* This LWP is gone, try another one. */
+ continue;
+ }
+
+ return TARGET_XFER_EOF;
+ }
+
+ *xfered_len = res;
return TARGET_XFER_OK;
}
-}
+ /* No luck. */
+ return TARGET_XFER_EOF;
+}
/* Parse LINE as a signal set and add its set bits to SIGS. */
+2021-07-01 Pedro Alves <pedro@palves.net>
+
+ PR mi/15729
+ PR gdb/13463
+ * gdb.base/access-mem-running.c: New.
+ * gdb.base/access-mem-running.exp: New.
+ * gdb.threads/access-mem-running-thread-exit.c: New.
+ * gdb.threads/access-mem-running-thread-exit.exp: New.
+
2021-06-29 Simon Marchi <simon.marchi@polymtl.ca>
* gdb.dwarf2/dw2-reg-undefined.exp: Update regexp.
--- /dev/null
+/* This testcase is part of GDB, the GNU debugger.
+
+ Copyright 2021 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>
+
+static unsigned int global_counter = 1;
+
+static volatile unsigned int global_var = 123;
+
+static void
+maybe_stop_here ()
+{
+}
+
+int
+main (void)
+{
+ global_counter = 1;
+
+ while (global_counter > 0)
+ {
+ global_counter++;
+
+ /* Less than 1s, so the counter increments at least once while
+ the .exp sleep 1s, but slow enough that the counter doesn't
+ wrap in 1s. */
+ usleep (5000);
+
+ maybe_stop_here ();
+ }
+
+ return 0;
+}
--- /dev/null
+# Copyright (C) 2021 Free Software Foundation, Inc.
+
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+# Test that we can access memory while the inferior is running.
+
+standard_testfile
+
+if {[prepare_for_testing "failed to prepare" $testfile $srcfile {debug}] == -1} {
+ return -1
+}
+
+# The test proper. NON_STOP indicates whether we're testing in
+# non-stop, or all-stop mode.
+
+proc test { non_stop } {
+ global srcfile binfile
+ global gdb_prompt
+ global GDBFLAGS
+ global decimal
+
+ save_vars { GDBFLAGS } {
+ append GDBFLAGS " -ex \"set non-stop $non_stop\""
+ clean_restart ${binfile}
+ }
+
+ if ![runto_main] {
+ return -1
+ }
+
+ # If debugging with target remote, check whether the all-stop variant
+ # of the RSP is being used. If so, we can't run the background tests.
+ if {!$non_stop
+ && [target_info exists gdb_protocol]
+ && ([target_info gdb_protocol] == "remote"
+ || [target_info gdb_protocol] == "extended-remote")} {
+
+ gdb_test_multiple "maint show target-non-stop" "" {
+ -wrap -re "(is|currently) on.*" {
+ }
+ -wrap -re "(is|currently) off.*" {
+ unsupported "can't issue commands while target is running"
+ return 0
+ }
+ }
+ }
+
+ delete_breakpoints
+
+ if {$non_stop == "off"} {
+ set cmd "continue &"
+ } else {
+ set cmd "continue -a &"
+ }
+ gdb_test_multiple $cmd "continuing" {
+ -re "Continuing\.\r\n$gdb_prompt " {
+ pass $gdb_test_name
+ }
+ }
+
+ # Check we can read/write variables.
+
+ # Check that we can read the counter variable, and that the
+ # counter is increasing, meaning the process really is running.
+
+ sleep 1
+ set global_counter1 \
+ [get_integer_valueof "global_counter" 0 \
+ "get global_counter once"]
+
+ sleep 1
+ set global_counter2 \
+ [get_integer_valueof "global_counter" 0 \
+ "get global_counter twice"]
+
+ gdb_assert {$global_counter1 != 0 \
+ && $global_counter2 != 0 \
+ && $global_counter1 != $global_counter2} \
+ "value changed"
+
+ # Check that we can write variables.
+
+ gdb_test "print global_var" " = 123" \
+ "print global_var before writing"
+ gdb_test "print global_var = 321" " = 321" \
+ "write to global_var"
+ gdb_test "print global_var" " = 321" \
+ "print global_var after writing"
+ gdb_test "print global_var = 123" " = 123" \
+ "write to global_var again"
+
+ # Check we can set a breakpoint while the process is running. The
+ # breakpoint should hit immediately.
+ set any "\[^\r\n\]*"
+
+ gdb_test_multiple "b maybe_stop_here" "" {
+ -re "Breakpoint $decimal at $any: file $any${srcfile}, line $decimal.\r\n$gdb_prompt " {
+ pass $gdb_test_name
+ }
+ }
+ gdb_test_multiple "" "breakpoint hits" {
+ -re "Breakpoint $decimal, maybe_stop_here \\(\\) at $any${srcfile}:$decimal\r\n" {
+ pass "$gdb_test_name"
+ }
+ }
+}
+
+foreach non_stop { "off" "on" } {
+ set stop_mode [expr ($non_stop=="off")?"all-stop":"non-stop"]
+ with_test_prefix "$stop_mode" {
+ test $non_stop
+ }
+}
--- /dev/null
+/* This testcase is part of GDB, the GNU debugger.
+
+ Copyright 2021 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/>. */
+
+#define _GNU_SOURCE
+#include <assert.h>
+#include <pthread.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <unistd.h>
+#include <string.h>
+
+#define THREADS 20
+
+static volatile unsigned int global_var = 123;
+
+/* Wrapper around pthread_create. */
+
+static void
+create_thread (pthread_t *child,
+ void *(*start_routine) (void *), void *arg)
+{
+ int rc;
+
+ while ((rc = pthread_create (child, NULL, start_routine, arg)) != 0)
+ {
+ fprintf (stderr, "unexpected error from pthread_create: %s (%d)\n",
+ strerror (rc), rc);
+ sleep (1);
+ }
+}
+
+/* Data passed to threads on creation. This is allocated on the heap
+ and ownership transferred from parent to child. */
+
+struct thread_arg
+{
+ /* The thread's parent. */
+ pthread_t parent;
+
+ /* Whether to call pthread_join on the parent. */
+ int join_parent;
+};
+
+/* Entry point for threads. */
+
+static void *
+thread_fn (void *arg)
+{
+ struct thread_arg *p = arg;
+
+ /* Passing no argument makes the thread exit immediately. */
+ if (p == NULL)
+ return NULL;
+
+ if (p->join_parent)
+ assert (pthread_join (p->parent, NULL) == 0);
+
+ /* Spawn a number of threads that exit immediately, and then join
+ them. The idea is to maximize the time window when we mostly
+ have threads exiting. */
+ {
+ pthread_t child[THREADS];
+ int i;
+
+ /* Passing no argument makes the thread exit immediately. */
+ for (i = 0; i < THREADS; i++)
+ create_thread (&child[i], thread_fn, NULL);
+
+ for (i = 0; i < THREADS; i++)
+ pthread_join (child[i], NULL);
+ }
+
+ /* Spawn a new thread that joins us, and exit. The idea here is to
+ not have any thread that stays around forever. */
+ {
+ pthread_t child;
+
+ p->parent = pthread_self ();
+ p->join_parent = 1;
+ create_thread (&child, thread_fn, p);
+ }
+
+ return NULL;
+}
+
+int
+main (void)
+{
+ int i;
+
+ for (i = 0; i < 4; i++)
+ {
+ struct thread_arg *p;
+ pthread_t child;
+
+ p = malloc (sizeof *p);
+ p->parent = pthread_self ();
+ /* Only join the parent once. */
+ if (i == 0)
+ p->join_parent = 1;
+ else
+ p->join_parent = 0;
+ create_thread (&child, thread_fn, p);
+ }
+
+ /* Exit the leader to make sure that we can access memory with the
+ leader gone. */
+ pthread_exit (NULL);
+}
--- /dev/null
+# Copyright (C) 2021 Free Software Foundation, Inc.
+
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+# Test that we can access memory while all the threads of the inferior
+# are running, and even if:
+#
+# - the leader thread exits
+# - the selected thread exits
+#
+# This test constantly spawns short lived threads to make sure that on
+# systems with debug APIs that require passing down a specific thread
+# to work with (e.g., GNU/Linux ptrace and /proc filesystem), GDB
+# copes with accessing memory just while the thread it is accessing
+# memory through exits.
+#
+# The test spawns two processes and alternates memory accesses between
+# them to force flushing per-process caches. At the time of writing,
+# the Linux backend accesses inferior memory via /proc/<pid>/mem, and
+# keeps one such file open, as a cache. Alternating inferiors forces
+# opening such file for a different process, which fails if GDB tries
+# to open the file for a thread that exited. The test does ensures
+# those reopen/fail code paths are exercised.
+
+standard_testfile
+
+if {[prepare_for_testing "failed to prepare" $testfile $srcfile {debug pthreads}] == -1} {
+ return -1
+}
+
+# The test proper. NON_STOP indicates whether we're testing in
+# non-stop, or all-stop mode.
+
+proc test { non_stop } {
+ global binfile
+ global gdb_prompt
+ global GDBFLAGS
+
+ save_vars { GDBFLAGS } {
+ append GDBFLAGS " -ex \"set non-stop $non_stop\""
+ clean_restart ${binfile}
+ }
+
+ if ![runto_main] {
+ fail "cannot run to main"
+ return -1
+ }
+
+ # If debugging with target remote, check whether the all-stop variant
+ # of the RSP is being used. If so, we can't run the background tests.
+ if {!$non_stop
+ && [target_info exists gdb_protocol]
+ && ([target_info gdb_protocol] == "remote"
+ || [target_info gdb_protocol] == "extended-remote")} {
+
+ gdb_test_multiple "maint show target-non-stop" "" {
+ -wrap -re "(is|currently) on.*" {
+ }
+ -wrap -re "(is|currently) off.*" {
+ unsupported "can't issue commands while target is running"
+ return 0
+ }
+ }
+ }
+
+ delete_breakpoints
+
+ # Start the second inferior.
+ with_test_prefix "second inferior" {
+ gdb_test "add-inferior -no-connection" "New inferior 2.*"
+ gdb_test "inferior 2" "Switching to inferior 2 .*"
+
+ gdb_load $binfile
+
+ if ![runto_main] {
+ fail "cannot run to main"
+ return -1
+ }
+ }
+
+ delete_breakpoints
+
+ # These put too much noise in the logs.
+ gdb_test_no_output "set print thread-events off"
+
+ # Continue all threads of both processes.
+ gdb_test_no_output "set schedule-multiple on"
+ if {$non_stop == "off"} {
+ set cmd "continue &"
+ } else {
+ set cmd "continue -a &"
+ }
+ gdb_test_multiple $cmd "continuing" {
+ -re "Continuing\.\r\n$gdb_prompt " {
+ pass $gdb_test_name
+ }
+ }
+
+ # Like gdb_test, but:
+ # - don't issue a pass on success.
+ # - on failure, clear the ok variable in the calling context, and
+ # break it.
+ proc my_gdb_test {cmd pattern message} {
+ upvar inf inf
+ upvar iter iter
+ if {[gdb_test_multiple $cmd "access mem ($message, inf=$inf, iter=$iter)" {
+ -wrap -re $pattern {
+ }
+ }] != 0} {
+ uplevel 1 {set ok 0}
+ return -code break
+ }
+ }
+
+ # Hammer away for 5 seconds, alternating between inferiors.
+ set ::done 0
+ after 5000 { set ::done 1 }
+
+ set inf 1
+ set ok 1
+ set iter 0
+ while {!$::done && $ok} {
+ incr iter
+ verbose -log "xxxxx: iteration $iter"
+ gdb_test "info threads" ".*" ""
+
+ if {$inf == 1} {
+ set inf 2
+ } else {
+ set inf 1
+ }
+
+ my_gdb_test "inferior $inf" ".*" "inferior $inf"
+
+ my_gdb_test "print global_var = 555" " = 555" \
+ "write to global_var"
+ my_gdb_test "print global_var" " = 555" \
+ "print global_var after writing"
+ my_gdb_test "print global_var = 333" " = 333" \
+ "write to global_var again"
+ my_gdb_test "print global_var" " = 333" \
+ "print global_var after writing again"
+ }
+
+ if {$ok} {
+ pass "access mem"
+ }
+}
+
+foreach non_stop { "off" "on" } {
+ set stop_mode [expr ($non_stop=="off")?"all-stop":"non-stop"]
+ with_test_prefix "$stop_mode" {
+ test $non_stop
+ }
+}