Skip to content

Commit f22d020

Browse files
rostedtgregkh
authored andcommitted
ftrace: Handle tracing when switching between context
commit 726b3d3 upstream. When an interrupt or NMI comes in and switches the context, there's a delay from when the preempt_count() shows the update. As the preempt_count() is used to detect recursion having each context have its own bit get set when tracing starts, and if that bit is already set, it is considered a recursion and the function exits. But if this happens in that section where context has changed but preempt_count() has not been updated, this will be incorrectly flagged as a recursion. To handle this case, create another bit call TRANSITION and test it if the current context bit is already set. Flag the call as a recursion if the TRANSITION bit is already set, and if not, set it and continue. The TRANSITION bit will be cleared normally on the return of the function that set it, or if the current context bit is clear, set it and clear the TRANSITION bit to allow for another transition between the current context and an even higher one. Cc: stable@vger.kernel.org Fixes: edc15ca ("tracing: Avoid unnecessary multiple recursion checks") Signed-off-by: Steven Rostedt (VMware) <rostedt@goodmis.org> Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
1 parent 25d4a03 commit f22d020

2 files changed

Lines changed: 28 additions & 4 deletions

File tree

kernel/trace/trace.h

Lines changed: 21 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -636,6 +636,12 @@ enum {
636636
* function is called to clear it.
637637
*/
638638
TRACE_GRAPH_NOTRACE_BIT,
639+
640+
/*
641+
* When transitioning between context, the preempt_count() may
642+
* not be correct. Allow for a single recursion to cover this case.
643+
*/
644+
TRACE_TRANSITION_BIT,
639645
};
640646

641647
#define trace_recursion_set(bit) do { (current)->trace_recursion |= (1<<(bit)); } while (0)
@@ -690,8 +696,21 @@ static __always_inline int trace_test_and_set_recursion(int start, int max)
690696
return 0;
691697

692698
bit = trace_get_context_bit() + start;
693-
if (unlikely(val & (1 << bit)))
694-
return -1;
699+
if (unlikely(val & (1 << bit))) {
700+
/*
701+
* It could be that preempt_count has not been updated during
702+
* a switch between contexts. Allow for a single recursion.
703+
*/
704+
bit = TRACE_TRANSITION_BIT;
705+
if (trace_recursion_test(bit))
706+
return -1;
707+
trace_recursion_set(bit);
708+
barrier();
709+
return bit + 1;
710+
}
711+
712+
/* Normal check passed, clear the transition to allow it again */
713+
trace_recursion_clear(TRACE_TRANSITION_BIT);
695714

696715
val |= 1 << bit;
697716
current->trace_recursion = val;

kernel/trace/trace_selftest.c

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -492,8 +492,13 @@ trace_selftest_function_recursion(void)
492492
unregister_ftrace_function(&test_rec_probe);
493493

494494
ret = -1;
495-
if (trace_selftest_recursion_cnt != 1) {
496-
pr_cont("*callback not called once (%d)* ",
495+
/*
496+
* Recursion allows for transitions between context,
497+
* and may call the callback twice.
498+
*/
499+
if (trace_selftest_recursion_cnt != 1 &&
500+
trace_selftest_recursion_cnt != 2) {
501+
pr_cont("*callback not called once (or twice) (%d)* ",
497502
trace_selftest_recursion_cnt);
498503
goto out;
499504
}

0 commit comments

Comments
 (0)