root/sys/cddl/dev/dtrace/dtrace_debug.c
/*-
 * Copyright (C) 2008 John Birrell <jb@freebsd.org>.
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice(s), this list of conditions and the following disclaimer as
 *    the first lines of this file unmodified other than the possible 
 *    addition of one or more copyright notices.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice(s), this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDER(S) ``AS IS'' AND ANY
 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED.  IN NO EVENT SHALL THE COPYRIGHT HOLDER(S) BE LIABLE FOR ANY
 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
 * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
 * DAMAGE.
 *
 */

#ifdef DEBUG

#include <machine/atomic.h>

#define DTRACE_DEBUG_BUFR_SIZE  (32 * 1024)

struct dtrace_debug_data {
        uintptr_t lock __aligned(CACHE_LINE_SIZE);
        char bufr[DTRACE_DEBUG_BUFR_SIZE];
        char *first;
        char *last;
        char *next;
} dtrace_debug_data[MAXCPU];

static char dtrace_debug_bufr[DTRACE_DEBUG_BUFR_SIZE];

static void
dtrace_debug_lock(int cpu)
{
         uintptr_t tid;

        tid = (uintptr_t)curthread;
        spinlock_enter();
        while (atomic_cmpset_acq_ptr(&dtrace_debug_data[cpu].lock, 0, tid) == 0)                /* Loop until the lock is obtained. */
                ;
}

static void
dtrace_debug_unlock(int cpu)
{
        atomic_store_rel_ptr(&dtrace_debug_data[cpu].lock, 0);
        spinlock_exit();
}

static void
dtrace_debug_init(void *dummy)
{
        int i;
        struct dtrace_debug_data *d;

        CPU_FOREACH(i) {
                d = &dtrace_debug_data[i];

                if (d->first == NULL) {
                        d->first = d->bufr;
                        d->next = d->bufr;
                        d->last = d->bufr + DTRACE_DEBUG_BUFR_SIZE - 1;
                        *(d->last) = '\0';
                }
        }
}

SYSINIT(dtrace_debug_init, SI_SUB_KDTRACE, SI_ORDER_ANY, dtrace_debug_init, NULL);
SYSINIT(dtrace_debug_smpinit, SI_SUB_SMP, SI_ORDER_ANY, dtrace_debug_init, NULL);

static void
dtrace_debug_output(void)
{
        char *p;
        int i;
        struct dtrace_debug_data *d;
        uintptr_t count;

        CPU_FOREACH(i) {
                dtrace_debug_lock(i);

                d = &dtrace_debug_data[i];

                count = 0;

                if (d->first < d->next) {
                        char *p1 = dtrace_debug_bufr;

                        count = (uintptr_t) d->next - (uintptr_t) d->first;

                        for (p = d->first; p < d->next; p++)
                                *p1++ = *p;
                } else if (d->first > d->next) {
                        char *p1 = dtrace_debug_bufr;

                        count = (uintptr_t) d->last - (uintptr_t) d->first;

                        for (p = d->first; p < d->last; p++)
                                *p1++ = *p;

                        count += (uintptr_t) d->next - (uintptr_t) d->bufr;

                        for (p = d->bufr; p < d->next; p++)
                                *p1++ = *p;
                }

                d->first = d->bufr;
                d->next = d->bufr;

                dtrace_debug_unlock(i);

                if (count > 0) {
                        char *last = dtrace_debug_bufr + count;

                        p = dtrace_debug_bufr;

                        while (p < last) {
                                if (*p == '\0') {
                                        p++;
                                        continue;
                                }

                                printf("%s", p);

                                p += strlen(p);
                        }
                }
        }
}

/*
 * Functions below here are called from the probe context, so they can't call
 * _any_ functions outside the dtrace module without running foul of the function
 * boundary trace provider (fbt). The purpose of these functions is limited to
 * buffering debug strings for output when the probe completes on the current CPU.
 */

static __inline void
dtrace_debug__putc(int cpu, char c)
{
        struct dtrace_debug_data *d;

        d = &dtrace_debug_data[cpu];
        *d->next++ = c;

        if (d->next == d->last)
                d->next = d->bufr;

        *(d->next) = '\0';

        if (d->next == d->first)
                d->first++;

        if (d->first == d->last)
                d->first = d->bufr;
}

static void __used
dtrace_debug_putc(char c)
{
        int cpu;

        cpu = curcpu;
        dtrace_debug_lock(cpu);

        dtrace_debug__putc(cpu, c);

        dtrace_debug_unlock(cpu);
}

static void __used
dtrace_debug_puts(const char *s)
{
        int cpu;
        
        cpu = curcpu;
        dtrace_debug_lock(cpu);

        while (*s != '\0')
                dtrace_debug__putc(cpu, *s++);

        dtrace_debug__putc(cpu, '\0');

        dtrace_debug_unlock(cpu);
}

/*
 * Snaffled from sys/kern/subr_prf.c
 *
 * Put a NUL-terminated ASCII number (base <= 36) in a buffer in reverse
 * order; return an optional length and a pointer to the last character
 * written in the buffer (i.e., the first character of the string).
 * The buffer pointed to by `nbuf' must have length >= MAXNBUF.
 */
static char *
dtrace_debug_ksprintn(char *nbuf, uintmax_t num, int base, int *lenp, int upper)
{
        char *p, c;

        p = nbuf;
        *p = '\0';
        do {
                c = hex2ascii(num % base);
                *++p = upper ? toupper(c) : c;
        } while (num /= base);
        if (lenp)
                *lenp = p - nbuf;
        return (p);
}

#define MAXNBUF (sizeof(intmax_t) * NBBY + 1)

static void
dtrace_debug_vprintf(int cpu, const char *fmt, va_list ap)
{
        char nbuf[MAXNBUF];
        const char *p, *percent, *q;
        u_char *up;
        int ch, n;
        uintmax_t num;
        int base, lflag, qflag, tmp, width, ladjust, sharpflag, neg, sign, dot;
        int cflag, hflag, jflag, tflag, zflag;
        int dwidth, upper;
        int radix = 10;
        char padc;
        int stop = 0, retval = 0;

        num = 0;

        if (fmt == NULL)
                fmt = "(fmt null)\n";

        for (;;) {
                padc = ' ';
                width = 0;
                while ((ch = (u_char)*fmt++) != '%' || stop) {
                        if (ch == '\0') {
                                dtrace_debug__putc(cpu, '\0');
                                return;
                        }
                        dtrace_debug__putc(cpu, ch);
                }
                percent = fmt - 1;
                qflag = 0; lflag = 0; ladjust = 0; sharpflag = 0; neg = 0;
                sign = 0; dot = 0; dwidth = 0; upper = 0;
                cflag = 0; hflag = 0; jflag = 0; tflag = 0; zflag = 0;
reswitch:       switch (ch = (u_char)*fmt++) {
                case '.':
                        dot = 1;
                        goto reswitch;
                case '#':
                        sharpflag = 1;
                        goto reswitch;
                case '+':
                        sign = 1;
                        goto reswitch;
                case '-':
                        ladjust = 1;
                        goto reswitch;
                case '%':
                        dtrace_debug__putc(cpu, ch);
                        break;
                case '*':
                        if (!dot) {
                                width = va_arg(ap, int);
                                if (width < 0) {
                                        ladjust = !ladjust;
                                        width = -width;
                                }
                        } else {
                                dwidth = va_arg(ap, int);
                        }
                        goto reswitch;
                case '0':
                        if (!dot) {
                                padc = '0';
                                goto reswitch;
                        }
                case '1': case '2': case '3': case '4':
                case '5': case '6': case '7': case '8': case '9':
                                for (n = 0;; ++fmt) {
                                        n = n * 10 + ch - '0';
                                        ch = *fmt;
                                        if (ch < '0' || ch > '9')
                                                break;
                                }
                        if (dot)
                                dwidth = n;
                        else
                                width = n;
                        goto reswitch;
                case 'b':
                        num = (u_int)va_arg(ap, int);
                        p = va_arg(ap, char *);
                        for (q = dtrace_debug_ksprintn(nbuf, num, *p++, NULL, 0); *q;)
                                dtrace_debug__putc(cpu, *q--);

                        if (num == 0)
                                break;

                        for (tmp = 0; *p;) {
                                n = *p++;
                                if (num & (1 << (n - 1))) {
                                        dtrace_debug__putc(cpu, tmp ? ',' : '<');
                                        for (; (n = *p) > ' '; ++p)
                                                dtrace_debug__putc(cpu, n);
                                        tmp = 1;
                                } else
                                        for (; *p > ' '; ++p)
                                                continue;
                        }
                        if (tmp)
                                dtrace_debug__putc(cpu, '>');
                        break;
                case 'c':
                        dtrace_debug__putc(cpu, va_arg(ap, int));
                        break;
                case 'D':
                        up = va_arg(ap, u_char *);
                        p = va_arg(ap, char *);
                        if (!width)
                                width = 16;
                        while(width--) {
                                dtrace_debug__putc(cpu, hex2ascii(*up >> 4));
                                dtrace_debug__putc(cpu, hex2ascii(*up & 0x0f));
                                up++;
                                if (width)
                                        for (q=p;*q;q++)
                                                dtrace_debug__putc(cpu, *q);
                        }
                        break;
                case 'd':
                case 'i':
                        base = 10;
                        sign = 1;
                        goto handle_sign;
                case 'h':
                        if (hflag) {
                                hflag = 0;
                                cflag = 1;
                        } else
                                hflag = 1;
                        goto reswitch;
                case 'j':
                        jflag = 1;
                        goto reswitch;
                case 'l':
                        if (lflag) {
                                lflag = 0;
                                qflag = 1;
                        } else
                                lflag = 1;
                        goto reswitch;
                case 'n':
                        if (jflag)
                                *(va_arg(ap, intmax_t *)) = retval;
                        else if (qflag)
                                *(va_arg(ap, quad_t *)) = retval;
                        else if (lflag)
                                *(va_arg(ap, long *)) = retval;
                        else if (zflag)
                                *(va_arg(ap, size_t *)) = retval;
                        else if (hflag)
                                *(va_arg(ap, short *)) = retval;
                        else if (cflag)
                                *(va_arg(ap, char *)) = retval;
                        else
                                *(va_arg(ap, int *)) = retval;
                        break;
                case 'o':
                        base = 8;
                        goto handle_nosign;
                case 'p':
                        base = 16;
                        sharpflag = (width == 0);
                        sign = 0;
                        num = (uintptr_t)va_arg(ap, void *);
                        goto number;
                case 'q':
                        qflag = 1;
                        goto reswitch;
                case 'r':
                        base = radix;
                        if (sign)
                                goto handle_sign;
                        goto handle_nosign;
                case 's':
                        p = va_arg(ap, char *);
                        if (p == NULL)
                                p = "(null)";
                        if (!dot)
                                n = strlen (p);
                        else
                                for (n = 0; n < dwidth && p[n]; n++)
                                        continue;

                        width -= n;

                        if (!ladjust && width > 0)
                                while (width--)
                                        dtrace_debug__putc(cpu, padc);
                        while (n--)
                                dtrace_debug__putc(cpu, *p++);
                        if (ladjust && width > 0)
                                while (width--)
                                        dtrace_debug__putc(cpu, padc);
                        break;
                case 't':
                        tflag = 1;
                        goto reswitch;
                case 'u':
                        base = 10;
                        goto handle_nosign;
                case 'X':
                        upper = 1;
                case 'x':
                        base = 16;
                        goto handle_nosign;
                case 'y':
                        base = 16;
                        sign = 1;
                        goto handle_sign;
                case 'z':
                        zflag = 1;
                        goto reswitch;
handle_nosign:
                        sign = 0;
                        if (jflag)
                                num = va_arg(ap, uintmax_t);
                        else if (qflag)
                                num = va_arg(ap, u_quad_t);
                        else if (tflag)
                                num = va_arg(ap, ptrdiff_t);
                        else if (lflag)
                                num = va_arg(ap, u_long);
                        else if (zflag)
                                num = va_arg(ap, size_t);
                        else if (hflag)
                                num = (u_short)va_arg(ap, int);
                        else if (cflag)
                                num = (u_char)va_arg(ap, int);
                        else
                                num = va_arg(ap, u_int);
                        goto number;
handle_sign:
                        if (jflag)
                                num = va_arg(ap, intmax_t);
                        else if (qflag)
                                num = va_arg(ap, quad_t);
                        else if (tflag)
                                num = va_arg(ap, ptrdiff_t);
                        else if (lflag)
                                num = va_arg(ap, long);
                        else if (zflag)
                                num = va_arg(ap, size_t);
                        else if (hflag)
                                num = (short)va_arg(ap, int);
                        else if (cflag)
                                num = (char)va_arg(ap, int);
                        else
                                num = va_arg(ap, int);
number:
                        if (sign && (intmax_t)num < 0) {
                                neg = 1;
                                num = -(intmax_t)num;
                        }
                        p = dtrace_debug_ksprintn(nbuf, num, base, &tmp, upper);
                        if (sharpflag && num != 0) {
                                if (base == 8)
                                        tmp++;
                                else if (base == 16)
                                        tmp += 2;
                        }
                        if (neg)
                                tmp++;

                        if (!ladjust && padc != '0' && width
                            && (width -= tmp) > 0)
                                while (width--)
                                        dtrace_debug__putc(cpu, padc);
                        if (neg)
                                dtrace_debug__putc(cpu, '-');
                        if (sharpflag && num != 0) {
                                if (base == 8) {
                                        dtrace_debug__putc(cpu, '0');
                                } else if (base == 16) {
                                        dtrace_debug__putc(cpu, '0');
                                        dtrace_debug__putc(cpu, 'x');
                                }
                        }
                        if (!ladjust && width && (width -= tmp) > 0)
                                while (width--)
                                        dtrace_debug__putc(cpu, padc);

                        while (*p)
                                dtrace_debug__putc(cpu, *p--);

                        if (ladjust && width && (width -= tmp) > 0)
                                while (width--)
                                        dtrace_debug__putc(cpu, padc);

                        break;
                default:
                        while (percent < fmt)
                                dtrace_debug__putc(cpu, *percent++);
                        /*
                         * Since we ignore an formatting argument it is no 
                         * longer safe to obey the remaining formatting
                         * arguments as the arguments will no longer match
                         * the format specs.
                         */
                        stop = 1;
                        break;
                }
        }

        dtrace_debug__putc(cpu, '\0');
}

void
dtrace_debug_printf(const char *fmt, ...)
{
        va_list ap;
        int cpu;

        cpu = curcpu;
        dtrace_debug_lock(cpu);

        va_start(ap, fmt);

        dtrace_debug_vprintf(cpu, fmt, ap);

        va_end(ap);

        dtrace_debug_unlock(cpu);
}

#else

#define dtrace_debug_output()
#define dtrace_debug_puts(_s)
#define dtrace_debug_printf(fmt, ...)

#endif