elfxx.c revision 96c1bca2b08f147011814b1b7be6863151ef0be9
1/* libunwind - a platform-independent unwind library
2   Copyright (C) 2003-2005 Hewlett-Packard Co
3   Copyright (C) 2007 David Mosberger-Tang
4	Contributed by David Mosberger-Tang <dmosberger@gmail.com>
5
6This file is part of libunwind.
7
8Permission is hereby granted, free of charge, to any person obtaining
9a copy of this software and associated documentation files (the
10"Software"), to deal in the Software without restriction, including
11without limitation the rights to use, copy, modify, merge, publish,
12distribute, sublicense, and/or sell copies of the Software, and to
13permit persons to whom the Software is furnished to do so, subject to
14the following conditions:
15
16The above copyright notice and this permission notice shall be
17included in all copies or substantial portions of the Software.
18
19THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
20EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
21MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
22NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
23LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
24OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
25WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.  */
26
27#include "libunwind_i.h"
28
29#include <stdio.h>
30#include <sys/param.h>
31
32#ifdef HAVE_LZMA
33#include <lzma.h>
34#endif /* HAVE_LZMA */
35
36static Elf_W (Shdr)*
37elf_w (section_table) (struct elf_image *ei)
38{
39  Elf_W (Ehdr) *ehdr = ei->image;
40  Elf_W (Off) soff;
41
42  soff = ehdr->e_shoff;
43  if (soff + ehdr->e_shnum * ehdr->e_shentsize > ei->size)
44    {
45      Debug (1, "section table outside of image? (%lu > %lu)\n",
46	     (unsigned long) (soff + ehdr->e_shnum * ehdr->e_shentsize),
47	     (unsigned long) ei->size);
48      return NULL;
49    }
50
51  return (Elf_W (Shdr) *) ((char *) ei->image + soff);
52}
53
54static char*
55elf_w (string_table) (struct elf_image *ei, int section)
56{
57  Elf_W (Ehdr) *ehdr = ei->image;
58  Elf_W (Off) soff, str_soff;
59  Elf_W (Shdr) *str_shdr;
60
61  /* this offset is assumed to be OK */
62  soff = ehdr->e_shoff;
63
64  str_soff = soff + (section * ehdr->e_shentsize);
65  if (str_soff + ehdr->e_shentsize > ei->size)
66    {
67      Debug (1, "string shdr table outside of image? (%lu > %lu)\n",
68	     (unsigned long) (str_soff + ehdr->e_shentsize),
69	     (unsigned long) ei->size);
70      return NULL;
71    }
72  str_shdr = (Elf_W (Shdr) *) ((char *) ei->image + str_soff);
73
74  if (str_shdr->sh_offset + str_shdr->sh_size > ei->size)
75    {
76      Debug (1, "string table outside of image? (%lu > %lu)\n",
77	     (unsigned long) (str_shdr->sh_offset + str_shdr->sh_size),
78	     (unsigned long) ei->size);
79      return NULL;
80    }
81
82  Debug (16, "strtab=0x%lx\n", (long) str_shdr->sh_offset);
83  return ei->image + str_shdr->sh_offset;
84}
85
86static int
87elf_w (lookup_symbol) (unw_addr_space_t as,
88		       unw_word_t ip, struct elf_image *ei,
89		       Elf_W (Addr) load_offset,
90		       char *buf, size_t buf_len, Elf_W (Addr) *min_dist)
91{
92  size_t syment_size;
93  Elf_W (Ehdr) *ehdr = ei->image;
94  Elf_W (Sym) *sym, *symtab, *symtab_end;
95  Elf_W (Shdr) *shdr;
96  Elf_W (Addr) val;
97  int i, ret = -UNW_ENOINFO;
98  char *strtab;
99
100  if (!elf_w (valid_object) (ei))
101    return -UNW_ENOINFO;
102
103  shdr = elf_w (section_table) (ei);
104  if (!shdr)
105    return -UNW_ENOINFO;
106
107  for (i = 0; i < ehdr->e_shnum; ++i)
108    {
109      switch (shdr->sh_type)
110	{
111	case SHT_SYMTAB:
112	case SHT_DYNSYM:
113	  symtab = (Elf_W (Sym) *) ((char *) ei->image + shdr->sh_offset);
114	  symtab_end = (Elf_W (Sym) *) ((char *) symtab + shdr->sh_size);
115	  syment_size = shdr->sh_entsize;
116
117	  strtab = elf_w (string_table) (ei, shdr->sh_link);
118	  if (!strtab)
119	    break;
120
121	  Debug (16, "symtab=0x%lx[%d]\n",
122		 (long) shdr->sh_offset, shdr->sh_type);
123
124	  for (sym = symtab;
125	       sym < symtab_end;
126	       sym = (Elf_W (Sym) *) ((char *) sym + syment_size))
127	    {
128	      if (ELF_W (ST_TYPE) (sym->st_info) == STT_FUNC
129		  && sym->st_shndx != SHN_UNDEF)
130		{
131		  if (tdep_get_func_addr (as, sym->st_value, &val) < 0)
132		    continue;
133		  if (sym->st_shndx != SHN_ABS)
134		    val += load_offset;
135		  Debug (16, "0x%016lx info=0x%02x %s\n",
136			 (long) val, sym->st_info, strtab + sym->st_name);
137
138		  if ((Elf_W (Addr)) (ip - val) < *min_dist)
139		    {
140		      *min_dist = (Elf_W (Addr)) (ip - val);
141		      strncpy (buf, strtab + sym->st_name, buf_len);
142		      buf[buf_len - 1] = '\0';
143		      ret = (strlen (strtab + sym->st_name) >= buf_len
144			     ? -UNW_ENOMEM : 0);
145		    }
146		}
147	    }
148	  break;
149
150	default:
151	  break;
152	}
153      shdr = (Elf_W (Shdr) *) (((char *) shdr) + ehdr->e_shentsize);
154    }
155  return ret;
156}
157
158static Elf_W (Addr)
159elf_w (get_load_offset) (struct elf_image *ei, unsigned long segbase,
160			 unsigned long mapoff)
161{
162  Elf_W (Addr) offset = 0;
163  Elf_W (Ehdr) *ehdr;
164  Elf_W (Phdr) *phdr;
165  int i;
166
167  ehdr = ei->image;
168  phdr = (Elf_W (Phdr) *) ((char *) ei->image + ehdr->e_phoff);
169
170  for (i = 0; i < ehdr->e_phnum; ++i)
171    if (phdr[i].p_type == PT_LOAD && phdr[i].p_offset == mapoff)
172      {
173	offset = segbase - phdr[i].p_vaddr;
174	break;
175      }
176
177  return offset;
178}
179
180#if HAVE_LZMA
181static size_t
182xz_uncompressed_size (uint8_t *compressed, size_t length)
183{
184  uint64_t memlimit = UINT64_MAX;
185  size_t ret = 0, pos = 0;
186  lzma_stream_flags options;
187  lzma_index *index;
188
189  if (length < LZMA_STREAM_HEADER_SIZE)
190    return 0;
191
192  uint8_t *footer = compressed + length - LZMA_STREAM_HEADER_SIZE;
193  if (lzma_stream_footer_decode (&options, footer) != LZMA_OK)
194    return 0;
195
196  if (length < LZMA_STREAM_HEADER_SIZE + options.backward_size)
197    return 0;
198
199  uint8_t *indexdata = footer - options.backward_size;
200  if (lzma_index_buffer_decode (&index, &memlimit, NULL, indexdata,
201				&pos, options.backward_size) != LZMA_OK)
202    return 0;
203
204  if (lzma_index_size (index) == options.backward_size)
205    {
206      ret = lzma_index_uncompressed_size (index);
207    }
208
209  lzma_index_end (index, NULL);
210  return ret;
211}
212
213static int
214elf_w (extract_minidebuginfo) (struct elf_image *ei, struct elf_image *mdi)
215{
216  Elf_W (Ehdr) *ehdr = ei->image;
217  Elf_W (Shdr) *shdr;
218  char *strtab;
219  int i;
220  uint8_t *compressed = NULL;
221  uint64_t memlimit = UINT64_MAX; /* no memory limit */
222  size_t compressed_len, uncompressed_len;
223
224  if (!elf_w (valid_object) (ei))
225    return 0;
226
227  shdr = elf_w (section_table) (ei);
228  if (!shdr)
229    return 0;
230
231  strtab = elf_w (string_table) (ei, ehdr->e_shstrndx);
232  if (!strtab)
233    return 0;
234
235  for (i = 0; i < ehdr->e_shnum; ++i)
236    {
237      if (strcmp (strtab + shdr->sh_name, ".gnu_debugdata") == 0)
238	{
239	  if (shdr->sh_offset + shdr->sh_size > ei->size)
240	    {
241	      Debug (1, ".gnu_debugdata outside image? (0x%lu > 0x%lu)\n",
242		     (unsigned long) shdr->sh_offset + shdr->sh_size,
243		     (unsigned long) ei->size);
244	      return 0;
245	    }
246
247	  Debug (16, "found .gnu_debugdata at 0x%lx\n",
248		 (unsigned long) shdr->sh_offset);
249	  compressed = ((uint8_t *) ei->image) + shdr->sh_offset;
250	  compressed_len = shdr->sh_size;
251	  break;
252	}
253
254      shdr = (Elf_W (Shdr) *) (((char *) shdr) + ehdr->e_shentsize);
255    }
256
257  /* not found */
258  if (!compressed)
259    return 0;
260
261  uncompressed_len = xz_uncompressed_size (compressed, compressed_len);
262  if (uncompressed_len == 0)
263    {
264      Debug (1, "invalid .gnu_debugdata contents\n");
265      return 0;
266    }
267
268  mdi->size = uncompressed_len;
269  mdi->image = mmap (NULL, uncompressed_len, PROT_READ|PROT_WRITE,
270		     MAP_PRIVATE|MAP_ANONYMOUS, -1, 0);
271
272  if (mdi->image == MAP_FAILED)
273    return 0;
274
275  size_t in_pos = 0, out_pos = 0;
276  lzma_ret lret;
277  lret = lzma_stream_buffer_decode (&memlimit, 0, NULL,
278				    compressed, &in_pos, compressed_len,
279				    mdi->image, &out_pos, mdi->size);
280  if (lret != LZMA_OK)
281    {
282      Debug (1, "LZMA decompression failed: %d\n", lret);
283      munmap (mdi->image, mdi->size);
284      return 0;
285    }
286
287  return 1;
288}
289#else
290static int
291elf_w (extract_minidebuginfo) (struct elf_image *ei, struct elf_image *mdi)
292{
293  return 0;
294}
295#endif /* !HAVE_LZMA */
296
297/* Find the ELF image that contains IP and return the "closest"
298   procedure name, if there is one.  With some caching, this could be
299   sped up greatly, but until an application materializes that's
300   sensitive to the performance of this routine, why bother...  */
301
302HIDDEN int
303elf_w (get_proc_name_in_image) (unw_addr_space_t as, struct elf_image *ei,
304		       unsigned long segbase,
305		       unsigned long mapoff,
306		       unw_word_t ip,
307		       char *buf, size_t buf_len, unw_word_t *offp)
308{
309  Elf_W (Addr) load_offset;
310  Elf_W (Addr) min_dist = ~(Elf_W (Addr))0;
311  int ret;
312
313  load_offset = elf_w (get_load_offset) (ei, segbase, mapoff);
314  ret = elf_w (lookup_symbol) (as, ip, ei, load_offset, buf, buf_len, &min_dist);
315
316  /* If the ELF image has MiniDebugInfo embedded in it, look up the symbol in
317     there as well and replace the previously found if it is closer. */
318  struct elf_image mdi;
319  if (elf_w (extract_minidebuginfo) (ei, &mdi))
320    {
321      int ret_mdi;
322
323      load_offset = elf_w (get_load_offset) (&mdi, segbase, mapoff);
324      ret_mdi = elf_w (lookup_symbol) (as, ip, &mdi, load_offset, buf,
325				       buf_len, &min_dist);
326
327      /* Closer symbol was found (possibly truncated). */
328      if (ret_mdi == 0 || ret_mdi == -UNW_ENOMEM)
329	{
330	  ret = ret_mdi;
331	}
332
333      munmap (mdi.image, mdi.size);
334    }
335
336  if (min_dist >= ei->size)
337    return -UNW_ENOINFO;		/* not found */
338  if (offp)
339    *offp = min_dist;
340  return ret;
341}
342
343HIDDEN int
344elf_w (get_proc_name) (unw_addr_space_t as, pid_t pid, unw_word_t ip,
345		       char *buf, size_t buf_len, unw_word_t *offp)
346{
347  unsigned long segbase, mapoff;
348  struct elf_image ei;
349  int ret;
350
351  ret = tdep_get_elf_image (&ei, pid, ip, &segbase, &mapoff, NULL, 0);
352  if (ret < 0)
353    return ret;
354
355  ret = elf_w (get_proc_name_in_image) (as, &ei, segbase, mapoff, ip, buf, buf_len, offp);
356
357  munmap (ei.image, ei.size);
358  ei.image = NULL;
359
360  return ret;
361}
362