diff options
Diffstat (limited to 'tools/debugedit.c')
-rw-r--r-- | tools/debugedit.c | 1661 |
1 files changed, 1661 insertions, 0 deletions
diff --git a/tools/debugedit.c b/tools/debugedit.c new file mode 100644 index 0000000..d1b9335 --- /dev/null +++ b/tools/debugedit.c @@ -0,0 +1,1661 @@ +/* Copyright (C) 2001, 2002, 2003, 2005, 2007, 2009, 2010, 2011 Red Hat, Inc. + Written by Alexander Larsson <alexl@redhat.com>, 2002 + Based on code by Jakub Jelinek <jakub@redhat.com>, 2001. + + 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 + the Free Software Foundation; either version 2, or (at your option) + any later version. + + This program 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 this program; if not, write to the Free Software Foundation, + Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ + +#include "system.h" + +/* Needed for libelf */ +#define _FILE_OFFSET_BITS 64 + +#include <assert.h> +#include <byteswap.h> +#include <endian.h> +#include <errno.h> +#include <error.h> +#include <limits.h> +#include <string.h> +#include <stdlib.h> +#include <stdint.h> +#include <unistd.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <fcntl.h> +#include <popt.h> + +#include <gelf.h> +#include <dwarf.h> + +#include <rpm/rpmio.h> +#include <rpm/rpmpgp.h> +#include "tools/hashtab.h" + +#define DW_TAG_partial_unit 0x3c +#define DW_FORM_sec_offset 0x17 +#define DW_FORM_exprloc 0x18 +#define DW_FORM_flag_present 0x19 +#define DW_FORM_ref_sig8 0x20 + +char *base_dir = NULL; +char *dest_dir = NULL; +char *list_file = NULL; +int list_file_fd = -1; +int do_build_id = 0; + +typedef struct +{ + Elf *elf; + GElf_Ehdr ehdr; + Elf_Scn **scn; + const char *filename; + int lastscn; + GElf_Shdr shdr[0]; +} DSO; + +typedef struct +{ + unsigned char *ptr; + uint32_t addend; +} REL; + +#define read_uleb128(ptr) ({ \ + unsigned int ret = 0; \ + unsigned int c; \ + int shift = 0; \ + do \ + { \ + c = *ptr++; \ + ret |= (c & 0x7f) << shift; \ + shift += 7; \ + } while (c & 0x80); \ + \ + if (shift >= 35) \ + ret = UINT_MAX; \ + ret; \ +}) + +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 int ptr_size; +static int cu_version; + +static inline uint16_t +buf_read_ule16 (unsigned char *data) +{ + return data[0] | (data[1] << 8); +} + +static inline uint16_t +buf_read_ube16 (unsigned char *data) +{ + return data[1] | (data[0] << 8); +} + +static inline uint32_t +buf_read_ule32 (unsigned char *data) +{ + return data[0] | (data[1] << 8) | (data[2] << 16) | (data[3] << 24); +} + +static inline uint32_t +buf_read_ube32 (unsigned char *data) +{ + return data[3] | (data[2] << 8) | (data[1] << 16) | (data[0] << 24); +} + +static const char * +strptr (DSO *dso, int sec, off_t offset) +{ + Elf_Scn *scn; + Elf_Data *data; + + scn = dso->scn[sec]; + if (offset >= 0 && (GElf_Addr) offset < dso->shdr[sec].sh_size) + { + data = NULL; + while ((data = elf_rawdata (scn, data)) != NULL) + { + if (data->d_buf + && offset >= data->d_off + && offset < data->d_off + data->d_size) + return (const char *) data->d_buf + (offset - data->d_off); + } + } + + return NULL; +} + + +#define read_1(ptr) *ptr++ + +#define read_16(ptr) ({ \ + uint16_t ret = do_read_16 (ptr); \ + ptr += 2; \ + ret; \ +}) + +#define read_32(ptr) ({ \ + uint32_t ret = do_read_32 (ptr); \ + ptr += 4; \ + ret; \ +}) + +REL *relptr, *relend; +int reltype; + +#define do_read_32_relocated(ptr) ({ \ + uint32_t dret = do_read_32 (ptr); \ + if (relptr) \ + { \ + while (relptr < relend && relptr->ptr < ptr) \ + ++relptr; \ + if (relptr < relend && relptr->ptr == ptr) \ + { \ + if (reltype == SHT_REL) \ + dret += relptr->addend; \ + else \ + dret = relptr->addend; \ + } \ + } \ + dret; \ +}) + +#define read_32_relocated(ptr) ({ \ + uint32_t ret = do_read_32_relocated (ptr); \ + ptr += 4; \ + ret; \ +}) + +static void +dwarf2_write_le32 (unsigned char *p, GElf_Addr val) +{ + uint32_t v = (uint32_t) val; + + 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) +{ + uint32_t v = (uint32_t) val; + + p[3] = v; + p[2] = v >> 8; + p[1] = v >> 16; + p[0] = v >> 24; +} + +static struct + { + const char *name; + unsigned char *data; + Elf_Data *elf_data; + size_t size; + int sec, relsec; + } debug_sections[] = + { +#define DEBUG_INFO 0 +#define DEBUG_ABBREV 1 +#define DEBUG_LINE 2 +#define DEBUG_ARANGES 3 +#define DEBUG_PUBNAMES 4 +#define DEBUG_PUBTYPES 5 +#define DEBUG_MACINFO 6 +#define DEBUG_LOC 7 +#define DEBUG_STR 8 +#define DEBUG_FRAME 9 +#define DEBUG_RANGES 10 +#define DEBUG_TYPES 11 + { ".debug_info", NULL, NULL, 0, 0, 0 }, + { ".debug_abbrev", NULL, NULL, 0, 0, 0 }, + { ".debug_line", NULL, NULL, 0, 0, 0 }, + { ".debug_aranges", NULL, NULL, 0, 0, 0 }, + { ".debug_pubnames", NULL, NULL, 0, 0, 0 }, + { ".debug_pubtypes", NULL, NULL, 0, 0, 0 }, + { ".debug_macinfo", NULL, NULL, 0, 0, 0 }, + { ".debug_loc", NULL, NULL, 0, 0, 0 }, + { ".debug_str", NULL, NULL, 0, 0, 0 }, + { ".debug_frame", NULL, NULL, 0, 0, 0 }, + { ".debug_ranges", NULL, NULL, 0, 0, 0 }, + { ".debug_types", NULL, NULL, 0, 0, 0 }, + { NULL, NULL, NULL, 0, 0, 0 } + }; + +struct abbrev_attr + { + unsigned int attr; + unsigned int form; + }; + +struct abbrev_tag + { + unsigned int entry; + unsigned int tag; + int nattr; + struct abbrev_attr attr[0]; + }; + +static hashval_t +abbrev_hash (const void *p) +{ + struct abbrev_tag *t = (struct abbrev_tag *)p; + + return t->entry; +} + +static int +abbrev_eq (const void *p, const void *q) +{ + struct abbrev_tag *t1 = (struct abbrev_tag *)p; + struct abbrev_tag *t2 = (struct abbrev_tag *)q; + + return t1->entry == t2->entry; +} + +static void +abbrev_del (void *p) +{ + free (p); +} + +static htab_t +read_abbrev (DSO *dso, unsigned char *ptr) +{ + htab_t h = htab_try_create (50, abbrev_hash, abbrev_eq, abbrev_del); + unsigned int attr, form; + struct abbrev_tag *t; + int size; + void **slot; + + if (h == NULL) + { +no_memory: + error (0, ENOMEM, "%s: Could not read .debug_abbrev", dso->filename); + if (h) + htab_delete (h); + return NULL; + } + + while ((attr = read_uleb128 (ptr)) != 0) + { + size = 10; + t = malloc (sizeof (*t) + size * sizeof (struct abbrev_attr)); + if (t == NULL) + goto no_memory; + t->entry = attr; + t->nattr = 0; + slot = htab_find_slot (h, t, INSERT); + if (slot == NULL) + { + free (t); + goto no_memory; + } + if (*slot != NULL) + { + error (0, 0, "%s: Duplicate DWARF abbreviation %d", dso->filename, + t->entry); + free (t); + htab_delete (h); + return NULL; + } + t->tag = read_uleb128 (ptr); + ++ptr; /* skip children flag. */ + while ((attr = read_uleb128 (ptr)) != 0) + { + if (t->nattr == size) + { + size += 10; + t = realloc (t, sizeof (*t) + size * sizeof (struct abbrev_attr)); + if (t == NULL) + goto no_memory; + } + form = read_uleb128 (ptr); + if (form == 2 + || (form > DW_FORM_flag_present && form != DW_FORM_ref_sig8)) + { + error (0, 0, "%s: Unknown DWARF DW_FORM_%d", dso->filename, form); + htab_delete (h); + return NULL; + } + + t->attr[t->nattr].attr = attr; + t->attr[t->nattr++].form = form; + } + if (read_uleb128 (ptr) != 0) + { + error (0, 0, "%s: DWARF abbreviation does not end with 2 zeros", + dso->filename); + htab_delete (h); + return NULL; + } + *slot = t; + } + + return h; +} + +#define IS_DIR_SEPARATOR(c) ((c)=='/') + +static char * +canonicalize_path (const char *s, char *d) +{ + char *rv = d; + const char *sroot; + char *droot; + + if (IS_DIR_SEPARATOR (*s)) + { + *d++ = *s++; + if (IS_DIR_SEPARATOR (*s) && !IS_DIR_SEPARATOR (s[1])) + { + /* Special case for "//foo" meaning a Posix namespace + escape. */ + *d++ = *s++; + } + while (IS_DIR_SEPARATOR (*s)) + s++; + } + droot = d; + sroot = s; + + while (*s) + { + /* At this point, we're always at the beginning of a path + segment. */ + + if (s[0] == '.' && (s[1] == 0 || IS_DIR_SEPARATOR (s[1]))) + { + s++; + if (*s) + while (IS_DIR_SEPARATOR (*s)) + ++s; + } + + else if (s[0] == '.' && s[1] == '.' + && (s[2] == 0 || IS_DIR_SEPARATOR (s[2]))) + { + char *pre = d - 1; /* includes slash */ + while (droot < pre && IS_DIR_SEPARATOR (*pre)) + pre--; + if (droot <= pre && ! IS_DIR_SEPARATOR (*pre)) + { + while (droot < pre && ! IS_DIR_SEPARATOR (*pre)) + pre--; + /* pre now points to the slash */ + if (droot < pre) + pre++; + if (pre + 3 == d && pre[0] == '.' && pre[1] == '.') + { + *d++ = *s++; + *d++ = *s++; + } + else + { + d = pre; + s += 2; + if (*s) + while (IS_DIR_SEPARATOR (*s)) + s++; + } + } + else + { + *d++ = *s++; + *d++ = *s++; + } + } + else + { + while (*s && ! IS_DIR_SEPARATOR (*s)) + *d++ = *s++; + } + + if (IS_DIR_SEPARATOR (*s)) + { + *d++ = *s++; + while (IS_DIR_SEPARATOR (*s)) + s++; + } + } + while (droot < d && IS_DIR_SEPARATOR (d[-1])) + --d; + if (d == rv) + *d++ = '.'; + *d = 0; + + return rv; +} + +static int +has_prefix (const char *str, + const char *prefix) +{ + size_t str_len; + size_t prefix_len; + + str_len = strlen (str); + prefix_len = strlen (prefix); + + if (str_len < prefix_len) + return 0; + + return strncmp (str, prefix, prefix_len) == 0; +} + +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 +edit_dwarf2_line (DSO *dso, uint32_t off, char *comp_dir, int phase) +{ + 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 = strlen (comp_dir); + size_t abs_file_cnt = 0, abs_dir_cnt = 0; + + if (phase != 0) + return 0; + + ptr += off; + + endcu = ptr + 4; + endcu += read_32 (ptr); + if (endcu == ptr + 0xffffffff) + { + error (0, 0, "%s: 64-bit DWARF not supported", dso->filename); + return 1; + } + + if (endcu > endsec) + { + error (0, 0, "%s: .debug_line CU does not fit into section", + dso->filename); + return 1; + } + + value = read_16 (ptr); + if (value != 2 && value != 3 && value != 4) + { + error (0, 0, "%s: DWARF version %d unhandled", dso->filename, + value); + return 1; + } + + endprol = ptr + 4; + endprol += read_32 (ptr); + if (endprol > endcu) + { + error (0, 0, "%s: .debug_line CU prologue does not fit into CU", + dso->filename); + return 1; + } + + opcode_base = ptr[4 + (value >= 4)]; + ptr = dir = ptr + 4 + (value >= 4) + opcode_base; + + /* dir table: */ + value = 1; + while (*ptr != 0) + { + ptr = (unsigned char *) strchr ((char *)ptr, 0) + 1; + ++value; + } + + dirt = (unsigned char **) alloca (value * sizeof (unsigned char *)); + dirt[0] = (unsigned char *) "."; + dirt_cnt = 1; + ptr = dir; + while (*ptr != 0) + { + dirt[dirt_cnt++] = ptr; + ptr = (unsigned char *) strchr ((char *)ptr, 0) + 1; + } + ptr++; + + /* file table: */ + while (*ptr != 0) + { + char *s, *file; + size_t file_len, dir_len; + + file = (char *) ptr; + ptr = (unsigned char *) strchr ((char *)ptr, 0) + 1; + value = read_uleb128 (ptr); + + if (value >= dirt_cnt) + { + error (0, 0, "%s: Wrong directory table index %u", + dso->filename, value); + return 1; + } + file_len = strlen (file); + 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; + } + if (*file == '/') + { + memcpy (s, file, file_len + 1); + if (dest_dir && has_prefix (file, base_dir)) + ++abs_file_cnt; + } + else if (*dirt[value] == '/') + { + memcpy (s, dirt[value], dir_len); + s[dir_len] = '/'; + memcpy (s + dir_len + 1, file, file_len + 1); + } + else + { + char *p = s; + if (comp_dir_len != 0) + { + memcpy (s, comp_dir, comp_dir_len); + s[comp_dir_len] = '/'; + p += comp_dir_len + 1; + } + memcpy (p, dirt[value], dir_len); + p[dir_len] = '/'; + memcpy (p + dir_len + 1, file, file_len + 1); + } + canonicalize_path (s, s); + if (list_file_fd != -1) + { + 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); + + if (p) + { + size_t size = strlen (p) + 1; + while (size > 0) + { + ssize_t ret = write (list_file_fd, p, size); + if (ret == -1) + break; + size -= ret; + p += ret; + } + } + } + + free (s); + + 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; + + 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; +} + +static unsigned char * +edit_attributes (DSO *dso, unsigned char *ptr, struct abbrev_tag *t, int phase) +{ + int i; + uint32_t list_offs; + int found_list_offs; + char *comp_dir; + + comp_dir = NULL; + list_offs = 0; + found_list_offs = 0; + for (i = 0; i < t->nattr; ++i) + { + uint32_t form = t->attr[i].form; + size_t len = 0; + size_t base_len, dest_len; + + while (1) + { + 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 (t->attr[i].attr == DW_AT_comp_dir) + { + if (form == DW_FORM_string) + { + free (comp_dir); + comp_dir = strdup ((char *)ptr); + + if (phase == 1 && dest_dir && has_prefix ((char *)ptr, base_dir)) + { + base_len = strlen (base_dir); + dest_len = strlen (dest_dir); + + memcpy (ptr, dest_dir, dest_len); + if (dest_len < base_len) + { + memset(ptr + dest_len, '/', + base_len - dest_len); + + } + 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); + + free (comp_dir); + comp_dir = strdup (dir); + + if (phase == 1 && dest_dir && has_prefix (dir, base_dir)) + { + 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); + } + } + } + else if ((t->tag == DW_TAG_compile_unit + || t->tag == DW_TAG_partial_unit) + && t->attr[i].attr == DW_AT_name + && form == DW_FORM_strp + && debug_sections[DEBUG_STR].data) + { + char *name; + + name = (char *) debug_sections[DEBUG_STR].data + + do_read_32_relocated (ptr); + if (*name == '/' && comp_dir == NULL) + { + char *enddir = strrchr (name, '/'); + + if (enddir != name) + { + comp_dir = malloc (enddir - name + 1); + memcpy (comp_dir, name, enddir - name); + comp_dir [enddir - name] = '\0'; + } + else + comp_dir = strdup ("/"); + } + + if (phase == 1 && dest_dir && has_prefix (name, base_dir)) + { + 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); + } + } + + switch (form) + { + case DW_FORM_ref_addr: + if (cu_version == 2) + ptr += ptr_size; + else + ptr += 4; + break; + case DW_FORM_flag_present: + break; + case DW_FORM_addr: + ptr += ptr_size; + break; + case DW_FORM_ref1: + case DW_FORM_flag: + case DW_FORM_data1: + ++ptr; + break; + case DW_FORM_ref2: + case DW_FORM_data2: + ptr += 2; + break; + case DW_FORM_ref4: + case DW_FORM_data4: + case DW_FORM_sec_offset: + ptr += 4; + break; + case DW_FORM_ref8: + case DW_FORM_data8: + case DW_FORM_ref_sig8: + ptr += 8; + break; + case DW_FORM_sdata: + case DW_FORM_ref_udata: + case DW_FORM_udata: + read_uleb128 (ptr); + break; + case DW_FORM_strp: + ptr += 4; + break; + case DW_FORM_string: + ptr = (unsigned char *) strchr ((char *)ptr, '\0') + 1; + break; + case DW_FORM_indirect: + form = read_uleb128 (ptr); + continue; + case DW_FORM_block1: + len = *ptr++; + break; + case DW_FORM_block2: + len = read_16 (ptr); + form = DW_FORM_block1; + break; + case DW_FORM_block4: + len = read_32 (ptr); + form = DW_FORM_block1; + break; + case DW_FORM_block: + case DW_FORM_exprloc: + len = read_uleb128 (ptr); + form = DW_FORM_block1; + assert (len < UINT_MAX); + break; + default: + error (0, 0, "%s: Unknown DWARF DW_FORM_%d", dso->filename, + form); + return NULL; + } + + if (form == DW_FORM_block1) + ptr += len; + + break; + } + } + + /* Ensure the CU current directory will exist even if only empty. Source + filenames possibly located in its parent directories refer relatively to + it and the debugger (GDB) cannot safely optimize out the missing + CU current dir subdirectories. */ + if (comp_dir && list_file_fd != -1) + { + char *p; + 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 + p = comp_dir; + + size = strlen (p) + 1; + while (size > 0) + { + ssize_t ret = write (list_file_fd, p, size); + if (ret == -1) + break; + size -= ret; + p += ret; + } + } + + if (found_list_offs && comp_dir) + edit_dwarf2_line (dso, list_offs, comp_dir, phase); + + free (comp_dir); + + return ptr; +} + +static int +rel_cmp (const void *a, const void *b) +{ + REL *rela = (REL *) a, *relb = (REL *) b; + + if (rela->ptr < relb->ptr) + return -1; + + if (rela->ptr > relb->ptr) + return 1; + + return 0; +} + +static int +edit_dwarf2 (DSO *dso) +{ + Elf_Data *data; + Elf_Scn *scn; + int i, j; + + for (i = 0; debug_sections[i].name; ++i) + { + debug_sections[i].data = NULL; + debug_sections[i].size = 0; + debug_sections[i].sec = 0; + debug_sections[i].relsec = 0; + } + ptr_size = 0; + + for (i = 1; i < dso->ehdr.e_shnum; ++i) + if (! (dso->shdr[i].sh_flags & (SHF_ALLOC | SHF_WRITE | SHF_EXECINSTR)) + && dso->shdr[i].sh_size) + { + const char *name = strptr (dso, dso->ehdr.e_shstrndx, + dso->shdr[i].sh_name); + + if (strncmp (name, ".debug_", sizeof (".debug_") - 1) == 0) + { + for (j = 0; debug_sections[j].name; ++j) + if (strcmp (name, debug_sections[j].name) == 0) + { + if (debug_sections[j].data) + { + error (0, 0, "%s: Found two copies of %s section", + dso->filename, name); + return 1; + } + + scn = dso->scn[i]; + data = elf_rawdata (scn, NULL); + assert (data != NULL && data->d_buf != NULL); + assert (elf_rawdata (scn, data) == NULL); + assert (data->d_off == 0); + assert (data->d_size == dso->shdr[i].sh_size); + debug_sections[j].data = data->d_buf; + debug_sections[j].elf_data = data; + debug_sections[j].size = data->d_size; + debug_sections[j].sec = i; + break; + } + + if (debug_sections[j].name == NULL) + { + error (0, 0, "%s: Unknown debugging section %s", + dso->filename, name); + } + } + else if (dso->ehdr.e_type == ET_REL + && ((dso->shdr[i].sh_type == SHT_REL + && strncmp (name, ".rel.debug_", + sizeof (".rel.debug_") - 1) == 0) + || (dso->shdr[i].sh_type == SHT_RELA + && strncmp (name, ".rela.debug_", + sizeof (".rela.debug_") - 1) == 0))) + { + for (j = 0; debug_sections[j].name; ++j) + if (strcmp (name + sizeof (".rel") - 1 + + (dso->shdr[i].sh_type == SHT_RELA), + debug_sections[j].name) == 0) + { + debug_sections[j].relsec = i; + break; + } + } + } + + if (dso->ehdr.e_ident[EI_DATA] == ELFDATA2LSB) + { + do_read_16 = buf_read_ule16; + do_read_32 = buf_read_ule32; + 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; + } + else + { + error (0, 0, "%s: Wrong ELF data enconding", dso->filename); + return 1; + } + + if (debug_sections[DEBUG_INFO].data != NULL) + { + unsigned char *ptr, *endcu, *endsec; + uint32_t value; + htab_t abbrev; + struct abbrev_tag tag, *t; + int phase; + REL *relbuf = NULL; + + if (debug_sections[DEBUG_INFO].relsec) + { + int ndx, maxndx; + GElf_Rel rel; + GElf_Rela rela; + GElf_Sym sym; + GElf_Addr base = dso->shdr[debug_sections[DEBUG_INFO].sec].sh_addr; + Elf_Data *symdata = NULL; + int rtype; + + i = debug_sections[DEBUG_INFO].relsec; + scn = dso->scn[i]; + data = elf_getdata (scn, NULL); + assert (data != NULL && data->d_buf != NULL); + assert (elf_getdata (scn, data) == NULL); + assert (data->d_off == 0); + assert (data->d_size == dso->shdr[i].sh_size); + maxndx = dso->shdr[i].sh_size / dso->shdr[i].sh_entsize; + relbuf = malloc (maxndx * sizeof (REL)); + reltype = dso->shdr[i].sh_type; + if (relbuf == NULL) + error (1, errno, "%s: Could not allocate memory", dso->filename); + + symdata = elf_getdata (dso->scn[dso->shdr[i].sh_link], NULL); + assert (symdata != NULL && symdata->d_buf != NULL); + assert (elf_getdata (dso->scn[dso->shdr[i].sh_link], symdata) + == NULL); + assert (symdata->d_off == 0); + assert (symdata->d_size + == dso->shdr[dso->shdr[i].sh_link].sh_size); + + for (ndx = 0, relend = relbuf; ndx < maxndx; ++ndx) + { + if (dso->shdr[i].sh_type == SHT_REL) + { + gelf_getrel (data, ndx, &rel); + rela.r_offset = rel.r_offset; + rela.r_info = rel.r_info; + rela.r_addend = 0; + } + else + gelf_getrela (data, ndx, &rela); + gelf_getsym (symdata, ELF64_R_SYM (rela.r_info), &sym); + /* Relocations against section symbols are uninteresting + in REL. */ + if (dso->shdr[i].sh_type == SHT_REL && sym.st_value == 0) + continue; + /* Only consider relocations against .debug_str, .debug_line + and .debug_abbrev. */ + if (sym.st_shndx != debug_sections[DEBUG_STR].sec + && sym.st_shndx != debug_sections[DEBUG_LINE].sec + && sym.st_shndx != debug_sections[DEBUG_ABBREV].sec) + continue; + rela.r_addend += sym.st_value; + rtype = ELF64_R_TYPE (rela.r_info); + switch (dso->ehdr.e_machine) + { + case EM_SPARC: + case EM_SPARC32PLUS: + case EM_SPARCV9: + if (rtype != R_SPARC_32 && rtype != R_SPARC_UA32) + goto fail; + break; + case EM_386: + if (rtype != R_386_32) + goto fail; + break; + case EM_PPC: + case EM_PPC64: + if (rtype != R_PPC_ADDR32 && rtype != R_PPC_UADDR32) + goto fail; + break; + case EM_S390: + if (rtype != R_390_32) + goto fail; + break; + case EM_IA_64: + if (rtype != R_IA64_SECREL32LSB) + goto fail; + break; + case EM_X86_64: + if (rtype != R_X86_64_32) + goto fail; + break; + case EM_ALPHA: + if (rtype != R_ALPHA_REFLONG) + goto fail; + break; + default: + fail: + error (1, 0, "%s: Unhandled relocation %d in .debug_info section", + dso->filename, rtype); + } + relend->ptr = debug_sections[DEBUG_INFO].data + + (rela.r_offset - base); + relend->addend = rela.r_addend; + ++relend; + } + if (relbuf == relend) + { + free (relbuf); + relbuf = NULL; + relend = NULL; + } + else + qsort (relbuf, relend - relbuf, sizeof (REL), rel_cmp); + } + + for (phase = 0; phase < 2; phase++) + { + ptr = debug_sections[DEBUG_INFO].data; + relptr = relbuf; + endsec = ptr + debug_sections[DEBUG_INFO].size; + while (ptr < endsec) + { + if (ptr + 11 > endsec) + { + error (0, 0, "%s: .debug_info CU header too small", + dso->filename); + return 1; + } + + endcu = ptr + 4; + endcu += read_32 (ptr); + if (endcu == ptr + 0xffffffff) + { + error (0, 0, "%s: 64-bit DWARF not supported", dso->filename); + return 1; + } + + if (endcu > endsec) + { + error (0, 0, "%s: .debug_info too small", dso->filename); + return 1; + } + + cu_version = read_16 (ptr); + if (cu_version != 2 && cu_version != 3 && cu_version != 4) + { + error (0, 0, "%s: DWARF version %d unhandled", dso->filename, + cu_version); + return 1; + } + + value = read_32_relocated (ptr); + if (value >= debug_sections[DEBUG_ABBREV].size) + { + if (debug_sections[DEBUG_ABBREV].data == NULL) + error (0, 0, "%s: .debug_abbrev not present", dso->filename); + else + error (0, 0, "%s: DWARF CU abbrev offset too large", + dso->filename); + return 1; + } + + if (ptr_size == 0) + { + ptr_size = read_1 (ptr); + if (ptr_size != 4 && ptr_size != 8) + { + error (0, 0, "%s: Invalid DWARF pointer size %d", + dso->filename, ptr_size); + return 1; + } + } + else if (read_1 (ptr) != ptr_size) + { + error (0, 0, "%s: DWARF pointer size differs between CUs", + dso->filename); + return 1; + } + + abbrev = read_abbrev (dso, + debug_sections[DEBUG_ABBREV].data + value); + if (abbrev == NULL) + return 1; + + while (ptr < endcu) + { + tag.entry = read_uleb128 (ptr); + if (tag.entry == 0) + continue; + t = htab_find_with_hash (abbrev, &tag, tag.entry); + if (t == NULL) + { + error (0, 0, "%s: Could not find DWARF abbreviation %d", + dso->filename, tag.entry); + htab_delete (abbrev); + return 1; + } + + ptr = edit_attributes (dso, ptr, t, phase); + if (ptr == NULL) + break; + } + + htab_delete (abbrev); + } + } + free (relbuf); + } + + return 0; +} + +static struct poptOption optionsTable[] = { + { "base-dir", 'b', POPT_ARG_STRING, &base_dir, 0, + "base build directory of objects", NULL }, + { "dest-dir", 'd', POPT_ARG_STRING, &dest_dir, 0, + "directory to rewrite base-dir into", NULL }, + { "list-file", 'l', POPT_ARG_STRING, &list_file, 0, + "file where to put list of source and header file names", NULL }, + { "build-id", 'i', POPT_ARG_NONE, &do_build_id, 0, + "recompute build ID note and print ID on stdout", NULL }, + POPT_AUTOHELP + { NULL, 0, 0, NULL, 0, NULL, NULL } +}; + +static DSO * +fdopen_dso (int fd, const char *name) +{ + Elf *elf = NULL; + GElf_Ehdr ehdr; + int i; + DSO *dso = NULL; + + elf = elf_begin (fd, ELF_C_RDWR_MMAP, NULL); + if (elf == NULL) + { + error (0, 0, "cannot open ELF file: %s", elf_errmsg (-1)); + goto error_out; + } + + if (elf_kind (elf) != ELF_K_ELF) + { + error (0, 0, "\"%s\" is not an ELF file", name); + goto error_out; + } + + if (gelf_getehdr (elf, &ehdr) == NULL) + { + error (0, 0, "cannot get the ELF header: %s", + elf_errmsg (-1)); + goto error_out; + } + + if (ehdr.e_type != ET_DYN && ehdr.e_type != ET_EXEC && ehdr.e_type != ET_REL) + { + error (0, 0, "\"%s\" is not a shared library", name); + goto error_out; + } + + /* Allocate DSO structure. Leave place for additional 20 new section + headers. */ + dso = (DSO *) + malloc (sizeof(DSO) + (ehdr.e_shnum + 20) * sizeof(GElf_Shdr) + + (ehdr.e_shnum + 20) * sizeof(Elf_Scn *)); + if (!dso) + { + error (0, ENOMEM, "Could not open DSO"); + goto error_out; + } + + elf_flagelf (elf, ELF_C_SET, ELF_F_LAYOUT); + + memset (dso, 0, sizeof(DSO)); + dso->elf = elf; + dso->ehdr = ehdr; + dso->scn = (Elf_Scn **) &dso->shdr[ehdr.e_shnum + 20]; + + for (i = 0; i < ehdr.e_shnum; ++i) + { + dso->scn[i] = elf_getscn (elf, i); + gelf_getshdr (dso->scn[i], dso->shdr + i); + } + + dso->filename = (const char *) strdup (name); + return dso; + +error_out: + if (dso) + { + free ((char *) dso->filename); + free (dso); + } + if (elf) + elf_end (elf); + if (fd != -1) + close (fd); + return NULL; +} + +static const pgpHashAlgo algorithms[] = { PGPHASHALGO_MD5, + PGPHASHALGO_SHA1, PGPHASHALGO_SHA256, PGPHASHALGO_SHA384, PGPHASHALGO_SHA512 }; + +/* Compute a fresh build ID bit-string from the editted file contents. */ +static void +handle_build_id (DSO *dso, Elf_Data *build_id, + size_t build_id_offset, size_t build_id_size) +{ + DIGEST_CTX ctx; + pgpHashAlgo algorithm; + int i = sizeof(algorithms)/sizeof(algorithms[0]); + void *digest = NULL; + size_t len; + + while (i-- > 0) + { + algorithm = algorithms[i]; + if (rpmDigestLength(algorithm) == build_id_size) + break; + } + if (i < 0) + { + fprintf (stderr, "Cannot handle %Zu-byte build ID\n", build_id_size); + exit (1); + } + + if (!dirty_elf) + 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); + + ctx = rpmDigestInit(algorithm, 0); + + /* Slurp the relevant header bits and section contents and feed them + into the hash function. The only bits we ignore are the offset + fields in ehdr and shdrs, since the semantically identical ELF file + could be written differently if it doesn't change the phdr layout. + We always use the GElf (i.e. Elf64) formats for the bits to hash + since it is convenient. It doesn't matter whether this is an Elf32 + or Elf64 object, only that we are consistent in what bits feed the + hash so it comes out the same for the same file contents. */ + { + union + { + GElf_Ehdr ehdr; + GElf_Phdr phdr; + GElf_Shdr shdr; + } u; + Elf_Data x = { .d_version = EV_CURRENT, .d_buf = &u }; + + x.d_type = ELF_T_EHDR; + x.d_size = sizeof u.ehdr; + u.ehdr = dso->ehdr; + u.ehdr.e_phoff = u.ehdr.e_shoff = 0; + if (elf64_xlatetom (&x, &x, dso->ehdr.e_ident[EI_DATA]) == NULL) + { + bad: + fprintf (stderr, "Failed to compute header checksum: %s\n", + elf_errmsg (elf_errno ())); + exit (1); + } + + x.d_type = ELF_T_PHDR; + x.d_size = sizeof u.phdr; + for (i = 0; i < dso->ehdr.e_phnum; ++i) + { + if (gelf_getphdr (dso->elf, i, &u.phdr) == NULL) + goto bad; + if (elf64_xlatetom (&x, &x, dso->ehdr.e_ident[EI_DATA]) == NULL) + goto bad; + rpmDigestUpdate(ctx, x.d_buf, x.d_size); + } + + x.d_type = ELF_T_SHDR; + x.d_size = sizeof u.shdr; + for (i = 0; i < dso->ehdr.e_shnum; ++i) + if (dso->scn[i] != NULL) + { + u.shdr = dso->shdr[i]; + u.shdr.sh_offset = 0; + if (elf64_xlatetom (&x, &x, dso->ehdr.e_ident[EI_DATA]) == NULL) + goto bad; + rpmDigestUpdate(ctx, x.d_buf, x.d_size); + + if (u.shdr.sh_type != SHT_NOBITS) + { + Elf_Data *d = elf_rawdata (dso->scn[i], NULL); + if (d == NULL) + goto bad; + rpmDigestUpdate(ctx, d->d_buf, d->d_size); + } + } + } + + rpmDigestFinal(ctx, &digest, &len, 0); + memcpy((unsigned char *)build_id->d_buf + build_id_offset, digest, build_id_size); + free(digest); + + elf_flagdata (build_id, ELF_C_SET, ELF_F_DIRTY); + + print: + /* Now format the build ID bits in hex to print out. */ + { + const uint8_t * id = (uint8_t *)build_id->d_buf + build_id_offset; + char *hex = pgpHexStr(id, build_id_size); + puts (hex); + free(hex); + } +} + +int +main (int argc, char *argv[]) +{ + DSO *dso; + int fd, i; + const char *file; + poptContext optCon; /* context for parsing command-line options */ + 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; + + optCon = poptGetContext("debugedit", argc, (const char **)argv, optionsTable, 0); + + while ((nextopt = poptGetNextOpt (optCon)) > 0 || nextopt == POPT_ERROR_BADOPT) + /* do nothing */ ; + + if (nextopt != -1) + { + fprintf (stderr, "Error on option %s: %s.\nRun '%s --help' to see a full list of available command line options.\n", + poptBadOption (optCon, 0), + poptStrerror (nextopt), + argv[0]); + exit (1); + } + + args = poptGetArgs (optCon); + if (args == NULL || args[0] == NULL || args[1] != NULL) + { + poptPrintHelp(optCon, stdout, 0); + exit (1); + } + + if (dest_dir != NULL) + { + if (base_dir == NULL) + { + 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); + } + } + + /* 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); + } + + file = args[0]; + + if (elf_version(EV_CURRENT) == EV_NONE) + { + fprintf (stderr, "library out of date\n"); + exit (1); + } + + if (stat(file, &stat_buf) < 0) + { + fprintf (stderr, "Failed to open input file '%s': %s\n", file, strerror(errno)); + exit (1); + } + + /* Make sure we can read and write */ + chmod (file, stat_buf.st_mode | S_IRUSR | S_IWUSR); + + fd = open (file, O_RDWR); + if (fd < 0) + { + fprintf (stderr, "Failed to open input file '%s': %s\n", file, strerror(errno)); + exit (1); + } + + dso = fdopen_dso (fd, file); + if (dso == NULL) + exit (1); + + for (i = 1; i < dso->ehdr.e_shnum; i++) + { + const char *name; + + switch (dso->shdr[i].sh_type) + { + case SHT_PROGBITS: + name = strptr (dso, dso->ehdr.e_shstrndx, dso->shdr[i].sh_name); + /* TODO: Handle stabs */ + if (strcmp (name, ".stab") == 0) + { + fprintf (stderr, "Stabs debuginfo not supported: %s\n", file); + exit (1); + } + if (strcmp (name, ".debug_info") == 0) + edit_dwarf2 (dso); + + break; + case SHT_NOTE: + if (do_build_id + && 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); + Elf32_Nhdr nh; + Elf_Data dst = + { + .d_version = EV_CURRENT, .d_type = ELF_T_NHDR, + .d_buf = &nh, .d_size = sizeof nh + }; + Elf_Data src = dst; + src.d_buf = data->d_buf; + assert (sizeof (Elf32_Nhdr) == sizeof (Elf64_Nhdr)); + while ((char *) data->d_buf + data->d_size - + (char *) src.d_buf > (int) sizeof nh + && elf32_xlatetom (&dst, &src, dso->ehdr.e_ident[EI_DATA])) + { + Elf32_Word len = sizeof nh + nh.n_namesz; + len = (len + 3) & ~3; + + if (nh.n_namesz == sizeof "GNU" && nh.n_type == 3 + && !memcmp ((char *) src.d_buf + sizeof nh, "GNU", sizeof "GNU")) + { + build_id = data; + build_id_offset = (char *) src.d_buf + len - + (char *) data->d_buf; + build_id_size = nh.n_descsz; + break; + } + + len += nh.n_descsz; + len = (len + 3) & ~3; + src.d_buf = (char *) src.d_buf + len; + } + } + break; + default: + break; + } + } + + if (do_build_id && build_id != NULL) + handle_build_id (dso, build_id, build_id_offset, build_id_size); + + if (elf_update (dso->elf, ELF_C_WRITE) < 0) + { + fprintf (stderr, "Failed to write file: %s\n", elf_errmsg (elf_errno())); + exit (1); + } + if (elf_end (dso->elf) < 0) + { + fprintf (stderr, "elf_end failed: %s\n", elf_errmsg (elf_errno())); + exit (1); + } + close (fd); + + /* Restore old access rights */ + chmod (file, stat_buf.st_mode); + + poptFreeContext (optCon); + + return 0; +} |