Decompress debug symbols.
Debug symbols may be stored in LZMA compressed section .gnu_debugdata.
libunwind already contained some support for it however it was
disabled at compile time. This CL re-enables it and extends it.
The decompression code is based on LZMA SDK.
Change-Id: Ie199ce1fc31770940db288b5e57b50942f12be7b
diff --git a/Android.mk b/Android.mk
index d2e5c06..eb44edc 100644
--- a/Android.mk
+++ b/Android.mk
@@ -226,6 +226,8 @@
libunwind_src_files_mips += src/elf32.c
libunwind_src_files_x86 += src/elf32.c
+libunwind_static_libraries := liblzma
+
libunwind_shared_libraries_target := \
libdl \
diff --git a/include/config.h b/include/config.h
index bcef953..48549f6 100644
--- a/include/config.h
+++ b/include/config.h
@@ -131,7 +131,7 @@
#define HAVE_LINK_H 1
/* Define if you have liblzma */
-/* #undef HAVE_LZMA */
+#define HAVE_LZMA 1
/* Define to 1 if you have the <memory.h> header file. */
#define HAVE_MEMORY_H 1
diff --git a/include/dwarf.h b/include/dwarf.h
index 3665e00..96eaaab 100644
--- a/include/dwarf.h
+++ b/include/dwarf.h
@@ -355,6 +355,8 @@
/* The debug frame itself. */
char *debug_frame;
size_t debug_frame_size;
+ /* Relocation amount since debug_frame was compressed. */
+ unw_word_t segbase_bias;
/* Index (for binary search). */
struct table_entry *index;
size_t index_size;
diff --git a/src/dwarf/Gfind_proc_info-lsb.c b/src/dwarf/Gfind_proc_info-lsb.c
index bc04cd1..2e27255 100644
--- a/src/dwarf/Gfind_proc_info-lsb.c
+++ b/src/dwarf/Gfind_proc_info-lsb.c
@@ -91,7 +91,8 @@
/* XXX: Could use mmap; but elf_map_image keeps tons mapped in. */
static int
-load_debug_frame (const char *file, char **buf, size_t *bufsize, int is_local)
+load_debug_frame (const char *file, char **buf, size_t *bufsize,
+ int is_local, Elf_W(Addr)* segbase_bias)
{
FILE *f;
Elf_W (Ehdr) ehdr;
@@ -101,24 +102,24 @@
unsigned int i;
size_t linksize = 0;
char *linkbuf = NULL;
-
+
*buf = NULL;
*bufsize = 0;
-
+
f = fopen (file, "r");
-
+
if (!f)
return 1;
-
+
if (fread (&ehdr, sizeof (Elf_W (Ehdr)), 1, f) != 1)
goto file_error;
/* Verify this is actually an elf file. */
if (memcmp(ehdr.e_ident, ELFMAG, SELFMAG) != 0)
goto file_error;
-
+
shstrndx = ehdr.e_shstrndx;
-
+
Debug (4, "opened file '%s'. Section header at offset %d\n",
file, (int) ehdr.e_shoff);
@@ -135,7 +136,7 @@
fseek (f, sec_hdrs[shstrndx].sh_offset, SEEK_SET);
if (stringtab == NULL || fread (stringtab, 1, sec_size, f) != sec_size)
goto file_error;
-
+
for (i = 1; i < ehdr.e_shnum && *buf == NULL; i++)
{
size_t sec_position = sec_hdrs[i].sh_name;
@@ -148,7 +149,6 @@
{
*bufsize = sec_hdrs[i].sh_size;
*buf = malloc (*bufsize);
-
fseek (f, sec_hdrs[i].sh_offset, SEEK_SET);
if (*buf == NULL || fread (*buf, 1, *bufsize, f) != *bufsize)
goto file_error;
@@ -161,7 +161,6 @@
{
linksize = sec_hdrs[i].sh_size;
linkbuf = malloc (linksize);
-
fseek (f, sec_hdrs[i].sh_offset, SEEK_SET);
if (linkbuf == NULL || fread (linkbuf, 1, linksize, f) != linksize)
goto file_error;
@@ -169,6 +168,62 @@
Debug (4, "read %zd bytes of .gnu_debuglink from offset %ld\n",
linksize, (long) sec_hdrs[i].sh_offset);
}
+ /* ANDROID support update. */
+ else if (sec_position + sizeof(".gnu_debugdata") <= sec_size
+ && strcmp (secname, ".gnu_debugdata") == 0)
+ {
+ size_t xz_size = sec_hdrs[i].sh_size;
+ uint8_t* xz_data = malloc (xz_size);
+ struct elf_image mdi;
+ if (xz_data == NULL)
+ goto file_error;
+ fseek (f, sec_hdrs[i].sh_offset, SEEK_SET);
+ if (fread (xz_data, 1, xz_size, f) != xz_size) {
+ free(xz_data);
+ goto file_error;
+ }
+ Debug (4, "read %zd bytes of .gnu_debugdata from offset %ld\n",
+ xz_size, (long) sec_hdrs[i].sh_offset);
+ if (elf_w (xz_decompress) (xz_data, xz_size,
+ (uint8_t**)&mdi.u.mapped.image, &mdi.u.mapped.size)) {
+ uint8_t* found_section;
+ Elf_W(Addr) old_text_vaddr, new_text_vaddr;
+ mdi.valid = elf_w (valid_object_mapped) (&mdi);
+ mdi.mapped = true;
+ Debug (4, "decompressed .gnu_debugdata\n");
+ if (elf_w (find_section_mapped) (&mdi, ".debug_frame", &found_section, bufsize, NULL)) {
+ Debug (4, "found .debug_frame in .gnu_debugdata\n");
+ *buf = malloc (*bufsize);
+ if (*buf == NULL) {
+ free(xz_data);
+ free(mdi.u.mapped.image);
+ goto file_error;
+ }
+ memcpy(*buf, found_section, *bufsize);
+ // The ELF file might have been relocated since .gnu_debugdata was created.
+ if (elf_w (find_section_mapped) (&mdi, ".text", NULL, NULL, &old_text_vaddr)) {
+ int j;
+ for (j = 1; j < ehdr.e_shnum; j++) {
+ if (sec_hdrs[j].sh_name + sizeof(".text") <= sec_size
+ && strcmp(&stringtab[sec_hdrs[j].sh_name], ".text") == 0) {
+ new_text_vaddr = sec_hdrs[j].sh_addr;
+ *segbase_bias = new_text_vaddr - old_text_vaddr;
+ Debug (4, "ELF file was relocated by 0x%llx bytes since it was created.\n",
+ (unsigned long long)*segbase_bias);
+ break;
+ }
+ }
+ }
+ } else {
+ Debug (1, "can not find .debug_frame inside .gnu_debugdata\n");
+ }
+ free(mdi.u.mapped.image);
+ } else {
+ Debug (1, "failed to decompress .gnu_debugdata\n");
+ }
+ free(xz_data);
+ }
+ /* End of ANDROID update. */
}
free (stringtab);
@@ -208,14 +263,14 @@
strcpy (newname, basedir);
strcat (newname, "/");
strcat (newname, linkbuf);
- ret = load_debug_frame (newname, buf, bufsize, -1);
+ ret = load_debug_frame (newname, buf, bufsize, -1, segbase_bias);
if (ret == 1)
{
strcpy (newname, basedir);
strcat (newname, "/.debug/");
strcat (newname, linkbuf);
- ret = load_debug_frame (newname, buf, bufsize, -1);
+ ret = load_debug_frame (newname, buf, bufsize, -1, segbase_bias);
}
if (ret == 1 && is_local == 1)
@@ -224,7 +279,7 @@
strcat (newname, basedir);
strcat (newname, "/");
strcat (newname, linkbuf);
- ret = load_debug_frame (newname, buf, bufsize, -1);
+ ret = load_debug_frame (newname, buf, bufsize, -1, segbase_bias);
}
free (basedir);
@@ -266,6 +321,7 @@
size_t bufsize;
/* ANDROID support update. */
char *name = NULL;
+ Elf_W(Addr) segbase_bias = 0;
/* End of ANDROID update. */
/* First, see if we loaded this frame already. */
@@ -301,8 +357,8 @@
else
name = (char*) dlname;
- err = load_debug_frame (name, &buf, &bufsize, as == unw_local_addr_space);
-
+ err = load_debug_frame (name, &buf, &bufsize, as == unw_local_addr_space, &segbase_bias);
+
if (!err)
{
fdesc = malloc (sizeof (struct unw_debug_frame_list));
@@ -311,9 +367,10 @@
fdesc->end = end;
fdesc->debug_frame = buf;
fdesc->debug_frame_size = bufsize;
+ fdesc->segbase_bias = segbase_bias;
fdesc->index = NULL;
fdesc->next = as->debug_frames;
-
+
as->debug_frames = fdesc;
}
@@ -343,10 +400,10 @@
tab->size *= 2;
tab->tab = realloc (tab->tab, sizeof (struct table_entry) * tab->size);
}
-
+
tab->tab[length].fde_offset = fde_offset;
tab->tab[length].start_ip_offset = start_ip;
-
+
tab->length = length + 1;
}
@@ -364,7 +421,7 @@
debug_frame_tab_compare (const void *a, const void *b)
{
const struct table_entry *fa = a, *fb = b;
-
+
if (fa->start_ip_offset > fb->start_ip_offset)
return 1;
else if (fa->start_ip_offset < fb->start_ip_offset)
@@ -508,7 +565,7 @@
di->u.ti.name_ptr = (unw_word_t) (uintptr_t) obj_name;
di->u.ti.table_data = (unw_word_t *) fdesc;
di->u.ti.table_len = sizeof (*fdesc) / sizeof (unw_word_t);
- di->u.ti.segbase = segbase;
+ di->u.ti.segbase = segbase + fdesc->segbase_bias;
found = 1;
Debug (15, "found debug_frame table `%s': segbase=0x%lx, len=%lu, "
@@ -580,7 +637,7 @@
else if (phdr->p_type == PT_DYNAMIC)
p_dynamic = phdr;
}
-
+
if (!p_text)
return 0;
diff --git a/src/elfxx.c b/src/elfxx.c
index 6676d0c..63986f0 100644
--- a/src/elfxx.c
+++ b/src/elfxx.c
@@ -29,8 +29,10 @@
#include <stdio.h>
#include <sys/param.h>
-#ifdef HAVE_LZMA
-#include <lzma.h>
+#if HAVE_LZMA
+#include <7zCrc.h>
+#include <Xz.h>
+#include <XzCrc64.h>
#endif /* HAVE_LZMA */
// --------------------------------------------------------------------------
@@ -401,6 +403,19 @@
return false;
}
+static Elf_W(Addr) elf_w (get_min_vaddr_mapped) (struct elf_image *ei) {
+ Elf_W(Ehdr) *ehdr = ei->u.mapped.image;
+ Elf_W(Phdr) *phdr = (Elf_W(Phdr) *) ((char *) ei->u.mapped.image + ehdr->e_phoff);
+ Elf_W(Addr) min_vaddr = ~0u;
+ int i;
+ for (i = 0; i < ehdr->e_phnum; ++i) {
+ if (phdr[i].p_type == PT_LOAD && phdr[i].p_vaddr < min_vaddr) {
+ min_vaddr = phdr[i].p_vaddr;
+ }
+ }
+ return min_vaddr;
+}
+
// --------------------------------------------------------------------------
static inline bool elf_w (lookup_symbol) (
@@ -434,50 +449,75 @@
}
}
-#if HAVE_LZMA
-static size_t xz_uncompressed_size (uint8_t* compressed, size_t length) {
- uint64_t memlimit = UINT64_MAX;
- size_t ret = 0, pos = 0;
- lzma_stream_flags options;
- lzma_index *index;
-
- if (length < LZMA_STREAM_HEADER_SIZE) {
- return 0;
- }
-
- uint8_t *footer = compressed + length - LZMA_STREAM_HEADER_SIZE;
- if (lzma_stream_footer_decode (&options, footer) != LZMA_OK) {
- return 0;
- }
-
- if (length < LZMA_STREAM_HEADER_SIZE + options.backward_size) {
- return 0;
- }
-
- uint8_t* indexdata = footer - options.backward_size;
- if (lzma_index_buffer_decode (&index, &memlimit, NULL, indexdata,
- &pos, options.backward_size) != LZMA_OK) {
- return 0;
- }
-
- if (lzma_index_size (index) == options.backward_size) {
- ret = lzma_index_uncompressed_size (index);
- }
-
- lzma_index_end (index, NULL);
- return ret;
+/* ANDROID support update. */
+static void* xz_alloc(void* p, size_t size) {
+ return malloc(size);
}
-static bool elf_w (extract_minidebuginfo) (struct elf_image* ei, struct elf_image* mdi, Elf_W(Ehdr)* ehdr) {
- Elf_W(Ehdr)* ehdr = ei->image;
- Elf_W(Shdr)* shdr;
+static void xz_free(void* p, void* address) {
+ free(address);
+}
+
+HIDDEN bool
+elf_w (xz_decompress) (uint8_t* src, size_t src_size,
+ uint8_t** dst, size_t* dst_size) {
+#if HAVE_LZMA
+ size_t src_offset = 0;
+ size_t dst_offset = 0;
+ size_t src_remaining;
+ size_t dst_remaining;
+ ISzAlloc alloc;
+ CXzUnpacker state;
+ ECoderStatus status;
+ alloc.Alloc = xz_alloc;
+ alloc.Free = xz_free;
+ XzUnpacker_Construct(&state, &alloc);
+ CrcGenerateTable();
+ Crc64GenerateTable();
+ *dst_size = 2 * src_size;
+ *dst = NULL;
+ do {
+ *dst_size *= 2;
+ *dst = realloc(*dst, *dst_size);
+ src_remaining = src_size - src_offset;
+ dst_remaining = *dst_size - dst_offset;
+ int res = XzUnpacker_Code(&state,
+ *dst + dst_offset, &dst_remaining,
+ src + src_offset, &src_remaining,
+ CODER_FINISH_ANY, &status);
+ if (res != SZ_OK) {
+ Debug (1, "LZMA decompression failed with error %d\n", res);
+ free(*dst);
+ XzUnpacker_Free(&state);
+ return false;
+ }
+ src_offset += src_remaining;
+ dst_offset += dst_remaining;
+ } while (status == CODER_STATUS_NOT_FINISHED);
+ XzUnpacker_Free(&state);
+ if (!XzUnpacker_IsStreamWasFinished(&state)) {
+ Debug (1, "LZMA decompression failed due to incomplete stream.\n");
+ free(*dst);
+ return false;
+ }
+ *dst_size = dst_offset;
+ *dst = realloc(*dst, *dst_size);
+ return true;
+#else
+ Debug (1, "Decompression failed - compiled without LZMA support.\n",
+ return false;
+#endif // HAVE_LZMA
+}
+
+HIDDEN bool
+elf_w (find_section_mapped) (struct elf_image *ei, const char* name,
+ uint8_t** section, size_t* size, Elf_W(Addr)* vaddr) {
+ Elf_W (Ehdr) *ehdr = ei->u.mapped.image;
+ Elf_W (Shdr) *shdr;
char *strtab;
int i;
- uint8_t *compressed = NULL;
- uint64_t memlimit = UINT64_MAX; /* no memory limit */
- size_t compressed_len, uncompressed_len;
- if (!ei->valid) {
+ if (!ei->valid || !ei->mapped) {
return false;
}
@@ -492,61 +532,39 @@
}
for (i = 0; i < ehdr->e_shnum; ++i) {
- if (strcmp (strtab + shdr->sh_name, ".gnu_debugdata") == 0) {
- if (shdr->sh_offset + shdr->sh_size > ei->size) {
- Debug (1, ".gnu_debugdata outside image? (0x%lu > 0x%lu)\n",
- (unsigned long) shdr->sh_offset + shdr->sh_size,
- (unsigned long) ei->size);
- return false;
+ if (strcmp (strtab + shdr->sh_name, name) == 0) {
+ if (section != NULL && size != NULL) {
+ if (shdr->sh_offset + shdr->sh_size > ei->u.mapped.size) {
+ Debug (1, "section %s outside image? (0x%lu > 0x%lu)\n", name,
+ (unsigned long) (shdr->sh_offset + shdr->sh_size),
+ (unsigned long) ei->u.mapped.size);
+ return false;
+ }
+ *section = ((uint8_t *) ei->u.mapped.image) + shdr->sh_offset;
+ *size = shdr->sh_size;
}
-
- Debug (16, "found .gnu_debugdata at 0x%lx\n",
- (unsigned long) shdr->sh_offset);
- compressed = ((uint8_t *) ei->image) + shdr->sh_offset;
- compressed_len = shdr->sh_size;
- break;
+ if (vaddr != NULL) {
+ *vaddr = shdr->sh_addr;
+ }
+ return true;
}
-
- shdr = (Elf_W(Shdr) *) (((char *) shdr) + ehdr->e_shentsize);
+ shdr = (Elf_W (Shdr) *) (((char *) shdr) + ehdr->e_shentsize);
}
-
- /* not found */
- if (!compressed) {
- return false;
- }
-
- uncompressed_len = xz_uncompressed_size (compressed, compressed_len);
- if (uncompressed_len == 0) {
- Debug (1, "invalid .gnu_debugdata contents\n");
- return false;
- }
-
- mdi->size = uncompressed_len;
- mdi->image = mmap (NULL, uncompressed_len, PROT_READ|PROT_WRITE,
- MAP_PRIVATE|MAP_ANONYMOUS, -1, 0);
-
- if (mdi->image == MAP_FAILED) {
- return false;
- }
-
- size_t in_pos = 0, out_pos = 0;
- lzma_ret lret;
- lret = lzma_stream_buffer_decode (&memlimit, 0, NULL,
- compressed, &in_pos, compressed_len,
- mdi->image, &out_pos, mdi->size);
- if (lret != LZMA_OK) {
- Debug (1, "LZMA decompression failed: %d\n", lret);
- munmap (mdi->image, mdi->size);
- return false;
- }
-
- return true;
-}
-#else
-static bool elf_w (extract_minidebuginfo) (struct elf_image* ei, struct elf_image* mdi, Elf_W(Ehdr)* ehdr) {
return false;
}
-#endif /* !HAVE_LZMA */
+
+static bool
+elf_w (extract_minidebuginfo_mapped) (struct elf_image *ei, struct elf_image *mdi)
+{
+ uint8_t *compressed = NULL;
+ size_t compressed_len;
+ if (elf_w (find_section_mapped) (ei, ".gnu_debugdata", &compressed, &compressed_len, NULL)) {
+ return elf_w (xz_decompress) (compressed, compressed_len,
+ (uint8_t**)&mdi->u.mapped.image, &mdi->u.mapped.size);
+ }
+ return false;
+}
+/* ANDROID support update. */
// Find the ELF image that contains IP and return the procedure name from
// the symbol table that matches the IP.
@@ -567,15 +585,17 @@
// If the ELF image doesn't contain a match, look up the symbol in
// the MiniDebugInfo.
struct elf_image mdi;
- if (elf_w (extract_minidebuginfo) (ei, &mdi, &ehdr)) {
- if (!elf_w (get_load_offset) (&mdi, segbase, mapoff, &ehdr, &load_offset)) {
- return false;
- }
- if (elf_w (lookup_symbol) (as, ip, &mdi, load_offset, buf, buf_len, offp, &ehdr)) {
- munmap (mdi.u.mapped.image, mdi.u.mapped.size);
- return true;
- }
- return false;
+ if (ei->mapped && elf_w (extract_minidebuginfo_mapped) (ei, &mdi)) {
+ mdi.valid = elf_w (valid_object_mapped) (&mdi);
+ mdi.mapped = true;
+ // The ELF file might have been relocated after the debug
+ // information has been compresses and embedded.
+ Elf_W(Addr) ei_base_address = elf_w (get_min_vaddr_mapped) (ei);
+ Elf_W(Addr) mdi_base_address = elf_w (get_min_vaddr_mapped) (&mdi);
+ load_offset += ei_base_address - mdi_base_address;
+ bool ret_val = elf_w (lookup_symbol) (as, ip, &mdi, load_offset, buf, buf_len, offp, &ehdr);
+ free(mdi.u.mapped.image);
+ return ret_val;
}
return false;
}
diff --git a/src/elfxx.h b/src/elfxx.h
index dec1b45..01f14f5 100644
--- a/src/elfxx.h
+++ b/src/elfxx.h
@@ -83,6 +83,12 @@
extern size_t elf_w (memory_read) (
struct elf_image* ei, unw_word_t addr, uint8_t* buffer, size_t bytes, bool string_read);
+extern bool elf_w (xz_decompress) (uint8_t* src, size_t src_size,
+ uint8_t** dst, size_t* dst_size);
+
+extern bool elf_w (find_section_mapped) (struct elf_image *ei, const char* name,
+ uint8_t** section, size_t* size, Elf_W(Addr)* vaddr);
+
static inline bool elf_w (valid_object_mapped) (struct elf_image* ei) {
if (ei->u.mapped.size <= EI_VERSION) {
return false;