1/* Get CFI from ELF file's exception-handling info.
2   Copyright (C) 2009-2010 Red Hat, Inc.
3   This file is part of Red Hat elfutils.
4
5   Red Hat elfutils is free software; you can redistribute it and/or modify
6   it under the terms of the GNU General Public License as published by the
7   Free Software Foundation; version 2 of the License.
8
9   Red Hat elfutils is distributed in the hope that it will be useful, but
10   WITHOUT ANY WARRANTY; without even the implied warranty of
11   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
12   General Public License for more details.
13
14   You should have received a copy of the GNU General Public License along
15   with Red Hat elfutils; if not, write to the Free Software Foundation,
16   Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301 USA.
17
18   In addition, as a special exception, Red Hat, Inc. gives You the
19   additional right to link the code of Red Hat elfutils with code licensed
20   under any Open Source Initiative certified open source license
21   (http://www.opensource.org/licenses/index.php) which requires the
22   distribution of source code with any binary distribution and to
23   distribute linked combinations of the two.  Non-GPL Code permitted under
24   this exception must only link to the code of Red Hat elfutils through
25   those well defined interfaces identified in the file named EXCEPTION
26   found in the source code files (the "Approved Interfaces").  The files
27   of Non-GPL Code may instantiate templates or use macros or inline
28   functions from the Approved Interfaces without causing the resulting
29   work to be covered by the GNU General Public License.  Only Red Hat,
30   Inc. may make changes or additions to the list of Approved Interfaces.
31   Red Hat's grant of this exception is conditioned upon your not adding
32   any new exceptions.  If you wish to add a new Approved Interface or
33   exception, please contact Red Hat.  You must obey the GNU General Public
34   License in all respects for all of the Red Hat elfutils code and other
35   code used in conjunction with Red Hat elfutils except the Non-GPL Code
36   covered by this exception.  If you modify this file, you may extend this
37   exception to your version of the file, but you are not obligated to do
38   so.  If you do not wish to provide this exception without modification,
39   you must delete this exception statement from your version and license
40   this file solely under the GPL without exception.
41
42   Red Hat elfutils is an included package of the Open Invention Network.
43   An included package of the Open Invention Network is a package for which
44   Open Invention Network licensees cross-license their patents.  No patent
45   license is granted, either expressly or impliedly, by designation as an
46   included package.  Should you wish to participate in the Open Invention
47   Network licensing program, please visit www.openinventionnetwork.com
48   <http://www.openinventionnetwork.com>.  */
49
50#ifdef HAVE_CONFIG_H
51# include <config.h>
52#endif
53
54#include <stdlib.h>
55#include <string.h>
56#include <assert.h>
57
58#include "libdwP.h"
59#include "cfi.h"
60#include "encoded-value.h"
61#include <dwarf.h>
62
63
64static Dwarf_CFI *
65allocate_cfi (Elf *elf, GElf_Addr vaddr)
66{
67  Dwarf_CFI *cfi = calloc (1, sizeof *cfi);
68  if (cfi == NULL)
69    {
70      __libdw_seterrno (DWARF_E_NOMEM);
71      return NULL;
72    }
73
74  cfi->e_ident = (unsigned char *) elf_getident (elf, NULL);
75  if (cfi->e_ident == NULL)
76    {
77      free (cfi);
78      __libdw_seterrno (DWARF_E_GETEHDR_ERROR);
79      return NULL;
80    }
81
82  if ((BYTE_ORDER == LITTLE_ENDIAN && cfi->e_ident[EI_DATA] == ELFDATA2MSB)
83      || (BYTE_ORDER == BIG_ENDIAN && cfi->e_ident[EI_DATA] == ELFDATA2LSB))
84    cfi->other_byte_order = true;
85
86  cfi->frame_vaddr = vaddr;
87  cfi->textrel = 0;		/* XXX ? */
88  cfi->datarel = 0;		/* XXX ? */
89
90  return cfi;
91}
92
93static const uint8_t *
94parse_eh_frame_hdr (const uint8_t *hdr, size_t hdr_size, GElf_Addr hdr_vaddr,
95		    const GElf_Ehdr *ehdr, GElf_Addr *eh_frame_vaddr,
96		    size_t *table_entries, uint8_t *table_encoding)
97{
98  const uint8_t *h = hdr;
99
100  if (*h++ != 1)		/* version */
101    return (void *) -1l;
102
103  uint8_t eh_frame_ptr_encoding = *h++;
104  uint8_t fde_count_encoding = *h++;
105  uint8_t fde_table_encoding = *h++;
106
107  if (eh_frame_ptr_encoding == DW_EH_PE_omit)
108    return (void *) -1l;
109
110  /* Dummy used by read_encoded_value.  */
111  Elf_Data_Scn dummy_cfi_hdr_data =
112    {
113      .d = { .d_buf = (void *) hdr, .d_size = hdr_size }
114    };
115  Dwarf_CFI dummy_cfi =
116    {
117      .e_ident = ehdr->e_ident,
118      .datarel = hdr_vaddr,
119      .frame_vaddr = hdr_vaddr,
120      .data = &dummy_cfi_hdr_data,
121    };
122
123  if (unlikely (read_encoded_value (&dummy_cfi, eh_frame_ptr_encoding, &h,
124				    eh_frame_vaddr)))
125    return (void *) -1l;
126
127  if (fde_count_encoding != DW_EH_PE_omit)
128    {
129      Dwarf_Word fde_count;
130      if (unlikely (read_encoded_value (&dummy_cfi, fde_count_encoding, &h,
131					&fde_count)))
132	return (void *) -1l;
133      if (fde_count != 0 && (size_t) fde_count == fde_count
134	  && fde_table_encoding != DW_EH_PE_omit
135	  && (fde_table_encoding &~ DW_EH_PE_signed) != DW_EH_PE_uleb128)
136	{
137	  *table_entries = fde_count;
138	  *table_encoding = fde_table_encoding;
139	  return h;
140	}
141    }
142
143  return NULL;
144}
145
146static Dwarf_CFI *
147getcfi_gnu_eh_frame (Elf *elf, const GElf_Ehdr *ehdr, const GElf_Phdr *phdr)
148{
149  if (unlikely (phdr->p_filesz < 4))
150    goto invalid;
151
152  Elf_Data *data = elf_getdata_rawchunk (elf, phdr->p_offset, phdr->p_filesz,
153					 ELF_T_BYTE);
154  if (data == NULL)
155    {
156    invalid_hdr:
157    invalid:
158      /* XXX might be read error or corrupt phdr */
159      __libdw_seterrno (DWARF_E_INVALID_CFI);
160      return NULL;
161    }
162
163  Dwarf_Addr eh_frame_ptr;
164  size_t search_table_entries;
165  uint8_t search_table_encoding;
166  const uint8_t *search_table = parse_eh_frame_hdr (data->d_buf, phdr->p_filesz,
167						    phdr->p_vaddr, ehdr,
168						    &eh_frame_ptr,
169						    &search_table_entries,
170						    &search_table_encoding);
171  if (search_table == (void *) -1l)
172    goto invalid_hdr;
173
174  Dwarf_Off eh_frame_offset = eh_frame_ptr - phdr->p_vaddr + phdr->p_offset;
175  Dwarf_Word eh_frame_size = 0;
176
177  /* XXX we have no way without section headers to know the size
178     of the .eh_frame data.  Calculate the largest it might possibly be.
179     This won't be wasteful if the file is already mmap'd, but if it isn't
180     it might be quite excessive.  */
181  size_t filesize;
182  if (elf_rawfile (elf, &filesize) != NULL)
183    eh_frame_size = filesize - eh_frame_offset;
184
185  data = elf_getdata_rawchunk (elf, eh_frame_offset, eh_frame_size, ELF_T_BYTE);
186  if (data == NULL)
187    {
188      __libdw_seterrno (DWARF_E_INVALID_ELF); /* XXX might be read error */
189      return NULL;
190    }
191  Dwarf_CFI *cfi = allocate_cfi (elf, eh_frame_ptr);
192  if (cfi != NULL)
193    {
194      cfi->data = (Elf_Data_Scn *) data;
195
196      if (search_table != NULL)
197	{
198	  cfi->search_table = search_table;
199	  cfi->search_table_vaddr = phdr->p_vaddr;
200	  cfi->search_table_encoding = search_table_encoding;
201	  cfi->search_table_entries = search_table_entries;
202	}
203    }
204  return cfi;
205}
206
207/* Search the phdrs for PT_GNU_EH_FRAME.  */
208static Dwarf_CFI *
209getcfi_phdr (Elf *elf, const GElf_Ehdr *ehdr)
210{
211  size_t phnum;
212  if (unlikely (elf_getphdrnum (elf, &phnum) != 0))
213    return NULL;
214
215  for (size_t i = 0; i < phnum; ++i)
216    {
217      GElf_Phdr phdr_mem;
218      GElf_Phdr *phdr = gelf_getphdr (elf, i, &phdr_mem);
219      if (unlikely (phdr == NULL))
220	return NULL;
221      if (phdr->p_type == PT_GNU_EH_FRAME)
222	return getcfi_gnu_eh_frame (elf, ehdr, phdr);
223    }
224
225  __libdw_seterrno (DWARF_E_NO_DWARF);
226  return NULL;
227}
228
229static Dwarf_CFI *
230getcfi_scn_eh_frame (Elf *elf, const GElf_Ehdr *ehdr,
231		     Elf_Scn *scn, GElf_Shdr *shdr,
232		     Elf_Scn *hdr_scn, GElf_Addr hdr_vaddr)
233{
234  Elf_Data *data = elf_rawdata (scn, NULL);
235  if (data == NULL)
236    {
237      __libdw_seterrno (DWARF_E_INVALID_ELF);
238      return NULL;
239    }
240  Dwarf_CFI *cfi = allocate_cfi (elf, shdr->sh_addr);
241  if (cfi != NULL)
242    {
243      cfi->data = (Elf_Data_Scn *) data;
244      if (hdr_scn != NULL)
245	{
246	  Elf_Data *hdr_data = elf_rawdata (hdr_scn, NULL);
247	  if (hdr_data != NULL)
248	    {
249	      GElf_Addr eh_frame_vaddr;
250	      cfi->search_table_vaddr = hdr_vaddr;
251	      cfi->search_table
252		= parse_eh_frame_hdr (hdr_data->d_buf, hdr_data->d_size,
253				      hdr_vaddr, ehdr, &eh_frame_vaddr,
254				      &cfi->search_table_entries,
255				      &cfi->search_table_encoding);
256	      if (cfi->search_table == (void *) -1l)
257		{
258		  free (cfi);
259		  /* XXX might be read error or corrupt phdr */
260		  __libdw_seterrno (DWARF_E_INVALID_CFI);
261		  return NULL;
262		}
263
264	      /* Sanity check.  */
265	      if (unlikely (eh_frame_vaddr != shdr->sh_addr))
266		cfi->search_table = NULL;
267	    }
268	}
269    }
270  return cfi;
271}
272
273/* Search for the sections named ".eh_frame" and ".eh_frame_hdr".  */
274static Dwarf_CFI *
275getcfi_shdr (Elf *elf, const GElf_Ehdr *ehdr)
276{
277  size_t shstrndx;
278  if (elf_getshdrstrndx (elf, &shstrndx) != 0)
279    {
280      __libdw_seterrno (DWARF_E_GETEHDR_ERROR);
281      return NULL;
282    }
283
284  if (shstrndx != 0)
285    {
286      Elf_Scn *hdr_scn = NULL;
287      GElf_Addr hdr_vaddr = 0;
288      Elf_Scn *scn = NULL;
289      while ((scn = elf_nextscn (elf, scn)) != NULL)
290	{
291	  GElf_Shdr shdr_mem;
292	  GElf_Shdr *shdr = gelf_getshdr (scn, &shdr_mem);
293	  if (shdr == NULL)
294	    continue;
295	  const char *name = elf_strptr (elf, shstrndx, shdr->sh_name);
296	  if (name == NULL)
297	    continue;
298	  if (!strcmp (name, ".eh_frame_hdr"))
299	    {
300	      hdr_scn = scn;
301	      hdr_vaddr = shdr->sh_addr;
302	    }
303	  else if (!strcmp (name, ".eh_frame"))
304	    return getcfi_scn_eh_frame (elf, ehdr, scn, shdr,
305					hdr_scn, hdr_vaddr);
306	}
307    }
308
309  return (void *) -1l;
310}
311
312Dwarf_CFI *
313dwarf_getcfi_elf (elf)
314     Elf *elf;
315{
316  if (elf_kind (elf) != ELF_K_ELF)
317    {
318      __libdw_seterrno (DWARF_E_NOELF);
319      return NULL;
320    }
321
322  GElf_Ehdr ehdr_mem;
323  GElf_Ehdr *ehdr = gelf_getehdr (elf, &ehdr_mem);
324  if (unlikely (ehdr == NULL))
325    {
326      __libdw_seterrno (DWARF_E_INVALID_ELF);
327      return NULL;
328    }
329
330  Dwarf_CFI *result = getcfi_shdr (elf, ehdr);
331  if (result == (void *) -1l)
332    result = getcfi_phdr (elf, ehdr);
333
334  return result;
335}
336INTDEF (dwarf_getcfi_elf)
337