summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorSteven Rostedt <srostedt@redhat.com>2008-05-12 19:20:48 (GMT)
committerThomas Gleixner <tglx@linutronix.de>2008-05-23 18:54:04 (GMT)
commit37ad508419f0fdfda7b378756eb1f35cfd26d96d (patch)
tree53db6e201e605c1926adc74fa965b1a615066da8
parent088b1e427dbba2af93cb6a7d39258c10ff58dd27 (diff)
downloadlinux-fsl-qoriq-37ad508419f0fdfda7b378756eb1f35cfd26d96d.tar.xz
ftrace - fix dynamic ftrace memory leak
The ftrace dynamic function update allocates a record to store the instruction pointers that are being modified. If the modified instruction pointer fails to update, then the record is marked as failed and nothing more is done. Worse, if the modification fails, but the record ip function is still called, it will allocate a new record and try again. In just a matter of time, will this cause a serious memory leak and crash the system. This patch plugs this memory leak. When a record fails, it is included back into the pool of records to be used. Now a record may fail over and over again, but the number of allocated records will not increase. Signed-off-by: Steven Rostedt <srostedt@redhat.com> Signed-off-by: Ingo Molnar <mingo@elte.hu> Signed-off-by: Thomas Gleixner <tglx@linutronix.de>
-rw-r--r--include/linux/ftrace.h7
-rw-r--r--kernel/trace/ftrace.c45
2 files changed, 46 insertions, 6 deletions
diff --git a/include/linux/ftrace.h b/include/linux/ftrace.h
index a842d96..61e757b 100644
--- a/include/linux/ftrace.h
+++ b/include/linux/ftrace.h
@@ -43,9 +43,10 @@ extern void mcount(void);
# define FTRACE_HASHSIZE (1<<FTRACE_HASHBITS)
enum {
- FTRACE_FL_FAILED = (1 << 0),
- FTRACE_FL_FILTER = (1 << 1),
- FTRACE_FL_ENABLED = (1 << 2),
+ FTRACE_FL_FREE = (1 << 0),
+ FTRACE_FL_FAILED = (1 << 1),
+ FTRACE_FL_FILTER = (1 << 2),
+ FTRACE_FL_ENABLED = (1 << 3),
};
struct dyn_ftrace {
diff --git a/kernel/trace/ftrace.c b/kernel/trace/ftrace.c
index a15e068..8e02aa6 100644
--- a/kernel/trace/ftrace.c
+++ b/kernel/trace/ftrace.c
@@ -188,6 +188,8 @@ static int ftraced_suspend;
static int ftrace_record_suspend;
+static struct dyn_ftrace *ftrace_free_records;
+
static inline int
notrace ftrace_ip_in_hash(unsigned long ip, unsigned long key)
{
@@ -211,8 +213,35 @@ ftrace_add_hash(struct dyn_ftrace *node, unsigned long key)
hlist_add_head(&node->node, &ftrace_hash[key]);
}
+static notrace void ftrace_free_rec(struct dyn_ftrace *rec)
+{
+ /* no locking, only called from kstop_machine */
+
+ rec->ip = (unsigned long)ftrace_free_records;
+ ftrace_free_records = rec;
+ rec->flags |= FTRACE_FL_FREE;
+}
+
static notrace struct dyn_ftrace *ftrace_alloc_dyn_node(unsigned long ip)
{
+ struct dyn_ftrace *rec;
+
+ /* First check for freed records */
+ if (ftrace_free_records) {
+ rec = ftrace_free_records;
+
+ /* todo, disable tracing altogether on this warning */
+ if (unlikely(!(rec->flags & FTRACE_FL_FREE))) {
+ WARN_ON_ONCE(1);
+ ftrace_free_records = NULL;
+ return NULL;
+ }
+
+ ftrace_free_records = (void *)rec->ip;
+ memset(rec, 0, sizeof(*rec));
+ return rec;
+ }
+
if (ftrace_pages->index == ENTRIES_PER_PAGE) {
if (!ftrace_pages->next)
return NULL;
@@ -356,8 +385,16 @@ __ftrace_replace_code(struct dyn_ftrace *rec,
}
failed = ftrace_modify_code(ip, old, new);
- if (failed)
- rec->flags |= FTRACE_FL_FAILED;
+ if (failed) {
+ unsigned long key;
+ /* It is possible that the function hasn't been converted yet */
+ key = hash_long(ip, FTRACE_HASHBITS);
+ if (!ftrace_ip_in_hash(ip, key)) {
+ rec->flags |= FTRACE_FL_FAILED;
+ ftrace_free_rec(rec);
+ }
+
+ }
}
static void notrace ftrace_replace_code(int enable)
@@ -407,8 +444,10 @@ ftrace_code_disable(struct dyn_ftrace *rec)
call = ftrace_call_replace(ip, MCOUNT_ADDR);
failed = ftrace_modify_code(ip, call, nop);
- if (failed)
+ if (failed) {
rec->flags |= FTRACE_FL_FAILED;
+ ftrace_free_rec(rec);
+ }
}
static int notrace __ftrace_modify_code(void *data)