usr/src/lib/libc/port/threads/assfail.c
author Christopher Siden <chris.siden@delphix.com>
Mon, 21 May 2012 12:11:39 -0700
changeset 13700 2889e2596bd6
parent 11913 283e725df792
child 13764 38b4aca480b3
permissions -rw-r--r--
2619 asynchronous destruction of ZFS file systems 2747 SPA versioning with zfs feature flags Reviewed by: Matt Ahrens <[email protected]> Reviewed by: George Wilson <[email protected]> Reviewed by: Richard Lowe <[email protected]> Reviewed by: Dan Kruchinin <[email protected]> Approved by: Eric Schrock <[email protected]>

/*
 * CDDL HEADER START
 *
 * The contents of this file are subject to the terms of the
 * Common Development and Distribution License (the "License").
 * You may not use this file except in compliance with the License.
 *
 * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
 * or http://www.opensolaris.org/os/licensing.
 * See the License for the specific language governing permissions
 * and limitations under the License.
 *
 * When distributing Covered Code, include this CDDL HEADER in each
 * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
 * If applicable, add the following below this CDDL HEADER, with the
 * fields enclosed by brackets "[]" replaced with your own identifying
 * information: Portions Copyright [yyyy] [name of copyright owner]
 *
 * CDDL HEADER END
 */

/*
 * Copyright 2010 Sun Microsystems, Inc.  All rights reserved.
 * Use is subject to license terms.
 */
/*
 * Copyright (c) 2012 by Delphix. All rights reserved.
 */

#include "lint.h"
#include "thr_uberdata.h"

const char *panicstr;
ulwp_t *panic_thread;

static mutex_t assert_lock = DEFAULTMUTEX;
static ulwp_t *assert_thread = NULL;

/*
 * Called from __assert() to set panicstr and panic_thread.
 */
void
__set_panicstr(const char *msg)
{
	panicstr = msg;
	panic_thread = __curthread();
}

/*
 * Called from exit() (atexit function) to give precedence
 * to assertion failures and a core dump over _exit().
 */
void
grab_assert_lock()
{
	(void) _lwp_mutex_lock(&assert_lock);
}

static void
Abort(const char *msg)
{
	ulwp_t *self;
	struct sigaction act;
	sigset_t sigmask;
	lwpid_t lwpid;

	/* to help with core file debugging */
	panicstr = msg;
	if ((self = __curthread()) != NULL) {
		panic_thread = self;
		lwpid = self->ul_lwpid;
	} else {
		lwpid = _lwp_self();
	}

	/* set SIGABRT signal handler to SIG_DFL w/o grabbing any locks */
	(void) memset(&act, 0, sizeof (act));
	act.sa_sigaction = SIG_DFL;
	(void) __sigaction(SIGABRT, &act, NULL);

	/* delete SIGABRT from the signal mask */
	(void) sigemptyset(&sigmask);
	(void) sigaddset(&sigmask, SIGABRT);
	(void) __lwp_sigmask(SIG_UNBLOCK, &sigmask);

	(void) _lwp_kill(lwpid, SIGABRT);	/* never returns */
	(void) kill(getpid(), SIGABRT);	/* if it does, try harder */
	_exit(127);
}

/*
 * Write a panic message w/o grabbing any locks other than assert_lock.
 * We have no idea what locks are held at this point.
 */
static void
common_panic(const char *head, const char *why)
{
	char msg[400];	/* no panic() message in the library is this long */
	ulwp_t *self;
	size_t len1, len2;

	if ((self = __curthread()) != NULL)
		enter_critical(self);
	(void) _lwp_mutex_lock(&assert_lock);

	(void) memset(msg, 0, sizeof (msg));
	(void) strcpy(msg, head);
	len1 = strlen(msg);
	len2 = strlen(why);
	if (len1 + len2 >= sizeof (msg))
		len2 = sizeof (msg) - len1 - 1;
	(void) strncat(msg, why, len2);
	len1 = strlen(msg);
	if (msg[len1 - 1] != '\n')
		msg[len1++] = '\n';
	(void) __write(2, msg, len1);
	Abort(msg);
}

void
thr_panic(const char *why)
{
	common_panic("*** libc thread failure: ", why);
}

void
aio_panic(const char *why)
{
	common_panic("*** libc aio system failure: ", why);
}

/*
 * Utility function for converting a long integer to a string, avoiding stdio.
 * 'base' must be one of 10 or 16
 */
void
ultos(uint64_t n, int base, char *s)
{
	char lbuf[24];		/* 64 bits fits in 16 hex digits, 20 decimal */
	char *cp = lbuf;

	do {
		*cp++ = "0123456789abcdef"[n%base];
		n /= base;
	} while (n);
	if (base == 16) {
		*s++ = '0';
		*s++ = 'x';
	}
	do {
		*s++ = *--cp;
	} while (cp > lbuf);
	*s = '\0';
}

/*
 * Report application lock usage error for mutexes and condvars.
 * Not called if _THREAD_ERROR_DETECTION=0.
 * Continue execution if _THREAD_ERROR_DETECTION=1.
 * Dump core if _THREAD_ERROR_DETECTION=2.
 */
void
lock_error(const mutex_t *mp, const char *who, void *cv, const char *msg)
{
	mutex_t mcopy;
	char buf[800];
	uberdata_t *udp;
	ulwp_t *self;
	lwpid_t lwpid;
	pid_t pid;

	/*
	 * Take a snapshot of the mutex before it changes (we hope!).
	 * Use memcpy() rather than 'mcopy = *mp' in case mp is unaligned.
	 */
	(void) memcpy(&mcopy, mp, sizeof (mcopy));

	/* avoid recursion deadlock */
	if ((self = __curthread()) != NULL) {
		if (assert_thread == self)
			_exit(127);
		enter_critical(self);
		(void) _lwp_mutex_lock(&assert_lock);
		assert_thread = self;
		lwpid = self->ul_lwpid;
		udp = self->ul_uberdata;
		pid = udp->pid;
	} else {
		self = NULL;
		(void) _lwp_mutex_lock(&assert_lock);
		lwpid = _lwp_self();
		udp = &__uberdata;
		pid = getpid();
	}

	(void) strcpy(buf,
	    "\n*** _THREAD_ERROR_DETECTION: lock usage error detected ***\n");
	(void) strcat(buf, who);
	(void) strcat(buf, "(");
	if (cv != NULL) {
		ultos((uint64_t)(uintptr_t)cv, 16, buf + strlen(buf));
		(void) strcat(buf, ", ");
	}
	ultos((uint64_t)(uintptr_t)mp, 16, buf + strlen(buf));
	(void) strcat(buf, ")");
	if (msg != NULL) {
		(void) strcat(buf, ": ");
		(void) strcat(buf, msg);
	} else if (!mutex_held(&mcopy)) {
		(void) strcat(buf, ": calling thread does not own the lock");
	} else if (mcopy.mutex_rcount) {
		(void) strcat(buf, ": mutex rcount = ");
		ultos((uint64_t)mcopy.mutex_rcount, 10, buf + strlen(buf));
	} else {
		(void) strcat(buf, ": calling thread already owns the lock");
	}
	(void) strcat(buf, "\ncalling thread is ");
	ultos((uint64_t)(uintptr_t)self, 16, buf + strlen(buf));
	(void) strcat(buf, " thread-id ");
	ultos((uint64_t)lwpid, 10, buf + strlen(buf));
	if (msg != NULL || mutex_held(&mcopy))
		/* EMPTY */;
	else if (mcopy.mutex_lockw == 0)
		(void) strcat(buf, "\nthe lock is unowned");
	else if (!(mcopy.mutex_type & USYNC_PROCESS)) {
		(void) strcat(buf, "\nthe lock owner is ");
		ultos((uint64_t)mcopy.mutex_owner, 16, buf + strlen(buf));
	} else {
		(void) strcat(buf, " in process ");
		ultos((uint64_t)pid, 10, buf + strlen(buf));
		(void) strcat(buf, "\nthe lock owner is ");
		ultos((uint64_t)mcopy.mutex_owner, 16, buf + strlen(buf));
		(void) strcat(buf, " in process ");
		ultos((uint64_t)mcopy.mutex_ownerpid, 10, buf + strlen(buf));
	}
	(void) strcat(buf, "\n\n");
	(void) __write(2, buf, strlen(buf));
	if (udp->uberflags.uf_thread_error_detection >= 2)
		Abort(buf);
	assert_thread = NULL;
	(void) _lwp_mutex_unlock(&assert_lock);
	if (self != NULL)
		exit_critical(self);
}

/*
 * Report application lock usage error for rwlocks.
 * Not called if _THREAD_ERROR_DETECTION=0.
 * Continue execution if _THREAD_ERROR_DETECTION=1.
 * Dump core if _THREAD_ERROR_DETECTION=2.
 */
void
rwlock_error(const rwlock_t *rp, const char *who, const char *msg)
{
	rwlock_t rcopy;
	uint32_t rwstate;
	char buf[800];
	uberdata_t *udp;
	ulwp_t *self;
	lwpid_t lwpid;
	pid_t pid;
	int process;

	/*
	 * Take a snapshot of the rwlock before it changes (we hope!).
	 * Use memcpy() rather than 'rcopy = *rp' in case rp is unaligned.
	 */
	(void) memcpy(&rcopy, rp, sizeof (rcopy));

	/* avoid recursion deadlock */
	if ((self = __curthread()) != NULL) {
		if (assert_thread == self)
			_exit(127);
		enter_critical(self);
		(void) _lwp_mutex_lock(&assert_lock);
		assert_thread = self;
		lwpid = self->ul_lwpid;
		udp = self->ul_uberdata;
		pid = udp->pid;
	} else {
		self = NULL;
		(void) _lwp_mutex_lock(&assert_lock);
		lwpid = _lwp_self();
		udp = &__uberdata;
		pid = getpid();
	}

	rwstate = (uint32_t)rcopy.rwlock_readers;
	process = (rcopy.rwlock_type & USYNC_PROCESS);

	(void) strcpy(buf,
	    "\n*** _THREAD_ERROR_DETECTION: lock usage error detected ***\n");
	(void) strcat(buf, who);
	(void) strcat(buf, "(");
	ultos((uint64_t)(uintptr_t)rp, 16, buf + strlen(buf));
	(void) strcat(buf, "): ");
	(void) strcat(buf, msg);
	(void) strcat(buf, "\ncalling thread is ");
	ultos((uint64_t)(uintptr_t)self, 16, buf + strlen(buf));
	(void) strcat(buf, " thread-id ");
	ultos((uint64_t)lwpid, 10, buf + strlen(buf));
	if (process) {
		(void) strcat(buf, " in process ");
		ultos((uint64_t)pid, 10, buf + strlen(buf));
	}
	if (rwstate & URW_WRITE_LOCKED) {
		(void) strcat(buf, "\nthe writer lock owner is ");
		ultos((uint64_t)rcopy.rwlock_owner, 16,
		    buf + strlen(buf));
		if (process) {
			(void) strcat(buf, " in process ");
			ultos((uint64_t)rcopy.rwlock_ownerpid, 10,
			    buf + strlen(buf));
		}
	} else if (rwstate & URW_READERS_MASK) {
		(void) strcat(buf, "\nthe reader lock is held by ");
		ultos((uint64_t)(rwstate & URW_READERS_MASK), 10,
		    buf + strlen(buf));
		(void) strcat(buf, " readers");
	} else {
		(void) strcat(buf, "\nthe lock is unowned");
	}
	if (rwstate & URW_HAS_WAITERS)
		(void) strcat(buf, "\nand the lock appears to have waiters");
	(void) strcat(buf, "\n\n");
	(void) __write(2, buf, strlen(buf));
	if (udp->uberflags.uf_thread_error_detection >= 2)
		Abort(buf);
	assert_thread = NULL;
	(void) _lwp_mutex_unlock(&assert_lock);
	if (self != NULL)
		exit_critical(self);
}

/*
 * Report a thread usage error.
 * Not called if _THREAD_ERROR_DETECTION=0.
 * Writes message and continues execution if _THREAD_ERROR_DETECTION=1.
 * Writes message and dumps core if _THREAD_ERROR_DETECTION=2.
 */
void
thread_error(const char *msg)
{
	char buf[800];
	uberdata_t *udp;
	ulwp_t *self;
	lwpid_t lwpid;

	/* avoid recursion deadlock */
	if ((self = __curthread()) != NULL) {
		if (assert_thread == self)
			_exit(127);
		enter_critical(self);
		(void) _lwp_mutex_lock(&assert_lock);
		assert_thread = self;
		lwpid = self->ul_lwpid;
		udp = self->ul_uberdata;
	} else {
		self = NULL;
		(void) _lwp_mutex_lock(&assert_lock);
		lwpid = _lwp_self();
		udp = &__uberdata;
	}

	(void) strcpy(buf, "\n*** _THREAD_ERROR_DETECTION: "
	    "thread usage error detected ***\n*** ");
	(void) strcat(buf, msg);

	(void) strcat(buf, "\n*** calling thread is ");
	ultos((uint64_t)(uintptr_t)self, 16, buf + strlen(buf));
	(void) strcat(buf, " thread-id ");
	ultos((uint64_t)lwpid, 10, buf + strlen(buf));
	(void) strcat(buf, "\n\n");
	(void) __write(2, buf, strlen(buf));
	if (udp->uberflags.uf_thread_error_detection >= 2)
		Abort(buf);
	assert_thread = NULL;
	(void) _lwp_mutex_unlock(&assert_lock);
	if (self != NULL)
		exit_critical(self);
}

/*
 * We use __assfail() because the libc __assert() calls
 * gettext() which calls malloc() which grabs a mutex.
 * We do everything without calling standard i/o.
 * assfail() and _assfail() are exported functions;
 * __assfail() is private to libc.
 */
#pragma weak _assfail = __assfail
void
__assfail(const char *assertion, const char *filename, int line_num)
{
	char buf[800];	/* no assert() message in the library is this long */
	ulwp_t *self;
	lwpid_t lwpid;

	/* avoid recursion deadlock */
	if ((self = __curthread()) != NULL) {
		if (assert_thread == self)
			_exit(127);
		enter_critical(self);
		(void) _lwp_mutex_lock(&assert_lock);
		assert_thread = self;
		lwpid = self->ul_lwpid;
	} else {
		self = NULL;
		(void) _lwp_mutex_lock(&assert_lock);
		lwpid = _lwp_self();
	}

	(void) strcpy(buf, "assertion failed for thread ");
	ultos((uint64_t)(uintptr_t)self, 16, buf + strlen(buf));
	(void) strcat(buf, ", thread-id ");
	ultos((uint64_t)lwpid, 10, buf + strlen(buf));
	(void) strcat(buf, ": ");
	(void) strcat(buf, assertion);
	(void) strcat(buf, ", file ");
	(void) strcat(buf, filename);
	(void) strcat(buf, ", line ");
	ultos((uint64_t)line_num, 10, buf + strlen(buf));
	(void) strcat(buf, "\n");
	(void) __write(2, buf, strlen(buf));
	/*
	 * We could replace the call to Abort() with the following code
	 * if we want just to issue a warning message and not die.
	 *	assert_thread = NULL;
	 *	_lwp_mutex_unlock(&assert_lock);
	 *	if (self != NULL)
	 *		exit_critical(self);
	 */
	Abort(buf);
}

/*
 * We define and export this version of assfail() just because libaio
 * used to define and export it, needlessly.  Now that libaio is folded
 * into libc, we need to continue this for ABI/version reasons.
 * We don't use "#pragma weak assfail __assfail" in order to avoid
 * warnings from the check_fnames utility at build time for libraries
 * that define their own version of assfail().
 */
void
assfail(const char *assertion, const char *filename, int line_num)
{
	__assfail(assertion, filename, line_num);
}

void
assfail3(const char *assertion, uintmax_t lv, const char *op, uintmax_t rv,
    const char *filename, int line_num)
{
	char buf[1000];
	(void) strcpy(buf, assertion);
	(void) strcat(buf, " (0x");
	ultos((uint64_t)lv, 16, buf + strlen(buf));
	(void) strcat(buf, " ");
	(void) strcat(buf, op);
	(void) strcat(buf, " 0x");
	ultos((uint64_t)rv, 16, buf + strlen(buf));
	(void) strcat(buf, ")");
	__assfail(buf, filename, line_num);
}