tech-kern archive

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index][Old Index]

Debug Register (x86) trap - race



I've been investigating the race in ptrace(2) ATF tests: dbregs_* for
x86 (i386 and amd64).

Examples:

NetBSD/amd64
http://releng.netbsd.org/b5reports/amd64/2018/2018.03.06.11.21.31/test.html#lib_libc_sys_t_ptrace_wait6_dbregs_dr3_trap_variable_readwrite_write_2bytes

NetBSD/i386
http://releng.netbsd.org/b5reports/i386/2017/2017.12.06.17.54.58/test.html#lib_libc_sys_t_ptrace_wait3_dbregs_dr2_trap_variable_readwrite_read_2bytes



I still don't know what is the root cause for the race and I'm open for
suggestions.



Observations:
1. This bug is reproducible only for the scenario with debug register
trap according to the tests of mine.

2. It's reproducible on slower x86 hardware or in softemu (qemu). I have
not been able to reproduce a single failure on post-Core2Duo CPU.

3. Adding or changing the scenario slightly of tests (like to PT_STEP or
changing SIGSTOP to other signal) makes the test disappear at all.

4. Adding almost any debug code anywhere makes this test either
disappear or make reproducible much less frequently (sometimes once a day).

5. I've detected that the bug is caused by the fact that _lwp_kill(2)
(called via raise(2)) [under a debugger] can trigger two calls of
wait(2) to return for the same signal.

A few days ago, I've prepared this document:

http://netbsd.org/~kamil/kernel/sigstop.txt

6. According to my tests in lwp_userret():

   1549 	/*
   1550 	 * It is safe to do this read unlocked on a MP system..
   1551 	 */
   1552 	while ((l->l_flag & LW_USERRET) != 0) {
   1553 		/*
   1554 		 * Process pending signals first, unless the process
   1555 		 * is dumping core or exiting, where we will instead
   1556 		 * enter the LW_WSUSPEND case below.
   1557 		 */
   1558 		if ((l->l_flag & (LW_PENDSIG | LW_WCORE | LW_WEXIT)) ==
   1559 		    LW_PENDSIG) {
   1560 			mutex_enter(p->p_lock);
   1561 			while ((sig = issignal(l)) != 0)
   1562 				postsig(sig);
   1563 			mutex_exit(p->p_lock);
   1564 		}
   1565

 -- src/sys/kern/kern_lwp.c

We always enter the while() loop and call issignal(). Sometimes we
extract a signal and call postsig(). Usually for the call, we see no
misbehavior.

7. I've checked that PT_CONTINUE branch where p_stat != SSTOP is never
taken, at least for this race. We always call:

    828 	/* Finally, deliver the requested signal (or none). */
    829 	if (t->p_stat == SSTOP) {
    830 		/*
    831 		 * Unstop the process.  If it needs to take a
    832 		 * signal, make all efforts to ensure that at
    833 		 * an LWP runs to see it.
    834 		 */
    835 		t->p_xsig = signo;
    836 		if (resume_all)
    837 			proc_unstop(t);
    838 		else
    839 			lwp_unstop(lt);
    840 		return 0;
    841 	}

 -- src/sys/kern/sys_ptrace_common.c

8. We checked that we call sigpost() twice for a single _lwp_kill(2) call:
 A. handle_syscall() -> syscall() -> sv_invoke() -> sys__lwp_kill() ->
kpsignal2() -> sigpost()
 B. lwp_userret() -> issignal() -> sigswitch() -> proc_stop() -> sigpost()

9. Adding two consequential wait(2) calls, one after (checking for error
condition for another with WNOHANG) - makes the bug disappear completely.



I'm looking for hints to speedup the research.

Attachment: signature.asc
Description: OpenPGP digital signature



Home | Main Index | Thread Index | Old Index