| 1 | /* libunwind - a platform-independent unwind library |
| 2 | Copyright (c) 2003-2005 Hewlett-Packard Development Company, L.P. |
| 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 | /* Locate an FDE via the ELF data-structures defined by LSB v1.3 |
| 27 | (http://www.linuxbase.org/spec/). */ |
| 28 | |
| 29 | #include <stddef.h> |
| 30 | #include <stdio.h> |
| 31 | #include <limits.h> |
| 32 | |
| 33 | #include "dwarf_i.h" |
| 34 | #include "dwarf-eh.h" |
| 35 | #include "libunwind_i.h" |
| 36 | |
| 37 | struct table_entry |
| 38 | { |
| 39 | int32_t start_ip_offset; |
| 40 | int32_t fde_offset; |
| 41 | }; |
| 42 | |
| 43 | #ifndef UNW_REMOTE_ONLY |
| 44 | |
| 45 | #ifdef __linux |
| 46 | #include "os-linux.h" |
| 47 | #endif |
| 48 | |
| 49 | static ALIAS(dwarf_search_unwind_table) int |
| 50 | dwarf_search_unwind_table_int (unw_addr_space_t as, |
| 51 | unw_word_t ip, |
| 52 | unw_dyn_info_t *di, |
| 53 | unw_proc_info_t *pi, |
| 54 | int need_unwind_info, void *arg); |
| 55 | static int |
| 56 | linear_search (unw_addr_space_t as, unw_word_t ip, |
| 57 | unw_word_t eh_frame_start, unw_word_t eh_frame_end, |
| 58 | unw_word_t fde_count, |
| 59 | unw_proc_info_t *pi, int need_unwind_info, void *arg) |
| 60 | { |
| 61 | unw_accessors_t *a = unw_get_accessors_int (unw_local_addr_space); |
| 62 | unw_word_t i = 0, fde_addr, addr = eh_frame_start; |
| 63 | int ret; |
| 64 | |
| 65 | while (i++ < fde_count && addr < eh_frame_end) |
| 66 | { |
| 67 | fde_addr = addr; |
| 68 | if ((ret = dwarf_extract_proc_info_from_fde (as, a, &addr, pi, |
| 69 | eh_frame_start, |
| 70 | 0, 0, arg)) < 0) |
| 71 | return ret; |
| 72 | |
| 73 | if (ip >= pi->start_ip && ip < pi->end_ip) |
| 74 | { |
| 75 | if (!need_unwind_info) |
| 76 | return 1; |
| 77 | addr = fde_addr; |
| 78 | if ((ret = dwarf_extract_proc_info_from_fde (as, a, &addr, pi, |
| 79 | eh_frame_start, |
| 80 | need_unwind_info, 0, |
| 81 | arg)) |
| 82 | < 0) |
| 83 | return ret; |
| 84 | return 1; |
| 85 | } |
| 86 | } |
| 87 | return -UNW_ENOINFO; |
| 88 | } |
| 89 | #endif /* !UNW_REMOTE_ONLY */ |
| 90 | |
| 91 | #ifdef CONFIG_DEBUG_FRAME |
| 92 | /* Load .debug_frame section from FILE. Allocates and returns space |
| 93 | in *BUF, and sets *BUFSIZE to its size. IS_LOCAL is 1 if using the |
| 94 | local process, in which case we can search the system debug file |
| 95 | directory; 0 for other address spaces, in which case we do |
| 96 | not. Returns 0 on success, 1 on error. Succeeds even if the file |
| 97 | contains no .debug_frame. */ |
| 98 | /* XXX: Could use mmap; but elf_map_image keeps tons mapped in. */ |
| 99 | |
| 100 | static int |
| 101 | load_debug_frame (const char *file, char **buf, size_t *bufsize, int is_local) |
| 102 | { |
| 103 | struct elf_image ei; |
| 104 | Elf_W (Shdr) *shdr; |
| 105 | int ret; |
| 106 | |
| 107 | ei.image = NULL; |
| 108 | |
| 109 | ret = elf_w (load_debuglink) (file, &ei, is_local); |
| 110 | if (ret != 0) |
| 111 | return ret; |
| 112 | |
| 113 | shdr = elf_w (find_section) (&ei, ".debug_frame" ); |
| 114 | if (!shdr || |
| 115 | (shdr->sh_offset + shdr->sh_size > ei.size)) |
| 116 | { |
| 117 | munmap(ei.image, ei.size); |
| 118 | return 1; |
| 119 | } |
| 120 | |
| 121 | *bufsize = shdr->sh_size; |
| 122 | *buf = malloc (*bufsize); |
| 123 | |
| 124 | memcpy(*buf, shdr->sh_offset + ei.image, *bufsize); |
| 125 | |
| 126 | Debug (4, "read %zd bytes of .debug_frame from offset %zd\n" , |
| 127 | *bufsize, shdr->sh_offset); |
| 128 | |
| 129 | munmap(ei.image, ei.size); |
| 130 | return 0; |
| 131 | } |
| 132 | |
| 133 | /* Locate the binary which originated the contents of address ADDR. Return |
| 134 | the name of the binary in *name (space is allocated by the caller) |
| 135 | Returns 0 if a binary is successfully found, or 1 if an error occurs. */ |
| 136 | |
| 137 | static int |
| 138 | find_binary_for_address (unw_word_t ip, char *name, size_t name_size) |
| 139 | { |
| 140 | #if defined(__linux) && (!UNW_REMOTE_ONLY) |
| 141 | struct map_iterator mi; |
| 142 | int found = 0; |
| 143 | int pid = getpid (); |
| 144 | unsigned long segbase, mapoff, hi; |
| 145 | |
| 146 | if (maps_init (&mi, pid) != 0) |
| 147 | return 1; |
| 148 | |
| 149 | while (maps_next (&mi, &segbase, &hi, &mapoff)) |
| 150 | if (ip >= segbase && ip < hi) |
| 151 | { |
| 152 | size_t len = strlen (mi.path); |
| 153 | |
| 154 | if (len + 1 <= name_size) |
| 155 | { |
| 156 | memcpy (name, mi.path, len + 1); |
| 157 | found = 1; |
| 158 | } |
| 159 | break; |
| 160 | } |
| 161 | maps_close (&mi); |
| 162 | return !found; |
| 163 | #endif |
| 164 | |
| 165 | return 1; |
| 166 | } |
| 167 | |
| 168 | /* Locate and/or try to load a debug_frame section for address ADDR. Return |
| 169 | pointer to debug frame descriptor, or zero if not found. */ |
| 170 | |
| 171 | static struct unw_debug_frame_list * |
| 172 | locate_debug_info (unw_addr_space_t as, unw_word_t addr, const char *dlname, |
| 173 | unw_word_t start, unw_word_t end) |
| 174 | { |
| 175 | struct unw_debug_frame_list *w, *fdesc = 0; |
| 176 | char path[PATH_MAX]; |
| 177 | char *name = path; |
| 178 | int err; |
| 179 | char *buf; |
| 180 | size_t bufsize; |
| 181 | |
| 182 | /* First, see if we loaded this frame already. */ |
| 183 | |
| 184 | for (w = as->debug_frames; w; w = w->next) |
| 185 | { |
| 186 | Debug (4, "checking %p: %lx-%lx\n" , w, (long)w->start, (long)w->end); |
| 187 | if (addr >= w->start && addr < w->end) |
| 188 | return w; |
| 189 | } |
| 190 | |
| 191 | /* If the object name we receive is blank, there's still a chance of locating |
| 192 | the file by parsing /proc/self/maps. */ |
| 193 | |
| 194 | if (strcmp (dlname, "" ) == 0) |
| 195 | { |
| 196 | err = find_binary_for_address (addr, name, sizeof(path)); |
| 197 | if (err) |
| 198 | { |
| 199 | Debug (15, "tried to locate binary for 0x%" PRIx64 ", but no luck\n" , |
| 200 | (uint64_t) addr); |
| 201 | return 0; |
| 202 | } |
| 203 | } |
| 204 | else |
| 205 | name = (char*) dlname; |
| 206 | |
| 207 | err = load_debug_frame (name, &buf, &bufsize, as == unw_local_addr_space); |
| 208 | |
| 209 | if (!err) |
| 210 | { |
| 211 | fdesc = malloc (sizeof (struct unw_debug_frame_list)); |
| 212 | |
| 213 | fdesc->start = start; |
| 214 | fdesc->end = end; |
| 215 | fdesc->debug_frame = buf; |
| 216 | fdesc->debug_frame_size = bufsize; |
| 217 | fdesc->index = NULL; |
| 218 | fdesc->next = as->debug_frames; |
| 219 | |
| 220 | as->debug_frames = fdesc; |
| 221 | } |
| 222 | |
| 223 | return fdesc; |
| 224 | } |
| 225 | |
| 226 | struct debug_frame_tab |
| 227 | { |
| 228 | struct table_entry *tab; |
| 229 | uint32_t length; |
| 230 | uint32_t size; |
| 231 | }; |
| 232 | |
| 233 | static void |
| 234 | debug_frame_tab_append (struct debug_frame_tab *tab, |
| 235 | unw_word_t fde_offset, unw_word_t start_ip) |
| 236 | { |
| 237 | unsigned int length = tab->length; |
| 238 | |
| 239 | if (length == tab->size) |
| 240 | { |
| 241 | tab->size *= 2; |
| 242 | tab->tab = realloc (tab->tab, sizeof (struct table_entry) * tab->size); |
| 243 | } |
| 244 | |
| 245 | tab->tab[length].fde_offset = fde_offset; |
| 246 | tab->tab[length].start_ip_offset = start_ip; |
| 247 | |
| 248 | tab->length = length + 1; |
| 249 | } |
| 250 | |
| 251 | static void |
| 252 | debug_frame_tab_shrink (struct debug_frame_tab *tab) |
| 253 | { |
| 254 | if (tab->size > tab->length) |
| 255 | { |
| 256 | tab->tab = realloc (tab->tab, sizeof (struct table_entry) * tab->length); |
| 257 | tab->size = tab->length; |
| 258 | } |
| 259 | } |
| 260 | |
| 261 | static int |
| 262 | debug_frame_tab_compare (const void *a, const void *b) |
| 263 | { |
| 264 | const struct table_entry *fa = a, *fb = b; |
| 265 | |
| 266 | if (fa->start_ip_offset > fb->start_ip_offset) |
| 267 | return 1; |
| 268 | else if (fa->start_ip_offset < fb->start_ip_offset) |
| 269 | return -1; |
| 270 | else |
| 271 | return 0; |
| 272 | } |
| 273 | |
| 274 | HIDDEN int |
| 275 | dwarf_find_debug_frame (int found, unw_dyn_info_t *di_debug, unw_word_t ip, |
| 276 | unw_word_t segbase, const char* obj_name, |
| 277 | unw_word_t start, unw_word_t end) |
| 278 | { |
| 279 | unw_dyn_info_t *di; |
| 280 | struct unw_debug_frame_list *fdesc = 0; |
| 281 | unw_accessors_t *a; |
| 282 | unw_word_t addr; |
| 283 | |
| 284 | Debug (15, "Trying to find .debug_frame for %s\n" , obj_name); |
| 285 | di = di_debug; |
| 286 | |
| 287 | fdesc = locate_debug_info (unw_local_addr_space, ip, obj_name, start, end); |
| 288 | |
| 289 | if (!fdesc) |
| 290 | { |
| 291 | Debug (15, "couldn't load .debug_frame\n" ); |
| 292 | return found; |
| 293 | } |
| 294 | else |
| 295 | { |
| 296 | char *buf; |
| 297 | size_t bufsize; |
| 298 | unw_word_t item_start, item_end = 0; |
| 299 | uint32_t u32val = 0; |
| 300 | uint64_t cie_id = 0; |
| 301 | struct debug_frame_tab tab; |
| 302 | |
| 303 | Debug (15, "loaded .debug_frame\n" ); |
| 304 | |
| 305 | buf = fdesc->debug_frame; |
| 306 | bufsize = fdesc->debug_frame_size; |
| 307 | |
| 308 | if (bufsize == 0) |
| 309 | { |
| 310 | Debug (15, "zero-length .debug_frame\n" ); |
| 311 | return found; |
| 312 | } |
| 313 | |
| 314 | /* Now create a binary-search table, if it does not already exist. */ |
| 315 | if (!fdesc->index) |
| 316 | { |
| 317 | addr = (unw_word_t) (uintptr_t) buf; |
| 318 | |
| 319 | a = unw_get_accessors_int (unw_local_addr_space); |
| 320 | |
| 321 | /* Find all FDE entries in debug_frame, and make into a sorted |
| 322 | index. */ |
| 323 | |
| 324 | tab.length = 0; |
| 325 | tab.size = 16; |
| 326 | tab.tab = calloc (tab.size, sizeof (struct table_entry)); |
| 327 | |
| 328 | while (addr < (unw_word_t) (uintptr_t) (buf + bufsize)) |
| 329 | { |
| 330 | uint64_t id_for_cie; |
| 331 | item_start = addr; |
| 332 | |
| 333 | dwarf_readu32 (unw_local_addr_space, a, &addr, &u32val, NULL); |
| 334 | |
| 335 | if (u32val == 0) |
| 336 | break; |
| 337 | else if (u32val != 0xffffffff) |
| 338 | { |
| 339 | uint32_t cie_id32 = 0; |
| 340 | item_end = addr + u32val; |
| 341 | dwarf_readu32 (unw_local_addr_space, a, &addr, &cie_id32, |
| 342 | NULL); |
| 343 | cie_id = cie_id32; |
| 344 | id_for_cie = 0xffffffff; |
| 345 | } |
| 346 | else |
| 347 | { |
| 348 | uint64_t u64val = 0; |
| 349 | /* Extended length. */ |
| 350 | dwarf_readu64 (unw_local_addr_space, a, &addr, &u64val, NULL); |
| 351 | item_end = addr + u64val; |
| 352 | |
| 353 | dwarf_readu64 (unw_local_addr_space, a, &addr, &cie_id, NULL); |
| 354 | id_for_cie = 0xffffffffffffffffull; |
| 355 | } |
| 356 | |
| 357 | /*Debug (1, "CIE/FDE id = %.8x\n", (int) cie_id);*/ |
| 358 | |
| 359 | if (cie_id == id_for_cie) |
| 360 | ; |
| 361 | /*Debug (1, "Found CIE at %.8x.\n", item_start);*/ |
| 362 | else |
| 363 | { |
| 364 | unw_word_t fde_addr = item_start; |
| 365 | unw_proc_info_t this_pi; |
| 366 | int err; |
| 367 | |
| 368 | /*Debug (1, "Found FDE at %.8x\n", item_start);*/ |
| 369 | |
| 370 | err = dwarf_extract_proc_info_from_fde (unw_local_addr_space, |
| 371 | a, &fde_addr, |
| 372 | &this_pi, |
| 373 | (uintptr_t) buf, 0, 1, |
| 374 | NULL); |
| 375 | if (err == 0) |
| 376 | { |
| 377 | Debug (15, "start_ip = %lx, end_ip = %lx\n" , |
| 378 | (long) this_pi.start_ip, (long) this_pi.end_ip); |
| 379 | debug_frame_tab_append (&tab, |
| 380 | item_start - (unw_word_t) (uintptr_t) buf, |
| 381 | this_pi.start_ip); |
| 382 | } |
| 383 | /*else |
| 384 | Debug (1, "FDE parse failed\n");*/ |
| 385 | } |
| 386 | |
| 387 | addr = item_end; |
| 388 | } |
| 389 | |
| 390 | debug_frame_tab_shrink (&tab); |
| 391 | qsort (tab.tab, tab.length, sizeof (struct table_entry), |
| 392 | debug_frame_tab_compare); |
| 393 | /* for (i = 0; i < tab.length; i++) |
| 394 | { |
| 395 | fprintf (stderr, "ip %x, fde offset %x\n", |
| 396 | (int) tab.tab[i].start_ip_offset, |
| 397 | (int) tab.tab[i].fde_offset); |
| 398 | }*/ |
| 399 | fdesc->index = tab.tab; |
| 400 | fdesc->index_size = tab.length; |
| 401 | } |
| 402 | |
| 403 | di->format = UNW_INFO_FORMAT_TABLE; |
| 404 | di->start_ip = fdesc->start; |
| 405 | di->end_ip = fdesc->end; |
| 406 | di->u.ti.name_ptr = (unw_word_t) (uintptr_t) obj_name; |
| 407 | di->u.ti.table_data = (unw_word_t *) fdesc; |
| 408 | di->u.ti.table_len = sizeof (*fdesc) / sizeof (unw_word_t); |
| 409 | di->u.ti.segbase = segbase; |
| 410 | |
| 411 | found = 1; |
| 412 | Debug (15, "found debug_frame table `%s': segbase=0x%lx, len=%lu, " |
| 413 | "gp=0x%lx, table_data=0x%lx\n" , |
| 414 | (char *) (uintptr_t) di->u.ti.name_ptr, |
| 415 | (long) di->u.ti.segbase, (long) di->u.ti.table_len, |
| 416 | (long) di->gp, (long) di->u.ti.table_data); |
| 417 | } |
| 418 | return found; |
| 419 | } |
| 420 | |
| 421 | #endif /* CONFIG_DEBUG_FRAME */ |
| 422 | |
| 423 | #ifndef UNW_REMOTE_ONLY |
| 424 | |
| 425 | static Elf_W (Addr) |
| 426 | dwarf_find_eh_frame_section(struct dl_phdr_info *info) |
| 427 | { |
| 428 | int rc; |
| 429 | struct elf_image ei; |
| 430 | Elf_W (Addr) eh_frame = 0; |
| 431 | Elf_W (Shdr)* shdr; |
| 432 | const char *file = info->dlpi_name; |
| 433 | char exepath[PATH_MAX]; |
| 434 | |
| 435 | if (strlen(file) == 0) |
| 436 | { |
| 437 | tdep_get_exe_image_path(exepath); |
| 438 | file = exepath; |
| 439 | } |
| 440 | |
| 441 | Debug (1, "looking for .eh_frame section in %s\n" , |
| 442 | file); |
| 443 | |
| 444 | rc = elf_map_image (&ei, file); |
| 445 | if (rc != 0) |
| 446 | return 0; |
| 447 | |
| 448 | shdr = elf_w (find_section) (&ei, ".eh_frame" ); |
| 449 | if (!shdr) |
| 450 | goto out; |
| 451 | |
| 452 | eh_frame = shdr->sh_addr + info->dlpi_addr; |
| 453 | Debug (4, "found .eh_frame at address %lx\n" , |
| 454 | eh_frame); |
| 455 | |
| 456 | out: |
| 457 | munmap (ei.image, ei.size); |
| 458 | |
| 459 | return eh_frame; |
| 460 | } |
| 461 | |
| 462 | struct dwarf_callback_data |
| 463 | { |
| 464 | /* in: */ |
| 465 | unw_word_t ip; /* instruction-pointer we're looking for */ |
| 466 | unw_proc_info_t *pi; /* proc-info pointer */ |
| 467 | int need_unwind_info; |
| 468 | /* out: */ |
| 469 | int single_fde; /* did we find a single FDE? (vs. a table) */ |
| 470 | unw_dyn_info_t di; /* table info (if single_fde is false) */ |
| 471 | unw_dyn_info_t di_debug; /* additional table info for .debug_frame */ |
| 472 | }; |
| 473 | |
| 474 | /* ptr is a pointer to a dwarf_callback_data structure and, on entry, |
| 475 | member ip contains the instruction-pointer we're looking |
| 476 | for. */ |
| 477 | HIDDEN int |
| 478 | dwarf_callback (struct dl_phdr_info *info, size_t size, void *ptr) |
| 479 | { |
| 480 | struct dwarf_callback_data *cb_data = ptr; |
| 481 | unw_dyn_info_t *di = &cb_data->di; |
| 482 | const Elf_W(Phdr) *phdr, *p_eh_hdr, *p_dynamic, *p_text; |
| 483 | unw_word_t addr, eh_frame_start, eh_frame_end, fde_count, ip; |
| 484 | Elf_W(Addr) load_base, max_load_addr = 0; |
| 485 | int ret, need_unwind_info = cb_data->need_unwind_info; |
| 486 | unw_proc_info_t *pi = cb_data->pi; |
| 487 | struct dwarf_eh_frame_hdr *hdr = NULL; |
| 488 | unw_accessors_t *a; |
| 489 | long n; |
| 490 | int found = 0; |
| 491 | struct dwarf_eh_frame_hdr synth_eh_frame_hdr; |
| 492 | #ifdef CONFIG_DEBUG_FRAME |
| 493 | unw_word_t start, end; |
| 494 | #endif /* CONFIG_DEBUG_FRAME*/ |
| 495 | |
| 496 | ip = cb_data->ip; |
| 497 | |
| 498 | /* Make sure struct dl_phdr_info is at least as big as we need. */ |
| 499 | if (size < offsetof (struct dl_phdr_info, dlpi_phnum) |
| 500 | + sizeof (info->dlpi_phnum)) |
| 501 | return -1; |
| 502 | |
| 503 | Debug (15, "checking %s, base=0x%lx)\n" , |
| 504 | info->dlpi_name, (long) info->dlpi_addr); |
| 505 | |
| 506 | phdr = info->dlpi_phdr; |
| 507 | load_base = info->dlpi_addr; |
| 508 | p_text = NULL; |
| 509 | p_eh_hdr = NULL; |
| 510 | p_dynamic = NULL; |
| 511 | |
| 512 | /* See if PC falls into one of the loaded segments. Find the |
| 513 | eh-header segment at the same time. */ |
| 514 | for (n = info->dlpi_phnum; --n >= 0; phdr++) |
| 515 | { |
| 516 | if (phdr->p_type == PT_LOAD) |
| 517 | { |
| 518 | Elf_W(Addr) vaddr = phdr->p_vaddr + load_base; |
| 519 | |
| 520 | if (ip >= vaddr && ip < vaddr + phdr->p_memsz) |
| 521 | p_text = phdr; |
| 522 | |
| 523 | if (vaddr + phdr->p_filesz > max_load_addr) |
| 524 | max_load_addr = vaddr + phdr->p_filesz; |
| 525 | } |
| 526 | else if (phdr->p_type == PT_GNU_EH_FRAME) |
| 527 | p_eh_hdr = phdr; |
| 528 | else if (phdr->p_type == PT_DYNAMIC) |
| 529 | p_dynamic = phdr; |
| 530 | } |
| 531 | |
| 532 | if (!p_text) |
| 533 | return 0; |
| 534 | |
| 535 | if (p_eh_hdr) |
| 536 | { |
| 537 | hdr = (struct dwarf_eh_frame_hdr *) (p_eh_hdr->p_vaddr + load_base); |
| 538 | } |
| 539 | else |
| 540 | { |
| 541 | Elf_W (Addr) eh_frame; |
| 542 | Debug (1, "no .eh_frame_hdr section found\n" ); |
| 543 | eh_frame = dwarf_find_eh_frame_section (info); |
| 544 | if (eh_frame) |
| 545 | { |
| 546 | Debug (1, "using synthetic .eh_frame_hdr section for %s\n" , |
| 547 | info->dlpi_name); |
| 548 | synth_eh_frame_hdr.version = DW_EH_VERSION; |
| 549 | synth_eh_frame_hdr.eh_frame_ptr_enc = DW_EH_PE_absptr | |
| 550 | ((sizeof(Elf_W (Addr)) == 4) ? DW_EH_PE_udata4 : DW_EH_PE_udata8); |
| 551 | synth_eh_frame_hdr.fde_count_enc = DW_EH_PE_omit; |
| 552 | synth_eh_frame_hdr.table_enc = DW_EH_PE_omit; |
| 553 | synth_eh_frame_hdr.eh_frame = eh_frame; |
| 554 | hdr = &synth_eh_frame_hdr; |
| 555 | } |
| 556 | } |
| 557 | |
| 558 | if (hdr) |
| 559 | { |
| 560 | if (p_dynamic) |
| 561 | { |
| 562 | /* For dynamicly linked executables and shared libraries, |
| 563 | DT_PLTGOT is the value that data-relative addresses are |
| 564 | relative to for that object. We call this the "gp". */ |
| 565 | Elf_W(Dyn) *dyn = (Elf_W(Dyn) *)(p_dynamic->p_vaddr + load_base); |
| 566 | for (; dyn->d_tag != DT_NULL; ++dyn) |
| 567 | if (dyn->d_tag == DT_PLTGOT) |
| 568 | { |
| 569 | /* Assume that _DYNAMIC is writable and GLIBC has |
| 570 | relocated it (true for x86 at least). */ |
| 571 | di->gp = dyn->d_un.d_ptr; |
| 572 | break; |
| 573 | } |
| 574 | } |
| 575 | else |
| 576 | /* Otherwise this is a static executable with no _DYNAMIC. Assume |
| 577 | that data-relative addresses are relative to 0, i.e., |
| 578 | absolute. */ |
| 579 | di->gp = 0; |
| 580 | pi->gp = di->gp; |
| 581 | |
| 582 | if (hdr->version != DW_EH_VERSION) |
| 583 | { |
| 584 | Debug (1, "table `%s' has unexpected version %d\n" , |
| 585 | info->dlpi_name, hdr->version); |
| 586 | return 0; |
| 587 | } |
| 588 | |
| 589 | a = unw_get_accessors_int (unw_local_addr_space); |
| 590 | addr = (unw_word_t) (uintptr_t) (&hdr->eh_frame); |
| 591 | |
| 592 | /* (Optionally) read eh_frame_ptr: */ |
| 593 | if ((ret = dwarf_read_encoded_pointer (unw_local_addr_space, a, |
| 594 | &addr, hdr->eh_frame_ptr_enc, pi, |
| 595 | &eh_frame_start, NULL)) < 0) |
| 596 | return ret; |
| 597 | |
| 598 | /* (Optionally) read fde_count: */ |
| 599 | if ((ret = dwarf_read_encoded_pointer (unw_local_addr_space, a, |
| 600 | &addr, hdr->fde_count_enc, pi, |
| 601 | &fde_count, NULL)) < 0) |
| 602 | return ret; |
| 603 | |
| 604 | if (hdr->table_enc != (DW_EH_PE_datarel | DW_EH_PE_sdata4)) |
| 605 | { |
| 606 | /* If there is no search table or it has an unsupported |
| 607 | encoding, fall back on linear search. */ |
| 608 | if (hdr->table_enc == DW_EH_PE_omit) |
| 609 | Debug (4, "table `%s' lacks search table; doing linear search\n" , |
| 610 | info->dlpi_name); |
| 611 | else |
| 612 | Debug (4, "table `%s' has encoding 0x%x; doing linear search\n" , |
| 613 | info->dlpi_name, hdr->table_enc); |
| 614 | |
| 615 | eh_frame_end = max_load_addr; /* XXX can we do better? */ |
| 616 | |
| 617 | if (hdr->fde_count_enc == DW_EH_PE_omit) |
| 618 | fde_count = ~0UL; |
| 619 | if (hdr->eh_frame_ptr_enc == DW_EH_PE_omit) |
| 620 | abort (); |
| 621 | |
| 622 | Debug (1, "eh_frame_start = %lx eh_frame_end = %lx\n" , |
| 623 | eh_frame_start, eh_frame_end); |
| 624 | |
| 625 | /* XXX we know how to build a local binary search table for |
| 626 | .debug_frame, so we could do that here too. */ |
| 627 | found = linear_search (unw_local_addr_space, ip, |
| 628 | eh_frame_start, eh_frame_end, fde_count, |
| 629 | pi, need_unwind_info, NULL); |
| 630 | if (found != 1) |
| 631 | found = 0; |
| 632 | else |
| 633 | cb_data->single_fde = 1; |
| 634 | } |
| 635 | else |
| 636 | { |
| 637 | di->format = UNW_INFO_FORMAT_REMOTE_TABLE; |
| 638 | di->start_ip = p_text->p_vaddr + load_base; |
| 639 | di->end_ip = p_text->p_vaddr + load_base + p_text->p_memsz; |
| 640 | di->u.rti.name_ptr = (unw_word_t) (uintptr_t) info->dlpi_name; |
| 641 | di->u.rti.table_data = addr; |
| 642 | assert (sizeof (struct table_entry) % sizeof (unw_word_t) == 0); |
| 643 | di->u.rti.table_len = (fde_count * sizeof (struct table_entry) |
| 644 | / sizeof (unw_word_t)); |
| 645 | /* For the binary-search table in the eh_frame_hdr, data-relative |
| 646 | means relative to the start of that section... */ |
| 647 | di->u.rti.segbase = (unw_word_t) (uintptr_t) hdr; |
| 648 | |
| 649 | found = 1; |
| 650 | Debug (15, "found table `%s': segbase=0x%lx, len=%lu, gp=0x%lx, " |
| 651 | "table_data=0x%lx\n" , (char *) (uintptr_t) di->u.rti.name_ptr, |
| 652 | (long) di->u.rti.segbase, (long) di->u.rti.table_len, |
| 653 | (long) di->gp, (long) di->u.rti.table_data); |
| 654 | } |
| 655 | } |
| 656 | |
| 657 | #ifdef CONFIG_DEBUG_FRAME |
| 658 | /* Find the start/end of the described region by parsing the phdr_info |
| 659 | structure. */ |
| 660 | start = (unw_word_t) -1; |
| 661 | end = 0; |
| 662 | |
| 663 | for (n = 0; n < info->dlpi_phnum; n++) |
| 664 | { |
| 665 | if (info->dlpi_phdr[n].p_type == PT_LOAD) |
| 666 | { |
| 667 | unw_word_t seg_start = info->dlpi_addr + info->dlpi_phdr[n].p_vaddr; |
| 668 | unw_word_t seg_end = seg_start + info->dlpi_phdr[n].p_memsz; |
| 669 | |
| 670 | if (seg_start < start) |
| 671 | start = seg_start; |
| 672 | |
| 673 | if (seg_end > end) |
| 674 | end = seg_end; |
| 675 | } |
| 676 | } |
| 677 | |
| 678 | found = dwarf_find_debug_frame (found, &cb_data->di_debug, ip, |
| 679 | info->dlpi_addr, info->dlpi_name, start, |
| 680 | end); |
| 681 | #endif /* CONFIG_DEBUG_FRAME */ |
| 682 | |
| 683 | return found; |
| 684 | } |
| 685 | |
| 686 | HIDDEN int |
| 687 | dwarf_find_proc_info (unw_addr_space_t as, unw_word_t ip, |
| 688 | unw_proc_info_t *pi, int need_unwind_info, void *arg) |
| 689 | { |
| 690 | struct dwarf_callback_data cb_data; |
| 691 | intrmask_t saved_mask; |
| 692 | int ret; |
| 693 | |
| 694 | Debug (14, "looking for IP=0x%lx\n" , (long) ip); |
| 695 | |
| 696 | memset (&cb_data, 0, sizeof (cb_data)); |
| 697 | cb_data.ip = ip; |
| 698 | cb_data.pi = pi; |
| 699 | cb_data.need_unwind_info = need_unwind_info; |
| 700 | cb_data.di.format = -1; |
| 701 | cb_data.di_debug.format = -1; |
| 702 | |
| 703 | SIGPROCMASK (SIG_SETMASK, &unwi_full_mask, &saved_mask); |
| 704 | ret = dl_iterate_phdr (dwarf_callback, &cb_data); |
| 705 | SIGPROCMASK (SIG_SETMASK, &saved_mask, NULL); |
| 706 | |
| 707 | if (ret > 0) |
| 708 | { |
| 709 | if (cb_data.single_fde) |
| 710 | /* already got the result in *pi */ |
| 711 | return 0; |
| 712 | |
| 713 | /* search the table: */ |
| 714 | if (cb_data.di.format != -1) |
| 715 | ret = dwarf_search_unwind_table_int (as, ip, &cb_data.di, |
| 716 | pi, need_unwind_info, arg); |
| 717 | else |
| 718 | ret = -UNW_ENOINFO; |
| 719 | |
| 720 | if (ret == -UNW_ENOINFO && cb_data.di_debug.format != -1) |
| 721 | ret = dwarf_search_unwind_table_int (as, ip, &cb_data.di_debug, pi, |
| 722 | need_unwind_info, arg); |
| 723 | } |
| 724 | else |
| 725 | ret = -UNW_ENOINFO; |
| 726 | |
| 727 | return ret; |
| 728 | } |
| 729 | |
| 730 | static inline const struct table_entry * |
| 731 | lookup (const struct table_entry *table, size_t table_size, int32_t rel_ip) |
| 732 | { |
| 733 | unsigned long table_len = table_size / sizeof (struct table_entry); |
| 734 | const struct table_entry *e = NULL; |
| 735 | unsigned long lo, hi, mid; |
| 736 | |
| 737 | /* do a binary search for right entry: */ |
| 738 | for (lo = 0, hi = table_len; lo < hi;) |
| 739 | { |
| 740 | mid = (lo + hi) / 2; |
| 741 | e = table + mid; |
| 742 | Debug (15, "e->start_ip_offset = %lx\n" , (long) e->start_ip_offset); |
| 743 | if (rel_ip < e->start_ip_offset) |
| 744 | hi = mid; |
| 745 | else |
| 746 | lo = mid + 1; |
| 747 | } |
| 748 | if (hi <= 0) |
| 749 | return NULL; |
| 750 | e = table + hi - 1; |
| 751 | return e; |
| 752 | } |
| 753 | |
| 754 | #endif /* !UNW_REMOTE_ONLY */ |
| 755 | |
| 756 | #ifndef UNW_LOCAL_ONLY |
| 757 | |
| 758 | /* Lookup an unwind-table entry in remote memory. Returns 1 if an |
| 759 | entry is found, 0 if no entry is found, negative if an error |
| 760 | occurred reading remote memory. */ |
| 761 | static int |
| 762 | remote_lookup (unw_addr_space_t as, |
| 763 | unw_word_t table, size_t table_size, int32_t rel_ip, |
| 764 | struct table_entry *e, int32_t *last_ip_offset, void *arg) |
| 765 | { |
| 766 | unsigned long table_len = table_size / sizeof (struct table_entry); |
| 767 | unw_accessors_t *a = unw_get_accessors_int (as); |
| 768 | unsigned long lo, hi, mid; |
| 769 | unw_word_t e_addr = 0; |
| 770 | int32_t start = 0; |
| 771 | int ret; |
| 772 | |
| 773 | /* do a binary search for right entry: */ |
| 774 | for (lo = 0, hi = table_len; lo < hi;) |
| 775 | { |
| 776 | mid = (lo + hi) / 2; |
| 777 | e_addr = table + mid * sizeof (struct table_entry); |
| 778 | if ((ret = dwarf_reads32 (as, a, &e_addr, &start, arg)) < 0) |
| 779 | return ret; |
| 780 | |
| 781 | if (rel_ip < start) |
| 782 | hi = mid; |
| 783 | else |
| 784 | lo = mid + 1; |
| 785 | } |
| 786 | if (hi <= 0) |
| 787 | return 0; |
| 788 | e_addr = table + (hi - 1) * sizeof (struct table_entry); |
| 789 | if ((ret = dwarf_reads32 (as, a, &e_addr, &e->start_ip_offset, arg)) < 0 |
| 790 | || (ret = dwarf_reads32 (as, a, &e_addr, &e->fde_offset, arg)) < 0 |
| 791 | || (hi < table_len && |
| 792 | (ret = dwarf_reads32 (as, a, &e_addr, last_ip_offset, arg)) < 0)) |
| 793 | return ret; |
| 794 | return 1; |
| 795 | } |
| 796 | |
| 797 | #endif /* !UNW_LOCAL_ONLY */ |
| 798 | |
| 799 | static int is_remote_table(int format) |
| 800 | { |
| 801 | return (format == UNW_INFO_FORMAT_REMOTE_TABLE || |
| 802 | format == UNW_INFO_FORMAT_IP_OFFSET); |
| 803 | } |
| 804 | |
| 805 | int |
| 806 | dwarf_search_unwind_table (unw_addr_space_t as, unw_word_t ip, |
| 807 | unw_dyn_info_t *di, unw_proc_info_t *pi, |
| 808 | int need_unwind_info, void *arg) |
| 809 | { |
| 810 | const struct table_entry *e = NULL, *table; |
| 811 | unw_word_t ip_base = 0, segbase = 0, last_ip, fde_addr; |
| 812 | unw_accessors_t *a; |
| 813 | #ifndef UNW_LOCAL_ONLY |
| 814 | struct table_entry ent; |
| 815 | #endif |
| 816 | int ret; |
| 817 | unw_word_t debug_frame_base; |
| 818 | size_t table_len; |
| 819 | |
| 820 | #ifdef UNW_REMOTE_ONLY |
| 821 | assert (is_remote_table(di->format)); |
| 822 | #else |
| 823 | assert (is_remote_table(di->format) |
| 824 | || di->format == UNW_INFO_FORMAT_TABLE); |
| 825 | #endif |
| 826 | assert (ip >= di->start_ip && ip < di->end_ip); |
| 827 | |
| 828 | if (is_remote_table(di->format)) |
| 829 | { |
| 830 | table = (const struct table_entry *) (uintptr_t) di->u.rti.table_data; |
| 831 | table_len = di->u.rti.table_len * sizeof (unw_word_t); |
| 832 | debug_frame_base = 0; |
| 833 | } |
| 834 | else |
| 835 | { |
| 836 | assert(di->format == UNW_INFO_FORMAT_TABLE); |
| 837 | #ifndef UNW_REMOTE_ONLY |
| 838 | struct unw_debug_frame_list *fdesc = (void *) di->u.ti.table_data; |
| 839 | |
| 840 | /* UNW_INFO_FORMAT_TABLE (i.e. .debug_frame) is read from local address |
| 841 | space. Both the index and the unwind tables live in local memory, but |
| 842 | the address space to check for properties like the address size and |
| 843 | endianness is the target one. */ |
| 844 | as = unw_local_addr_space; |
| 845 | table = fdesc->index; |
| 846 | table_len = fdesc->index_size * sizeof (struct table_entry); |
| 847 | debug_frame_base = (uintptr_t) fdesc->debug_frame; |
| 848 | #endif |
| 849 | } |
| 850 | |
| 851 | a = unw_get_accessors_int (as); |
| 852 | |
| 853 | segbase = di->u.rti.segbase; |
| 854 | if (di->format == UNW_INFO_FORMAT_IP_OFFSET) { |
| 855 | ip_base = di->start_ip; |
| 856 | } else { |
| 857 | ip_base = segbase; |
| 858 | } |
| 859 | |
| 860 | #ifndef UNW_REMOTE_ONLY |
| 861 | if (as == unw_local_addr_space) |
| 862 | { |
| 863 | e = lookup (table, table_len, ip - ip_base); |
| 864 | if (e && &e[1] < &table[table_len]) |
| 865 | last_ip = e[1].start_ip_offset + ip_base; |
| 866 | else |
| 867 | last_ip = di->end_ip; |
| 868 | } |
| 869 | else |
| 870 | #endif |
| 871 | { |
| 872 | #ifndef UNW_LOCAL_ONLY |
| 873 | int32_t last_ip_offset = di->end_ip - ip_base; |
| 874 | segbase = di->u.rti.segbase; |
| 875 | if ((ret = remote_lookup (as, (uintptr_t) table, table_len, |
| 876 | ip - ip_base, &ent, &last_ip_offset, arg)) < 0) |
| 877 | return ret; |
| 878 | if (ret) |
| 879 | { |
| 880 | e = &ent; |
| 881 | last_ip = last_ip_offset + ip_base; |
| 882 | } |
| 883 | else |
| 884 | e = NULL; /* no info found */ |
| 885 | #endif |
| 886 | } |
| 887 | if (!e) |
| 888 | { |
| 889 | Debug (1, "IP %lx inside range %lx-%lx, but no explicit unwind info found\n" , |
| 890 | (long) ip, (long) di->start_ip, (long) di->end_ip); |
| 891 | /* IP is inside this table's range, but there is no explicit |
| 892 | unwind info. */ |
| 893 | return -UNW_ENOINFO; |
| 894 | } |
| 895 | Debug (15, "ip=0x%lx, start_ip=0x%lx\n" , |
| 896 | (long) ip, (long) (e->start_ip_offset)); |
| 897 | if (debug_frame_base) |
| 898 | fde_addr = e->fde_offset + debug_frame_base; |
| 899 | else |
| 900 | fde_addr = e->fde_offset + segbase; |
| 901 | Debug (1, "e->fde_offset = %lx, segbase = %lx, debug_frame_base = %lx, " |
| 902 | "fde_addr = %lx\n" , (long) e->fde_offset, (long) segbase, |
| 903 | (long) debug_frame_base, (long) fde_addr); |
| 904 | if ((ret = dwarf_extract_proc_info_from_fde (as, a, &fde_addr, pi, |
| 905 | debug_frame_base ? |
| 906 | debug_frame_base : segbase, |
| 907 | need_unwind_info, |
| 908 | debug_frame_base != 0, arg)) < 0) |
| 909 | return ret; |
| 910 | |
| 911 | /* .debug_frame uses an absolute encoding that does not know about any |
| 912 | shared library relocation. */ |
| 913 | if (di->format == UNW_INFO_FORMAT_TABLE) |
| 914 | { |
| 915 | pi->start_ip += segbase; |
| 916 | pi->end_ip += segbase; |
| 917 | pi->flags = UNW_PI_FLAG_DEBUG_FRAME; |
| 918 | } |
| 919 | |
| 920 | #if defined(NEED_LAST_IP) |
| 921 | pi->last_ip = last_ip; |
| 922 | #else |
| 923 | (void)last_ip; |
| 924 | #endif |
| 925 | if (ip < pi->start_ip || ip >= pi->end_ip) |
| 926 | return -UNW_ENOINFO; |
| 927 | |
| 928 | return 0; |
| 929 | } |
| 930 | |
| 931 | HIDDEN void |
| 932 | dwarf_put_unwind_info (unw_addr_space_t as, unw_proc_info_t *pi, void *arg) |
| 933 | { |
| 934 | return; /* always a nop */ |
| 935 | } |
| 936 | |