trace, lockdep: manual preempt count adding for local_bh_disable
Impact: fix to preempt trace triggering lockdep check_flag failure In local_bh_disable, the use of add_preempt_count causes the preempt tracer to start recording the time preemption is off. But because it already modified the preempt_count to show softirqs disabled, and before it called the lockdep code to handle this, it causes a state that lockdep can not handle. The preempt tracer will reset the ring buffer on start of a trace, and the ring buffer reset code does a spin_lock_irqsave. This calls into lockdep and lockdep will fail when it detects the invalid state of having softirqs disabled but the internal current->softirqs_enabled is still set. The fix is to manually add the SOFTIRQ_OFFSET to preempt count and call the preempt tracer code outside the lockdep critical area. Thanks to Peter Zijlstra for suggesting this solution. Signed-off-by: Steven Rostedt <srostedt@redhat.com> Signed-off-by: Ingo Molnar <mingo@elte.hu>
This commit is contained in:
committed by
Ingo Molnar
parent
b06a830183
commit
7e49fcce1b
@@ -137,6 +137,8 @@ extern unsigned long nr_uninterruptible(void);
|
|||||||
extern unsigned long nr_active(void);
|
extern unsigned long nr_active(void);
|
||||||
extern unsigned long nr_iowait(void);
|
extern unsigned long nr_iowait(void);
|
||||||
|
|
||||||
|
extern unsigned long get_parent_ip(unsigned long addr);
|
||||||
|
|
||||||
struct seq_file;
|
struct seq_file;
|
||||||
struct cfs_rq;
|
struct cfs_rq;
|
||||||
struct task_group;
|
struct task_group;
|
||||||
|
@@ -4399,10 +4399,7 @@ void scheduler_tick(void)
|
|||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
#if defined(CONFIG_PREEMPT) && (defined(CONFIG_DEBUG_PREEMPT) || \
|
unsigned long get_parent_ip(unsigned long addr)
|
||||||
defined(CONFIG_PREEMPT_TRACER))
|
|
||||||
|
|
||||||
static inline unsigned long get_parent_ip(unsigned long addr)
|
|
||||||
{
|
{
|
||||||
if (in_lock_functions(addr)) {
|
if (in_lock_functions(addr)) {
|
||||||
addr = CALLER_ADDR2;
|
addr = CALLER_ADDR2;
|
||||||
@@ -4412,6 +4409,9 @@ static inline unsigned long get_parent_ip(unsigned long addr)
|
|||||||
return addr;
|
return addr;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#if defined(CONFIG_PREEMPT) && (defined(CONFIG_DEBUG_PREEMPT) || \
|
||||||
|
defined(CONFIG_PREEMPT_TRACER))
|
||||||
|
|
||||||
void __kprobes add_preempt_count(int val)
|
void __kprobes add_preempt_count(int val)
|
||||||
{
|
{
|
||||||
#ifdef CONFIG_DEBUG_PREEMPT
|
#ifdef CONFIG_DEBUG_PREEMPT
|
||||||
|
@@ -21,6 +21,7 @@
|
|||||||
#include <linux/freezer.h>
|
#include <linux/freezer.h>
|
||||||
#include <linux/kthread.h>
|
#include <linux/kthread.h>
|
||||||
#include <linux/rcupdate.h>
|
#include <linux/rcupdate.h>
|
||||||
|
#include <linux/ftrace.h>
|
||||||
#include <linux/smp.h>
|
#include <linux/smp.h>
|
||||||
#include <linux/tick.h>
|
#include <linux/tick.h>
|
||||||
|
|
||||||
@@ -79,13 +80,23 @@ static void __local_bh_disable(unsigned long ip)
|
|||||||
WARN_ON_ONCE(in_irq());
|
WARN_ON_ONCE(in_irq());
|
||||||
|
|
||||||
raw_local_irq_save(flags);
|
raw_local_irq_save(flags);
|
||||||
add_preempt_count(SOFTIRQ_OFFSET);
|
/*
|
||||||
|
* The preempt tracer hooks into add_preempt_count and will break
|
||||||
|
* lockdep because it calls back into lockdep after SOFTIRQ_OFFSET
|
||||||
|
* is set and before current->softirq_enabled is cleared.
|
||||||
|
* We must manually increment preempt_count here and manually
|
||||||
|
* call the trace_preempt_off later.
|
||||||
|
*/
|
||||||
|
preempt_count() += SOFTIRQ_OFFSET;
|
||||||
/*
|
/*
|
||||||
* Were softirqs turned off above:
|
* Were softirqs turned off above:
|
||||||
*/
|
*/
|
||||||
if (softirq_count() == SOFTIRQ_OFFSET)
|
if (softirq_count() == SOFTIRQ_OFFSET)
|
||||||
trace_softirqs_off(ip);
|
trace_softirqs_off(ip);
|
||||||
raw_local_irq_restore(flags);
|
raw_local_irq_restore(flags);
|
||||||
|
|
||||||
|
if (preempt_count() == SOFTIRQ_OFFSET)
|
||||||
|
trace_preempt_off(CALLER_ADDR0, get_parent_ip(CALLER_ADDR1));
|
||||||
}
|
}
|
||||||
#else /* !CONFIG_TRACE_IRQFLAGS */
|
#else /* !CONFIG_TRACE_IRQFLAGS */
|
||||||
static inline void __local_bh_disable(unsigned long ip)
|
static inline void __local_bh_disable(unsigned long ip)
|
||||||
|
Reference in New Issue
Block a user