mirror of
https://sourceware.org/git/binutils-gdb.git
synced 2024-12-21 04:42:53 +08:00
e671cd59d7
In my multi-target branch I ran into problems with GDB's terminal handling that exist in master as well, with multi-inferior debugging. This patch adds a testcase for said problems (gdb.multi/multi-term-settings.exp), fixes the problems, fixes PR gdb/13211 as well (and adds a testcase for that too, gdb.base/interrupt-daemon.exp). The basis of the problem I ran into is the following. Consider a scenario where you have: - inferior 1 - started with "attach", process is running on some other terminal. - inferior 2 - started with "run", process is sharing gdb's terminal. In this scenario, when you stop/resume both inferiors, you want GDB to save/restore the terminal settings of inferior 2, the one that is sharing GDB's terminal. I.e., you want inferior 2 to "own" the terminal (in target_terminal::is_ours/target_terminal::is_inferior sense). Unfortunately, that's not what you get currently. Because GDB doesn't know whether an attached inferior is actually sharing GDB's terminal, it tries to save/restore its settings anyway, ignoring errors. In this case, this is pointless, because inferior 1 is running on a different terminal, but GDB doesn't know better. And then, because it is only possible to have the terminal settings of a single inferior be in effect at a time, or make one inferior/pgrp be the terminal's foreground pgrp (aka, only one inferior can "own" the terminal, ignoring fork children here), if GDB happens to try to restore the terminal settings of inferior 1 first, then GDB never restores the terminal settings of inferior 2. This patch fixes that and a few things more along the way: - Moves enum target_terminal::terminal_state out of the target_terminal class (it's currently private) and makes it a scoped enum so that it can be easily used elsewhere. - Replaces the inflow.c:terminal_is_ours boolean with a target_terminal_state variable. This allows distinguishing is_ours and is_ours_for_output states. This allows finally making child_terminal_ours_1 do something with its "output_only" parameter. - Makes each inferior have its own copy of the is_ours/is_ours_for_output/is_inferior state. - Adds a way for GDB to tell whether the inferior is sharing GDB's terminal. Works best on Linux and Solaris; the fallback works just as well as currently. - With that, we can remove the inf->attach_flag tests from child_terminal_inferior/child_terminal_ours. - Currently target_ops.to_ours is responsible for both saving the current inferior's terminal state, and restoring gdb's state. Because each inferior has its own terminal state (possibly handled by different targets in a multi-target world, even), we need to split the inferior-saving part from the gdb-restoring part. The patch adds a new target_ops.to_save_inferior target method for that. - Adds a new target_terminal::save_inferior() function, so that sequences like: scoped_restore_terminal_state save_state; target_terminal::ours_for_output (); ... restore back inferiors that were target_terminal_state::is_inferior before back to is_inferior, and leaves inferiors that were is_ours alone. - Along the way, this adds a default implementation of target_pass_ctrlc to inflow.c (for inf-child.c), that handles passing the Ctrl-C to a process running on GDB's terminal or to some other process otherwise. - Similarly, adds a new target default implementation of target_interrupt, for the "interrupt" command. The current implementation of this hook in inf-ptrace.c kills the whole process group, but that's incorrect/undesirable because we may not be attached to all processes in the process group. And also, it's incorrect because inferior_process_group() doesn't really return the inferior's real process group id if the inferior is not a process group leader... This is the cause of PR gdb/13211 [1], which this patch fixes. While at it, that target method's "ptid" parameter is eliminated, because it's not really used. - A new test is included that exercises and fixes PR gdb/13211, and also fixes a GDB issue reported on stackoverflow that I ran into while working on this [2]. The problem is similar to PR gdb/13211, except that it also triggers with Ctrl-C. When debugging a daemon (i.e., a process that disconnects from the controlling terminal and is not a process group leader, then Ctrl-C doesn't work, you just can't interrupt the inferior at all, resulting in a hung debug session. The problem is that since the inferior is no longer associated with gdb's session / controlling terminal, then trying to put the inferior in the foreground fails. And so Ctrl-C never reaches the inferior directly. pass_signal is only used when the inferior is attached, but that is not the case here. This is fixed by the new child_pass_ctrlc. Without the fix, the new interrupt-daemon.exp testcase fails with timeout waiting for a SIGINT that never arrives. [1] PR gdb/13211 - Async / Process group and interrupt not working https://sourceware.org/bugzilla/show_bug.cgi?id=13211 [2] GDB not reacting Ctrl-C when after fork() and setsid() https://stackoverflow.com/questions/46101292/gdb-not-reacting-ctrl-c-when-after-fork-and-setsid Note this patch does _not_ fix: - PR gdb/14559 - The 'interrupt' command does not work if sigwait is in use https://sourceware.org/bugzilla/show_bug.cgi?id=14559 - PR gdb/9425 - When using "sigwait" GDB doesn't trap SIGINT. Ctrl+C terminates program when should break gdb. https://sourceware.org/bugzilla/show_bug.cgi?id=9425 The only way to fix that that I know of (without changing the kernel) is to make GDB put inferiors in a separate session (create a pseudo-tty master/slave pair, make the inferior run with the slave as its terminal, and have gdb pump output/input on the master end). gdb/ChangeLog: 2018-01-30 Pedro Alves <palves@redhat.com> PR gdb/13211 * config.in, configure: Regenerate. * configure.ac: Check for getpgid. * go32-nat.c (go32_pass_ctrlc): New. (go32_target): Install it. * inf-child.c (inf_child_target): Install child_terminal_save_inferior, child_pass_ctrlc and child_interrupt. * inf-ptrace.c (inf_ptrace_interrupt): Delete. (inf_ptrace_target): No longer install it. * infcmd.c (interrupt_target_1): Adjust. * inferior.h (child_terminal_save_inferior, child_pass_ctrlc) (child_interrupt): Declare. (inferior::terminal_state): New. * inflow.c (struct terminal_info): Update comments. (inferior_process_group): Delete. (terminal_is_ours): Delete. (gdb_tty_state): New. (child_terminal_init): Adjust. (is_gdb_terminal, sharing_input_terminal_1) (sharing_input_terminal): New functions. (child_terminal_inferior): Adjust. Use sharing_input_terminal. Set the process's actual process group in the foreground if possible. Handle is_ours_for_output/is_ours distinction. Don't mark terminal as the inferior's if not sharing GDB's terminal. Don't check attach_flag. (child_terminal_ours_for_output, child_terminal_ours): Adjust to pass down a target_terminal_state. (child_terminal_save_inferior): New, factored out from ... (child_terminal_ours_1): ... this. Handle target_terminal_state::is_ours_for_output. (child_interrupt, child_pass_ctrlc): New. (inflow_inferior_exit): Clear the inferior's terminal_state. (copy_terminal_info): Copy the inferior's terminal state. (_initialize_inflow): Remove reference to terminal_is_ours. * inflow.h (inferior_process_group): Delete. * nto-procfs.c (nto_handle_sigint, procfs_interrupt): Adjust. * procfs.c (procfs_target): Don't install procfs_interrupt. (procfs_interrupt): Delete. * remote.c (remote_serial_quit_handler): Adjust. (remote_interrupt): Remove ptid parameter. Adjust. * target-delegates.c: Regenerate. * target.c: Include "terminal.h". (target_terminal::terminal_state): Rename to ... (target_terminal::m_terminal_state): ... this. (target_terminal::init): Adjust. (target_terminal::inferior): Adjust to per-inferior terminal_state. (target_terminal::restore_inferior, target_terminal_is_ours_kind): New. (target_terminal::ours, target_terminal::ours_for_output): Use target_terminal_is_ours_kind. (target_interrupt): Remove ptid parameter. Adjust. (default_target_pass_ctrlc): Adjust. * target.h (target_ops::to_terminal_save_inferior): New field. (target_ops::to_interrupt): Remove ptid_t parameter. (target_interrupt): Remove ptid_t parameter. Update comment. (target_pass_ctrlc): Update comment. * target/target.h (target_terminal_state): New scoped enum, factored out of ... (target_terminal::terminal_state): ... here. (target_terminal::inferior): Update comments. (target_terminal::restore_inferior): New. (target_terminal::is_inferior, target_terminal::is_ours) (target_terminal::is_ours_for_output): Adjust. (target_terminal::scoped_restore_terminal_state): Adjust to rename, and call restore_inferior() instead of inferior(). (target_terminal::scoped_restore_terminal_state::m_state): Change type. (target_terminal::terminal_state): Rename to ... (target_terminal::m_terminal_state): ... this and change type. gdb/gdbserver/ChangeLog: 2018-01-30 Pedro Alves <palves@redhat.com> PR gdb/13211 * target.c (target_terminal::terminal_state): Rename to ... (target_terminal::m_terminal_state): ... this. gdb/testsuite/ChangeLog: 2018-01-30 Pedro Alves <palves@redhat.com> PR gdb/13211 * gdb.base/interrupt-daemon.c: New. * gdb.base/interrupt-daemon.exp: New. * gdb.multi/multi-term-settings.c: New. * gdb.multi/multi-term-settings.exp: New.
408 lines
9.3 KiB
C
408 lines
9.3 KiB
C
/* Target operations for the remote server for GDB.
|
|
Copyright (C) 2002-2018 Free Software Foundation, Inc.
|
|
|
|
Contributed by MontaVista Software.
|
|
|
|
This file is part of GDB.
|
|
|
|
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 "server.h"
|
|
#include "tracepoint.h"
|
|
|
|
struct target_ops *the_target;
|
|
|
|
int
|
|
set_desired_thread ()
|
|
{
|
|
thread_info *found = find_thread_ptid (general_thread);
|
|
|
|
current_thread = found;
|
|
return (current_thread != NULL);
|
|
}
|
|
|
|
/* The thread that was current before prepare_to_access_memory was
|
|
called. done_accessing_memory uses this to restore the previous
|
|
selected thread. */
|
|
static ptid_t prev_general_thread;
|
|
|
|
/* See target.h. */
|
|
|
|
int
|
|
prepare_to_access_memory (void)
|
|
{
|
|
/* The first thread found. */
|
|
struct thread_info *first = NULL;
|
|
/* The first stopped thread found. */
|
|
struct thread_info *stopped = NULL;
|
|
/* The current general thread, if found. */
|
|
struct thread_info *current = NULL;
|
|
|
|
/* Save the general thread value, since prepare_to_access_memory could change
|
|
it. */
|
|
prev_general_thread = general_thread;
|
|
|
|
if (the_target->prepare_to_access_memory != NULL)
|
|
{
|
|
int res;
|
|
|
|
res = the_target->prepare_to_access_memory ();
|
|
if (res != 0)
|
|
return res;
|
|
}
|
|
|
|
for_each_thread (prev_general_thread.pid (), [&] (thread_info *thread)
|
|
{
|
|
if (mythread_alive (thread->id))
|
|
{
|
|
if (stopped == NULL && the_target->thread_stopped != NULL
|
|
&& thread_stopped (thread))
|
|
stopped = thread;
|
|
|
|
if (first == NULL)
|
|
first = thread;
|
|
|
|
if (current == NULL && prev_general_thread == thread->id)
|
|
current = thread;
|
|
}
|
|
});
|
|
|
|
/* The thread we end up choosing. */
|
|
struct thread_info *thread;
|
|
|
|
/* Prefer a stopped thread. If none is found, try the current
|
|
thread. Otherwise, take the first thread in the process. If
|
|
none is found, undo the effects of
|
|
target->prepare_to_access_memory() and return error. */
|
|
if (stopped != NULL)
|
|
thread = stopped;
|
|
else if (current != NULL)
|
|
thread = current;
|
|
else if (first != NULL)
|
|
thread = first;
|
|
else
|
|
{
|
|
done_accessing_memory ();
|
|
return 1;
|
|
}
|
|
|
|
current_thread = thread;
|
|
general_thread = ptid_of (thread);
|
|
|
|
return 0;
|
|
}
|
|
|
|
/* See target.h. */
|
|
|
|
void
|
|
done_accessing_memory (void)
|
|
{
|
|
if (the_target->done_accessing_memory != NULL)
|
|
the_target->done_accessing_memory ();
|
|
|
|
/* Restore the previous selected thread. */
|
|
general_thread = prev_general_thread;
|
|
switch_to_thread (general_thread);
|
|
}
|
|
|
|
int
|
|
read_inferior_memory (CORE_ADDR memaddr, unsigned char *myaddr, int len)
|
|
{
|
|
int res;
|
|
res = (*the_target->read_memory) (memaddr, myaddr, len);
|
|
check_mem_read (memaddr, myaddr, len);
|
|
return res;
|
|
}
|
|
|
|
/* See target/target.h. */
|
|
|
|
int
|
|
target_read_memory (CORE_ADDR memaddr, gdb_byte *myaddr, ssize_t len)
|
|
{
|
|
return read_inferior_memory (memaddr, myaddr, len);
|
|
}
|
|
|
|
/* See target/target.h. */
|
|
|
|
int
|
|
target_read_uint32 (CORE_ADDR memaddr, uint32_t *result)
|
|
{
|
|
return read_inferior_memory (memaddr, (gdb_byte *) result, sizeof (*result));
|
|
}
|
|
|
|
int
|
|
write_inferior_memory (CORE_ADDR memaddr, const unsigned char *myaddr,
|
|
int len)
|
|
{
|
|
/* Lacking cleanups, there is some potential for a memory leak if the
|
|
write fails and we go through error(). Make sure that no more than
|
|
one buffer is ever pending by making BUFFER static. */
|
|
static unsigned char *buffer = 0;
|
|
int res;
|
|
|
|
if (buffer != NULL)
|
|
free (buffer);
|
|
|
|
buffer = (unsigned char *) xmalloc (len);
|
|
memcpy (buffer, myaddr, len);
|
|
check_mem_write (memaddr, buffer, myaddr, len);
|
|
res = (*the_target->write_memory) (memaddr, buffer, len);
|
|
free (buffer);
|
|
buffer = NULL;
|
|
|
|
return res;
|
|
}
|
|
|
|
/* See target/target.h. */
|
|
|
|
int
|
|
target_write_memory (CORE_ADDR memaddr, const gdb_byte *myaddr, ssize_t len)
|
|
{
|
|
return write_inferior_memory (memaddr, myaddr, len);
|
|
}
|
|
|
|
ptid_t
|
|
mywait (ptid_t ptid, struct target_waitstatus *ourstatus, int options,
|
|
int connected_wait)
|
|
{
|
|
ptid_t ret;
|
|
|
|
if (connected_wait)
|
|
server_waiting = 1;
|
|
|
|
ret = target_wait (ptid, ourstatus, options);
|
|
|
|
/* We don't expose _LOADED events to gdbserver core. See the
|
|
`dlls_changed' global. */
|
|
if (ourstatus->kind == TARGET_WAITKIND_LOADED)
|
|
ourstatus->kind = TARGET_WAITKIND_STOPPED;
|
|
|
|
/* If GDB is connected through TCP/serial, then GDBserver will most
|
|
probably be running on its own terminal/console, so it's nice to
|
|
print there why is GDBserver exiting. If however, GDB is
|
|
connected through stdio, then there's no need to spam the GDB
|
|
console with this -- the user will already see the exit through
|
|
regular GDB output, in that same terminal. */
|
|
if (!remote_connection_is_stdio ())
|
|
{
|
|
if (ourstatus->kind == TARGET_WAITKIND_EXITED)
|
|
fprintf (stderr,
|
|
"\nChild exited with status %d\n", ourstatus->value.integer);
|
|
else if (ourstatus->kind == TARGET_WAITKIND_SIGNALLED)
|
|
fprintf (stderr, "\nChild terminated with signal = 0x%x (%s)\n",
|
|
gdb_signal_to_host (ourstatus->value.sig),
|
|
gdb_signal_to_name (ourstatus->value.sig));
|
|
}
|
|
|
|
if (connected_wait)
|
|
server_waiting = 0;
|
|
|
|
return ret;
|
|
}
|
|
|
|
/* See target/target.h. */
|
|
|
|
void
|
|
target_stop_and_wait (ptid_t ptid)
|
|
{
|
|
struct target_waitstatus status;
|
|
int was_non_stop = non_stop;
|
|
struct thread_resume resume_info;
|
|
|
|
resume_info.thread = ptid;
|
|
resume_info.kind = resume_stop;
|
|
resume_info.sig = GDB_SIGNAL_0;
|
|
(*the_target->resume) (&resume_info, 1);
|
|
|
|
non_stop = 1;
|
|
mywait (ptid, &status, 0, 0);
|
|
non_stop = was_non_stop;
|
|
}
|
|
|
|
/* See target/target.h. */
|
|
|
|
ptid_t
|
|
target_wait (ptid_t ptid, struct target_waitstatus *status, int options)
|
|
{
|
|
return (*the_target->wait) (ptid, status, options);
|
|
}
|
|
|
|
/* See target/target.h. */
|
|
|
|
void
|
|
target_mourn_inferior (ptid_t ptid)
|
|
{
|
|
(*the_target->mourn) (find_process_pid (ptid_get_pid (ptid)));
|
|
}
|
|
|
|
/* See target/target.h. */
|
|
|
|
void
|
|
target_continue_no_signal (ptid_t ptid)
|
|
{
|
|
struct thread_resume resume_info;
|
|
|
|
resume_info.thread = ptid;
|
|
resume_info.kind = resume_continue;
|
|
resume_info.sig = GDB_SIGNAL_0;
|
|
(*the_target->resume) (&resume_info, 1);
|
|
}
|
|
|
|
/* See target/target.h. */
|
|
|
|
void
|
|
target_continue (ptid_t ptid, enum gdb_signal signal)
|
|
{
|
|
struct thread_resume resume_info;
|
|
|
|
resume_info.thread = ptid;
|
|
resume_info.kind = resume_continue;
|
|
resume_info.sig = gdb_signal_to_host (signal);
|
|
(*the_target->resume) (&resume_info, 1);
|
|
}
|
|
|
|
/* See target/target.h. */
|
|
|
|
int
|
|
target_supports_multi_process (void)
|
|
{
|
|
return (the_target->supports_multi_process != NULL ?
|
|
(*the_target->supports_multi_process) () : 0);
|
|
}
|
|
|
|
int
|
|
start_non_stop (int nonstop)
|
|
{
|
|
if (the_target->start_non_stop == NULL)
|
|
{
|
|
if (nonstop)
|
|
return -1;
|
|
else
|
|
return 0;
|
|
}
|
|
|
|
return (*the_target->start_non_stop) (nonstop);
|
|
}
|
|
|
|
void
|
|
set_target_ops (struct target_ops *target)
|
|
{
|
|
the_target = XNEW (struct target_ops);
|
|
memcpy (the_target, target, sizeof (*the_target));
|
|
}
|
|
|
|
/* Convert pid to printable format. */
|
|
|
|
const char *
|
|
target_pid_to_str (ptid_t ptid)
|
|
{
|
|
static char buf[80];
|
|
|
|
if (ptid_equal (ptid, minus_one_ptid))
|
|
xsnprintf (buf, sizeof (buf), "<all threads>");
|
|
else if (ptid_equal (ptid, null_ptid))
|
|
xsnprintf (buf, sizeof (buf), "<null thread>");
|
|
else if (ptid_get_tid (ptid) != 0)
|
|
xsnprintf (buf, sizeof (buf), "Thread %d.0x%lx",
|
|
ptid_get_pid (ptid), ptid_get_tid (ptid));
|
|
else if (ptid_get_lwp (ptid) != 0)
|
|
xsnprintf (buf, sizeof (buf), "LWP %d.%ld",
|
|
ptid_get_pid (ptid), ptid_get_lwp (ptid));
|
|
else
|
|
xsnprintf (buf, sizeof (buf), "Process %d",
|
|
ptid_get_pid (ptid));
|
|
|
|
return buf;
|
|
}
|
|
|
|
int
|
|
kill_inferior (int pid)
|
|
{
|
|
gdb_agent_about_to_close (pid);
|
|
|
|
return (*the_target->kill) (pid);
|
|
}
|
|
|
|
/* Target can do hardware single step. */
|
|
|
|
int
|
|
target_can_do_hardware_single_step (void)
|
|
{
|
|
return 1;
|
|
}
|
|
|
|
/* Default implementation for breakpoint_kind_for_pc.
|
|
|
|
The default behavior for targets that don't implement breakpoint_kind_for_pc
|
|
is to use the size of a breakpoint as the kind. */
|
|
|
|
int
|
|
default_breakpoint_kind_from_pc (CORE_ADDR *pcptr)
|
|
{
|
|
int size = 0;
|
|
|
|
gdb_assert (the_target->sw_breakpoint_from_kind != NULL);
|
|
|
|
(*the_target->sw_breakpoint_from_kind) (0, &size);
|
|
return size;
|
|
}
|
|
|
|
/* Define it. */
|
|
|
|
target_terminal_state target_terminal::m_terminal_state
|
|
= target_terminal_state::is_ours;
|
|
|
|
/* See target/target.h. */
|
|
|
|
void
|
|
target_terminal::init ()
|
|
{
|
|
/* Placeholder needed because of fork_inferior. Not necessary on
|
|
GDBserver. */
|
|
}
|
|
|
|
/* See target/target.h. */
|
|
|
|
void
|
|
target_terminal::inferior ()
|
|
{
|
|
/* Placeholder needed because of fork_inferior. Not necessary on
|
|
GDBserver. */
|
|
}
|
|
|
|
/* See target/target.h. */
|
|
|
|
void
|
|
target_terminal::ours ()
|
|
{
|
|
/* Placeholder needed because of fork_inferior. Not necessary on
|
|
GDBserver. */
|
|
}
|
|
|
|
/* See target/target.h. */
|
|
|
|
void
|
|
target_terminal::ours_for_output (void)
|
|
{
|
|
/* Placeholder. */
|
|
}
|
|
|
|
/* See target/target.h. */
|
|
|
|
void
|
|
target_terminal::info (const char *arg, int from_tty)
|
|
{
|
|
/* Placeholder. */
|
|
}
|