From: Michal Schmidt <mschmidt@redhat.com> Date: Wed, 27 Aug 2008 11:21:05 +0200 Subject: [x86] execute stack overflow warning on interrupt stack Message-id: 20080827112105.72c9523b@brian.englab.brq.redhat.com O-Subject: [RHEL5.3 PATCH] i386: Execute stack overflow warning on interrupt stack Bugzilla: 459810 RH-Acked-by: Anton Arapov <aarapov@redhat.com> RH-Acked-by: Dave Anderson <anderson@redhat.com> RH-Acked-by: Neil Horman <nhorman@redhat.com> RH-Acked-by: Don Dutile <ddutile@redhat.com> RH-Acked-by: Eric Sandeen <sandeen@redhat.com> BZ: https://bugzilla.redhat.com/show_bug.cgi?id=459810 RHEL kernel uses 4K stacks and has CONFIG_DEBUG_STACKOVERFLOW enabled. When an interrupt comes, do_IRQ() checks for the remaining stack space and if it is less than 512 bytes, it prints a warning and a backtrace. Unfortunately, the printing itself eats quite a lot of stack and so instead of printing the warning and continuing, the kernel is very likely to crash. This patch changes do_IRQ() to print the low stack warning from the interrupt stack which has enough space. Based on a patch from Andi Kleen, upstream commit 04b361abfdc522239e3a071f3afdebf5787d9f03. I added the same fix for the xen kernel. Brew build: https://brewweb.devel.redhat.com/taskinfo?taskID=1439323 Tested sucessfully with both 'kernel' and 'kernel-xen' packages on my workstation. Without the patch both crashed. A reproducer is attached in the BZ. Michal irq-xen.c | 48 +++++++++++++++++++++++++++++++++--------------- irq.c | 48 +++++++++++++++++++++++++++++++++--------------- 2 files changed, 66 insertions(+), 30 deletions(-) diff --git a/arch/i386/kernel/irq-xen.c b/arch/i386/kernel/irq-xen.c index 1c04c6e..8862ff4 100644 --- a/arch/i386/kernel/irq-xen.c +++ b/arch/i386/kernel/irq-xen.c @@ -46,6 +46,26 @@ static union irq_ctx *hardirq_ctx[NR_CPUS] __read_mostly; static union irq_ctx *softirq_ctx[NR_CPUS] __read_mostly; #endif +static void stack_overflow(void) +{ + printk(KERN_WARNING "low stack detected by irq handler\n"); + dump_stack(); +} + +static inline void call_on_stack2(void *func, void *stack, + unsigned long arg1, unsigned long arg2) +{ + unsigned long bx; + asm volatile( + " xchgl %%ebx,%%esp \n" + " call *%%edi \n" + " movl %%ebx,%%esp \n" + : "=a" (arg1), "=d" (arg2), "=b" (bx) + : "0" (arg1), "1" (arg2), "2" (stack), + "D" (func) + : "memory", "cc", "ecx"); +} + /* * do_IRQ handles all normal device IRQ's (the special * SMP cross-CPU interrupts have their own specific @@ -59,6 +79,7 @@ fastcall unsigned int do_IRQ(struct pt_regs *regs) union irq_ctx *curctx, *irqctx; u32 *isp; #endif + int overflow = 0; if (unlikely((unsigned)irq >= NR_IRQS)) { printk(KERN_EMERG "%s: cannot handle IRQ %d\n", @@ -74,11 +95,8 @@ fastcall unsigned int do_IRQ(struct pt_regs *regs) __asm__ __volatile__("andl %%esp,%0" : "=r" (esp) : "0" (THREAD_SIZE - 1)); - if (unlikely(esp < (sizeof(struct thread_info) + STACK_WARN))) { - printk("do_IRQ: stack overflow: %ld\n", - esp - sizeof(struct thread_info)); - dump_stack(); - } + if (unlikely(esp < (sizeof(struct thread_info) + STACK_WARN))) + overflow = 1; } #endif @@ -94,8 +112,6 @@ fastcall unsigned int do_IRQ(struct pt_regs *regs) * current stack (which is the irq stack already after all) */ if (curctx != irqctx) { - int arg1, arg2, ebx; - /* build the stack frame on the IRQ stack */ isp = (u32*) ((char*)irqctx + sizeof(*irqctx)); irqctx->tinfo.task = curctx->tinfo.task; @@ -109,17 +125,19 @@ fastcall unsigned int do_IRQ(struct pt_regs *regs) (irqctx->tinfo.preempt_count & ~SOFTIRQ_MASK) | (curctx->tinfo.preempt_count & SOFTIRQ_MASK); - asm volatile( - " xchgl %%ebx,%%esp \n" - " call __do_IRQ \n" - " movl %%ebx,%%esp \n" - : "=a" (arg1), "=d" (arg2), "=b" (ebx) - : "0" (irq), "1" (regs), "2" (isp) - : "memory", "cc", "ecx" - ); + /* Execute warning on interrupt stack */ + if (unlikely(overflow)) + call_on_stack2(stack_overflow, isp, 0, 0); + + call_on_stack2(__do_IRQ, isp, irq, (unsigned long)regs); } else #endif + { + /* AK: Slightly bogus here */ + if (overflow) + stack_overflow(); __do_IRQ(irq, regs); + } irq_exit(); diff --git a/arch/i386/kernel/irq.c b/arch/i386/kernel/irq.c index 5fe547c..7ba1f81 100644 --- a/arch/i386/kernel/irq.c +++ b/arch/i386/kernel/irq.c @@ -46,6 +46,26 @@ static union irq_ctx *hardirq_ctx[NR_CPUS] __read_mostly; static union irq_ctx *softirq_ctx[NR_CPUS] __read_mostly; #endif +static void stack_overflow(void) +{ + printk(KERN_WARNING "low stack detected by irq handler\n"); + dump_stack(); +} + +static inline void call_on_stack2(void *func, void *stack, + unsigned long arg1, unsigned long arg2) +{ + unsigned long bx; + asm volatile( + " xchgl %%ebx,%%esp \n" + " call *%%edi \n" + " movl %%ebx,%%esp \n" + : "=a" (arg1), "=d" (arg2), "=b" (bx) + : "0" (arg1), "1" (arg2), "2" (stack), + "D" (func) + : "memory", "cc", "ecx"); +} + /* * do_IRQ handles all normal device IRQ's (the special * SMP cross-CPU interrupts have their own specific @@ -59,6 +79,7 @@ fastcall unsigned int do_IRQ(struct pt_regs *regs) union irq_ctx *curctx, *irqctx; u32 *isp; #endif + int overflow = 0; if (unlikely((unsigned)irq >= NR_IRQS)) { printk(KERN_EMERG "%s: cannot handle IRQ %d\n", @@ -74,11 +95,8 @@ fastcall unsigned int do_IRQ(struct pt_regs *regs) __asm__ __volatile__("andl %%esp,%0" : "=r" (esp) : "0" (THREAD_SIZE - 1)); - if (unlikely(esp < (sizeof(struct thread_info) + STACK_WARN))) { - printk("do_IRQ: stack overflow: %ld\n", - esp - sizeof(struct thread_info)); - dump_stack(); - } + if (unlikely(esp < (sizeof(struct thread_info) + STACK_WARN))) + overflow = 1; } #endif @@ -94,8 +112,6 @@ fastcall unsigned int do_IRQ(struct pt_regs *regs) * current stack (which is the irq stack already after all) */ if (curctx != irqctx) { - int arg1, arg2, ebx; - /* build the stack frame on the IRQ stack */ isp = (u32*) ((char*)irqctx + sizeof(*irqctx)); irqctx->tinfo.task = curctx->tinfo.task; @@ -109,17 +125,19 @@ fastcall unsigned int do_IRQ(struct pt_regs *regs) (irqctx->tinfo.preempt_count & ~SOFTIRQ_MASK) | (curctx->tinfo.preempt_count & SOFTIRQ_MASK); - asm volatile( - " xchgl %%ebx,%%esp \n" - " call __do_IRQ \n" - " movl %%ebx,%%esp \n" - : "=a" (arg1), "=d" (arg2), "=b" (ebx) - : "0" (irq), "1" (regs), "2" (isp) - : "memory", "cc", "ecx" - ); + /* Execute warning on interrupt stack */ + if (unlikely(overflow)) + call_on_stack2(stack_overflow, isp, 0, 0); + + call_on_stack2(__do_IRQ, isp, irq, (unsigned long)regs); } else #endif + { + /* AK: Slightly bogus here */ + if (overflow) + stack_overflow(); __do_IRQ(irq, regs); + } irq_exit();