2106 lines
63 KiB
Diff
2106 lines
63 KiB
Diff
From 481a17c207809d10efcc87c1f831c5eef83c069f Mon Sep 17 00:00:00 2001
|
|
Message-Id: <481a17c207809d10efcc87c1f831c5eef83c069f.1488964568.git.pmatilai@redhat.com>
|
|
In-Reply-To: <189b4f88c8e100155ec23a1e0b214bdc8473532a.1488964568.git.pmatilai@redhat.com>
|
|
References: <189b4f88c8e100155ec23a1e0b214bdc8473532a.1488964568.git.pmatilai@redhat.com>
|
|
From: Mark Wielaard <mark@klomp.org>
|
|
Date: Fri, 17 Feb 2017 14:13:57 +0100
|
|
Subject: [PATCH 10/11] debugedit: Support String/Line table rewriting for
|
|
larger/smaller paths.
|
|
|
|
debugedit --base to --dest rewriting of debug source file paths only
|
|
supported dest paths that were smaller or equal than the base path
|
|
(and the size should differ more than 1 character for correct debug lines).
|
|
All paths were changed "in place". Which could in theory mess up debug str
|
|
sharing.
|
|
|
|
This rewrite supports base and dest strings of any size (some limitations,
|
|
see below). This is done by reconstructing the debug_str and debug_line
|
|
tables and updating the references in the debug_info attributes pointing
|
|
to these tables. Plus, if necessary (only for ET_REL kernel modules),
|
|
updating any relocations for the debug_info and debug_line sections.
|
|
|
|
This has the nice benefit of merging any duplicate strings in the
|
|
debug_str table which might resulting on slightly smaller files.
|
|
kernel modules are ET_REL files that often contain a lot of duplicate
|
|
strings.
|
|
|
|
The rewrite uses elfutils (either libebl or libdw) to reconstruct the
|
|
debug_str table. Since we are changing some section sizes now we cannot
|
|
just use mmap and rawdata to poke the values, but need to read in and
|
|
write out the changed sections. This does take a bit more memory because
|
|
we now also need to keep track of all string/line references.
|
|
|
|
There are still some limitations (already in the original debugedit)
|
|
not fixed by this rewrite:
|
|
- DW_AT_comp_dir in .debug_info using DW_FORM_string can not be made
|
|
larger. We only warn about that now instead of failing. The only
|
|
producer of DW_FORM_string comp_dirs is binutils gas. It seems simpler
|
|
to fix gas than to try to support resizing the debug_info section.
|
|
- A DW_AT_name on a DW_TAG_compile_unit is only rewritten for DW_FORM_strp
|
|
not for DW_FORM_string. Probably no problem in practice since this
|
|
wasn't supported originally either.
|
|
- The debug_line program isn't scanned for DW_LNE_define_file which
|
|
could in theory define an absolute path that might need rewriting.
|
|
Again probably not a problem because this wasn't supported before
|
|
and there are no know producers for this construct.
|
|
|
|
To support the upcoming DWARFv5 in gcc 7 (not on by default), we will
|
|
need to add support for the new debug_line format and scan the new
|
|
debug_macro section that can have references to the debug_str table.
|
|
|
|
Signed-off-by: Mark Wielaard <mark@klomp.org>
|
|
---
|
|
Makefile.am | 8 +-
|
|
configure.ac | 6 +
|
|
tools/debugedit.c | 1569 ++++++++++++++++++++++++++++++++++++++++++++---------
|
|
3 files changed, 1330 insertions(+), 253 deletions(-)
|
|
|
|
diff --git a/Makefile.am b/Makefile.am
|
|
index 6b37b58..1b77730 100644
|
|
--- a/Makefile.am
|
|
+++ b/Makefile.am
|
|
@@ -156,13 +156,18 @@ rpm2archive_LDADD += @WITH_NSS_LIB@ @WITH_POPT_LIB@ @WITH_ZLIB_LIB@ @WITH_ARCHIV
|
|
|
|
if LIBELF
|
|
if LIBDWARF
|
|
+if LIBDW
|
|
rpmconfig_SCRIPTS += scripts/find-debuginfo.sh
|
|
|
|
rpmlibexec_PROGRAMS += debugedit
|
|
debugedit_SOURCES = tools/debugedit.c tools/hashtab.c tools/hashtab.h
|
|
debugedit_LDADD = rpmio/librpmio.la
|
|
debugedit_LDADD += @WITH_LIBELF_LIB@ @WITH_POPT_LIB@
|
|
-
|
|
+if HAVE_LIBDW_STRTAB
|
|
+debugedit_LDADD += @WITH_LIBDW_LIB@
|
|
+else
|
|
+debugedit_LDADD += @WITH_LIBDW_LIB@ -lebl
|
|
+endif
|
|
rpmlibexec_PROGRAMS += elfdeps
|
|
elfdeps_SOURCES = tools/elfdeps.c
|
|
elfdeps_LDADD = rpmio/librpmio.la
|
|
@@ -173,6 +178,7 @@ sepdebugcrcfix_SOURCES = tools/sepdebugcrcfix.c
|
|
sepdebugcrcfix_LDADD = @WITH_LIBELF_LIB@
|
|
endif
|
|
endif
|
|
+endif
|
|
|
|
rpmlibexec_PROGRAMS += rpmdeps
|
|
rpmdeps_SOURCES = tools/rpmdeps.c
|
|
diff --git a/configure.ac b/configure.ac
|
|
index 4baa3f1..743fe08 100644
|
|
--- a/configure.ac
|
|
+++ b/configure.ac
|
|
@@ -369,18 +369,24 @@ AM_CONDITIONAL(WITH_ARCHIVE,[test "$with_archive" = yes])
|
|
#=================
|
|
# Check for elfutils libdw library with dwelf_elf_gnu_build_id.
|
|
WITH_LIBDW_LIB=
|
|
+HAVE_LIBDW_STRTAB=
|
|
AS_IF([test "$WITH_LIBELF" = yes],[
|
|
AC_CHECK_HEADERS([elfutils/libdwelf.h],[
|
|
+ # dwelf_elf_gnu_build_id was introduced in elfutils 0.159
|
|
AC_CHECK_LIB(dw, dwelf_elf_gnu_build_id, [
|
|
AC_DEFINE(HAVE_LIBDW, 1,
|
|
[Define to 1 if you have elfutils libdw library])
|
|
WITH_LIBDW_LIB="-ldw"
|
|
WITH_LIBDW=yes
|
|
+ # If possible we also want the strtab functions from elfutils 0.167.
|
|
+ # But we can fall back on the (unsupported) ebl alternatives if not.
|
|
+ AC_CHECK_LIB(dw, dwelf_strtab_init, [HAVE_LIBDW_STRTAB=yes])
|
|
])
|
|
])
|
|
])
|
|
AC_SUBST(WITH_LIBDW_LIB)
|
|
AM_CONDITIONAL(LIBDW,[test "$WITH_LIBDW" = yes])
|
|
+AM_CONDITIONAL(HAVE_LIBDW_STRTAB,[test "$HAVE_LIBDW_STRTAB" = yes])
|
|
|
|
#=================
|
|
# Process --with/without-external-db
|
|
diff --git a/tools/debugedit.c b/tools/debugedit.c
|
|
index c0147f0..4798c63 100644
|
|
--- a/tools/debugedit.c
|
|
+++ b/tools/debugedit.c
|
|
@@ -1,6 +1,7 @@
|
|
-/* Copyright (C) 2001-2003, 2005, 2007, 2009-2011, 2016 Red Hat, Inc.
|
|
+/* Copyright (C) 2001-2003, 2005, 2007, 2009-2011, 2016, 2017 Red Hat, Inc.
|
|
Written by Alexander Larsson <alexl@redhat.com>, 2002
|
|
Based on code by Jakub Jelinek <jakub@redhat.com>, 2001.
|
|
+ String/Line table rewriting by Mark Wielaard <mjw@redhat.com>, 2017.
|
|
|
|
This program is free software; you can redistribute it and/or modify
|
|
it under the terms of the GNU General Public License as published by
|
|
@@ -30,6 +31,7 @@
|
|
#include <string.h>
|
|
#include <stdlib.h>
|
|
#include <stdint.h>
|
|
+#include <inttypes.h>
|
|
#include <unistd.h>
|
|
#include <sys/types.h>
|
|
#include <sys/stat.h>
|
|
@@ -39,6 +41,35 @@
|
|
#include <gelf.h>
|
|
#include <dwarf.h>
|
|
|
|
+/* Unfortunately strtab manipulation functions were only officially added
|
|
+ to elfutils libdw in 0.167. Before that there were internal unsupported
|
|
+ ebl variants. While libebl.h isn't supported we'll try to use it anyway
|
|
+ if the elfutils we build against is too old. */
|
|
+#include <elfutils/version.h>
|
|
+#if _ELFUTILS_PREREQ (0, 167)
|
|
+#include <elfutils/libdwelf.h>
|
|
+typedef Dwelf_Strent Strent;
|
|
+typedef Dwelf_Strtab Strtab;
|
|
+#define strtab_init dwelf_strtab_init
|
|
+#define strtab_add(X,Y) dwelf_strtab_add(X,Y)
|
|
+#define strtab_add_len(X,Y,Z) dwelf_strtab_add_len(X,Y,Z)
|
|
+#define strtab_free dwelf_strtab_free
|
|
+#define strtab_finalize dwelf_strtab_finalize
|
|
+#define strent_offset dwelf_strent_off
|
|
+#else
|
|
+#include <elfutils/libebl.h>
|
|
+typedef struct Ebl_Strent Strent;
|
|
+typedef struct Ebl_Strtab Strtab;
|
|
+#define strtab_init ebl_strtabinit
|
|
+#define strtab_add(X,Y) ebl_strtabadd(X,Y,0)
|
|
+#define strtab_add_len(X,Y,Z) ebl_strtabadd(X,Y,Z)
|
|
+#define strtab_free ebl_strtabfree
|
|
+#define strtab_finalize ebl_strtabfinalize
|
|
+#define strent_offset ebl_strtaboffset
|
|
+#endif
|
|
+
|
|
+#include <search.h>
|
|
+
|
|
#include <rpm/rpmio.h>
|
|
#include <rpm/rpmpgp.h>
|
|
#include "tools/hashtab.h"
|
|
@@ -56,6 +87,99 @@ int list_file_fd = -1;
|
|
int do_build_id = 0;
|
|
char *build_id_seed = NULL;
|
|
|
|
+/* We go over the debug sections in two phases. In phase zero we keep
|
|
+ track of any needed changes and collect strings, indexes and
|
|
+ sizes. In phase one we do the actual replacements updating the
|
|
+ strings, indexes and writing out new debug sections. The following
|
|
+ keep track of various changes that might be needed. */
|
|
+
|
|
+/* Whether we need to do any literal string (DW_FORM_string) replacements
|
|
+ in debug_info. */
|
|
+static bool need_string_replacement = false;
|
|
+/* Whether we need to do any updates of the string indexes (DW_FORM_strp)
|
|
+ in debug_info for string indexes. */
|
|
+static bool need_strp_update = false;
|
|
+/* If the debug_line changes size we will need to update the
|
|
+ DW_AT_stmt_list attributes indexes in the debug_info. */
|
|
+static bool need_stmt_update = false;
|
|
+
|
|
+/* Storage for dynamically allocated strings to put into string
|
|
+ table. Keep together in memory blocks of 16K. */
|
|
+#define STRMEMSIZE (16 * 1024)
|
|
+struct strmemblock
|
|
+{
|
|
+ struct strmemblock *next;
|
|
+ char memory[0];
|
|
+};
|
|
+
|
|
+/* We keep track of each index in the original string table and the
|
|
+ associated entry in the new table so we don't insert identical
|
|
+ strings into the new string table. If constructed correctly the
|
|
+ original strtab shouldn't contain duplicate strings anyway. Any
|
|
+ actual identical strings could be deduplicated, but searching for
|
|
+ and comparing the indexes is much faster than comparing strings
|
|
+ (and we don't have to construct replacement strings). */
|
|
+struct stridxentry
|
|
+{
|
|
+ uint32_t idx; /* Original index in the string table. */
|
|
+ Strent *entry; /* Entry in the new table. */
|
|
+};
|
|
+
|
|
+/* Storage for new string table entries. Keep together in memory to
|
|
+ quickly search through them with tsearch. */
|
|
+#define STRIDXENTRIES ((16 * 1024) / sizeof (struct stridxentry))
|
|
+struct strentblock
|
|
+{
|
|
+ struct strentblock *next;
|
|
+ struct stridxentry entry[0];
|
|
+};
|
|
+
|
|
+/* All data to keep track of the existing and new string table. */
|
|
+struct strings
|
|
+{
|
|
+ Strtab *str_tab; /* The new string table. */
|
|
+ char *str_buf; /* New Elf_Data d_buf. */
|
|
+ struct strmemblock *blocks; /* The first strmemblock. */
|
|
+ struct strmemblock *last_block; /* The currently used strmemblock. */
|
|
+ size_t stridx; /* Next free byte in last block. */
|
|
+ struct strentblock *entries; /* The first string index block. */
|
|
+ struct strentblock *last_entries; /* The currently used strentblock. */
|
|
+ size_t entryidx; /* Next free entry in the last block. */
|
|
+ void *strent_root; /* strent binary search tree root. */
|
|
+};
|
|
+
|
|
+struct line_table
|
|
+{
|
|
+ size_t old_idx; /* Original offset. */
|
|
+ size_t new_idx; /* Offset in new debug_line section. */
|
|
+ ssize_t size_diff; /* Difference in (header) size. */
|
|
+ bool replace_dirs; /* Whether to replace any dir paths. */
|
|
+ bool replace_files; /* Whether to replace any file paths. */
|
|
+
|
|
+ /* Header fields. */
|
|
+ uint32_t unit_length;
|
|
+ uint16_t version;
|
|
+ uint32_t header_length;
|
|
+ uint8_t min_instr_len;
|
|
+ uint8_t max_op_per_instr; /* Only if version >= 4 */
|
|
+ uint8_t default_is_stmt;
|
|
+ int8_t line_base;
|
|
+ uint8_t line_range;
|
|
+ uint8_t opcode_base;
|
|
+};
|
|
+
|
|
+struct debug_lines
|
|
+{
|
|
+ struct line_table *table; /* Malloc/Realloced. */
|
|
+ size_t size; /* Total number of line_tables.
|
|
+ Updated by get_line_table. */
|
|
+ size_t used; /* Used number of line_tables.
|
|
+ Updated by get_line_table. */
|
|
+ size_t debug_lines_len; /* Total size of new debug_line section.
|
|
+ updated by edit_dwarf2_line. */
|
|
+ char *line_buf; /* New Elf_Data d_buf. */
|
|
+};
|
|
+
|
|
typedef struct
|
|
{
|
|
Elf *elf;
|
|
@@ -63,15 +187,42 @@ typedef struct
|
|
Elf_Scn **scn;
|
|
const char *filename;
|
|
int lastscn;
|
|
+ size_t phnum;
|
|
+ struct strings strings;
|
|
+ struct debug_lines lines;
|
|
GElf_Shdr shdr[0];
|
|
} DSO;
|
|
|
|
+static void
|
|
+setup_lines (struct debug_lines *lines)
|
|
+{
|
|
+ lines->table = NULL;
|
|
+ lines->size = 0;
|
|
+ lines->used = 0;
|
|
+ lines->debug_lines_len = 0;
|
|
+ lines->line_buf = NULL;
|
|
+}
|
|
+
|
|
+static void
|
|
+destroy_lines (struct debug_lines *lines)
|
|
+{
|
|
+ free (lines->table);
|
|
+ free (lines->line_buf);
|
|
+}
|
|
+
|
|
typedef struct
|
|
{
|
|
unsigned char *ptr;
|
|
uint32_t addend;
|
|
+ int ndx;
|
|
} REL;
|
|
|
|
+typedef struct
|
|
+{
|
|
+ Elf64_Addr r_offset;
|
|
+ int ndx;
|
|
+} LINE_REL;
|
|
+
|
|
#define read_uleb128(ptr) ({ \
|
|
unsigned int ret = 0; \
|
|
unsigned int c; \
|
|
@@ -88,9 +239,23 @@ typedef struct
|
|
ret; \
|
|
})
|
|
|
|
+#define write_uleb128(ptr,val) ({ \
|
|
+ uint32_t valv = (val); \
|
|
+ do \
|
|
+ { \
|
|
+ unsigned char c = valv & 0x7f; \
|
|
+ valv >>= 7; \
|
|
+ if (valv) \
|
|
+ c |= 0x80; \
|
|
+ *ptr++ = c; \
|
|
+ } \
|
|
+ while (valv); \
|
|
+})
|
|
+
|
|
static uint16_t (*do_read_16) (unsigned char *ptr);
|
|
static uint32_t (*do_read_32) (unsigned char *ptr);
|
|
-static void (*write_32) (unsigned char *ptr, GElf_Addr val);
|
|
+static void (*do_write_16) (unsigned char *ptr, uint16_t val);
|
|
+static void (*do_write_32) (unsigned char *ptr, uint32_t val);
|
|
|
|
static int ptr_size;
|
|
static int cu_version;
|
|
@@ -129,7 +294,7 @@ strptr (DSO *dso, int sec, off_t offset)
|
|
if (offset >= 0 && (GElf_Addr) offset < dso->shdr[sec].sh_size)
|
|
{
|
|
data = NULL;
|
|
- while ((data = elf_rawdata (scn, data)) != NULL)
|
|
+ while ((data = elf_getdata (scn, data)) != NULL)
|
|
{
|
|
if (data->d_buf
|
|
&& offset >= data->d_off
|
|
@@ -142,7 +307,7 @@ strptr (DSO *dso, int sec, off_t offset)
|
|
}
|
|
|
|
|
|
-#define read_1(ptr) *ptr++
|
|
+#define read_8(ptr) *ptr++
|
|
|
|
#define read_16(ptr) ({ \
|
|
uint16_t ret = do_read_16 (ptr); \
|
|
@@ -183,28 +348,73 @@ int reltype;
|
|
})
|
|
|
|
static void
|
|
-dwarf2_write_le32 (unsigned char *p, GElf_Addr val)
|
|
+dwarf2_write_le16 (unsigned char *p, uint16_t v)
|
|
{
|
|
- uint32_t v = (uint32_t) val;
|
|
+ p[0] = v;
|
|
+ p[1] = v >> 8;
|
|
+}
|
|
|
|
+static void
|
|
+dwarf2_write_le32 (unsigned char *p, uint32_t v)
|
|
+{
|
|
p[0] = v;
|
|
p[1] = v >> 8;
|
|
p[2] = v >> 16;
|
|
p[3] = v >> 24;
|
|
}
|
|
|
|
-
|
|
static void
|
|
-dwarf2_write_be32 (unsigned char *p, GElf_Addr val)
|
|
+dwarf2_write_be16 (unsigned char *p, uint16_t v)
|
|
{
|
|
- uint32_t v = (uint32_t) val;
|
|
+ p[1] = v;
|
|
+ p[0] = v >> 8;
|
|
+}
|
|
|
|
+static void
|
|
+dwarf2_write_be32 (unsigned char *p, uint32_t v)
|
|
+{
|
|
p[3] = v;
|
|
p[2] = v >> 8;
|
|
p[1] = v >> 16;
|
|
p[0] = v >> 24;
|
|
}
|
|
|
|
+#define write_8(ptr,val) ({ \
|
|
+ *ptr++ = (val); \
|
|
+})
|
|
+
|
|
+#define write_16(ptr,val) ({ \
|
|
+ do_write_16 (ptr,val); \
|
|
+ ptr += 2; \
|
|
+})
|
|
+
|
|
+#define write_32(ptr,val) ({ \
|
|
+ do_write_32 (ptr,val); \
|
|
+ ptr += 4; \
|
|
+})
|
|
+
|
|
+/* relocated writes can only be called immediately after
|
|
+ do_read_32_relocated. ptr must be equal to relptr->ptr (or
|
|
+ relend). Might just update the addend. So relocations need to be
|
|
+ updated at the end. */
|
|
+
|
|
+#define do_write_32_relocated(ptr,val) ({ \
|
|
+ if (relptr && relptr < relend && relptr->ptr == ptr) \
|
|
+ { \
|
|
+ if (reltype == SHT_REL) \
|
|
+ do_write_32 (ptr, val - relptr->addend); \
|
|
+ else \
|
|
+ relptr->addend = val; \
|
|
+ } \
|
|
+ else \
|
|
+ do_write_32 (ptr,val); \
|
|
+})
|
|
+
|
|
+#define write_32_relocated(ptr,val) ({ \
|
|
+ do_write_32_relocated (ptr,val); \
|
|
+ ptr += 4; \
|
|
+})
|
|
+
|
|
static struct
|
|
{
|
|
const char *name;
|
|
@@ -448,90 +658,638 @@ canonicalize_path (const char *s, char *d)
|
|
return rv;
|
|
}
|
|
|
|
+/* Returns the rest of PATH if it starts with DIR_PREFIX, skipping any
|
|
+ / path separators, or NULL if PATH doesn't start with
|
|
+ DIR_PREFIX. Might return the empty string if PATH equals DIR_PREFIX
|
|
+ (modulo trailing slashes). Never returns path starting with '/'. */
|
|
+static const char *
|
|
+skip_dir_prefix (const char *path, const char *dir_prefix)
|
|
+{
|
|
+ size_t prefix_len = strlen (dir_prefix);
|
|
+ if (strncmp (path, dir_prefix, prefix_len) == 0)
|
|
+ {
|
|
+ path += prefix_len;
|
|
+ while (IS_DIR_SEPARATOR (path[0]))
|
|
+ path++;
|
|
+ return path;
|
|
+ }
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+/* Most strings will be in the existing debug string table. But to
|
|
+ replace the base/dest directory prefix we need some new storage.
|
|
+ Keep new strings somewhat close together for faster comparison and
|
|
+ copying. SIZE should be at least one (and includes space for the
|
|
+ zero terminator). The returned pointer points to uninitialized
|
|
+ data. */
|
|
+static char *
|
|
+new_string_storage (struct strings *strings, size_t size)
|
|
+{
|
|
+ assert (size > 0);
|
|
+
|
|
+ /* If the string is extra long just create a whole block for
|
|
+ it. Normally strings are much smaller than STRMEMSIZE. */
|
|
+ if (strings->last_block == NULL
|
|
+ || size > STRMEMSIZE
|
|
+ || strings->stridx > STRMEMSIZE
|
|
+ || (STRMEMSIZE - strings->stridx) < size)
|
|
+ {
|
|
+ struct strmemblock *newblock = malloc (sizeof (struct strmemblock)
|
|
+ + MAX (STRMEMSIZE, size));
|
|
+ if (newblock == NULL)
|
|
+ return NULL;
|
|
+
|
|
+ newblock->next = NULL;
|
|
+
|
|
+ if (strings->blocks == NULL)
|
|
+ strings->blocks = newblock;
|
|
+
|
|
+ if (strings->last_block != NULL)
|
|
+ strings->last_block->next = newblock;
|
|
+
|
|
+ strings->last_block = newblock;
|
|
+ strings->stridx = 0;
|
|
+ }
|
|
+
|
|
+ size_t stridx = strings->stridx;
|
|
+ strings->stridx += size + 1;
|
|
+ return &strings->last_block->memory[stridx];
|
|
+}
|
|
+
|
|
+/* Comparison function used for tsearch. */
|
|
static int
|
|
-has_prefix (const char *str,
|
|
- const char *prefix)
|
|
+strent_compare (const void *a, const void *b)
|
|
{
|
|
- size_t str_len;
|
|
- size_t prefix_len;
|
|
+ struct stridxentry *entry_a = (struct stridxentry *)a;
|
|
+ struct stridxentry *entry_b = (struct stridxentry *)b;
|
|
+ size_t idx_a = entry_a->idx;
|
|
+ size_t idx_b = entry_b->idx;
|
|
|
|
- str_len = strlen (str);
|
|
- prefix_len = strlen (prefix);
|
|
+ if (idx_a < idx_b)
|
|
+ return -1;
|
|
|
|
- if (str_len < prefix_len)
|
|
- return 0;
|
|
+ if (idx_a > idx_b)
|
|
+ return 1;
|
|
|
|
- return strncmp (str, prefix, prefix_len) == 0;
|
|
+ return 0;
|
|
}
|
|
|
|
-static int dirty_elf;
|
|
+/* Allocates and inserts a new entry for the old index if not yet
|
|
+ seen. Returns a stridxentry if the given index has not yet been
|
|
+ seen and needs to be filled in with the associated string (either
|
|
+ the original string or the replacement string). Returns NULL if the
|
|
+ idx is already known. Use in phase 0 to add all strings seen. In
|
|
+ phase 1 use string_find_entry instead to get existing entries. */
|
|
+static struct stridxentry *
|
|
+string_find_new_entry (struct strings *strings, size_t old_idx)
|
|
+{
|
|
+ /* Use next entry in the pool for lookup so we can use it directly
|
|
+ if this is a new index. */
|
|
+ struct stridxentry *entry;
|
|
+
|
|
+ /* Keep entries close together to make key comparison fast. */
|
|
+ if (strings->last_entries == NULL || strings->entryidx >= STRIDXENTRIES)
|
|
+ {
|
|
+ size_t entriessz = (sizeof (struct strentblock)
|
|
+ + (STRIDXENTRIES * sizeof (struct stridxentry)));
|
|
+ struct strentblock *newentries = malloc (entriessz);
|
|
+ if (newentries == NULL)
|
|
+ error (1, errno, "Couldn't allocate new string entries block");
|
|
+ else
|
|
+ {
|
|
+ if (strings->entries == NULL)
|
|
+ strings->entries = newentries;
|
|
+
|
|
+ if (strings->last_entries != NULL)
|
|
+ strings->last_entries->next = newentries;
|
|
+
|
|
+ strings->last_entries = newentries;
|
|
+ strings->last_entries->next = NULL;
|
|
+ strings->entryidx = 0;
|
|
+ }
|
|
+ }
|
|
+
|
|
+ entry = &strings->last_entries->entry[strings->entryidx];
|
|
+ entry->idx = old_idx;
|
|
+ struct stridxentry **tres = tsearch (entry, &strings->strent_root,
|
|
+ strent_compare);
|
|
+ if (tres == NULL)
|
|
+ error (1, ENOMEM, "Couldn't insert new strtab idx");
|
|
+ else if (*tres == entry)
|
|
+ {
|
|
+ /* idx not yet seen, must add actual str. */
|
|
+ strings->entryidx++;
|
|
+ return entry;
|
|
+ }
|
|
+
|
|
+ return NULL; /* We already know about this idx, entry already complete. */
|
|
+}
|
|
+
|
|
+static struct stridxentry *
|
|
+string_find_entry (struct strings *strings, size_t old_idx)
|
|
+{
|
|
+ struct stridxentry **ret;
|
|
+ struct stridxentry key;
|
|
+ key.idx = old_idx;
|
|
+ ret = tfind (&key, &strings->strent_root, strent_compare);
|
|
+ assert (ret != NULL); /* Can only happen for a bad/non-existing old_idx. */
|
|
+ return *ret;
|
|
+}
|
|
+
|
|
+/* Adds a string_idx_entry given an index into the old/existing string
|
|
+ table. Should be used in phase 0. Does nothing if the index was
|
|
+ already registered. Otherwise it checks the string associated with
|
|
+ the index. If the old string doesn't start with base_dir an entry
|
|
+ will be recorded for the index with the same string. Otherwise a
|
|
+ string will be recorded where the base_dir prefix will be replaced
|
|
+ by dest_dir. Returns true if this is a not yet seen index and there
|
|
+ a replacement file string has been recorded for it, otherwise
|
|
+ returns false. */
|
|
+static bool
|
|
+record_file_string_entry_idx (struct strings *strings, size_t old_idx)
|
|
+{
|
|
+ bool ret = false;
|
|
+ struct stridxentry *entry = string_find_new_entry (strings, old_idx);
|
|
+ if (entry != NULL)
|
|
+ {
|
|
+ Strent *strent;
|
|
+ const char *old_str = (char *)debug_sections[DEBUG_STR].data + old_idx;
|
|
+ const char *file = skip_dir_prefix (old_str, base_dir);
|
|
+ if (file == NULL)
|
|
+ {
|
|
+ /* Just record the existing string. */
|
|
+ strent = strtab_add_len (strings->str_tab, old_str,
|
|
+ strlen (old_str) + 1);
|
|
+ }
|
|
+ else
|
|
+ {
|
|
+ /* Create and record the altered file path. */
|
|
+ size_t dest_len = strlen (dest_dir);
|
|
+ size_t file_len = strlen (file);
|
|
+ size_t nsize = dest_len + 1; /* + '\0' */
|
|
+ if (file_len > 0)
|
|
+ nsize += 1 + file_len; /* + '/' */
|
|
+ char *nname = new_string_storage (strings, nsize);
|
|
+ if (nname == NULL)
|
|
+ error (1, ENOMEM, "Couldn't allocate new string storage");
|
|
+ memcpy (nname, dest_dir, dest_len);
|
|
+ if (file_len > 0)
|
|
+ {
|
|
+ nname[dest_len] = '/';
|
|
+ memcpy (nname + dest_len + 1, file, file_len + 1);
|
|
+ }
|
|
+ else
|
|
+ nname[dest_len] = '\0';
|
|
+
|
|
+ strent = strtab_add_len (strings->str_tab, nname, nsize);
|
|
+ ret = true;
|
|
+ }
|
|
+ if (strent == NULL)
|
|
+ error (1, ENOMEM, "Could not create new string table entry");
|
|
+ else
|
|
+ entry->entry = strent;
|
|
+ }
|
|
+
|
|
+ return ret;
|
|
+}
|
|
+
|
|
+/* Same as record_new_string_file_string_entry_idx but doesn't replace
|
|
+ base_dir with dest_dir, just records the existing string associated
|
|
+ with the index. */
|
|
static void
|
|
-dirty_section (unsigned int sec)
|
|
+record_existing_string_entry_idx (struct strings *strings, size_t old_idx)
|
|
{
|
|
- elf_flagdata (debug_sections[sec].elf_data, ELF_C_SET, ELF_F_DIRTY);
|
|
- dirty_elf = 1;
|
|
+ struct stridxentry *entry = string_find_new_entry (strings, old_idx);
|
|
+ if (entry != NULL)
|
|
+ {
|
|
+ const char *str = (char *)debug_sections[DEBUG_STR].data + old_idx;
|
|
+ Strent *strent = strtab_add_len (strings->str_tab,
|
|
+ str, strlen (str) + 1);
|
|
+ if (strent == NULL)
|
|
+ error (1, ENOMEM, "Could not create new string table entry");
|
|
+ else
|
|
+ entry->entry = strent;
|
|
+ }
|
|
}
|
|
|
|
-static int
|
|
-edit_dwarf2_line (DSO *dso, uint32_t off, char *comp_dir, int phase)
|
|
+static void
|
|
+setup_strings (struct strings *strings)
|
|
{
|
|
- unsigned char *ptr = debug_sections[DEBUG_LINE].data, *dir;
|
|
- unsigned char **dirt;
|
|
- unsigned char *endsec = ptr + debug_sections[DEBUG_LINE].size;
|
|
- unsigned char *endcu, *endprol;
|
|
- unsigned char opcode_base;
|
|
- uint32_t value, dirt_cnt;
|
|
- size_t comp_dir_len = !comp_dir ? 0 : strlen (comp_dir);
|
|
- size_t abs_file_cnt = 0, abs_dir_cnt = 0;
|
|
+ strings->str_tab = strtab_init (false);
|
|
+ strings->str_buf = NULL;
|
|
+ strings->blocks = NULL;
|
|
+ strings->last_block = NULL;
|
|
+ strings->entries = NULL;
|
|
+ strings->last_entries = NULL;
|
|
+ strings->strent_root = NULL;
|
|
+}
|
|
|
|
- if (phase != 0)
|
|
- return 0;
|
|
+/* Noop for tdestroy. */
|
|
+static void free_node (void *p __attribute__((__unused__))) { }
|
|
|
|
- /* XXX: RhBug:929365, should we error out instead of ignoring? */
|
|
+static void
|
|
+destroy_strings (struct strings *strings)
|
|
+{
|
|
+ struct strmemblock *smb = strings->blocks;
|
|
+ while (smb != NULL)
|
|
+ {
|
|
+ void *old = smb;
|
|
+ smb = smb->next;
|
|
+ free (old);
|
|
+ }
|
|
+
|
|
+ struct strentblock *emb = strings->entries;
|
|
+ while (emb != NULL)
|
|
+ {
|
|
+ void *old = emb;
|
|
+ emb = emb->next;
|
|
+ free (old);
|
|
+ }
|
|
+
|
|
+ strtab_free (strings->str_tab);
|
|
+ tdestroy (strings->strent_root, &free_node);
|
|
+ free (strings->str_buf);
|
|
+}
|
|
+
|
|
+/* The minimum number of line tables we pre-allocate. */
|
|
+#define MIN_LINE_TABLES 64
|
|
+
|
|
+/* Gets a line_table at offset. Returns true if not yet know and
|
|
+ successfully read, false otherwise. Sets *table to NULL and
|
|
+ outputs a warning if there was a problem reading the table at the
|
|
+ given offset. */
|
|
+static bool
|
|
+get_line_table (DSO *dso, size_t off, struct line_table **table)
|
|
+{
|
|
+ struct debug_lines *lines = &dso->lines;
|
|
+ /* Assume there aren't that many, just do a linear search. The
|
|
+ array is probably already sorted because the stmt_lists are
|
|
+ probably inserted in order. But we cannot rely on that (maybe we
|
|
+ should check that to make searching quicker if possible?). Once
|
|
+ we have all line tables for phase 1 (rewriting) we do explicitly
|
|
+ sort the array.*/
|
|
+ for (int i = 0; i < lines->used; i++)
|
|
+ if (lines->table[i].old_idx == off)
|
|
+ {
|
|
+ *table = &lines->table[i];
|
|
+ return false;
|
|
+ }
|
|
+
|
|
+ if (lines->size == lines->used)
|
|
+ {
|
|
+ struct line_table *new_table = realloc (lines->table,
|
|
+ (sizeof (struct line_table)
|
|
+ * (lines->size
|
|
+ + MIN_LINE_TABLES)));
|
|
+ if (new_table == NULL)
|
|
+ {
|
|
+ error (0, ENOMEM, "Couldn't add more debug_line tables");
|
|
+ *table = NULL;
|
|
+ return false;
|
|
+ }
|
|
+ lines->table = new_table;
|
|
+ lines->size += MIN_LINE_TABLES;
|
|
+ }
|
|
+
|
|
+ struct line_table *t = &lines->table[lines->used];
|
|
+ *table = NULL;
|
|
+
|
|
+ t->old_idx = off;
|
|
+ t->size_diff = 0;
|
|
+ t->replace_dirs = false;
|
|
+ t->replace_files = false;
|
|
+
|
|
+ unsigned char *ptr = debug_sections[DEBUG_LINE].data;
|
|
+ unsigned char *endsec = ptr + debug_sections[DEBUG_LINE].size;
|
|
if (ptr == NULL)
|
|
- return 0;
|
|
+ {
|
|
+ error (0, 0, "%s: No .line_table section", dso->filename);
|
|
+ return false;
|
|
+ }
|
|
|
|
+ if (off > debug_sections[DEBUG_LINE].size)
|
|
+ {
|
|
+ error (0, 0, "%s: Invalid .line_table offset 0x%zx",
|
|
+ dso->filename, off);
|
|
+ return false;
|
|
+ }
|
|
ptr += off;
|
|
|
|
- endcu = ptr + 4;
|
|
- endcu += read_32 (ptr);
|
|
+ /* unit_length */
|
|
+ unsigned char *endcu = ptr + 4;
|
|
+ t->unit_length = read_32 (ptr);
|
|
+ endcu += t->unit_length;
|
|
if (endcu == ptr + 0xffffffff)
|
|
{
|
|
error (0, 0, "%s: 64-bit DWARF not supported", dso->filename);
|
|
- return 1;
|
|
+ return false;
|
|
}
|
|
|
|
if (endcu > endsec)
|
|
{
|
|
error (0, 0, "%s: .debug_line CU does not fit into section",
|
|
dso->filename);
|
|
- return 1;
|
|
+ return false;
|
|
}
|
|
|
|
- value = read_16 (ptr);
|
|
- if (value != 2 && value != 3 && value != 4)
|
|
+ /* version */
|
|
+ t->version = read_16 (ptr);
|
|
+ if (t->version != 2 && t->version != 3 && t->version != 4)
|
|
{
|
|
error (0, 0, "%s: DWARF version %d unhandled", dso->filename,
|
|
- value);
|
|
- return 1;
|
|
+ t->version);
|
|
+ return false;
|
|
}
|
|
|
|
- endprol = ptr + 4;
|
|
- endprol += read_32 (ptr);
|
|
+ /* header_length */
|
|
+ unsigned char *endprol = ptr + 4;
|
|
+ t->header_length = read_32 (ptr);
|
|
+ endprol += t->header_length;
|
|
if (endprol > endcu)
|
|
{
|
|
error (0, 0, "%s: .debug_line CU prologue does not fit into CU",
|
|
dso->filename);
|
|
- return 1;
|
|
+ return false;
|
|
+ }
|
|
+
|
|
+ /* min instr len */
|
|
+ t->min_instr_len = *ptr++;
|
|
+
|
|
+ /* max op per instr, if version >= 4 */
|
|
+ if (t->version >= 4)
|
|
+ t->max_op_per_instr = *ptr++;
|
|
+
|
|
+ /* default is stmt */
|
|
+ t->default_is_stmt = *ptr++;
|
|
+
|
|
+ /* line base */
|
|
+ t->line_base = (*(int8_t *)ptr++);
|
|
+
|
|
+ /* line range */
|
|
+ t->line_range = *ptr++;
|
|
+
|
|
+ /* opcode base */
|
|
+ t->opcode_base = *ptr++;
|
|
+
|
|
+ if (ptr + t->opcode_base - 1 >= endcu)
|
|
+ {
|
|
+ error (0, 0, "%s: .debug_line opcode table does not fit into CU",
|
|
+ dso->filename);
|
|
+ return false;
|
|
}
|
|
+ lines->used++;
|
|
+ *table = t;
|
|
+ return true;
|
|
+}
|
|
|
|
- opcode_base = ptr[4 + (value >= 4)];
|
|
- ptr = dir = ptr + 4 + (value >= 4) + opcode_base;
|
|
+static int dirty_elf;
|
|
+static void
|
|
+dirty_section (unsigned int sec)
|
|
+{
|
|
+ elf_flagdata (debug_sections[sec].elf_data, ELF_C_SET, ELF_F_DIRTY);
|
|
+ dirty_elf = 1;
|
|
+}
|
|
+
|
|
+static int
|
|
+line_table_cmp (const void *a, const void *b)
|
|
+{
|
|
+ struct line_table *ta = (struct line_table *) a;
|
|
+ struct line_table *tb = (struct line_table *) b;
|
|
+
|
|
+ if (ta->old_idx < tb->old_idx)
|
|
+ return -1;
|
|
+
|
|
+ if (ta->old_idx > tb->old_idx)
|
|
+ return 1;
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+
|
|
+/* Called after phase zero (which records all adjustments needed for
|
|
+ the line tables referenced from debug_info) and before phase one
|
|
+ starts (phase one will adjust the .debug_line section stmt
|
|
+ references using the updated data structures). */
|
|
+static void
|
|
+edit_dwarf2_line (DSO *dso)
|
|
+{
|
|
+ Elf_Data *linedata = debug_sections[DEBUG_LINE].elf_data;
|
|
+ int linendx = debug_sections[DEBUG_LINE].sec;
|
|
+ Elf_Scn *linescn = dso->scn[linendx];
|
|
+ unsigned char *old_buf = linedata->d_buf;
|
|
+
|
|
+ /* Out with the old. */
|
|
+ linedata->d_size = 0;
|
|
+
|
|
+ /* In with the new. */
|
|
+ linedata = elf_newdata (linescn);
|
|
+
|
|
+ dso->lines.line_buf = malloc (dso->lines.debug_lines_len);
|
|
+ if (dso->lines.line_buf == NULL)
|
|
+ error (1, ENOMEM, "No memory for new .debug_line table (0x%zx bytes)",
|
|
+ dso->lines.debug_lines_len);
|
|
+
|
|
+ linedata->d_size = dso->lines.debug_lines_len;
|
|
+ linedata->d_buf = dso->lines.line_buf;
|
|
+ debug_sections[DEBUG_LINE].size = linedata->d_size;
|
|
+
|
|
+ /* Make sure the line tables are sorted on the old index. */
|
|
+ qsort (dso->lines.table, dso->lines.used, sizeof (struct line_table),
|
|
+ line_table_cmp);
|
|
+
|
|
+ unsigned char *ptr = linedata->d_buf;
|
|
+ for (int ldx = 0; ldx < dso->lines.used; ldx++)
|
|
+ {
|
|
+ struct line_table *t = &dso->lines.table[ldx];
|
|
+ unsigned char *optr = old_buf + t->old_idx;
|
|
+ t->new_idx = ptr - (unsigned char *) linedata->d_buf;
|
|
+
|
|
+ /* Just copy the whole table if nothing needs replacing. */
|
|
+ if (! t->replace_dirs && ! t->replace_files)
|
|
+ {
|
|
+ assert (t->size_diff == 0);
|
|
+ memcpy (ptr, optr, t->unit_length + 4);
|
|
+ ptr += t->unit_length + 4;
|
|
+ continue;
|
|
+ }
|
|
+
|
|
+ /* Header fields. */
|
|
+ write_32 (ptr, t->unit_length + t->size_diff);
|
|
+ write_16 (ptr, t->version);
|
|
+ write_32 (ptr, t->header_length + t->size_diff);
|
|
+ write_8 (ptr, t->min_instr_len);
|
|
+ if (t->version >= 4)
|
|
+ write_8 (ptr, t->max_op_per_instr);
|
|
+ write_8 (ptr, t->default_is_stmt);
|
|
+ write_8 (ptr, t->line_base);
|
|
+ write_8 (ptr, t->line_range);
|
|
+ write_8 (ptr, t->opcode_base);
|
|
+
|
|
+ optr += (4 /* unit len */
|
|
+ + 2 /* version */
|
|
+ + 4 /* header len */
|
|
+ + 1 /* min instr len */
|
|
+ + (t->version >= 4) /* max op per instr, if version >= 4 */
|
|
+ + 1 /* default is stmt */
|
|
+ + 1 /* line base */
|
|
+ + 1 /* line range */
|
|
+ + 1); /* opcode base */
|
|
+
|
|
+ /* opcode len table. */
|
|
+ memcpy (ptr, optr, t->opcode_base - 1);
|
|
+ optr += t->opcode_base - 1;
|
|
+ ptr += t->opcode_base - 1;
|
|
+
|
|
+ /* directory table. We need to find the end (start of file
|
|
+ table) anyway, so loop over all dirs, even if replace_dirs is
|
|
+ false. */
|
|
+ while (*optr != 0)
|
|
+ {
|
|
+ const char *dir = (const char *) optr;
|
|
+ const char *file_path = NULL;
|
|
+ if (t->replace_dirs)
|
|
+ {
|
|
+ file_path = skip_dir_prefix (dir, base_dir);
|
|
+ if (file_path != NULL)
|
|
+ {
|
|
+ size_t dest_len = strlen (dest_dir);
|
|
+ size_t file_len = strlen (file_path);
|
|
+ memcpy (ptr, dest_dir, dest_len);
|
|
+ ptr += dest_len;
|
|
+ if (file_len > 0)
|
|
+ {
|
|
+ *ptr++ = '/';
|
|
+ memcpy (ptr, file_path, file_len);
|
|
+ ptr += file_len;
|
|
+ }
|
|
+ *ptr++ = '\0';
|
|
+ }
|
|
+ }
|
|
+ if (file_path == NULL)
|
|
+ {
|
|
+ size_t dir_len = strlen (dir);
|
|
+ memcpy (ptr, dir, dir_len + 1);
|
|
+ ptr += dir_len + 1;
|
|
+ }
|
|
+
|
|
+ optr = (unsigned char *) strchr (dir, 0) + 1;
|
|
+ }
|
|
+ optr++;
|
|
+ *ptr++ = '\0';
|
|
+
|
|
+ /* file table */
|
|
+ if (t->replace_files)
|
|
+ {
|
|
+ while (*optr != 0)
|
|
+ {
|
|
+ const char *file = (const char *) optr;
|
|
+ const char *file_path = NULL;
|
|
+ if (t->replace_dirs)
|
|
+ {
|
|
+ file_path = skip_dir_prefix (file, base_dir);
|
|
+ if (file_path != NULL)
|
|
+ {
|
|
+ size_t dest_len = strlen (dest_dir);
|
|
+ size_t file_len = strlen (file_path);
|
|
+ memcpy (ptr, dest_dir, dest_len);
|
|
+ ptr += dest_len;
|
|
+ if (file_len > 0)
|
|
+ {
|
|
+ *ptr++ = '/';
|
|
+ memcpy (ptr, file_path, file_len);
|
|
+ ptr += file_len;
|
|
+ }
|
|
+ *ptr++ = '\0';
|
|
+ }
|
|
+ }
|
|
+ if (file_path == NULL)
|
|
+ {
|
|
+ size_t file_len = strlen (file);
|
|
+ memcpy (ptr, file, file_len + 1);
|
|
+ ptr += file_len + 1;
|
|
+ }
|
|
+
|
|
+ optr = (unsigned char *) strchr (file, 0) + 1;
|
|
+
|
|
+ /* dir idx, time, len */
|
|
+ uint32_t dir_idx = read_uleb128 (optr);
|
|
+ write_uleb128 (ptr, dir_idx);
|
|
+ uint32_t time = read_uleb128 (optr);
|
|
+ write_uleb128 (ptr, time);
|
|
+ uint32_t len = read_uleb128 (optr);
|
|
+ write_uleb128 (ptr, len);
|
|
+ }
|
|
+ optr++;
|
|
+ *ptr++ = '\0';
|
|
+ }
|
|
+
|
|
+ /* line number program (and file table if not copied above). */
|
|
+ size_t remaining = (t->unit_length + 4
|
|
+ - (optr - (old_buf + t->old_idx)));
|
|
+ memcpy (ptr, optr, remaining);
|
|
+ ptr += remaining;
|
|
+ }
|
|
+}
|
|
+
|
|
+/* Called during phase zero for each debug_line table referenced from
|
|
+ .debug_info. Outputs all source files seen and records any
|
|
+ adjustments needed in the debug_list data structures. Returns true
|
|
+ if line_table needs to be rewrite either the dir or file paths. */
|
|
+static bool
|
|
+read_dwarf2_line (DSO *dso, uint32_t off, char *comp_dir)
|
|
+{
|
|
+ unsigned char *ptr, *dir;
|
|
+ unsigned char **dirt;
|
|
+ uint32_t value, dirt_cnt;
|
|
+ size_t comp_dir_len = !comp_dir ? 0 : strlen (comp_dir);
|
|
+ struct line_table *table;
|
|
+
|
|
+ if (get_line_table (dso, off, &table) == false
|
|
+ || table == NULL)
|
|
+ {
|
|
+ if (table != NULL)
|
|
+ error (0, 0, ".debug_line offset 0x%x referenced multiple times",
|
|
+ off);
|
|
+ return false;
|
|
+ }
|
|
+
|
|
+ /* Skip to the directory table. The rest of the header has already
|
|
+ been read and checked by get_line_table. */
|
|
+ ptr = debug_sections[DEBUG_LINE].data + off;
|
|
+ ptr += (4 /* unit len */
|
|
+ + 2 /* version */
|
|
+ + 4 /* header len */
|
|
+ + 1 /* min instr len */
|
|
+ + (table->version >= 4) /* max op per instr, if version >= 4 */
|
|
+ + 1 /* default is stmt */
|
|
+ + 1 /* line base */
|
|
+ + 1 /* line range */
|
|
+ + 1 /* opcode base */
|
|
+ + table->opcode_base - 1); /* opcode len table */
|
|
+ dir = ptr;
|
|
|
|
/* dir table: */
|
|
value = 1;
|
|
while (*ptr != 0)
|
|
{
|
|
+ if (base_dir && dest_dir)
|
|
+ {
|
|
+ /* Do we need to replace any of the dirs? Calculate new size. */
|
|
+ const char *file_path = skip_dir_prefix ((const char *)ptr,
|
|
+ base_dir);
|
|
+ if (file_path != NULL)
|
|
+ {
|
|
+ size_t old_size = strlen ((const char *)ptr) + 1;
|
|
+ size_t file_len = strlen (file_path);
|
|
+ size_t new_size = strlen (dest_dir) + 1;
|
|
+ if (file_len > 0)
|
|
+ new_size += 1 + file_len;
|
|
+ table->size_diff += (new_size - old_size);
|
|
+ table->replace_dirs = true;
|
|
+ }
|
|
+ }
|
|
+
|
|
ptr = (unsigned char *) strchr ((char *)ptr, 0) + 1;
|
|
++value;
|
|
}
|
|
@@ -561,21 +1319,34 @@ edit_dwarf2_line (DSO *dso, uint32_t off, char *comp_dir, int phase)
|
|
{
|
|
error (0, 0, "%s: Wrong directory table index %u",
|
|
dso->filename, value);
|
|
- return 1;
|
|
+ return false;
|
|
}
|
|
file_len = strlen (file);
|
|
+ if (base_dir && dest_dir)
|
|
+ {
|
|
+ /* Do we need to replace any of the files? Calculate new size. */
|
|
+ const char *file_path = skip_dir_prefix (file, base_dir);
|
|
+ if (file_path != NULL)
|
|
+ {
|
|
+ size_t old_size = file_len + 1;
|
|
+ size_t file_len = strlen (file_path);
|
|
+ size_t new_size = strlen (dest_dir) + 1;
|
|
+ if (file_len > 0)
|
|
+ new_size += 1 + file_len;
|
|
+ table->size_diff += (new_size - old_size);
|
|
+ table->replace_files = true;
|
|
+ }
|
|
+ }
|
|
dir_len = strlen ((char *)dirt[value]);
|
|
s = malloc (comp_dir_len + 1 + file_len + 1 + dir_len + 1);
|
|
if (s == NULL)
|
|
{
|
|
error (0, ENOMEM, "%s: Reading file table", dso->filename);
|
|
- return 1;
|
|
+ return false;
|
|
}
|
|
if (*file == '/')
|
|
{
|
|
memcpy (s, file, file_len + 1);
|
|
- if (dest_dir && has_prefix (file, base_dir))
|
|
- ++abs_file_cnt;
|
|
}
|
|
else if (*dirt[value] == '/')
|
|
{
|
|
@@ -599,13 +1370,15 @@ edit_dwarf2_line (DSO *dso, uint32_t off, char *comp_dir, int phase)
|
|
canonicalize_path (s, s);
|
|
if (list_file_fd != -1)
|
|
{
|
|
- char *p = NULL;
|
|
+ const char *p = NULL;
|
|
if (base_dir == NULL)
|
|
p = s;
|
|
- else if (has_prefix (s, base_dir))
|
|
- p = s + strlen (base_dir);
|
|
- else if (has_prefix (s, dest_dir))
|
|
- p = s + strlen (dest_dir);
|
|
+ else
|
|
+ {
|
|
+ p = skip_dir_prefix (s, base_dir);
|
|
+ if (p == NULL && dest_dir != NULL)
|
|
+ p = skip_dir_prefix (s, dest_dir);
|
|
+ }
|
|
|
|
if (p)
|
|
{
|
|
@@ -626,112 +1399,28 @@ edit_dwarf2_line (DSO *dso, uint32_t off, char *comp_dir, int phase)
|
|
read_uleb128 (ptr);
|
|
read_uleb128 (ptr);
|
|
}
|
|
- ++ptr;
|
|
-
|
|
- if (dest_dir)
|
|
- {
|
|
- unsigned char *srcptr, *buf = NULL;
|
|
- size_t base_len = strlen (base_dir);
|
|
- size_t dest_len = strlen (dest_dir);
|
|
- size_t shrank = 0;
|
|
-
|
|
- if (dest_len == base_len)
|
|
- abs_file_cnt = 0;
|
|
- if (abs_file_cnt)
|
|
- {
|
|
- srcptr = buf = malloc (ptr - dir);
|
|
- memcpy (srcptr, dir, ptr - dir);
|
|
- ptr = dir;
|
|
- }
|
|
- else
|
|
- ptr = srcptr = dir;
|
|
- while (*srcptr != 0)
|
|
- {
|
|
- size_t len = strlen ((char *)srcptr) + 1;
|
|
- const unsigned char *readptr = srcptr;
|
|
-
|
|
- char *orig = strdup ((const char *) srcptr);
|
|
-
|
|
- if (*srcptr == '/' && has_prefix ((char *)srcptr, base_dir))
|
|
- {
|
|
- if (dest_len < base_len)
|
|
- ++abs_dir_cnt;
|
|
- memcpy (ptr, dest_dir, dest_len);
|
|
- ptr += dest_len;
|
|
- readptr += base_len;
|
|
- }
|
|
- srcptr += len;
|
|
|
|
- shrank += srcptr - readptr;
|
|
- canonicalize_path ((char *)readptr, (char *)ptr);
|
|
- len = strlen ((char *)ptr) + 1;
|
|
- shrank -= len;
|
|
- ptr += len;
|
|
-
|
|
- if (memcmp (orig, ptr - len, len))
|
|
- dirty_section (DEBUG_STR);
|
|
- free (orig);
|
|
- }
|
|
-
|
|
- if (shrank > 0)
|
|
- {
|
|
- if (--shrank == 0)
|
|
- error (EXIT_FAILURE, 0,
|
|
- "canonicalization unexpectedly shrank by one character");
|
|
- else
|
|
- {
|
|
- memset (ptr, 'X', shrank);
|
|
- ptr += shrank;
|
|
- *ptr++ = '\0';
|
|
- }
|
|
- }
|
|
-
|
|
- if (abs_dir_cnt + abs_file_cnt != 0)
|
|
- {
|
|
- size_t len = (abs_dir_cnt + abs_file_cnt) * (base_len - dest_len);
|
|
-
|
|
- if (len == 1)
|
|
- error (EXIT_FAILURE, 0, "-b arg has to be either the same length as -d arg, or more than 1 char longer");
|
|
- memset (ptr, 'X', len - 1);
|
|
- ptr += len - 1;
|
|
- *ptr++ = '\0';
|
|
- }
|
|
- *ptr++ = '\0';
|
|
- ++srcptr;
|
|
-
|
|
- while (*srcptr != 0)
|
|
- {
|
|
- size_t len = strlen ((char *)srcptr) + 1;
|
|
+ dso->lines.debug_lines_len += 4 + table->unit_length + table->size_diff;
|
|
+ return table->replace_dirs || table->replace_files;
|
|
+}
|
|
|
|
- if (*srcptr == '/' && has_prefix ((char *)srcptr, base_dir))
|
|
- {
|
|
- memcpy (ptr, dest_dir, dest_len);
|
|
- if (dest_len < base_len)
|
|
- {
|
|
- memmove (ptr + dest_len, srcptr + base_len,
|
|
- len - base_len);
|
|
- ptr += dest_len - base_len;
|
|
- }
|
|
- dirty_section (DEBUG_STR);
|
|
- }
|
|
- else if (ptr != srcptr)
|
|
- memmove (ptr, srcptr, len);
|
|
- srcptr += len;
|
|
- ptr += len;
|
|
- dir = srcptr;
|
|
- read_uleb128 (srcptr);
|
|
- read_uleb128 (srcptr);
|
|
- read_uleb128 (srcptr);
|
|
- if (ptr != dir)
|
|
- memmove (ptr, dir, srcptr - dir);
|
|
- ptr += srcptr - dir;
|
|
- }
|
|
- *ptr = '\0';
|
|
- free (buf);
|
|
- }
|
|
- return 0;
|
|
+/* Called during phase one, after the table has been sorted. */
|
|
+static size_t
|
|
+find_new_list_offs (struct debug_lines *lines, size_t idx)
|
|
+{
|
|
+ struct line_table key;
|
|
+ key.old_idx = idx;
|
|
+ struct line_table *table = bsearch (&key, lines->table,
|
|
+ lines->used,
|
|
+ sizeof (struct line_table),
|
|
+ line_table_cmp);
|
|
+ return table->new_idx;
|
|
}
|
|
|
|
+/* This scans the attributes of one DIE described by the given abbrev_tag.
|
|
+ PTR points to the data in the debug_info. It will be advanced till all
|
|
+ abbrev data is consumed. In phase zero data is collected, in phase one
|
|
+ data might be replaced/updated. */
|
|
static unsigned char *
|
|
edit_attributes (DSO *dso, unsigned char *ptr, struct abbrev_tag *t, int phase)
|
|
{
|
|
@@ -747,20 +1436,36 @@ edit_attributes (DSO *dso, unsigned char *ptr, struct abbrev_tag *t, int phase)
|
|
{
|
|
uint32_t form = t->attr[i].form;
|
|
size_t len = 0;
|
|
- size_t base_len, dest_len;
|
|
-
|
|
while (1)
|
|
{
|
|
+ /* Whether we already handled a string as file for this
|
|
+ attribute. If we did then we don't need to handle/record
|
|
+ it again when handling the DW_FORM_strp later. */
|
|
+ bool handled_strp = false;
|
|
+
|
|
+ /* A stmt_list points into the .debug_line section. In
|
|
+ phase zero record all offsets. Then in phase one replace
|
|
+ them with the new offsets if we rewrote the line
|
|
+ tables. */
|
|
if (t->attr[i].attr == DW_AT_stmt_list)
|
|
{
|
|
if (form == DW_FORM_data4
|
|
|| form == DW_FORM_sec_offset)
|
|
{
|
|
list_offs = do_read_32_relocated (ptr);
|
|
- found_list_offs = 1;
|
|
+ if (phase == 0)
|
|
+ found_list_offs = 1;
|
|
+ else if (need_stmt_update) /* phase one */
|
|
+ {
|
|
+ size_t idx, new_idx;
|
|
+ idx = do_read_32_relocated (ptr);
|
|
+ new_idx = find_new_list_offs (&dso->lines, idx);
|
|
+ do_write_32_relocated (ptr, new_idx);
|
|
+ }
|
|
}
|
|
}
|
|
|
|
+ /* DW_AT_comp_dir is the current working directory. */
|
|
if (t->attr[i].attr == DW_AT_comp_dir)
|
|
{
|
|
if (form == DW_FORM_string)
|
|
@@ -768,44 +1473,65 @@ edit_attributes (DSO *dso, unsigned char *ptr, struct abbrev_tag *t, int phase)
|
|
free (comp_dir);
|
|
comp_dir = strdup ((char *)ptr);
|
|
|
|
- if (phase == 1 && dest_dir && has_prefix ((char *)ptr, base_dir))
|
|
+ if (dest_dir)
|
|
{
|
|
- base_len = strlen (base_dir);
|
|
- dest_len = strlen (dest_dir);
|
|
-
|
|
- memcpy (ptr, dest_dir, dest_len);
|
|
- if (dest_len < base_len)
|
|
+ /* In phase zero we are just collecting dir/file
|
|
+ names and check whether any need to be
|
|
+ adjusted. If so, in phase one we replace
|
|
+ those dir/files. */
|
|
+ const char *file = skip_dir_prefix (comp_dir, base_dir);
|
|
+ if (file != NULL && phase == 0)
|
|
+ need_string_replacement = true;
|
|
+ else if (file != NULL && phase == 1)
|
|
{
|
|
- memset(ptr + dest_len, '/',
|
|
- base_len - dest_len);
|
|
-
|
|
+ size_t orig_len = strlen (comp_dir);
|
|
+ size_t dest_len = strlen (dest_dir);
|
|
+ size_t file_len = strlen (file);
|
|
+ size_t new_len = dest_len;
|
|
+ if (file_len > 0)
|
|
+ new_len += 1 + file_len; /* + '/' */
|
|
+
|
|
+ /* We don't want to rewrite the whole
|
|
+ debug_info section, so we only replace
|
|
+ the comp_dir with something equal or
|
|
+ smaller, possibly adding some slashes
|
|
+ at the end of the new compdir. This
|
|
+ normally doesn't happen since most
|
|
+ producers will use DW_FORM_strp which is
|
|
+ more efficient. */
|
|
+ if (orig_len < new_len)
|
|
+ fprintf (stderr, "Warning, not replacing comp_dir "
|
|
+ "'%s' prefix ('%s' -> '%s') encoded as "
|
|
+ "DW_FORM_string. "
|
|
+ "Replacement too large.\n",
|
|
+ comp_dir, base_dir, dest_dir);
|
|
+ else
|
|
+ {
|
|
+ /* Add one or more slashes in between to
|
|
+ fill up all space (replacement must be
|
|
+ of the same length). */
|
|
+ memcpy (ptr, dest_dir, dest_len);
|
|
+ memset (ptr + dest_len, '/',
|
|
+ orig_len - new_len + 1);
|
|
+ }
|
|
}
|
|
- dirty_section (DEBUG_INFO);
|
|
}
|
|
}
|
|
else if (form == DW_FORM_strp &&
|
|
debug_sections[DEBUG_STR].data)
|
|
{
|
|
- char *dir;
|
|
-
|
|
- dir = (char *) debug_sections[DEBUG_STR].data
|
|
- + do_read_32_relocated (ptr);
|
|
+ const char *dir;
|
|
+ size_t idx = do_read_32_relocated (ptr);
|
|
+ dir = (char *) debug_sections[DEBUG_STR].data + idx;
|
|
|
|
free (comp_dir);
|
|
comp_dir = strdup (dir);
|
|
|
|
- if (phase == 1 && dest_dir && has_prefix (dir, base_dir))
|
|
+ if (dest_dir != NULL && phase == 0)
|
|
{
|
|
- base_len = strlen (base_dir);
|
|
- dest_len = strlen (dest_dir);
|
|
-
|
|
- memcpy (dir, dest_dir, dest_len);
|
|
- if (dest_len < base_len)
|
|
- {
|
|
- memmove (dir + dest_len, dir + base_len,
|
|
- strlen (dir + base_len) + 1);
|
|
- }
|
|
- dirty_section (DEBUG_STR);
|
|
+ if (record_file_string_entry_idx (&dso->strings, idx))
|
|
+ need_strp_update = true;
|
|
+ handled_strp = true;
|
|
}
|
|
}
|
|
}
|
|
@@ -815,10 +1541,13 @@ edit_attributes (DSO *dso, unsigned char *ptr, struct abbrev_tag *t, int phase)
|
|
&& form == DW_FORM_strp
|
|
&& debug_sections[DEBUG_STR].data)
|
|
{
|
|
+ /* DW_AT_name is the primary file for this compile
|
|
+ unit. If starting with / it is a full path name.
|
|
+ Note that we don't handle DW_FORM_string in this
|
|
+ case. */
|
|
char *name;
|
|
-
|
|
- name = (char *) debug_sections[DEBUG_STR].data
|
|
- + do_read_32_relocated (ptr);
|
|
+ size_t idx = do_read_32_relocated (ptr);
|
|
+ name = (char *) debug_sections[DEBUG_STR].data + idx;
|
|
if (*name == '/' && comp_dir == NULL)
|
|
{
|
|
char *enddir = strrchr (name, '/');
|
|
@@ -833,18 +1562,14 @@ edit_attributes (DSO *dso, unsigned char *ptr, struct abbrev_tag *t, int phase)
|
|
comp_dir = strdup ("/");
|
|
}
|
|
|
|
- if (phase == 1 && dest_dir && has_prefix (name, base_dir))
|
|
+ /* First pass (0) records the new name to be
|
|
+ added to the debug string pool, the second
|
|
+ pass (1) stores it (the new index). */
|
|
+ if (dest_dir && phase == 0)
|
|
{
|
|
- base_len = strlen (base_dir);
|
|
- dest_len = strlen (dest_dir);
|
|
-
|
|
- memcpy (name, dest_dir, dest_len);
|
|
- if (dest_len < base_len)
|
|
- {
|
|
- memmove (name + dest_len, name + base_len,
|
|
- strlen (name + base_len) + 1);
|
|
- }
|
|
- dirty_section (DEBUG_STR);
|
|
+ if (record_file_string_entry_idx (&dso->strings, idx))
|
|
+ need_strp_update = true;
|
|
+ handled_strp = true;
|
|
}
|
|
}
|
|
|
|
@@ -886,6 +1611,29 @@ edit_attributes (DSO *dso, unsigned char *ptr, struct abbrev_tag *t, int phase)
|
|
read_uleb128 (ptr);
|
|
break;
|
|
case DW_FORM_strp:
|
|
+ /* In the first pass we collect all strings, in the
|
|
+ second we put the new references back (if there are
|
|
+ any changes). */
|
|
+ if (phase == 0)
|
|
+ {
|
|
+ /* handled_strp is set for attributes refering to
|
|
+ files. If it is set the string is already
|
|
+ recorded. */
|
|
+ if (! handled_strp)
|
|
+ {
|
|
+ size_t idx = do_read_32_relocated (ptr);
|
|
+ record_existing_string_entry_idx (&dso->strings, idx);
|
|
+ }
|
|
+ }
|
|
+ else if (need_strp_update) /* && phase == 1 */
|
|
+ {
|
|
+ struct stridxentry *entry;
|
|
+ size_t idx, new_idx;
|
|
+ idx = do_read_32_relocated (ptr);
|
|
+ entry = string_find_entry (&dso->strings, idx);
|
|
+ new_idx = strent_offset (entry->entry);
|
|
+ do_write_32_relocated (ptr, new_idx);
|
|
+ }
|
|
ptr += 4;
|
|
break;
|
|
case DW_FORM_string:
|
|
@@ -930,14 +1678,17 @@ edit_attributes (DSO *dso, unsigned char *ptr, struct abbrev_tag *t, int phase)
|
|
CU current dir subdirectories. */
|
|
if (comp_dir && list_file_fd != -1)
|
|
{
|
|
- char *p;
|
|
+ const char *p = NULL;
|
|
size_t size;
|
|
|
|
- if (base_dir && has_prefix (comp_dir, base_dir))
|
|
- p = comp_dir + strlen (base_dir);
|
|
- else if (dest_dir && has_prefix (comp_dir, dest_dir))
|
|
- p = comp_dir + strlen (dest_dir);
|
|
- else
|
|
+ if (base_dir)
|
|
+ {
|
|
+ p = skip_dir_prefix (comp_dir, base_dir);
|
|
+ if (p == NULL && dest_dir != NULL)
|
|
+ p = skip_dir_prefix (comp_dir, dest_dir);
|
|
+ }
|
|
+
|
|
+ if (p == NULL)
|
|
p = comp_dir;
|
|
|
|
size = strlen (p) + 1;
|
|
@@ -951,8 +1702,13 @@ edit_attributes (DSO *dso, unsigned char *ptr, struct abbrev_tag *t, int phase)
|
|
}
|
|
}
|
|
|
|
- if (found_list_offs)
|
|
- edit_dwarf2_line (dso, list_offs, comp_dir, phase);
|
|
+ /* In phase zero we collect all file names (we need the comp_dir for
|
|
+ that). Note that calculating the new size and offsets is done
|
|
+ separately (at the end of phase zero after all CUs have been
|
|
+ scanned in dwarf2_edit). */
|
|
+ if (phase == 0 && found_list_offs
|
|
+ && read_dwarf2_line (dso, list_offs, comp_dir))
|
|
+ need_stmt_update = true;
|
|
|
|
free (comp_dir);
|
|
|
|
@@ -974,6 +1730,20 @@ rel_cmp (const void *a, const void *b)
|
|
}
|
|
|
|
static int
|
|
+line_rel_cmp (const void *a, const void *b)
|
|
+{
|
|
+ LINE_REL *rela = (LINE_REL *) a, *relb = (LINE_REL *) b;
|
|
+
|
|
+ if (rela->r_offset < relb->r_offset)
|
|
+ return -1;
|
|
+
|
|
+ if (rela->r_offset > relb->r_offset)
|
|
+ return 1;
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static int
|
|
edit_dwarf2 (DSO *dso)
|
|
{
|
|
Elf_Data *data;
|
|
@@ -1009,9 +1779,9 @@ edit_dwarf2 (DSO *dso)
|
|
}
|
|
|
|
scn = dso->scn[i];
|
|
- data = elf_rawdata (scn, NULL);
|
|
+ data = elf_getdata (scn, NULL);
|
|
assert (data != NULL && data->d_buf != NULL);
|
|
- assert (elf_rawdata (scn, data) == NULL);
|
|
+ assert (elf_getdata (scn, data) == NULL);
|
|
assert (data->d_off == 0);
|
|
assert (data->d_size == dso->shdr[i].sh_size);
|
|
debug_sections[j].data = data->d_buf;
|
|
@@ -1050,13 +1820,15 @@ edit_dwarf2 (DSO *dso)
|
|
{
|
|
do_read_16 = buf_read_ule16;
|
|
do_read_32 = buf_read_ule32;
|
|
- write_32 = dwarf2_write_le32;
|
|
+ do_write_16 = dwarf2_write_le16;
|
|
+ do_write_32 = dwarf2_write_le32;
|
|
}
|
|
else if (dso->ehdr.e_ident[EI_DATA] == ELFDATA2MSB)
|
|
{
|
|
do_read_16 = buf_read_ube16;
|
|
do_read_32 = buf_read_ube32;
|
|
- write_32 = dwarf2_write_be32;
|
|
+ do_write_16 = dwarf2_write_be16;
|
|
+ do_write_32 = dwarf2_write_be32;
|
|
}
|
|
else
|
|
{
|
|
@@ -1179,6 +1951,7 @@ edit_dwarf2 (DSO *dso)
|
|
relend->ptr = debug_sections[DEBUG_INFO].data
|
|
+ (rela.r_offset - base);
|
|
relend->addend = rela.r_addend;
|
|
+ relend->ndx = ndx;
|
|
++relend;
|
|
}
|
|
if (relbuf == relend)
|
|
@@ -1193,6 +1966,13 @@ edit_dwarf2 (DSO *dso)
|
|
|
|
for (phase = 0; phase < 2; phase++)
|
|
{
|
|
+ /* If we don't need to update anyhing, skip phase 1. */
|
|
+ if (phase == 1
|
|
+ && !need_strp_update
|
|
+ && !need_string_replacement
|
|
+ && !need_stmt_update)
|
|
+ break;
|
|
+
|
|
ptr = debug_sections[DEBUG_INFO].data;
|
|
relptr = relbuf;
|
|
endsec = ptr + debug_sections[DEBUG_INFO].size;
|
|
@@ -1240,7 +2020,7 @@ edit_dwarf2 (DSO *dso)
|
|
|
|
if (ptr_size == 0)
|
|
{
|
|
- ptr_size = read_1 (ptr);
|
|
+ ptr_size = read_8 (ptr);
|
|
if (ptr_size != 4 && ptr_size != 8)
|
|
{
|
|
error (0, 0, "%s: Invalid DWARF pointer size %d",
|
|
@@ -1248,7 +2028,7 @@ edit_dwarf2 (DSO *dso)
|
|
return 1;
|
|
}
|
|
}
|
|
- else if (read_1 (ptr) != ptr_size)
|
|
+ else if (read_8 (ptr) != ptr_size)
|
|
{
|
|
error (0, 0, "%s: DWARF pointer size differs between CUs",
|
|
dso->filename);
|
|
@@ -1281,7 +2061,185 @@ edit_dwarf2 (DSO *dso)
|
|
|
|
htab_delete (abbrev);
|
|
}
|
|
+
|
|
+ /* We might have to recalculate/rewrite the debug_line
|
|
+ section. We need to do that before going into phase one
|
|
+ so we have all new offsets. We do this separately from
|
|
+ scanning the dirs/file names because the DW_AT_stmt_lists
|
|
+ might not be in order or skip some padding we might have
|
|
+ to (re)move. */
|
|
+ if (phase == 0 && need_stmt_update)
|
|
+ {
|
|
+ edit_dwarf2_line (dso);
|
|
+
|
|
+ /* The line table programs will be moved
|
|
+ forward/backwards a bit in the new data. Update the
|
|
+ debug_line relocations to the new offsets. */
|
|
+ int rndx = debug_sections[DEBUG_LINE].relsec;
|
|
+ if (rndx != 0)
|
|
+ {
|
|
+ LINE_REL *rbuf;
|
|
+ size_t rels;
|
|
+ Elf_Data *rdata = elf_getdata (dso->scn[rndx], NULL);
|
|
+ int rtype = dso->shdr[rndx].sh_type;
|
|
+ rels = dso->shdr[rndx].sh_size / dso->shdr[rndx].sh_entsize;
|
|
+ rbuf = malloc (rels * sizeof (LINE_REL));
|
|
+ if (rbuf == NULL)
|
|
+ error (1, errno, "%s: Could not allocate line relocations",
|
|
+ dso->filename);
|
|
+
|
|
+ /* Sort them by offset into section. */
|
|
+ for (size_t i = 0; i < rels; i++)
|
|
+ {
|
|
+ if (rtype == SHT_RELA)
|
|
+ {
|
|
+ GElf_Rela rela;
|
|
+ if (gelf_getrela (rdata, i, &rela) == NULL)
|
|
+ error (1, 0, "Couldn't get relocation: %s",
|
|
+ elf_errmsg (-1));
|
|
+ rbuf[i].r_offset = rela.r_offset;
|
|
+ rbuf[i].ndx = i;
|
|
+ }
|
|
+ else
|
|
+ {
|
|
+ GElf_Rel rel;
|
|
+ if (gelf_getrel (rdata, i, &rel) == NULL)
|
|
+ error (1, 0, "Couldn't get relocation: %s",
|
|
+ elf_errmsg (-1));
|
|
+ rbuf[i].r_offset = rel.r_offset;
|
|
+ rbuf[i].ndx = i;
|
|
+ }
|
|
+ }
|
|
+ qsort (rbuf, rels, sizeof (LINE_REL), line_rel_cmp);
|
|
+
|
|
+ size_t lndx = 0;
|
|
+ for (size_t i = 0; i < rels; i++)
|
|
+ {
|
|
+ /* These relocations only happen in ET_REL files
|
|
+ and are section offsets. */
|
|
+ GElf_Addr r_offset;
|
|
+ size_t ndx = rbuf[i].ndx;
|
|
+
|
|
+ GElf_Rel rel;
|
|
+ GElf_Rela rela;
|
|
+ if (rtype == SHT_RELA)
|
|
+ {
|
|
+ if (gelf_getrela (rdata, ndx, &rela) == NULL)
|
|
+ error (1, 0, "Couldn't get relocation: %s",
|
|
+ elf_errmsg (-1));
|
|
+ r_offset = rela.r_offset;
|
|
+ }
|
|
+ else
|
|
+ {
|
|
+ if (gelf_getrel (rdata, ndx, &rel) == NULL)
|
|
+ error (1, 0, "Couldn't get relocation: %s",
|
|
+ elf_errmsg (-1));
|
|
+ r_offset = rel.r_offset;
|
|
+ }
|
|
+
|
|
+ while (r_offset > (dso->lines.table[lndx].old_idx
|
|
+ + 4
|
|
+ + dso->lines.table[lndx].unit_length)
|
|
+ && lndx < dso->lines.used)
|
|
+ lndx++;
|
|
+
|
|
+ if (lndx >= dso->lines.used)
|
|
+ error (1, 0,
|
|
+ ".debug_line relocation offset out of range");
|
|
+
|
|
+ /* Offset (pointing into the line program) moves
|
|
+ from old to new index including the header
|
|
+ size diff. */
|
|
+ r_offset += ((dso->lines.table[lndx].new_idx
|
|
+ - dso->lines.table[lndx].old_idx)
|
|
+ + dso->lines.table[lndx].size_diff);
|
|
+
|
|
+ if (rtype == SHT_RELA)
|
|
+ {
|
|
+ rela.r_offset = r_offset;
|
|
+ if (gelf_update_rela (rdata, ndx, &rela) == 0)
|
|
+ error (1, 0, "Couldn't update relocation: %s",
|
|
+ elf_errmsg (-1));
|
|
+ }
|
|
+ else
|
|
+ {
|
|
+ rel.r_offset = r_offset;
|
|
+ if (gelf_update_rel (rdata, ndx, &rel) == 0)
|
|
+ error (1, 0, "Couldn't update relocation: %s",
|
|
+ elf_errmsg (-1));
|
|
+ }
|
|
+ }
|
|
+
|
|
+ elf_flagdata (rdata, ELF_C_SET, ELF_F_DIRTY);
|
|
+ free (rbuf);
|
|
+ }
|
|
+ }
|
|
+
|
|
+ /* Same for the debug_str section. Make sure everything is
|
|
+ in place for phase 1 updating of debug_info
|
|
+ references. */
|
|
+ if (phase == 0 && need_strp_update)
|
|
+ {
|
|
+ Strtab *strtab = dso->strings.str_tab;
|
|
+ Elf_Data *strdata = debug_sections[DEBUG_STR].elf_data;
|
|
+ int strndx = debug_sections[DEBUG_STR].sec;
|
|
+ Elf_Scn *strscn = dso->scn[strndx];
|
|
+
|
|
+ /* Out with the old. */
|
|
+ strdata->d_size = 0;
|
|
+ /* In with the new. */
|
|
+ strdata = elf_newdata (strscn);
|
|
+
|
|
+ /* We really should check whether we had enough memory,
|
|
+ but the old ebl version will just abort on out of
|
|
+ memory... */
|
|
+ strtab_finalize (strtab, strdata);
|
|
+ debug_sections[DEBUG_STR].size = strdata->d_size;
|
|
+ dso->strings.str_buf = strdata->d_buf;
|
|
+ }
|
|
+
|
|
+ }
|
|
+
|
|
+ /* After phase 1 we might have rewritten the debug_info with
|
|
+ new strp, strings and/or linep offsets. */
|
|
+ if (need_strp_update || need_string_replacement || need_stmt_update)
|
|
+ dirty_section (DEBUG_INFO);
|
|
+
|
|
+ /* Update any debug_info relocations addends we might have touched. */
|
|
+ if (relbuf != NULL && reltype == SHT_RELA)
|
|
+ {
|
|
+ Elf_Data *symdata;
|
|
+ int relsec_ndx = debug_sections[DEBUG_INFO].relsec;
|
|
+ data = elf_getdata (dso->scn[relsec_ndx], NULL);
|
|
+ symdata = elf_getdata (dso->scn[dso->shdr[relsec_ndx].sh_link],
|
|
+ NULL);
|
|
+
|
|
+ relptr = relbuf;
|
|
+ while (relptr < relend)
|
|
+ {
|
|
+ GElf_Sym sym;
|
|
+ GElf_Rela rela;
|
|
+ int ndx = relptr->ndx;
|
|
+
|
|
+ if (gelf_getrela (data, ndx, &rela) == NULL)
|
|
+ error (1, 0, "Couldn't get relocation: %s",
|
|
+ elf_errmsg (-1));
|
|
+
|
|
+ if (gelf_getsym (symdata, GELF_R_SYM (rela.r_info),
|
|
+ &sym) == NULL)
|
|
+ error (1, 0, "Couldn't get symbol: %s", elf_errmsg (-1));
|
|
+
|
|
+ rela.r_addend = relptr->addend - sym.st_value;
|
|
+
|
|
+ if (gelf_update_rela (data, ndx, &rela) == 0)
|
|
+ error (1, 0, "Couldn't update relocations: %s",
|
|
+ elf_errmsg (-1));
|
|
+
|
|
+ ++relptr;
|
|
+ }
|
|
+ elf_flagdata (data, ELF_C_SET, ELF_F_DIRTY);
|
|
}
|
|
+
|
|
free (relbuf);
|
|
}
|
|
|
|
@@ -1310,8 +2268,9 @@ fdopen_dso (int fd, const char *name)
|
|
GElf_Ehdr ehdr;
|
|
int i;
|
|
DSO *dso = NULL;
|
|
+ size_t phnum;
|
|
|
|
- elf = elf_begin (fd, ELF_C_RDWR_MMAP, NULL);
|
|
+ elf = elf_begin (fd, ELF_C_RDWR, NULL);
|
|
if (elf == NULL)
|
|
{
|
|
error (0, 0, "cannot open ELF file: %s", elf_errmsg (-1));
|
|
@@ -1348,10 +2307,20 @@ fdopen_dso (int fd, const char *name)
|
|
goto error_out;
|
|
}
|
|
|
|
- elf_flagelf (elf, ELF_C_SET, ELF_F_LAYOUT);
|
|
+ if (elf_getphdrnum (elf, &phnum) != 0)
|
|
+ {
|
|
+ error (0, 0, "Couldn't get number of phdrs: %s", elf_errmsg (-1));
|
|
+ goto error_out;
|
|
+ }
|
|
+
|
|
+ /* If there are phdrs we want to maintain the layout of the
|
|
+ allocated sections in the file. */
|
|
+ if (phnum != 0)
|
|
+ elf_flagelf (elf, ELF_C_SET, ELF_F_LAYOUT);
|
|
|
|
memset (dso, 0, sizeof(DSO));
|
|
dso->elf = elf;
|
|
+ dso->phnum = phnum;
|
|
dso->ehdr = ehdr;
|
|
dso->scn = (Elf_Scn **) &dso->shdr[ehdr.e_shnum + 20];
|
|
|
|
@@ -1362,12 +2331,16 @@ fdopen_dso (int fd, const char *name)
|
|
}
|
|
|
|
dso->filename = (const char *) strdup (name);
|
|
+ setup_strings (&dso->strings);
|
|
+ setup_lines (&dso->lines);
|
|
return dso;
|
|
|
|
error_out:
|
|
if (dso)
|
|
{
|
|
free ((char *) dso->filename);
|
|
+ destroy_strings (&dso->strings);
|
|
+ destroy_lines (&dso->lines);
|
|
free (dso);
|
|
}
|
|
if (elf)
|
|
@@ -1406,13 +2379,6 @@ handle_build_id (DSO *dso, Elf_Data *build_id,
|
|
if (!dirty_elf && build_id_seed == NULL)
|
|
goto print;
|
|
|
|
- if (elf_update (dso->elf, ELF_C_NULL) < 0)
|
|
- {
|
|
- fprintf (stderr, "Failed to update file: %s\n",
|
|
- elf_errmsg (elf_errno ()));
|
|
- exit (1);
|
|
- }
|
|
-
|
|
/* Clear the old bits so they do not affect the new hash. */
|
|
memset ((char *) build_id->d_buf + build_id_offset, 0, build_id_size);
|
|
|
|
@@ -1475,7 +2441,7 @@ handle_build_id (DSO *dso, Elf_Data *build_id,
|
|
|
|
if (u.shdr.sh_type != SHT_NOBITS)
|
|
{
|
|
- Elf_Data *d = elf_rawdata (dso->scn[i], NULL);
|
|
+ Elf_Data *d = elf_getdata (dso->scn[i], NULL);
|
|
if (d == NULL)
|
|
goto bad;
|
|
rpmDigestUpdate(ctx, d->d_buf, d->d_size);
|
|
@@ -1509,7 +2475,6 @@ main (int argc, char *argv[])
|
|
int nextopt;
|
|
const char **args;
|
|
struct stat stat_buf;
|
|
- char *p;
|
|
Elf_Data *build_id = NULL;
|
|
size_t build_id_offset = 0, build_id_size = 0;
|
|
|
|
@@ -1541,11 +2506,6 @@ main (int argc, char *argv[])
|
|
fprintf (stderr, "You must specify a base dir if you specify a dest dir\n");
|
|
exit (1);
|
|
}
|
|
- if (strlen (dest_dir) > strlen (base_dir))
|
|
- {
|
|
- fprintf (stderr, "Dest dir longer than base dir is not supported\n");
|
|
- exit (1);
|
|
- }
|
|
}
|
|
|
|
if (build_id_seed != NULL && do_build_id == 0)
|
|
@@ -1561,30 +2521,13 @@ main (int argc, char *argv[])
|
|
exit (1);
|
|
}
|
|
|
|
- /* Ensure clean paths, users can muck with these */
|
|
+ /* Ensure clean paths, users can muck with these. Also removes any
|
|
+ trailing '/' from the paths. */
|
|
if (base_dir)
|
|
canonicalize_path(base_dir, base_dir);
|
|
if (dest_dir)
|
|
canonicalize_path(dest_dir, dest_dir);
|
|
|
|
- /* Make sure there are trailing slashes in dirs */
|
|
- if (base_dir != NULL && base_dir[strlen (base_dir)-1] != '/')
|
|
- {
|
|
- p = malloc (strlen (base_dir) + 2);
|
|
- strcpy (p, base_dir);
|
|
- strcat (p, "/");
|
|
- free (base_dir);
|
|
- base_dir = p;
|
|
- }
|
|
- if (dest_dir != NULL && dest_dir[strlen (dest_dir)-1] != '/')
|
|
- {
|
|
- p = malloc (strlen (dest_dir) + 2);
|
|
- strcpy (p, dest_dir);
|
|
- strcat (p, "/");
|
|
- free (dest_dir);
|
|
- dest_dir = p;
|
|
- }
|
|
-
|
|
if (list_file != NULL)
|
|
{
|
|
list_file_fd = open (list_file, O_WRONLY|O_CREAT|O_APPEND, 0644);
|
|
@@ -1641,7 +2584,7 @@ main (int argc, char *argv[])
|
|
&& build_id == NULL && (dso->shdr[i].sh_flags & SHF_ALLOC))
|
|
{
|
|
/* Look for a build-ID note here. */
|
|
- Elf_Data *data = elf_rawdata (elf_getscn (dso->elf, i), NULL);
|
|
+ Elf_Data *data = elf_getdata (elf_getscn (dso->elf, i), NULL);
|
|
Elf32_Nhdr nh;
|
|
Elf_Data dst =
|
|
{
|
|
@@ -1679,6 +2622,123 @@ main (int argc, char *argv[])
|
|
}
|
|
}
|
|
|
|
+ /* We might have changed the size of some debug sections. If so make
|
|
+ sure the section headers are updated and the data offsets are
|
|
+ correct. We set ELF_F_LAYOUT above because we don't want libelf
|
|
+ to move any allocated sections around itself if there are any
|
|
+ phdrs. Which means we are reponsible for setting the section size
|
|
+ and offset fields. Plus the shdr offsets. We don't want to change
|
|
+ anything for the phdrs allocated sections. Keep the offset of
|
|
+ allocated sections so they are at the same place in the file. Add
|
|
+ unallocated ones after the allocated ones. */
|
|
+ if (dso->phnum != 0 && (need_strp_update || need_stmt_update))
|
|
+ {
|
|
+ Elf *elf = dso->elf;
|
|
+ GElf_Off last_offset;
|
|
+ /* We position everything after the phdrs (which normally would
|
|
+ be at the start of the ELF file after the ELF header. */
|
|
+ last_offset = (dso->ehdr.e_phoff + gelf_fsize (elf, ELF_T_PHDR,
|
|
+ dso->phnum, EV_CURRENT));
|
|
+
|
|
+ /* First find the last allocated section. */
|
|
+ Elf_Scn *scn = NULL;
|
|
+ while ((scn = elf_nextscn (elf, scn)) != NULL)
|
|
+ {
|
|
+ GElf_Shdr shdr_mem;
|
|
+ GElf_Shdr *shdr = gelf_getshdr (scn, &shdr_mem);
|
|
+ if (shdr == NULL)
|
|
+ error (1, 0, "Couldn't get shdr: %s\n", elf_errmsg (-1));
|
|
+
|
|
+ /* Any sections we have changed aren't allocated sections,
|
|
+ so we don't need to lookup any changed section sizes. */
|
|
+ if ((shdr->sh_flags & SHF_ALLOC) != 0)
|
|
+ {
|
|
+ GElf_Off off = shdr->sh_offset + (shdr->sh_type != SHT_NOBITS
|
|
+ ? shdr->sh_size : 0);
|
|
+ if (last_offset < off)
|
|
+ last_offset = off;
|
|
+ }
|
|
+ }
|
|
+
|
|
+ /* Now adjust any sizes and offsets for the unallocated sections. */
|
|
+ scn = NULL;
|
|
+ while ((scn = elf_nextscn (elf, scn)) != NULL)
|
|
+ {
|
|
+ GElf_Shdr shdr_mem;
|
|
+ GElf_Shdr *shdr = gelf_getshdr (scn, &shdr_mem);
|
|
+ if (shdr == NULL)
|
|
+ error (1, 0, "Couldn't get shdr: %s\n", elf_errmsg (-1));
|
|
+
|
|
+ /* A bug in elfutils before 0.169 means we have to write out
|
|
+ all section data, even when nothing changed.
|
|
+ https://sourceware.org/bugzilla/show_bug.cgi?id=21199 */
|
|
+#if !_ELFUTILS_PREREQ (0, 169)
|
|
+ if (shdr->sh_type != SHT_NOBITS)
|
|
+ {
|
|
+ Elf_Data *d = elf_getdata (scn, NULL);
|
|
+ elf_flagdata (d, ELF_C_SET, ELF_F_DIRTY);
|
|
+ }
|
|
+#endif
|
|
+ if ((shdr->sh_flags & SHF_ALLOC) == 0)
|
|
+ {
|
|
+ GElf_Off sec_offset = shdr->sh_offset;
|
|
+ GElf_Xword sec_size = shdr->sh_size;
|
|
+
|
|
+ /* We might have changed the size (and content) of the
|
|
+ debug_str or debug_line section. */
|
|
+ size_t secnum = elf_ndxscn (scn);
|
|
+ if (secnum == debug_sections[DEBUG_STR].sec)
|
|
+ sec_size = debug_sections[DEBUG_STR].size;
|
|
+ if (secnum == debug_sections[DEBUG_LINE].sec)
|
|
+ sec_size = debug_sections[DEBUG_LINE].size;
|
|
+
|
|
+ /* Zero means one. No alignment constraints. */
|
|
+ size_t addralign = shdr->sh_addralign ?: 1;
|
|
+ last_offset = (last_offset + addralign - 1) & ~(addralign - 1);
|
|
+ sec_offset = last_offset;
|
|
+ if (shdr->sh_type != SHT_NOBITS)
|
|
+ last_offset += sec_size;
|
|
+
|
|
+ if (shdr->sh_size != sec_size
|
|
+ || shdr->sh_offset != sec_offset)
|
|
+ {
|
|
+ /* Make sure unchanged section data is written out
|
|
+ at the new location. */
|
|
+ if (shdr->sh_offset != sec_offset
|
|
+ && shdr->sh_type != SHT_NOBITS)
|
|
+ {
|
|
+ Elf_Data *d = elf_getdata (scn, NULL);
|
|
+ elf_flagdata (d, ELF_C_SET, ELF_F_DIRTY);
|
|
+ }
|
|
+
|
|
+ shdr->sh_size = sec_size;
|
|
+ shdr->sh_offset = sec_offset;
|
|
+ if (gelf_update_shdr (scn, shdr) == 0)
|
|
+ error (1, 0, "Couldn't update shdr: %s\n",
|
|
+ elf_errmsg (-1));
|
|
+ }
|
|
+ }
|
|
+ }
|
|
+
|
|
+ /* Position the shdrs after the last (unallocated) section. */
|
|
+ const size_t offsize = gelf_fsize (elf, ELF_T_OFF, 1, EV_CURRENT);
|
|
+ GElf_Off new_offset = ((last_offset + offsize - 1)
|
|
+ & ~((GElf_Off) (offsize - 1)));
|
|
+ if (dso->ehdr.e_shoff != new_offset)
|
|
+ {
|
|
+ dso->ehdr.e_shoff = new_offset;
|
|
+ if (gelf_update_ehdr (elf, &dso->ehdr) == 0)
|
|
+ error (1, 0, "Couldn't update ehdr: %s\n", elf_errmsg (-1));
|
|
+ }
|
|
+ }
|
|
+
|
|
+ if (elf_update (dso->elf, ELF_C_NULL) < 0)
|
|
+ {
|
|
+ fprintf (stderr, "Failed to update file: %s\n",
|
|
+ elf_errmsg (elf_errno ()));
|
|
+ exit (1);
|
|
+ }
|
|
+
|
|
if (do_build_id && build_id != NULL)
|
|
handle_build_id (dso, build_id, build_id_offset, build_id_size);
|
|
|
|
@@ -1697,6 +2757,11 @@ main (int argc, char *argv[])
|
|
/* Restore old access rights */
|
|
chmod (file, stat_buf.st_mode);
|
|
|
|
+ free ((char *) dso->filename);
|
|
+ destroy_strings (&dso->strings);
|
|
+ destroy_lines (&dso->lines);
|
|
+ free (dso);
|
|
+
|
|
poptFreeContext (optCon);
|
|
|
|
return 0;
|
|
--
|
|
2.9.3
|
|
|