diff options
Diffstat (limited to 'tools/objtool/elf.c')
| -rw-r--r-- | tools/objtool/elf.c | 1635 |
1 files changed, 1175 insertions, 460 deletions
diff --git a/tools/objtool/elf.c b/tools/objtool/elf.c index 4b384c907027..6a8ed9c62323 100644 --- a/tools/objtool/elf.c +++ b/tools/objtool/elf.c @@ -16,12 +16,16 @@ #include <string.h> #include <unistd.h> #include <errno.h> +#include <libgen.h> +#include <ctype.h> +#include <linux/interval_tree_generic.h> #include <objtool/builtin.h> - #include <objtool/elf.h> #include <objtool/warn.h> -#define MAX_NAME_LEN 128 +#define ALIGN_UP(x, align_to) (((x) + ((align_to)-1)) & ~((align_to)-1)) +#define ALIGN_UP_POW2(x) (1U << ((8 * sizeof(x)) - __builtin_clz((x) - 1U))) +#define MAX(a, b) ((a) > (b) ? (a) : (b)) static inline u32 str_hash(const char *str) { @@ -31,54 +35,99 @@ static inline u32 str_hash(const char *str) #define __elf_table(name) (elf->name##_hash) #define __elf_bits(name) (elf->name##_bits) -#define elf_hash_add(name, node, key) \ - hlist_add_head(node, &__elf_table(name)[hash_min(key, __elf_bits(name))]) - -#define elf_hash_for_each_possible(name, obj, member, key) \ - hlist_for_each_entry(obj, &__elf_table(name)[hash_min(key, __elf_bits(name))], member) - -#define elf_alloc_hash(name, size) \ -({ \ - __elf_bits(name) = max(10, ilog2(size)); \ - __elf_table(name) = mmap(NULL, sizeof(struct hlist_head) << __elf_bits(name), \ - PROT_READ|PROT_WRITE, \ - MAP_PRIVATE|MAP_ANON, -1, 0); \ - if (__elf_table(name) == (void *)-1L) { \ - WARN("mmap fail " #name); \ - __elf_table(name) = NULL; \ - } \ - __elf_table(name); \ +#define __elf_table_entry(name, key) \ + __elf_table(name)[hash_min(key, __elf_bits(name))] + +#define elf_hash_add(name, node, key) \ +({ \ + struct elf_hash_node *__node = node; \ + __node->next = __elf_table_entry(name, key); \ + __elf_table_entry(name, key) = __node; \ }) -static bool symbol_to_offset(struct rb_node *a, const struct rb_node *b) +static inline void __elf_hash_del(struct elf_hash_node *node, + struct elf_hash_node **head) { - struct symbol *sa = rb_entry(a, struct symbol, node); - struct symbol *sb = rb_entry(b, struct symbol, node); + struct elf_hash_node *cur, *prev; - if (sa->offset < sb->offset) - return true; - if (sa->offset > sb->offset) - return false; + if (node == *head) { + *head = node->next; + return; + } - if (sa->len < sb->len) - return true; - if (sa->len > sb->len) - return false; + for (prev = NULL, cur = *head; cur; prev = cur, cur = cur->next) { + if (cur == node) { + prev->next = cur->next; + break; + } + } +} + +#define elf_hash_del(name, node, key) \ + __elf_hash_del(node, &__elf_table_entry(name, key)) - sa->alias = sb; +#define elf_list_entry(ptr, type, member) \ +({ \ + typeof(ptr) __ptr = (ptr); \ + __ptr ? container_of(__ptr, type, member) : NULL; \ +}) - return false; +#define elf_hash_for_each_possible(name, obj, member, key) \ + for (obj = elf_list_entry(__elf_table_entry(name, key), typeof(*obj), member); \ + obj; \ + obj = elf_list_entry(obj->member.next, typeof(*(obj)), member)) + +#define elf_alloc_hash(name, size) \ +({ \ + __elf_bits(name) = max(10, ilog2(size)); \ + __elf_table(name) = mmap(NULL, sizeof(struct elf_hash_node *) << __elf_bits(name), \ + PROT_READ|PROT_WRITE, \ + MAP_PRIVATE|MAP_ANON, -1, 0); \ + if (__elf_table(name) == (void *)-1L) { \ + ERROR_GLIBC("mmap fail " #name); \ + __elf_table(name) = NULL; \ + } \ + __elf_table(name); \ +}) + +static inline unsigned long __sym_start(struct symbol *s) +{ + return s->offset; } -static int symbol_by_offset(const void *key, const struct rb_node *node) +static inline unsigned long __sym_last(struct symbol *s) +{ + return s->offset + (s->len ? s->len - 1 : 0); +} + +INTERVAL_TREE_DEFINE(struct symbol, node, unsigned long, __subtree_last, + __sym_start, __sym_last, static inline __maybe_unused, + __sym) + +#define __sym_for_each(_iter, _tree, _start, _end) \ + for (_iter = __sym_iter_first((_tree), (_start), (_end)); \ + _iter; _iter = __sym_iter_next(_iter, (_start), (_end))) + +struct symbol_hole { + unsigned long key; + const struct symbol *sym; +}; + +/* + * Find the last symbol before @offset. + */ +static int symbol_hole_by_offset(const void *key, const struct rb_node *node) { const struct symbol *s = rb_entry(node, struct symbol, node); - const unsigned long *o = key; + struct symbol_hole *sh = (void *)key; - if (*o < s->offset) + if (sh->key < s->offset) return -1; - if (*o >= s->offset + s->len) + + if (sh->key >= s->offset + s->len) { + sh->sym = s; return 1; + } return 0; } @@ -122,13 +171,12 @@ static struct symbol *find_symbol_by_index(struct elf *elf, unsigned int idx) struct symbol *find_symbol_by_offset(struct section *sec, unsigned long offset) { - struct rb_node *node; - - rb_for_each(node, &offset, &sec->symbol_tree, symbol_by_offset) { - struct symbol *s = rb_entry(node, struct symbol, node); + struct rb_root_cached *tree = (struct rb_root_cached *)&sec->symbol_tree; + struct symbol *sym; - if (s->offset == offset && s->type != STT_SECTION) - return s; + __sym_for_each(sym, tree, offset, offset) { + if (sym->offset == offset && !is_sec_sym(sym)) + return sym->alias; } return NULL; @@ -136,13 +184,12 @@ struct symbol *find_symbol_by_offset(struct section *sec, unsigned long offset) struct symbol *find_func_by_offset(struct section *sec, unsigned long offset) { - struct rb_node *node; + struct rb_root_cached *tree = (struct rb_root_cached *)&sec->symbol_tree; + struct symbol *func; - rb_for_each(node, &offset, &sec->symbol_tree, symbol_by_offset) { - struct symbol *s = rb_entry(node, struct symbol, node); - - if (s->offset == offset && s->type == STT_FUNC) - return s; + __sym_for_each(func, tree, offset, offset) { + if (func->offset == offset && is_func_sym(func)) + return func->alias; } return NULL; @@ -150,27 +197,80 @@ struct symbol *find_func_by_offset(struct section *sec, unsigned long offset) struct symbol *find_symbol_containing(const struct section *sec, unsigned long offset) { - struct rb_node *node; + struct rb_root_cached *tree = (struct rb_root_cached *)&sec->symbol_tree; + struct symbol *sym = NULL, *tmp; - rb_for_each(node, &offset, &sec->symbol_tree, symbol_by_offset) { - struct symbol *s = rb_entry(node, struct symbol, node); + __sym_for_each(tmp, tree, offset, offset) { + if (tmp->len) { + if (!sym) { + sym = tmp; + continue; + } - if (s->type != STT_SECTION) - return s; + if (sym->offset != tmp->offset || sym->len != tmp->len) { + /* + * In the rare case of overlapping symbols, + * pick the smaller one. + * + * TODO: outlaw overlapping symbols + */ + if (tmp->len < sym->len) + sym = tmp; + } + } } - return NULL; + return sym ? sym->alias : NULL; } -struct symbol *find_func_containing(struct section *sec, unsigned long offset) +/* + * Returns size of hole starting at @offset. + */ +int find_symbol_hole_containing(const struct section *sec, unsigned long offset) { - struct rb_node *node; + struct symbol_hole hole = { + .key = offset, + .sym = NULL, + }; + struct rb_node *n; + struct symbol *s; + + /* + * Find the rightmost symbol for which @offset is after it. + */ + n = rb_find(&hole, &sec->symbol_tree.rb_root, symbol_hole_by_offset); + + /* found a symbol that contains @offset */ + if (n) + return 0; /* not a hole */ - rb_for_each(node, &offset, &sec->symbol_tree, symbol_by_offset) { - struct symbol *s = rb_entry(node, struct symbol, node); + /* + * @offset >= sym->offset + sym->len, find symbol after it. + * When hole.sym is empty, use the first node to compute the hole. + * If there is no symbol in the section, the first node will be NULL, + * in which case, -1 is returned to skip the whole section. + */ + if (hole.sym) + n = rb_next(&hole.sym->node); + else + n = rb_first_cached(&sec->symbol_tree); + + if (!n) + return -1; /* until end of address space */ + + /* hole until start of next symbol */ + s = rb_entry(n, struct symbol, node); + return s->offset - offset; +} - if (s->type == STT_FUNC) - return s; +struct symbol *find_func_containing(struct section *sec, unsigned long offset) +{ + struct rb_root_cached *tree = (struct rb_root_cached *)&sec->symbol_tree; + struct symbol *func; + + __sym_for_each(func, tree, offset, offset) { + if (is_func_sym(func)) + return func->alias; } return NULL; @@ -188,25 +288,55 @@ struct symbol *find_symbol_by_name(const struct elf *elf, const char *name) return NULL; } +/* Find local symbol with matching STT_FILE */ +static struct symbol *find_local_symbol_by_file_and_name(const struct elf *elf, + struct symbol *file, + const char *name) +{ + struct symbol *sym; + + elf_hash_for_each_possible(symbol_name, sym, name_hash, str_hash(name)) { + if (sym->bind == STB_LOCAL && sym->file == file && + !strcmp(sym->name, name)) { + return sym; + } + } + + return NULL; +} + +struct symbol *find_global_symbol_by_name(const struct elf *elf, const char *name) +{ + struct symbol *sym; + + elf_hash_for_each_possible(symbol_name, sym, name_hash, str_hash(name)) { + if (!strcmp(sym->name, name) && !is_local_sym(sym)) + return sym; + } + + return NULL; +} + struct reloc *find_reloc_by_dest_range(const struct elf *elf, struct section *sec, unsigned long offset, unsigned int len) { struct reloc *reloc, *r = NULL; + struct section *rsec; unsigned long o; - if (!sec->reloc) + rsec = sec->rsec; + if (!rsec) return NULL; - sec = sec->reloc; - for_offset_range(o, offset, offset + len) { elf_hash_for_each_possible(reloc, reloc, hash, - sec_offset_hash(sec, o)) { - if (reloc->sec != sec) + sec_offset_hash(rsec, o)) { + if (reloc->sec != rsec) continue; - if (reloc->offset >= offset && reloc->offset < offset + len) { - if (!r || reloc->offset < r->offset) + if (reloc_offset(reloc) >= offset && + reloc_offset(reloc) < offset + len) { + if (!r || reloc_offset(reloc) < reloc_offset(r)) r = reloc; } } @@ -222,6 +352,11 @@ struct reloc *find_reloc_by_dest(const struct elf *elf, struct section *sec, uns return find_reloc_by_dest_range(elf, sec, offset, 1); } +static bool is_dwarf_section(struct section *sec) +{ + return !strncmp(sec->name, ".debug_", 7); +} + static int read_sections(struct elf *elf) { Elf_Scn *s = NULL; @@ -230,12 +365,12 @@ static int read_sections(struct elf *elf) int i; if (elf_getshdrnum(elf->elf, §ions_nr)) { - WARN_ELF("elf_getshdrnum"); + ERROR_ELF("elf_getshdrnum"); return -1; } if (elf_getshdrstrndx(elf->elf, &shstrndx)) { - WARN_ELF("elf_getshdrstrndx"); + ERROR_ELF("elf_getshdrstrndx"); return -1; } @@ -243,105 +378,170 @@ static int read_sections(struct elf *elf) !elf_alloc_hash(section_name, sections_nr)) return -1; + elf->section_data = calloc(sections_nr, sizeof(*sec)); + if (!elf->section_data) { + ERROR_GLIBC("calloc"); + return -1; + } for (i = 0; i < sections_nr; i++) { - sec = malloc(sizeof(*sec)); - if (!sec) { - perror("malloc"); - return -1; - } - memset(sec, 0, sizeof(*sec)); + sec = &elf->section_data[i]; INIT_LIST_HEAD(&sec->symbol_list); - INIT_LIST_HEAD(&sec->reloc_list); s = elf_getscn(elf->elf, i); if (!s) { - WARN_ELF("elf_getscn"); + ERROR_ELF("elf_getscn"); return -1; } sec->idx = elf_ndxscn(s); if (!gelf_getshdr(s, &sec->sh)) { - WARN_ELF("gelf_getshdr"); + ERROR_ELF("gelf_getshdr"); return -1; } sec->name = elf_strptr(elf->elf, shstrndx, sec->sh.sh_name); if (!sec->name) { - WARN_ELF("elf_strptr"); + ERROR_ELF("elf_strptr"); return -1; } - if (sec->sh.sh_size != 0) { + if (sec_size(sec) != 0 && !is_dwarf_section(sec)) { sec->data = elf_getdata(s, NULL); if (!sec->data) { - WARN_ELF("elf_getdata"); + ERROR_ELF("elf_getdata"); return -1; } if (sec->data->d_off != 0 || - sec->data->d_size != sec->sh.sh_size) { - WARN("unexpected data attributes for %s", - sec->name); + sec->data->d_size != sec_size(sec)) { + ERROR("unexpected data attributes for %s", sec->name); return -1; } } - if (sec->sh.sh_flags & SHF_EXECINSTR) - elf->text_size += sec->sh.sh_size; - list_add_tail(&sec->list, &elf->sections); elf_hash_add(section, &sec->hash, sec->idx); elf_hash_add(section_name, &sec->name_hash, str_hash(sec->name)); + + if (is_reloc_sec(sec)) + elf->num_relocs += sec_num_entries(sec); } - if (stats) { + if (opts.stats) { printf("nr_sections: %lu\n", (unsigned long)sections_nr); printf("section_bits: %d\n", elf->section_bits); } /* sanity check, one more call to elf_nextscn() should return NULL */ if (elf_nextscn(elf->elf, s)) { - WARN("section entry mismatch"); + ERROR("section entry mismatch"); return -1; } return 0; } -static void elf_add_symbol(struct elf *elf, struct symbol *sym) +static const char *demangle_name(struct symbol *sym) +{ + char *str; + + if (!is_local_sym(sym)) + return sym->name; + + if (!is_func_sym(sym) && !is_object_sym(sym)) + return sym->name; + + if (!strstarts(sym->name, "__UNIQUE_ID_") && !strchr(sym->name, '.')) + return sym->name; + + str = strdup(sym->name); + if (!str) { + ERROR_GLIBC("strdup"); + return NULL; + } + + for (int i = strlen(str) - 1; i >= 0; i--) { + char c = str[i]; + + if (!isdigit(c) && c != '.') { + str[i + 1] = '\0'; + break; + } + } + + return str; +} + +static int elf_add_symbol(struct elf *elf, struct symbol *sym) { struct list_head *entry; struct rb_node *pnode; + struct symbol *iter; + + INIT_LIST_HEAD(&sym->pv_target); + sym->alias = sym; sym->type = GELF_ST_TYPE(sym->sym.st_info); sym->bind = GELF_ST_BIND(sym->sym.st_info); + if (is_file_sym(sym)) + elf->num_files++; + sym->offset = sym->sym.st_value; sym->len = sym->sym.st_size; - rb_add(&sym->node, &sym->sec->symbol_tree, symbol_to_offset); + __sym_for_each(iter, &sym->sec->symbol_tree, sym->offset, sym->offset) { + if (!is_undef_sym(iter) && iter->offset == sym->offset && + iter->type == sym->type && iter->len == sym->len) + iter->alias = sym; + } + + __sym_insert(sym, &sym->sec->symbol_tree); pnode = rb_prev(&sym->node); if (pnode) entry = &rb_entry(pnode, struct symbol, node)->list; else entry = &sym->sec->symbol_list; list_add(&sym->list, entry); + + list_add_tail(&sym->global_list, &elf->symbols); elf_hash_add(symbol, &sym->hash, sym->idx); elf_hash_add(symbol_name, &sym->name_hash, str_hash(sym->name)); - /* - * Don't store empty STT_NOTYPE symbols in the rbtree. They - * can exist within a function, confusing the sorting. - */ - if (!sym->len) - rb_erase(&sym->node, &sym->sec->symbol_tree); + if (is_func_sym(sym) && + (strstarts(sym->name, "__pfx_") || + strstarts(sym->name, "__cfi_") || + strstarts(sym->name, "__pi___pfx_") || + strstarts(sym->name, "__pi___cfi_"))) + sym->prefix = 1; + + if (strstarts(sym->name, ".klp.sym")) + sym->klp = 1; + + if (!sym->klp && !is_sec_sym(sym) && strstr(sym->name, ".cold")) { + sym->cold = 1; + + /* + * Clang doesn't mark cold subfunctions as STT_FUNC, which + * breaks several objtool assumptions. Fake it. + */ + sym->type = STT_FUNC; + } + + sym->pfunc = sym->cfunc = sym; + + sym->demangled_name = demangle_name(sym); + if (!sym->demangled_name) + return -1; + + return 0; } static int read_symbols(struct elf *elf) { struct section *symtab, *symtab_shndx, *sec; - struct symbol *sym, *pfunc; + struct symbol *sym, *pfunc, *file = NULL; int symbols_nr, i; char *coldstr; Elf_Data *shndx_data = NULL; @@ -353,7 +553,7 @@ static int read_symbols(struct elf *elf) if (symtab_shndx) shndx_data = symtab_shndx->data; - symbols_nr = symtab->sh.sh_size / symtab->sh.sh_entsize; + symbols_nr = sec_num_entries(symtab); } else { /* * A missing symbol table is actually possible if it's an empty @@ -368,29 +568,30 @@ static int read_symbols(struct elf *elf) !elf_alloc_hash(symbol_name, symbols_nr)) return -1; + elf->symbol_data = calloc(symbols_nr, sizeof(*sym)); + if (!elf->symbol_data) { + ERROR_GLIBC("calloc"); + return -1; + } + + INIT_LIST_HEAD(&elf->symbols); + for (i = 0; i < symbols_nr; i++) { - sym = malloc(sizeof(*sym)); - if (!sym) { - perror("malloc"); - return -1; - } - memset(sym, 0, sizeof(*sym)); - INIT_LIST_HEAD(&sym->pv_target); - sym->alias = sym; + sym = &elf->symbol_data[i]; sym->idx = i; if (!gelf_getsymshndx(symtab->data, shndx_data, i, &sym->sym, &shndx)) { - WARN_ELF("gelf_getsymshndx"); - goto err; + ERROR_ELF("gelf_getsymshndx"); + return -1; } sym->name = elf_strptr(elf->elf, symtab->sh.sh_link, sym->sym.st_name); if (!sym->name) { - WARN_ELF("elf_strptr"); - goto err; + ERROR_ELF("elf_strptr"); + return -1; } if ((sym->sym.st_shndx > SHN_UNDEF && @@ -401,9 +602,8 @@ static int read_symbols(struct elf *elf) sym->sec = find_section_by_index(elf, shndx); if (!sym->sec) { - WARN("couldn't find section for symbol %s", - sym->name); - goto err; + ERROR("couldn't find section for symbol %s", sym->name); + return -1; } if (GELF_ST_TYPE(sym->sym.st_info) == STT_SECTION) { sym->name = sym->sec->name; @@ -412,51 +612,55 @@ static int read_symbols(struct elf *elf) } else sym->sec = find_section_by_index(elf, 0); - elf_add_symbol(elf, sym); + if (elf_add_symbol(elf, sym)) + return -1; + + if (sym->type == STT_FILE) + file = sym; + else if (sym->bind == STB_LOCAL) + sym->file = file; } - if (stats) { + if (opts.stats) { printf("nr_symbols: %lu\n", (unsigned long)symbols_nr); printf("symbol_bits: %d\n", elf->symbol_bits); } /* Create parent/child links for any cold subfunctions */ list_for_each_entry(sec, &elf->sections, list) { - list_for_each_entry(sym, &sec->symbol_list, list) { - char pname[MAX_NAME_LEN + 1]; + sec_for_each_sym(sec, sym) { + char *pname; size_t pnamelen; - if (sym->type != STT_FUNC) - continue; - - if (sym->pfunc == NULL) - sym->pfunc = sym; - if (sym->cfunc == NULL) - sym->cfunc = sym; + if (!sym->cold) + continue; coldstr = strstr(sym->name, ".cold"); - if (!coldstr) - continue; + if (!coldstr) { + ERROR("%s(): cold subfunction without \".cold\"?", sym->name); + return -1; + } pnamelen = coldstr - sym->name; - if (pnamelen > MAX_NAME_LEN) { - WARN("%s(): parent function name exceeds maximum length of %d characters", - sym->name, MAX_NAME_LEN); + pname = strndup(sym->name, pnamelen); + if (!pname) { + ERROR("%s(): failed to allocate memory", sym->name); return -1; } - strncpy(pname, sym->name, pnamelen); - pname[pnamelen] = '\0'; - pfunc = find_symbol_by_name(elf, pname); + pfunc = find_local_symbol_by_file_and_name(elf, sym->file, pname); + if (!pfunc) + pfunc = find_global_symbol_by_name(elf, pname); + free(pname); if (!pfunc) { - WARN("%s(): can't find parent function", - sym->name); + ERROR("%s(): can't find parent function", sym->name); return -1; } - sym->pfunc = pfunc; + sym->pfunc = pfunc->alias; pfunc->cfunc = sym; + pfunc->alias->cfunc = sym; /* * Unfortunately, -fnoreorder-functions puts the child @@ -475,174 +679,442 @@ static int read_symbols(struct elf *elf) } return 0; - -err: - free(sym); - return -1; } -static struct section *elf_create_reloc_section(struct elf *elf, - struct section *base, - int reltype); - -int elf_add_reloc(struct elf *elf, struct section *sec, unsigned long offset, - unsigned int type, struct symbol *sym, int addend) +static int mark_group_syms(struct elf *elf) { - struct reloc *reloc; + struct section *symtab, *sec; + struct symbol *sym; - if (!sec->reloc && !elf_create_reloc_section(elf, sec, SHT_RELA)) + symtab = find_section_by_name(elf, ".symtab"); + if (!symtab) { + ERROR("no .symtab"); return -1; + } - reloc = malloc(sizeof(*reloc)); - if (!reloc) { - perror("malloc"); - return -1; + for_each_sec(elf, sec) { + if (sec->sh.sh_type == SHT_GROUP && + sec->sh.sh_link == symtab->idx) { + sym = find_symbol_by_index(elf, sec->sh.sh_info); + if (!sym) { + ERROR("%s: can't find SHT_GROUP signature symbol", + sec->name); + return -1; + } + + sym->group_sec = sec; + } } - memset(reloc, 0, sizeof(*reloc)); - reloc->sec = sec->reloc; - reloc->offset = offset; - reloc->type = type; - reloc->sym = sym; - reloc->addend = addend; + return 0; +} - list_add_tail(&reloc->list, &sec->reloc->reloc_list); - elf_hash_add(reloc, &reloc->hash, reloc_hash(reloc)); +/* + * @sym's idx has changed. Update the relocs which reference it. + */ +static int elf_update_sym_relocs(struct elf *elf, struct symbol *sym) +{ + struct reloc *reloc; - sec->reloc->sh.sh_size += sec->reloc->sh.sh_entsize; - sec->reloc->changed = true; + for (reloc = sym->relocs; reloc; reloc = sym_next_reloc(reloc)) + set_reloc_sym(elf, reloc, reloc->sym->idx); return 0; } -int elf_add_reloc_to_insn(struct elf *elf, struct section *sec, - unsigned long offset, unsigned int type, - struct section *insn_sec, unsigned long insn_off) +/* + * The libelf API is terrible; gelf_update_sym*() takes a data block relative + * index value, *NOT* the symbol index. As such, iterate the data blocks and + * adjust index until it fits. + * + * If no data block is found, allow adding a new data block provided the index + * is only one past the end. + */ +static int elf_update_symbol(struct elf *elf, struct section *symtab, + struct section *symtab_shndx, struct symbol *sym) { - struct symbol *sym; - int addend; + Elf32_Word shndx; + Elf_Data *symtab_data = NULL, *shndx_data = NULL; + Elf64_Xword entsize = symtab->sh.sh_entsize; + int max_idx, idx = sym->idx; + Elf_Scn *s, *t = NULL; + bool is_special_shndx = sym->sym.st_shndx >= SHN_LORESERVE && + sym->sym.st_shndx != SHN_XINDEX; - if (insn_sec->sym) { - sym = insn_sec->sym; - addend = insn_off; + shndx = is_special_shndx ? sym->sym.st_shndx : sym->sec->idx; - } else { - /* - * The Clang assembler strips section symbols, so we have to - * reference the function symbol instead: - */ - sym = find_symbol_containing(insn_sec, insn_off); - if (!sym) { + s = elf_getscn(elf->elf, symtab->idx); + if (!s) { + ERROR_ELF("elf_getscn"); + return -1; + } + + if (symtab_shndx) { + t = elf_getscn(elf->elf, symtab_shndx->idx); + if (!t) { + ERROR_ELF("elf_getscn"); + return -1; + } + } + + for (;;) { + /* get next data descriptor for the relevant sections */ + symtab_data = elf_getdata(s, symtab_data); + if (t) + shndx_data = elf_getdata(t, shndx_data); + + /* end-of-list */ + if (!symtab_data) { /* - * Hack alert. This happens when we need to reference - * the NOP pad insn immediately after the function. + * Over-allocate to avoid O(n^2) symbol creation + * behaviour. The down side is that libelf doesn't + * like this; see elf_truncate_section() for the fixup. */ - sym = find_symbol_containing(insn_sec, insn_off - 1); + int num = max(1U, sym->idx/3); + void *buf; + + if (idx) { + /* we don't do holes in symbol tables */ + ERROR("index out of range"); + return -1; + } + + /* if @idx == 0, it's the next contiguous entry, create it */ + symtab_data = elf_newdata(s); + if (t) + shndx_data = elf_newdata(t); + + buf = calloc(num, entsize); + if (!buf) { + ERROR_GLIBC("calloc"); + return -1; + } + + symtab_data->d_buf = buf; + symtab_data->d_size = num * entsize; + symtab_data->d_align = 1; + symtab_data->d_type = ELF_T_SYM; + + mark_sec_changed(elf, symtab, true); + symtab->truncate = true; + + if (t) { + buf = calloc(num, sizeof(Elf32_Word)); + if (!buf) { + ERROR_GLIBC("calloc"); + return -1; + } + + shndx_data->d_buf = buf; + shndx_data->d_size = num * sizeof(Elf32_Word); + shndx_data->d_align = sizeof(Elf32_Word); + shndx_data->d_type = ELF_T_WORD; + + mark_sec_changed(elf, symtab_shndx, true); + symtab_shndx->truncate = true; + } + + break; } - if (!sym) { - WARN("can't find symbol containing %s+0x%lx", insn_sec->name, insn_off); + /* empty blocks should not happen */ + if (!symtab_data->d_size) { + ERROR("zero size data"); return -1; } - addend = insn_off - sym->offset; + /* is this the right block? */ + max_idx = symtab_data->d_size / entsize; + if (idx < max_idx) + break; + + /* adjust index and try again */ + idx -= max_idx; } - return elf_add_reloc(elf, sec, offset, type, sym, addend); -} + /* something went side-ways */ + if (idx < 0) { + ERROR("negative index"); + return -1; + } -static int read_rel_reloc(struct section *sec, int i, struct reloc *reloc, unsigned int *symndx) -{ - if (!gelf_getrel(sec->data, i, &reloc->rel)) { - WARN_ELF("gelf_getrel"); + /* setup extended section index magic and write the symbol */ + if (shndx < SHN_LORESERVE || is_special_shndx) { + sym->sym.st_shndx = shndx; + if (!shndx_data) + shndx = 0; + } else { + sym->sym.st_shndx = SHN_XINDEX; + if (!shndx_data) { + ERROR("no .symtab_shndx"); + return -1; + } + } + + if (!gelf_update_symshndx(symtab_data, shndx_data, idx, &sym->sym, shndx)) { + ERROR_ELF("gelf_update_symshndx"); return -1; } - reloc->type = GELF_R_TYPE(reloc->rel.r_info); - reloc->addend = 0; - reloc->offset = reloc->rel.r_offset; - *symndx = GELF_R_SYM(reloc->rel.r_info); + return 0; } -static int read_rela_reloc(struct section *sec, int i, struct reloc *reloc, unsigned int *symndx) +struct symbol *elf_create_symbol(struct elf *elf, const char *name, + struct section *sec, unsigned int bind, + unsigned int type, unsigned long offset, + size_t size) { - if (!gelf_getrela(sec->data, i, &reloc->rela)) { - WARN_ELF("gelf_getrela"); - return -1; + struct section *symtab, *symtab_shndx; + Elf32_Word first_non_local, new_idx; + struct symbol *old, *sym; + + sym = calloc(1, sizeof(*sym)); + if (!sym) { + ERROR_GLIBC("calloc"); + return NULL; } - reloc->type = GELF_R_TYPE(reloc->rela.r_info); - reloc->addend = reloc->rela.r_addend; - reloc->offset = reloc->rela.r_offset; - *symndx = GELF_R_SYM(reloc->rela.r_info); - return 0; + + sym->name = strdup(name); + if (!sym->name) { + ERROR_GLIBC("strdup"); + return NULL; + } + + if (type != STT_SECTION) { + sym->sym.st_name = elf_add_string(elf, NULL, sym->name); + if (sym->sym.st_name == -1) + return NULL; + } + + if (sec) { + sym->sec = sec; + } else { + sym->sec = find_section_by_index(elf, 0); + if (!sym->sec) { + ERROR("no NULL section"); + return NULL; + } + } + + sym->sym.st_info = GELF_ST_INFO(bind, type); + sym->sym.st_value = offset; + sym->sym.st_size = size; + + symtab = find_section_by_name(elf, ".symtab"); + if (!symtab) { + ERROR("no .symtab"); + return NULL; + } + + symtab_shndx = find_section_by_name(elf, ".symtab_shndx"); + + new_idx = sec_num_entries(symtab); + + if (bind != STB_LOCAL) + goto non_local; + + /* + * Move the first global symbol, as per sh_info, into a new, higher + * symbol index. This frees up a spot for a new local symbol. + */ + first_non_local = symtab->sh.sh_info; + old = find_symbol_by_index(elf, first_non_local); + if (old) { + + elf_hash_del(symbol, &old->hash, old->idx); + elf_hash_add(symbol, &old->hash, new_idx); + old->idx = new_idx; + + if (elf_update_symbol(elf, symtab, symtab_shndx, old)) { + ERROR("elf_update_symbol move"); + return NULL; + } + + if (elf_update_sym_relocs(elf, old)) + return NULL; + + if (old->group_sec) { + old->group_sec->sh.sh_info = new_idx; + mark_sec_changed(elf, old->group_sec, true); + } + + new_idx = first_non_local; + } + + /* + * Either way, we will add a LOCAL symbol. + */ + symtab->sh.sh_info += 1; + +non_local: + sym->idx = new_idx; + if (sym->idx && elf_update_symbol(elf, symtab, symtab_shndx, sym)) + return NULL; + + symtab->sh.sh_size += symtab->sh.sh_entsize; + mark_sec_changed(elf, symtab, true); + + if (symtab_shndx) { + symtab_shndx->sh.sh_size += sizeof(Elf32_Word); + mark_sec_changed(elf, symtab_shndx, true); + } + + if (elf_add_symbol(elf, sym)) + return NULL; + + return sym; +} + +struct symbol *elf_create_section_symbol(struct elf *elf, struct section *sec) +{ + struct symbol *sym = calloc(1, sizeof(*sym)); + + sym = elf_create_symbol(elf, sec->name, sec, STB_LOCAL, STT_SECTION, 0, 0); + if (!sym) + return NULL; + + sec->sym = sym; + + return sym; +} + +struct reloc *elf_init_reloc(struct elf *elf, struct section *rsec, + unsigned int reloc_idx, unsigned long offset, + struct symbol *sym, s64 addend, unsigned int type) +{ + struct reloc *reloc, empty = { 0 }; + + if (reloc_idx >= sec_num_entries(rsec)) { + ERROR("%s: bad reloc_idx %u for %s with %d relocs", + __func__, reloc_idx, rsec->name, sec_num_entries(rsec)); + return NULL; + } + + reloc = &rsec->relocs[reloc_idx]; + + if (memcmp(reloc, &empty, sizeof(empty))) { + ERROR("%s: %s: reloc %d already initialized!", + __func__, rsec->name, reloc_idx); + return NULL; + } + + reloc->sec = rsec; + reloc->sym = sym; + + set_reloc_offset(elf, reloc, offset); + set_reloc_sym(elf, reloc, sym->idx); + set_reloc_type(elf, reloc, type); + set_reloc_addend(elf, reloc, addend); + + elf_hash_add(reloc, &reloc->hash, reloc_hash(reloc)); + set_sym_next_reloc(reloc, sym->relocs); + sym->relocs = reloc; + + return reloc; +} + +struct reloc *elf_init_reloc_text_sym(struct elf *elf, struct section *sec, + unsigned long offset, + unsigned int reloc_idx, + struct section *insn_sec, + unsigned long insn_off) +{ + struct symbol *sym = insn_sec->sym; + s64 addend = insn_off; + + if (!is_text_sec(insn_sec)) { + ERROR("bad call to %s() for data symbol %s", __func__, sym->name); + return NULL; + } + + if (!sym) { + /* + * Due to how weak functions work, we must use section based + * relocations. Symbol based relocations would result in the + * weak and non-weak function annotations being overlaid on the + * non-weak function after linking. + */ + sym = elf_create_section_symbol(elf, insn_sec); + if (!sym) + return NULL; + } + + return elf_init_reloc(elf, sec->rsec, reloc_idx, offset, sym, addend, + elf_text_rela_type(elf)); +} + +struct reloc *elf_init_reloc_data_sym(struct elf *elf, struct section *sec, + unsigned long offset, + unsigned int reloc_idx, + struct symbol *sym, + s64 addend) +{ + if (is_text_sec(sec)) { + ERROR("bad call to %s() for text symbol %s", __func__, sym->name); + return NULL; + } + + return elf_init_reloc(elf, sec->rsec, reloc_idx, offset, sym, addend, + elf_data_rela_type(elf)); } static int read_relocs(struct elf *elf) { - struct section *sec; + unsigned long nr_reloc, max_reloc = 0; + struct section *rsec; struct reloc *reloc; - int i; unsigned int symndx; - unsigned long nr_reloc, max_reloc = 0, tot_reloc = 0; + struct symbol *sym; + int i; - if (!elf_alloc_hash(reloc, elf->text_size / 16)) + if (!elf_alloc_hash(reloc, elf->num_relocs)) return -1; - list_for_each_entry(sec, &elf->sections, list) { - if ((sec->sh.sh_type != SHT_RELA) && - (sec->sh.sh_type != SHT_REL)) + list_for_each_entry(rsec, &elf->sections, list) { + if (!is_reloc_sec(rsec)) continue; - sec->base = find_section_by_index(elf, sec->sh.sh_info); - if (!sec->base) { - WARN("can't find base section for reloc section %s", - sec->name); + rsec->base = find_section_by_index(elf, rsec->sh.sh_info); + if (!rsec->base) { + ERROR("can't find base section for reloc section %s", rsec->name); return -1; } - sec->base->reloc = sec; + rsec->base->rsec = rsec; + + /* nr_alloc_relocs=0: libelf owns d_buf */ + rsec->nr_alloc_relocs = 0; + + rsec->relocs = calloc(sec_num_entries(rsec), sizeof(*reloc)); + if (!rsec->relocs) { + ERROR_GLIBC("calloc"); + return -1; + } nr_reloc = 0; - for (i = 0; i < sec->sh.sh_size / sec->sh.sh_entsize; i++) { - reloc = malloc(sizeof(*reloc)); - if (!reloc) { - perror("malloc"); - return -1; - } - memset(reloc, 0, sizeof(*reloc)); - switch (sec->sh.sh_type) { - case SHT_REL: - if (read_rel_reloc(sec, i, reloc, &symndx)) - return -1; - break; - case SHT_RELA: - if (read_rela_reloc(sec, i, reloc, &symndx)) - return -1; - break; - default: return -1; - } + for (i = 0; i < sec_num_entries(rsec); i++) { + reloc = &rsec->relocs[i]; - reloc->sec = sec; - reloc->idx = i; - reloc->sym = find_symbol_by_index(elf, symndx); + reloc->sec = rsec; + symndx = reloc_sym(reloc); + reloc->sym = sym = find_symbol_by_index(elf, symndx); if (!reloc->sym) { - WARN("can't find reloc entry symbol %d for %s", - symndx, sec->name); + ERROR("can't find reloc entry symbol %d for %s", symndx, rsec->name); return -1; } - list_add_tail(&reloc->list, &sec->reloc_list); elf_hash_add(reloc, &reloc->hash, reloc_hash(reloc)); + set_sym_next_reloc(reloc, sym->relocs); + sym->relocs = reloc; nr_reloc++; } max_reloc = max(max_reloc, nr_reloc); - tot_reloc += nr_reloc; } - if (stats) { + if (opts.stats) { printf("max_reloc: %lu\n", max_reloc); - printf("tot_reloc: %lu\n", tot_reloc); + printf("num_relocs: %lu\n", elf->num_relocs); printf("reloc_bits: %d\n", elf->reloc_bits); } @@ -658,10 +1130,10 @@ struct elf *elf_open_read(const char *name, int flags) elf = malloc(sizeof(*elf)); if (!elf) { - perror("malloc"); + ERROR_GLIBC("malloc"); return NULL; } - memset(elf, 0, offsetof(struct elf, sections)); + memset(elf, 0, sizeof(*elf)); INIT_LIST_HEAD(&elf->sections); @@ -672,6 +1144,12 @@ struct elf *elf_open_read(const char *name, int flags) goto err; } + elf->name = strdup(name); + if (!elf->name) { + ERROR_GLIBC("strdup"); + return NULL; + } + if ((flags & O_ACCMODE) == O_RDONLY) cmd = ELF_C_READ_MMAP; else if ((flags & O_ACCMODE) == O_RDWR) @@ -681,12 +1159,12 @@ struct elf *elf_open_read(const char *name, int flags) elf->elf = elf_begin(elf->fd, cmd, NULL); if (!elf->elf) { - WARN_ELF("elf_begin"); + ERROR_ELF("elf_begin"); goto err; } if (!gelf_getehdr(elf->elf, &elf->ehdr)) { - WARN_ELF("gelf_getehdr"); + ERROR_ELF("gelf_getehdr"); goto err; } @@ -696,6 +1174,9 @@ struct elf *elf_open_read(const char *name, int flags) if (read_symbols(elf)) goto err; + if (mark_group_syms(elf)) + goto err; + if (read_relocs(elf)) goto err; @@ -706,265 +1187,489 @@ err: return NULL; } -static int elf_add_string(struct elf *elf, struct section *strtab, char *str) +struct elf *elf_create_file(GElf_Ehdr *ehdr, const char *name) { - Elf_Data *data; - Elf_Scn *s; - int len; + struct section *null, *symtab, *strtab, *shstrtab; + char *dir, *base, *tmp_name; + struct symbol *sym; + struct elf *elf; + + elf_version(EV_CURRENT); + + elf = calloc(1, sizeof(*elf)); + if (!elf) { + ERROR_GLIBC("calloc"); + return NULL; + } + + INIT_LIST_HEAD(&elf->sections); + + dir = strdup(name); + if (!dir) { + ERROR_GLIBC("strdup"); + return NULL; + } + + dir = dirname(dir); + + base = strdup(name); + if (!base) { + ERROR_GLIBC("strdup"); + return NULL; + } + + base = basename(base); + + tmp_name = malloc(256); + if (!tmp_name) { + ERROR_GLIBC("malloc"); + return NULL; + } + + snprintf(tmp_name, 256, "%s/%s.XXXXXX", dir, base); + + elf->fd = mkstemp(tmp_name); + if (elf->fd == -1) { + ERROR_GLIBC("can't create tmp file"); + exit(1); + } + + elf->tmp_name = tmp_name; + + elf->name = strdup(name); + if (!elf->name) { + ERROR_GLIBC("strdup"); + return NULL; + } + + elf->elf = elf_begin(elf->fd, ELF_C_WRITE, NULL); + if (!elf->elf) { + ERROR_ELF("elf_begin"); + return NULL; + } + + if (!gelf_newehdr(elf->elf, ELFCLASS64)) { + ERROR_ELF("gelf_newehdr"); + return NULL; + } + + memcpy(&elf->ehdr, ehdr, sizeof(elf->ehdr)); + + if (!gelf_update_ehdr(elf->elf, &elf->ehdr)) { + ERROR_ELF("gelf_update_ehdr"); + return NULL; + } + + INIT_LIST_HEAD(&elf->symbols); + + if (!elf_alloc_hash(section, 1000) || + !elf_alloc_hash(section_name, 1000) || + !elf_alloc_hash(symbol, 10000) || + !elf_alloc_hash(symbol_name, 10000) || + !elf_alloc_hash(reloc, 100000)) + return NULL; + + null = elf_create_section(elf, NULL, 0, 0, SHT_NULL, 0, 0); + shstrtab = elf_create_section(elf, NULL, 0, 0, SHT_STRTAB, 1, 0); + strtab = elf_create_section(elf, NULL, 0, 0, SHT_STRTAB, 1, 0); + + if (!null || !shstrtab || !strtab) + return NULL; + + null->name = ""; + shstrtab->name = ".shstrtab"; + strtab->name = ".strtab"; + + null->sh.sh_name = elf_add_string(elf, shstrtab, null->name); + shstrtab->sh.sh_name = elf_add_string(elf, shstrtab, shstrtab->name); + strtab->sh.sh_name = elf_add_string(elf, shstrtab, strtab->name); + + if (null->sh.sh_name == -1 || shstrtab->sh.sh_name == -1 || strtab->sh.sh_name == -1) + return NULL; + + elf_hash_add(section_name, &null->name_hash, str_hash(null->name)); + elf_hash_add(section_name, &strtab->name_hash, str_hash(strtab->name)); + elf_hash_add(section_name, &shstrtab->name_hash, str_hash(shstrtab->name)); + + if (elf_add_string(elf, strtab, "") == -1) + return NULL; + + symtab = elf_create_section(elf, ".symtab", 0x18, 0x18, SHT_SYMTAB, 0x8, 0); + if (!symtab) + return NULL; + + symtab->sh.sh_link = strtab->idx; + symtab->sh.sh_info = 1; + + elf->ehdr.e_shstrndx = shstrtab->idx; + if (!gelf_update_ehdr(elf->elf, &elf->ehdr)) { + ERROR_ELF("gelf_update_ehdr"); + return NULL; + } + + sym = calloc(1, sizeof(*sym)); + if (!sym) { + ERROR_GLIBC("calloc"); + return NULL; + } + + sym->name = ""; + sym->sec = null; + elf_add_symbol(elf, sym); + + return elf; +} + +unsigned int elf_add_string(struct elf *elf, struct section *strtab, const char *str) +{ + unsigned int offset; if (!strtab) strtab = find_section_by_name(elf, ".strtab"); if (!strtab) { - WARN("can't find .strtab section"); + ERROR("can't find .strtab section"); return -1; } - s = elf_getscn(elf->elf, strtab->idx); - if (!s) { - WARN_ELF("elf_getscn"); + if (!strtab->sh.sh_addralign) { + ERROR("'%s': invalid sh_addralign", strtab->name); return -1; } - data = elf_newdata(s); - if (!data) { - WARN_ELF("elf_newdata"); + offset = ALIGN_UP(strtab->sh.sh_size, strtab->sh.sh_addralign); + + if (!elf_add_data(elf, strtab, str, strlen(str) + 1)) return -1; + + return offset; +} + +void *elf_add_data(struct elf *elf, struct section *sec, const void *data, size_t size) +{ + unsigned long offset; + Elf_Scn *s; + + if (!sec->sh.sh_addralign) { + ERROR("'%s': invalid sh_addralign", sec->name); + return NULL; + } + + s = elf_getscn(elf->elf, sec->idx); + if (!s) { + ERROR_ELF("elf_getscn"); + return NULL; + } + + sec->data = elf_newdata(s); + if (!sec->data) { + ERROR_ELF("elf_newdata"); + return NULL; } - data->d_buf = str; - data->d_size = strlen(str) + 1; - data->d_align = 1; + sec->data->d_buf = calloc(1, size); + if (!sec->data->d_buf) { + ERROR_GLIBC("calloc"); + return NULL; + } + + if (data) + memcpy(sec->data->d_buf, data, size); + + sec->data->d_size = size; + sec->data->d_align = 1; - len = strtab->sh.sh_size; - strtab->sh.sh_size += data->d_size; - strtab->changed = true; + offset = ALIGN_UP(sec->sh.sh_size, sec->sh.sh_addralign); + sec->sh.sh_size = offset + size; - return len; + mark_sec_changed(elf, sec, true); + + return sec->data->d_buf; } struct section *elf_create_section(struct elf *elf, const char *name, - unsigned int sh_flags, size_t entsize, int nr) + size_t size, size_t entsize, + unsigned int type, unsigned int align, + unsigned int flags) { struct section *sec, *shstrtab; - size_t size = entsize * nr; Elf_Scn *s; - sec = malloc(sizeof(*sec)); + if (name && find_section_by_name(elf, name)) { + ERROR("section '%s' already exists", name); + return NULL; + } + + sec = calloc(1, sizeof(*sec)); if (!sec) { - perror("malloc"); + ERROR_GLIBC("calloc"); return NULL; } - memset(sec, 0, sizeof(*sec)); INIT_LIST_HEAD(&sec->symbol_list); - INIT_LIST_HEAD(&sec->reloc_list); + + /* don't actually create the section, just the data structures */ + if (type == SHT_NULL) + goto add; s = elf_newscn(elf->elf); if (!s) { - WARN_ELF("elf_newscn"); - return NULL; - } - - sec->name = strdup(name); - if (!sec->name) { - perror("strdup"); + ERROR_ELF("elf_newscn"); return NULL; } sec->idx = elf_ndxscn(s); - sec->changed = true; - sec->data = elf_newdata(s); - if (!sec->data) { - WARN_ELF("elf_newdata"); - return NULL; - } + if (size) { + sec->data = elf_newdata(s); + if (!sec->data) { + ERROR_ELF("elf_newdata"); + return NULL; + } - sec->data->d_size = size; - sec->data->d_align = 1; + sec->data->d_size = size; + sec->data->d_align = 1; - if (size) { - sec->data->d_buf = malloc(size); + sec->data->d_buf = calloc(1, size); if (!sec->data->d_buf) { - perror("malloc"); + ERROR_GLIBC("calloc"); return NULL; } - memset(sec->data->d_buf, 0, size); } if (!gelf_getshdr(s, &sec->sh)) { - WARN_ELF("gelf_getshdr"); + ERROR_ELF("gelf_getshdr"); return NULL; } sec->sh.sh_size = size; sec->sh.sh_entsize = entsize; - sec->sh.sh_type = SHT_PROGBITS; - sec->sh.sh_addralign = 1; - sec->sh.sh_flags = SHF_ALLOC | sh_flags; - - /* Add section name to .shstrtab (or .strtab for Clang) */ - shstrtab = find_section_by_name(elf, ".shstrtab"); - if (!shstrtab) - shstrtab = find_section_by_name(elf, ".strtab"); - if (!shstrtab) { - WARN("can't find .shstrtab or .strtab section"); - return NULL; + sec->sh.sh_type = type; + sec->sh.sh_addralign = align; + sec->sh.sh_flags = flags; + + if (name) { + sec->name = strdup(name); + if (!sec->name) { + ERROR("strdup"); + return NULL; + } + + /* Add section name to .shstrtab (or .strtab for Clang) */ + shstrtab = find_section_by_name(elf, ".shstrtab"); + if (!shstrtab) { + shstrtab = find_section_by_name(elf, ".strtab"); + if (!shstrtab) { + ERROR("can't find .shstrtab or .strtab"); + return NULL; + } + } + sec->sh.sh_name = elf_add_string(elf, shstrtab, sec->name); + if (sec->sh.sh_name == -1) + return NULL; + + elf_hash_add(section_name, &sec->name_hash, str_hash(sec->name)); } - sec->sh.sh_name = elf_add_string(elf, shstrtab, sec->name); - if (sec->sh.sh_name == -1) - return NULL; +add: list_add_tail(&sec->list, &elf->sections); elf_hash_add(section, &sec->hash, sec->idx); - elf_hash_add(section_name, &sec->name_hash, str_hash(sec->name)); - elf->changed = true; + mark_sec_changed(elf, sec, true); return sec; } -static struct section *elf_create_rel_reloc_section(struct elf *elf, struct section *base) +static int elf_alloc_reloc(struct elf *elf, struct section *rsec) { - char *relocname; - struct section *sec; + struct reloc *old_relocs, *old_relocs_end, *new_relocs; + unsigned int nr_relocs_old = sec_num_entries(rsec); + unsigned int nr_relocs_new = nr_relocs_old + 1; + unsigned long nr_alloc; + struct symbol *sym; - relocname = malloc(strlen(base->name) + strlen(".rel") + 1); - if (!relocname) { - perror("malloc"); - return NULL; + if (!rsec->data) { + rsec->data = elf_newdata(elf_getscn(elf->elf, rsec->idx)); + if (!rsec->data) { + ERROR_ELF("elf_newdata"); + return -1; + } + + rsec->data->d_align = 1; + rsec->data->d_type = ELF_T_RELA; + rsec->data->d_buf = NULL; } - strcpy(relocname, ".rel"); - strcat(relocname, base->name); - sec = elf_create_section(elf, relocname, 0, sizeof(GElf_Rel), 0); - free(relocname); - if (!sec) - return NULL; + rsec->data->d_size = nr_relocs_new * elf_rela_size(elf); + rsec->sh.sh_size = rsec->data->d_size; - base->reloc = sec; - sec->base = base; + nr_alloc = MAX(64, ALIGN_UP_POW2(nr_relocs_new)); + if (nr_alloc <= rsec->nr_alloc_relocs) + return 0; - sec->sh.sh_type = SHT_REL; - sec->sh.sh_addralign = 8; - sec->sh.sh_link = find_section_by_name(elf, ".symtab")->idx; - sec->sh.sh_info = base->idx; - sec->sh.sh_flags = SHF_INFO_LINK; + if (rsec->data->d_buf && !rsec->nr_alloc_relocs) { + void *orig_buf = rsec->data->d_buf; - return sec; -} + /* + * The original d_buf is owned by libelf so it can't be + * realloced. + */ + rsec->data->d_buf = malloc(nr_alloc * elf_rela_size(elf)); + if (!rsec->data->d_buf) { + ERROR_GLIBC("malloc"); + return -1; + } + memcpy(rsec->data->d_buf, orig_buf, + nr_relocs_old * elf_rela_size(elf)); + } else { + rsec->data->d_buf = realloc(rsec->data->d_buf, + nr_alloc * elf_rela_size(elf)); + if (!rsec->data->d_buf) { + ERROR_GLIBC("realloc"); + return -1; + } + } -static struct section *elf_create_rela_reloc_section(struct elf *elf, struct section *base) -{ - char *relocname; - struct section *sec; + rsec->nr_alloc_relocs = nr_alloc; - relocname = malloc(strlen(base->name) + strlen(".rela") + 1); - if (!relocname) { - perror("malloc"); - return NULL; + old_relocs = rsec->relocs; + new_relocs = calloc(nr_alloc, sizeof(struct reloc)); + if (!new_relocs) { + ERROR_GLIBC("calloc"); + return -1; } - strcpy(relocname, ".rela"); - strcat(relocname, base->name); - sec = elf_create_section(elf, relocname, 0, sizeof(GElf_Rela), 0); - free(relocname); - if (!sec) - return NULL; + if (!old_relocs) + goto done; - base->reloc = sec; - sec->base = base; + /* + * The struct reloc's address has changed. Update all the symbols and + * relocs which reference it. + */ - sec->sh.sh_type = SHT_RELA; - sec->sh.sh_addralign = 8; - sec->sh.sh_link = find_section_by_name(elf, ".symtab")->idx; - sec->sh.sh_info = base->idx; - sec->sh.sh_flags = SHF_INFO_LINK; + old_relocs_end = &old_relocs[nr_relocs_old]; + for_each_sym(elf, sym) { + struct reloc *reloc; - return sec; -} + reloc = sym->relocs; + if (!reloc) + continue; -static struct section *elf_create_reloc_section(struct elf *elf, - struct section *base, - int reltype) -{ - switch (reltype) { - case SHT_REL: return elf_create_rel_reloc_section(elf, base); - case SHT_RELA: return elf_create_rela_reloc_section(elf, base); - default: return NULL; - } -} + if (reloc >= old_relocs && reloc < old_relocs_end) + sym->relocs = &new_relocs[reloc - old_relocs]; -static int elf_rebuild_rel_reloc_section(struct section *sec) -{ - struct reloc *reloc; - int idx = 0; - void *buf; + while (1) { + struct reloc *next_reloc = sym_next_reloc(reloc); - /* Allocate a buffer for relocations */ - buf = malloc(sec->sh.sh_size); - if (!buf) { - perror("malloc"); - return -1; - } + if (!next_reloc) + break; - sec->data->d_buf = buf; - sec->data->d_size = sec->sh.sh_size; - sec->data->d_type = ELF_T_REL; + if (next_reloc >= old_relocs && next_reloc < old_relocs_end) + set_sym_next_reloc(reloc, &new_relocs[next_reloc - old_relocs]); - idx = 0; - list_for_each_entry(reloc, &sec->reloc_list, list) { - reloc->rel.r_offset = reloc->offset; - reloc->rel.r_info = GELF_R_INFO(reloc->sym->idx, reloc->type); - if (!gelf_update_rel(sec->data, idx, &reloc->rel)) { - WARN_ELF("gelf_update_rel"); - return -1; + reloc = next_reloc; } - idx++; } + memcpy(new_relocs, old_relocs, nr_relocs_old * sizeof(struct reloc)); + + for (int i = 0; i < nr_relocs_old; i++) { + struct reloc *old = &old_relocs[i]; + struct reloc *new = &new_relocs[i]; + u32 key = reloc_hash(old); + + elf_hash_del(reloc, &old->hash, key); + elf_hash_add(reloc, &new->hash, key); + } + + free(old_relocs); +done: + rsec->relocs = new_relocs; return 0; } -static int elf_rebuild_rela_reloc_section(struct section *sec) +struct section *elf_create_rela_section(struct elf *elf, struct section *sec, + unsigned int nr_relocs) { - struct reloc *reloc; - int idx = 0; - void *buf; + struct section *rsec; + char *rsec_name; - /* Allocate a buffer for relocations with addends */ - buf = malloc(sec->sh.sh_size); - if (!buf) { - perror("malloc"); - return -1; + rsec_name = malloc(strlen(sec->name) + strlen(".rela") + 1); + if (!rsec_name) { + ERROR_GLIBC("malloc"); + return NULL; } + strcpy(rsec_name, ".rela"); + strcat(rsec_name, sec->name); + + rsec = elf_create_section(elf, rsec_name, nr_relocs * elf_rela_size(elf), + elf_rela_size(elf), SHT_RELA, elf_addr_size(elf), + SHF_INFO_LINK); + free(rsec_name); + if (!rsec) + return NULL; - sec->data->d_buf = buf; - sec->data->d_size = sec->sh.sh_size; - sec->data->d_type = ELF_T_RELA; + if (nr_relocs) { + rsec->data->d_type = ELF_T_RELA; - idx = 0; - list_for_each_entry(reloc, &sec->reloc_list, list) { - reloc->rela.r_offset = reloc->offset; - reloc->rela.r_addend = reloc->addend; - reloc->rela.r_info = GELF_R_INFO(reloc->sym->idx, reloc->type); - if (!gelf_update_rela(sec->data, idx, &reloc->rela)) { - WARN_ELF("gelf_update_rela"); - return -1; + rsec->nr_alloc_relocs = nr_relocs; + rsec->relocs = calloc(nr_relocs, sizeof(struct reloc)); + if (!rsec->relocs) { + ERROR_GLIBC("calloc"); + return NULL; } - idx++; } - return 0; + rsec->sh.sh_link = find_section_by_name(elf, ".symtab")->idx; + rsec->sh.sh_info = sec->idx; + + sec->rsec = rsec; + rsec->base = sec; + + return rsec; } -static int elf_rebuild_reloc_section(struct elf *elf, struct section *sec) +struct reloc *elf_create_reloc(struct elf *elf, struct section *sec, + unsigned long offset, + struct symbol *sym, s64 addend, + unsigned int type) { - switch (sec->sh.sh_type) { - case SHT_REL: return elf_rebuild_rel_reloc_section(sec); - case SHT_RELA: return elf_rebuild_rela_reloc_section(sec); - default: return -1; + struct section *rsec = sec->rsec; + + if (!rsec) { + rsec = elf_create_rela_section(elf, sec, 0); + if (!rsec) + return NULL; } + + if (find_reloc_by_dest(elf, sec, offset)) { + ERROR_FUNC(sec, offset, "duplicate reloc"); + return NULL; + } + + if (elf_alloc_reloc(elf, rsec)) + return NULL; + + mark_sec_changed(elf, rsec, true); + + return elf_init_reloc(elf, rsec, sec_num_entries(rsec) - 1, offset, sym, + addend, type); +} + +struct section *elf_create_section_pair(struct elf *elf, const char *name, + size_t entsize, unsigned int nr, + unsigned int nr_relocs) +{ + struct section *sec; + + sec = elf_create_section(elf, name, nr * entsize, entsize, + SHT_PROGBITS, 1, SHF_ALLOC); + if (!sec) + return NULL; + + if (!elf_create_rela_section(elf, sec, nr_relocs)) + return NULL; + + return sec; } int elf_write_insn(struct elf *elf, struct section *sec, @@ -974,44 +1679,68 @@ int elf_write_insn(struct elf *elf, struct section *sec, Elf_Data *data = sec->data; if (data->d_type != ELF_T_BYTE || data->d_off) { - WARN("write to unexpected data for section: %s", sec->name); + ERROR("write to unexpected data for section: %s", sec->name); return -1; } memcpy(data->d_buf + offset, insn, len); - elf_flagdata(data, ELF_C_SET, ELF_F_DIRTY); - elf->changed = true; + mark_sec_changed(elf, sec, true); return 0; } -int elf_write_reloc(struct elf *elf, struct reloc *reloc) +/* + * When Elf_Scn::sh_size is smaller than the combined Elf_Data::d_size + * do you: + * + * A) adhere to the section header and truncate the data, or + * B) ignore the section header and write out all the data you've got? + * + * Yes, libelf sucks and we need to manually truncate if we over-allocate data. + */ +static int elf_truncate_section(struct elf *elf, struct section *sec) { - struct section *sec = reloc->sec; + u64 size = sec_size(sec); + bool truncated = false; + Elf_Data *data = NULL; + Elf_Scn *s; + + s = elf_getscn(elf->elf, sec->idx); + if (!s) { + ERROR_ELF("elf_getscn"); + return -1; + } - if (sec->sh.sh_type == SHT_REL) { - reloc->rel.r_info = GELF_R_INFO(reloc->sym->idx, reloc->type); - reloc->rel.r_offset = reloc->offset; + for (;;) { + /* get next data descriptor for the relevant section */ + data = elf_getdata(s, data); + if (!data) { + if (size) { + ERROR("end of section data but non-zero size left\n"); + return -1; + } + return 0; + } - if (!gelf_update_rel(sec->data, reloc->idx, &reloc->rel)) { - WARN_ELF("gelf_update_rel"); + if (truncated) { + /* when we remove symbols */ + ERROR("truncated; but more data\n"); return -1; } - } else { - reloc->rela.r_info = GELF_R_INFO(reloc->sym->idx, reloc->type); - reloc->rela.r_addend = reloc->addend; - reloc->rela.r_offset = reloc->offset; - if (!gelf_update_rela(sec->data, reloc->idx, &reloc->rela)) { - WARN_ELF("gelf_update_rela"); + if (!data->d_size) { + ERROR("zero size data"); return -1; } - } - elf->changed = true; + if (data->d_size > size) { + truncated = true; + data->d_size = size; + } - return 0; + size -= data->d_size; + } } int elf_write(struct elf *elf) @@ -1021,25 +1750,23 @@ int elf_write(struct elf *elf) /* Update changed relocation sections and section headers: */ list_for_each_entry(sec, &elf->sections, list) { - if (sec->changed) { + if (sec->truncate && elf_truncate_section(elf, sec)) + return -1; + + if (sec_changed(sec)) { s = elf_getscn(elf->elf, sec->idx); if (!s) { - WARN_ELF("elf_getscn"); - return -1; - } - if (!gelf_update_shdr(s, &sec->sh)) { - WARN_ELF("gelf_update_shdr"); + ERROR_ELF("elf_getscn"); return -1; } - if (sec->base && - elf_rebuild_reloc_section(elf, sec)) { - WARN("elf_rebuild_reloc_section"); + /* Note this also flags the section dirty */ + if (!gelf_update_shdr(s, &sec->sh)) { + ERROR_ELF("gelf_update_shdr"); return -1; } - sec->changed = false; - elf->changed = true; + mark_sec_changed(elf, sec, false); } } @@ -1048,7 +1775,7 @@ int elf_write(struct elf *elf) /* Write all changes to the file. */ if (elf_update(elf->elf, ELF_C_WRITE) < 0) { - WARN_ELF("elf_update"); + ERROR_ELF("elf_update"); return -1; } @@ -1057,32 +1784,20 @@ int elf_write(struct elf *elf) return 0; } -void elf_close(struct elf *elf) +int elf_close(struct elf *elf) { - struct section *sec, *tmpsec; - struct symbol *sym, *tmpsym; - struct reloc *reloc, *tmpreloc; - if (elf->elf) elf_end(elf->elf); if (elf->fd > 0) close(elf->fd); - list_for_each_entry_safe(sec, tmpsec, &elf->sections, list) { - list_for_each_entry_safe(sym, tmpsym, &sec->symbol_list, list) { - list_del(&sym->list); - hash_del(&sym->hash); - free(sym); - } - list_for_each_entry_safe(reloc, tmpreloc, &sec->reloc_list, list) { - list_del(&reloc->list); - hash_del(&reloc->hash); - free(reloc); - } - list_del(&sec->list); - free(sec); - } + if (elf->tmp_name && rename(elf->tmp_name, elf->name)) + return -1; - free(elf); + /* + * NOTE: All remaining allocations are leaked on purpose. Objtool is + * about to exit anyway. + */ + return 0; } |
