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;