411 lines
17 KiB
C
411 lines
17 KiB
C
/*
|
|
* This file is part of the SPL: Solaris Porting Layer.
|
|
*
|
|
* Copyright (c) 2008 Lawrence Livermore National Security, LLC.
|
|
* Produced at Lawrence Livermore National Laboratory
|
|
* Written by:
|
|
* Brian Behlendorf <behlendorf1@llnl.gov>,
|
|
* Herb Wartens <wartens2@llnl.gov>,
|
|
* Jim Garlick <garlick@llnl.gov>
|
|
* UCRL-CODE-235197
|
|
*
|
|
* This is free software; you can redistribute it and/or modify it
|
|
* under the terms of the GNU General Public License as published by
|
|
* the Free Software Foundation; either version 2 of the License, or
|
|
* (at your option) any later version.
|
|
*
|
|
* This is distributed in the hope that it will be useful, but WITHOUT
|
|
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
|
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
|
* for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License along
|
|
* with this program; if not, write to the Free Software Foundation, Inc.,
|
|
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
|
*/
|
|
|
|
#ifndef _SPL_DEBUG_H
|
|
#define _SPL_DEBUG_H
|
|
|
|
#ifdef __cplusplus
|
|
extern "C" {
|
|
#endif
|
|
|
|
#include <linux/sched.h> /* THREAD_SIZE */
|
|
#include <linux/proc_fs.h>
|
|
|
|
extern unsigned long spl_debug_mask;
|
|
extern unsigned long spl_debug_subsys;
|
|
|
|
#define S_UNDEFINED 0x00000001
|
|
#define S_ATOMIC 0x00000002
|
|
#define S_KOBJ 0x00000004
|
|
#define S_VNODE 0x00000008
|
|
#define S_TIME 0x00000010
|
|
#define S_RWLOCK 0x00000020
|
|
#define S_THREAD 0x00000040
|
|
#define S_CONDVAR 0x00000080
|
|
#define S_MUTEX 0x00000100
|
|
#define S_RNG 0x00000200
|
|
#define S_TASKQ 0x00000400
|
|
#define S_KMEM 0x00000800
|
|
#define S_DEBUG 0x00001000
|
|
#define S_GENERIC 0x00002000
|
|
#define S_PROC 0x00004000
|
|
#define S_MODULE 0x00008000
|
|
|
|
#define D_TRACE 0x00000001
|
|
#define D_INFO 0x00000002
|
|
#define D_WARNING 0x00000004
|
|
#define D_ERROR 0x00000008
|
|
#define D_EMERG 0x00000010
|
|
#define D_CONSOLE 0x00000020
|
|
#define D_IOCTL 0x00000040
|
|
#define D_DPRINTF 0x00000080
|
|
#define D_OTHER 0x00000100
|
|
|
|
#define D_CANTMASK (D_ERROR | D_EMERG | D_WARNING | D_CONSOLE)
|
|
#define DEBUG_SUBSYSTEM S_UNDEFINED
|
|
|
|
int debug_init(void);
|
|
void debug_fini(void);
|
|
int spl_debug_mask2str(char *str, int size, unsigned long mask, int is_subsys);
|
|
int spl_debug_str2mask(unsigned long *mask, const char *str, int is_subsys);
|
|
|
|
extern unsigned long spl_debug_subsys;
|
|
extern unsigned long spl_debug_mask;
|
|
extern unsigned long spl_debug_printk;
|
|
extern int spl_debug_mb;
|
|
extern unsigned int spl_debug_binary;
|
|
extern unsigned int spl_debug_catastrophe;
|
|
extern unsigned int spl_debug_panic_on_bug;
|
|
extern char spl_debug_file_path[PATH_MAX];
|
|
extern unsigned int spl_console_ratelimit;
|
|
extern long spl_console_max_delay;
|
|
extern long spl_console_min_delay;
|
|
extern unsigned int spl_console_backoff;
|
|
extern unsigned int spl_debug_stack;
|
|
|
|
#define TCD_MAX_PAGES (5 << (20 - PAGE_SHIFT))
|
|
#define TCD_STOCK_PAGES (TCD_MAX_PAGES)
|
|
#define TRACE_CONSOLE_BUFFER_SIZE 1024
|
|
|
|
#define SPL_DEFAULT_MAX_DELAY (600 * HZ)
|
|
#define SPL_DEFAULT_MIN_DELAY ((HZ + 1) / 2)
|
|
#define SPL_DEFAULT_BACKOFF 2
|
|
|
|
#define DL_NOTHREAD 0x0001 /* Do not create a new thread */
|
|
#define DL_SINGLE_CPU 0x0002 /* Collect pages from this CPU */
|
|
|
|
typedef struct dumplog_priv {
|
|
wait_queue_head_t dp_waitq;
|
|
pid_t dp_pid;
|
|
int dp_flags;
|
|
atomic_t dp_done;
|
|
} dumplog_priv_t;
|
|
|
|
typedef struct {
|
|
unsigned long cdls_next;
|
|
int cdls_count;
|
|
long cdls_delay;
|
|
} spl_debug_limit_state_t;
|
|
|
|
/* Three trace data types */
|
|
typedef enum {
|
|
TCD_TYPE_PROC,
|
|
TCD_TYPE_SOFTIRQ,
|
|
TCD_TYPE_IRQ,
|
|
TCD_TYPE_MAX
|
|
} tcd_type_t;
|
|
|
|
union trace_data_union {
|
|
struct trace_cpu_data {
|
|
/* pages with trace records not yet processed by tracefiled */
|
|
struct list_head tcd_pages;
|
|
/* number of pages on ->tcd_pages */
|
|
unsigned long tcd_cur_pages;
|
|
/* Max number of pages allowed on ->tcd_pages */
|
|
unsigned long tcd_max_pages;
|
|
|
|
/*
|
|
* preallocated pages to write trace records into. Pages from
|
|
* ->tcd_stock_pages are moved to ->tcd_pages by spl_debug_msg().
|
|
*
|
|
* This list is necessary, because on some platforms it's
|
|
* impossible to perform efficient atomic page allocation in a
|
|
* non-blockable context.
|
|
*
|
|
* Such platforms fill ->tcd_stock_pages "on occasion", when
|
|
* tracing code is entered in blockable context.
|
|
*
|
|
* trace_get_tage_try() tries to get a page from
|
|
* ->tcd_stock_pages first and resorts to atomic page
|
|
* allocation only if this queue is empty. ->tcd_stock_pages
|
|
* is replenished when tracing code is entered in blocking
|
|
* context (darwin-tracefile.c:trace_get_tcd()). We try to
|
|
* maintain TCD_STOCK_PAGES (40 by default) pages in this
|
|
* queue. Atomic allocation is only required if more than
|
|
* TCD_STOCK_PAGES pagesful are consumed by trace records all
|
|
* emitted in non-blocking contexts. Which is quite unlikely.
|
|
*/
|
|
struct list_head tcd_stock_pages;
|
|
/* number of pages on ->tcd_stock_pages */
|
|
unsigned long tcd_cur_stock_pages;
|
|
|
|
unsigned short tcd_shutting_down;
|
|
unsigned short tcd_cpu;
|
|
unsigned short tcd_type;
|
|
/* The factors to share debug memory. */
|
|
unsigned short tcd_pages_factor;
|
|
} tcd;
|
|
char __pad[L1_CACHE_ALIGN(sizeof(struct trace_cpu_data))];
|
|
};
|
|
|
|
extern union trace_data_union (*trace_data[TCD_TYPE_MAX])[NR_CPUS];
|
|
|
|
#define tcd_for_each(tcd, i, j) \
|
|
for (i = 0; trace_data[i] != NULL; i++) \
|
|
for (j = 0, ((tcd) = &(*trace_data[i])[j].tcd); \
|
|
j < num_possible_cpus(); j++, (tcd) = &(*trace_data[i])[j].tcd)
|
|
|
|
#define tcd_for_each_type_lock(tcd, i) \
|
|
for (i = 0; trace_data[i] && \
|
|
(tcd = &(*trace_data[i])[smp_processor_id()].tcd) && \
|
|
trace_lock_tcd(tcd); trace_unlock_tcd(tcd), i++)
|
|
|
|
struct trace_page {
|
|
struct page * page; /* page itself */
|
|
struct list_head linkage; /* Used by lists in trace_data_union */
|
|
unsigned int used; /* number of bytes used within this page */
|
|
unsigned short cpu; /* cpu that owns this page */
|
|
unsigned short type; /* type(context) of this page */
|
|
};
|
|
|
|
struct page_collection {
|
|
struct list_head pc_pages;
|
|
spinlock_t pc_lock;
|
|
int pc_want_daemon_pages;
|
|
};
|
|
|
|
#define SBUG() spl_debug_bug(__FILE__, __FUNCTION__, __LINE__, 0);
|
|
|
|
#ifdef NDEBUG
|
|
|
|
#define CDEBUG_STACK() (0)
|
|
#define CDEBUG(mask, format, a...) ((void)0)
|
|
#define CWARN(fmt, a...) ((void)0)
|
|
#define CERROR(fmt, a...) ((void)0)
|
|
#define CEMERG(fmt, a...) ((void)0)
|
|
#define CONSOLE(mask, fmt, a...) ((void)0)
|
|
|
|
#define ENTRY ((void)0)
|
|
#define EXIT ((void)0)
|
|
#define RETURN(x) return (x)
|
|
#define GOTO(x, y) { ((void)(y)); goto x; }
|
|
|
|
#define __ASSERT(x) ((void)0)
|
|
#define __ASSERT_TAGE_INVARIANT(x) ((void)0)
|
|
#define ASSERT(x) ((void)0)
|
|
#define VERIFY(x) ((void)(x))
|
|
|
|
#define VERIFY3_IMPL(x, y, z, t, f, c) if (x == z) ((void)0)
|
|
|
|
#define VERIFY3S(x,y,z) VERIFY3_IMPL(x, y, z, int64_t, "%ld", (long))
|
|
#define VERIFY3U(x,y,z) VERIFY3_IMPL(x, y, z, uint64_t, "%lu", (unsigned long))
|
|
#define VERIFY3P(x,y,z) VERIFY3_IMPL(x, y, z, uintptr_t, "%p", (void *))
|
|
|
|
#define ASSERT3S(x,y,z) VERIFY3S(x, y, z)
|
|
#define ASSERT3U(x,y,z) VERIFY3U(x, y, z)
|
|
#define ASSERT3P(x,y,z) VERIFY3P(x, y, z)
|
|
|
|
#else /* NDEBUG */
|
|
|
|
#ifdef __ia64__
|
|
#define CDEBUG_STACK() (THREAD_SIZE - \
|
|
((unsigned long)__builtin_dwarf_cfa() & \
|
|
(THREAD_SIZE - 1)))
|
|
#else
|
|
#define CDEBUG_STACK() (THREAD_SIZE - \
|
|
((unsigned long)__builtin_frame_address(0) & \
|
|
(THREAD_SIZE - 1)))
|
|
# endif /* __ia64__ */
|
|
|
|
/* DL_SINGLE_CPU flag is passed to spl_debug_bug() because we are about
|
|
* to over run our stack and likely damage at least one other unknown
|
|
* thread stack. We must finish generating the needed debug info within
|
|
* this thread context because once we yeild the CPU its very likely
|
|
* the system will crash.
|
|
*/
|
|
#define __CHECK_STACK(file, func, line) \
|
|
do { \
|
|
if (unlikely(CDEBUG_STACK() > spl_debug_stack)) { \
|
|
spl_debug_stack = CDEBUG_STACK(); \
|
|
\
|
|
if (unlikely(CDEBUG_STACK() > (4 * THREAD_SIZE) / 5)) { \
|
|
spl_debug_msg(NULL, D_TRACE, D_WARNING, \
|
|
file, func, line, "Error " \
|
|
"exceeded maximum safe stack " \
|
|
"size (%lu/%lu)\n", \
|
|
CDEBUG_STACK(), THREAD_SIZE); \
|
|
spl_debug_bug(file, func, line, DL_SINGLE_CPU); \
|
|
} \
|
|
} \
|
|
} while (0)
|
|
|
|
#define CHECK_STACK() __CHECK_STACK(__FILE__, __func__, __LINE__)
|
|
|
|
/* ASSERTION that is safe to use within the debug system */
|
|
#define __ASSERT(cond) \
|
|
do { \
|
|
if (unlikely(!(cond))) { \
|
|
printk(KERN_ERR "ASSERTION(" #cond ") failed"); \
|
|
BUG(); \
|
|
} \
|
|
} while (0)
|
|
|
|
#define __ASSERT_TAGE_INVARIANT(tage) \
|
|
do { \
|
|
__ASSERT(tage != NULL); \
|
|
__ASSERT(tage->page != NULL); \
|
|
__ASSERT(tage->used <= PAGE_SIZE); \
|
|
__ASSERT(page_count(tage->page) > 0); \
|
|
} while(0)
|
|
|
|
/* ASSERTION that will debug log used outside the debug sysytem */
|
|
#define ASSERT(cond) \
|
|
do { \
|
|
CHECK_STACK(); \
|
|
\
|
|
if (unlikely(!(cond))) { \
|
|
spl_debug_msg(NULL, DEBUG_SUBSYSTEM, D_EMERG, \
|
|
__FILE__, __FUNCTION__, __LINE__, \
|
|
"ASSERTION(" #cond ") failed\n"); \
|
|
SBUG(); \
|
|
} \
|
|
} while (0)
|
|
|
|
#define ASSERTF(cond, fmt, a...) \
|
|
do { \
|
|
CHECK_STACK(); \
|
|
\
|
|
if (unlikely(!(cond))) { \
|
|
spl_debug_msg(NULL, DEBUG_SUBSYSTEM, D_EMERG, \
|
|
__FILE__, __FUNCTION__, __LINE__, \
|
|
"ASSERTION(" #cond ") failed: " fmt, \
|
|
## a); \
|
|
SBUG(); \
|
|
} \
|
|
} while (0)
|
|
|
|
#define VERIFY3_IMPL(LEFT, OP, RIGHT, TYPE, FMT, CAST) \
|
|
do { \
|
|
CHECK_STACK(); \
|
|
\
|
|
if (!((TYPE)(LEFT) OP (TYPE)(RIGHT))) { \
|
|
spl_debug_msg(NULL, DEBUG_SUBSYSTEM, D_EMERG, \
|
|
__FILE__, __FUNCTION__, __LINE__, \
|
|
"VERIFY3(" FMT " " #OP " " FMT ")\n", \
|
|
CAST (LEFT), CAST (RIGHT)); \
|
|
SBUG(); \
|
|
} \
|
|
} while (0)
|
|
|
|
#define VERIFY3S(x,y,z) VERIFY3_IMPL(x, y, z, int64_t, "%ld", (long))
|
|
#define VERIFY3U(x,y,z) VERIFY3_IMPL(x, y, z, uint64_t, "%lu", (unsigned long))
|
|
#define VERIFY3P(x,y,z) VERIFY3_IMPL(x, y, z, uintptr_t, "%p", (void *))
|
|
|
|
#define ASSERT3S(x,y,z) VERIFY3S(x, y, z)
|
|
#define ASSERT3U(x,y,z) VERIFY3U(x, y, z)
|
|
#define ASSERT3P(x,y,z) VERIFY3P(x, y, z)
|
|
|
|
#define VERIFY(x) ASSERT(x)
|
|
|
|
#define __CDEBUG(cdls, subsys, mask, format, a...) \
|
|
do { \
|
|
CHECK_STACK(); \
|
|
\
|
|
if (((mask) & D_CANTMASK) != 0 || \
|
|
((spl_debug_mask & (mask)) != 0 && \
|
|
(spl_debug_subsys & (subsys)) != 0)) \
|
|
spl_debug_msg(cdls, subsys, mask, \
|
|
__FILE__, __FUNCTION__, __LINE__, \
|
|
format, ## a); \
|
|
} while (0)
|
|
|
|
#define CDEBUG(mask, format, a...) \
|
|
__CDEBUG(NULL, DEBUG_SUBSYSTEM, mask, format, ## a)
|
|
|
|
#define __CDEBUG_LIMIT(subsys, mask, format, a...) \
|
|
do { \
|
|
static spl_debug_limit_state_t cdls; \
|
|
\
|
|
__CDEBUG(&cdls, subsys, mask, format, ## a); \
|
|
} while (0)
|
|
|
|
#define CDEBUG_LIMIT(mask, format, a...) \
|
|
__CDEBUG_LIMIT(DEBUG_SUBSYSTEM, mask, format, ## a)
|
|
|
|
#define CWARN(fmt, a...) CDEBUG_LIMIT(D_WARNING, fmt, ## a)
|
|
#define CERROR(fmt, a...) CDEBUG_LIMIT(D_ERROR, fmt, ## a)
|
|
#define CEMERG(fmt, a...) CDEBUG_LIMIT(D_EMERG, fmt, ## a)
|
|
#define CONSOLE(mask, fmt, a...) CDEBUG(D_CONSOLE | (mask), fmt, ## a)
|
|
|
|
#define GOTO(label, rc) \
|
|
do { \
|
|
long GOTO__ret = (long)(rc); \
|
|
CDEBUG(D_TRACE,"Process leaving via %s (rc=%lu : %ld : %lx)\n", \
|
|
#label, (unsigned long)GOTO__ret, (signed long)GOTO__ret,\
|
|
(signed long)GOTO__ret); \
|
|
goto label; \
|
|
} while (0)
|
|
|
|
#define RETURN(rc) \
|
|
do { \
|
|
typeof(rc) RETURN__ret = (rc); \
|
|
CDEBUG(D_TRACE, "Process leaving (rc=%lu : %ld : %lx)\n", \
|
|
(long)RETURN__ret, (long)RETURN__ret, (long)RETURN__ret);\
|
|
return RETURN__ret; \
|
|
} while (0)
|
|
|
|
#define __ENTRY(subsys) \
|
|
do { \
|
|
__CDEBUG(NULL, subsys, D_TRACE, "Process entered\n"); \
|
|
} while (0)
|
|
|
|
#define __EXIT(subsys) \
|
|
do { \
|
|
__CDEBUG(NULL, subsys, D_TRACE, "Process leaving\n"); \
|
|
} while(0)
|
|
|
|
#define ENTRY __ENTRY(DEBUG_SUBSYSTEM)
|
|
#define EXIT __EXIT(DEBUG_SUBSYSTEM)
|
|
#endif /* NDEBUG */
|
|
|
|
#define spl_debug_msg(cdls, subsys, mask, file, fn, line, format, a...) \
|
|
spl_debug_vmsg(cdls, subsys, mask, file, fn, \
|
|
line, NULL, NULL, format, ##a)
|
|
|
|
extern int spl_debug_vmsg(spl_debug_limit_state_t *cdls, int subsys, int mask,
|
|
const char *file, const char *fn, const int line,
|
|
const char *format1, va_list args, const char *format2, ...);
|
|
|
|
extern unsigned long spl_debug_set_mask(unsigned long mask);
|
|
extern unsigned long spl_debug_get_mask(void);
|
|
extern unsigned long spl_debug_set_subsys(unsigned long mask);
|
|
extern unsigned long spl_debug_get_subsys(void);
|
|
extern int spl_debug_set_mb(int mb);
|
|
extern int spl_debug_get_mb(void);
|
|
|
|
extern int spl_debug_dumplog(int flags);
|
|
extern void spl_debug_dumpstack(struct task_struct *tsk);
|
|
extern void spl_debug_bug(char *file, const char *func, const int line, int flags);
|
|
|
|
extern int spl_debug_clear_buffer(void);
|
|
extern int spl_debug_mark_buffer(char *text);
|
|
|
|
#ifdef __cplusplus
|
|
}
|
|
#endif
|
|
|
|
#endif /* SPL_DEBUG_H */
|