Expose more functionality in the ELF library. This will be needed for the upcoming "objtool klp" support. Signed-off-by: Josh Poimboeuf <jpoimboe@xxxxxxxxxx> --- tools/objtool/elf.c | 338 +++++++++++++++++++--------- tools/objtool/include/objtool/elf.h | 30 ++- tools/objtool/orc_gen.c | 6 +- 3 files changed, 260 insertions(+), 114 deletions(-) diff --git a/tools/objtool/elf.c b/tools/objtool/elf.c index 84cb6fc235c9..0c95d7cdf0f5 100644 --- a/tools/objtool/elf.c +++ b/tools/objtool/elf.c @@ -18,10 +18,11 @@ #include <errno.h> #include <linux/interval_tree_generic.h> #include <objtool/builtin.h> - #include <objtool/elf.h> #include <objtool/warn.h> +#define ALIGN_UP(x, align_to) (((x) + ((align_to)-1)) & ~((align_to)-1)) + static inline u32 str_hash(const char *str) { return jhash(str, strlen(str), 0); @@ -261,6 +262,18 @@ struct symbol *find_symbol_by_name(const struct elf *elf, const char *name) 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_symbol(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) { @@ -549,7 +562,7 @@ static void elf_update_sym_relocs(struct elf *elf, struct symbol *sym) static void elf_update_symbol(struct elf *elf, struct section *symtab, struct section *symtab_shndx, struct symbol *sym) { - Elf32_Word shndx = sym->sec ? sym->sec->idx : SHN_UNDEF; + Elf32_Word shndx; Elf_Data *symtab_data = NULL, *shndx_data = NULL; Elf64_Xword entsize = symtab->sh.sh_entsize; int max_idx, idx = sym->idx; @@ -557,8 +570,7 @@ static void elf_update_symbol(struct elf *elf, struct section *symtab, bool is_special_shndx = sym->sym.st_shndx >= SHN_LORESERVE && sym->sym.st_shndx != SHN_XINDEX; - if (is_special_shndx) - shndx = sym->sym.st_shndx; + shndx = is_special_shndx ? sym->sym.st_shndx : sym->sec->idx; s = elf_getscn(elf->elf, symtab->idx); if (!s) @@ -654,12 +666,29 @@ static void elf_update_symbol(struct elf *elf, struct section *symtab, ERROR_ELF("gelf_update_symshndx"); } -static struct symbol * -__elf_create_symbol(struct elf *elf, struct symbol *sym) +static 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) { struct section *symtab, *symtab_shndx; Elf32_Word first_non_local, new_idx; - struct symbol *old; + struct symbol *old, *sym; + + sym = calloc(1, sizeof(*sym)); + ERROR_ON(!sym, "calloc"); + + if (name) { + sym->name = strdup(name); + if (type != STT_SECTION) + sym->sym.st_name = elf_add_string(elf, NULL, sym->name); + } + + sym->sec = sec ? : find_section_by_index(elf, 0); + + 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) @@ -669,7 +698,7 @@ __elf_create_symbol(struct elf *elf, struct symbol *sym) new_idx = sec_num_entries(symtab); - if (GELF_ST_BIND(sym->sym.st_info) != STB_LOCAL) + if (bind != STB_LOCAL) goto non_local; /* @@ -698,7 +727,8 @@ __elf_create_symbol(struct elf *elf, struct symbol *sym) non_local: sym->idx = new_idx; - elf_update_symbol(elf, symtab, symtab_shndx, sym); + if (sym->idx) + elf_update_symbol(elf, symtab, symtab_shndx, sym); symtab->sh.sh_size += symtab->sh.sh_entsize; mark_sec_changed(elf, symtab, true); @@ -708,63 +738,49 @@ __elf_create_symbol(struct elf *elf, struct symbol *sym) mark_sec_changed(elf, symtab_shndx, true); } + elf_add_symbol(elf, sym); + return sym; } -static struct symbol * -elf_create_section_symbol(struct elf *elf, struct section *sec) +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) +{ + return __elf_create_symbol(elf, name, sec, bind, type, offset, size); +} + +struct symbol *elf_create_section_symbol(struct elf *elf, struct section *sec) { struct symbol *sym; - sym = calloc(1, sizeof(*sym)); - ERROR_ON(!sym, "calloc"); - - sym->name = sec->name; - sym->sec = sec; - - // st_name 0 - sym->sym.st_info = GELF_ST_INFO(STB_LOCAL, STT_SECTION); - // st_other 0 - // st_value 0 - // st_size 0 - - sym = __elf_create_symbol(elf, sym); - elf_add_symbol(elf, sym); + sym = elf_create_symbol(elf, sec->name, sec, STB_LOCAL, STT_SECTION, 0, 0); + sec->sym = sym; return sym; } -static int elf_add_string(struct elf *elf, struct section *strtab, const char *str); - struct symbol * -elf_create_prefix_symbol(struct elf *elf, struct symbol *orig, long size) +elf_create_prefix_symbol(struct elf *elf, struct symbol *orig, size_t size) { - struct symbol *sym = calloc(1, sizeof(*sym)); size_t namelen = strlen(orig->name) + sizeof("__pfx_"); - char *name = malloc(namelen); - - ERROR_ON(!sym || !name, "malloc"); + char name[SYM_NAME_LEN]; + unsigned long offset; snprintf(name, namelen, "__pfx_%s", orig->name); - sym->name = name; - sym->sec = orig->sec; + offset = orig->sym.st_value - size; - sym->sym.st_name = elf_add_string(elf, NULL, name); - sym->sym.st_info = orig->sym.st_info; - sym->sym.st_value = orig->sym.st_value - size; - sym->sym.st_size = size; - - sym = __elf_create_symbol(elf, sym); - elf_add_symbol(elf, sym); - - return sym; + return elf_create_symbol(elf, name, orig->sec, + GELF_ST_BIND(orig->sym.st_info), + GELF_ST_TYPE(orig->sym.st_info), + offset, size); } -static 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 *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 }; @@ -800,7 +816,7 @@ struct reloc *elf_init_reloc_text_sym(struct elf *elf, struct section *sec, unsigned long insn_off) { struct symbol *sym = insn_sec->sym; - int addend = insn_off; + s64 addend = insn_off; if (!is_text_section(insn_sec)) ERROR("bad call to %s() for data symbol %s", __func__, sym->name); @@ -813,8 +829,6 @@ struct reloc *elf_init_reloc_text_sym(struct elf *elf, struct section *sec, * non-weak function after linking. */ sym = elf_create_section_symbol(elf, insn_sec); - - insn_sec->sym = sym; } return elf_init_reloc(elf, sec->rsec, reloc_idx, offset, sym, addend, @@ -926,11 +940,9 @@ struct elf *elf_open_read(const char *name, int flags) return elf; } -static int elf_add_string(struct elf *elf, struct section *strtab, const char *str) +unsigned long elf_add_string(struct elf *elf, struct section *strtab, const char *str) { - Elf_Data *data; - Elf_Scn *s; - int len; + unsigned long offset; if (!strtab) { strtab = find_section_by_name(elf, ".strtab"); @@ -938,56 +950,77 @@ static int elf_add_string(struct elf *elf, struct section *strtab, const char *s ERROR("can't find .strtab section"); } - s = elf_getscn(elf->elf, strtab->idx); - if (!s) - ERROR_ELF("elf_getscn"); + offset = ALIGN_UP(strtab->sh.sh_size, strtab->sh.sh_addralign); - data = elf_newdata(s); - if (!data) - ERROR_ELF("elf_newdata"); + elf_add_data(elf, strtab, str, strlen(str) + 1); - data->d_buf = strdup(str); - data->d_size = strlen(str) + 1; - data->d_align = 1; - - len = strtab->sh.sh_size; - strtab->sh.sh_size += data->d_size; - - mark_sec_changed(elf, strtab, true); - - return len; + return offset; } -struct section *elf_create_section(struct elf *elf, const char *name, - size_t entsize, unsigned int nr) +void *elf_add_data(struct elf *elf, struct section *sec, const void *data, size_t size) { - struct section *sec, *shstrtab; - size_t size = entsize * nr; + unsigned long offset; Elf_Scn *s; - sec = malloc(sizeof(*sec)); - ERROR_ON(!sec, "malloc"); - memset(sec, 0, sizeof(*sec)); - - INIT_LIST_HEAD(&sec->symbol_list); - - s = elf_newscn(elf->elf); + s = elf_getscn(elf->elf, sec->idx); if (!s) - ERROR_ELF("elf_newscn"); - - sec->name = strdup(name); - ERROR_ON(!sec->name, "strdup"); - - sec->idx = elf_ndxscn(s); + ERROR_ELF("elf_getscn"); sec->data = elf_newdata(s); if (!sec->data) ERROR_ELF("elf_newdata"); + sec->data->d_buf = calloc(1, size); + ERROR_ON(!sec->data->d_buf, "calloc"); + + if (data) + memcpy(sec->data->d_buf, data, size); + sec->data->d_size = size; - sec->data->d_align = 1; + sec->data->d_align = sec->sh.sh_addralign; + + offset = ALIGN_UP(sec->sh.sh_size, sec->sh.sh_addralign); + sec->sh.sh_size = offset + size; + + mark_sec_changed(elf, sec, true); + + return sec->data->d_buf; +} + +struct section *elf_create_section(struct elf *elf, const char *name, + size_t size, size_t entsize, + unsigned int type, unsigned int align, + unsigned int flags) +{ + struct section *sec, *shstrtab; + Elf_Scn *s; + + if (name && find_section_by_name(elf, name)) + ERROR("section '%s' already exists", name); + + sec = calloc(1, sizeof(*sec)); + ERROR_ON(!sec, "calloc"); + + INIT_LIST_HEAD(&sec->symbol_list); + + /* don't actually create the section, just the data structures */ + if (type == SHT_NULL) + goto add; + + s = elf_newscn(elf->elf); + if (!s) + ERROR_ELF("elf_newscn"); + + sec->idx = elf_ndxscn(s); if (size) { + sec->data = elf_newdata(s); + if (!sec->data) + ERROR_ELF("elf_newdata"); + + sec->data->d_size = size; + sec->data->d_align = 1; + sec->data->d_buf = calloc(1, size); ERROR_ON(!sec->data->d_buf, "calloc"); } @@ -997,31 +1030,37 @@ struct section *elf_create_section(struct elf *elf, const char *name, 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; + sec->sh.sh_type = type; + sec->sh.sh_addralign = align; + sec->sh.sh_flags = 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) - ERROR("can't find .shstrtab or .strtab section"); + if (name) { + sec->name = strdup(name); + ERROR_ON(!sec->name, "strdup"); + + /* 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 section"); + } + sec->sh.sh_name = elf_add_string(elf, shstrtab, sec->name); + + elf_hash_add(section_name, &sec->name_hash, str_hash(sec->name)); } - sec->sh.sh_name = elf_add_string(elf, shstrtab, sec->name); +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)); mark_sec_changed(elf, sec, true); return sec; } -static struct section *elf_create_rela_section(struct elf *elf, - struct section *sec, - unsigned int reloc_nr) +struct section *elf_create_rela_section(struct elf *elf, struct section *sec, + unsigned int reloc_nr) { struct section *rsec; char *rsec_name; @@ -1032,33 +1071,110 @@ static struct section *elf_create_rela_section(struct elf *elf, strcpy(rsec_name, ".rela"); strcat(rsec_name, sec->name); - rsec = elf_create_section(elf, rsec_name, elf_rela_size(elf), reloc_nr); - free(rsec_name); + rsec = elf_create_section(elf, rsec_name, reloc_nr * elf_rela_size(elf), + elf_rela_size(elf), SHT_RELA, elf_addr_size(elf), + SHF_INFO_LINK); - rsec->data->d_type = ELF_T_RELA; - rsec->sh.sh_type = SHT_RELA; - rsec->sh.sh_addralign = elf_addr_size(elf); rsec->sh.sh_link = find_section_by_name(elf, ".symtab")->idx; rsec->sh.sh_info = sec->idx; - rsec->sh.sh_flags = SHF_INFO_LINK; - rsec->relocs = calloc(sec_num_entries(rsec), sizeof(struct reloc)); - ERROR_ON(!rsec->relocs, "calloc"); + if (reloc_nr) { + rsec->data->d_type = ELF_T_RELA; + rsec->relocs = calloc(sec_num_entries(rsec), sizeof(struct reloc)); + ERROR_ON(!rsec->relocs, "calloc"); + } sec->rsec = rsec; + free(rsec_name); + rsec->base = sec; return rsec; } +// TODO: preallocate sec->relocs so this doesn't happen often +// TODO: can avoid for bundled sections +static void elf_alloc_reloc(struct elf *elf, struct section *rsec) +{ + unsigned int nr_relocs = sec_num_entries(rsec); + struct reloc *old_relocs, *new_relocs; + struct symbol *sym; + + old_relocs = rsec->relocs; + new_relocs = calloc(1, (nr_relocs + 1) * sizeof(struct reloc)); + ERROR_ON(!new_relocs, "calloc"); + + if (!old_relocs) + goto done; + + // update syms and relocs which reference the reloc + for_each_sym(elf, sym) { + struct reloc **reloc; + + for (reloc = &sym->relocs; *reloc; ) { + struct reloc **next = &((*reloc)->sym_next_reloc); + if (*reloc >= old_relocs && *reloc < &old_relocs[nr_relocs]) { + *reloc = &new_relocs[*reloc - old_relocs]; + } + reloc = next; + } + } + + memcpy(new_relocs, old_relocs, (nr_relocs * sizeof(struct reloc))); + + for (int i = 0; i < nr_relocs; 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; +} + +struct reloc *elf_create_reloc(struct elf *elf, struct section *sec, + unsigned long offset, + struct symbol *sym, s64 addend, + unsigned int type) +{ + struct section *rsec = sec->rsec; + + if (!rsec) + rsec = elf_create_rela_section(elf, sec, 0); + + if (find_reloc_by_dest(elf, sec, offset)) + ERROR_FUNC(sec, offset, "duplicate reloc"); + + if (!rsec->data) { + rsec->data = elf_newdata(elf_getscn(elf->elf, rsec->idx)); + rsec->data->d_align = 1; + rsec->data->d_type = ELF_T_RELA; + } + + elf_alloc_reloc(elf, rsec); + + rsec->sh.sh_size += elf_rela_size(elf); + rsec->data->d_size = rsec->sh.sh_size; + rsec->data->d_buf = realloc(rsec->data->d_buf, rsec->sh.sh_size); + 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 reloc_nr) { struct section *sec; - sec = elf_create_section(elf, name, entsize, nr); + sec = elf_create_section(elf, name, nr * entsize, entsize, + SHT_PROGBITS, 1, SHF_ALLOC); + elf_create_rela_section(elf, sec, reloc_nr); + return sec; } diff --git a/tools/objtool/include/objtool/elf.h b/tools/objtool/include/objtool/elf.h index 8585b9802e1b..e91bbe7f07bf 100644 --- a/tools/objtool/include/objtool/elf.h +++ b/tools/objtool/include/objtool/elf.h @@ -15,6 +15,8 @@ #include <linux/jhash.h> #include <arch/elf.h> +#define SYM_NAME_LEN 512 + #ifdef LIBELF_USE_DEPRECATED # define elf_getshdrnum elf_getshnum # define elf_getshdrstrndx elf_getshstrndx @@ -109,12 +111,33 @@ struct elf { struct elf *elf_open_read(const char *name, int flags); struct section *elf_create_section(struct elf *elf, const char *name, - size_t entsize, unsigned int nr); + size_t size, size_t entsize, + unsigned int type, unsigned int align, + unsigned int flags); struct section *elf_create_section_pair(struct elf *elf, const char *name, size_t entsize, unsigned int nr, unsigned int reloc_nr); -struct symbol *elf_create_prefix_symbol(struct elf *elf, struct symbol *orig, long size); +struct section *elf_create_rela_section(struct elf *elf, struct section *sec, + unsigned int reloc_nr); + +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); +struct symbol *elf_create_section_symbol(struct elf *elf, struct section *sec); +struct symbol *elf_create_prefix_symbol(struct elf *elf, struct symbol *orig, + size_t size); + +struct reloc *elf_create_reloc(struct elf *elf, struct section *sec, + unsigned long offset, struct symbol *sym, + s64 addend, unsigned int type); +void *elf_add_data(struct elf *elf, struct section *sec, const void *data, + size_t size); + +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 *elf_init_reloc_text_sym(struct elf *elf, struct section *sec, unsigned long offset, @@ -128,6 +151,8 @@ struct reloc *elf_init_reloc_data_sym(struct elf *elf, struct section *sec, struct symbol *sym, s64 addend); +unsigned long elf_add_string(struct elf *elf, struct section *strtab, const char *str); + void elf_write_insn(struct elf *elf, struct section *sec, unsigned long offset, unsigned int len, const char *insn); @@ -138,6 +163,7 @@ struct section *find_section_by_name(const struct elf *elf, const char *name); struct symbol *find_func_by_offset(struct section *sec, unsigned long offset); struct symbol *find_symbol_by_offset(struct section *sec, unsigned long offset); struct symbol *find_symbol_by_name(const struct elf *elf, const char *name); +struct symbol *find_global_symbol_by_name(const struct elf *elf, const char *name); struct symbol *find_symbol_containing(const struct section *sec, unsigned long offset); int find_symbol_hole_containing(const struct section *sec, unsigned long offset); struct reloc *find_reloc_by_dest(const struct elf *elf, struct section *sec, unsigned long offset); diff --git a/tools/objtool/orc_gen.c b/tools/objtool/orc_gen.c index 56aca3845e20..3301128b5188 100644 --- a/tools/objtool/orc_gen.c +++ b/tools/objtool/orc_gen.c @@ -121,7 +121,11 @@ int orc_create(struct objtool_file *file) return 0; } orc_sec = elf_create_section(file->elf, ".orc_unwind", - sizeof(struct orc_entry), nr); + nr * sizeof(struct orc_entry), + sizeof(struct orc_entry), + SHT_PROGBITS, + 1, + SHF_ALLOC); sec = elf_create_section_pair(file->elf, ".orc_unwind_ip", sizeof(int), nr, nr); -- 2.45.2