aboutsummaryrefslogtreecommitdiffstats
path: root/arch/x86/kernel/dumpstack_64.c
diff options
context:
space:
mode:
authorFrederic Weisbecker <fweisbec@gmail.com>2009-12-05 23:34:27 -0500
committerFrederic Weisbecker <fweisbec@gmail.com>2009-12-06 02:27:24 -0500
commitaf2d8289f57e427836be482c6f72cca674028121 (patch)
tree8bf5da73fb7459a4eb3f3552eebec87c30e7cc6d /arch/x86/kernel/dumpstack_64.c
parentb625b3b3b740e177a1148594cd3ad5ff52f35315 (diff)
x86: Fixup wrong irq frame link in stacktraces
When we enter in irq, two things can happen to preserve the link to the previous frame pointer: - If we were in an irq already, we don't switch to the irq stack as we are inside. We just need to save the previous frame pointer and to link the new one to the previous. - Otherwise we need another level of indirection. We enter the irq with the previous stack. We save the previous bp inside and make bp pointing to its saved address. Then we switch to the irq stack and push bp another time but to the new stack. This makes two levels to dereference instead of one. In the second case, the current stacktrace code omits the second level and loses the frame pointer accuracy. The stack that follows will then be considered as unreliable. Handling that makes the perf callchain happier. Before: 43.94% [k] _raw_read_lock | --- _read_lock | |--60.53%-- send_sigio | __kill_fasync | kill_fasync | evdev_pass_event | evdev_event | input_pass_event | input_handle_event | input_event | synaptics_process_byte | psmouse_handle_byte | psmouse_interrupt | serio_interrupt | i8042_interrupt | handle_IRQ_event | handle_edge_irq | handle_irq | __irqentry_text_start | ret_from_intr | | | |--30.43%-- __select | | | |--17.39%-- 0x454f15 | | | |--13.04%-- __read | | | |--13.04%-- vread_hpet | | | |--13.04%-- _xcb_lock_io | | | --13.04%-- 0x7f630878ce8 After: 50.00% [k] _raw_read_lock | --- _read_lock | |--98.97%-- send_sigio | __kill_fasync | kill_fasync | evdev_pass_event | evdev_event | input_pass_event | input_handle_event | input_event | | | |--96.88%-- synaptics_process_byte | | psmouse_handle_byte | | psmouse_interrupt | | serio_interrupt | | i8042_interrupt | | handle_IRQ_event | | handle_edge_irq | | handle_irq | | __irqentry_text_start | | ret_from_intr | | | | | |--39.78%-- __const_udelay | | | | | | | |--91.89%-- ath5k_hw_register_timeout | | | | ath5k_hw_noise_floor_calibration | | | | ath5k_hw_reset | | | | ath5k_reset | | | | ath5k_config | | | | ieee80211_hw_config | | | | | | | | | |--88.24%-- ieee80211_scan_work | | | | | worker_thread | | | | | kthread | | | | | child_rip | | | | | | | | | --11.76%-- ieee80211_scan_completed | | | | ieee80211_scan_work | | | | worker_thread | | | | kthread | | | | child_rip | | | | | | | --8.11%-- ath5k_hw_noise_floor_calibration | | | ath5k_hw_reset | | | ath5k_reset | | | ath5k_config Note: This does not only affect perf events but also x86-64 stacktraces. They were considered as unreliable once we quit the irq stack frame. Signed-off-by: Frederic Weisbecker <fweisbec@gmail.com> Cc: Peter Zijlstra <peterz@infradead.org> Cc: Paul Mackerras <paulus@samba.org> Cc: Arnaldo Carvalho de Melo <acme@redhat.com> Cc: "K. Prasad" <prasad@linux.vnet.ibm.com> Cc: Thomas Gleixner <tglx@linutronix.de> Cc: "H. Peter Anvin" <hpa@zytor.com>
Diffstat (limited to 'arch/x86/kernel/dumpstack_64.c')
-rw-r--r--arch/x86/kernel/dumpstack_64.c33
1 files changed, 32 insertions, 1 deletions
diff --git a/arch/x86/kernel/dumpstack_64.c b/arch/x86/kernel/dumpstack_64.c
index a071e6be177..004b8aa6a35 100644
--- a/arch/x86/kernel/dumpstack_64.c
+++ b/arch/x86/kernel/dumpstack_64.c
@@ -101,6 +101,35 @@ static unsigned long *in_exception_stack(unsigned cpu, unsigned long stack,
101 return NULL; 101 return NULL;
102} 102}
103 103
104static inline int
105in_irq_stack(unsigned long *stack, unsigned long *irq_stack,
106 unsigned long *irq_stack_end)
107{
108 return (stack >= irq_stack && stack < irq_stack_end);
109}
110
111/*
112 * We are returning from the irq stack and go to the previous one.
113 * If the previous stack is also in the irq stack, then bp in the first
114 * frame of the irq stack points to the previous, interrupted one.
115 * Otherwise we have another level of indirection: We first save
116 * the bp of the previous stack, then we switch the stack to the irq one
117 * and save a new bp that links to the previous one.
118 * (See save_args())
119 */
120static inline unsigned long
121fixup_bp_irq_link(unsigned long bp, unsigned long *stack,
122 unsigned long *irq_stack, unsigned long *irq_stack_end)
123{
124#ifdef CONFIG_FRAME_POINTER
125 struct stack_frame *frame = (struct stack_frame *)bp;
126
127 if (!in_irq_stack(stack, irq_stack, irq_stack_end))
128 return (unsigned long)frame->next_frame;
129#endif
130 return bp;
131}
132
104/* 133/*
105 * x86-64 can have up to three kernel stacks: 134 * x86-64 can have up to three kernel stacks:
106 * process stack 135 * process stack
@@ -173,7 +202,7 @@ void dump_trace(struct task_struct *task, struct pt_regs *regs,
173 irq_stack = irq_stack_end - 202 irq_stack = irq_stack_end -
174 (IRQ_STACK_SIZE - 64) / sizeof(*irq_stack); 203 (IRQ_STACK_SIZE - 64) / sizeof(*irq_stack);
175 204
176 if (stack >= irq_stack && stack < irq_stack_end) { 205 if (in_irq_stack(stack, irq_stack, irq_stack_end)) {
177 if (ops->stack(data, "IRQ") < 0) 206 if (ops->stack(data, "IRQ") < 0)
178 break; 207 break;
179 bp = print_context_stack(tinfo, stack, bp, 208 bp = print_context_stack(tinfo, stack, bp,
@@ -184,6 +213,8 @@ void dump_trace(struct task_struct *task, struct pt_regs *regs,
184 * pointer (index -1 to end) in the IRQ stack: 213 * pointer (index -1 to end) in the IRQ stack:
185 */ 214 */
186 stack = (unsigned long *) (irq_stack_end[-1]); 215 stack = (unsigned long *) (irq_stack_end[-1]);
216 bp = fixup_bp_irq_link(bp, stack, irq_stack,
217 irq_stack_end);
187 irq_stack_end = NULL; 218 irq_stack_end = NULL;
188 ops->stack(data, "EOI"); 219 ops->stack(data, "EOI");
189 continue; 220 continue;