#include <xalloc.h>
#include "modpost.h"
struct syminfo {
unsigned int symbol_index;
unsigned int section_index;
Elf_Addr addr;
};
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;
}
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;
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__);
}
static void symsearch_fixup(struct syminfo *table, unsigned int table_size)
{
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 = xmalloc(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;
}
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;
while (hi > lo) {
unsigned int mid = lo + (hi - lo) / 2;
if (syminfo_compare(&table[mid], &target) > 0)
hi = mid;
else
lo = mid + 1;
}
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;
}