Commit 261842b7c9099f56de2eb969c8ad65402d68e00e

Authored by Steven Rostedt
Committed by Steven Rostedt
1 parent 12acd473d4

tracing: add same level recursion detection

The tracing infrastructure allows for recursion. That is, an interrupt
may interrupt the act of tracing an event, and that interrupt may very well
perform its own trace. This is a recursive trace, and is fine to do.

The problem arises when there is a bug, and the utility doing the trace
calls something that recurses back into the tracer. This recursion is not
caused by an external event like an interrupt, but by code that is not
expected to recurse. The result could be a lockup.

This patch adds a bitmask to the task structure that keeps track
of the trace recursion. To find the interrupt depth, the following
algorithm is used:

  level = hardirq_count() + softirq_count() + in_nmi;

Here, level will be the depth of interrutps and softirqs, and even handles
the nmi. Then the corresponding bit is set in the recursion bitmask.
If the bit was already set, we know we had a recursion at the same level
and we warn about it and fail the writing to the buffer.

After the data has been committed to the buffer, we clear the bit.
No atomics are needed. The only races are with interrupts and they reset
the bitmask before returning anywy.

[ Impact: detect same irq level trace recursion ]

Signed-off-by: Steven Rostedt <rostedt@goodmis.org>

Showing 4 changed files with 53 additions and 1 deletions Side-by-side Diff

include/linux/ftrace.h
... ... @@ -488,8 +488,15 @@
488 488  
489 489 extern int ftrace_dump_on_oops;
490 490  
  491 +#ifdef CONFIG_PREEMPT
  492 +#define INIT_TRACE_RECURSION .trace_recursion = 0,
  493 +#endif
  494 +
491 495 #endif /* CONFIG_TRACING */
492 496  
  497 +#ifndef INIT_TRACE_RECURSION
  498 +#define INIT_TRACE_RECURSION
  499 +#endif
493 500  
494 501 #ifdef CONFIG_HW_BRANCH_TRACER
495 502  
include/linux/init_task.h
... ... @@ -187,6 +187,7 @@
187 187 INIT_TRACE_IRQFLAGS \
188 188 INIT_LOCKDEP \
189 189 INIT_FTRACE_GRAPH \
  190 + INIT_TRACE_RECURSION \
190 191 }
191 192  
192 193  
include/linux/sched.h
... ... @@ -1428,7 +1428,9 @@
1428 1428 #ifdef CONFIG_TRACING
1429 1429 /* state flags for use by tracers */
1430 1430 unsigned long trace;
1431   -#endif
  1431 + /* bitmask of trace recursion */
  1432 + unsigned long trace_recursion;
  1433 +#endif /* CONFIG_TRACING */
1432 1434 };
1433 1435  
1434 1436 /* Future-safe accessor for struct task_struct's cpus_allowed. */
kernel/trace/ring_buffer.c
... ... @@ -1481,6 +1481,40 @@
1481 1481 return event;
1482 1482 }
1483 1483  
  1484 +static int trace_irq_level(void)
  1485 +{
  1486 + return hardirq_count() + softirq_count() + in_nmi();
  1487 +}
  1488 +
  1489 +static int trace_recursive_lock(void)
  1490 +{
  1491 + int level;
  1492 +
  1493 + level = trace_irq_level();
  1494 +
  1495 + if (unlikely(current->trace_recursion & (1 << level))) {
  1496 + /* Disable all tracing before we do anything else */
  1497 + tracing_off_permanent();
  1498 + WARN_ON_ONCE(1);
  1499 + return -1;
  1500 + }
  1501 +
  1502 + current->trace_recursion |= 1 << level;
  1503 +
  1504 + return 0;
  1505 +}
  1506 +
  1507 +static void trace_recursive_unlock(void)
  1508 +{
  1509 + int level;
  1510 +
  1511 + level = trace_irq_level();
  1512 +
  1513 + WARN_ON_ONCE(!current->trace_recursion & (1 << level));
  1514 +
  1515 + current->trace_recursion &= ~(1 << level);
  1516 +}
  1517 +
1484 1518 static DEFINE_PER_CPU(int, rb_need_resched);
1485 1519  
1486 1520 /**
... ... @@ -1514,6 +1548,9 @@
1514 1548 /* If we are tracing schedule, we don't want to recurse */
1515 1549 resched = ftrace_preempt_disable();
1516 1550  
  1551 + if (trace_recursive_lock())
  1552 + goto out_nocheck;
  1553 +
1517 1554 cpu = raw_smp_processor_id();
1518 1555  
1519 1556 if (!cpumask_test_cpu(cpu, buffer->cpumask))
... ... @@ -1543,6 +1580,9 @@
1543 1580 return event;
1544 1581  
1545 1582 out:
  1583 + trace_recursive_unlock();
  1584 +
  1585 + out_nocheck:
1546 1586 ftrace_preempt_enable(resched);
1547 1587 return NULL;
1548 1588 }
... ... @@ -1580,6 +1620,8 @@
1580 1620 cpu_buffer = buffer->buffers[cpu];
1581 1621  
1582 1622 rb_commit(cpu_buffer, event);
  1623 +
  1624 + trace_recursive_unlock();
1583 1625  
1584 1626 /*
1585 1627 * Only the last preempt count needs to restore preemption.