Loading...
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 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; } |