Expose more functionality in the ELF library.  This will be needed for
the upcoming "objtool klp" support.

Signed-off-by: Josh Poimboeuf <jpoim...@kernel.org>
---
 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


Reply via email to