kmemleak: add module param to print warnings to dmesg

Currently, kmemleak only prints the number of suspected leaks to dmesg but
requires the user to read a debugfs file to get the actual stack traces of
the objects' allocation points.  Add a module option to print the full
object information to dmesg too.  It can be enabled with
kmemleak.verbose=1 on the kernel command line, or "echo 1 >
/sys/module/kmemleak/parameters/verbose":

This allows easier integration of kmemleak into test systems: We have
automated test infrastructure to test our Linux systems.  With this
option, running our tests with kmemleak is as simple as enabling kmemleak
and passing this command line option; the test infrastructure knows how to
save kernel logs, which will now include kmemleak reports.  Without this
option, the test infrastructure needs to be specifically taught to read
out the kmemleak debugfs file.  Removing this need for special handling
makes kmemleak more similar to other kernel debug options (slab debugging,
debug objects, etc).

Link: http://lkml.kernel.org/r/20180903144046.21023-1-vincent.whitchurch@axis.com
Signed-off-by: Vincent Whitchurch <vincent.whitchurch@axis.com>
Acked-by: Catalin Marinas <catalin.marinas@arm.com>
Signed-off-by: Andrew Morton <akpm@linux-foundation.org>
Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
This commit is contained in:
Vincent Whitchurch 2018-10-26 15:03:42 -07:00 committed by Linus Torvalds
parent 4e15a073a1
commit 154221c3e5

View File

@ -86,6 +86,7 @@
#include <linux/seq_file.h> #include <linux/seq_file.h>
#include <linux/cpumask.h> #include <linux/cpumask.h>
#include <linux/spinlock.h> #include <linux/spinlock.h>
#include <linux/module.h>
#include <linux/mutex.h> #include <linux/mutex.h>
#include <linux/rcupdate.h> #include <linux/rcupdate.h>
#include <linux/stacktrace.h> #include <linux/stacktrace.h>
@ -181,6 +182,7 @@ struct kmemleak_object {
/* flag set to not scan the object */ /* flag set to not scan the object */
#define OBJECT_NO_SCAN (1 << 2) #define OBJECT_NO_SCAN (1 << 2)
#define HEX_PREFIX " "
/* number of bytes to print per line; must be 16 or 32 */ /* number of bytes to print per line; must be 16 or 32 */
#define HEX_ROW_SIZE 16 #define HEX_ROW_SIZE 16
/* number of bytes to print at a time (1, 2, 4, 8) */ /* number of bytes to print at a time (1, 2, 4, 8) */
@ -235,6 +237,9 @@ static int kmemleak_skip_disable;
/* If there are leaks that can be reported */ /* If there are leaks that can be reported */
static bool kmemleak_found_leaks; static bool kmemleak_found_leaks;
static bool kmemleak_verbose;
module_param_named(verbose, kmemleak_verbose, bool, 0600);
/* /*
* Early object allocation/freeing logging. Kmemleak is initialized after the * Early object allocation/freeing logging. Kmemleak is initialized after the
* kernel allocator. However, both the kernel allocator and kmemleak may * kernel allocator. However, both the kernel allocator and kmemleak may
@ -299,6 +304,25 @@ static void kmemleak_disable(void);
kmemleak_disable(); \ kmemleak_disable(); \
} while (0) } while (0)
#define warn_or_seq_printf(seq, fmt, ...) do { \
if (seq) \
seq_printf(seq, fmt, ##__VA_ARGS__); \
else \
pr_warn(fmt, ##__VA_ARGS__); \
} while (0)
static void warn_or_seq_hex_dump(struct seq_file *seq, int prefix_type,
int rowsize, int groupsize, const void *buf,
size_t len, bool ascii)
{
if (seq)
seq_hex_dump(seq, HEX_PREFIX, prefix_type, rowsize, groupsize,
buf, len, ascii);
else
print_hex_dump(KERN_WARNING, pr_fmt(HEX_PREFIX), prefix_type,
rowsize, groupsize, buf, len, ascii);
}
/* /*
* Printing of the objects hex dump to the seq file. The number of lines to be * Printing of the objects hex dump to the seq file. The number of lines to be
* printed is limited to HEX_MAX_LINES to prevent seq file spamming. The * printed is limited to HEX_MAX_LINES to prevent seq file spamming. The
@ -314,10 +338,10 @@ static void hex_dump_object(struct seq_file *seq,
/* limit the number of lines to HEX_MAX_LINES */ /* limit the number of lines to HEX_MAX_LINES */
len = min_t(size_t, object->size, HEX_MAX_LINES * HEX_ROW_SIZE); len = min_t(size_t, object->size, HEX_MAX_LINES * HEX_ROW_SIZE);
seq_printf(seq, " hex dump (first %zu bytes):\n", len); warn_or_seq_printf(seq, " hex dump (first %zu bytes):\n", len);
kasan_disable_current(); kasan_disable_current();
seq_hex_dump(seq, " ", DUMP_PREFIX_NONE, HEX_ROW_SIZE, warn_or_seq_hex_dump(seq, DUMP_PREFIX_NONE, HEX_ROW_SIZE,
HEX_GROUP_SIZE, ptr, len, HEX_ASCII); HEX_GROUP_SIZE, ptr, len, HEX_ASCII);
kasan_enable_current(); kasan_enable_current();
} }
@ -365,17 +389,17 @@ static void print_unreferenced(struct seq_file *seq,
int i; int i;
unsigned int msecs_age = jiffies_to_msecs(jiffies - object->jiffies); unsigned int msecs_age = jiffies_to_msecs(jiffies - object->jiffies);
seq_printf(seq, "unreferenced object 0x%08lx (size %zu):\n", warn_or_seq_printf(seq, "unreferenced object 0x%08lx (size %zu):\n",
object->pointer, object->size); object->pointer, object->size);
seq_printf(seq, " comm \"%s\", pid %d, jiffies %lu (age %d.%03ds)\n", warn_or_seq_printf(seq, " comm \"%s\", pid %d, jiffies %lu (age %d.%03ds)\n",
object->comm, object->pid, object->jiffies, object->comm, object->pid, object->jiffies,
msecs_age / 1000, msecs_age % 1000); msecs_age / 1000, msecs_age % 1000);
hex_dump_object(seq, object); hex_dump_object(seq, object);
seq_printf(seq, " backtrace:\n"); warn_or_seq_printf(seq, " backtrace:\n");
for (i = 0; i < object->trace_len; i++) { for (i = 0; i < object->trace_len; i++) {
void *ptr = (void *)object->trace[i]; void *ptr = (void *)object->trace[i];
seq_printf(seq, " [<%p>] %pS\n", ptr, ptr); warn_or_seq_printf(seq, " [<%p>] %pS\n", ptr, ptr);
} }
} }
@ -1598,6 +1622,10 @@ static void kmemleak_scan(void)
if (unreferenced_object(object) && if (unreferenced_object(object) &&
!(object->flags & OBJECT_REPORTED)) { !(object->flags & OBJECT_REPORTED)) {
object->flags |= OBJECT_REPORTED; object->flags |= OBJECT_REPORTED;
if (kmemleak_verbose)
print_unreferenced(NULL, object);
new_leaks++; new_leaks++;
} }
spin_unlock_irqrestore(&object->lock, flags); spin_unlock_irqrestore(&object->lock, flags);