1/* libunwind - a platform-independent unwind library
2   Copyright (C) 2003-2004 Hewlett-Packard Co
3        Contributed by David Mosberger-Tang <davidm@hpl.hp.com>
4
5This file is part of libunwind.
6
7Permission is hereby granted, free of charge, to any person obtaining
8a copy of this software and associated documentation files (the
9"Software"), to deal in the Software without restriction, including
10without limitation the rights to use, copy, modify, merge, publish,
11distribute, sublicense, and/or sell copies of the Software, and to
12permit persons to whom the Software is furnished to do so, subject to
13the following conditions:
14
15The above copyright notice and this permission notice shall be
16included in all copies or substantial portions of the Software.
17
18THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
19EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
20MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
21NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
22LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
23OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
24WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.  */
25
26#include <fcntl.h>
27#include <inttypes.h>
28#include <string.h>
29#include <unistd.h>
30
31#include <sys/mman.h>
32
33#include "libunwind_i.h"
34#include "dwarf-eh.h"
35#include "dwarf_i.h"
36
37HIDDEN int
38dwarf_find_unwind_table (struct elf_dyn_info *edi, unw_addr_space_t as,
39                         char *path, unw_word_t segbase, unw_word_t mapoff,
40                         unw_word_t ip)
41{
42  Elf_W(Phdr) *phdr, *ptxt = NULL, *peh_hdr = NULL, *pdyn = NULL;
43  unw_word_t addr, eh_frame_start, fde_count, load_base;
44  unw_word_t max_load_addr = 0;
45  unw_word_t start_ip = (unw_word_t) -1;
46  unw_word_t end_ip = 0;
47  struct dwarf_eh_frame_hdr *hdr;
48  unw_proc_info_t pi;
49  unw_accessors_t *a;
50  Elf_W(Ehdr) *ehdr;
51#if UNW_TARGET_ARM
52  const Elf_W(Phdr) *parm_exidx = NULL;
53#endif
54  int i, ret, found = 0;
55
56  Debug (3, "(edi %p, %p, \"%s\", 0x%lx, 0x%lx, 0x%lx)\n",
57         edi, as, path, (long) segbase, (long) mapoff, (long) ip);
58
59  /* XXX: Much of this code is Linux/LSB-specific.  */
60
61  if (!elf_w(valid_object) (&edi->ei))
62    {
63      Debug(3, "returning, invalid elf object\n");
64      return -UNW_ENOINFO;
65    }
66
67  ehdr = edi->ei.image;
68  phdr = (Elf_W(Phdr) *) ((char *) edi->ei.image + ehdr->e_phoff);
69
70  for (i = 0; i < ehdr->e_phnum; ++i)
71    {
72      switch (phdr[i].p_type)
73        {
74        case PT_LOAD:
75          if (phdr[i].p_vaddr < start_ip)
76            start_ip = phdr[i].p_vaddr;
77
78          if (phdr[i].p_vaddr + phdr[i].p_memsz > end_ip)
79            end_ip = phdr[i].p_vaddr + phdr[i].p_memsz;
80
81          if (phdr[i].p_offset == mapoff)
82            ptxt = phdr + i;
83          if ((uintptr_t) edi->ei.image + phdr->p_filesz > max_load_addr)
84            max_load_addr = (uintptr_t) edi->ei.image + phdr->p_filesz;
85          break;
86
87        case PT_GNU_EH_FRAME:
88          peh_hdr = phdr + i;
89          break;
90
91        case PT_DYNAMIC:
92          pdyn = phdr + i;
93          break;
94
95#if UNW_TARGET_ARM
96        case PT_ARM_EXIDX:
97          parm_exidx = phdr + i;
98          break;
99#endif
100
101        default:
102          break;
103        }
104    }
105
106  if (!ptxt)
107    {
108      Debug(3, "returning 0, no text\n");
109      return 0;
110    }
111
112  load_base = segbase - ptxt->p_vaddr;
113  start_ip += load_base;
114  end_ip += load_base;
115
116  if (peh_hdr)
117    {
118      if (pdyn)
119        {
120          /* For dynamicly linked executables and shared libraries,
121             DT_PLTGOT is the value that data-relative addresses are
122             relative to for that object.  We call this the "gp".  */
123                Elf_W(Dyn) *dyn = (Elf_W(Dyn) *)(pdyn->p_offset
124                                                 + (char *) edi->ei.image);
125          for (; dyn->d_tag != DT_NULL; ++dyn)
126            if (dyn->d_tag == DT_PLTGOT)
127              {
128                /* Assume that _DYNAMIC is writable and GLIBC has
129                   relocated it (true for x86 at least).  */
130                edi->di_cache.gp = dyn->d_un.d_ptr;
131                break;
132              }
133        }
134      else
135        /* Otherwise this is a static executable with no _DYNAMIC.  Assume
136           that data-relative addresses are relative to 0, i.e.,
137           absolute.  */
138        edi->di_cache.gp = 0;
139
140      hdr = (struct dwarf_eh_frame_hdr *) (peh_hdr->p_offset
141                                           + (char *) edi->ei.image);
142      if (hdr->version != DW_EH_VERSION)
143        {
144          Debug (1, "returning, table `%s' has unexpected version %d\n",
145                 path, hdr->version);
146          return -UNW_ENOINFO;
147        }
148
149      a = unw_get_accessors (unw_local_addr_space);
150      addr = (unw_word_t) (hdr + 1);
151
152      /* Fill in a dummy proc_info structure.  We just need to fill in
153         enough to ensure that dwarf_read_encoded_pointer() can do it's
154         job.  Since we don't have a procedure-context at this point, all
155         we have to do is fill in the global-pointer.  */
156      memset (&pi, 0, sizeof (pi));
157      pi.gp = edi->di_cache.gp;
158
159      /* (Optionally) read eh_frame_ptr: */
160      if ((ret = dwarf_read_encoded_pointer (unw_local_addr_space, a,
161                                             &addr, hdr->eh_frame_ptr_enc, &pi,
162                                             &eh_frame_start, NULL)) < 0)
163        {
164          Debug(3, "returning, dwarf_read_encoded_pointer failed: %d\n", ret);
165          return -UNW_ENOINFO;
166        }
167
168      /* (Optionally) read fde_count: */
169      if ((ret = dwarf_read_encoded_pointer (unw_local_addr_space, a,
170                                             &addr, hdr->fde_count_enc, &pi,
171                                             &fde_count, NULL)) < 0)
172        {
173          Debug(3, "returning, dwarf_read_encoded_pointer failed: %d\n", ret);
174          return -UNW_ENOINFO;
175        }
176
177      if (hdr->table_enc != (DW_EH_PE_datarel | DW_EH_PE_sdata4))
178        {
179    #if 1
180          assert (0);
181    #else
182          unw_word_t eh_frame_end;
183
184          /* If there is no search table or it has an unsupported
185             encoding, fall back on linear search.  */
186          if (hdr->table_enc == DW_EH_PE_omit)
187            Debug (4, "EH lacks search table; doing linear search\n");
188          else
189            Debug (4, "EH table has encoding 0x%x; doing linear search\n",
190                   hdr->table_enc);
191
192          eh_frame_end = max_load_addr; /* XXX can we do better? */
193
194          if (hdr->fde_count_enc == DW_EH_PE_omit)
195            fde_count = ~0UL;
196          if (hdr->eh_frame_ptr_enc == DW_EH_PE_omit)
197            assert (0);
198
199          return linear_search (unw_local_addr_space, ip,
200                                eh_frame_start, eh_frame_end, fde_count,
201                                pi, need_unwind_info, NULL);
202    #endif
203        }
204
205      edi->di_cache.start_ip = start_ip;
206      edi->di_cache.end_ip = end_ip;
207      edi->di_cache.format = UNW_INFO_FORMAT_REMOTE_TABLE;
208      edi->di_cache.u.rti.name_ptr = 0;
209      /* two 32-bit values (ip_offset/fde_offset) per table-entry: */
210      edi->di_cache.u.rti.table_len = (fde_count * 8) / sizeof (unw_word_t);
211      edi->di_cache.u.rti.table_data = ((load_base + peh_hdr->p_vaddr)
212                                        + sizeof(*hdr));
213
214      /* For the binary-search table in the eh_frame_hdr, data-relative
215         means relative to the start of that section... */
216      edi->di_cache.u.rti.segbase = (load_base + peh_hdr->p_vaddr);
217      found = 1;
218    }
219
220#if UNW_TARGET_ARM
221  if (parm_exidx)
222    {
223      edi->di_arm.format = UNW_INFO_FORMAT_ARM_EXIDX;
224      edi->di_arm.start_ip = start_ip;
225      edi->di_arm.end_ip = end_ip;
226      edi->di_arm.u.rti.name_ptr = (unw_word_t) path;
227      edi->di_arm.u.rti.table_data = load_base + parm_exidx->p_vaddr;
228      edi->di_arm.u.rti.table_len = parm_exidx->p_memsz;
229      found = 1;
230    }
231#endif
232
233#ifdef CONFIG_DEBUG_FRAME
234  /* Try .debug_frame. */
235  found = dwarf_find_debug_frame (found, &edi->di_debug, ip, load_base, path,
236                                  start_ip, end_ip);
237#endif
238
239  Debug(3, "returning, found %d\n", found);
240  return found;
241}
242
243/* Exported version that uses the address space of the potentially
244   remote process.
245   We only need to read memory, but pass |as| to keep a consistent API.  */
246
247HIDDEN int
248dwarf_as_find_unwind_table (struct as_elf_dyn_info *edi, unw_addr_space_t as,
249                            const char *path, unw_word_t segbase, unw_word_t mapoff,
250                            unw_word_t ip)
251{
252  Elf_W(Phdr) *phdr, *ptxt = NULL, *peh_hdr = NULL, *pdyn = NULL;
253  unw_word_t addr, eh_frame_start, fde_count, load_base;
254  unw_word_t start_ip = (unw_word_t) -1;
255  unw_word_t end_ip = 0;
256  struct dwarf_eh_frame_hdr *hdr;
257  unw_proc_info_t pi;
258  Elf_W(Ehdr) *ehdr;
259#if 0
260#if UNW_TARGET_ARM
261  const Elf_W(Phdr) *parm_exidx = NULL;
262#endif
263#endif
264  int i, ret, found = 0;
265
266  Debug (3, "(edi %p, %p, \"%s\", 0x%lx, 0x%lx, 0x%lx)\n",
267         edi, as, path, (long) segbase, (long) mapoff, (long) ip);
268
269  // TODO(dje): byteswapping of ehdr values
270  ret = unwi_load_as_contents(as, &edi->ehdr, segbase, sizeof(*ehdr), edi->arg);
271  if (ret < 0)
272    {
273      Debug(3, "returning, unwi_load_as_contents failed: %d\n", ret);
274      return ret;
275    }
276  ehdr = edi->ehdr.data;
277
278  {
279    /* Construct a fake elf_image sufficient for valid_object.  */
280    struct elf_image ei;
281    ei.image = ehdr;
282    ei.size = sizeof (*ehdr);
283    if (!elf_w(valid_object) (&ei))
284      {
285        Debug(3, "returning, invalid elf object\n");
286        return -UNW_ENOINFO;
287      }
288  }
289
290  // TODO(dje): byteswapping of ehdr values
291  {
292    size_t phdr_size = ehdr->e_phnum * ehdr->e_phentsize;
293    ret = unwi_load_as_contents(as, &edi->phdr, segbase + ehdr->e_phoff, phdr_size, edi->arg);
294    if (ret < 0)
295      {
296        Debug(3, "returning, unwi_load_as_contents failed: %d\n", ret);
297        return ret;
298      }
299    phdr = edi->phdr.data;
300  }
301
302  Debug (3, "scanning phdrs\n");
303
304  for (i = 0; i < ehdr->e_phnum; ++i)
305    {
306      Debug (5, "phdr[%d]: type 0x%x, vaddr 0x%lx, memsz 0x%lx\n",
307             i, phdr[i].p_type, (long) phdr[i].p_vaddr, (long) phdr[i].p_memsz);
308
309      switch (phdr[i].p_type)
310        {
311        case PT_LOAD:
312          if (phdr[i].p_vaddr < start_ip)
313            start_ip = phdr[i].p_vaddr;
314
315          if (phdr[i].p_vaddr + phdr[i].p_memsz > end_ip)
316            end_ip = phdr[i].p_vaddr + phdr[i].p_memsz;
317
318          if (phdr[i].p_offset == mapoff)
319            ptxt = phdr + i;
320
321          break;
322
323        case PT_GNU_EH_FRAME:
324          peh_hdr = phdr + i;
325          ret = unwi_load_as_contents(as, &edi->eh, segbase + peh_hdr->p_vaddr, peh_hdr->p_memsz, edi->arg);
326          if (ret < 0)
327            {
328              Debug(3, "returning, unwi_load_as_contents failed: %d\n", ret);
329              return ret;
330            }
331          break;
332
333        case PT_DYNAMIC:
334          pdyn = phdr + i;
335          ret = unwi_load_as_contents(as, &edi->dyn, segbase + pdyn->p_vaddr, pdyn->p_memsz, edi->arg);
336          if (ret < 0)
337            {
338              Debug(3, "returning, unwi_load_as_contents failed: %d\n", ret);
339              return ret;
340            }
341          break;
342
343#if 0
344#if UNW_TARGET_ARM
345        case PT_ARM_EXIDX:
346          parm_exidx = phdr + i;
347          break;
348#endif
349#endif
350
351        default:
352          break;
353        }
354    }
355
356  Debug (3, "scanning phdrs, ptxt %p\n", ptxt);
357
358  if (!ptxt)
359    {
360      Debug(3, "returning 0, no text\n");
361      return 0;
362    }
363
364  load_base = segbase - ptxt->p_vaddr; // ???
365  start_ip += load_base;
366  end_ip += load_base;
367
368  Debug (3, "load_base 0x%lx, start_ip 0x%lx, end_ip 0x%lx\n",
369         (long) load_base, (long) start_ip, (long) end_ip);
370
371  if (peh_hdr)
372    {
373      if (pdyn)
374        {
375          /* For dynamicly linked executables and shared libraries,
376             DT_PLTGOT is the value that data-relative addresses are
377             relative to for that object.  We call this the "gp".  */
378          Elf_W(Dyn) *dyn = (Elf_W(Dyn) *)(edi->dyn.data);
379          for (; dyn->d_tag != DT_NULL; ++dyn)
380            if (dyn->d_tag == DT_PLTGOT)
381              {
382                /* Assume that _DYNAMIC is writable and GLIBC has
383                   relocated it (true for x86 at least).  */
384                edi->di_cache.gp = dyn->d_un.d_ptr;
385                break;
386              }
387        }
388      else
389        /* Otherwise this is a static executable with no _DYNAMIC.  Assume
390           that data-relative addresses are relative to 0, i.e.,
391           absolute.  */
392        edi->di_cache.gp = 0;
393
394      hdr = (struct dwarf_eh_frame_hdr *) (edi->eh.data);
395      if (hdr->version != DW_EH_VERSION)
396        {
397          Debug (1, "returning, table `%s' has unexpected version %d\n",
398                 path, hdr->version);
399          return -UNW_ENOINFO;
400        }
401
402      addr = (unw_word_t) (hdr + 1);
403
404      /* Fill in a dummy proc_info structure.  We just need to fill in
405         enough to ensure that dwarf_read_encoded_pointer() can do it's
406         job.  Since we don't have a procedure-context at this point, all
407         we have to do is fill in the global-pointer.  */
408      memset (&pi, 0, sizeof (pi));
409      pi.gp = edi->di_cache.gp;
410
411      // We're reading from the .eh_frame we just read in, so it's our local addr space.
412      unw_accessors_t *la = unw_get_accessors (unw_local_addr_space);
413
414      Debug(5, "unw_local_addr_space %p, la %p\n", unw_local_addr_space, la);
415
416      /* (Optionally) read eh_frame_ptr: */
417      if ((ret = dwarf_read_encoded_pointer (unw_local_addr_space, la,
418                                             &addr, hdr->eh_frame_ptr_enc, &pi,
419                                             &eh_frame_start, NULL)) < 0)
420        {
421          Debug(3, "returning, dwarf_read_encoded_pointer failed: %d\n", ret);
422          return -UNW_ENOINFO;
423        }
424
425      /* (Optionally) read fde_count: */
426      if ((ret = dwarf_read_encoded_pointer (unw_local_addr_space, la,
427                                             &addr, hdr->fde_count_enc, &pi,
428                                             &fde_count, NULL)) < 0)
429        {
430          Debug(3, "returning, dwarf_read_encoded_pointer failed: %d\n", ret);
431          return -UNW_ENOINFO;
432        }
433
434      if (hdr->table_enc != (DW_EH_PE_datarel | DW_EH_PE_sdata4))
435        {
436          /* If there is no search table or it has an unsupported
437             encoding, fail.  For now.  */
438          Debug(3, "returning, weird table encoding\n");
439          return -UNW_ENOINFO;
440        }
441
442      //Debug(3, "fde_count %lu \n", (long) fde_count);
443
444      edi->di_cache.start_ip = start_ip;
445      edi->di_cache.end_ip = end_ip;
446      edi->di_cache.format = UNW_INFO_FORMAT_REMOTE_TABLE;
447      edi->di_cache.u.rti.name_ptr = 0;
448      /* two 32-bit values (ip_offset/fde_offset) per table-entry: */
449      edi->di_cache.u.rti.table_len = (fde_count * 8) / sizeof (unw_word_t);
450      edi->di_cache.u.rti.table_data = ((load_base + peh_hdr->p_vaddr)
451                                        + sizeof(*hdr));
452
453      /* For the binary-search table in the eh_frame_hdr, data-relative
454         means relative to the start of that section... */
455      edi->di_cache.u.rti.segbase = (load_base + peh_hdr->p_vaddr);
456      found = 1;
457    }
458
459#if 0
460#if UNW_TARGET_ARM
461  if (parm_exidx)
462    {
463      edi->di_arm.format = UNW_INFO_FORMAT_ARM_EXIDX;
464      edi->di_arm.start_ip = start_ip;
465      edi->di_arm.end_ip = end_ip;
466      edi->di_arm.u.rti.name_ptr = (unw_word_t) path;
467      edi->di_arm.u.rti.table_data = load_base + parm_exidx->p_vaddr;
468      edi->di_arm.u.rti.table_len = parm_exidx->p_memsz;
469      found = 1;
470    }
471#endif
472#endif
473
474#ifdef CONFIG_DEBUG_FRAME
475  /* Try .debug_frame. */
476  found = dwarf_find_debug_frame (found, &edi->di_debug, ip, load_base, path,
477                                  start_ip, end_ip);
478#endif
479
480  Debug(3, "returning, found %d\n", found);
481  return found;
482}
483