Skip to contentSkip to conversation

lib/bug: use stackdepot for WARN*ONCE to make it more useful

RFC PATCH In review Michal Hocko 1 participant 2 messages Jan 3, 2017 – Jan 11, 2017

Subsystems (2)
lore
Share & tools
lore
#0 lore raw

RFC PATCH 1/1 lib/bug: use stackdepot for WARN*ONCE to make it more useful

From: Michal Hocko <[email protected]>

One of the main problem of WARN_*ONCE style of warnings is that they might easily hide different callpaths which lead to the warning. This is especially true for library functions. Rebooting after each offender hitting the particular WARN_*ONCE is just not feasible.

This patch (ab)uses stackdepot to make WARN_*ONCE more usable. We can store whether a certain callpath has been seen in the stackdepot's stack_record. This way only uniq stack traces will be reported.

On the other hand this is not for free. WARN_*ONCE will consume more stack (at least WARN_STACK_DEPTH*sizeof(unsigned long) + sizeof(struct stack_trace)) and also requires to allocate a memory sometimes which might fail. In those cases the warninig will be issued multiple times potentially.

---
Hi,
this is an (untested) RFC to show the idea. It has some known problems:
- depot_test_set_reported_stack is GPL symbol which is not suitable for generic thing as WARN_*ONCE but the rest of the stackdepot is exported as GPL only and I haven't explored why.
- there needs to be a recursion detection because nothing really prevents depot_test_set_reported_stack to call into WARN_*ONCE inderectly (e.g. through memory allocation)
- WARN_STACK_DEPTH doesn't really cooperate with other users of the stack depot well (namely KASAN_STACK_DEPTH)
- configuration space would need to be considered because the STACKDEPOT cannot be selected manually currently

and maybe others. But I think the idea should be at least considered. So here it goes.

Are there any thoughts, comments?

 include/asm-generic/bug.h  | 22 +++++++++++++
 include/linux/stackdepot.h |  2 ++
 lib/stackdepot.c           | 79 ++++++++++++++++++++++++++++++++++++++--------
 3 files changed, 90 insertions(+), 13 deletions(-)
include/asm-generic/bug.h +22 -0
index 6f96247226a4..0b19fddea7ff 100644
@@ -11,6 +11,7 @@
 
 #ifndef __ASSEMBLY__
 #include <linux/kernel.h>
+#include <linux/stackdepot.h>
 
 #ifdef CONFIG_BUG
 
@@ -112,6 +113,7 @@ void __warn(const char *file, int line, void *caller, unsigned taint,
 	unlikely(__ret_warn_on);					\
 })
 
+#ifndef CONFIG_STACKDEPOT
 #define WARN_ON_ONCE(condition)	({				\
 	static bool __section(.data.unlikely) __warned;		\
 	int __ret_warn_once = !!(condition);			\
@@ -133,6 +135,26 @@ void __warn(const char *file, int line, void *caller, unsigned taint,
 	}							\
 	unlikely(__ret_warn_once);				\
 })
+#else
+#define WARN_STACK_DEPTH 16
+#define WARN_ON_ONCE(condition)	({				\
+	int __ret_warn_once = !!(condition);			\
+								\
+	if (unlikely(__ret_warn_once && !depot_test_set_reported_stack(WARN_STACK_DEPTH))) {		\
+		WARN_ON(1);					\
+	}							\
+	unlikely(__ret_warn_once);				\
+})
+
+#define WARN_ONCE(condition, format...)	({			\
+	int __ret_warn_once = !!(condition);			\
+								\
+	if (unlikely(__ret_warn_once && !depot_test_set_reported_stack(WARN_STACK_DEPTH))) {		\
+		WARN(1, format);				\
+	}							\
+	unlikely(__ret_warn_once);				\
+})
+#endif
 
 #define WARN_TAINT_ONCE(condition, taint, format...)	({	\
 	static bool __section(.data.unlikely) __warned;		\
include/linux/stackdepot.h +2 -0
index 7978b3e2c1e1..0e6e047c5e02 100644
@@ -29,4 +29,6 @@ depot_stack_handle_t depot_save_stack(struct stack_trace *trace, gfp_t flags);
 
 void depot_fetch_stack(depot_stack_handle_t handle, struct stack_trace *trace);
 
+bool depot_test_set_reported_stack(int stack_depth);
+
 #endif
lib/stackdepot.c +66 -13

119 lines not on this page · raw message

Signature · 1 line
2.11.0
#1 lore raw

Has this just fallen through cracks or the silence means that people do not really care about the current WARN_*ONCE behavior?

Quoted 231 lines · Michal Hocko

From: Michal Hocko <[email protected]>

One of the main problem of WARN_*ONCE style of warnings is that they might easily hide different callpaths which lead to the warning. This is especially true for library functions. Rebooting after each offender hitting the particular WARN_*ONCE is just not feasible.

This patch (ab)uses stackdepot to make WARN_*ONCE more usable. We can store whether a certain callpath has been seen in the stackdepot's stack_record. This way only uniq stack traces will be reported.

On the other hand this is not for free. WARN_*ONCE will consume more stack (at least WARN_STACK_DEPTH*sizeof(unsigned long) + sizeof(struct stack_trace)) and also requires to allocate a memory sometimes which might fail. In those cases the warninig will be issued multiple times potentially.

---
Hi,
this is an (untested) RFC to show the idea. It has some known problems:
- depot_test_set_reported_stack is GPL symbol which is not suitable for generic thing as WARN_*ONCE but the rest of the stackdepot is exported as GPL only and I haven't explored why.
- there needs to be a recursion detection because nothing really prevents depot_test_set_reported_stack to call into WARN_*ONCE inderectly (e.g. through memory allocation)
- WARN_STACK_DEPTH doesn't really cooperate with other users of the stack depot well (namely KASAN_STACK_DEPTH)
- configuration space would need to be considered because the STACKDEPOT cannot be selected manually currently

and maybe others. But I think the idea should be at least considered. So here it goes.

Are there any thoughts, comments?

 include/asm-generic/bug.h  | 22 +++++++++++++
 include/linux/stackdepot.h |  2 ++
 lib/stackdepot.c           | 79 ++++++++++++++++++++++++++++++++++++++--------
 3 files changed, 90 insertions(+), 13 deletions(-)
include/asm-generic/bug.h +22 -0
index 6f96247226a4..0b19fddea7ff 100644
@@ -11,6 +11,7 @@
 
 #ifndef __ASSEMBLY__
 #include <linux/kernel.h>
+#include <linux/stackdepot.h>
 
 #ifdef CONFIG_BUG
 
@@ -112,6 +113,7 @@ void __warn(const char *file, int line, void *caller, unsigned taint,
 	unlikely(__ret_warn_on);					\
 })
 
+#ifndef CONFIG_STACKDEPOT
 #define WARN_ON_ONCE(condition)	({				\
 	static bool __section(.data.unlikely) __warned;		\
 	int __ret_warn_once = !!(condition);			\
@@ -133,6 +135,26 @@ void __warn(const char *file, int line, void *caller, unsigned taint,
 	}							\
 	unlikely(__ret_warn_once);				\
 })
+#else
+#define WARN_STACK_DEPTH 16
+#define WARN_ON_ONCE(condition)	({				\
+	int __ret_warn_once = !!(condition);			\
+								\
+	if (unlikely(__ret_warn_once && !depot_test_set_reported_stack(WARN_STACK_DEPTH))) {		\
+		WARN_ON(1);					\
+	}							\
+	unlikely(__ret_warn_once);				\
+})
+
+#define WARN_ONCE(condition, format...)	({			\
+	int __ret_warn_once = !!(condition);			\
+								\
+	if (unlikely(__ret_warn_once && !depot_test_set_reported_stack(WARN_STACK_DEPTH))) {		\
+		WARN(1, format);				\
+	}							\
+	unlikely(__ret_warn_once);				\
+})
+#endif
 
 #define WARN_TAINT_ONCE(condition, taint, format...)	({	\
 	static bool __section(.data.unlikely) __warned;		\
include/linux/stackdepot.h +2 -0
index 7978b3e2c1e1..0e6e047c5e02 100644
@@ -29,4 +29,6 @@ depot_stack_handle_t depot_save_stack(struct stack_trace *trace, gfp_t flags);
 
 void depot_fetch_stack(depot_stack_handle_t handle, struct stack_trace *trace);
 
+bool depot_test_set_reported_stack(int stack_depth);
+
 #endif
lib/stackdepot.c +66 -13
index f87d138e9672..39a471912aa1 100644
@@ -68,7 +68,10 @@ union handle_parts {
 struct stack_record {
 	struct stack_record *next;	/* Link in the hashtable */
 	u32 hash;			/* Hash in the hastable */
-	u32 size;			/* Number of frames in the stack */
+	u32 size;			/* Number of frames in the stack - the top bit
+					   encodes whether the stack has been seen already
+					   by depot_test_set_reported_stack. Use entries_count
+					   to get the value */
 	union handle_parts handle;
 	unsigned long entries[1];	/* Variable-sized array of entries. */
 };
@@ -80,6 +83,15 @@ static int next_slab_inited;
 static size_t depot_offset;
 static DEFINE_SPINLOCK(depot_lock);
 
+#define STACK_SEEN_BIT	31
+#define STACK_SEEN_MASK (1u<<STACK_SEEN_BIT)
+#define STACK_COUNT_MASK ~STACK_SEEN_MASK
+
+static inline u32 entries_count(struct stack_record *record)
+{
+	return record->size & STACK_COUNT_MASK;
+}
+
 static bool init_stack_slab(void **prealloc)
 {
 	if (!*prealloc)
@@ -172,7 +184,7 @@ static inline struct stack_record *find_stack(struct stack_record *bucket,
 
 	for (found = bucket; found; found = found->next) {
 		if (found->hash == hash &&
-		    found->size == size &&
+		    entries_count(found) == size &&
 		    !memcmp(entries, found->entries,
 			    size * sizeof(unsigned long))) {
 			return found;
@@ -188,24 +200,16 @@ void depot_fetch_stack(depot_stack_handle_t handle, struct stack_trace *trace)
 	size_t offset = parts.offset << STACK_ALLOC_ALIGN;
 	struct stack_record *stack = slab + offset;
 
-	trace->nr_entries = trace->max_entries = stack->size;
+	trace->nr_entries = trace->max_entries = entries_count(stack);
 	trace->entries = stack->entries;
 	trace->skip = 0;
 }
 EXPORT_SYMBOL_GPL(depot_fetch_stack);
 
-/**
- * depot_save_stack - save stack in a stack depot.
- * @trace - the stacktrace to save.
- * @alloc_flags - flags for allocating additional memory if required.
- *
- * Returns the handle of the stack struct stored in depot.
- */
-depot_stack_handle_t depot_save_stack(struct stack_trace *trace,
+struct stack_record *__depot_save_stack(struct stack_trace *trace,
 				    gfp_t alloc_flags)
 {
 	u32 hash;
-	depot_stack_handle_t retval = 0;
 	struct stack_record *found = NULL, **bucket;
 	unsigned long flags;
 	struct page *page = NULL;
@@ -279,9 +283,58 @@ depot_stack_handle_t depot_save_stack(struct stack_trace *trace,
 		/* Nobody used this memory, ok to free it. */
 		free_pages((unsigned long)prealloc, STACK_ALLOC_ORDER);
 	}
+fast_exit:
+	return found;
+}
+
+/**
+ * depot_save_stack - save stack in a stack depot.
+ * @trace - the stacktrace to save.
+ * @alloc_flags - flags for allocating additional memory if required.
+ *
+ * Returns the handle of the stack struct stored in depot.
+ */
+depot_stack_handle_t depot_save_stack(struct stack_trace *trace,
+				    gfp_t alloc_flags)
+{
+	depot_stack_handle_t retval = 0;
+	struct stack_record *found = NULL;
+
+	found = __depot_save_stack(trace, alloc_flags);
 	if (found)
 		retval = found->handle.handle;
-fast_exit:
+
 	return retval;
 }
 EXPORT_SYMBOL_GPL(depot_save_stack);
+
+/* FIXME be careful about recursion */
+bool depot_test_set_reported_stack(int stack_depth)
+{
+	unsigned long entries[stack_depth];
+	struct stack_trace trace = {
+		.nr_entries = 0,
+		.entries = entries,
+		.max_entries = stack_depth,
+		.skip = 0
+	};
+	struct stack_record *record;
+
+	/* FIXME deduplicate save_stack from kasan.c */
+	save_stack_trace(&trace);
+	if (trace.nr_entries != 0 &&
+	    trace.entries[trace.nr_entries-1] == ULONG_MAX)
+		trace.nr_entries--;
+
+	record = __depot_save_stack(&trace, GFP_ATOMIC);
+	if (!record)
+		return false;
+
+	/* This is racy but races should be too rare to matter */
+	if (record->size & STACK_SEEN_MASK)
+		return true;
+
+	record->size = STACK_SEEN_MASK | entries_count(record);
+	return false;
+}
+EXPORT_SYMBOL_GPL(depot_test_set_reported_stack);
Signature · 1 line
2.11.0
Signature · 2 lines
Michal Hocko
SUSE Labs