summaryrefslogtreecommitdiffstats
path: root/src/libdwfl/dwfl_segment_report_module.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/libdwfl/dwfl_segment_report_module.c')
-rw-r--r--src/libdwfl/dwfl_segment_report_module.c385
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 ();