Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux

ARM: 7187/1: fix unwinding for XIP kernels

The linker places the unwind tables in readonly sections. So when using
an XIP kernel these are located in ROM and cannot be modified.
For that reason the current approach to convert the relative offsets in
the unwind index to absolute addresses early in the boot process doesn't
work with XIP.

The offsets in the unwind index section are signed 31 bit numbers and
the structs are sorted by this offset. So it first has offsets between
0x40000000 and 0x7fffffff (i.e. the negative offsets) and then offsets
between 0x00000000 and 0x3fffffff. When seperating these two blocks the
numbers are sorted even when interpreting the offsets as unsigned longs.

So determine the first non-negative entry once and track that using the
new origin pointer. The actual bisection can then use a plain unsigned
long comparison. The only thing that makes the new bisection more
complicated is that the offsets are relative to their position in the
index section, so the key to search needs to be adapted accordingly in
each step.

Moreover several consts are added to catch future writes and rename the
member "addr" of struct unwind_idx to "addr_offset" to better match the
new semantic. (This has the additional benefit of breaking eventual
users at compile time to make them aware of the change.)

In my tests the new algorithm was a tad faster than the original and has
the additional upside of not needing the initial conversion and so saves
some boot time and it's possible to unwind even earlier.

Acked-by: Catalin Marinas <catalin.marinas@arm.com>
Acked-by: Nicolas Pitre <nico@fluxnic.net>
Signed-off-by: Uwe Kleine-König <u.kleine-koenig@pengutronix.de>
Signed-off-by: Russell King <rmk+kernel@arm.linux.org.uk>

authored by

Uwe Kleine-König and committed by
Russell King
de66a979 974c0724

+90 -61
+4 -12
arch/arm/include/asm/unwind.h
··· 30 30 }; 31 31 32 32 struct unwind_idx { 33 - unsigned long addr; 33 + unsigned long addr_offset; 34 34 unsigned long insn; 35 35 }; 36 36 37 37 struct unwind_table { 38 38 struct list_head list; 39 - struct unwind_idx *start; 40 - struct unwind_idx *stop; 39 + const struct unwind_idx *start; 40 + const struct unwind_idx *origin; 41 + const struct unwind_idx *stop; 41 42 unsigned long begin_addr; 42 43 unsigned long end_addr; 43 44 }; ··· 49 48 unsigned long text_size); 50 49 extern void unwind_table_del(struct unwind_table *tab); 51 50 extern void unwind_backtrace(struct pt_regs *regs, struct task_struct *tsk); 52 - 53 - #ifdef CONFIG_ARM_UNWIND 54 - extern int __init unwind_init(void); 55 - #else 56 - static inline int __init unwind_init(void) 57 - { 58 - return 0; 59 - } 60 - #endif 61 51 62 52 #endif /* !__ASSEMBLY__ */ 63 53
-2
arch/arm/kernel/setup.c
··· 895 895 { 896 896 struct machine_desc *mdesc; 897 897 898 - unwind_init(); 899 - 900 898 setup_processor(); 901 899 mdesc = setup_machine_fdt(__atags_pointer); 902 900 if (!mdesc)
+86 -47
arch/arm/kernel/unwind.c
··· 67 67 68 68 struct unwind_ctrl_block { 69 69 unsigned long vrs[16]; /* virtual register set */ 70 - unsigned long *insn; /* pointer to the current instructions word */ 70 + const unsigned long *insn; /* pointer to the current instructions word */ 71 71 int entries; /* number of entries left to interpret */ 72 72 int byte; /* current byte number in the instructions word */ 73 73 }; ··· 83 83 PC = 15 84 84 }; 85 85 86 - extern struct unwind_idx __start_unwind_idx[]; 87 - extern struct unwind_idx __stop_unwind_idx[]; 86 + extern const struct unwind_idx __start_unwind_idx[]; 87 + static const struct unwind_idx *__origin_unwind_idx; 88 + extern const struct unwind_idx __stop_unwind_idx[]; 88 89 89 90 static DEFINE_SPINLOCK(unwind_lock); 90 91 static LIST_HEAD(unwind_tables); ··· 99 98 }) 100 99 101 100 /* 102 - * Binary search in the unwind index. The entries entries are 101 + * Binary search in the unwind index. The entries are 103 102 * guaranteed to be sorted in ascending order by the linker. 103 + * 104 + * start = first entry 105 + * origin = first entry with positive offset (or stop if there is no such entry) 106 + * stop - 1 = last entry 104 107 */ 105 - static struct unwind_idx *search_index(unsigned long addr, 106 - struct unwind_idx *first, 107 - struct unwind_idx *last) 108 + static const struct unwind_idx *search_index(unsigned long addr, 109 + const struct unwind_idx *start, 110 + const struct unwind_idx *origin, 111 + const struct unwind_idx *stop) 108 112 { 109 - pr_debug("%s(%08lx, %p, %p)\n", __func__, addr, first, last); 113 + unsigned long addr_prel31; 110 114 111 - if (addr < first->addr) { 112 - pr_warning("unwind: Unknown symbol address %08lx\n", addr); 113 - return NULL; 114 - } else if (addr >= last->addr) 115 - return last; 115 + pr_debug("%s(%08lx, %p, %p, %p)\n", 116 + __func__, addr, start, origin, stop); 116 117 117 - while (first < last - 1) { 118 - struct unwind_idx *mid = first + ((last - first + 1) >> 1); 118 + /* 119 + * only search in the section with the matching sign. This way the 120 + * prel31 numbers can be compared as unsigned longs. 121 + */ 122 + if (addr < (unsigned long)start) 123 + /* negative offsets: [start; origin) */ 124 + stop = origin; 125 + else 126 + /* positive offsets: [origin; stop) */ 127 + start = origin; 119 128 120 - if (addr < mid->addr) 121 - last = mid; 122 - else 123 - first = mid; 129 + /* prel31 for address relavive to start */ 130 + addr_prel31 = (addr - (unsigned long)start) & 0x7fffffff; 131 + 132 + while (start < stop - 1) { 133 + const struct unwind_idx *mid = start + ((stop - start) >> 1); 134 + 135 + /* 136 + * As addr_prel31 is relative to start an offset is needed to 137 + * make it relative to mid. 138 + */ 139 + if (addr_prel31 - ((unsigned long)mid - (unsigned long)start) < 140 + mid->addr_offset) 141 + stop = mid; 142 + else { 143 + /* keep addr_prel31 relative to start */ 144 + addr_prel31 -= ((unsigned long)mid - 145 + (unsigned long)start); 146 + start = mid; 147 + } 124 148 } 125 149 126 - return first; 150 + if (likely(start->addr_offset <= addr_prel31)) 151 + return start; 152 + else { 153 + pr_warning("unwind: Unknown symbol address %08lx\n", addr); 154 + return NULL; 155 + } 127 156 } 128 157 129 - static struct unwind_idx *unwind_find_idx(unsigned long addr) 158 + static const struct unwind_idx *unwind_find_origin( 159 + const struct unwind_idx *start, const struct unwind_idx *stop) 130 160 { 131 - struct unwind_idx *idx = NULL; 161 + pr_debug("%s(%p, %p)\n", __func__, start, stop); 162 + while (start < stop - 1) { 163 + const struct unwind_idx *mid = start + ((stop - start) >> 1); 164 + 165 + if (mid->addr_offset >= 0x40000000) 166 + /* negative offset */ 167 + start = mid; 168 + else 169 + /* positive offset */ 170 + stop = mid; 171 + } 172 + pr_debug("%s -> %p\n", __func__, stop); 173 + return stop; 174 + } 175 + 176 + static const struct unwind_idx *unwind_find_idx(unsigned long addr) 177 + { 178 + const struct unwind_idx *idx = NULL; 132 179 unsigned long flags; 133 180 134 181 pr_debug("%s(%08lx)\n", __func__, addr); 135 182 136 - if (core_kernel_text(addr)) 183 + if (core_kernel_text(addr)) { 184 + if (unlikely(!__origin_unwind_idx)) 185 + __origin_unwind_idx = 186 + unwind_find_origin(__start_unwind_idx, 187 + __stop_unwind_idx); 188 + 137 189 /* main unwind table */ 138 190 idx = search_index(addr, __start_unwind_idx, 139 - __stop_unwind_idx - 1); 140 - else { 191 + __origin_unwind_idx, 192 + __stop_unwind_idx); 193 + } else { 141 194 /* module unwind tables */ 142 195 struct unwind_table *table; 143 196 ··· 200 145 if (addr >= table->begin_addr && 201 146 addr < table->end_addr) { 202 147 idx = search_index(addr, table->start, 203 - table->stop - 1); 148 + table->origin, 149 + table->stop); 204 150 /* Move-to-front to exploit common traces */ 205 151 list_move(&table->list, &unwind_tables); 206 152 break; ··· 330 274 int unwind_frame(struct stackframe *frame) 331 275 { 332 276 unsigned long high, low; 333 - struct unwind_idx *idx; 277 + const struct unwind_idx *idx; 334 278 struct unwind_ctrl_block ctrl; 335 279 336 280 /* only go to a higher address on the stack */ ··· 455 399 unsigned long text_size) 456 400 { 457 401 unsigned long flags; 458 - struct unwind_idx *idx; 459 402 struct unwind_table *tab = kmalloc(sizeof(*tab), GFP_KERNEL); 460 403 461 404 pr_debug("%s(%08lx, %08lx, %08lx, %08lx)\n", __func__, start, size, ··· 463 408 if (!tab) 464 409 return tab; 465 410 466 - tab->start = (struct unwind_idx *)start; 467 - tab->stop = (struct unwind_idx *)(start + size); 411 + tab->start = (const struct unwind_idx *)start; 412 + tab->stop = (const struct unwind_idx *)(start + size); 413 + tab->origin = unwind_find_origin(tab->start, tab->stop); 468 414 tab->begin_addr = text_addr; 469 415 tab->end_addr = text_addr + text_size; 470 - 471 - /* Convert the symbol addresses to absolute values */ 472 - for (idx = tab->start; idx < tab->stop; idx++) 473 - idx->addr = prel31_to_addr(&idx->addr); 474 416 475 417 spin_lock_irqsave(&unwind_lock, flags); 476 418 list_add_tail(&tab->list, &unwind_tables); ··· 488 436 spin_unlock_irqrestore(&unwind_lock, flags); 489 437 490 438 kfree(tab); 491 - } 492 - 493 - int __init unwind_init(void) 494 - { 495 - struct unwind_idx *idx; 496 - 497 - /* Convert the symbol addresses to absolute values */ 498 - for (idx = __start_unwind_idx; idx < __stop_unwind_idx; idx++) 499 - idx->addr = prel31_to_addr(&idx->addr); 500 - 501 - pr_debug("unwind: ARM stack unwinding initialised\n"); 502 - 503 - return 0; 504 439 }