This is the mail archive of the
gdb-patches@sources.redhat.com
mailing list for the GDB project.
[patch] Use TD_DEATH and PTRACE_EVENT_CLONE when available (was: Re: [RFC]: fix for recycled thread ids)
On Thu, Mar 25, 2004 at 03:22:13PM -0500, Jeff Johnston wrote:
>
> Daniel Jacobowitz wrote:
> >On Wed, Mar 24, 2004 at 11:39:50PM -0500, Daniel Jacobowitz wrote:
> >
> >>Do you have any code for PTRACE_EVENT_CLONE yet, or should I put
> >>something together in the morning to verify whether that's the problem?
> >
> >
> >Here you go. Again, this patch is obviously not ready to go into GDB,
> >but I have not been able to make it misbehave yet. I don't know if all
> >the bits it needs work right in RHEL3, or if my testing was conclusive.
> >
> >The highlights:
> > - Includes most of the previous patch
> > - Uses PTRACE_EVENT_CLONE to attach to new threads
> > - Moves handling of events closer to the waitpid call
> >
> >There are some potential races but I haven't hit any of them in
> >practice. I suspect that with a heavy fork or vfork load (not clone)
> >you could produce interesting failure modes.
> >
> >Give it a try, please. If it works I'll clean it up.
> >
>
> Works great. I was unable to get it to misbehave on RHEL3. Please go
> ahead cleaning it up.
Here's the final patch. The description of the patch is unchanged from
the above. The background, for those who did not read the whole
discussion: in NPTL, a signal delivered to the process when any thread
is not attached is likely to terminate the process. So we need to be
careful to:
- know when threads have exited, so that we can attach to new threads
which reuse the same thread ID
- attach to threads as early as possible
Both of these could cause problems in LinuxThreads, but they were less
drastic.
Tested with both LinuxThreads and NPTL. I'll commit this to HEAD on
Monday, barring objections. We've missed the boat for GDB 6.1 at this
point.
The testcase you posted has credits in it, so we can't just grab it for
the testsuite. But all we should need to test this is a loop that
creates short-lived threads, and then verifying that we can send C-c,
get a prompt, issue info threads, and continue a couple of times
without seeing anyting matching "error:". Interested in writing that
test? :)
--
Daniel Jacobowitz
MontaVista Software Debian GNU/Linux Developer
2004-03-26 Daniel Jacobowitz <drow@mvista.com>
* Makefile.in (linux_nat_h): Update dependencies.
* configure.in: Check for <gnu/libc-version.h>.
* configure: Regenerate.
* config.in: Regenerate.
* linux-nat.h: Include "target.h". Add waitstatus field to
struct lwp_info.
* lin-lwp.c (add_lwp): Initialize waitstatus.kind.
(lin_lwp_attach_lwp): Don't attach to LWPs we have already attached
to.
(lin_lwp_handle_extended): New function. Handle clone events.
(wait_lwp): Use lin_lwp_handle_extended. Update comment about
thread exit events.
(child_wait): Handle clone events.
(lin_lwp_wait: Use lin_lwp_handle_extended and handle clone events.
* linux-nat.c (linux_enable_event_reporting): Turn on
PTRACE_O_TRACECLONE.
(linux_handle_extended_wait): Handle clone events.
* thread-db.c: Include <gnu/libc-version.h>.
(struct private_thread_info): Add dying flag.
(enable_thread_event_reporting): Enable TD_DEATH for glibc 2.2 and
higher.
(attach_thread): Update comments. Handle dying threads.
(detach_thread): Set the dying flag.
(check_event): Always call attach_thread.
Index: Makefile.in
===================================================================
RCS file: /cvs/src/src/gdb/Makefile.in,v
retrieving revision 1.530
diff -u -p -r1.530 Makefile.in
--- Makefile.in 25 Mar 2004 01:27:26 -0000 1.530
+++ Makefile.in 26 Mar 2004 17:10:34 -0000
@@ -699,7 +699,7 @@ kod_h = kod.h
language_h = language.h
libunwind_frame_h = libunwind-frame.h $(libunwind_h)
linespec_h = linespec.h
-linux_nat_h = linux-nat.h
+linux_nat_h = linux-nat.h $(target_h)
m2_lang_h = m2-lang.h
m68k_tdep_h = m68k-tdep.h
macroexp_h = macroexp.h
Index: config.in
===================================================================
RCS file: /cvs/src/src/gdb/config.in,v
retrieving revision 1.61
diff -u -p -r1.61 config.in
--- config.in 20 Jan 2004 09:29:13 -0000 1.61
+++ config.in 26 Mar 2004 17:10:34 -0000
@@ -266,6 +266,9 @@
/* Define if you have the <dirent.h> header file. */
#undef HAVE_DIRENT_H
+/* Define if you have the <gnu/libc-version.h> header file. */
+#undef HAVE_GNU_LIBC_VERSION_H
+
/* Define if you have the <libunwind-ia64.h> header file. */
#undef HAVE_LIBUNWIND_IA64_H
Index: configure
===================================================================
RCS file: /cvs/src/src/gdb/configure,v
retrieving revision 1.148
diff -u -p -r1.148 configure
--- configure 26 Feb 2004 00:41:46 -0000 1.148
+++ configure 26 Mar 2004 17:10:35 -0000
@@ -4756,7 +4756,7 @@ else
fi
done
-for ac_hdr in proc_service.h thread_db.h
+for ac_hdr in proc_service.h thread_db.h gnu/libc-version.h
do
ac_safe=`echo "$ac_hdr" | sed 'y%./+-%__p_%'`
echo $ac_n "checking for $ac_hdr""... $ac_c" 1>&6
Index: configure.in
===================================================================
RCS file: /cvs/src/src/gdb/configure.in,v
retrieving revision 1.149
diff -u -p -r1.149 configure.in
--- configure.in 26 Feb 2004 00:41:46 -0000 1.149
+++ configure.in 26 Mar 2004 17:10:36 -0000
@@ -342,7 +342,7 @@ AC_CHECK_HEADERS(link.h)
AC_CHECK_HEADERS(machine/reg.h)
AC_CHECK_HEADERS(nlist.h)
AC_CHECK_HEADERS(poll.h sys/poll.h)
-AC_CHECK_HEADERS(proc_service.h thread_db.h)
+AC_CHECK_HEADERS(proc_service.h thread_db.h gnu/libc-version.h)
AC_CHECK_HEADERS(stddef.h)
AC_CHECK_HEADERS(stdlib.h)
AC_CHECK_HEADERS(stdint.h)
Index: lin-lwp.c
===================================================================
RCS file: /cvs/src/src/gdb/lin-lwp.c,v
retrieving revision 1.53
diff -u -p -r1.53 lin-lwp.c
--- lin-lwp.c 22 Mar 2004 20:18:33 -0000 1.53
+++ lin-lwp.c 26 Mar 2004 17:10:36 -0000
@@ -1,5 +1,5 @@
/* Multi-threaded debugging support for GNU/Linux (LWP layer).
- Copyright 2000, 2001, 2002, 2003 Free Software Foundation, Inc.
+ Copyright 2000, 2001, 2002, 2003, 2004 Free Software Foundation, Inc.
This file is part of GDB.
@@ -183,6 +183,8 @@ add_lwp (ptid_t ptid)
memset (lp, 0, sizeof (struct lwp_info));
+ lp->waitstatus.kind = TARGET_WAITKIND_IGNORE;
+
lp->ptid = ptid;
lp->next = lwp_list;
@@ -278,7 +280,7 @@ lin_lwp_open (char *args, int from_tty)
void
lin_lwp_attach_lwp (ptid_t ptid, int verbose)
{
- struct lwp_info *lp;
+ struct lwp_info *lp, *found_lp;
gdb_assert (is_lwp (ptid));
@@ -293,13 +295,17 @@ lin_lwp_attach_lwp (ptid_t ptid, int ver
if (verbose)
printf_filtered ("[New %s]\n", target_pid_to_str (ptid));
- lp = find_lwp_pid (ptid);
+ found_lp = lp = find_lwp_pid (ptid);
if (lp == NULL)
lp = add_lwp (ptid);
- /* We assume that we're already attached to any LWP that has an
- id equal to the overall process id. */
- if (GET_LWP (ptid) != GET_PID (ptid))
+ /* We assume that we're already attached to any LWP that has an id
+ equal to the overall process id, and to any LWP that is already
+ in our list of LWPs. If we're not seeing exit events from threads
+ and we've had PID wraparound since we last tried to stop all threads,
+ this assumption might be wrong; fortunately, this is very unlikely
+ to happen. */
+ if (GET_LWP (ptid) != GET_PID (ptid) && found_lp == NULL)
{
pid_t pid;
int status;
@@ -590,6 +596,41 @@ kill_lwp (int lwpid, int signo)
return kill (lwpid, signo);
}
+/* Handle a GNU/Linux extended wait response. Most of the work we
+ just pass off to linux_handle_extended_wait, but if it reports a
+ clone event we need to add the new LWP to our list (and not report
+ the trap to higher layers). This function returns non-zero if
+ the event should be ignored and we should wait again. */
+
+static int
+lin_lwp_handle_extended (struct lwp_info *lp, int status)
+{
+ linux_handle_extended_wait (GET_LWP (lp->ptid), status,
+ &lp->waitstatus);
+
+ /* TARGET_WAITKIND_SPURIOUS is used to indicate clone events. */
+ if (lp->waitstatus.kind == TARGET_WAITKIND_SPURIOUS)
+ {
+ struct lwp_info *new_lp;
+ new_lp = add_lwp (BUILD_LWP (lp->waitstatus.value.related_pid,
+ GET_PID (inferior_ptid)));
+ new_lp->cloned = 1;
+ new_lp->stopped = 1;
+
+ lp->waitstatus.kind = TARGET_WAITKIND_IGNORE;
+
+ if (debug_lin_lwp)
+ fprintf_unfiltered (gdb_stdlog,
+ "LLHE: Got clone event from LWP %ld, resuming\n",
+ GET_LWP (lp->ptid));
+ ptrace (PTRACE_CONT, GET_LWP (lp->ptid), 0, 0);
+
+ return 1;
+ }
+
+ return 0;
+}
+
/* Wait for LP to stop. Returns the wait status, or 0 if the LWP has
exited. */
@@ -609,9 +650,11 @@ wait_lwp (struct lwp_info *lp)
pid = waitpid (GET_LWP (lp->ptid), &status, __WCLONE);
if (pid == -1 && errno == ECHILD)
{
- /* The thread has previously exited. We need to delete it now
- because in the case of NPTL threads, there won't be an
- exit event unless it is the main thread. */
+ /* The thread has previously exited. We need to delete it
+ now because, for some vendor 2.4 kernels with NPTL
+ support backported, there won't be an exit event unless
+ it is the main thread. 2.6 kernels will report an exit
+ event for each thread that exits, as expected. */
thread_dead = 1;
if (debug_lin_lwp)
fprintf_unfiltered (gdb_stdlog, "WL: %s vanished.\n",
@@ -658,6 +701,17 @@ wait_lwp (struct lwp_info *lp)
gdb_assert (WIFSTOPPED (status));
+ /* Handle GNU/Linux's extended waitstatus for trace events. */
+ if (WIFSTOPPED (status) && WSTOPSIG (status) == SIGTRAP && status >> 16 != 0)
+ {
+ if (debug_lin_lwp)
+ fprintf_unfiltered (gdb_stdlog,
+ "WL: Handling extended status 0x%06x\n",
+ status);
+ if (lin_lwp_handle_extended (lp, status))
+ return wait_lwp (lp);
+ }
+
return status;
}
@@ -1097,6 +1151,8 @@ child_wait (ptid_t ptid, struct target_w
int status;
pid_t pid;
+ ourstatus->kind = TARGET_WAITKIND_IGNORE;
+
do
{
set_sigint_trap (); /* Causes SIGINT to be passed on to the
@@ -1143,6 +1199,25 @@ child_wait (ptid_t ptid, struct target_w
save_errno = EINTR;
}
+ /* Handle GNU/Linux's extended waitstatus for trace events. */
+ if (pid != -1 && WIFSTOPPED (status) && WSTOPSIG (status) == SIGTRAP
+ && status >> 16 != 0)
+ {
+ linux_handle_extended_wait (pid, status, ourstatus);
+
+ /* If we see a clone event, detach the child, and don't
+ report the event. It would be nice to offer some way to
+ switch into a non-thread-db based threaded mode at this
+ point. */
+ if (ourstatus->kind == TARGET_WAITKIND_SPURIOUS)
+ {
+ ptrace (PTRACE_DETACH, ourstatus->value.related_pid, 0, 0);
+ ourstatus->kind = TARGET_WAITKIND_IGNORE;
+ pid = -1;
+ save_errno = EINTR;
+ }
+ }
+
clear_sigio_trap ();
clear_sigint_trap ();
}
@@ -1159,11 +1234,9 @@ child_wait (ptid_t ptid, struct target_w
return minus_one_ptid;
}
- /* Handle GNU/Linux's extended waitstatus for trace events. */
- if (WIFSTOPPED (status) && WSTOPSIG (status) == SIGTRAP && status >> 16 != 0)
- return linux_handle_extended_wait (pid, status, ourstatus);
+ if (ourstatus->kind == TARGET_WAITKIND_IGNORE)
+ store_waitstatus (ourstatus, status);
- store_waitstatus (ourstatus, status);
return pid_to_ptid (pid);
}
@@ -1371,6 +1444,20 @@ retry:
}
}
+ /* Handle GNU/Linux's extended waitstatus for trace events. */
+ if (WIFSTOPPED (status) && WSTOPSIG (status) == SIGTRAP && status >> 16 != 0)
+ {
+ if (debug_lin_lwp)
+ fprintf_unfiltered (gdb_stdlog,
+ "LLW: Handling extended status 0x%06x\n",
+ status);
+ if (lin_lwp_handle_extended (lp, status))
+ {
+ status = 0;
+ continue;
+ }
+ }
+
/* Check if the thread has exited. */
if ((WIFEXITED (status) || WIFSIGNALED (status)) && num_lwps > 1)
{
@@ -1588,14 +1675,14 @@ retry:
else
trap_ptid = null_ptid;
- /* Handle GNU/Linux's extended waitstatus for trace events. */
- if (WIFSTOPPED (status) && WSTOPSIG (status) == SIGTRAP && status >> 16 != 0)
+ if (lp->waitstatus.kind != TARGET_WAITKIND_IGNORE)
{
- linux_handle_extended_wait (GET_LWP (lp->ptid), status, ourstatus);
- return trap_ptid;
+ *ourstatus = lp->waitstatus;
+ lp->waitstatus.kind = TARGET_WAITKIND_IGNORE;
}
+ else
+ store_waitstatus (ourstatus, status);
- store_waitstatus (ourstatus, status);
return (threaded ? lp->ptid : pid_to_ptid (GET_LWP (lp->ptid)));
}
Index: linux-nat.c
===================================================================
RCS file: /cvs/src/src/gdb/linux-nat.c,v
retrieving revision 1.5
diff -u -p -r1.5 linux-nat.c
--- linux-nat.c 17 Aug 2003 20:17:02 -0000 1.5
+++ linux-nat.c 26 Mar 2004 17:10:36 -0000
@@ -1,5 +1,5 @@
/* GNU/Linux native-dependent code common to multiple platforms.
- Copyright (C) 2003 Free Software Foundation, Inc.
+ Copyright (C) 2003, 2004 Free Software Foundation, Inc.
This file is part of GDB.
@@ -224,7 +224,8 @@ linux_enable_event_reporting (ptid_t pti
if (! linux_supports_tracefork ())
return;
- options = PTRACE_O_TRACEFORK | PTRACE_O_TRACEVFORK | PTRACE_O_TRACEEXEC;
+ options = PTRACE_O_TRACEFORK | PTRACE_O_TRACEVFORK | PTRACE_O_TRACEEXEC
+ | PTRACE_O_TRACECLONE;
if (linux_supports_tracevforkdone ())
options |= PTRACE_O_TRACEVFORKDONE;
@@ -391,11 +392,8 @@ linux_handle_extended_wait (int pid, int
{
int event = status >> 16;
- if (event == PTRACE_EVENT_CLONE)
- internal_error (__FILE__, __LINE__,
- "unexpected clone event");
-
- if (event == PTRACE_EVENT_FORK || event == PTRACE_EVENT_VFORK)
+ if (event == PTRACE_EVENT_FORK || event == PTRACE_EVENT_VFORK
+ || event == PTRACE_EVENT_CLONE)
{
unsigned long new_pid;
int ret;
@@ -406,12 +404,10 @@ linux_handle_extended_wait (int pid, int
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.
-
- It won't be a clone (we didn't ask for clones in the event mask)
- so we can just call waitpid and wait for the SIGSTOP. */
+ hits the SIGSTOP, but we're already attached. */
do {
- ret = waitpid (new_pid, &status, 0);
+ ret = waitpid (new_pid, &status,
+ (event == PTRACE_EVENT_CLONE) ? __WCLONE : 0);
} while (ret == -1 && errno == EINTR);
if (ret == -1)
perror_with_name ("waiting for new child");
@@ -423,8 +419,13 @@ linux_handle_extended_wait (int pid, int
"wait returned unexpected status 0x%x", status);
}
- ourstatus->kind = (event == PTRACE_EVENT_FORK)
- ? TARGET_WAITKIND_FORKED : TARGET_WAITKIND_VFORKED;
+ if (event == PTRACE_EVENT_FORK)
+ ourstatus->kind = TARGET_WAITKIND_FORKED;
+ else if (event == PTRACE_EVENT_VFORK)
+ ourstatus->kind = TARGET_WAITKIND_VFORKED;
+ else
+ ourstatus->kind = TARGET_WAITKIND_SPURIOUS;
+
ourstatus->value.related_pid = new_pid;
return inferior_ptid;
}
Index: linux-nat.h
===================================================================
RCS file: /cvs/src/src/gdb/linux-nat.h,v
retrieving revision 1.5
diff -u -p -r1.5 linux-nat.h
--- linux-nat.h 14 Sep 2003 02:04:44 -0000 1.5
+++ linux-nat.h 26 Mar 2004 17:10:36 -0000
@@ -1,5 +1,5 @@
/* Native debugging support for GNU/Linux (LWP layer).
- Copyright 2000, 2001, 2002, 2003 Free Software Foundation, Inc.
+ Copyright 2000, 2001, 2002, 2003, 2004 Free Software Foundation, Inc.
This file is part of GDB.
@@ -18,6 +18,8 @@
Foundation, Inc., 59 Temple Place - Suite 330,
Boston, MA 02111-1307, USA. */
+#include "target.h"
+
/* Structure describing an LWP. */
struct lwp_info
@@ -52,6 +54,11 @@ struct lwp_info
/* Non-zero if we were stepping this LWP. */
int step;
+ /* If WAITSTATUS->KIND != TARGET_WAITKIND_SPURIOUS, the waitstatus
+ for this LWP's last event. This may correspond to STATUS above,
+ or to a local variable in lin_lwp_wait. */
+ struct target_waitstatus waitstatus;
+
/* Next LWP in list. */
struct lwp_info *next;
};
@@ -60,7 +67,6 @@ struct lwp_info
system". */
struct mem_attrib;
struct target_ops;
-struct target_waitstatus;
extern int linux_proc_xfer_memory (CORE_ADDR addr, char *myaddr, int len,
int write, struct mem_attrib *attrib,
Index: thread-db.c
===================================================================
RCS file: /cvs/src/src/gdb/thread-db.c,v
retrieving revision 1.37
diff -u -p -r1.37 thread-db.c
--- thread-db.c 29 Feb 2004 02:39:47 -0000 1.37
+++ thread-db.c 26 Mar 2004 17:10:36 -0000
@@ -1,6 +1,6 @@
/* libthread_db assisted debugging support, generic parts.
- Copyright 1999, 2000, 2001, 2003 Free Software Foundation, Inc.
+ Copyright 1999, 2000, 2001, 2003, 2004 Free Software Foundation, Inc.
This file is part of GDB.
@@ -35,6 +35,10 @@
#include "regcache.h"
#include "solib-svr4.h"
+#ifdef HAVE_GNU_LIBC_VERSION_H
+#include <gnu/libc-version.h>
+#endif
+
#ifndef LIBTHREAD_DB_SO
#define LIBTHREAD_DB_SO "libthread_db.so.1"
#endif
@@ -130,6 +134,7 @@ static CORE_ADDR td_death_bp_addr;
static void thread_db_find_new_threads (void);
static void attach_thread (ptid_t ptid, const td_thrhandle_t *th_p,
const td_thrinfo_t *ti_p, int verbose);
+static void detach_thread (ptid_t ptid, int verbose);
/* Building process ids. */
@@ -150,6 +155,9 @@ static void attach_thread (ptid_t ptid,
struct private_thread_info
{
+ /* Flag set when we see a TD_DEATH event for this thread. */
+ unsigned int dying:1;
+
/* Cached thread state. */
unsigned int th_valid:1;
unsigned int ti_valid:1;
@@ -491,6 +499,10 @@ enable_thread_event_reporting (void)
td_thr_events_t events;
td_notify_t notify;
td_err_e err;
+#ifdef HAVE_GNU_LIBC_VERSION_H
+ const char *libc_version;
+ int libc_major, libc_minor;
+#endif
/* We cannot use the thread event reporting facility if these
functions aren't available. */
@@ -501,12 +513,16 @@ enable_thread_event_reporting (void)
/* Set the process wide mask saying which events we're interested in. */
td_event_emptyset (&events);
td_event_addset (&events, TD_CREATE);
-#if 0
+
+#ifdef HAVE_GNU_LIBC_VERSION_H
/* FIXME: kettenis/2000-04-23: The event reporting facility is
broken for TD_DEATH events in glibc 2.1.3, so don't enable it for
now. */
- td_event_addset (&events, TD_DEATH);
+ libc_version = gnu_get_libc_version ();
+ if (sscanf (libc_version, "%d.%d", &libc_major, &libc_minor) == 2
+ && (libc_major > 2 || (libc_major == 2 && libc_minor > 1)))
#endif
+ td_event_addset (&events, TD_DEATH);
err = td_ta_set_event_p (thread_agent, &events);
if (err != TD_OK)
@@ -689,6 +705,10 @@ quit:
target_new_objfile_chain (objfile);
}
+/* Attach to a new thread. This function is called when we receive a
+ TD_CREATE event or when we iterate over all threads and find one
+ that wasn't already in our list. */
+
static void
attach_thread (ptid_t ptid, const td_thrhandle_t *th_p,
const td_thrinfo_t *ti_p, int verbose)
@@ -696,6 +716,27 @@ attach_thread (ptid_t ptid, const td_thr
struct thread_info *tp;
td_err_e err;
+ /* If we're being called after a TD_CREATE event, we may already
+ know about this thread. There are two ways this can happen. We
+ may have iterated over all threads between the thread creation
+ and the TD_CREATE event, for instance when the user has issued
+ the `info threads' command before the SIGTRAP for hitting the
+ thread creation breakpoint was reported. Alternatively, the
+ thread may have exited and a new one been created with the same
+ thread ID. In the first case we don't need to do anything; in
+ the second case we should discard information about the dead
+ thread and attach to the new one. */
+ if (in_thread_list (ptid))
+ {
+ tp = find_thread_pid (ptid);
+ gdb_assert (tp != NULL);
+
+ if (!tp->private->dying)
+ return;
+
+ delete_thread (ptid);
+ }
+
check_thread_signals ();
/* Add the thread to GDB's thread list. */
@@ -741,8 +782,21 @@ thread_db_attach (char *args, int from_t
static void
detach_thread (ptid_t ptid, int verbose)
{
+ struct thread_info *thread_info;
+
if (verbose)
printf_unfiltered ("[%s exited]\n", target_pid_to_str (ptid));
+
+ /* Don't delete the thread now, because it still reports as active
+ until it has executed a few instructions after the event
+ breakpoint - if we deleted it now, "info threads" would cause us
+ to re-attach to it. Just mark it as having had a TD_DEATH
+ event. This means that we won't delete it from our thread list
+ until we notice that it's dead (via prune_threads), or until
+ something re-uses its thread ID. */
+ thread_info = find_thread_pid (ptid);
+ gdb_assert (thread_info != NULL);
+ thread_info->private->dying = 1;
}
static void
@@ -847,12 +901,9 @@ check_event (ptid_t ptid)
switch (msg.event)
{
case TD_CREATE:
-
- /* We may already know about this thread, for instance when the
- user has issued the `info threads' command before the SIGTRAP
- for hitting the thread creation breakpoint was reported. */
- if (!in_thread_list (ptid))
- attach_thread (ptid, msg.th_p, &ti, 1);
+ /* Call attach_thread whether or not we already know about a
+ thread with this thread ID. */
+ attach_thread (ptid, msg.th_p, &ti, 1);
break;