[PATCH 04/14] Run all-stop on non-stop

Yao Qi
2012-05-03T13:14:04Z

This patch is mostly split from original V2,

[RFC/WIP PATCH v2 02/14] Implement all-stop on top of a target running non-stop mode
http://sourceware.org/ml/gdb-patches/2011-12/msg00564.html

except some minor fixes I pointed out in changelog entry.

gdb:

2012-04-12 Pedro Alves <pedro@codesourcery.com>

* breakpoint.c (breakpoints_always_inserted_mode): Check
target_is_non_stop_p instead of non_stop
(update_global_location_list): Ditto.
* infrun.c (use_displaced_stepping): Ditto.
(adjust_pc_after_break): Ditto.
(handle_inferior_event): Ditto.
(process_event_stop_test:): Ditto.
* linux-nat.c (get_pending_status): Ditto.
(linux_nat_detach): Ditto.
(linux_handle_extended_wait): Ditto.
(linux_nat_wait): Ditto.
(linux_nat_stop_lwp): Ditto.
* target.c (update_current_target): INHERIT and de_fault
to_is_non_stop_p.
(target_wait): Return TARGET_WAITKIND_NO_RESUMED by default.
* target.h (struct target_ops) <to_is_non_stop_p>: New field.
(target_is_non_stop_p): New.
* gdbthread.h (ALL_THREADS, ALL_LIVE_THREADS): New.
(struct thread_control_state): New field `resumed'.
(struct thread_suspend_state): New fields `waitstatus' and
`waitstatus_pending_p'.
* inf-ptrace.c (inf_ptrace_attach): Set the main thread's resumed
flag.
* infrun.c (displaced_step_next): Use do_target_resume.
(displaced_step_queued_p): New.
(do_target_resume): New.
(resume): Return early is the thread has a pending status. Resume
all threads if placed in the displaced stepping queue. Use
do_target_resume.
(print_target_wait_results): Print the whole ptid.
(do_target_wait): New.
(prepare_for_detach): Use do_target_wait.
(cancel_breakpoints): New.
(set_executing_from_state): Add the event thread to the thread list
if not already there. Stop all threads and select the event
thread.
(select_event_thread): New.
(wait_for_inferior): Use do_target_wait. Call
set_executing_from_state. Add the event thread to the thread list
if not already there. Stop all threads and select the event
thread.
(fetch_inferior_event): Ditto.
(wait_one): New.
(stop_all_threads): New.
(handle_inferior_event): Adjust to interface changes. Use
target_is_non_stop.
(stop_stepping): Stop all threads if necessary.
(keep_going): Stop all threads if starting a step-over.
(infrun_async_inferior_event_handler): New.
* linux-nat.c (linux_handle_extended_wait): Set the resumed flag of the
new lwp.
* thread.c (thread_list): Make extern.
(new_thread): Clear the suspend.waitstatus.kind field.

2012-04-12 Yao Qi <yao@codesourcery.com>

* infrun.c (stop_all_threads): s/infrun.c/infrun/ in debug log.
---
gdb/breakpoint.c | 4 +-
gdb/gdbthread.h | 22 ++
gdb/inf-ptrace.c | 4 +-
gdb/infrun.c | 828 +++++++++++++++++++++++++++++++++++++++++++++++++++---
gdb/linux-nat.c | 19 +-
gdb/target.c | 6 +-
gdb/target.h | 4 +
gdb/thread.c | 3 +-
8 files changed, 831 insertions(+), 59 deletions(-)

diff --git a/gdb/breakpoint.c b/gdb/breakpoint.c
index ab5f324..12a784e 100644
--- a/gdb/breakpoint.c
+++ b/gdb/breakpoint.c
@@ -409,7 +409,7 @@ int
breakpoints_always_inserted_mode (void)
{
return (always_inserted_mode == always_inserted_on
- || (always_inserted_mode == always_inserted_auto && non_stop));
+ || (always_inserted_mode == always_inserted_auto && target_is_non_stop_p ()));
}

static const char condition_evaluation_both[] = "host or target";
@@ -11782,7 +11782,7 @@ update_global_location_list (int should_insert)

if (!found_object)
{
- if (removed && non_stop
+ if (removed && target_is_non_stop_p ()
&& breakpoint_address_is_meaningful (old_loc->owner)
&& !is_hardware_watchpoint (old_loc->owner))
{
diff --git a/gdb/gdbthread.h b/gdb/gdbthread.h
index fb8de16..9600b1a 100644
--- a/gdb/gdbthread.h
+++ b/gdb/gdbthread.h
@@ -38,6 +38,15 @@ enum thread_state
THREAD_EXITED,
};

+#define ALL_THREADS(T) \
+ for ((T) = thread_list; (T); (T) = (T)->next)
+
+#define ALL_LIVE_THREADS(T) \
+ for ((T) = thread_list; (T); (T) = (T)->next) \
+ if ((T)->state != THREAD_EXITED)
+
+extern struct thread_info *thread_list;
+
/* Inferior thread specific part of `struct infcall_control_state'.

Inferior process counterpart is `struct inferior_control_state'. */
@@ -114,6 +123,14 @@ struct thread_control_state
/* Chain containing status of breakpoint(s) the thread stopped
at. */
bpstat stop_bpstat;
+
+ /* Non-zero if this thread will be/has been resumed. Note that a
+ thread can be marked both as stopped and resumed at the same
+ time. This happens if we try to resume a thread that has a wait
+ status pending. We shouldn't let the thread run until that wait
+ status has been processed, but we should not report that wait
+ status if GDB didn't try to let the thread run. */
+ int resumed;
};

/* Inferior thread specific part of `struct infcall_suspend_state'.
@@ -124,6 +141,11 @@ struct thread_suspend_state
{
/* Last signal that the inferior received (why it stopped). */
enum target_signal stop_signal;
+
+ /* The waitstatus for this thread's last event. */
+ struct target_waitstatus waitstatus;
+ /* If true WAITSTATUS hasn't been handled yet. */
+ int waitstatus_pending_p;
};

struct thread_info
diff --git a/gdb/inf-ptrace.c b/gdb/inf-ptrace.c
index fc8ab87..c059caa 100644
--- a/gdb/inf-ptrace.c
+++ b/gdb/inf-ptrace.c
@@ -193,6 +193,7 @@ inf_ptrace_attach (struct target_ops *ops, char *args, int from_tty)
char *exec_file;
pid_t pid;
struct inferior *inf;
+ struct thread_info *tp;

/* Do not change either targets above or the same target if already present.
The reason is the target stack is shared across multiple inferiors. */
@@ -242,7 +243,8 @@ inf_ptrace_attach (struct target_ops *ops, char *args, int from_tty)

/* Always add a main thread. If some target extends the ptrace
target, it should decorate the ptid later with more info. */
- add_thread_silent (inferior_ptid);
+ tp = add_thread_silent (inferior_ptid);
+ tp->control.resumed = 1;

if (! ops_already_pushed)
discard_cleanups (back_to);
diff --git a/gdb/infrun.c b/gdb/infrun.c
index 9de9674..b7afa39 100644
--- a/gdb/infrun.c
+++ b/gdb/infrun.c
@@ -57,6 +57,12 @@
#include "skip.h"
#include "probe.h"
#include "objfiles.h"
+#include "event-loop.h"
+
+/* Asynchronous signal handle registered as event loop source for
+ when we have pending events ready to be passed to the core. */
+
+static struct async_event_handler *infrun_async_inferior_event_token;

/* Prototypes for local functions */

@@ -1232,7 +1238,7 @@ static int
use_displaced_stepping (struct gdbarch *gdbarch)
{
return (((can_use_displaced_stepping == can_use_displaced_stepping_auto
- && non_stop)
+ && target_is_non_stop_p ())
|| can_use_displaced_stepping == can_use_displaced_stepping_on)
&& gdbarch_displaced_step_copy_insn_p (gdbarch)
&& !RECORD_IS_USED);
@@ -1487,6 +1493,8 @@ displaced_step_fixup (ptid_t event_ptid, enum target_signal signal)
return ret;
}

+static void do_target_resume (ptid_t ptid, int step, enum target_signal signo);
+
/* Process pending displaced stepping requests. */

static void
@@ -1509,6 +1517,7 @@ displaced_step_next (ptid_t event_ptid)
struct gdbarch *gdbarch;
CORE_ADDR actual_pc;
struct address_space *aspace;
+ struct thread_info *tp;

head = displaced->step_request_queue;
ptid = head->ptid;
@@ -1521,6 +1530,11 @@ displaced_step_next (ptid_t event_ptid)
actual_pc = regcache_read_pc (regcache);
aspace = get_regcache_aspace (regcache);

+ tp = inferior_thread ();
+
+ gdb_assert (!tp->control.resumed);
+ gdb_assert (!tp->executing);
+
if (breakpoint_here_p (aspace, actual_pc))
{
if (debug_displaced)
@@ -1545,9 +1559,9 @@ displaced_step_next (ptid_t event_ptid)

if (gdbarch_displaced_step_hw_singlestep (gdbarch,
displaced->step_closure))
- target_resume (ptid, 1, TARGET_SIGNAL_0);
+ do_target_resume (ptid, 1, TARGET_SIGNAL_0);
else
- target_resume (ptid, 0, TARGET_SIGNAL_0);
+ do_target_resume (ptid, 0, TARGET_SIGNAL_0);

/* Done, we're stepping a thread. */
break;
@@ -1569,7 +1583,7 @@ displaced_step_next (ptid_t event_ptid)
"displaced: breakpoint is gone: %s, step(%d)\n",
target_pid_to_str (tp->ptid), step);

- target_resume (ptid, step, TARGET_SIGNAL_0);
+ do_target_resume (ptid, step, TARGET_SIGNAL_0);
tp->suspend.stop_signal = TARGET_SIGNAL_0;

/* This request was discarded. See if there's any other
@@ -1717,6 +1731,214 @@ user_visible_resume_ptid (int step)
return resume_ptid;
}

+/* Return 1 if displaced stepping to thread PTID has been queued. */
+
+static int
+displaced_step_queued_p (ptid_t ptid)
+{
+ struct displaced_step_inferior_state *displaced
+ = get_displaced_stepping_state (ptid_get_pid (ptid));
+ struct displaced_step_request *head;
+
+ if (displaced != NULL)
+ for (head = displaced->step_request_queue; head != NULL; head = head->next)
+ if (ptid_equal (head->ptid, ptid))
+ return 1;
+
+ return 0;
+}
+
+static void
+do_target_resume (ptid_t ptid, int step, enum target_signal signo)
+{
+ int resume_many;
+ struct thread_info *tp;
+ struct regcache *regcache = get_current_regcache ();
+ struct gdbarch *gdbarch = get_regcache_arch (regcache);
+ int want_all_signals;
+ int can_wildcard;
+ int any_to_resume;
+ ptid_t leader_ptid = inferior_ptid;
+
+ /* A specific PTID means `step only this process id'. */
+ resume_many = (ptid_equal (minus_one_ptid, ptid)
+ || ptid_is_pid (ptid));
+
+ /* See if it's the current inferior that should be handled
+ specially. */
+ if (resume_many)
+ tp = find_thread_ptid (inferior_ptid);
+ else
+ tp = find_thread_ptid (ptid);
+ gdb_assert (tp != NULL);
+ gdb_assert (tp->state != THREAD_EXITED);
+
+ /* Advise target which signals may be handled silently. If we have
+ removed breakpoints because we are stepping over one (which can
+ happen only if we are not using displaced stepping), we need to
+ receive all signals to avoid accidentally skipping a breakpoint
+ during execution of a signal handler. */
+ want_all_signals = ((step || singlestep_breakpoints_inserted_p)
+ && tp->control.trap_expected
+ && !use_displaced_stepping (gdbarch));
+ if (want_all_signals)
+ target_pass_signals (0, NULL);
+ else
+ target_pass_signals ((int) TARGET_SIGNAL_LAST, signal_pass);
+
+ can_wildcard = 1;
+
+ ALL_LIVE_THREADS (tp)
+ {
+ if (ptid_match (tp->ptid, ptid))
+ {
+ if (tp->executing)
+ {
+ if (debug_infrun)
+ fprintf_unfiltered (gdb_stdlog,
+ "infrun: do_target_resume: executing\n");
+
+ /* Need to prevent wildcard; the target may want to read
+ PC for example. And, we may have set EXECUTING
+ temporarily true, while the thread really isn't
+ executing, which would mean it's not safe to assume
+ the target ignores this thread. */
+ can_wildcard = 0;
+ continue;
+ }
+
+ if (displaced_step_queued_p (tp->ptid))
+ {
+ if (debug_infrun)
+ fprintf_unfiltered (gdb_stdlog,
+ "infrun: do_target_resume: queued for displaced step\n");
+ can_wildcard = 0;
+ continue;
+ }
+
+ /* Avoid confusing the next resume, if the next stop/resume
+ happens to apply to another thread. */
+ tp->suspend.stop_signal = TARGET_SIGNAL_0;
+ tp->control.resumed = 1;
+
+ if (tp->suspend.waitstatus_pending_p)
+ {
+ /* FIXME: What should we do if we are supposed to
+ continue this thread with a signal? */
+ gdb_assert (signo == TARGET_SIGNAL_0);
+
+ if (!step
+ && tp->suspend.waitstatus.kind == TARGET_WAITKIND_STOPPED
+ && !want_all_signals
+ && signal_pass[signo])
+ {
+ if (debug_infrun)
+ {
+ char *statstr;
+
+ statstr = target_waitstatus_to_string (&tp->suspend.waitstatus);
+ fprintf_unfiltered (gdb_stdlog,
+ "infrun: Not short circuiting for ignored "
+ "status %s\n",
+ statstr);
+ xfree (statstr);
+ }
+
+ signo = tp->suspend.waitstatus.value.sig;
+ tp->suspend.waitstatus_pending_p = 0;
+ }
+ else
+ {
+ can_wildcard = 0;
+
+ if (target_can_async_p ())
+ {
+ target_async (inferior_event_handler, 0);
+
+ /* Tell the event loop we have something to
+ process. */
+ mark_async_event_handler (infrun_async_inferior_event_token);
+ }
+ return; /// XXX why?
+ }
+ }
+
+ if (!tp->suspend.waitstatus_pending_p)
+ any_to_resume = 1;
+ }
+ }
+
+ if (can_wildcard)
+ {
+ if (debug_infrun)
+ fprintf_unfiltered (gdb_stdlog,
+ "infrun: managed a wildcard target_resume\n");
+ target_resume (ptid, step, signo);
+ }
+ else
+ {
+ if (debug_infrun)
+ fprintf_unfiltered (gdb_stdlog,
+ "infrun: resuming threads individually\n");
+ ALL_LIVE_THREADS (tp)
+ {
+ if (ptid_match (tp->ptid, ptid))
+ {
+ if (tp->executing)
+ {
+ if (debug_infrun)
+ fprintf_unfiltered (gdb_stdlog,
+ "infrun: Not resuming %s (executing)\n",
+ target_pid_to_str (tp->ptid));
+ }
+ else if (displaced_step_queued_p (tp->ptid))
+ {
+ if (debug_infrun)
+ fprintf_unfiltered (gdb_stdlog,
+ "infrun: Not resuming %s (queued for displaced step)\n",
+ target_pid_to_str (tp->ptid));
+ }
+ else if (!tp->suspend.waitstatus_pending_p)
+ {
+ if (debug_infrun)
+ fprintf_unfiltered (gdb_stdlog,
+ "infrun: resuming %s (no pending status)\n",
+ target_pid_to_str (tp->ptid));
+ switch_to_thread (tp->ptid);
+
+ if (ptid_equal (tp->ptid, leader_ptid))
+ target_resume (tp->ptid, step, signo);
+ else
+ target_resume (tp->ptid, 0, TARGET_SIGNAL_0);
+ }
+ else
+ {
+ if (debug_infrun)
+ {
+ char *statstr;
+
+ statstr = target_waitstatus_to_string (&tp->suspend.waitstatus);
+ fprintf_unfiltered (gdb_stdlog,
+ "infrun: not resuming %s, has pending status %s\n",
+ target_pid_to_str (tp->ptid), statstr);
+ xfree (statstr);
+ }
+ }
+ }
+ }
+
+ set_running (ptid, 1);
+ clear_inline_frame_state (ptid);
+ }
+
+ if (target_can_async_p ())
+ {
+ target_async (inferior_event_handler, 0);
+ /* Tell the event loop we have something to process. */
+ mark_async_event_handler (infrun_async_inferior_event_token);
+ }
+}
+
/* Resume the inferior, but allow a QUIT. This is useful if the user
wants to interrupt some lengthy single-stepping operation
(for child processes, the SIGINT goes to the inferior, and so
@@ -1738,6 +1960,26 @@ resume (int step, enum target_signal sig)

QUIT;

+ if (tp->suspend.waitstatus_pending_p)
+ {
+ /* Avoid confusing the next resume, if the next stop/resume
+ happens to apply to another thread. */
+ tp->suspend.stop_signal = TARGET_SIGNAL_0;
+ tp->control.resumed = 1;
+
+ set_running (inferior_ptid, 1);
+ clear_inline_frame_state (inferior_ptid);
+ discard_cleanups (old_cleanups);
+
+ if (target_can_async_p ())
+ {
+ target_async (inferior_event_handler, 0);
+ /* Tell the event loop we have something to process. */
+ mark_async_event_handler (infrun_async_inferior_event_token);
+ }
+ return;
+ }
+
if (current_inferior ()->waiting_for_vfork_done)
{
/* Don't try to single-step a vfork parent that is waiting for
@@ -1801,14 +2043,34 @@ a command like `return' or `jump' to continue execution."));

if (!displaced_step_prepare (inferior_ptid))
{
+ ptid_t resume_ptid;
+
+ if (debug_infrun)
+ fprintf_unfiltered (gdb_stdlog,
+ "Got placed in displaced stepping queue\n");
+
/* Got placed in displaced stepping queue. Will be resumed
later when all the currently queued displaced stepping
requests finish. The thread is not executing at this point,
and the call to set_executing will be made later. But we
need to call set_running here, since from frontend point of view,
the thread is running. */
+
+ tp->suspend.stop_signal = TARGET_SIGNAL_0;
set_running (inferior_ptid, 1);
+ clear_inline_frame_state (inferior_ptid);
discard_cleanups (old_cleanups);
+
+ resume_ptid = user_visible_resume_ptid (step);
+
+ gdb_assert (!tp->control.resumed);
+
+ /* Resume others. */
+ tp->executing = 1;
+ do_target_resume (resume_ptid, 0, TARGET_SIGNAL_0);
+ tp->executing = 0;
+
+ gdb_assert (!tp->control.resumed);
return;
}

@@ -1944,23 +2206,7 @@ a command like `return' or `jump' to continue execution."));
/* Install inferior's terminal modes. */
target_terminal_inferior ();

- /* Avoid confusing the next resume, if the next stop/resume
- happens to apply to another thread. */
- tp->suspend.stop_signal = TARGET_SIGNAL_0;
-
- /* Advise target which signals may be handled silently. If we have
- removed breakpoints because we are stepping over one (which can
- happen only if we are not using displaced stepping), we need to
- receive all signals to avoid accidentally skipping a breakpoint
- during execution of a signal handler. */
- if ((step || singlestep_breakpoints_inserted_p)
- && tp->control.trap_expected
- && !use_displaced_stepping (gdbarch))
- target_pass_signals (0, NULL);
- else
- target_pass_signals ((int) TARGET_SIGNAL_LAST, signal_pass);
-
- target_resume (resume_ptid, step, sig);
+ do_target_resume (resume_ptid, step, sig);
}

discard_cleanups (old_cleanups);
@@ -1974,6 +2220,15 @@ a command like `return' or `jump' to continue execution."));
static void
clear_proceed_status_thread (struct thread_info *tp)
{
+ if (tp->state == THREAD_RUNNING)
+ {
+ if (debug_infrun)
+ fprintf_unfiltered (gdb_stdlog,
+ "infrun: clear_proceed_status_thread (%s): ignored, thread is running\n",
+ target_pid_to_str (tp->ptid));
+ return;
+ }
+
if (debug_infrun)
fprintf_unfiltered (gdb_stdlog,
"infrun: clear_proceed_status_thread (%s)\n",
@@ -2602,14 +2857,20 @@ print_target_wait_results (ptid_t waiton_ptid, ptid_t result_ptid,
is set. */

fprintf_unfiltered (tmp_stream,
- "infrun: target_wait (%d", PIDGET (waiton_ptid));
+ "infrun: target_wait (%d.%ld.%ld",
+ ptid_get_pid (waiton_ptid),
+ ptid_get_lwp (waiton_ptid),
+ ptid_get_tid (waiton_ptid));
if (PIDGET (waiton_ptid) != -1)
fprintf_unfiltered (tmp_stream,
" [%s]", target_pid_to_str (waiton_ptid));
fprintf_unfiltered (tmp_stream, ", status) =\n");
fprintf_unfiltered (tmp_stream,
- "infrun: %d [%s],\n",
- PIDGET (result_ptid), target_pid_to_str (result_ptid));
+ "infrun: %d.%ld.%ld [%s],\n",
+ ptid_get_pid (result_ptid),
+ ptid_get_lwp (result_ptid),
+ ptid_get_tid (result_ptid),
+ target_pid_to_str (result_ptid));
fprintf_unfiltered (tmp_stream,
"infrun: %s\n",
status_string);
@@ -2625,6 +2886,88 @@ print_target_wait_results (ptid_t waiton_ptid, ptid_t result_ptid,
ui_file_delete (tmp_stream);
}

+static ptid_t
+do_target_wait (ptid_t ptid, struct target_waitstatus *status, int options)
+{
+ ptid_t event_ptid;
+ struct thread_info *tp;
+
+ /* First check if there is a thread with a wait status pending. */
+ if (ptid_equal (ptid, minus_one_ptid) || ptid_is_pid (ptid))
+ {
+ ALL_LIVE_THREADS (tp)
+ {
+ /* Only report a pending wait status if we pretend that this
+ has indeed been resumed. */
+ if (ptid_match (tp->ptid, ptid)
+ && tp->control.resumed
+ && tp->suspend.waitstatus_pending_p)
+ {
+ gdb_assert (!tp->executing);
+ break;
+ }
+ }
+ }
+ else
+ {
+ if (debug_infrun)
+ fprintf_unfiltered (gdb_stdlog,
+ "infrun: Waiting for specific thread %s.\n",
+ target_pid_to_str (ptid));
+
+ /* We have a specific thread to check. */
+ tp = find_thread_ptid (ptid);
+ gdb_assert (tp);
+ if (!tp->suspend.waitstatus_pending_p)
+ tp = NULL;
+ }
+
+ if (tp)
+ {
+ if (debug_infrun)
+ {
+ char *statstr;
+
+ statstr = target_waitstatus_to_string (&tp->suspend.waitstatus);
+ fprintf_unfiltered (gdb_stdlog,
+ "infrun: Using pending wait status %s for %s.\n",
+ statstr,
+ target_pid_to_str (tp->ptid));
+ xfree (statstr);
+ }
+
+ *status = tp->suspend.waitstatus;
+ tp->suspend.waitstatus_pending_p = 0;
+ return tp->ptid;
+ }
+
+ /* But if we don't find one, we'll have to wait. */
+
+ if (deprecated_target_wait_hook)
+ event_ptid = deprecated_target_wait_hook (ptid, status, options);
+ else
+ event_ptid = target_wait (ptid, status, options);
+
+ if (status->kind != TARGET_WAITKIND_EXITED
+ && status->kind != TARGET_WAITKIND_SIGNALLED
+ /* If a thread not the main thread execl's, we'll see the event
+ happening on the main thread, even if it was stopped. It's
+ as if the pre-exec thread changes its thread id (well, it
+ really is that way on the Linux kernel). */
+ && status->kind != TARGET_WAITKIND_EXECD
+ && status->kind != TARGET_WAITKIND_IGNORE)
+ {
+ tp = find_thread_ptid (event_ptid);
+ if (tp)
+ {
+ gdb_assert (tp->control.resumed);
+ tp->suspend.waitstatus = *status;
+ }
+ }
+
+ return event_ptid;
+}
+
/* Prepare and stabilize the inferior for detaching it. E.g.,
detaching while a thread is displaced stepping is a recipe for
crashing it, as nothing would readjust the PC out of the scratch
@@ -2663,10 +3006,7 @@ prepare_for_detach (void)

overlay_cache_invalid = 1;

- if (deprecated_target_wait_hook)
- ecs->ptid = deprecated_target_wait_hook (pid_ptid, &ecs->ws, 0);
- else
- ecs->ptid = target_wait (pid_ptid, &ecs->ws, 0);
+ ecs->ptid = do_target_wait (pid_ptid, &ecs->ws, 0);

if (debug_infrun)
print_target_wait_results (pid_ptid, ecs->ptid, &ecs->ws);
@@ -2704,6 +3044,189 @@ prepare_for_detach (void)
discard_cleanups (old_chain_1);
}

+static void stop_all_threads (void);
+static int adjust_pc_after_break (struct thread_info *thread,
+ struct target_waitstatus *ws);
+
+static void
+cancel_breakpoints (void)
+{
+ struct thread_info *t;
+
+ /* Cancel any breakpoints in other threads that have hit a
+ breakpoint. If we do not do this, then we run the risk that the
+ user will delete or disable the breakpoint, but the thread will
+ have already tripped on it. */
+ ALL_LIVE_THREADS (t)
+ if (t->control.resumed
+ && t->suspend.waitstatus_pending_p)
+ {
+ if (adjust_pc_after_break (t, &t->suspend.waitstatus))
+ {
+ /* Throw away the trap. The thread will re-trap if
+ re-resumed and the breakpoint is still
+ installed. */
+ t->suspend.waitstatus_pending_p = 0;
+ t->suspend.waitstatus.kind = TARGET_WAITKIND_IGNORE;
+
+ /* Need to call displaced_step_fixup here too, for SSS
+ targets? */
+ }
+ else if (t->suspend.waitstatus.kind == TARGET_WAITKIND_STOPPED
+ && displaced_step_fixup (t->ptid,
+ t->suspend.waitstatus.value.sig) > 0)
+ {
+ /* Throw away the trap. */
+ t->suspend.waitstatus_pending_p = 0;
+ t->suspend.waitstatus.kind = TARGET_WAITKIND_IGNORE;
+ }
+ else if (t->suspend.waitstatus.kind == TARGET_WAITKIND_STOPPED
+ && t->suspend.waitstatus.value.sig == TARGET_SIGNAL_TRAP
+ && currently_stepping (t))
+ {
+ /* Throw away the step trap. */
+ t->suspend.waitstatus_pending_p = 0;
+ t->suspend.waitstatus.kind = TARGET_WAITKIND_IGNORE;
+ }
+ }
+}
+
+static void
+set_executing_from_state (struct execution_control_state *ecs)
+{
+ if (ecs->ws.kind == TARGET_WAITKIND_IGNORE
+ || ecs->ws.kind == TARGET_WAITKIND_NO_RESUMED)
+ ;
+ else if (ecs->ws.kind == TARGET_WAITKIND_SIGNALLED
+ || ecs->ws.kind == TARGET_WAITKIND_EXITED)
+ {
+ int exited_pid = ptid_get_pid (ecs->ptid);
+ struct thread_info *t;
+
+ ALL_THREADS (t)
+ if (ptid_get_pid (t->ptid) == exited_pid)
+ t->executing = 0;
+ }
+ else
+ set_executing (ecs->ptid, 0);
+}
+
+int stop_only_if_needed = 1;
+
+static void
+select_event_thread (struct execution_control_state *ecs)
+{
+ if (!non_stop && target_is_non_stop_p () && !stop_only_if_needed)
+ {
+ stop_all_threads ();
+
+ if (ecs->ws.kind != TARGET_WAITKIND_NO_RESUMED
+ && ecs->ws.kind != TARGET_WAITKIND_EXITED
+ && ecs->ws.kind != TARGET_WAITKIND_SIGNALLED
+ && (ptid_equal (waiton_ptid, minus_one_ptid)
+ || ptid_is_pid (waiton_ptid)))
+ {
+ /* If we're not waiting for a specific thread, choose an
+ event thread from among those that have had events.
+ Giving equal priority to all threads that have had events
+ helps prevent starvation. */
+
+ int num_events = 0;
+ int random_selector;
+ struct thread_info *event_tp;
+
+ ecs->event_thread = find_thread_ptid (ecs->ptid);
+ /* Record the wait status for the original thread. */
+ ecs->event_thread->suspend.waitstatus = ecs->ws;
+
+ /* Give preference to any thread that is being
+ single-stepped. */
+ ALL_LIVE_THREADS (event_tp)
+ if (ptid_match (event_tp->ptid, waiton_ptid)
+ && event_tp->control.resumed
+ && event_tp->suspend.waitstatus_pending_p
+ && currently_stepping (event_tp))
+ break;
+
+ if (event_tp != NULL)
+ {
+ if (debug_infrun)
+ fprintf_unfiltered (gdb_stdlog,
+ "infrun: Select single-step %s\n",
+ target_pid_to_str (event_tp->ptid));
+ }
+ else
+ {
+ /* No single-stepping thread. Select one at random,
+ out of those which have had events.
+
+ First see how many events we have. Count only
+ resumed threads that have an event pending. */
+ ALL_LIVE_THREADS (event_tp)
+ if (ptid_match (event_tp->ptid, waiton_ptid)
+ && event_tp->control.resumed
+ && event_tp->suspend.waitstatus_pending_p)
+ num_events++;
+
+ /* Now randomly pick a LWP out of those that have had a SIGTRAP. */
+ random_selector = (int)
+ ((num_events * (double) rand ()) / (RAND_MAX + 1.0));
+
+ if (debug_infrun && num_events > 1)
+ fprintf_unfiltered (gdb_stdlog,
+ "infrun: Found %d events, selecting #%d\n",
+ num_events, random_selector);
+
+ /* Select the Nth thread that has had an event. */
+ ALL_LIVE_THREADS (event_tp)
+ if (ptid_match (event_tp->ptid, waiton_ptid)
+ && event_tp->control.resumed
+ && event_tp->suspend.waitstatus_pending_p)
+ if (random_selector-- == 0)
+ break;
+ }
+
+ if (event_tp != NULL)
+ {
+ /* Switch the event thread. */
+ ecs->ptid = event_tp->ptid;
+ ecs->event_thread = event_tp;
+ ecs->ws = event_tp->suspend.waitstatus;
+ }
+
+ /* Flush the wait status for the event thread. */
+ ecs->event_thread->suspend.waitstatus_pending_p = 0;
+ }
+
+ {
+ struct thread_info *t;
+
+ /* Now that we've selected our final event thread, cancel
+ any breakpoints in other threads that have hit a
+ breakpoint. If we do not do this, then we run the risk
+ that the user will delete or disable the breakpoint,
+ but the thread will have already tripped on it. */
+ cancel_breakpoints ();
+
+ /* In all-stop, from the core's perspective, all threads
+ are now stopped until a new resume action is sent
+ over. */
+ ALL_LIVE_THREADS (t)
+ t->control.resumed = 0;
+ }
+ }
+ else
+ {
+ if (ecs->ws.kind != TARGET_WAITKIND_NO_RESUMED
+ && ecs->ws.kind != TARGET_WAITKIND_EXITED
+ && ecs->ws.kind != TARGET_WAITKIND_SIGNALLED)
+ {
+ ecs->event_thread = find_thread_ptid (ecs->ptid);
+ ecs->event_thread->control.resumed = 0;
+ }
+ }
+}
+
/* Wait for control to return from inferior to debugger.

If inferior gets a signal, we may decide to start it up again
@@ -2734,19 +3257,26 @@ wait_for_inferior (void)

overlay_cache_invalid = 1;

- if (deprecated_target_wait_hook)
- ecs->ptid = deprecated_target_wait_hook (waiton_ptid, &ecs->ws, 0);
- else
- ecs->ptid = target_wait (waiton_ptid, &ecs->ws, 0);
+ ecs->ptid = do_target_wait (waiton_ptid, &ecs->ws, 0);

if (debug_infrun)
print_target_wait_results (waiton_ptid, ecs->ptid, &ecs->ws);

+ set_executing_from_state (ecs);
+
/* If an error happens while handling the event, propagate GDB's
knowledge of the executing state to the frontend/user running
state. */
old_chain = make_cleanup (finish_thread_state_cleanup, &minus_one_ptid);

+ if (ecs->ws.kind != TARGET_WAITKIND_EXITED
+ && ecs->ws.kind != TARGET_WAITKIND_SIGNALLED
+ && ecs->ws.kind != TARGET_WAITKIND_NO_RESUMED
+ && !in_thread_list (ecs->ptid))
+ add_thread (ecs->ptid);
+
+ select_event_thread (ecs);
+
/* Now figure out what to do with the result of the result. */
handle_inferior_event (ecs);

@@ -2803,15 +3333,23 @@ fetch_inferior_event (void *client_data)
make_cleanup_restore_integer (&execution_direction);
execution_direction = target_execution_direction ();

- if (deprecated_target_wait_hook)
- ecs->ptid =
- deprecated_target_wait_hook (waiton_ptid, &ecs->ws, TARGET_WNOHANG);
- else
- ecs->ptid = target_wait (waiton_ptid, &ecs->ws, TARGET_WNOHANG);
+ ecs->ptid = do_target_wait (waiton_ptid, &ecs->ws, TARGET_WNOHANG);

if (debug_infrun)
print_target_wait_results (waiton_ptid, ecs->ptid, &ecs->ws);

+ if (ecs->ws.kind != TARGET_WAITKIND_EXITED
+ && ecs->ws.kind != TARGET_WAITKIND_SIGNALLED
+ && ecs->ws.kind != TARGET_WAITKIND_IGNORE
+ && ecs->ws.kind != TARGET_WAITKIND_NO_RESUMED
+ && !in_thread_list (ecs->ptid))
+ add_thread (ecs->ptid);
+
+ set_executing_from_state (ecs);
+
+ if (ecs->ws.kind != TARGET_WAITKIND_IGNORE)
+ select_event_thread (ecs);
+
if (non_stop
&& ecs->ws.kind != TARGET_WAITKIND_IGNORE
&& ecs->ws.kind != TARGET_WAITKIND_NO_RESUMED
@@ -3029,7 +3567,8 @@ adjust_pc_after_break (struct thread_info *thread,
SIGTRAPs, we keep a list of such breakpoint locations for a bit,
and retire them after a number of stop events are reported. */
if (software_breakpoint_inserted_here_p (aspace, breakpoint_pc)
- || (non_stop && moribund_breakpoint_here_p (aspace, breakpoint_pc)))
+ || (target_is_non_stop_p ()
+ && moribund_breakpoint_here_p (aspace, breakpoint_pc)))
{
struct cleanup *old_cleanups = NULL;

@@ -3184,6 +3723,165 @@ fill_in_stop_func (struct gdbarch *gdbarch,
}
}

+static ptid_t
+wait_one (ptid_t wait_ptid, struct target_waitstatus *ws)
+{
+ struct cleanup *old_chain;
+ ptid_t event_ptid;
+
+ /* We have to invalidate the registers BEFORE calling target_wait
+ because they can be loaded from the target while in target_wait.
+ This makes remote debugging a bit more efficient for those
+ targets that provide critical registers as part of their normal
+ status mechanism. */
+
+ overlay_cache_invalid = 1;
+ registers_changed ();
+
+ if (deprecated_target_wait_hook)
+ event_ptid = deprecated_target_wait_hook (wait_ptid, ws, 0);
+ else
+ event_ptid = target_wait (wait_ptid, ws, 0);
+
+ if (debug_infrun)
+ print_target_wait_results (wait_ptid, event_ptid, ws);
+
+ if (ws->kind == TARGET_WAITKIND_SYSCALL_ENTRY
+ || ws->kind == TARGET_WAITKIND_SYSCALL_RETURN)
+ ws->value.syscall_number = UNKNOWN_SYSCALL;
+
+ return event_ptid;
+}
+
+static void
+stop_all_threads (void)
+{
+ /* We may need multiple passes to discover all threads. */
+ int pass;
+ int iterations = 0;
+
+ if (debug_infrun)
+ fprintf_unfiltered (gdb_stdlog, "infrun: stop_all_threads\n");
+
+ /* Stop threads in two passes since threads could be spawning as we
+ go through the first pass. In the second pass, we will stop such
+ spawned threads. */
+ for (pass = 0; pass < 2; pass++, iterations++)
+ {
+ if (debug_infrun)
+ fprintf_unfiltered (gdb_stdlog,
+ "infrun: stop_all_threads, pass=%d, iterations=%d\n",
+ pass, iterations);
+ while (1)
+ {
+ ptid_t event_ptid;
+ struct target_waitstatus ws;
+ int need_wait = 0;
+ struct thread_info *t = NULL;
+
+ update_thread_list ();
+
+ /* Go through all threads looking for threads that we need
+ to tell the target to stop. */
+ ALL_LIVE_THREADS (t)
+ {
+ if (t->executing)
+ {
+ /* If already stopping, don't request a stop again.
+ We just haven't seen the notification yet. */
+ if (!t->stop_requested)
+ {
+ if (debug_infrun)
+ fprintf_unfiltered (gdb_stdlog,
+ "infrun: %s executing, need stop\n",
+ target_pid_to_str (t->ptid));
+ target_stop (t->ptid);
+ t->stop_requested = 1;
+ }
+ else
+ {
+ if (debug_infrun)
+ fprintf_unfiltered (gdb_stdlog,
+ "infrun: %s executing, already stopping\n",
+ target_pid_to_str (t->ptid));
+ }
+
+ /* FIXME: original: if (t->stop_requested) */
+ need_wait = 1;
+ }
+ else
+ {
+ if (debug_infrun)
+ fprintf_unfiltered (gdb_stdlog,
+ "infrun: %s not executing\n",
+ target_pid_to_str (t->ptid));
+ }
+ }
+
+ if (!need_wait)
+ break;
+
+ /* If we find new threads on the second iteration, restart
+ over. We want to see two iterations in a row with all
+ threads stopped. */
+ if (pass > 0)
+ pass = -1;
+
+ event_ptid = wait_one (minus_one_ptid, &ws);
+ if (ws.kind == TARGET_WAITKIND_NO_RESUMED)
+ /* All resumed threads exited. */
+ ;
+ else if (ws.kind == TARGET_WAITKIND_EXITED
+ || ws.kind == TARGET_WAITKIND_SIGNALLED)
+ warning (_("exited while stopping threads\n"));
+ else
+ {
+ if (!in_thread_list (event_ptid))
+ t = add_thread (event_ptid);
+ else
+ t = find_thread_ptid (event_ptid);
+
+ t->stop_requested = 0;
+ t->executing = 0;
+
+ if (ws.kind == TARGET_WAITKIND_STOPPED && ws.value.sig == TARGET_SIGNAL_0)
+ {
+ /* We caught the event that we intended to catch, so
+ there's no event pending. */
+ t->suspend.waitstatus.kind = TARGET_WAITKIND_IGNORE;
+ t->suspend.waitstatus_pending_p = 0;
+ }
+ else
+ {
+ if (debug_infrun)
+ {
+ char *statstr;
+
+ statstr = target_waitstatus_to_string (&ws);
+ fprintf_unfiltered (gdb_stdlog,
+ "infrun: target_wait %s, saving "
+ "status for %d.%ld.%ld\n",
+ statstr,
+ ptid_get_pid (t->ptid),
+ ptid_get_lwp (t->ptid),
+ ptid_get_tid (t->ptid));
+ xfree (statstr);
+ }
+
+ /* Record for later. */
+ t->suspend.waitstatus = ws;
+ t->suspend.waitstatus_pending_p = 1;
+ }
+
+ set_running (event_ptid, 0);
+ }
+ }
+ }
+
+ if (debug_infrun)
+ fprintf_unfiltered (gdb_stdlog, "infrun: stop_all_threads done\n");
+}
+
/* Given an execution control state that has been freshly filled in
by an event from the inferior, figure out what it means and take
appropriate action. */
@@ -3314,7 +4012,7 @@ handle_inferior_event (struct execution_control_state *ecs)
we're handling a process exit in non-stop mode, there's nothing
to do, as threads of the dead process are gone, and threads of
any other process were left running. */
- if (!non_stop)
+ if (!non_stop && !(target_is_non_stop_p () && stop_only_if_needed))
set_executing (minus_one_ptid, 0);
else if (ecs->ws.kind != TARGET_WAITKIND_SIGNALLED
&& ecs->ws.kind != TARGET_WAITKIND_EXITED)
@@ -3748,7 +4446,7 @@ handle_inferior_event (struct execution_control_state *ecs)

if (ecs->new_thread_event)
{
- if (non_stop)
+ if (target_is_non_stop_p ())
/* Non-stop assumes that the target handles adding new threads
to the thread list. */
internal_error (__FILE__, __LINE__,
@@ -4656,7 +5354,7 @@ process_event_stop_test:

/* In all-stop mode, if we're currently stepping but have stopped in
some other thread, we need to switch back to the stepped thread. */
- if (!non_stop)
+ if (!target_is_non_stop_p ())
{
struct thread_info *tp;

@@ -5697,6 +6395,23 @@ stop_stepping (struct execution_control_state *ecs)

/* Let callers know we don't want to wait for the inferior anymore. */
ecs->wait_some_more = 0;
+
+ if (!non_stop
+ && target_is_non_stop_p ()
+ && stop_only_if_needed)
+ {
+ struct thread_info *t;
+
+ stop_all_threads ();
+
+ cancel_breakpoints ();
+
+ /* In all-stop, from the core's perspective, all threads
+ are now stopped until a new resume action is sent
+ over. */
+ ALL_LIVE_THREADS (t)
+ t->control.resumed = 0;
+ }
}

/* This function handles various cases where we need to continue
@@ -5748,10 +6463,23 @@ keep_going (struct execution_control_state *ecs)
struct regcache *thread_regcache = get_thread_regcache (ecs->ptid);

if (!use_displaced_stepping (get_regcache_arch (thread_regcache)))
- /* Since we can't do a displaced step, we have to remove
- the breakpoint while we step it. To keep things
- simple, we remove them all. */
- remove_breakpoints ();
+ {
+ struct thread_info *t;
+
+ /* Since we can't do a displaced step, we have to remove
+ the breakpoint while we step it. To keep things
+ simple, we remove them all. */
+ stop_all_threads ();
+
+ cancel_breakpoints ();
+ /* In all-stop, from the core's perspective, all threads
+ are now stopped until a new resume action is sent
+ over. */
+ ALL_LIVE_THREADS (t)
+ t->control.resumed = 0;
+
+ remove_breakpoints ();
+ }
}
else
{
@@ -7122,12 +7850,22 @@ static const struct internalvar_funcs siginfo_funcs =
NULL
};

+static void
+infrun_async_inferior_event_handler (gdb_client_data data)
+{
+ inferior_event_handler (INF_REG_EVENT, NULL);
+}
+
void
_initialize_infrun (void)
{
int i;
int numsigs;

+ /* Register extra event sources in the event loop. */
+ infrun_async_inferior_event_token
+ = create_async_event_handler (infrun_async_inferior_event_handler, NULL);
+
add_info ("signals", signals_info, _("\
What debugger does when program gets various signals.\n\
Specify a signal as argument to print info on that signal only."));
diff --git a/gdb/linux-nat.c b/gdb/linux-nat.c
index ac1a0ea..a96a73c 100644
--- a/gdb/linux-nat.c
+++ b/gdb/linux-nat.c
@@ -1732,13 +1732,13 @@ get_pending_status (struct lwp_info *lp, int *status)
signo = TARGET_SIGNAL_0; /* a pending ptrace event, not a real signal. */
else if (lp->status)
signo = target_signal_from_host (WSTOPSIG (lp->status));
- else if (non_stop && !is_executing (lp->ptid))
+ else if (target_is_non_stop_p () && !is_executing (lp->ptid))
{
struct thread_info *tp = find_thread_ptid (lp->ptid);

signo = tp->suspend.stop_signal;
}
- else if (!non_stop)
+ else if (!target_is_non_stop_p ())
{
struct target_waitstatus last;
ptid_t last_ptid;
@@ -1890,7 +1890,7 @@ linux_nat_detach (struct target_ops *ops, char *args, int from_tty)
available. */
linux_fork_detach (args, from_tty);

- if (non_stop && target_can_async_p ())
+ if (target_is_non_stop_p () && target_can_async_p ())
target_async (inferior_event_handler, 0);
}
else
@@ -2368,7 +2368,7 @@ linux_handle_extended_wait (struct lwp_info *lp, int status,
status = 0;
}

- if (non_stop)
+ if (target_is_non_stop_p ())
{
/* Add the new thread to GDB's lists as soon as possible
so that:
@@ -2392,6 +2392,7 @@ linux_handle_extended_wait (struct lwp_info *lp, int status,
{
set_running (new_lp->ptid, 1);
set_executing (new_lp->ptid, 1);
+ find_thread_ptid (new_lp->ptid)->control.resumed = 1;
/* thread_db_attach_lwp -> lin_lwp_attach_lwp forced
resume_stop. */
new_lp->last_resume_kind = resume_continue;
@@ -3734,7 +3735,7 @@ retry:
core before this one is handled. All-stop
always cancels breakpoint hits in all
threads. */
- if (non_stop
+ if (target_is_non_stop_p ()
&& linux_nat_lp_status_is_event (lp)
&& cancel_breakpoint (lp))
{
@@ -3893,7 +3894,7 @@ retry:
goto retry;
}

- if (!non_stop)
+ if (!target_is_non_stop_p ())
{
/* Only do the below in all-stop, as we currently use SIGINT
to implement target_stop (see linux_nat_stop) in
@@ -3921,7 +3922,7 @@ retry:
fprintf_unfiltered (gdb_stdlog, "LLW: Candidate event %s in %s.\n",
status_to_str (status), target_pid_to_str (lp->ptid));

- if (!non_stop)
+ if (!target_is_non_stop_p ())
{
/* Now stop all other LWP's ... */
iterate_over_lwps (minus_one_ptid, stop_callback, NULL);
@@ -4068,7 +4069,7 @@ linux_nat_wait (struct target_ops *ops,
specific_process, for example, see linux_nat_wait_1), and
meanwhile the event became uninteresting. Don't bother resuming
LWPs we're not going to wait for if they'd stop immediately. */
- if (non_stop)
+ if (target_is_non_stop_p ())
iterate_over_lwps (minus_one_ptid, resume_stopped_resumed_lwps, &ptid);

event_ptid = linux_nat_wait_1 (ops, ptid, ourstatus, target_options);
@@ -5133,7 +5134,7 @@ linux_nat_stop_lwp (struct lwp_info *lwp, void *data)
static void
linux_nat_stop (ptid_t ptid)
{
- if (non_stop)
+ if (target_is_non_stop_p ())
iterate_over_lwps (ptid, linux_nat_stop_lwp, NULL);
else
linux_ops->to_stop (ptid);
diff --git a/gdb/target.c b/gdb/target.c
index cffea2c..aa386db 100644
--- a/gdb/target.c
+++ b/gdb/target.c
@@ -660,6 +660,8 @@ update_current_target (void)
INHERIT (to_can_async_p, t);
INHERIT (to_is_async_p, t);
INHERIT (to_async, t);
+ /* Do not inherit to_supports_non_stop. */
+ INHERIT (to_is_non_stop_p, t);
INHERIT (to_find_memory_regions, t);
INHERIT (to_make_corefile_notes, t);
INHERIT (to_get_bookmark, t);
@@ -940,6 +942,7 @@ update_current_target (void)
(int (*) (void))
return_zero);
de_fault (to_execution_direction, default_execution_direction);
+ de_fault (to_is_non_stop_p, return_zero);

#undef de_fault

@@ -2639,7 +2642,8 @@ target_wait (ptid_t ptid, struct target_waitstatus *status, int options)
}
}

- noprocess ();
+ status->kind = TARGET_WAITKIND_NO_RESUMED;
+ return minus_one_ptid;
}

char *
diff --git a/gdb/target.h b/gdb/target.h
index 50a0ea6..6148665 100644
--- a/gdb/target.h
+++ b/gdb/target.h
@@ -538,6 +538,7 @@ struct target_ops
int (*to_is_async_p) (void);
void (*to_async) (void (*) (enum inferior_event_type, void *), void *);
int (*to_supports_non_stop) (void);
+ int (*to_is_non_stop_p) (void);
/* find_memory_regions support method for gcore */
int (*to_find_memory_regions) (find_memory_region_ftype func, void *data);
/* make_corefile_notes support method for gcore */
@@ -1370,6 +1371,9 @@ extern int target_async_permitted;

int target_supports_non_stop (void);

+/* Is the target in non-stop mode? */
+#define target_is_non_stop_p() (current_target.to_is_non_stop_p ())
+
/* Put the target in async mode with the specified callback function. */
#define target_async(CALLBACK,CONTEXT) \
(current_target.to_async ((CALLBACK), (CONTEXT)))
diff --git a/gdb/thread.c b/gdb/thread.c
index d361dd8..4ad226d 100644
--- a/gdb/thread.c
+++ b/gdb/thread.c
@@ -54,7 +54,7 @@ void _initialize_thread (void);

/* Prototypes for local functions. */

-static struct thread_info *thread_list = NULL;
+struct thread_info *thread_list = NULL;
static int highest_thread_num;

static void thread_command (char *tidstr, int from_tty);
@@ -173,6 +173,7 @@ new_thread (ptid_t ptid)
/* Nothing to follow yet. */
tp->pending_follow.kind = TARGET_WAITKIND_SPURIOUS;
tp->state = THREAD_STOPPED;
+ tp->suspend.waitstatus.kind = TARGET_WAITKIND_IGNORE;

return tp;
}
--
1.7.0.4