1 /* libunwind - a platform-independent unwind library
2 Copyright 2011 Linaro Limited
3
4 This file is part of libunwind.
5
6 Permission is hereby granted, free of charge, to any person obtaining
7 a copy of this software and associated documentation files (the
8 "Software"), to deal in the Software without restriction, including
9 without limitation the rights to use, copy, modify, merge, publish,
10 distribute, sublicense, and/or sell copies of the Software, and to
11 permit persons to whom the Software is furnished to do so, subject to
12 the following conditions:
13
14 The above copyright notice and this permission notice shall be
15 included in all copies or substantial portions of the Software.
16
17 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
18 EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
19 MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
20 NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
21 LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
22 OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
23 WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
24
25 /* This file contains functionality for parsing and interpreting the ARM
26 specific unwind information. Documentation about the exception handling
27 ABI for the ARM architecture can be found at:
28 http://infocenter.arm.com/help/topic/com.arm.doc.ihi0038a/IHI0038A_ehabi.pdf
29 */
30
31 #include "libunwind_i.h"
32
33 #define ARM_EXBUF_START(x) (((x) >> 4) & 0x0f)
34 #define ARM_EXBUF_COUNT(x) ((x) & 0x0f)
35 #define ARM_EXBUF_END(x) (ARM_EXBUF_START(x) + ARM_EXBUF_COUNT(x))
36
37 #define ARM_EXIDX_CANT_UNWIND 0x00000001
38 #define ARM_EXIDX_COMPACT 0x80000000
39
40 #define ARM_EXTBL_OP_FINISH 0xb0
41
42 enum arm_exbuf_cmd_flags {
43 ARM_EXIDX_VFP_SHIFT_16 = 1 << 16,
44 ARM_EXIDX_VFP_DOUBLE = 1 << 17,
45 };
46
47 struct arm_cb_data
48 {
49 /* in: */
50 unw_word_t ip; /* instruction-pointer we're looking for */
51 unw_proc_info_t *pi; /* proc-info pointer */
52 /* out: */
53 unw_dyn_info_t di; /* info about the ARM exidx segment */
54 };
55
56 static inline uint32_t CONST_ATTR
prel31_read(uint32_t prel31)57 prel31_read (uint32_t prel31)
58 {
59 return ((int32_t)prel31 << 1) >> 1;
60 }
61
62 static inline int
prel31_to_addr(unw_addr_space_t as,void * arg,unw_word_t prel31,unw_word_t * val)63 prel31_to_addr (unw_addr_space_t as, void *arg, unw_word_t prel31,
64 unw_word_t *val)
65 {
66 unw_word_t offset;
67
68 if ((*as->acc.access_mem)(as, prel31, &offset, 0, arg) < 0)
69 return -UNW_EINVAL;
70
71 offset = ((long)offset << 1) >> 1;
72 *val = prel31 + offset;
73
74 return 0;
75 }
76
77 /**
78 * Applies the given command onto the new state to the given dwarf_cursor.
79 */
80 HIDDEN int
arm_exidx_apply_cmd(struct arm_exbuf_data * edata,struct dwarf_cursor * c)81 arm_exidx_apply_cmd (struct arm_exbuf_data *edata, struct dwarf_cursor *c)
82 {
83 int ret = 0;
84 unsigned i;
85
86 switch (edata->cmd)
87 {
88 case ARM_EXIDX_CMD_FINISH:
89 /* Set LR to PC if not set already. */
90 if (DWARF_IS_NULL_LOC (c->loc[UNW_ARM_R15]))
91 c->loc[UNW_ARM_R15] = c->loc[UNW_ARM_R14];
92 /* Set IP. */
93 dwarf_get (c, c->loc[UNW_ARM_R15], &c->ip);
94 break;
95 case ARM_EXIDX_CMD_DATA_PUSH:
96 Debug (2, "vsp = vsp - %d\n", edata->data);
97 c->cfa -= edata->data;
98 break;
99 case ARM_EXIDX_CMD_DATA_POP:
100 Debug (2, "vsp = vsp + %d\n", edata->data);
101 c->cfa += edata->data;
102 break;
103 case ARM_EXIDX_CMD_REG_POP:
104 for (i = 0; i < 16; i++)
105 if (edata->data & (1 << i))
106 {
107 Debug (2, "pop {r%d}\n", i);
108 c->loc[UNW_ARM_R0 + i] = DWARF_LOC (c->cfa, 0);
109 c->cfa += 4;
110 }
111 /* Set cfa in case the SP got popped. */
112 if (edata->data & (1 << 13))
113 dwarf_get (c, c->loc[UNW_ARM_R13], &c->cfa);
114 break;
115 case ARM_EXIDX_CMD_REG_TO_SP:
116 assert (edata->data < 16);
117 Debug (2, "vsp = r%d\n", edata->data);
118 c->loc[UNW_ARM_R13] = c->loc[UNW_ARM_R0 + edata->data];
119 dwarf_get (c, c->loc[UNW_ARM_R13], &c->cfa);
120 break;
121 case ARM_EXIDX_CMD_VFP_POP:
122 /* Skip VFP registers, but be sure to adjust stack */
123 for (i = ARM_EXBUF_START (edata->data); i <= ARM_EXBUF_END (edata->data);
124 i++)
125 c->cfa += 8;
126 if (!(edata->data & ARM_EXIDX_VFP_DOUBLE))
127 c->cfa += 4;
128 break;
129 case ARM_EXIDX_CMD_WREG_POP:
130 for (i = ARM_EXBUF_START (edata->data); i <= ARM_EXBUF_END (edata->data);
131 i++)
132 c->cfa += 8;
133 break;
134 case ARM_EXIDX_CMD_WCGR_POP:
135 for (i = 0; i < 4; i++)
136 if (edata->data & (1 << i))
137 c->cfa += 4;
138 break;
139 case ARM_EXIDX_CMD_REFUSED:
140 case ARM_EXIDX_CMD_RESERVED:
141 ret = -1;
142 break;
143 }
144 return ret;
145 }
146
147 /**
148 * Decodes the given unwind instructions into arm_exbuf_data and calls
149 * arm_exidx_apply_cmd that applies the command onto the dwarf_cursor.
150 */
151 HIDDEN int
arm_exidx_decode(const uint8_t * buf,uint8_t len,struct dwarf_cursor * c)152 arm_exidx_decode (const uint8_t *buf, uint8_t len, struct dwarf_cursor *c)
153 {
154 #define READ_OP() *buf++
155 assert(buf != NULL);
156 assert(len > 0);
157
158 const uint8_t *end = buf + len;
159 int ret;
160 struct arm_exbuf_data edata;
161
162 while (buf < end)
163 {
164 uint8_t op = READ_OP ();
165 if ((op & 0xc0) == 0x00)
166 {
167 edata.cmd = ARM_EXIDX_CMD_DATA_POP;
168 edata.data = (((int)op & 0x3f) << 2) + 4;
169 }
170 else if ((op & 0xc0) == 0x40)
171 {
172 edata.cmd = ARM_EXIDX_CMD_DATA_PUSH;
173 edata.data = (((int)op & 0x3f) << 2) + 4;
174 }
175 else if ((op & 0xf0) == 0x80)
176 {
177 uint8_t op2 = READ_OP ();
178 if (op == 0x80 && op2 == 0x00)
179 edata.cmd = ARM_EXIDX_CMD_REFUSED;
180 else
181 {
182 edata.cmd = ARM_EXIDX_CMD_REG_POP;
183 edata.data = ((op & 0xf) << 8) | op2;
184 edata.data = edata.data << 4;
185 }
186 }
187 else if ((op & 0xf0) == 0x90)
188 {
189 if (op == 0x9d || op == 0x9f)
190 edata.cmd = ARM_EXIDX_CMD_RESERVED;
191 else
192 {
193 edata.cmd = ARM_EXIDX_CMD_REG_TO_SP;
194 edata.data = op & 0x0f;
195 }
196 }
197 else if ((op & 0xf0) == 0xa0)
198 {
199 unsigned end = (op & 0x07);
200 edata.data = (1 << (end + 1)) - 1;
201 edata.data = edata.data << 4;
202 if (op & 0x08)
203 edata.data |= 1 << 14;
204 edata.cmd = ARM_EXIDX_CMD_REG_POP;
205 }
206 else if (op == ARM_EXTBL_OP_FINISH)
207 {
208 edata.cmd = ARM_EXIDX_CMD_FINISH;
209 buf = end;
210 }
211 else if (op == 0xb1)
212 {
213 uint8_t op2 = READ_OP ();
214 if (op2 == 0 || (op2 & 0xf0))
215 edata.cmd = ARM_EXIDX_CMD_RESERVED;
216 else
217 {
218 edata.cmd = ARM_EXIDX_CMD_REG_POP;
219 edata.data = op2 & 0x0f;
220 }
221 }
222 else if (op == 0xb2)
223 {
224 uint32_t offset = 0;
225 uint8_t byte, shift = 0;
226 do
227 {
228 byte = READ_OP ();
229 offset |= (byte & 0x7f) << shift;
230 shift += 7;
231 }
232 while (byte & 0x80);
233 edata.data = offset * 4 + 0x204;
234 edata.cmd = ARM_EXIDX_CMD_DATA_POP;
235 }
236 else if (op == 0xb3 || op == 0xc8 || op == 0xc9)
237 {
238 edata.cmd = ARM_EXIDX_CMD_VFP_POP;
239 edata.data = READ_OP ();
240 if (op == 0xc8)
241 edata.data |= ARM_EXIDX_VFP_SHIFT_16;
242 if (op != 0xb3)
243 edata.data |= ARM_EXIDX_VFP_DOUBLE;
244 }
245 else if ((op & 0xf8) == 0xb8 || (op & 0xf8) == 0xd0)
246 {
247 edata.cmd = ARM_EXIDX_CMD_VFP_POP;
248 edata.data = 0x80 | (op & 0x07);
249 if ((op & 0xf8) == 0xd0)
250 edata.data |= ARM_EXIDX_VFP_DOUBLE;
251 }
252 else if (op >= 0xc0 && op <= 0xc5)
253 {
254 edata.cmd = ARM_EXIDX_CMD_WREG_POP;
255 edata.data = 0xa0 | (op & 0x07);
256 }
257 else if (op == 0xc6)
258 {
259 edata.cmd = ARM_EXIDX_CMD_WREG_POP;
260 edata.data = READ_OP ();
261 }
262 else if (op == 0xc7)
263 {
264 uint8_t op2 = READ_OP ();
265 if (op2 == 0 || (op2 & 0xf0))
266 edata.cmd = ARM_EXIDX_CMD_RESERVED;
267 else
268 {
269 edata.cmd = ARM_EXIDX_CMD_WCGR_POP;
270 edata.data = op2 & 0x0f;
271 }
272 }
273 else
274 edata.cmd = ARM_EXIDX_CMD_RESERVED;
275
276 ret = arm_exidx_apply_cmd (&edata, c);
277 if (ret < 0)
278 return ret;
279 }
280 return 0;
281 }
282
283 /**
284 * Reads the entry from the given cursor and extracts the unwind instructions
285 * into buf. Returns the number of the extracted unwind insns or
286 * -UNW_ESTOPUNWIND if the special bit pattern ARM_EXIDX_CANT_UNWIND (0x1) was
287 * found.
288 */
289 HIDDEN int
arm_exidx_extract(struct dwarf_cursor * c,uint8_t * buf)290 arm_exidx_extract (struct dwarf_cursor *c, uint8_t *buf)
291 {
292 int nbuf = 0;
293 unw_word_t entry = (unw_word_t) c->pi.unwind_info;
294 unw_word_t addr;
295 uint32_t data;
296
297 /* An ARM unwind entry consists of a prel31 offset to the start of a
298 function followed by 31bits of data:
299 * if set to 0x1: the function cannot be unwound (EXIDX_CANTUNWIND)
300 * if bit 31 is one: this is a table entry itself (ARM_EXIDX_COMPACT)
301 * if bit 31 is zero: this is a prel31 offset of the start of the
302 table entry for this function */
303 if (prel31_to_addr(c->as, c->as_arg, entry, &addr) < 0)
304 return -UNW_EINVAL;
305
306 if ((*c->as->acc.access_mem)(c->as, entry + 4, &data, 0, c->as_arg) < 0)
307 return -UNW_EINVAL;
308
309 if (data == ARM_EXIDX_CANT_UNWIND)
310 {
311 Debug (2, "0x1 [can't unwind]\n");
312 nbuf = -UNW_ESTOPUNWIND;
313 }
314 else if (data & ARM_EXIDX_COMPACT)
315 {
316 Debug (2, "%p compact model %d [%8.8x]\n", (void *)addr,
317 (data >> 24) & 0x7f, data);
318 buf[nbuf++] = data >> 16;
319 buf[nbuf++] = data >> 8;
320 buf[nbuf++] = data;
321 }
322 else
323 {
324 unw_word_t extbl_data;
325 unsigned int n_table_words = 0;
326
327 if (prel31_to_addr(c->as, c->as_arg, entry + 4, &extbl_data) < 0)
328 return -UNW_EINVAL;
329
330 if ((*c->as->acc.access_mem)(c->as, extbl_data, &data, 0, c->as_arg) < 0)
331 return -UNW_EINVAL;
332
333 if (data & ARM_EXIDX_COMPACT)
334 {
335 int pers = (data >> 24) & 0x0f;
336 Debug (2, "%p compact model %d [%8.8x]\n", (void *)addr, pers, data);
337 if (pers == 1 || pers == 2)
338 {
339 n_table_words = (data >> 16) & 0xff;
340 extbl_data += 4;
341 }
342 else
343 buf[nbuf++] = data >> 16;
344 buf[nbuf++] = data >> 8;
345 buf[nbuf++] = data;
346 }
347 else
348 {
349 unw_word_t pers;
350 if (prel31_to_addr (c->as, c->as_arg, extbl_data, &pers) < 0)
351 return -UNW_EINVAL;
352 Debug (2, "%p Personality routine: %8p\n", (void *)addr,
353 (void *)pers);
354 if ((*c->as->acc.access_mem)(c->as, extbl_data + 4, &data, 0,
355 c->as_arg) < 0)
356 return -UNW_EINVAL;
357 n_table_words = data >> 24;
358 buf[nbuf++] = data >> 16;
359 buf[nbuf++] = data >> 8;
360 buf[nbuf++] = data;
361 extbl_data += 8;
362 }
363 assert (n_table_words <= 5);
364 if(n_table_words > 5) {
365 Debug (2, "n_table_words is %d will be crash the stack\n",n_table_words);
366 return -UNW_EINVAL;
367 }
368 unsigned j;
369 for (j = 0; j < n_table_words; j++)
370 {
371 if ((*c->as->acc.access_mem)(c->as, extbl_data, &data, 0,
372 c->as_arg) < 0)
373 return -UNW_EINVAL;
374 extbl_data += 4;
375 buf[nbuf++] = data >> 24;
376 buf[nbuf++] = data >> 16;
377 buf[nbuf++] = data >> 8;
378 buf[nbuf++] = data >> 0;
379 }
380 }
381
382 if (nbuf > 0 && buf[nbuf - 1] != ARM_EXTBL_OP_FINISH)
383 buf[nbuf++] = ARM_EXTBL_OP_FINISH;
384
385 return nbuf;
386 }
387
388 static int
arm_search_unwind_table(unw_addr_space_t as,unw_word_t ip,unw_dyn_info_t * di,unw_proc_info_t * pi,int need_unwind_info,void * arg)389 arm_search_unwind_table (unw_addr_space_t as, unw_word_t ip,
390 unw_dyn_info_t *di, unw_proc_info_t *pi,
391 int need_unwind_info, void *arg)
392 {
393 /* The .ARM.exidx section contains a sorted list of key-value pairs -
394 the unwind entries. The 'key' is a prel31 offset to the start of a
395 function. We binary search this section in order to find the
396 appropriate unwind entry. */
397 unw_word_t first = di->u.rti.table_data;
398 unw_word_t last = di->u.rti.table_data + di->u.rti.table_len - 8;
399 unw_word_t entry, val;
400
401 if (prel31_to_addr (as, arg, first, &val) < 0 || ip < val)
402 return -UNW_ENOINFO;
403
404 if (prel31_to_addr (as, arg, last, &val) < 0)
405 return -UNW_EINVAL;
406
407 if (ip >= val)
408 {
409 entry = last;
410
411 if (prel31_to_addr (as, arg, last, &pi->start_ip) < 0)
412 return -UNW_EINVAL;
413
414 pi->end_ip = di->end_ip -1;
415 }
416 else
417 {
418 while (first < last - 8)
419 {
420 entry = first + (((last - first) / 8 + 1) >> 1) * 8;
421
422 if (prel31_to_addr (as, arg, entry, &val) < 0)
423 return -UNW_EINVAL;
424
425 if (ip < val)
426 last = entry;
427 else
428 first = entry;
429 }
430
431 entry = first;
432
433 if (prel31_to_addr (as, arg, entry, &pi->start_ip) < 0)
434 return -UNW_EINVAL;
435
436 if (prel31_to_addr (as, arg, entry + 8, &pi->end_ip) < 0)
437 return -UNW_EINVAL;
438
439 pi->end_ip--;
440 }
441
442 if (need_unwind_info)
443 {
444 pi->unwind_info_size = 8;
445 pi->unwind_info = (void *) entry;
446 pi->format = UNW_INFO_FORMAT_ARM_EXIDX;
447 }
448 return 0;
449 }
450
451 int
tdep_search_unwind_table(unw_addr_space_t as,unw_word_t ip,unw_dyn_info_t * di,unw_proc_info_t * pi,int need_unwind_info,void * arg)452 tdep_search_unwind_table (unw_addr_space_t as, unw_word_t ip,
453 unw_dyn_info_t *di, unw_proc_info_t *pi,
454 int need_unwind_info, void *arg)
455 {
456 if (UNW_TRY_METHOD (UNW_ARM_METHOD_EXIDX)
457 && di->format == UNW_INFO_FORMAT_ARM_EXIDX)
458 return arm_search_unwind_table (as, ip, di, pi, need_unwind_info, arg);
459 else if (UNW_TRY_METHOD(UNW_ARM_METHOD_DWARF)
460 && di->format != UNW_INFO_FORMAT_ARM_EXIDX)
461 return dwarf_search_unwind_table (as, ip, di, pi, need_unwind_info, arg);
462
463 return -UNW_ENOINFO;
464 }
465
466 #ifndef UNW_REMOTE_ONLY
467 /**
468 * Callback to dl_iterate_phdr to find infos about the ARM exidx segment.
469 */
470 static int
arm_phdr_cb(struct dl_phdr_info * info,size_t size,void * data)471 arm_phdr_cb (struct dl_phdr_info *info, size_t size, void *data)
472 {
473 struct arm_cb_data *cb_data = data;
474 const Elf_W(Phdr) *p_text = NULL;
475 const Elf_W(Phdr) *p_arm_exidx = NULL;
476 const Elf_W(Phdr) *phdr = info->dlpi_phdr;
477 long n;
478
479 for (n = info->dlpi_phnum; --n >= 0; phdr++)
480 {
481 switch (phdr->p_type)
482 {
483 case PT_LOAD:
484 if (cb_data->ip >= phdr->p_vaddr + info->dlpi_addr &&
485 cb_data->ip < phdr->p_vaddr + info->dlpi_addr + phdr->p_memsz)
486 p_text = phdr;
487 break;
488
489 case PT_ARM_EXIDX:
490 p_arm_exidx = phdr;
491 break;
492
493 default:
494 break;
495 }
496 }
497
498 if (p_text && p_arm_exidx)
499 {
500 cb_data->di.format = UNW_INFO_FORMAT_ARM_EXIDX;
501 cb_data->di.start_ip = p_text->p_vaddr + info->dlpi_addr;
502 cb_data->di.end_ip = cb_data->di.start_ip + p_text->p_memsz;
503 cb_data->di.u.rti.name_ptr = (unw_word_t) info->dlpi_name;
504 cb_data->di.u.rti.table_data = p_arm_exidx->p_vaddr + info->dlpi_addr;
505 cb_data->di.u.rti.table_len = p_arm_exidx->p_memsz;
506 return 1;
507 }
508
509 return 0;
510 }
511
512 HIDDEN int
arm_find_proc_info2(unw_addr_space_t as,unw_word_t ip,unw_proc_info_t * pi,int need_unwind_info,void * arg,int methods)513 arm_find_proc_info2 (unw_addr_space_t as, unw_word_t ip,
514 unw_proc_info_t *pi, int need_unwind_info, void *arg,
515 int methods)
516 {
517 int ret = -1;
518 intrmask_t saved_mask;
519
520 Debug (14, "looking for IP=0x%lx\n", (long) ip);
521
522 if (UNW_TRY_METHOD (UNW_ARM_METHOD_DWARF) && (methods & UNW_ARM_METHOD_DWARF))
523 ret = dwarf_find_proc_info (as, ip, pi, need_unwind_info, arg);
524
525 if (ret < 0 && UNW_TRY_METHOD (UNW_ARM_METHOD_EXIDX) &&
526 (methods & UNW_ARM_METHOD_EXIDX))
527 {
528 struct arm_cb_data cb_data;
529
530 memset (&cb_data, 0, sizeof (cb_data));
531 cb_data.ip = ip;
532 cb_data.pi = pi;
533 cb_data.di.format = -1;
534
535 SIGPROCMASK (SIG_SETMASK, &unwi_full_mask, &saved_mask);
536 ret = dl_iterate_phdr (arm_phdr_cb, &cb_data);
537 SIGPROCMASK (SIG_SETMASK, &saved_mask, NULL);
538
539 if (cb_data.di.format != -1)
540 ret = arm_search_unwind_table (as, ip, &cb_data.di, pi,
541 need_unwind_info, arg);
542 else
543 ret = -UNW_ENOINFO;
544 }
545
546 return ret;
547 }
548
549 HIDDEN int
arm_find_proc_info(unw_addr_space_t as,unw_word_t ip,unw_proc_info_t * pi,int need_unwind_info,void * arg)550 arm_find_proc_info (unw_addr_space_t as, unw_word_t ip,
551 unw_proc_info_t *pi, int need_unwind_info, void *arg)
552 {
553 return arm_find_proc_info2 (as, ip, pi, need_unwind_info, arg,
554 UNW_ARM_METHOD_ALL);
555 }
556
557 HIDDEN void
arm_put_unwind_info(unw_addr_space_t as,unw_proc_info_t * proc_info,void * arg)558 arm_put_unwind_info (unw_addr_space_t as, unw_proc_info_t *proc_info, void *arg)
559 {
560 /* it's a no-op */
561 }
562 #endif /* !UNW_REMOTE_ONLY */
563
564