• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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