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