Newer gcc versions generate a warning about accessing an array out of
bounds:

In function 'search_index',
    inlined from 'unwind_find_idx' at arch/arm/lib32/unwind.c:109: ,
    inlined from 'unwind_frame' at arch/arm/lib32/unwind.c:248:8:
arch/arm/lib32/unwind.c:86:32: warning: array subscript -1 is outside array 
bounds of 'struct unwind_idx[268435455]' [-Warray-bounds]
   86 |         } else if (addr >= last->addr)
      |                            ~~~~^~~~~~
arch/arm/lib32/unwind.c: In function 'unwind_frame':
arch/arm/lib32/unwind.c:46:26: note: at offset -8 into object 
'__stop_unwind_idx' of size [0, 2147483647]
   46 | extern struct unwind_idx __stop_unwind_idx[];
      |                          ^~~~~~~~~~~~~~~~~

This was fixed by accident in the Kernel back in 2011 in
de66a979012d ("ARM: 7187/1: fix unwinding for XIP kernels") . Adopt that
commit for barebox.

Signed-off-by: Sascha Hauer <[email protected]>
---
 arch/arm/include/asm/unwind.h |   7 +-
 arch/arm/lib32/unwind.c       | 116 +++++++++++++++++++++++-----------
 2 files changed, 84 insertions(+), 39 deletions(-)

diff --git a/arch/arm/include/asm/unwind.h b/arch/arm/include/asm/unwind.h
index 319527ec9b..37aba2aa4c 100644
--- a/arch/arm/include/asm/unwind.h
+++ b/arch/arm/include/asm/unwind.h
@@ -16,14 +16,15 @@ enum unwind_reason_code {
 };
 
 struct unwind_idx {
-       unsigned long addr;
+       unsigned long addr_offset;
        unsigned long insn;
 };
 
 struct unwind_table {
        struct list_head list;
-       struct unwind_idx *start;
-       struct unwind_idx *stop;
+       const struct unwind_idx *start;
+       const struct unwind_idx *origin;
+       const struct unwind_idx *stop;
        unsigned long begin_addr;
        unsigned long end_addr;
 };
diff --git a/arch/arm/lib32/unwind.c b/arch/arm/lib32/unwind.c
index 79bf7420d7..6f73cb1b73 100644
--- a/arch/arm/lib32/unwind.c
+++ b/arch/arm/lib32/unwind.c
@@ -28,7 +28,7 @@ EXPORT_SYMBOL(__aeabi_unwind_cpp_pr2);
 
 struct unwind_ctrl_block {
        unsigned long vrs[16];          /* virtual register set */
-       unsigned long *insn;            /* pointer to the current instructions 
word */
+       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 */
 };
@@ -42,8 +42,9 @@ enum regs {
 
 #define THREAD_SIZE               8192
 
-extern struct unwind_idx __start_unwind_idx[];
-extern struct unwind_idx __stop_unwind_idx[];
+extern const struct unwind_idx __start_unwind_idx[];
+static const struct unwind_idx *__origin_unwind_idx;
+extern const struct unwind_idx __stop_unwind_idx[];
 
 /* Convert a prel31 symbol to an absolute address */
 #define prel31_to_addr(ptr)                            \
@@ -71,44 +72,99 @@ static void dump_backtrace_entry(unsigned long where, 
unsigned long from,
 }
 
 /*
- * Binary search in the unwind index. The entries entries are
+ * 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 struct unwind_idx *search_index(unsigned long addr,
-                                      struct unwind_idx *first,
-                                      struct unwind_idx *last)
+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)
 {
-       pr_debug("%s(%08lx, %p, %p)\n", __func__, addr, first, last);
+       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;
+               }
+       }
 
-       if (addr < first->addr) {
+       if (likely(start->addr_offset <= addr_prel31))
+               return start;
+       else {
                pr_warning("unwind: Unknown symbol address %08lx\n", addr);
                return NULL;
-       } else if (addr >= last->addr)
-               return last;
+       }
+}
 
-       while (first < last - 1) {
-               struct unwind_idx *mid = first + ((last - first + 1) >> 1);
+static const struct unwind_idx *unwind_find_origin(
+               const struct unwind_idx *start, const struct unwind_idx *stop)
+{
+       pr_debug("%s(%p, %p)\n", __func__, start, stop);
+       while (start < stop - 1) {
+               const struct unwind_idx *mid = start + ((stop - start) >> 1);
 
-               if (addr < mid->addr)
-                       last = mid;
+               if (mid->addr_offset >= 0x40000000)
+                       /* negative offset */
+                       start = mid;
                else
-                       first = mid;
+                       /* positive offset */
+                       stop = mid;
        }
 
-       return first;
-}
+       pr_debug("%s -> %p\n", __func__, stop);
+       return stop;
+ }
 
-static struct unwind_idx *unwind_find_idx(unsigned long addr)
+static const struct unwind_idx *unwind_find_idx(unsigned long addr)
 {
-       struct unwind_idx *idx = NULL;
+       const struct unwind_idx *idx = NULL;
 
        pr_debug("%s(%08lx)\n", __func__, addr);
 
-       if (is_kernel_text(addr))
+       if (is_kernel_text(addr)) {
+               if (unlikely(!__origin_unwind_idx))
+                       __origin_unwind_idx =
+                               unwind_find_origin(__start_unwind_idx,
+                                               __stop_unwind_idx);
+
                /* main unwind table */
                idx = search_index(addr, __start_unwind_idx,
-                                  __stop_unwind_idx - 1);
-       else {
+                                       __origin_unwind_idx,
+                                       __stop_unwind_idx);
+       } else {
                /* module unwinding not supported */
        }
 
@@ -232,7 +288,7 @@ static int unwind_exec_insn(struct unwind_ctrl_block *ctrl)
 int unwind_frame(struct stackframe *frame)
 {
        unsigned long high, low;
-       struct unwind_idx *idx;
+       const struct unwind_idx *idx;
        struct unwind_ctrl_block ctrl;
 
        /* only go to a higher address on the stack */
@@ -342,15 +398,3 @@ void dump_stack(void)
 {
        unwind_backtrace(NULL);
 }
-
-static int unwind_init(void)
-{
-       struct unwind_idx *idx;
-
-       /* Convert the symbol addresses to absolute values */
-       for (idx = __start_unwind_idx; idx < __stop_unwind_idx; idx++)
-               idx->addr = prel31_to_addr(&idx->addr);
-
-       return 0;
-}
-core_initcall(unwind_init);
-- 
2.30.2


Reply via email to