diff options
author | Elliott Hughes <enh@google.com> | 2015-02-18 22:19:45 -0800 |
---|---|---|
committer | Elliott Hughes <enh@google.com> | 2015-02-18 23:24:43 -0800 |
commit | 03333823c75a1c1887e923828113a1b0fd12020c (patch) | |
tree | a63e40b5dd9927bd04ec7427c8797975c2a71f85 /src/libdwfl/dwfl_segment_report_module.c | |
parent | b48bfdc826ebdd316d5d8a4ff8f213391f1c710b (diff) | |
download | android_external_elfutils-03333823c75a1c1887e923828113a1b0fd12020c.tar.gz android_external_elfutils-03333823c75a1c1887e923828113a1b0fd12020c.tar.bz2 android_external_elfutils-03333823c75a1c1887e923828113a1b0fd12020c.zip |
Upgrade to elfutils 0.161.
Change-Id: Iee07e1329d9750f092afc6c2fbd6af8db32862b0
Diffstat (limited to 'src/libdwfl/dwfl_segment_report_module.c')
-rw-r--r-- | src/libdwfl/dwfl_segment_report_module.c | 385 |
1 files changed, 317 insertions, 68 deletions
diff --git a/src/libdwfl/dwfl_segment_report_module.c b/src/libdwfl/dwfl_segment_report_module.c index 012a0fde..898457f1 100644 --- a/src/libdwfl/dwfl_segment_report_module.c +++ b/src/libdwfl/dwfl_segment_report_module.c @@ -1,56 +1,36 @@ /* Sniff out modules from ELF headers visible in memory segments. - Copyright (C) 2008-2010 Red Hat, Inc. - This file is part of Red Hat elfutils. + Copyright (C) 2008-2012, 2014 Red Hat, Inc. + This file is part of elfutils. - Red Hat elfutils is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by the - Free Software Foundation; version 2 of the License. + This file is free software; you can redistribute it and/or modify + it under the terms of either - Red Hat elfutils is distributed in the hope that it will be useful, but + * the GNU Lesser General Public License as published by the Free + Software Foundation; either version 3 of the License, or (at + your option) any later version + + or + + * the GNU General Public License as published by the Free + Software Foundation; either version 2 of the License, or (at + your option) any later version + + or both in parallel, as here. + + elfutils is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. - You should have received a copy of the GNU General Public License along - with Red Hat elfutils; if not, write to the Free Software Foundation, - Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301 USA. - - In addition, as a special exception, Red Hat, Inc. gives You the - additional right to link the code of Red Hat elfutils with code licensed - under any Open Source Initiative certified open source license - (http://www.opensource.org/licenses/index.php) which requires the - distribution of source code with any binary distribution and to - distribute linked combinations of the two. Non-GPL Code permitted under - this exception must only link to the code of Red Hat elfutils through - those well defined interfaces identified in the file named EXCEPTION - found in the source code files (the "Approved Interfaces"). The files - of Non-GPL Code may instantiate templates or use macros or inline - functions from the Approved Interfaces without causing the resulting - work to be covered by the GNU General Public License. Only Red Hat, - Inc. may make changes or additions to the list of Approved Interfaces. - Red Hat's grant of this exception is conditioned upon your not adding - any new exceptions. If you wish to add a new Approved Interface or - exception, please contact Red Hat. You must obey the GNU General Public - License in all respects for all of the Red Hat elfutils code and other - code used in conjunction with Red Hat elfutils except the Non-GPL Code - covered by this exception. If you modify this file, you may extend this - exception to your version of the file, but you are not obligated to do - so. If you do not wish to provide this exception without modification, - you must delete this exception statement from your version and license - this file solely under the GPL without exception. - - Red Hat elfutils is an included package of the Open Invention Network. - An included package of the Open Invention Network is a package for which - Open Invention Network licensees cross-license their patents. No patent - license is granted, either expressly or impliedly, by designation as an - included package. Should you wish to participate in the Open Invention - Network licensing program, please visit www.openinventionnetwork.com - <http://www.openinventionnetwork.com>. */ + You should have received copies of the GNU General Public License and + the GNU Lesser General Public License along with this program. If + not, see <http://www.gnu.org/licenses/>. */ #include <config.h> #include "../libelf/libelfP.h" /* For NOTE_ALIGN. */ #undef _ #include "libdwflP.h" +#include "common.h" #include <elf.h> #include <gelf.h> @@ -58,6 +38,8 @@ #include <sys/param.h> #include <alloca.h> #include <endian.h> +#include <unistd.h> +#include <fcntl.h> /* A good size for the initial read from memory, if it's not too costly. @@ -99,12 +81,165 @@ addr_segndx (Dwfl *dwfl, size_t segment, GElf_Addr addr, bool next) return ndx; } +/* Return whether there is SZ bytes available at PTR till END. */ + +static bool +buf_has_data (const void *ptr, const void *end, size_t sz) +{ + return ptr < end && (size_t) (end - ptr) >= sz; +} + +/* Read SZ bytes into *RETP from *PTRP (limited by END) in format EI_DATA. + Function comes from src/readelf.c . */ + +static bool +buf_read_ulong (unsigned char ei_data, size_t sz, + const void **ptrp, const void *end, uint64_t *retp) +{ + if (! buf_has_data (*ptrp, end, sz)) + return false; + + union + { + uint64_t u64; + uint32_t u32; + } u; + + memcpy (&u, *ptrp, sz); + (*ptrp) += sz; + + if (retp == NULL) + return true; + + if (MY_ELFDATA != ei_data) + { + if (sz == 4) + CONVERT (u.u32); + else + CONVERT (u.u64); + } + if (sz == 4) + *retp = u.u32; + else + *retp = u.u64; + return true; +} + +/* Try to find matching entry for module from address MODULE_START to + MODULE_END in NT_FILE note located at NOTE_FILE of NOTE_FILE_SIZE + bytes in format EI_CLASS and EI_DATA. */ + +static const char * +handle_file_note (GElf_Addr module_start, GElf_Addr module_end, + unsigned char ei_class, unsigned char ei_data, + const void *note_file, size_t note_file_size) +{ + if (note_file == NULL) + return NULL; + + size_t sz; + switch (ei_class) + { + case ELFCLASS32: + sz = 4; + break; + case ELFCLASS64: + sz = 8; + break; + default: + return NULL; + } + + const void *ptr = note_file; + const void *end = note_file + note_file_size; + uint64_t count; + if (! buf_read_ulong (ei_data, sz, &ptr, end, &count)) + return NULL; + if (! buf_read_ulong (ei_data, sz, &ptr, end, NULL)) // page_size + return NULL; + + uint64_t maxcount = (size_t) (end - ptr) / (3 * sz); + if (count > maxcount) + return NULL; + + /* Where file names are stored. */ + const char *fptr = ptr + 3 * count * sz; + + ssize_t firstix = -1; + ssize_t lastix = -1; + for (size_t mix = 0; mix < count; mix++) + { + uint64_t mstart, mend, moffset; + if (! buf_read_ulong (ei_data, sz, &ptr, fptr, &mstart) + || ! buf_read_ulong (ei_data, sz, &ptr, fptr, &mend) + || ! buf_read_ulong (ei_data, sz, &ptr, fptr, &moffset)) + return NULL; + if (mstart == module_start && moffset == 0) + firstix = lastix = mix; + if (firstix != -1 && mstart < module_end) + lastix = mix; + if (mend >= module_end) + break; + } + if (firstix == -1) + return NULL; + + const char *retval = NULL; + for (ssize_t mix = 0; mix <= lastix; mix++) + { + const char *fnext = memchr (fptr, 0, (const char *) end - fptr); + if (fnext == NULL) + return NULL; + if (mix == firstix) + retval = fptr; + if (firstix < mix && mix <= lastix && strcmp (fptr, retval) != 0) + return NULL; + fptr = fnext + 1; + } + return retval; +} + +/* Return true iff we are certain ELF cannot match BUILD_ID of + BUILD_ID_LEN bytes. Pass DISK_FILE_HAS_BUILD_ID as false if it is + certain ELF does not contain build-id (it is only a performance hit + to pass it always as true). */ + +static bool +invalid_elf (Elf *elf, bool disk_file_has_build_id, + const void *build_id, size_t build_id_len) +{ + if (! disk_file_has_build_id && build_id_len > 0) + { + /* Module found in segments with build-id is more reliable + than a module found via DT_DEBUG on disk without any + build-id. */ + return true; + } + if (disk_file_has_build_id && build_id_len > 0) + { + const void *elf_build_id; + ssize_t elf_build_id_len; + + /* If there is a build id in the elf file, check it. */ + elf_build_id_len = INTUSE(dwelf_elf_gnu_build_id) (elf, &elf_build_id); + if (elf_build_id_len > 0) + { + if (build_id_len != (size_t) elf_build_id_len + || memcmp (build_id, elf_build_id, build_id_len) != 0) + return true; + } + } + return false; +} + int dwfl_segment_report_module (Dwfl *dwfl, int ndx, const char *name, Dwfl_Memory_Callback *memory_callback, void *memory_callback_arg, Dwfl_Module_Callback *read_eagerly, - void *read_eagerly_arg) + void *read_eagerly_arg, + const void *note_file, size_t note_file_size, + const struct r_debug_info *r_debug_info) { size_t segment = ndx; @@ -140,10 +275,16 @@ dwfl_segment_report_module (Dwfl *dwfl, int ndx, const char *name, void *buffer = NULL; size_t buffer_available = INITIAL_READ; + Elf *elf = NULL; + int fd = -1; inline int finish (void) { release_buffer (&buffer, &buffer_available); + if (elf != NULL) + elf_end (elf); + if (fd != -1) + close (fd); return ndx; } @@ -155,7 +296,11 @@ dwfl_segment_report_module (Dwfl *dwfl, int ndx, const char *name, inline bool read_portion (void **data, size_t *data_size, GElf_Addr vaddr, size_t filesz) { - if (vaddr - start + filesz > buffer_available) + if (vaddr - start + filesz > buffer_available + /* If we're in string mode, then don't consider the buffer we have + sufficient unless it contains the terminator of the string. */ + || (filesz == 0 && memchr (vaddr - start + buffer, '\0', + buffer_available - (vaddr - start)) == NULL)) { *data = NULL; *data_size = filesz; @@ -176,6 +321,10 @@ dwfl_segment_report_module (Dwfl *dwfl, int ndx, const char *name, } /* Extract the information we need from the file header. */ + const unsigned char *e_ident; + unsigned char ei_class; + unsigned char ei_data; + uint16_t e_type; union { Elf32_Ehdr e32; @@ -198,13 +347,16 @@ dwfl_segment_report_module (Dwfl *dwfl, int ndx, const char *name, .d_size = sizeof ehdr, .d_version = EV_CURRENT, }; - switch (((const unsigned char *) buffer)[EI_CLASS]) + e_ident = ((const unsigned char *) buffer); + ei_class = e_ident[EI_CLASS]; + ei_data = e_ident[EI_DATA]; + switch (ei_class) { case ELFCLASS32: xlatefrom.d_size = sizeof (Elf32_Ehdr); - if (elf32_xlatetom (&xlateto, &xlatefrom, - ((const unsigned char *) buffer)[EI_DATA]) == NULL) + if (elf32_xlatetom (&xlateto, &xlatefrom, ei_data) == NULL) return finish (); + e_type = ehdr.e32.e_type; phoff = ehdr.e32.e_phoff; phnum = ehdr.e32.e_phnum; phentsize = ehdr.e32.e_phentsize; @@ -215,9 +367,9 @@ dwfl_segment_report_module (Dwfl *dwfl, int ndx, const char *name, case ELFCLASS64: xlatefrom.d_size = sizeof (Elf64_Ehdr); - if (elf64_xlatetom (&xlateto, &xlatefrom, - ((const unsigned char *) buffer)[EI_DATA]) == NULL) + if (elf64_xlatetom (&xlateto, &xlatefrom, ei_data) == NULL) return finish (); + e_type = ehdr.e64.e_type; phoff = ehdr.e64.e_phoff; phnum = ehdr.e64.e_phnum; phentsize = ehdr.e64.e_phentsize; @@ -296,7 +448,7 @@ dwfl_segment_report_module (Dwfl *dwfl, int ndx, const char *name, assert (sizeof (Elf32_Nhdr) == sizeof (Elf64_Nhdr)); void *notes; - if (ehdr.e32.e_ident[EI_DATA] == MY_ELFDATA) + if (ei_data == MY_ELFDATA) notes = data; else { @@ -412,10 +564,9 @@ dwfl_segment_report_module (Dwfl *dwfl, int ndx, const char *name, break; } } - if (ehdr.e32.e_ident[EI_CLASS] == ELFCLASS32) + if (ei_class == ELFCLASS32) { - if (elf32_xlatetom (&xlateto, &xlatefrom, - ehdr.e32.e_ident[EI_DATA]) == NULL) + if (elf32_xlatetom (&xlateto, &xlatefrom, ei_data) == NULL) found_bias = false; /* Trigger error check. */ else for (uint_fast16_t i = 0; i < phnum; ++i) @@ -426,8 +577,7 @@ dwfl_segment_report_module (Dwfl *dwfl, int ndx, const char *name, } else { - if (elf64_xlatetom (&xlateto, &xlatefrom, - ehdr.e32.e_ident[EI_DATA]) == NULL) + if (elf64_xlatetom (&xlateto, &xlatefrom, ei_data) == NULL) found_bias = false; /* Trigger error check. */ else for (uint_fast16_t i = 0; i < phnum; ++i) @@ -442,7 +592,10 @@ dwfl_segment_report_module (Dwfl *dwfl, int ndx, const char *name, /* We must have seen the segment covering offset 0, or else the ELF header we read at START was not produced by these program headers. */ if (unlikely (!found_bias)) - return finish (); + { + free (build_id); + return finish (); + } /* Now we know enough to report a module for sure: its bounds. */ module_start += bias; @@ -450,6 +603,95 @@ dwfl_segment_report_module (Dwfl *dwfl, int ndx, const char *name, dyn_vaddr += bias; + /* NAME found from link map has precedence over DT_SONAME possibly read + below. */ + bool name_is_final = false; + + /* Try to match up DYN_VADDR against L_LD as found in link map. + Segments sniffing may guess invalid address as the first read-only memory + mapping may not be dumped to the core file (if ELF headers are not dumped) + and the ELF header is dumped first with the read/write mapping of the same + file at higher addresses. */ + if (r_debug_info != NULL) + for (const struct r_debug_info_module *module = r_debug_info->module; + module != NULL; module = module->next) + if (module_start <= module->l_ld && module->l_ld < module_end) + { + /* L_LD read from link map must be right while DYN_VADDR is unsafe. + Therefore subtract DYN_VADDR and add L_LD to get a possibly + corrective displacement for all addresses computed so far. */ + GElf_Addr fixup = module->l_ld - dyn_vaddr; + if ((fixup & (dwfl->segment_align - 1)) == 0 + && module_start + fixup <= module->l_ld + && module->l_ld < module_end + fixup) + { + module_start += fixup; + module_end += fixup; + dyn_vaddr += fixup; + bias += fixup; + if (module->name[0] != '\0') + { + name = basename (module->name); + name_is_final = true; + } + break; + } + } + + if (r_debug_info != NULL) + { + bool skip_this_module = false; + for (struct r_debug_info_module *module = r_debug_info->module; + module != NULL; module = module->next) + if ((module_end > module->start && module_start < module->end) + || dyn_vaddr == module->l_ld) + { + if (module->elf != NULL + && invalid_elf (module->elf, module->disk_file_has_build_id, + build_id, build_id_len)) + { + elf_end (module->elf); + close (module->fd); + module->elf = NULL; + module->fd = -1; + } + if (module->elf != NULL) + { + /* Ignore this found module if it would conflict in address + space with any already existing module of DWFL. */ + skip_this_module = true; + } + } + if (skip_this_module) + { + free (build_id); + return finish (); + } + } + + const char *file_note_name = handle_file_note (module_start, module_end, + ei_class, ei_data, + note_file, note_file_size); + if (file_note_name) + { + name = file_note_name; + name_is_final = true; + bool invalid = false; + fd = open64 (name, O_RDONLY); + if (fd >= 0) + { + Dwfl_Error error = __libdw_open_file (&fd, &elf, true, false); + if (error == DWFL_E_NOERROR) + invalid = invalid_elf (elf, true /* disk_file_has_build_id */, + build_id, build_id_len); + } + if (invalid) + { + free (build_id); + return finish (); + } + } + /* Our return value now says to skip the segments contained within the module. */ ndx = addr_segndx (dwfl, segment, module_end, true); @@ -491,7 +733,7 @@ dwfl_segment_report_module (Dwfl *dwfl, int ndx, const char *name, return soname_stroff != 0 && dynstr_vaddr != 0 && dynstrsz != 0; } - const size_t dyn_entsize = (ehdr.e32.e_ident[EI_CLASS] == ELFCLASS32 + const size_t dyn_entsize = (ei_class == ELFCLASS32 ? sizeof (Elf32_Dyn) : sizeof (Elf64_Dyn)); void *dyn_data = NULL; size_t dyn_data_size = 0; @@ -510,18 +752,16 @@ dwfl_segment_report_module (Dwfl *dwfl, int ndx, const char *name, xlateto.d_buf = &dyn; xlateto.d_size = sizeof dyn; - if (ehdr.e32.e_ident[EI_CLASS] == ELFCLASS32) + if (ei_class == ELFCLASS32) { - if (elf32_xlatetom (&xlateto, &xlatefrom, - ehdr.e32.e_ident[EI_DATA]) != NULL) + if (elf32_xlatetom (&xlateto, &xlatefrom, ei_data) != NULL) for (size_t i = 0; i < dyn_filesz / sizeof dyn.d32[0]; ++i) if (consider_dyn (dyn.d32[i].d_tag, dyn.d32[i].d_un.d_val)) break; } else { - if (elf64_xlatetom (&xlateto, &xlatefrom, - ehdr.e32.e_ident[EI_DATA]) != NULL) + if (elf64_xlatetom (&xlateto, &xlatefrom, ei_data) != NULL) for (size_t i = 0; i < dyn_filesz / sizeof dyn.d64[0]; ++i) if (consider_dyn (dyn.d64[i].d_tag, dyn.d64[i].d_un.d_val)) break; @@ -531,11 +771,11 @@ dwfl_segment_report_module (Dwfl *dwfl, int ndx, const char *name, /* We'll use the name passed in or a stupid default if not DT_SONAME. */ if (name == NULL) - name = ehdr.e32.e_type == ET_EXEC ? "[exe]" : execlike ? "[pie]" : "[dso]"; + name = e_type == ET_EXEC ? "[exe]" : execlike ? "[pie]" : "[dso]"; void *soname = NULL; size_t soname_size = 0; - if (dynstrsz != 0 && dynstr_vaddr != 0) + if (! name_is_final && dynstrsz != 0 && dynstr_vaddr != 0) { /* We know the bounds of the .dynstr section. @@ -569,6 +809,12 @@ dwfl_segment_report_module (Dwfl *dwfl, int ndx, const char *name, Dwfl_Module *mod = INTUSE(dwfl_report_module) (dwfl, name, module_start, module_end); + + // !execlike && ET_EXEC is PIE. + // execlike && !ET_EXEC is a static executable. + if (mod != NULL && (execlike || ehdr.e32.e_type == ET_EXEC)) + mod->is_executable = true; + if (likely (mod != NULL) && build_id != NULL && unlikely (INTUSE(dwfl_module_report_build_id) (mod, build_id, @@ -600,10 +846,10 @@ dwfl_segment_report_module (Dwfl *dwfl, int ndx, const char *name, : dynstr_vaddr + dynstrsz - start); const GElf_Off whole = MAX (file_trimmed_end, shdrs_end); - Elf *elf = NULL; - if ((*read_eagerly) (MODCB_ARGS (mod), &buffer, &buffer_available, - cost, worthwhile, whole, contiguous, - read_eagerly_arg, &elf) + if (elf == NULL + && (*read_eagerly) (MODCB_ARGS (mod), &buffer, &buffer_available, + cost, worthwhile, whole, contiguous, + read_eagerly_arg, &elf) && elf == NULL) { /* The caller wants to read the whole file in right now, but hasn't @@ -633,7 +879,7 @@ dwfl_segment_report_module (Dwfl *dwfl, int ndx, const char *name, final_read (offset, vaddr + bias, filesz); } - if (ehdr.e32.e_ident[EI_CLASS] == ELFCLASS32) + if (ei_class == ELFCLASS32) for (uint_fast16_t i = 0; i < phnum; ++i) read_phdr (phdrs.p32[i].p_type, phdrs.p32[i].p_vaddr, phdrs.p32[i].p_offset, phdrs.p32[i].p_filesz); @@ -665,8 +911,11 @@ dwfl_segment_report_module (Dwfl *dwfl, int ndx, const char *name, { /* Install the file in the module. */ mod->main.elf = elf; + elf = NULL; + fd = -1; mod->main.vaddr = module_start - bias; mod->main.address_sync = module_address_sync; + mod->main_bias = bias; } return finish (); |