|
@@ -0,0 +1,149 @@
|
|
|
|
+/*
|
|
|
|
+ * arch/arm/kernel/unwind.c
|
|
|
|
+ *
|
|
|
|
+ * Copyright (C) 2008 ARM Limited
|
|
|
|
+ *
|
|
|
|
+ * This program is free software; you can redistribute it and/or modify
|
|
|
|
+ * it under the terms of the GNU General Public License version 2 as
|
|
|
|
+ * published by the Free Software Foundation.
|
|
|
|
+ *
|
|
|
|
+ * This program is distributed in the hope that it will be useful,
|
|
|
|
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
|
|
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
|
|
+ * GNU General Public License for more details.
|
|
|
|
+ *
|
|
|
|
+ * You should have received a copy of the GNU General Public License
|
|
|
|
+ * along with this program; if not, write to the Free Software
|
|
|
|
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
|
|
|
+ *
|
|
|
|
+ *
|
|
|
|
+ * Stack unwinding support for ARM
|
|
|
|
+ *
|
|
|
|
+ * An ARM EABI version of gcc is required to generate the unwind
|
|
|
|
+ * tables. For information about the structure of the unwind tables,
|
|
|
|
+ * see "Exception Handling ABI for the ARM Architecture" at:
|
|
|
|
+ *
|
|
|
|
+ * http://infocenter.arm.com/help/topic/com.arm.doc.subset.swdev.abi/index.html
|
|
|
|
+ */
|
|
|
|
+
|
|
|
|
+#ifndef __CHECKER__
|
|
|
|
+#if !defined (__ARM_EABI__)
|
|
|
|
+#warning Your compiler does not have EABI support.
|
|
|
|
+#warning ARM unwind is known to compile only with EABI compilers.
|
|
|
|
+#warning Change compiler or disable ARM_UNWIND option.
|
|
|
|
+#elif (__GNUC__ == 4 && __GNUC_MINOR__ <= 2)
|
|
|
|
+#warning Your compiler is too buggy; it is known to not compile ARM unwind support.
|
|
|
|
+#warning Change compiler or disable ARM_UNWIND option.
|
|
|
|
+#endif
|
|
|
|
+#endif /* __CHECKER__ */
|
|
|
|
+
|
|
|
|
+#include <linux/kernel.h>
|
|
|
|
+#include <linux/init.h>
|
|
|
|
+#include <linux/export.h>
|
|
|
|
+#include <linux/sched.h>
|
|
|
|
+#include <linux/slab.h>
|
|
|
|
+#include <linux/spinlock.h>
|
|
|
|
+#include <linux/list.h>
|
|
|
|
+
|
|
|
|
+#include <asm/stacktrace.h>
|
|
|
|
+#include <asm/traps.h>
|
|
|
|
+#include <asm/unwind.h>
|
|
|
|
+
|
|
|
|
+/* Dummy functions to avoid linker complaints */
|
|
|
|
+void __aeabi_unwind_cpp_pr0(void)
|
|
|
|
+{
|
|
|
|
+};
|
|
|
|
+EXPORT_SYMBOL(__aeabi_unwind_cpp_pr0);
|
|
|
|
+
|
|
|
|
+void __aeabi_unwind_cpp_pr1(void)
|
|
|
|
+{
|
|
|
|
+};
|
|
|
|
+EXPORT_SYMBOL(__aeabi_unwind_cpp_pr1);
|
|
|
|
+
|
|
|
|
+void __aeabi_unwind_cpp_pr2(void)
|
|
|
|
+{
|
|
|
|
+};
|
|
|
|
+EXPORT_SYMBOL(__aeabi_unwind_cpp_pr2);
|
|
|
|
+
|
|
|
|
+struct unwind_ctrl_block {
|
|
|
|
+ unsigned long vrs[16]; /* virtual register set */
|
|
|
|
+ const unsigned long *insn; /* pointer to the current instructions word */
|
|
|
|
+ int entries; /* number of entries left to interpret */
|
|
|
|
+ int byte; /* current byte number in the instructions word */
|
|
|
|
+};
|
|
|
|
+
|
|
|
|
+enum regs {
|
|
|
|
+#ifdef CONFIG_THUMB2_KERNEL
|
|
|
|
+ FP = 7,
|
|
|
|
+#else
|
|
|
|
+ FP = 11,
|
|
|
|
+#endif
|
|
|
|
+ SP = 13,
|
|
|
|
+ LR = 14,
|
|
|
|
+ PC = 15
|
|
|
|
+};
|
|
|
|
+
|
|
|
|
+extern const struct unwind_idx __start_unwind_idx[];
|
|
|
|
+static const struct unwind_idx *__origin_unwind_idx;
|
|
|
|
+extern const struct unwind_idx __stop_unwind_idx[];
|
|
|
|
+
|
|
|
|
+static DEFINE_SPINLOCK(unwind_lock);
|
|
|
|
+static LIST_HEAD(unwind_tables);
|
|
|
|
+
|
|
|
|
+/* Convert a prel31 symbol to an absolute address */
|
|
|
|
+#define prel31_to_addr(ptr) \
|
|
|
|
+({ \
|
|
|
|
+ /* sign-extend to 32 bits */ \
|
|
|
|
+ long offset = (((long)*(ptr)) << 1) >> 1; \
|
|
|
|
+ (unsigned long)(ptr) + offset; \
|
|
|
|
+})
|
|
|
|
+
|
|
|
|
+/*
|
|
|
|
+ * Binary search in the unwind index. The entries are
|
|
|
|
+ * guaranteed to be sorted in ascending order by the linker.
|
|
|
|
+ *
|
|
|
|
+ * start = first entry
|
|
|
|
+ * origin = first entry with positive offset (or stop if there is no such entry)
|
|
|
|
+ * stop - 1 = last entry
|
|
|
|
+ */
|
|
|
|
+static const struct unwind_idx *search_index(unsigned long addr,
|
|
|
|
+ const struct unwind_idx *start,
|
|
|
|
+ const struct unwind_idx *origin,
|
|
|
|
+ const struct unwind_idx *stop)
|
|
|
|
+{
|
|
|
|
+ unsigned long addr_prel31;
|
|
|
|
+
|
|
|
|
+ pr_debug("%s(%08lx, %p, %p, %p)\n",
|
|
|
|
+ __func__, addr, start, origin, stop);
|
|
|
|
+
|
|
|
|
+ /*
|
|
|
|
+ * only search in the section with the matching sign. This way the
|
|
|
|
+ * prel31 numbers can be compared as unsigned longs.
|
|
|
|
+ */
|
|
|
|
+ if (addr < (unsigned long)start)
|
|
|
|
+ /* negative offsets: [start; origin) */
|
|
|
|
+ stop = origin;
|
|
|
|
+ else
|
|
|
|
+ /* positive offsets: [origin; stop) */
|
|
|
|
+ start = origin;
|
|
|
|
+
|
|
|
|
+ /* prel31 for address relavive to start */
|
|
|
|
+ addr_prel31 = (addr - (unsigned long)start) & 0x7fffffff;
|
|
|
|
+
|
|
|
|
+ while (start < stop - 1) {
|
|
|
|
+ const struct unwind_idx *mid = start + ((stop - start) >> 1);
|
|
|
|
+
|
|
|
|
+ /*
|
|
|
|
+ * As addr_prel31 is relative to start an offset is needed to
|
|
|
|
+ * make it relative to mid.
|
|
|
|
+ */
|
|
|
|
+ if (addr_prel31 - ((unsigned long)mid - (unsigned long)start) <
|
|
|
|
+ mid->addr_offset)
|
|
|
|
+ stop = mid;
|
|
|
|
+ else {
|
|
|
|
+ /* keep addr_prel31 relative to start */
|
|
|
|
+ addr_prel31 -= ((unsigned long)mid -
|
|
|
|
+ (unsigned long)start);
|
|
|
|
+ start = mid;
|
|
|
|
+ }
|
|
|
|
+ }
|