summaryrefslogtreecommitdiff
path: root/scripts/mod
diff options
context:
space:
mode:
authorLinus Torvalds <torvalds@linux-foundation.org>2023-11-04 08:07:19 -1000
committerLinus Torvalds <torvalds@linux-foundation.org>2023-11-04 08:07:19 -1000
commit5c5e048b2417a56b7b52bdbb66d4fc99d0c20dd2 (patch)
treea715272c8e6294469e28d1cbbe74e529624015c9 /scripts/mod
parent3062a9879afbca810d9f1613698963ecfcb35701 (diff)
parent5f56cb030e4bcf14be2233332d5cd83fff62a376 (diff)
Merge tag 'kbuild-v6.7' of git://git.kernel.org/pub/scm/linux/kernel/git/masahiroy/linux-kbuild
Pull Kbuild updates from Masahiro Yamada: - Implement the binary search in modpost for faster symbol lookup - Respect HOSTCC when linking host programs written in Rust - Change the binrpm-pkg target to generate kernel-devel RPM package - Fix endianness issues for tee and ishtp MODULE_DEVICE_TABLE - Unify vdso_install rules - Remove unused __memexit* annotations - Eliminate stale whitelisting for __devinit/__devexit from modpost - Enable dummy-tools to handle the -fpatchable-function-entry flag - Add 'userldlibs' syntax * tag 'kbuild-v6.7' of git://git.kernel.org/pub/scm/linux/kernel/git/masahiroy/linux-kbuild: (30 commits) kbuild: support 'userldlibs' syntax kbuild: dummy-tools: pretend we understand -fpatchable-function-entry kbuild: Correct missing architecture-specific hyphens modpost: squash ALL_{INIT,EXIT}_TEXT_SECTIONS to ALL_TEXT_SECTIONS modpost: merge sectioncheck table entries regarding init/exit sections modpost: use ALL_INIT_SECTIONS for the section check from DATA_SECTIONS modpost: disallow the combination of EXPORT_SYMBOL and __meminit* modpost: remove EXIT_SECTIONS macro modpost: remove MEM_INIT_SECTIONS macro modpost: remove more symbol patterns from the section check whitelist modpost: disallow *driver to reference .meminit* sections linux/init: remove __memexit* annotations modpost: remove ALL_EXIT_DATA_SECTIONS macro kbuild: simplify cmd_ld_multi_m kbuild: avoid too many execution of scripts/pahole-flags.sh kbuild: remove ARCH_POSTLINK from module builds kbuild: unify no-compiler-targets and no-sync-config-targets kbuild: unify vdso_install rules docs: kbuild: add INSTALL_DTBS_PATH UML: remove unused cmd_vdso_install ...
Diffstat (limited to 'scripts/mod')
-rw-r--r--scripts/mod/Makefile4
-rw-r--r--scripts/mod/file2alias.c14
-rw-r--r--scripts/mod/modpost.c201
-rw-r--r--scripts/mod/modpost.h50
-rw-r--r--scripts/mod/symsearch.c199
5 files changed, 294 insertions, 174 deletions
diff --git a/scripts/mod/Makefile b/scripts/mod/Makefile
index c9e38ad937fd..3c54125eb373 100644
--- a/scripts/mod/Makefile
+++ b/scripts/mod/Makefile
@@ -5,7 +5,7 @@ CFLAGS_REMOVE_empty.o += $(CC_FLAGS_LTO)
hostprogs-always-y += modpost mk_elfconfig
always-y += empty.o
-modpost-objs := modpost.o file2alias.o sumversion.o
+modpost-objs := modpost.o file2alias.o sumversion.o symsearch.o
devicetable-offsets-file := devicetable-offsets.h
@@ -16,7 +16,7 @@ targets += $(devicetable-offsets-file) devicetable-offsets.s
# dependencies on generated files need to be listed explicitly
-$(obj)/modpost.o $(obj)/file2alias.o $(obj)/sumversion.o: $(obj)/elfconfig.h
+$(obj)/modpost.o $(obj)/file2alias.o $(obj)/sumversion.o $(obj)/symsearch.o: $(obj)/elfconfig.h
$(obj)/file2alias.o: $(obj)/$(devicetable-offsets-file)
quiet_cmd_elfconfig = MKELF $@
diff --git a/scripts/mod/file2alias.c b/scripts/mod/file2alias.c
index 5bc02ce92958..4829680a0a6d 100644
--- a/scripts/mod/file2alias.c
+++ b/scripts/mod/file2alias.c
@@ -1348,13 +1348,13 @@ static int do_typec_entry(const char *filename, void *symval, char *alias)
/* Looks like: tee:uuid */
static int do_tee_entry(const char *filename, void *symval, char *alias)
{
- DEF_FIELD(symval, tee_client_device_id, uuid);
+ DEF_FIELD_ADDR(symval, tee_client_device_id, uuid);
sprintf(alias, "tee:%02x%02x%02x%02x-%02x%02x-%02x%02x-%02x%02x-%02x%02x%02x%02x%02x%02x",
- uuid.b[0], uuid.b[1], uuid.b[2], uuid.b[3], uuid.b[4],
- uuid.b[5], uuid.b[6], uuid.b[7], uuid.b[8], uuid.b[9],
- uuid.b[10], uuid.b[11], uuid.b[12], uuid.b[13], uuid.b[14],
- uuid.b[15]);
+ uuid->b[0], uuid->b[1], uuid->b[2], uuid->b[3], uuid->b[4],
+ uuid->b[5], uuid->b[6], uuid->b[7], uuid->b[8], uuid->b[9],
+ uuid->b[10], uuid->b[11], uuid->b[12], uuid->b[13], uuid->b[14],
+ uuid->b[15]);
add_wildcard(alias);
return 1;
@@ -1401,10 +1401,10 @@ static int do_mhi_ep_entry(const char *filename, void *symval, char *alias)
/* Looks like: ishtp:{guid} */
static int do_ishtp_entry(const char *filename, void *symval, char *alias)
{
- DEF_FIELD(symval, ishtp_device_id, guid);
+ DEF_FIELD_ADDR(symval, ishtp_device_id, guid);
strcpy(alias, ISHTP_MODULE_PREFIX "{");
- add_guid(alias, guid);
+ add_guid(alias, *guid);
strcat(alias, "}");
return 1;
diff --git a/scripts/mod/modpost.c b/scripts/mod/modpost.c
index ea72af229d2b..973b5e5ae2dd 100644
--- a/scripts/mod/modpost.c
+++ b/scripts/mod/modpost.c
@@ -22,7 +22,6 @@
#include <errno.h>
#include "modpost.h"
#include "../../include/linux/license.h"
-#include "../../include/linux/module_symbol.h"
static bool module_enabled;
/* Are we using CONFIG_MODVERSIONS? */
@@ -577,11 +576,14 @@ static int parse_elf(struct elf_info *info, const char *filename)
*p = TO_NATIVE(*p);
}
+ symsearch_init(info);
+
return 1;
}
static void parse_elf_finish(struct elf_info *info)
{
+ symsearch_finish(info);
release_file(info->hdr, info->size);
}
@@ -792,24 +794,16 @@ static void check_section(const char *modname, struct elf_info *elf,
#define ALL_INIT_DATA_SECTIONS \
".init.setup", ".init.rodata", ".meminit.rodata", \
".init.data", ".meminit.data"
-#define ALL_EXIT_DATA_SECTIONS \
- ".exit.data", ".memexit.data"
-
-#define ALL_INIT_TEXT_SECTIONS \
- ".init.text", ".meminit.text"
-#define ALL_EXIT_TEXT_SECTIONS \
- ".exit.text", ".memexit.text"
#define ALL_PCI_INIT_SECTIONS \
".pci_fixup_early", ".pci_fixup_header", ".pci_fixup_final", \
".pci_fixup_enable", ".pci_fixup_resume", \
".pci_fixup_resume_early", ".pci_fixup_suspend"
-#define ALL_XXXINIT_SECTIONS MEM_INIT_SECTIONS
-#define ALL_XXXEXIT_SECTIONS MEM_EXIT_SECTIONS
+#define ALL_XXXINIT_SECTIONS ".meminit.*"
#define ALL_INIT_SECTIONS INIT_SECTIONS, ALL_XXXINIT_SECTIONS
-#define ALL_EXIT_SECTIONS EXIT_SECTIONS, ALL_XXXEXIT_SECTIONS
+#define ALL_EXIT_SECTIONS ".exit.*"
#define DATA_SECTIONS ".data", ".data.rel"
#define TEXT_SECTIONS ".text", ".text.*", ".sched.text", \
@@ -819,20 +813,13 @@ static void check_section(const char *modname, struct elf_info *elf,
".coldtext", ".softirqentry.text"
#define INIT_SECTIONS ".init.*"
-#define MEM_INIT_SECTIONS ".meminit.*"
-#define EXIT_SECTIONS ".exit.*"
-#define MEM_EXIT_SECTIONS ".memexit.*"
-
-#define ALL_TEXT_SECTIONS ALL_INIT_TEXT_SECTIONS, ALL_EXIT_TEXT_SECTIONS, \
+#define ALL_TEXT_SECTIONS ".init.text", ".meminit.text", ".exit.text", \
TEXT_SECTIONS, OTHER_TEXT_SECTIONS
enum mismatch {
- TEXT_TO_ANY_INIT,
- DATA_TO_ANY_INIT,
- TEXTDATA_TO_ANY_EXIT,
+ TEXTDATA_TO_ANY_INIT_EXIT,
XXXINIT_TO_SOME_INIT,
- XXXEXIT_TO_SOME_EXIT,
ANY_INIT_TO_ANY_EXIT,
ANY_EXIT_TO_ANY_INIT,
EXTABLE_TO_NON_TEXT,
@@ -863,19 +850,9 @@ static const struct sectioncheck sectioncheck[] = {
* normal code and data
*/
{
- .fromsec = { TEXT_SECTIONS, NULL },
- .bad_tosec = { ALL_INIT_SECTIONS, NULL },
- .mismatch = TEXT_TO_ANY_INIT,
-},
-{
- .fromsec = { DATA_SECTIONS, NULL },
- .bad_tosec = { ALL_XXXINIT_SECTIONS, INIT_SECTIONS, NULL },
- .mismatch = DATA_TO_ANY_INIT,
-},
-{
.fromsec = { TEXT_SECTIONS, DATA_SECTIONS, NULL },
- .bad_tosec = { ALL_EXIT_SECTIONS, NULL },
- .mismatch = TEXTDATA_TO_ANY_EXIT,
+ .bad_tosec = { ALL_INIT_SECTIONS, ALL_EXIT_SECTIONS, NULL },
+ .mismatch = TEXTDATA_TO_ANY_INIT_EXIT,
},
/* Do not reference init code/data from meminit code/data */
{
@@ -883,12 +860,6 @@ static const struct sectioncheck sectioncheck[] = {
.bad_tosec = { INIT_SECTIONS, NULL },
.mismatch = XXXINIT_TO_SOME_INIT,
},
-/* Do not reference exit code/data from memexit code/data */
-{
- .fromsec = { ALL_XXXEXIT_SECTIONS, NULL },
- .bad_tosec = { EXIT_SECTIONS, NULL },
- .mismatch = XXXEXIT_TO_SOME_EXIT,
-},
/* Do not use exit code/data from init code */
{
.fromsec = { ALL_INIT_SECTIONS, NULL },
@@ -1006,19 +977,7 @@ static int secref_whitelist(const char *fromsec, const char *fromsym,
/* symbols in data sections that may refer to any init/exit sections */
if (match(fromsec, PATTERNS(DATA_SECTIONS)) &&
match(tosec, PATTERNS(ALL_INIT_SECTIONS, ALL_EXIT_SECTIONS)) &&
- match(fromsym, PATTERNS("*_template", // scsi uses *_template a lot
- "*_timer", // arm uses ops structures named _timer a lot
- "*_sht", // scsi also used *_sht to some extent
- "*_ops",
- "*_probe",
- "*_probe_one",
- "*_console")))
- return 0;
-
- /* symbols in data sections that may refer to meminit sections */
- if (match(fromsec, PATTERNS(DATA_SECTIONS)) &&
- match(tosec, PATTERNS(ALL_XXXINIT_SECTIONS, ALL_XXXEXIT_SECTIONS)) &&
- match(fromsym, PATTERNS("*driver")))
+ match(fromsym, PATTERNS("*_ops", "*_probe", "*_console")))
return 0;
/*
@@ -1028,7 +987,7 @@ static int secref_whitelist(const char *fromsec, const char *fromsym,
*/
if (!extra_warn &&
match(fromsec, PATTERNS(DATA_SECTIONS)) &&
- match(tosec, PATTERNS(EXIT_SECTIONS)) &&
+ match(tosec, PATTERNS(ALL_EXIT_SECTIONS)) &&
match(fromsym, PATTERNS("*driver")))
return 0;
@@ -1050,71 +1009,10 @@ static int secref_whitelist(const char *fromsec, const char *fromsym,
return 1;
}
-/*
- * If there's no name there, ignore it; likewise, ignore it if it's
- * one of the magic symbols emitted used by current tools.
- *
- * Otherwise if find_symbols_between() returns those symbols, they'll
- * fail the whitelist tests and cause lots of false alarms ... fixable
- * only by merging __exit and __init sections into __text, bloating
- * the kernel (which is especially evil on embedded platforms).
- */
-static inline bool is_valid_name(struct elf_info *elf, Elf_Sym *sym)
-{
- const char *name = elf->strtab + sym->st_name;
-
- if (!name || !strlen(name))
- return false;
- return !is_mapping_symbol(name);
-}
-
-/* Look up the nearest symbol based on the section and the address */
-static Elf_Sym *find_nearest_sym(struct elf_info *elf, Elf_Addr addr,
- unsigned int secndx, bool allow_negative,
- Elf_Addr min_distance)
-{
- Elf_Sym *sym;
- Elf_Sym *near = NULL;
- Elf_Addr sym_addr, distance;
- bool is_arm = (elf->hdr->e_machine == EM_ARM);
-
- for (sym = elf->symtab_start; sym < elf->symtab_stop; sym++) {
- if (get_secindex(elf, sym) != secndx)
- continue;
- if (!is_valid_name(elf, sym))
- continue;
-
- sym_addr = sym->st_value;
-
- /*
- * For ARM Thumb instruction, the bit 0 of st_value is set
- * if the symbol is STT_FUNC type. Mask it to get the address.
- */
- if (is_arm && ELF_ST_TYPE(sym->st_info) == STT_FUNC)
- sym_addr &= ~1;
-
- if (addr >= sym_addr)
- distance = addr - sym_addr;
- else if (allow_negative)
- distance = sym_addr - addr;
- else
- continue;
-
- if (distance <= min_distance) {
- min_distance = distance;
- near = sym;
- }
-
- if (min_distance == 0)
- break;
- }
- return near;
-}
-
static Elf_Sym *find_fromsym(struct elf_info *elf, Elf_Addr addr,
unsigned int secndx)
{
- return find_nearest_sym(elf, addr, secndx, false, ~0);
+ return symsearch_find_nearest(elf, addr, secndx, false, ~0);
}
static Elf_Sym *find_tosym(struct elf_info *elf, Elf_Addr addr, Elf_Sym *sym)
@@ -1127,7 +1025,8 @@ static Elf_Sym *find_tosym(struct elf_info *elf, Elf_Addr addr, Elf_Sym *sym)
* Strive to find a better symbol name, but the resulting name may not
* match the symbol referenced in the original code.
*/
- return find_nearest_sym(elf, addr, get_secindex(elf, sym), true, 20);
+ return symsearch_find_nearest(elf, addr, get_secindex(elf, sym),
+ true, 20);
}
static bool is_executable_section(struct elf_info *elf, unsigned int secndx)
@@ -1248,10 +1147,10 @@ static void check_export_symbol(struct module *mod, struct elf_info *elf,
ELF_ST_TYPE(sym->st_info) == STT_LOPROC)
s->is_func = true;
- if (match(secname, PATTERNS(INIT_SECTIONS)))
+ if (match(secname, PATTERNS(ALL_INIT_SECTIONS)))
warn("%s: %s: EXPORT_SYMBOL used for init symbol. Remove __init or EXPORT_SYMBOL.\n",
mod->name, name);
- else if (match(secname, PATTERNS(EXIT_SECTIONS)))
+ else if (match(secname, PATTERNS(ALL_EXIT_SECTIONS)))
warn("%s: %s: EXPORT_SYMBOL used for exit symbol. Remove __exit or EXPORT_SYMBOL.\n",
mod->name, name);
}
@@ -1468,32 +1367,20 @@ static void get_rel_type_and_sym(struct elf_info *elf, uint64_t r_info,
return;
}
- if (is_64bit) {
- Elf64_Xword r_info64 = r_info;
-
- r_info = TO_NATIVE(r_info64);
- } else {
- Elf32_Word r_info32 = r_info;
-
- r_info = TO_NATIVE(r_info32);
- }
+ if (is_64bit)
+ r_info = TO_NATIVE((Elf64_Xword)r_info);
+ else
+ r_info = TO_NATIVE((Elf32_Word)r_info);
*r_type = ELF_R_TYPE(r_info);
*r_sym = ELF_R_SYM(r_info);
}
static void section_rela(struct module *mod, struct elf_info *elf,
- Elf_Shdr *sechdr)
+ unsigned int fsecndx, const char *fromsec,
+ const Elf_Rela *start, const Elf_Rela *stop)
{
- Elf_Rela *rela;
- unsigned int fsecndx = sechdr->sh_info;
- const char *fromsec = sec_name(elf, fsecndx);
- Elf_Rela *start = (void *)elf->hdr + sechdr->sh_offset;
- Elf_Rela *stop = (void *)start + sechdr->sh_size;
-
- /* if from section (name) is know good then skip it */
- if (match(fromsec, section_white_list))
- return;
+ const Elf_Rela *rela;
for (rela = start; rela < stop; rela++) {
Elf_Addr taddr, r_offset;
@@ -1523,17 +1410,10 @@ static void section_rela(struct module *mod, struct elf_info *elf,
}
static void section_rel(struct module *mod, struct elf_info *elf,
- Elf_Shdr *sechdr)
+ unsigned int fsecndx, const char *fromsec,
+ const Elf_Rel *start, const Elf_Rel *stop)
{
- Elf_Rel *rel;
- unsigned int fsecndx = sechdr->sh_info;
- const char *fromsec = sec_name(elf, fsecndx);
- Elf_Rel *start = (void *)elf->hdr + sechdr->sh_offset;
- Elf_Rel *stop = (void *)start + sechdr->sh_size;
-
- /* if from section (name) is know good then skip it */
- if (match(fromsec, section_white_list))
- return;
+ const Elf_Rel *rel;
for (rel = start; rel < stop; rel++) {
Elf_Sym *tsym;
@@ -1581,16 +1461,33 @@ static void section_rel(struct module *mod, struct elf_info *elf,
static void check_sec_ref(struct module *mod, struct elf_info *elf)
{
int i;
- Elf_Shdr *sechdrs = elf->sechdrs;
/* Walk through all sections */
for (i = 0; i < elf->num_sections; i++) {
- check_section(mod->name, elf, &elf->sechdrs[i]);
+ Elf_Shdr *sechdr = &elf->sechdrs[i];
+
+ check_section(mod->name, elf, sechdr);
/* We want to process only relocation sections and not .init */
- if (sechdrs[i].sh_type == SHT_RELA)
- section_rela(mod, elf, &elf->sechdrs[i]);
- else if (sechdrs[i].sh_type == SHT_REL)
- section_rel(mod, elf, &elf->sechdrs[i]);
+ if (sechdr->sh_type == SHT_REL || sechdr->sh_type == SHT_RELA) {
+ /* section to which the relocation applies */
+ unsigned int secndx = sechdr->sh_info;
+ const char *secname = sec_name(elf, secndx);
+ const void *start, *stop;
+
+ /* If the section is known good, skip it */
+ if (match(secname, section_white_list))
+ continue;
+
+ start = sym_get_data_by_offset(elf, i, 0);
+ stop = start + sechdr->sh_size;
+
+ if (sechdr->sh_type == SHT_RELA)
+ section_rela(mod, elf, secndx, secname,
+ start, stop);
+ else
+ section_rel(mod, elf, secndx, secname,
+ start, stop);
+ }
}
}
diff --git a/scripts/mod/modpost.h b/scripts/mod/modpost.h
index 5f94c2c9f2d9..69baf014da4f 100644
--- a/scripts/mod/modpost.h
+++ b/scripts/mod/modpost.h
@@ -1,4 +1,5 @@
/* SPDX-License-Identifier: GPL-2.0 */
+#include <byteswap.h>
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
@@ -10,6 +11,7 @@
#include <fcntl.h>
#include <unistd.h>
#include <elf.h>
+#include "../../include/linux/module_symbol.h"
#include "list.h"
#include "elfconfig.h"
@@ -50,21 +52,19 @@
#define ELF_R_TYPE ELF64_R_TYPE
#endif
-#if KERNEL_ELFDATA != HOST_ELFDATA
+#define bswap(x) \
+({ \
+ _Static_assert(sizeof(x) == 1 || sizeof(x) == 2 || \
+ sizeof(x) == 4 || sizeof(x) == 8, "bug"); \
+ (typeof(x))(sizeof(x) == 2 ? bswap_16(x) : \
+ sizeof(x) == 4 ? bswap_32(x) : \
+ sizeof(x) == 8 ? bswap_64(x) : \
+ x); \
+})
-static inline void __endian(const void *src, void *dest, unsigned int size)
-{
- unsigned int i;
- for (i = 0; i < size; i++)
- ((unsigned char*)dest)[i] = ((unsigned char*)src)[size - i-1];
-}
+#if KERNEL_ELFDATA != HOST_ELFDATA
-#define TO_NATIVE(x) \
-({ \
- typeof(x) __x; \
- __endian(&(x), &(__x), sizeof(__x)); \
- __x; \
-})
+#define TO_NATIVE(x) (bswap(x))
#else /* endianness matches */
@@ -128,6 +128,8 @@ struct elf_info {
* take shndx from symtab_shndx_start[N] instead */
Elf32_Word *symtab_shndx_start;
Elf32_Word *symtab_shndx_stop;
+
+ struct symsearch *symsearch;
};
/* Accessor for sym->st_shndx, hides ugliness of "64k sections" */
@@ -154,6 +156,28 @@ static inline unsigned int get_secindex(const struct elf_info *info,
return index;
}
+/*
+ * If there's no name there, ignore it; likewise, ignore it if it's
+ * one of the magic symbols emitted used by current tools.
+ *
+ * Internal symbols created by tools should be ignored by modpost.
+ */
+static inline bool is_valid_name(struct elf_info *elf, Elf_Sym *sym)
+{
+ const char *name = elf->strtab + sym->st_name;
+
+ if (!name || !strlen(name))
+ return false;
+ return !is_mapping_symbol(name);
+}
+
+/* symsearch.c */
+void symsearch_init(struct elf_info *elf);
+void symsearch_finish(struct elf_info *elf);
+Elf_Sym *symsearch_find_nearest(struct elf_info *elf, Elf_Addr addr,
+ unsigned int secndx, bool allow_negative,
+ Elf_Addr min_distance);
+
/* file2alias.c */
void handle_moddevtable(struct module *mod, struct elf_info *info,
Elf_Sym *sym, const char *symname);
diff --git a/scripts/mod/symsearch.c b/scripts/mod/symsearch.c
new file mode 100644
index 000000000000..aa4ed51f9960
--- /dev/null
+++ b/scripts/mod/symsearch.c
@@ -0,0 +1,199 @@
+// SPDX-License-Identifier: GPL-2.0
+
+/*
+ * Helper functions for finding the symbol in an ELF which is "nearest"
+ * to a given address.
+ */
+
+#include "modpost.h"
+
+struct syminfo {
+ unsigned int symbol_index;
+ unsigned int section_index;
+ Elf_Addr addr;
+};
+
+/*
+ * Container used to hold an entire binary search table.
+ * Entries in table are ascending, sorted first by section_index,
+ * then by addr, and last by symbol_index. The sorting by
+ * symbol_index is used to ensure predictable behavior when
+ * multiple symbols are present with the same address; all
+ * symbols past the first are effectively ignored, by eliding
+ * them in symsearch_fixup().
+ */
+struct symsearch {
+ unsigned int table_size;
+ struct syminfo table[];
+};
+
+static int syminfo_compare(const void *s1, const void *s2)
+{
+ const struct syminfo *sym1 = s1;
+ const struct syminfo *sym2 = s2;
+
+ if (sym1->section_index > sym2->section_index)
+ return 1;
+ if (sym1->section_index < sym2->section_index)
+ return -1;
+ if (sym1->addr > sym2->addr)
+ return 1;
+ if (sym1->addr < sym2->addr)
+ return -1;
+ if (sym1->symbol_index > sym2->symbol_index)
+ return 1;
+ if (sym1->symbol_index < sym2->symbol_index)
+ return -1;
+ return 0;
+}
+
+static unsigned int symbol_count(struct elf_info *elf)
+{
+ unsigned int result = 0;
+
+ for (Elf_Sym *sym = elf->symtab_start; sym < elf->symtab_stop; sym++) {
+ if (is_valid_name(elf, sym))
+ result++;
+ }
+ return result;
+}
+
+/*
+ * Populate the search array that we just allocated.
+ * Be slightly paranoid here. The ELF file is mmap'd and could
+ * conceivably change between symbol_count() and symsearch_populate().
+ * If we notice any difference, bail out rather than potentially
+ * propagating errors or crashing.
+ */
+static void symsearch_populate(struct elf_info *elf,
+ struct syminfo *table,
+ unsigned int table_size)
+{
+ bool is_arm = (elf->hdr->e_machine == EM_ARM);
+
+ for (Elf_Sym *sym = elf->symtab_start; sym < elf->symtab_stop; sym++) {
+ if (is_valid_name(elf, sym)) {
+ if (table_size-- == 0)
+ fatal("%s: size mismatch\n", __func__);
+ table->symbol_index = sym - elf->symtab_start;
+ table->section_index = get_secindex(elf, sym);
+ table->addr = sym->st_value;
+
+ /*
+ * For ARM Thumb instruction, the bit 0 of st_value is
+ * set if the symbol is STT_FUNC type. Mask it to get
+ * the address.
+ */
+ if (is_arm && ELF_ST_TYPE(sym->st_info) == STT_FUNC)
+ table->addr &= ~1;
+
+ table++;
+ }
+ }
+
+ if (table_size != 0)
+ fatal("%s: size mismatch\n", __func__);
+}
+
+/*
+ * Do any fixups on the table after sorting.
+ * For now, this just finds adjacent entries which have
+ * the same section_index and addr, and it propagates
+ * the first symbol_index over the subsequent entries,
+ * so that only one symbol_index is seen for any given
+ * section_index and addr. This ensures that whether
+ * we're looking at an address from "above" or "below"
+ * that we see the same symbol_index.
+ * This does leave some duplicate entries in the table;
+ * in practice, these are a small fraction of the
+ * total number of entries, and they are harmless to
+ * the binary search algorithm other than a few occasional
+ * unnecessary comparisons.
+ */
+static void symsearch_fixup(struct syminfo *table, unsigned int table_size)
+{
+ /* Don't look at index 0, it will never change. */
+ for (unsigned int i = 1; i < table_size; i++) {
+ if (table[i].addr == table[i - 1].addr &&
+ table[i].section_index == table[i - 1].section_index) {
+ table[i].symbol_index = table[i - 1].symbol_index;
+ }
+ }
+}
+
+void symsearch_init(struct elf_info *elf)
+{
+ unsigned int table_size = symbol_count(elf);
+
+ elf->symsearch = NOFAIL(malloc(sizeof(struct symsearch) +
+ sizeof(struct syminfo) * table_size));
+ elf->symsearch->table_size = table_size;
+
+ symsearch_populate(elf, elf->symsearch->table, table_size);
+ qsort(elf->symsearch->table, table_size,
+ sizeof(struct syminfo), syminfo_compare);
+
+ symsearch_fixup(elf->symsearch->table, table_size);
+}
+
+void symsearch_finish(struct elf_info *elf)
+{
+ free(elf->symsearch);
+ elf->symsearch = NULL;
+}
+
+/*
+ * Find the syminfo which is in secndx and "nearest" to addr.
+ * allow_negative: allow returning a symbol whose address is > addr.
+ * min_distance: ignore symbols which are further away than this.
+ *
+ * Returns a pointer into the symbol table for success.
+ * Returns NULL if no legal symbol is found within the requested range.
+ */
+Elf_Sym *symsearch_find_nearest(struct elf_info *elf, Elf_Addr addr,
+ unsigned int secndx, bool allow_negative,
+ Elf_Addr min_distance)
+{
+ unsigned int hi = elf->symsearch->table_size;
+ unsigned int lo = 0;
+ struct syminfo *table = elf->symsearch->table;
+ struct syminfo target;
+
+ target.addr = addr;
+ target.section_index = secndx;
+ target.symbol_index = ~0; /* compares greater than any actual index */
+ while (hi > lo) {
+ unsigned int mid = lo + (hi - lo) / 2; /* Avoids overflow */
+
+ if (syminfo_compare(&table[mid], &target) > 0)
+ hi = mid;
+ else
+ lo = mid + 1;
+ }
+
+ /*
+ * table[hi], if it exists, is the first entry in the array which
+ * lies beyond target. table[hi - 1], if it exists, is the last
+ * entry in the array which comes before target, including the
+ * case where it perfectly matches the section and the address.
+ *
+ * Note -- if the address we're looking up falls perfectly
+ * in the middle of two symbols, this is written to always
+ * prefer the symbol with the lower address.
+ */
+ Elf_Sym *result = NULL;
+
+ if (allow_negative &&
+ hi < elf->symsearch->table_size &&
+ table[hi].section_index == secndx &&
+ table[hi].addr - addr <= min_distance) {
+ min_distance = table[hi].addr - addr;
+ result = &elf->symtab_start[table[hi].symbol_index];
+ }
+ if (hi > 0 &&
+ table[hi - 1].section_index == secndx &&
+ addr - table[hi - 1].addr <= min_distance) {
+ result = &elf->symtab_start[table[hi - 1].symbol_index];
+ }
+ return result;
+}