Add calls to the generic object debugging infrastructure and provide a fixup function which allows to keep the system alive when recoverable problems have been detected by the object debugging core code. Add a selftest, which covers the two possible mistakes: free/init of an active timer. Signed-off-by: Thomas Gleixner --- include/linux/debugobjects.h | 1 include/linux/timer.h | 22 +++++++++++ kernel/timer.c | 85 +++++++++++++++++++++++++++++++++++++++++++ lib/Kconfig.debug | 7 +++ lib/debugobjects.c | 18 ++++++++- 5 files changed, 131 insertions(+), 2 deletions(-) Index: linux-2.6/include/linux/debugobjects.h =================================================================== --- linux-2.6.orig/include/linux/debugobjects.h +++ linux-2.6/include/linux/debugobjects.h @@ -14,6 +14,7 @@ enum debug_obj_op { enum { ODEBUG_TYPE_UNKNOWN, + ODEBUG_TYPE_TIMER, ODEBUG_MAX_TYPES }; Index: linux-2.6/include/linux/timer.h =================================================================== --- linux-2.6.orig/include/linux/timer.h +++ linux-2.6/include/linux/timer.h @@ -4,6 +4,7 @@ #include #include #include +#include struct tvec_base; @@ -20,6 +21,9 @@ struct timer_list { char start_comm[16]; int start_pid; #endif +#ifdef CONFIG_DEBUG_OBJECT_TIMERS + struct debug_obj dobj; +#endif }; extern struct tvec_base boot_tvec_bases; @@ -164,5 +168,23 @@ unsigned long __round_jiffies_relative(u unsigned long round_jiffies(unsigned long j); unsigned long round_jiffies_relative(unsigned long j); +#ifdef CONFIG_DEBUG_OBJECT_TIMERS +static inline void +debug_timer_object_op(struct timer_list *timer, enum debug_obj_op mode) +{ + timer->dobj.type = ODEBUG_TYPE_TIMER; + debug_object_op(&timer->dobj, mode); +} +extern int timer_fixup_object(struct debug_obj *obj, enum debug_obj_op mode); +extern int timer_debug_object_selftest(void); +#else +static inline void +debug_timer_object_op(struct timer_list *timer, enum debug_obj_op mode) +{ +} +# define timer_fixup_object NULL +static inline int timer_debug_object_selftest(void) { return 0; } +#endif + #endif Index: linux-2.6/kernel/timer.c =================================================================== --- linux-2.6.orig/kernel/timer.c +++ linux-2.6/kernel/timer.c @@ -320,6 +320,84 @@ static void timer_stats_account_timer(st static void timer_stats_account_timer(struct timer_list *timer) {} #endif +#ifdef CONFIG_DEBUG_OBJECT_TIMERS + +static int timer_fixup_done __read_mostly; + +int timer_fixup_object(struct debug_obj *obj, enum debug_obj_op op) +{ + struct timer_list *timer = container_of(obj, struct timer_list, dobj); + + switch (op) { + case ODEBUG_INIT: + case ODEBUG_FREE: + del_timer_sync(timer); + timer_fixup_done++; + return 0; + /* + * These are fatal timer.c internal errors. No real way to + * survive: + */ + case ODEBUG_ADD: + case ODEBUG_DEL: + return -1; + default: + return -1; + } +} + +/* + * Test the two popular bugs: + * + * - reinit a timer which is enqueued + * - free a datastructure which contains an enqueued timer + */ + +static void __init timer_debug_selftest_fn(unsigned long arg) +{ +} + +int __init timer_debug_object_selftest(void) +{ + struct timer_list *timer; + int fixup_cnt = timer_fixup_done; + + timer = kmalloc(sizeof(struct timer_list), GFP_KERNEL); + if (!timer) + return -ENOMEM; + + setup_timer(timer, timer_debug_selftest_fn, 0); + + timer->expires = jiffies + HZ; + add_timer(timer); + + setup_timer(timer, timer_debug_selftest_fn, 0); + if (fixup_cnt == timer_fixup_done) + goto err; + +#ifdef CONFIG_DEBUG_OBJECTS_FREE + timer->expires = jiffies + HZ; + add_timer(timer); + + fixup_cnt = timer_fixup_done; + kfree(timer); + timer = NULL; + + if (fixup_cnt == timer_fixup_done) + goto err; +#else + kfree(timer); +#endif + return 0; + +err: + printk(KERN_ERR "TIMER: ODEBUG selftest failed\n"); + kfree(timer); + return -1; +} + +#endif + /** * init_timer - initialize a timer. * @timer: the timer to be initialized @@ -329,6 +407,8 @@ static void timer_stats_account_timer(st */ void init_timer(struct timer_list *timer) { + debug_timer_object_op(timer, ODEBUG_INIT); + timer->entry.next = NULL; timer->base = __raw_get_cpu_var(tvec_bases); #ifdef CONFIG_TIMER_STATS @@ -351,6 +431,8 @@ static inline void detach_timer(struct t { struct list_head *entry = &timer->entry; + debug_timer_object_op(timer, ODEBUG_DEL); + __list_del(entry->prev, entry->next); if (clear_pending) entry->next = NULL; @@ -405,6 +487,8 @@ int __mod_timer(struct timer_list *timer ret = 1; } + debug_timer_object_op(timer, ODEBUG_ADD); + new_base = __get_cpu_var(tvec_bases); if (base != new_base) { @@ -450,6 +534,7 @@ void add_timer_on(struct timer_list *tim BUG_ON(timer_pending(timer) || !timer->function); spin_lock_irqsave(&base->lock, flags); timer_set_base(timer, base); + debug_timer_object_op(timer, ODEBUG_ADD); internal_add_timer(base, timer); spin_unlock_irqrestore(&base->lock, flags); } Index: linux-2.6/lib/Kconfig.debug =================================================================== --- linux-2.6.orig/lib/Kconfig.debug +++ linux-2.6/lib/Kconfig.debug @@ -199,6 +199,13 @@ config DEBUG_OBJECT_FREE properly. This can make kmalloc/kfree-intensive workloads much slower. +config DEBUG_OBJECT_TIMERS + bool "Debug timer objects" + depends on DEBUG_OBJECT_OPS + help + If you say Y here, additional code will be inserted into the + timer routines to validate the timer operations. + config DEBUG_SLAB bool "Debug slab memory allocations" depends on DEBUG_KERNEL && SLAB Index: linux-2.6/lib/debugobjects.c =================================================================== --- linux-2.6.orig/lib/debugobjects.c +++ linux-2.6/lib/debugobjects.c @@ -12,6 +12,7 @@ #include #include #include +#include #define ODEBUG_HASH_SIZE 4096 #define ODEBUG_HASH_MASK (ODEBUG_HASH_SIZE - 1) @@ -48,11 +49,13 @@ static struct odebug *object_get_hash(un } static const void * const debug_fixup[ODEBUG_MAX_TYPES] = { + [ODEBUG_TYPE_TIMER] = timer_fixup_object, }; static const char * const obj_types[ODEBUG_MAX_TYPES] = { [ODEBUG_TYPE_UNKNOWN] = "unknown type", + [ODEBUG_TYPE_TIMER] = "timer_list", }; static void debug_print_object(struct debug_obj *obj, char *msg) @@ -246,11 +249,22 @@ void __init debug_objects_init(void) int __init debug_objects_do_selftest(void) { + int res; + if (!debug_objects_enabled) return 0; debug_objects_init_debugfs(); - printk(KERN_INFO "ODEBUG: Selftest pass\n"); - return 0; + + debug_objects_selftest = 1; + + res = timer_debug_object_selftest(); + + debug_objects_selftest = 0; + + if (!res) + printk(KERN_INFO "ODEBUG: Selftest pass\n"); + + return res; } __initcall(debug_objects_do_selftest); -- -- To unsubscribe from this list: send the line "unsubscribe linux-kernel" in the body of a message to majordomo@vger.kernel.org More majordomo info at http://vger.kernel.org/majordomo-info.html Please read the FAQ at http://www.tux.org/lkml/