Commit be24a897 authored by Mikulas Patocka's avatar Mikulas Patocka Committed by Helge Deller

parisc: Fix backtrace on PA-RISC

This patch fixes backtrace on PA-RISC

There were several problems:

1) The code that decodes instructions handles instructions that subtract
from the stack pointer incorrectly. If the instruction subtracts the
number X from the stack pointer the code increases the frame size by
(0x100000000-X).  This results in invalid accesses to memory and
recursive page faults.

2) Because gcc reorders blocks, handling instructions that subtract from
the frame pointer is incorrect. For example, this function
	int f(int a)
	{
		if (__builtin_expect(a, 1))
			return a;
		g();
		return a;
	}
is compiled in such a way, that the code that decreases the stack
pointer for the first "return a" is placed before the code for "g" call.
If we recognize this decrement, we mistakenly believe that the frame
size for the "g" call is zero.

To fix problems 1) and 2), the patch doesn't recognize instructions that
decrease the stack pointer at all. To further safeguard the unwind code
against nonsense values, we don't allow frame size larger than
Total_frame_size.

3) The backtrace is not locked. If stack dump races with module unload,
invalid table can be accessed.

This patch adds a spinlock when processing module tables.

Note, that for correct backtrace, you need recent binutils.
Binutils 2.18 from Debian 5 produce garbage unwind tables.
Binutils 2.21 work better (it sometimes forgets function frames, but at
least it doesn't generate garbage).
Signed-off-by: default avatarMikulas Patocka <mpatocka@redhat.com>
Signed-off-by: default avatarHelge Deller <deller@gmx.de>
parent 049ec1b5
...@@ -75,7 +75,10 @@ find_unwind_entry(unsigned long addr) ...@@ -75,7 +75,10 @@ find_unwind_entry(unsigned long addr)
if (addr >= kernel_unwind_table.start && if (addr >= kernel_unwind_table.start &&
addr <= kernel_unwind_table.end) addr <= kernel_unwind_table.end)
e = find_unwind_entry_in_table(&kernel_unwind_table, addr); e = find_unwind_entry_in_table(&kernel_unwind_table, addr);
else else {
unsigned long flags;
spin_lock_irqsave(&unwind_lock, flags);
list_for_each_entry(table, &unwind_tables, list) { list_for_each_entry(table, &unwind_tables, list) {
if (addr >= table->start && if (addr >= table->start &&
addr <= table->end) addr <= table->end)
...@@ -86,6 +89,8 @@ find_unwind_entry(unsigned long addr) ...@@ -86,6 +89,8 @@ find_unwind_entry(unsigned long addr)
break; break;
} }
} }
spin_unlock_irqrestore(&unwind_lock, flags);
}
return e; return e;
} }
...@@ -303,18 +308,16 @@ static void unwind_frame_regs(struct unwind_frame_info *info) ...@@ -303,18 +308,16 @@ static void unwind_frame_regs(struct unwind_frame_info *info)
insn = *(unsigned int *)npc; insn = *(unsigned int *)npc;
if ((insn & 0xffffc000) == 0x37de0000 || if ((insn & 0xffffc001) == 0x37de0000 ||
(insn & 0xffe00000) == 0x6fc00000) { (insn & 0xffe00001) == 0x6fc00000) {
/* ldo X(sp), sp, or stwm X,D(sp) */ /* ldo X(sp), sp, or stwm X,D(sp) */
frame_size += (insn & 0x1 ? -1 << 13 : 0) | frame_size += (insn & 0x3fff) >> 1;
((insn & 0x3fff) >> 1);
dbg("analyzing func @ %lx, insn=%08x @ " dbg("analyzing func @ %lx, insn=%08x @ "
"%lx, frame_size = %ld\n", info->ip, "%lx, frame_size = %ld\n", info->ip,
insn, npc, frame_size); insn, npc, frame_size);
} else if ((insn & 0xffe00008) == 0x73c00008) { } else if ((insn & 0xffe00009) == 0x73c00008) {
/* std,ma X,D(sp) */ /* std,ma X,D(sp) */
frame_size += (insn & 0x1 ? -1 << 13 : 0) | frame_size += ((insn >> 4) & 0x3ff) << 3;
(((insn >> 4) & 0x3ff) << 3);
dbg("analyzing func @ %lx, insn=%08x @ " dbg("analyzing func @ %lx, insn=%08x @ "
"%lx, frame_size = %ld\n", info->ip, "%lx, frame_size = %ld\n", info->ip,
insn, npc, frame_size); insn, npc, frame_size);
...@@ -333,6 +336,9 @@ static void unwind_frame_regs(struct unwind_frame_info *info) ...@@ -333,6 +336,9 @@ static void unwind_frame_regs(struct unwind_frame_info *info)
} }
} }
if (frame_size > e->Total_frame_size << 3)
frame_size = e->Total_frame_size << 3;
if (!unwind_special(info, e->region_start, frame_size)) { if (!unwind_special(info, e->region_start, frame_size)) {
info->prev_sp = info->sp - frame_size; info->prev_sp = info->sp - frame_size;
if (e->Millicode) if (e->Millicode)
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment