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
5 This file is part of libunwind.
6
7 Permission is hereby granted, free of charge, to any person obtaining
8 a copy of this software and associated documentation files (the
9 "Software"), to deal in the Software without restriction, including
10 without limitation the rights to use, copy, modify, merge, publish,
11 distribute, sublicense, and/or sell copies of the Software, and to
12 permit persons to whom the Software is furnished to do so, subject to
13 the following conditions:
14
15 The above copyright notice and this permission notice shall be
16 included in all copies or substantial portions of the Software.
17
18 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
19 EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
20 MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
21 NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
22 LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
23 OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
24 WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
25
26 #include <fcntl.h>
27 #include <string.h>
28 #include <unistd.h>
29 #include <stdint.h>
30
31 #include <sys/mman.h>
32
33 #include "libunwind_i.h"
34 #include "dwarf-eh.h"
35 #include "dwarf_i.h"
36
37 #define to_unw_word(p) ((unw_word_t) (uintptr_t) (p))
38 #ifndef PAGE_SIZE
39 #define PAGE_SIZE 4096
40 #endif
41
42 #ifndef NT_GNU_BUILD_ID
43 #define NT_GNU_BUILD_ID 3
44 #endif
45
46 #ifndef ElfW
47 #define ElfW(type) Elf_##type
48 #endif
49
50 #define ALIGN(val, align) (((val) + (align) - 1) & ~((align) - 1))
51
52 int
dwarf_find_unwind_table(struct elf_dyn_info * edi,struct elf_image * ei,unw_addr_space_t as,char * path,unw_word_t segbase,unw_word_t mapoff,unw_word_t ip)53 dwarf_find_unwind_table (struct elf_dyn_info *edi, struct elf_image *ei,
54 unw_addr_space_t as, char *path,
55 unw_word_t segbase, unw_word_t mapoff, unw_word_t ip)
56 {
57 Elf_W(Phdr) *phdr, *ptxt = NULL, *peh_hdr = NULL, *pdyn = NULL;
58 unw_word_t addr, eh_frame_start, fde_count, load_base;
59 unw_word_t max_load_addr = (unw_word_t)(ei->image) + ei->size;
60 unw_word_t min_load_addr = (unw_word_t)(ei->image);
61 unw_word_t start_ip = to_unw_word (-1);
62 unw_word_t end_ip = 0;
63 struct dwarf_eh_frame_hdr *hdr;
64 unw_proc_info_t pi;
65 unw_accessors_t *a;
66 Elf_W(Ehdr) *ehdr;
67 int first_load_section = 0;
68 int first_load_offset = 0;
69 #if UNW_TARGET_ARM
70 const Elf_W(Phdr) *param_exidx = NULL;
71 #endif
72 int i, ret, found = 0;
73
74 /* XXX: Much of this code is Linux/LSB-specific. */
75 if (!elf_w(valid_object) (ei))
76 return -UNW_ENOINFO;
77
78 if (path == NULL || strlen(path) > PAGE_SIZE || max_load_addr < min_load_addr) {
79 return -UNW_ENOINFO;
80 }
81
82 if (ei->has_dyn_info == 1 &&
83 ei->elf_dyn_info.start_ip <= ip &&
84 ei->elf_dyn_info.end_ip >= ip) {
85 *edi = ei->elf_dyn_info;
86 return 1; // found
87 }
88
89 ehdr = ei->image;
90 phdr = (Elf_W(Phdr) *) ((char *) ei->image + ehdr->e_phoff);
91 if (((unw_word_t)phdr) + sizeof(Elf_W(Phdr)) > max_load_addr ||
92 ((unw_word_t)phdr) + sizeof(Elf_W(Phdr)) < min_load_addr) {
93 return -UNW_ENOINFO;
94 }
95
96 #ifdef PARSE_BUILD_ID
97 struct build_id_note* note;
98 int note_len;
99 size_t note_offset;
100 #endif
101 unsigned long pagesize_alignment_mask = ~(((unsigned long)getpagesize()) - 1UL);
102 for (i = 0; i < ehdr->e_phnum; ++i)
103 {
104 if (((unw_word_t)(&(phdr[i])) + sizeof(Elf_W(Phdr))) > max_load_addr ||
105 ((unw_word_t)(&(phdr[i])) + sizeof(Elf_W(Phdr))) < min_load_addr) {
106 break;
107 }
108
109 switch (phdr[i].p_type)
110 {
111 case PT_LOAD:
112 if ((phdr[i].p_flags & PF_X) == 0) {
113 continue;
114 }
115
116 if (first_load_section == 0) {
117 ei->load_bias = phdr[i].p_vaddr - phdr[i].p_offset;
118 first_load_section = 1;
119 }
120
121 if (phdr[i].p_vaddr < start_ip)
122 start_ip = phdr[i].p_vaddr;
123
124 if (phdr[i].p_vaddr + phdr[i].p_memsz > end_ip)
125 end_ip = phdr[i].p_vaddr + phdr[i].p_memsz;
126
127 if ((phdr[i].p_offset & (-PAGE_SIZE)) == mapoff)
128 ptxt = phdr + i;
129
130 if (first_load_offset == 0) {
131 if ((phdr[i].p_offset & pagesize_alignment_mask) == mapoff) {
132 ei->load_offset = phdr[i].p_vaddr - (phdr[i].p_offset & (~pagesize_alignment_mask));
133 first_load_offset = 1;
134 } else {
135 ei->load_offset = 0;
136 }
137 }
138 break;
139 case PT_GNU_EH_FRAME:
140 #if defined __sun
141 case PT_SUNW_UNWIND:
142 #endif
143 peh_hdr = phdr + i;
144 break;
145
146 case PT_DYNAMIC:
147 pdyn = phdr + i;
148 break;
149
150 #if UNW_TARGET_ARM
151 case PT_ARM_EXIDX:
152 param_exidx = phdr + i;
153 break;
154 #endif
155 #ifdef PARSE_BUILD_ID
156 case PT_NOTE: {
157 note = (void *)(ei->image + phdr[i].p_offset);
158 note_len = phdr[i].p_filesz;
159 if (((unw_word_t)note + note_len) > max_load_addr ||
160 ((unw_word_t)note + note_len) < min_load_addr) {
161 Dprintf("Target Note Section is not valid:%s\n", path);
162 break;
163 }
164
165 while (note_len >= (int)(sizeof(struct build_id_note))) {
166 if (note->nhdr.n_type == NT_GNU_BUILD_ID &&
167 note->nhdr.n_descsz != 0 &&
168 note->nhdr.n_namesz == 4 &&
169 memcmp(note->name, "GNU", 4) == 0) {
170 ei->build_id_note = note;
171 break;
172 }
173
174 note_offset = sizeof(ElfW(Nhdr)) +
175 ALIGN(note->nhdr.n_namesz, 4) +
176 ALIGN(note->nhdr.n_descsz, 4);
177 // 05 00 00 00 04 00 00 00 4f 48 4f 53 00 01 00 00 00 00 00 00
178 if (note->nhdr.n_type == 0x534f484f && note_len > 20) {
179 // .note.ohos.ident is not a valid PT_NOTE section, use offset in section header later
180 note_offset = 20;
181 }
182 note = (struct build_id_note*)((char *)note + note_offset);
183 note_len -= note_offset;
184 }
185 }
186 break;
187 #endif
188 default:
189 break;
190 }
191 }
192
193 if (!ptxt)
194 return 0;
195
196 load_base = segbase - (ptxt->p_vaddr & (-PAGE_SIZE));
197 start_ip += load_base;
198 end_ip += load_base;
199
200 if (peh_hdr)
201 {
202 if (pdyn)
203 {
204 /* For dynamicly linked executables and shared libraries,
205 DT_PLTGOT is the value that data-relative addresses are
206 relative to for that object. We call this the "gp". */
207 Elf_W(Dyn) *dyn = (Elf_W(Dyn) *)(pdyn->p_offset + (char *) ei->image);
208 while (((unw_word_t)dyn + sizeof(Elf_W(Dyn))) <= max_load_addr &&
209 ((unw_word_t)dyn + sizeof(Elf_W(Dyn))) >= min_load_addr && dyn->d_tag != DT_NULL) {
210 if (dyn->d_tag == DT_PLTGOT) {
211 /* Assume that _DYNAMIC is writable and GLIBC has
212 relocated it (true for x86 at least). */
213 edi->di_cache.gp = dyn->d_un.d_ptr;
214 } else if (dyn->d_tag == DT_SONAME) {
215 ei->lib_name_offset = dyn->d_un.d_val;
216 }
217 ++dyn;
218 }
219 }
220 else
221 /* Otherwise this is a static executable with no _DYNAMIC. Assume
222 that data-relative addresses are relative to 0, i.e.,
223 absolute. */
224 edi->di_cache.gp = 0;
225
226 hdr = (struct dwarf_eh_frame_hdr *) (peh_hdr->p_offset
227 + (char *) ei->image);
228 if (((unw_word_t)hdr + sizeof(struct dwarf_eh_frame_hdr)) > max_load_addr ||
229 ((unw_word_t)hdr + sizeof(struct dwarf_eh_frame_hdr)) < min_load_addr) {
230 return -UNW_ENOINFO;
231 }
232
233 if (hdr->version != DW_EH_VERSION)
234 {
235 if (path != NULL) {
236 Debug (1, "table `%s' has unexpected version %d\n", path, hdr->version);
237 }
238 return -UNW_ENOINFO;
239 }
240
241 a = unw_get_accessors_int (unw_local_addr_space);
242 addr = to_unw_word (&hdr->eh_frame);
243
244 /* Fill in a dummy proc_info structure. We just need to fill in
245 enough to ensure that dwarf_read_encoded_pointer() can do it's
246 job. Since we don't have a procedure-context at this point, all
247 we have to do is fill in the global-pointer. */
248 memset (&pi, 0, sizeof (pi));
249 pi.gp = edi->di_cache.gp;
250
251 /* (Optionally) read eh_frame_ptr: */
252 if ((ret = dwarf_read_encoded_pointer (unw_local_addr_space, a,
253 &addr, hdr->eh_frame_ptr_enc, &pi,
254 &eh_frame_start, NULL)) < 0)
255 return -UNW_ENOINFO;
256
257 /* (Optionally) read fde_count: */
258 if ((ret = dwarf_read_encoded_pointer (unw_local_addr_space, a,
259 &addr, hdr->fde_count_enc, &pi,
260 &fde_count, NULL)) < 0)
261 return -UNW_ENOINFO;
262
263 if (hdr->table_enc != (DW_EH_PE_datarel | DW_EH_PE_sdata4))
264 {
265 #if 0
266 abort ();
267
268 unw_word_t eh_frame_end;
269
270 /* If there is no search table or it has an unsupported
271 encoding, fall back on linear search. */
272 if (hdr->table_enc == DW_EH_PE_omit)
273 Debug (4, "EH lacks search table; doing linear search\n");
274 else
275 Debug (4, "EH table has encoding 0x%x; doing linear search\n",
276 hdr->table_enc);
277
278 eh_frame_end = max_load_addr; /* XXX can we do better? */
279
280 if (hdr->fde_count_enc == DW_EH_PE_omit)
281 fde_count = ~0UL;
282 if (hdr->eh_frame_ptr_enc == DW_EH_PE_omit)
283 abort ();
284
285 return linear_search (unw_local_addr_space, ip,
286 eh_frame_start, eh_frame_end, fde_count,
287 pi, need_unwind_info, NULL);
288 #endif
289 }
290
291 edi->di_cache.start_ip = start_ip;
292 edi->di_cache.end_ip = end_ip;
293 edi->di_cache.load_offset = 0;
294 edi->di_cache.format = UNW_INFO_FORMAT_REMOTE_TABLE;
295 edi->di_cache.u.rti.name_ptr = 0;
296 /* two 32-bit values (ip_offset/fde_offset) per table-entry: */
297 edi->di_cache.u.rti.table_len = (fde_count * 8) / sizeof (unw_word_t);
298 edi->di_cache.u.rti.table_data = ((load_base + peh_hdr->p_vaddr)
299 + (addr - (unw_word_t) ei->image
300 - peh_hdr->p_offset));
301
302 /* For the binary-search table in the eh_frame_hdr, data-relative
303 means relative to the start of that section... */
304 /* Add For Cache MAP And ELF */
305 edi->di_cache.u.rti.segbase = ((load_base + peh_hdr->p_vaddr)
306 + ((unw_word_t) hdr - (unw_word_t) ei->image
307 - peh_hdr->p_offset));
308 /* Add For Cache MAP And ELF */
309 found = 1;
310 }
311
312 #if UNW_TARGET_ARM
313 if (param_exidx)
314 {
315 edi->di_arm.format = UNW_INFO_FORMAT_ARM_EXIDX;
316 edi->di_arm.start_ip = start_ip;
317 edi->di_arm.end_ip = end_ip;
318 edi->di_arm.u.rti.name_ptr = to_unw_word (path);
319 edi->di_arm.u.rti.table_data = load_base + param_exidx->p_vaddr;
320 edi->di_arm.u.rti.table_len = param_exidx->p_memsz;
321 found = 1;
322 }
323 #endif
324
325 #ifdef CONFIG_DEBUG_FRAME
326 /* Try .debug_frame. */
327 found = dwarf_find_debug_frame (found, &edi->di_debug, ip, load_base, path,
328 start_ip, end_ip);
329 #endif
330 if (found == 1) {
331 edi->start_ip = start_ip;
332 edi->end_ip = end_ip;
333 ei->elf_dyn_info = *edi;
334 ei->has_dyn_info = 1;
335 }
336 return found;
337 }
338