summaryrefslogtreecommitdiff
path: root/tools/objtool/elf.c
diff options
context:
space:
mode:
Diffstat (limited to 'tools/objtool/elf.c')
-rw-r--r--tools/objtool/elf.c200
1 files changed, 140 insertions, 60 deletions
diff --git a/tools/objtool/elf.c b/tools/objtool/elf.c
index 2ffe3ebfbe37..a791f4ea6ec1 100644
--- a/tools/objtool/elf.c
+++ b/tools/objtool/elf.c
@@ -23,24 +23,21 @@
#include <linux/log2.h>
#include <objtool/builtin.h>
#include <objtool/elf.h>
+#include <objtool/klp.h>
#include <objtool/warn.h>
-static inline u32 str_hash(const char *str)
+static ssize_t demangled_name_len(const char *name);
+
+u32 str_hash_demangled(const char *str)
{
- return jhash(str, strlen(str), 0);
+ return jhash(str, demangled_name_len(str), 0);
}
-#define __elf_table(name) (elf->name##_hash)
-#define __elf_bits(name) (elf->name##_bits)
-
-#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; \
+ __node->next = __elf_table_entry(elf, name, key); \
+ __elf_table_entry(elf, name, key) = __node; \
})
static inline void __elf_hash_del(struct elf_hash_node *node,
@@ -62,30 +59,20 @@ static inline void __elf_hash_del(struct elf_hash_node *node,
}
#define elf_hash_del(name, node, key) \
- __elf_hash_del(node, &__elf_table_entry(name, key))
-
-#define elf_list_entry(ptr, type, member) \
-({ \
- typeof(ptr) __ptr = (ptr); \
- __ptr ? container_of(__ptr, type, member) : NULL; \
-})
-
-#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))
+ __elf_hash_del(node, &__elf_table_entry(elf, name, key))
#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), \
+ __elf_bits(elf, name) = max(10, ilog2(size)); \
+ __elf_table(elf, name) = mmap(NULL, \
+ sizeof(struct elf_hash_node *) << __elf_bits(elf, name), \
PROT_READ|PROT_WRITE, \
MAP_PRIVATE|MAP_ANON, -1, 0); \
- if (__elf_table(name) == (void *)-1L) { \
+ if (__elf_table(elf, name) == (void *)-1L) { \
ERROR_GLIBC("mmap fail " #name); \
- __elf_table(name) = NULL; \
+ __elf_table(elf, name) = NULL; \
} \
- __elf_table(name); \
+ __elf_table(elf, name); \
})
static inline unsigned long __sym_start(struct symbol *s)
@@ -134,7 +121,7 @@ struct section *find_section_by_name(const struct elf *elf, const char *name)
{
struct section *sec;
- elf_hash_for_each_possible(section_name, sec, name_hash, str_hash(name)) {
+ elf_hash_for_each_possible(elf, section_name, sec, name_hash, str_hash(name)) {
if (!strcmp(sec->name, name))
return sec;
}
@@ -147,7 +134,7 @@ static struct section *find_section_by_index(struct elf *elf,
{
struct section *sec;
- elf_hash_for_each_possible(section, sec, hash, idx) {
+ elf_hash_for_each_possible(elf, section, sec, hash, idx) {
if (sec->idx == idx)
return sec;
}
@@ -159,7 +146,7 @@ static struct symbol *find_symbol_by_index(struct elf *elf, unsigned int idx)
{
struct symbol *sym;
- elf_hash_for_each_possible(symbol, sym, hash, idx) {
+ elf_hash_for_each_possible(elf, symbol, sym, hash, idx) {
if (sym->idx == idx)
return sym;
}
@@ -222,6 +209,20 @@ struct symbol *find_symbol_containing(const struct section *sec, unsigned long o
}
/*
+ * Also match the symbol end address which can be used for a bounds comparison.
+ */
+struct symbol *find_symbol_containing_inclusive(const struct section *sec,
+ unsigned long offset)
+{
+ struct symbol *sym = find_symbol_containing(sec, offset);
+
+ if (!sym && offset)
+ sym = find_symbol_containing(sec, offset - 1);
+
+ return sym;
+}
+
+/*
* Returns size of hole starting at @offset.
*/
int find_symbol_hole_containing(const struct section *sec, unsigned long offset)
@@ -278,7 +279,7 @@ struct symbol *find_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)) {
+ elf_hash_for_each_possible(elf, symbol_name, sym, name_hash, str_hash(name)) {
if (!strcmp(sym->name, name))
return sym;
}
@@ -293,7 +294,7 @@ static struct symbol *find_local_symbol_by_file_and_name(const struct elf *elf,
{
struct symbol *sym;
- elf_hash_for_each_possible(symbol_name, sym, name_hash, str_hash(name)) {
+ elf_hash_for_each_possible(elf, symbol_name, sym, name_hash, str_hash_demangled(name)) {
if (sym->bind == STB_LOCAL && sym->file == file &&
!strcmp(sym->name, name)) {
return sym;
@@ -307,7 +308,7 @@ struct symbol *find_global_symbol_by_name(const struct elf *elf, const char *nam
{
struct symbol *sym;
- elf_hash_for_each_possible(symbol_name, sym, name_hash, str_hash(name)) {
+ elf_hash_for_each_possible(elf, symbol_name, sym, name_hash, str_hash_demangled(name)) {
if (!strcmp(sym->name, name) && !is_local_sym(sym))
return sym;
}
@@ -315,8 +316,9 @@ struct symbol *find_global_symbol_by_name(const struct elf *elf, const char *nam
return NULL;
}
+/* If there are multiple matches, return the first one in the range */
struct reloc *find_reloc_by_dest_range(const struct elf *elf, struct section *sec,
- unsigned long offset, unsigned int len)
+ unsigned long offset, unsigned int len)
{
struct reloc *reloc, *r = NULL;
struct section *rsec;
@@ -327,7 +329,7 @@ struct reloc *find_reloc_by_dest_range(const struct elf *elf, struct section *se
return NULL;
for_offset_range(o, offset, offset + len) {
- elf_hash_for_each_possible(reloc, reloc, hash,
+ elf_hash_for_each_possible(elf, reloc, reloc, hash,
sec_offset_hash(rsec, o)) {
if (reloc->sec != rsec)
continue;
@@ -338,11 +340,11 @@ struct reloc *find_reloc_by_dest_range(const struct elf *elf, struct section *se
r = reloc;
}
}
- if (r)
+ if (r && (reloc_offset(r) & OFFSET_STRIDE_MASK) == o)
return r;
}
- return NULL;
+ return r;
}
struct reloc *find_reloc_by_dest(const struct elf *elf, struct section *sec, unsigned long offset)
@@ -440,34 +442,67 @@ static int read_sections(struct elf *elf)
return 0;
}
+/*
+ * Returns desired length of the demangled name.
+ * If name doesn't need demangling, return strlen(name).
+ */
+static ssize_t demangled_name_len(const char *name)
+{
+ ssize_t idx;
+ const char *p;
+
+ p = strstr(name, ".llvm.");
+ if (p)
+ return p - name;
+
+ if (!strstarts(name, "__UNIQUE_ID_") && !strchr(name, '.'))
+ return strlen(name);
+
+ for (idx = strlen(name) - 1; idx >= 0; idx--) {
+ char c = name[idx];
+
+ if (!isdigit(c) && c != '.' && c != '_')
+ break;
+ }
+ if (idx <= 0)
+ return strlen(name);
+ return idx + 1;
+}
+
+/*
+ * Remove number suffix of a symbol.
+ *
+ * Specifically, remove trailing numbers for "__UNIQUE_ID_" symbols and
+ * symbols with '.'.
+ *
+ * With CONFIG_LTO_CLANG_THIN, it is possible to have nested __UNIQUE_ID_,
+ * such as
+ *
+ * __UNIQUE_ID_addressable___UNIQUE_ID_pci_invalid_bar_694_695
+ *
+ * to remove both trailing numbers, also remove trailing '_'.
+ *
+ * For symbols with llvm suffix, i.e., foo.llvm.<hash>, remove the
+ * .llvm.<hash> part.
+ */
static const char *demangle_name(struct symbol *sym)
{
char *str;
-
- if (!is_local_sym(sym))
- return sym->name;
+ ssize_t len;
if (!is_func_sym(sym) && !is_object_sym(sym))
return sym->name;
- if (!strstarts(sym->name, "__UNIQUE_ID_") && !strchr(sym->name, '.'))
+ len = demangled_name_len(sym->name);
+ if (len == strlen(sym->name))
return sym->name;
- str = strdup(sym->name);
+ str = strndup(sym->name, len);
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;
}
@@ -503,9 +538,13 @@ static int elf_add_symbol(struct elf *elf, struct symbol *sym)
entry = &sym->sec->symbol_list;
list_add(&sym->list, entry);
+ sym->demangled_name = demangle_name(sym);
+ if (!sym->demangled_name)
+ return -1;
+
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));
+ elf_hash_add(symbol_name, &sym->name_hash, str_hash(sym->demangled_name));
if (is_func_sym(sym) &&
(strstarts(sym->name, "__pfx_") ||
@@ -529,10 +568,6 @@ static int elf_add_symbol(struct elf *elf, struct symbol *sym)
sym->pfunc = sym->cfunc = sym;
- sym->demangled_name = demangle_name(sym);
- if (!sym->demangled_name)
- return -1;
-
return 0;
}
@@ -592,6 +627,18 @@ static int read_symbols(struct elf *elf)
return -1;
}
+ /*
+ * "klp diff" renames the placeholder symbols of KLP relocs to
+ * hide them from modpost. Hide the prefix from the rest of
+ * objtool so its many name-based heuristics (noreturns,
+ * uaccess safe list, ...) still see the original symbol name.
+ *
+ * st_name is left alone, so the renamed symbol is preserved in
+ * the output file.
+ */
+ if (strstarts(sym->name, KLP_TOMBSTONE_PREFIX))
+ sym->name += strlen(KLP_TOMBSTONE_PREFIX);
+
if ((sym->sym.st_shndx > SHN_UNDEF &&
sym->sym.st_shndx < SHN_LORESERVE) ||
(shndx_data && sym->sym.st_shndx == SHN_XINDEX)) {
@@ -613,9 +660,9 @@ static int read_symbols(struct elf *elf)
if (elf_add_symbol(elf, sym))
return -1;
- if (sym->type == STT_FILE)
+ if (is_file_sym(sym))
file = sym;
- else if (sym->bind == STB_LOCAL)
+ else if (sym->bind == STB_LOCAL && !is_sec_sym(sym))
sym->file = file;
}
@@ -963,6 +1010,26 @@ non_local:
return sym;
}
+int elf_write_symbol(struct elf *elf, struct symbol *sym)
+{
+ struct section *symtab, *symtab_shndx;
+
+ symtab = find_section_by_name(elf, ".symtab");
+ if (!symtab) {
+ ERROR("no .symtab");
+ return -1;
+ }
+
+ symtab_shndx = find_section_by_name(elf, ".symtab_shndx");
+
+ if (elf_update_symbol(elf, symtab, symtab_shndx, sym))
+ return -1;
+
+ mark_sec_changed(elf, symtab, true);
+
+ return 0;
+}
+
struct symbol *elf_create_section_symbol(struct elf *elf, struct section *sec)
{
struct symbol *sym = calloc(1, sizeof(*sym));
@@ -1119,6 +1186,17 @@ static int read_relocs(struct elf *elf)
return 0;
}
+static void mark_rodata(struct elf *elf)
+{
+ struct section *sec;
+
+ for_each_sec(elf, sec) {
+ if ((strstarts(sec->name, ".rodata") && !strstr(sec->name, ".str1.")) ||
+ strstarts(sec->name, ".data.rel.ro"))
+ sec->rodata = true;
+ }
+}
+
struct elf *elf_open_read(const char *name, int flags)
{
struct elf *elf;
@@ -1169,6 +1247,8 @@ struct elf *elf_open_read(const char *name, int flags)
if (read_sections(elf))
goto err;
+ mark_rodata(elf);
+
if (read_symbols(elf))
goto err;
@@ -1318,7 +1398,7 @@ unsigned int elf_add_string(struct elf *elf, struct section *strtab, const char
return -1;
}
- offset = ALIGN(strtab->sh.sh_size, strtab->sh.sh_addralign);
+ offset = ALIGN(sec_size(strtab), strtab->sh.sh_addralign);
if (!elf_add_data(elf, strtab, str, strlen(str) + 1))
return -1;
@@ -1360,7 +1440,7 @@ void *elf_add_data(struct elf *elf, struct section *sec, const void *data, size_
sec->data->d_size = size;
sec->data->d_align = sec->sh.sh_addralign;
- offset = ALIGN(sec->sh.sh_size, sec->sh.sh_addralign);
+ offset = ALIGN(sec_size(sec), sec->sh.sh_addralign);
sec->sh.sh_size = offset + size;
mark_sec_changed(elf, sec, true);