1
0
Fork 0
mirror of https://github.com/tobast/libunwind-eh_elf.git synced 2024-06-25 19:01:44 +02:00
libunwind-eh_elf/src/dwarf/Gfind_unwind_table.c
Tommi Rantala a6f2a7a2a8 Fix incorrect .debug_frame lookup with non-zero text segment vaddr
Unwinding over ptrace and unwinding coredump fail to lookup the
.debug_frame dwarf data when the ELF file text segment virtual address
is non-zero. Looking at some binaries, the virtual address is non-zero
for non-pie binaries, and zero for PIC shared libraries and PIE
executables.

The core dump unwinder can be used for demonstrating the bug. Without
this patch, the unwinding fails badly (testing with a ARM qemu image):

 $ UNW_ARM_UNWIND_METHOD=1 ./test-coredump-unwind core `cat backing_files`
 test-coredump-unwind: unw_get_proc_info(ip=0x86d8) failed: ret=-10

After applying this patch, we can unwind all the way until running out
of dwarf data:

 $ UNW_ARM_UNWIND_METHOD=1 ./test-coredump-unwind core `cat backing_files`
     ip=0x000086d8 proc=000086d4-000086dc handler=0x00000000 lsda=0x00000000
 test-coredump-unwind: step
 test-coredump-unwind: step done:1
     ip=0x000086ef proc=000086dc-000086f2 handler=0x00000000 lsda=0x00000000
 test-coredump-unwind: step
 test-coredump-unwind: step done:1
     ip=0x000086e7 proc=000086dc-000086f2 handler=0x00000000 lsda=0x00000000
 test-coredump-unwind: step
 test-coredump-unwind: step done:1
     ip=0x00008597 proc=00008584-0000859a handler=0x00000000 lsda=0x00000000
 test-coredump-unwind: step
 test-coredump-unwind: step done:1
     ip=0x76eacc3b proc=76eacba0-76eaccec handler=0x00000000 lsda=0x00000000
 test-coredump-unwind: step
 test-coredump-unwind: step done:1
 test-coredump-unwind: unw_get_proc_info(ip=0x85c3) failed: ret=-10

Note how the binary itself is mapped to address 0x8000, the virtual
address for the text segment is 0x8000, and the .debug_frame program
counter values are relative to 0:

$ tr ' ' '\n' < backing_files
0x8000:/home/user/tests/crasher
0x76e96000:/lib/arm-linux-gnueabi/libc-2.13.so
0x76f77000:/lib/arm-linux-gnueabi/libgcc_s.so.1
0x76f88000:/lib/arm-linux-gnueabi/ld-2.13.so

$ readelf -l crasher
Elf file type is EXEC (Executable file)
Entry point 0x859d
There are 9 program headers, starting at offset 52

Program Headers:
  Type           Offset   VirtAddr   PhysAddr   FileSiz MemSiz  Flg Align
  EXIDX          0x0007b0 0x000087b0 0x000087b0 0x00030 0x00030 R   0x4
  PHDR           0x000034 0x00008034 0x00008034 0x00120 0x00120 R E 0x4
  INTERP         0x000154 0x00008154 0x00008154 0x00013 0x00013 R   0x1
      [Requesting program interpreter: /lib/ld-linux.so.3]
  LOAD           0x000000 0x00008000 0x00008000 0x007e4 0x007e4 R E 0x8000
  LOAD           0x000efc 0x00010efc 0x00010efc 0x00148 0x00154 RW 0x8000
  DYNAMIC        0x000f08 0x00010f08 0x00010f08 0x000f8 0x000f8 RW  0x4
  NOTE           0x000168 0x00008168 0x00008168 0x00044 0x00044 R   0x4
  GNU_STACK      0x000000 0x00000000 0x00000000 0x00000 0x00000 RW  0x4
  GNU_RELRO      0x000efc 0x00010efc 0x00010efc 0x00104 0x00104 R   0x1

$ readelf --debug-dump=frames crasher | grep FDE
00000010 00000024 00000000 FDE cie=00000000 pc=00008614..000086d4
00000038 0000000c 00000000 FDE cie=00000000 pc=000086d4..000086dc
00000048 00000014 00000000 FDE cie=00000000 pc=000086dc..000086f2
00000060 00000014 00000000 FDE cie=00000000 pc=00008584..0000859a
2012-09-04 14:58:38 +03:00

229 lines
6.7 KiB
C

/* libunwind - a platform-independent unwind library
Copyright (C) 2003-2004 Hewlett-Packard Co
Contributed by David Mosberger-Tang <davidm@hpl.hp.com>
This file is part of libunwind.
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
"Software"), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to
the following conditions:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
#include <elf.h>
#include <fcntl.h>
#include <string.h>
#include <unistd.h>
#include <sys/mman.h>
#include "libunwind_i.h"
#include "dwarf-eh.h"
#include "dwarf_i.h"
int
dwarf_find_unwind_table (struct elf_dyn_info *edi, unw_addr_space_t as,
char *path, unw_word_t segbase, unw_word_t mapoff,
unw_word_t ip)
{
Elf_W(Phdr) *phdr, *ptxt = NULL, *peh_hdr = NULL, *pdyn = NULL;
unw_word_t addr, eh_frame_start, fde_count, load_base;
unw_word_t max_load_addr = 0;
unw_word_t start_ip = (unw_word_t) -1;
unw_word_t end_ip = 0;
struct dwarf_eh_frame_hdr *hdr;
unw_proc_info_t pi;
unw_accessors_t *a;
Elf_W(Ehdr) *ehdr;
#if UNW_TARGET_ARM
const Elf_W(Phdr) *parm_exidx = NULL;
#endif
int i, ret, found = 0;
/* XXX: Much of this code is Linux/LSB-specific. */
if (!elf_w(valid_object) (&edi->ei))
return -UNW_ENOINFO;
ehdr = edi->ei.image;
phdr = (Elf_W(Phdr) *) ((char *) edi->ei.image + ehdr->e_phoff);
for (i = 0; i < ehdr->e_phnum; ++i)
{
switch (phdr[i].p_type)
{
case PT_LOAD:
if (phdr[i].p_vaddr < start_ip)
start_ip = phdr[i].p_vaddr;
if (phdr[i].p_vaddr + phdr[i].p_memsz > end_ip)
end_ip = phdr[i].p_vaddr + phdr[i].p_memsz;
if (phdr[i].p_offset == mapoff)
ptxt = phdr + i;
if ((uintptr_t) edi->ei.image + phdr->p_filesz > max_load_addr)
max_load_addr = (uintptr_t) edi->ei.image + phdr->p_filesz;
break;
case PT_GNU_EH_FRAME:
peh_hdr = phdr + i;
break;
case PT_DYNAMIC:
pdyn = phdr + i;
break;
#if UNW_TARGET_ARM
case PT_ARM_EXIDX:
parm_exidx = phdr + i;
break;
#endif
default:
break;
}
}
if (!ptxt)
return 0;
load_base = segbase - ptxt->p_vaddr;
start_ip += load_base;
end_ip += load_base;
if (peh_hdr)
{
if (pdyn)
{
/* For dynamicly linked executables and shared libraries,
DT_PLTGOT is the value that data-relative addresses are
relative to for that object. We call this the "gp". */
Elf_W(Dyn) *dyn = (Elf_W(Dyn) *)(pdyn->p_offset
+ (char *) edi->ei.image);
for (; dyn->d_tag != DT_NULL; ++dyn)
if (dyn->d_tag == DT_PLTGOT)
{
/* Assume that _DYNAMIC is writable and GLIBC has
relocated it (true for x86 at least). */
edi->di_cache.gp = dyn->d_un.d_ptr;
break;
}
}
else
/* Otherwise this is a static executable with no _DYNAMIC. Assume
that data-relative addresses are relative to 0, i.e.,
absolute. */
edi->di_cache.gp = 0;
hdr = (struct dwarf_eh_frame_hdr *) (peh_hdr->p_offset
+ (char *) edi->ei.image);
if (hdr->version != DW_EH_VERSION)
{
Debug (1, "table `%s' has unexpected version %d\n",
path, hdr->version);
return -UNW_ENOINFO;
}
a = unw_get_accessors (unw_local_addr_space);
addr = (unw_word_t) (hdr + 1);
/* Fill in a dummy proc_info structure. We just need to fill in
enough to ensure that dwarf_read_encoded_pointer() can do it's
job. Since we don't have a procedure-context at this point, all
we have to do is fill in the global-pointer. */
memset (&pi, 0, sizeof (pi));
pi.gp = edi->di_cache.gp;
/* (Optionally) read eh_frame_ptr: */
if ((ret = dwarf_read_encoded_pointer (unw_local_addr_space, a,
&addr, hdr->eh_frame_ptr_enc, &pi,
&eh_frame_start, NULL)) < 0)
return -UNW_ENOINFO;
/* (Optionally) read fde_count: */
if ((ret = dwarf_read_encoded_pointer (unw_local_addr_space, a,
&addr, hdr->fde_count_enc, &pi,
&fde_count, NULL)) < 0)
return -UNW_ENOINFO;
if (hdr->table_enc != (DW_EH_PE_datarel | DW_EH_PE_sdata4))
{
#if 1
abort ();
#else
unw_word_t eh_frame_end;
/* If there is no search table or it has an unsupported
encoding, fall back on linear search. */
if (hdr->table_enc == DW_EH_PE_omit)
Debug (4, "EH lacks search table; doing linear search\n");
else
Debug (4, "EH table has encoding 0x%x; doing linear search\n",
hdr->table_enc);
eh_frame_end = max_load_addr; /* XXX can we do better? */
if (hdr->fde_count_enc == DW_EH_PE_omit)
fde_count = ~0UL;
if (hdr->eh_frame_ptr_enc == DW_EH_PE_omit)
abort ();
return linear_search (unw_local_addr_space, ip,
eh_frame_start, eh_frame_end, fde_count,
pi, need_unwind_info, NULL);
#endif
}
edi->di_cache.start_ip = start_ip;
edi->di_cache.end_ip = end_ip;
edi->di_cache.format = UNW_INFO_FORMAT_REMOTE_TABLE;
edi->di_cache.u.rti.name_ptr = 0;
/* two 32-bit values (ip_offset/fde_offset) per table-entry: */
edi->di_cache.u.rti.table_len = (fde_count * 8) / sizeof (unw_word_t);
edi->di_cache.u.rti.table_data = ((load_base + peh_hdr->p_vaddr)
+ (addr - (unw_word_t) edi->ei.image
- peh_hdr->p_offset));
/* For the binary-search table in the eh_frame_hdr, data-relative
means relative to the start of that section... */
edi->di_cache.u.rti.segbase = ((load_base + peh_hdr->p_vaddr)
+ ((unw_word_t) hdr - (unw_word_t) edi->ei.image
- peh_hdr->p_offset));
found = 1;
}
#if UNW_TARGET_ARM
if (parm_exidx)
{
edi->di_arm.format = UNW_INFO_FORMAT_ARM_EXIDX;
edi->di_arm.start_ip = start_ip;
edi->di_arm.end_ip = end_ip;
edi->di_arm.u.rti.name_ptr = (unw_word_t) path;
edi->di_arm.u.rti.table_data = load_base + parm_exidx->p_vaddr;
edi->di_arm.u.rti.table_len = parm_exidx->p_memsz;
found = 1;
}
#endif
#ifdef CONFIG_DEBUG_FRAME
/* Try .debug_frame. */
found = dwarf_find_debug_frame (found, &edi->di_debug, ip, load_base, path,
start_ip, end_ip);
#endif
return found;
}