#define O_LARGEFILE 0
#endif
+/* If the system headers did not provide the constants, hard-code the normal
+ values. */
+#ifndef PTRACE_EVENT_FORK
+
+#define PTRACE_SETOPTIONS 0x4200
+#define PTRACE_GETEVENTMSG 0x4201
+
+/* options set using PTRACE_SETOPTIONS */
+#define PTRACE_O_TRACESYSGOOD 0x00000001
+#define PTRACE_O_TRACEFORK 0x00000002
+#define PTRACE_O_TRACEVFORK 0x00000004
+#define PTRACE_O_TRACECLONE 0x00000008
+#define PTRACE_O_TRACEEXEC 0x00000010
+#define PTRACE_O_TRACEVFORKDONE 0x00000020
+#define PTRACE_O_TRACEEXIT 0x00000040
+
+/* Wait extended result codes for the above trace options. */
+#define PTRACE_EVENT_FORK 1
+#define PTRACE_EVENT_VFORK 2
+#define PTRACE_EVENT_CLONE 3
+#define PTRACE_EVENT_EXEC 4
+#define PTRACE_EVENT_VFORK_DONE 5
+#define PTRACE_EVENT_EXIT 6
+
+#endif /* PTRACE_EVENT_FORK */
+
+/* We can't always assume that this flag is available, but all systems
+ with the ptrace event handlers also have __WALL, so it's safe to use
+ in some contexts. */
+#ifndef __WALL
+#define __WALL 0x40000000 /* Wait for any child. */
+#endif
+
#ifdef __UCLIBC__
#if !(defined(__UCLIBC_HAS_MMU__) || defined(__ARCH_HAS_MMU__))
#define HAS_NOMMU
#endif
#endif
-/* ``all_threads'' is keyed by the LWP ID - it should be the thread ID instead,
- however. This requires changing the ID in place when we go from !using_threads
- to using_threads, immediately.
+/* ``all_threads'' is keyed by the LWP ID, which we use as the GDB protocol
+ representation of the thread ID.
``all_processes'' is keyed by the process ID - which on Linux is (presently)
the same as the LWP ID. */
struct inferior_list all_processes;
+/* A list of all unknown processes which receive stop signals. Some other
+ process will presumably claim each of these as forked children
+ momentarily. */
+
+struct inferior_list stopped_pids;
+
/* FIXME this is a bit of a hack, and could be removed. */
int stopping_threads;
/* FIXME make into a target method? */
-int using_threads;
+int using_threads = 1;
+static int thread_db_active;
+
+static int must_set_ptrace_flags;
static void linux_resume_one_process (struct inferior_list_entry *entry,
int step, int signal, siginfo_t *info);
static void stop_all_processes (void);
static int linux_wait_for_event (struct thread_info *child);
static int check_removed_breakpoint (struct process_info *event_child);
+static void *add_process (unsigned long pid);
struct pending_signals
{
/* FIXME: Delete eventually. */
#define inferior_pid (pid_of (get_thread_process (current_inferior)))
+static void
+handle_extended_wait (struct process_info *event_child, int wstat)
+{
+ int event = wstat >> 16;
+ struct process_info *new_process;
+
+ if (event == PTRACE_EVENT_CLONE)
+ {
+ unsigned long new_pid;
+ int ret, status;
+
+ ptrace (PTRACE_GETEVENTMSG, inferior_pid, 0, &new_pid);
+
+ /* If we haven't already seen the new PID stop, wait for it now. */
+ if (! pull_pid_from_list (&stopped_pids, new_pid))
+ {
+ /* The new child has a pending SIGSTOP. We can't affect it until it
+ hits the SIGSTOP, but we're already attached. */
+
+ do {
+ ret = waitpid (new_pid, &status, __WALL);
+ } while (ret == -1 && errno == EINTR);
+
+ if (ret == -1)
+ perror_with_name ("waiting for new child");
+ else if (ret != new_pid)
+ warning ("wait returned unexpected PID %d", ret);
+ else if (!WIFSTOPPED (status) || WSTOPSIG (status) != SIGSTOP)
+ warning ("wait returned unexpected status 0x%x", status);
+ }
+
+ ptrace (PTRACE_SETOPTIONS, new_pid, 0, PTRACE_O_TRACECLONE);
+
+ new_process = (struct process_info *) add_process (new_pid);
+ add_thread (new_pid, new_process, new_pid);
+ new_thread_notify (thread_id_to_gdb_id (new_process->lwpid));
+
+ if (stopping_threads)
+ new_process->stopped = 1;
+ else
+ ptrace (PTRACE_CONT, new_pid, 0, 0);
+
+ /* Always resume the current thread. If we are stopping
+ threads, it will have a pending SIGSTOP; we may as well
+ collect it now. */
+ linux_resume_one_process (&event_child->head,
+ event_child->stepping, 0, NULL);
+ }
+}
+
/* This function should only be called if the process got a SIGTRAP.
The SIGTRAP could mean several things.
memset (process, 0, sizeof (*process));
process->head.id = pid;
-
- /* Default to tid == lwpid == pid. */
- process->tid = pid;
process->lwpid = pid;
add_inferior_to_list (&all_processes, &process->head);
new_process = add_process (pid);
add_thread (pid, new_process, pid);
+ must_set_ptrace_flags = 1;
return pid;
}
/* Attach to an inferior process. */
void
-linux_attach_lwp (unsigned long pid, unsigned long tid)
+linux_attach_lwp (unsigned long pid)
{
struct process_info *new_process;
fflush (stderr);
/* If we fail to attach to an LWP, just return. */
- if (!using_threads)
+ if (all_threads.head == NULL)
_exit (0177);
return;
}
+ ptrace (PTRACE_SETOPTIONS, pid, 0, PTRACE_O_TRACECLONE);
+
new_process = (struct process_info *) add_process (pid);
- add_thread (tid, new_process, pid);
+ add_thread (pid, new_process, pid);
+ new_thread_notify (thread_id_to_gdb_id (new_process->lwpid));
/* The next time we wait for this LWP we'll see a SIGSTOP as PTRACE_ATTACH
brings it to a halt. We should ignore that SIGSTOP and resume the process
{
struct process_info *process;
- linux_attach_lwp (pid, pid);
+ linux_attach_lwp (pid);
/* Don't ignore the initial SIGSTOP if we just attached to this process.
It will be collected by wait shortly. */
/* Return nonzero if the given thread is still alive. */
static int
-linux_thread_alive (unsigned long tid)
+linux_thread_alive (unsigned long lwpid)
{
- if (find_inferior_id (&all_threads, tid) != NULL)
+ if (find_inferior_id (&all_threads, lwpid) != NULL)
return 1;
else
return 0;
if (*childp != NULL)
to_wait_for = (*childp)->lwpid;
+retry:
while (1)
{
ret = waitpid (to_wait_for, wstatp, WNOHANG);
if (to_wait_for == -1)
*childp = (struct process_info *) find_inferior_id (&all_processes, ret);
+ /* If we didn't find a process, one of two things presumably happened:
+ - A process we started and then detached from has exited. Ignore it.
+ - A process we are controlling has forked and the new child's stop
+ was reported to us by the kernel. Save its PID. */
+ if (*childp == NULL && WIFSTOPPED (*wstatp))
+ {
+ add_pid_to_list (&stopped_pids, ret);
+ goto retry;
+ }
+ else if (*childp == NULL)
+ goto retry;
+
(*childp)->stopped = 1;
(*childp)->pending_is_breakpoint = 0;
&& WIFSTOPPED (*wstatp))
{
current_inferior = (struct thread_info *)
- find_inferior_id (&all_threads, (*childp)->tid);
+ find_inferior_id (&all_threads, (*childp)->lwpid);
/* For testing only; i386_stop_pc prints out a diagnostic. */
if (the_low_target.get_pc != NULL)
get_stop_pc ();
error ("event from unknown child");
current_inferior = (struct thread_info *)
- find_inferior_id (&all_threads, event_child->tid);
+ find_inferior_id (&all_threads, event_child->lwpid);
/* Check for thread exit. */
- if (using_threads && ! WIFSTOPPED (wstat))
+ if (! WIFSTOPPED (wstat))
{
if (debug_threads)
- fprintf (stderr, "Thread %ld (LWP %ld) exiting\n",
- event_child->tid, event_child->head.id);
+ fprintf (stderr, "LWP %ld exiting\n", event_child->head.id);
/* If the last thread is exiting, just return. */
if (all_threads.head == all_threads.tail)
return wstat;
- dead_thread_notify (event_child->tid);
+ dead_thread_notify (thread_id_to_gdb_id (event_child->lwpid));
remove_inferior (&all_processes, &event_child->head);
free (event_child);
continue;
}
- if (using_threads
- && WIFSTOPPED (wstat)
+ if (WIFSTOPPED (wstat)
&& WSTOPSIG (wstat) == SIGSTOP
&& event_child->stop_expected)
{
continue;
}
+ if (WIFSTOPPED (wstat) && WSTOPSIG (wstat) == SIGTRAP
+ && wstat >> 16 != 0)
+ {
+ handle_extended_wait (event_child, wstat);
+ continue;
+ }
+
/* If GDB is not interested in this signal, don't stop other
threads, and don't report it to GDB. Just resume the
inferior right away. We do this for threading-related
thread library? */
if (WIFSTOPPED (wstat)
&& !event_child->stepping
- && ((using_threads && (WSTOPSIG (wstat) == __SIGRTMIN
- || WSTOPSIG (wstat) == __SIGRTMIN + 1))
- || (pass_signals[target_signal_from_host (WSTOPSIG (wstat))]
- && (WSTOPSIG (wstat) != SIGSTOP
- || !event_child->sigstop_sent))))
+ && (
+#ifdef USE_THREAD_DB
+ (thread_db_active && (WSTOPSIG (wstat) == __SIGRTMIN
+ || WSTOPSIG (wstat) == __SIGRTMIN + 1))
+ ||
+#endif
+ (pass_signals[target_signal_from_host (WSTOPSIG (wstat))]
+ && (WSTOPSIG (wstat) != SIGSTOP || !stopping_threads))))
{
siginfo_t info, *info_p;
if (debug_threads)
- fprintf (stderr, "Ignored signal %d for %ld (LWP %ld).\n",
- WSTOPSIG (wstat), event_child->tid,
- event_child->head.id);
+ fprintf (stderr, "Ignored signal %d for LWP %ld.\n",
+ WSTOPSIG (wstat), event_child->head.id);
if (ptrace (PTRACE_GETSIGINFO, event_child->lwpid, 0, &info) == 0)
info_p = &info;
stop_all_processes ();
disable_async_io ();
+ if (must_set_ptrace_flags)
+ {
+ ptrace (PTRACE_SETOPTIONS, inferior_pid, 0, PTRACE_O_TRACECLONE);
+ must_set_ptrace_flags = 0;
+ }
+
/* If we are waiting for a particular child, and it exited,
linux_wait_for_event will return its exit status. Similarly if
the last child exited. If this is not the last child, however,
fprintf (stderr, "Sending sigstop to process %ld\n", process->head.id);
kill_lwp (process->head.id, SIGSTOP);
- process->sigstop_sent = 1;
}
static void
saved_inferior = current_inferior;
saved_tid = ((struct inferior_list_entry *) saved_inferior)->id;
thread = (struct thread_info *) find_inferior_id (&all_threads,
- process->tid);
+ process->lwpid);
wstat = linux_wait_for_event (thread);
/* If we stopped with a non-SIGSTOP signal, save it for later
&& WSTOPSIG (wstat) != SIGSTOP)
{
if (debug_threads)
- fprintf (stderr, "Process %ld (thread %ld) "
- "stopped with non-sigstop status %06x\n",
- process->lwpid, process->tid, wstat);
+ fprintf (stderr, "LWP %ld stopped with non-sigstop status %06x\n",
+ process->lwpid, wstat);
process->status_pending_p = 1;
process->status_pending = wstat;
process->stop_expected = 1;
return 0;
}
+static int linux_supports_tracefork_flag;
+
+/* A helper function for linux_test_for_tracefork, called after fork (). */
+
+static void
+linux_tracefork_child (void)
+{
+ ptrace (PTRACE_TRACEME, 0, 0, 0);
+ kill (getpid (), SIGSTOP);
+ fork ();
+ _exit (0);
+}
+
+/* Wrapper function for waitpid which handles EINTR. */
+
+static int
+my_waitpid (int pid, int *status, int flags)
+{
+ int ret;
+ do
+ {
+ ret = waitpid (pid, status, flags);
+ }
+ while (ret == -1 && errno == EINTR);
+
+ return ret;
+}
+
+/* Determine if PTRACE_O_TRACEFORK can be used to follow fork events. Make
+ sure that we can enable the option, and that it had the desired
+ effect. */
+
+static void
+linux_test_for_tracefork (void)
+{
+ int child_pid, ret, status;
+ long second_pid;
+
+ linux_supports_tracefork_flag = 0;
+
+ child_pid = fork ();
+ if (child_pid == -1)
+ perror_with_name ("fork");
+
+ if (child_pid == 0)
+ linux_tracefork_child ();
+
+ ret = my_waitpid (child_pid, &status, 0);
+ if (ret == -1)
+ perror_with_name ("waitpid");
+ else if (ret != child_pid)
+ error ("linux_test_for_tracefork: waitpid: unexpected result %d.", ret);
+ if (! WIFSTOPPED (status))
+ error ("linux_test_for_tracefork: waitpid: unexpected status %d.", status);
+
+ ret = ptrace (PTRACE_SETOPTIONS, child_pid, 0, PTRACE_O_TRACEFORK);
+ if (ret != 0)
+ {
+ ret = ptrace (PTRACE_KILL, child_pid, 0, 0);
+ if (ret != 0)
+ {
+ warning ("linux_test_for_tracefork: failed to kill child");
+ return;
+ }
+
+ ret = my_waitpid (child_pid, &status, 0);
+ if (ret != child_pid)
+ warning ("linux_test_for_tracefork: failed to wait for killed child");
+ else if (!WIFSIGNALED (status))
+ warning ("linux_test_for_tracefork: unexpected wait status 0x%x from "
+ "killed child", status);
+
+ return;
+ }
+
+ ret = ptrace (PTRACE_CONT, child_pid, 0, 0);
+ if (ret != 0)
+ warning ("linux_test_for_tracefork: failed to resume child");
+
+ ret = my_waitpid (child_pid, &status, 0);
+
+ if (ret == child_pid && WIFSTOPPED (status)
+ && status >> 16 == PTRACE_EVENT_FORK)
+ {
+ second_pid = 0;
+ ret = ptrace (PTRACE_GETEVENTMSG, child_pid, 0, &second_pid);
+ if (ret == 0 && second_pid != 0)
+ {
+ int second_status;
+
+ linux_supports_tracefork_flag = 1;
+ my_waitpid (second_pid, &second_status, 0);
+ ret = ptrace (PTRACE_KILL, second_pid, 0, 0);
+ if (ret != 0)
+ warning ("linux_test_for_tracefork: failed to kill second child");
+ my_waitpid (second_pid, &status, 0);
+ }
+ }
+ else
+ warning ("linux_test_for_tracefork: unexpected result from waitpid "
+ "(%d, status 0x%x)", ret, status);
+
+ do
+ {
+ ret = ptrace (PTRACE_KILL, child_pid, 0, 0);
+ if (ret != 0)
+ warning ("linux_test_for_tracefork: failed to kill child");
+ my_waitpid (child_pid, &status, 0);
+ }
+ while (WIFSTOPPED (status));
+}
+
+
static void
linux_look_up_symbols (void)
{
#ifdef USE_THREAD_DB
- if (using_threads)
+ if (thread_db_active)
return;
- using_threads = thread_db_init ();
+ thread_db_active = thread_db_init (!linux_supports_tracefork_flag);
#endif
}
void
initialize_low (void)
{
- using_threads = 0;
+ thread_db_active = 0;
set_target_ops (&linux_target_ops);
set_breakpoint_data (the_low_target.breakpoint,
the_low_target.breakpoint_len);
init_registers ();
linux_init_signals ();
+ linux_test_for_tracefork ();
}
extern int debug_threads;
+static int thread_db_use_events;
+
#ifdef HAVE_THREAD_DB_H
#include <thread_db.h>
#endif
/* Connection to the libthread_db library. */
static td_thragent_t *thread_agent;
-static int find_first_thread (void);
+static int find_one_thread (int);
static int find_new_threads_callback (const td_thrhandle_t *th_p, void *data);
static char *
created threads. */
process = get_thread_process (current_inferior);
if (process->thread_known == 0)
- find_first_thread ();
+ find_one_thread (process->lwpid);
/* msg.event == TD_EVENT_CREATE */
}
static int
-find_first_thread (void)
+find_one_thread (int lwpid)
{
td_thrhandle_t th;
td_thrinfo_t ti;
struct thread_info *inferior;
struct process_info *process;
- inferior = (struct thread_info *) all_threads.head;
+ inferior = (struct thread_info *) find_inferior_id (&all_threads, lwpid);
process = get_thread_process (inferior);
if (process->thread_known)
return 1;
- /* Get information about the one thread we know we have. */
+ /* Get information about this thread. */
err = td_ta_map_lwp2thr (thread_agent, process->lwpid, &th);
if (err != TD_OK)
- error ("Cannot get first thread handle: %s", thread_db_err_str (err));
+ error ("Cannot get thread handle for LWP %d: %s",
+ lwpid, thread_db_err_str (err));
err = td_thr_get_info (&th, &ti);
if (err != TD_OK)
- error ("Cannot get first thread info: %s", thread_db_err_str (err));
+ error ("Cannot get thread info for LWP %d: %s",
+ lwpid, thread_db_err_str (err));
if (debug_threads)
- fprintf (stderr, "Found first thread %ld (LWP %d)\n",
+ fprintf (stderr, "Found thread %ld (LWP %d)\n",
ti.ti_tid, ti.ti_lid);
if (process->lwpid != ti.ti_lid)
- fatal ("PID mismatch! Expected %ld, got %ld",
- (long) process->lwpid, (long) ti.ti_lid);
+ {
+ warning ("PID mismatch! Expected %ld, got %ld",
+ (long) process->lwpid, (long) ti.ti_lid);
+ return 0;
+ }
- /* If the new thread ID is zero, a final thread ID will be available
- later. Do not enable thread debugging yet. */
- if (ti.ti_tid == 0)
+ if (thread_db_use_events)
{
err = td_thr_event_enable (&th, 1);
if (err != TD_OK)
error ("Cannot enable thread event reporting for %d: %s",
ti.ti_lid, thread_db_err_str (err));
- return 0;
}
- /* Switch to indexing the threads list by TID. */
- change_inferior_id (&all_threads, ti.ti_tid);
-
- new_thread_notify (ti.ti_tid);
+ /* If the new thread ID is zero, a final thread ID will be available
+ later. Do not enable thread debugging yet. */
+ if (ti.ti_tid == 0)
+ return 0;
- process->tid = ti.ti_tid;
- process->lwpid = ti.ti_lid;
process->thread_known = 1;
+ process->tid = ti.ti_tid;
process->th = th;
- err = td_thr_event_enable (&th, 1);
- if (err != TD_OK)
- error ("Cannot enable thread event reporting for %d: %s",
- ti.ti_lid, thread_db_err_str (err));
-
return 1;
}
struct process_info *process;
inferior = (struct thread_info *) find_inferior_id (&all_threads,
- ti_p->ti_tid);
+ ti_p->ti_lid);
if (inferior != NULL)
return;
if (debug_threads)
fprintf (stderr, "Attaching to thread %ld (LWP %d)\n",
ti_p->ti_tid, ti_p->ti_lid);
- linux_attach_lwp (ti_p->ti_lid, ti_p->ti_tid);
+ linux_attach_lwp (ti_p->ti_lid);
inferior = (struct thread_info *) find_inferior_id (&all_threads,
- ti_p->ti_tid);
+ ti_p->ti_lid);
if (inferior == NULL)
{
warning ("Could not attach to thread %ld (LWP %d)\n",
process = inferior_target_data (inferior);
- new_thread_notify (ti_p->ti_tid);
-
process->tid = ti_p->ti_tid;
- process->lwpid = ti_p->ti_lid;
-
process->thread_known = 1;
process->th = *th_p;
- err = td_thr_event_enable (th_p, 1);
- if (err != TD_OK)
- error ("Cannot enable thread event reporting for %d: %s",
- ti_p->ti_lid, thread_db_err_str (err));
+
+ if (thread_db_use_events)
+ {
+ err = td_thr_event_enable (th_p, 1);
+ if (err != TD_OK)
+ error ("Cannot enable thread event reporting for %d: %s",
+ ti_p->ti_lid, thread_db_err_str (err));
+ }
}
static int
/* This function is only called when we first initialize thread_db.
First locate the initial thread. If it is not ready for
debugging yet, then stop. */
- if (find_first_thread () == 0)
+ if (find_one_thread (all_threads.head->id) == 0)
return;
/* Iterate over all user-space threads to discover new threads. */
process = get_thread_process (thread);
if (!process->thread_known)
- find_first_thread ();
+ find_one_thread (process->lwpid);
if (!process->thread_known)
return TD_NOTHR;
}
int
-thread_db_init ()
+thread_db_init (int use_events)
{
int err;
/* Allow new symbol lookups. */
all_symbols_looked_up = 0;
+ thread_db_use_events = use_events;
+
err = td_ta_new (&proc_handle, &thread_agent);
switch (err)
{
case TD_OK:
/* The thread library was detected. */
- if (thread_db_enable_reporting () == 0)
+ if (use_events && thread_db_enable_reporting () == 0)
return 0;
thread_db_find_new_threads ();
thread_db_look_up_symbols ();