Commit | Line | Data |
---|---|---|
a2928c42 ACM |
1 | #include "util.h" |
2 | #include "../perf.h" | |
a0055ae2 | 3 | #include "string.h" |
a2928c42 ACM |
4 | #include "symbol.h" |
5 | ||
6 | #include <libelf.h> | |
7 | #include <gelf.h> | |
8 | #include <elf.h> | |
9 | ||
10 | static struct symbol *symbol__new(uint64_t start, uint64_t len, | |
0085c954 | 11 | const char *name, unsigned int priv_size) |
a2928c42 | 12 | { |
0085c954 ACM |
13 | size_t namelen = strlen(name) + 1; |
14 | struct symbol *self = malloc(priv_size + sizeof(*self) + namelen); | |
a2928c42 ACM |
15 | |
16 | if (self != NULL) { | |
0085c954 ACM |
17 | if (priv_size) { |
18 | memset(self, 0, priv_size); | |
19 | self = ((void *)self) + priv_size; | |
20 | } | |
a2928c42 ACM |
21 | self->start = start; |
22 | self->end = start + len; | |
0085c954 | 23 | memcpy(self->name, name, namelen); |
a2928c42 ACM |
24 | } |
25 | ||
26 | return self; | |
27 | } | |
28 | ||
0085c954 | 29 | static void symbol__delete(struct symbol *self, unsigned int priv_size) |
a2928c42 | 30 | { |
0085c954 | 31 | free(((void *)self) - priv_size); |
a2928c42 ACM |
32 | } |
33 | ||
34 | static size_t symbol__fprintf(struct symbol *self, FILE *fp) | |
35 | { | |
36 | return fprintf(fp, " %llx-%llx %s\n", | |
37 | self->start, self->end, self->name); | |
38 | } | |
39 | ||
0085c954 | 40 | struct dso *dso__new(const char *name, unsigned int sym_priv_size) |
a2928c42 ACM |
41 | { |
42 | struct dso *self = malloc(sizeof(*self) + strlen(name) + 1); | |
43 | ||
44 | if (self != NULL) { | |
45 | strcpy(self->name, name); | |
46 | self->syms = RB_ROOT; | |
0085c954 | 47 | self->sym_priv_size = sym_priv_size; |
a2928c42 ACM |
48 | } |
49 | ||
50 | return self; | |
51 | } | |
52 | ||
53 | static void dso__delete_symbols(struct dso *self) | |
54 | { | |
55 | struct symbol *pos; | |
56 | struct rb_node *next = rb_first(&self->syms); | |
57 | ||
58 | while (next) { | |
59 | pos = rb_entry(next, struct symbol, rb_node); | |
60 | next = rb_next(&pos->rb_node); | |
c8c96525 | 61 | rb_erase(&pos->rb_node, &self->syms); |
0085c954 | 62 | symbol__delete(pos, self->sym_priv_size); |
a2928c42 ACM |
63 | } |
64 | } | |
65 | ||
66 | void dso__delete(struct dso *self) | |
67 | { | |
68 | dso__delete_symbols(self); | |
69 | free(self); | |
70 | } | |
71 | ||
72 | static void dso__insert_symbol(struct dso *self, struct symbol *sym) | |
73 | { | |
74 | struct rb_node **p = &self->syms.rb_node; | |
75 | struct rb_node *parent = NULL; | |
76 | const uint64_t ip = sym->start; | |
77 | struct symbol *s; | |
78 | ||
79 | while (*p != NULL) { | |
80 | parent = *p; | |
81 | s = rb_entry(parent, struct symbol, rb_node); | |
82 | if (ip < s->start) | |
83 | p = &(*p)->rb_left; | |
84 | else | |
85 | p = &(*p)->rb_right; | |
86 | } | |
87 | rb_link_node(&sym->rb_node, parent, p); | |
88 | rb_insert_color(&sym->rb_node, &self->syms); | |
89 | } | |
90 | ||
91 | struct symbol *dso__find_symbol(struct dso *self, uint64_t ip) | |
92 | { | |
93 | struct rb_node *n; | |
94 | ||
95 | if (self == NULL) | |
96 | return NULL; | |
97 | ||
98 | n = self->syms.rb_node; | |
99 | ||
100 | while (n) { | |
101 | struct symbol *s = rb_entry(n, struct symbol, rb_node); | |
102 | ||
103 | if (ip < s->start) | |
104 | n = n->rb_left; | |
105 | else if (ip > s->end) | |
106 | n = n->rb_right; | |
107 | else | |
108 | return s; | |
109 | } | |
110 | ||
111 | return NULL; | |
112 | } | |
113 | ||
114 | size_t dso__fprintf(struct dso *self, FILE *fp) | |
115 | { | |
116 | size_t ret = fprintf(fp, "dso: %s\n", self->name); | |
117 | ||
118 | struct rb_node *nd; | |
119 | for (nd = rb_first(&self->syms); nd; nd = rb_next(nd)) { | |
120 | struct symbol *pos = rb_entry(nd, struct symbol, rb_node); | |
121 | ret += symbol__fprintf(pos, fp); | |
122 | } | |
123 | ||
124 | return ret; | |
125 | } | |
126 | ||
69ee69f6 | 127 | static int dso__load_kallsyms(struct dso *self, symbol_filter_t filter) |
a2928c42 ACM |
128 | { |
129 | struct rb_node *nd, *prevnd; | |
130 | char *line = NULL; | |
131 | size_t n; | |
132 | FILE *file = fopen("/proc/kallsyms", "r"); | |
133 | ||
134 | if (file == NULL) | |
135 | goto out_failure; | |
136 | ||
137 | while (!feof(file)) { | |
a0055ae2 | 138 | __u64 start; |
a2928c42 ACM |
139 | struct symbol *sym; |
140 | int line_len, len; | |
141 | char symbol_type; | |
142 | ||
143 | line_len = getline(&line, &n, file); | |
144 | if (line_len < 0) | |
145 | break; | |
146 | ||
147 | if (!line) | |
148 | goto out_failure; | |
149 | ||
150 | line[--line_len] = '\0'; /* \n */ | |
151 | ||
a0055ae2 | 152 | len = hex2u64(line, &start); |
a2928c42 ACM |
153 | |
154 | len++; | |
155 | if (len + 2 >= line_len) | |
156 | continue; | |
157 | ||
158 | symbol_type = toupper(line[len]); | |
159 | /* | |
160 | * We're interested only in code ('T'ext) | |
161 | */ | |
162 | if (symbol_type != 'T' && symbol_type != 'W') | |
163 | continue; | |
164 | /* | |
165 | * Well fix up the end later, when we have all sorted. | |
166 | */ | |
0085c954 ACM |
167 | sym = symbol__new(start, 0xdead, line + len + 2, |
168 | self->sym_priv_size); | |
a2928c42 ACM |
169 | |
170 | if (sym == NULL) | |
171 | goto out_delete_line; | |
172 | ||
69ee69f6 ACM |
173 | if (filter && filter(self, sym)) |
174 | symbol__delete(sym, self->sym_priv_size); | |
175 | else | |
176 | dso__insert_symbol(self, sym); | |
a2928c42 ACM |
177 | } |
178 | ||
179 | /* | |
180 | * Now that we have all sorted out, just set the ->end of all | |
181 | * symbols | |
182 | */ | |
183 | prevnd = rb_first(&self->syms); | |
184 | ||
185 | if (prevnd == NULL) | |
186 | goto out_delete_line; | |
187 | ||
188 | for (nd = rb_next(prevnd); nd; nd = rb_next(nd)) { | |
189 | struct symbol *prev = rb_entry(prevnd, struct symbol, rb_node), | |
190 | *curr = rb_entry(nd, struct symbol, rb_node); | |
191 | ||
192 | prev->end = curr->start - 1; | |
193 | prevnd = nd; | |
194 | } | |
195 | ||
196 | free(line); | |
197 | fclose(file); | |
198 | ||
199 | return 0; | |
200 | ||
201 | out_delete_line: | |
202 | free(line); | |
203 | out_failure: | |
204 | return -1; | |
205 | } | |
206 | ||
207 | /** | |
208 | * elf_symtab__for_each_symbol - iterate thru all the symbols | |
209 | * | |
210 | * @self: struct elf_symtab instance to iterate | |
211 | * @index: uint32_t index | |
212 | * @sym: GElf_Sym iterator | |
213 | */ | |
214 | #define elf_symtab__for_each_symbol(syms, nr_syms, index, sym) \ | |
215 | for (index = 0, gelf_getsym(syms, index, &sym);\ | |
216 | index < nr_syms; \ | |
217 | index++, gelf_getsym(syms, index, &sym)) | |
218 | ||
219 | static inline uint8_t elf_sym__type(const GElf_Sym *sym) | |
220 | { | |
221 | return GELF_ST_TYPE(sym->st_info); | |
222 | } | |
223 | ||
224 | static inline int elf_sym__is_function(const GElf_Sym *sym) | |
225 | { | |
226 | return elf_sym__type(sym) == STT_FUNC && | |
227 | sym->st_name != 0 && | |
228 | sym->st_shndx != SHN_UNDEF && | |
229 | sym->st_size != 0; | |
230 | } | |
231 | ||
232 | static inline const char *elf_sym__name(const GElf_Sym *sym, | |
233 | const Elf_Data *symstrs) | |
234 | { | |
235 | return symstrs->d_buf + sym->st_name; | |
236 | } | |
237 | ||
238 | static Elf_Scn *elf_section_by_name(Elf *elf, GElf_Ehdr *ep, | |
239 | GElf_Shdr *shp, const char *name, | |
240 | size_t *index) | |
241 | { | |
242 | Elf_Scn *sec = NULL; | |
243 | size_t cnt = 1; | |
244 | ||
245 | while ((sec = elf_nextscn(elf, sec)) != NULL) { | |
246 | char *str; | |
247 | ||
248 | gelf_getshdr(sec, shp); | |
249 | str = elf_strptr(elf, ep->e_shstrndx, shp->sh_name); | |
250 | if (!strcmp(name, str)) { | |
251 | if (index) | |
252 | *index = cnt; | |
253 | break; | |
254 | } | |
255 | ++cnt; | |
256 | } | |
257 | ||
258 | return sec; | |
259 | } | |
260 | ||
8ce998d6 ACM |
261 | #define elf_section__for_each_rel(reldata, pos, pos_mem, idx, nr_entries) \ |
262 | for (idx = 0, pos = gelf_getrel(reldata, 0, &pos_mem); \ | |
263 | idx < nr_entries; \ | |
264 | ++idx, pos = gelf_getrel(reldata, idx, &pos_mem)) | |
265 | ||
266 | #define elf_section__for_each_rela(reldata, pos, pos_mem, idx, nr_entries) \ | |
267 | for (idx = 0, pos = gelf_getrela(reldata, 0, &pos_mem); \ | |
268 | idx < nr_entries; \ | |
269 | ++idx, pos = gelf_getrela(reldata, idx, &pos_mem)) | |
270 | ||
271 | static int dso__synthesize_plt_symbols(struct dso *self, Elf *elf, | |
272 | GElf_Ehdr *ehdr, Elf_Scn *scn_dynsym, | |
273 | GElf_Shdr *shdr_dynsym, | |
274 | size_t dynsym_idx) | |
275 | { | |
276 | uint32_t nr_rel_entries, idx; | |
277 | GElf_Sym sym; | |
278 | __u64 plt_offset; | |
279 | GElf_Shdr shdr_plt; | |
280 | struct symbol *f; | |
281 | GElf_Shdr shdr_rel_plt; | |
282 | Elf_Data *reldata, *syms, *symstrs; | |
283 | Elf_Scn *scn_plt_rel, *scn_symstrs; | |
284 | char sympltname[1024]; | |
285 | int nr = 0, symidx; | |
286 | ||
287 | scn_plt_rel = elf_section_by_name(elf, ehdr, &shdr_rel_plt, | |
288 | ".rela.plt", NULL); | |
289 | if (scn_plt_rel == NULL) { | |
290 | scn_plt_rel = elf_section_by_name(elf, ehdr, &shdr_rel_plt, | |
291 | ".rel.plt", NULL); | |
292 | if (scn_plt_rel == NULL) | |
293 | return 0; | |
294 | } | |
295 | ||
296 | if (shdr_rel_plt.sh_link != dynsym_idx) | |
297 | return 0; | |
298 | ||
299 | if (elf_section_by_name(elf, ehdr, &shdr_plt, ".plt", NULL) == NULL) | |
300 | return 0; | |
301 | ||
302 | /* | |
303 | * Fetch the relocation section to find the indexes to the GOT | |
304 | * and the symbols in the .dynsym they refer to. | |
305 | */ | |
306 | reldata = elf_getdata(scn_plt_rel, NULL); | |
307 | if (reldata == NULL) | |
308 | return -1; | |
309 | ||
310 | syms = elf_getdata(scn_dynsym, NULL); | |
311 | if (syms == NULL) | |
312 | return -1; | |
313 | ||
314 | scn_symstrs = elf_getscn(elf, shdr_dynsym->sh_link); | |
315 | if (scn_symstrs == NULL) | |
316 | return -1; | |
317 | ||
318 | symstrs = elf_getdata(scn_symstrs, NULL); | |
319 | if (symstrs == NULL) | |
320 | return -1; | |
321 | ||
322 | nr_rel_entries = shdr_rel_plt.sh_size / shdr_rel_plt.sh_entsize; | |
323 | plt_offset = shdr_plt.sh_offset; | |
324 | ||
325 | if (shdr_rel_plt.sh_type == SHT_RELA) { | |
326 | GElf_Rela pos_mem, *pos; | |
327 | ||
328 | elf_section__for_each_rela(reldata, pos, pos_mem, idx, | |
329 | nr_rel_entries) { | |
330 | symidx = GELF_R_SYM(pos->r_info); | |
331 | plt_offset += shdr_plt.sh_entsize; | |
332 | gelf_getsym(syms, symidx, &sym); | |
333 | snprintf(sympltname, sizeof(sympltname), | |
334 | "%s@plt", elf_sym__name(&sym, symstrs)); | |
335 | ||
336 | f = symbol__new(plt_offset, shdr_plt.sh_entsize, | |
337 | sympltname, self->sym_priv_size); | |
338 | if (!f) | |
339 | return -1; | |
340 | ||
341 | dso__insert_symbol(self, f); | |
342 | ++nr; | |
343 | } | |
344 | } else if (shdr_rel_plt.sh_type == SHT_REL) { | |
345 | GElf_Rel pos_mem, *pos; | |
346 | elf_section__for_each_rel(reldata, pos, pos_mem, idx, | |
347 | nr_rel_entries) { | |
348 | symidx = GELF_R_SYM(pos->r_info); | |
349 | plt_offset += shdr_plt.sh_entsize; | |
350 | gelf_getsym(syms, symidx, &sym); | |
351 | snprintf(sympltname, sizeof(sympltname), | |
352 | "%s@plt", elf_sym__name(&sym, symstrs)); | |
353 | ||
354 | f = symbol__new(plt_offset, shdr_plt.sh_entsize, | |
355 | sympltname, self->sym_priv_size); | |
356 | if (!f) | |
357 | return -1; | |
358 | ||
359 | dso__insert_symbol(self, f); | |
360 | ++nr; | |
361 | } | |
362 | } else { | |
363 | /* | |
364 | * TODO: There are still one more shdr_rel_plt.sh_type | |
365 | * I have to investigate, but probably should be ignored. | |
366 | */ | |
367 | } | |
368 | ||
369 | return nr; | |
370 | } | |
371 | ||
69ee69f6 ACM |
372 | static int dso__load_sym(struct dso *self, int fd, const char *name, |
373 | symbol_filter_t filter) | |
a2928c42 ACM |
374 | { |
375 | Elf_Data *symstrs; | |
376 | uint32_t nr_syms; | |
377 | int err = -1; | |
378 | uint32_t index; | |
379 | GElf_Ehdr ehdr; | |
380 | GElf_Shdr shdr; | |
381 | Elf_Data *syms; | |
382 | GElf_Sym sym; | |
8ce998d6 | 383 | Elf_Scn *sec, *sec_dynsym; |
a2928c42 | 384 | Elf *elf; |
8ce998d6 | 385 | size_t dynsym_idx; |
a2928c42 ACM |
386 | int nr = 0; |
387 | ||
388 | elf = elf_begin(fd, ELF_C_READ_MMAP, NULL); | |
389 | if (elf == NULL) { | |
390 | fprintf(stderr, "%s: cannot read %s ELF file.\n", | |
391 | __func__, name); | |
392 | goto out_close; | |
393 | } | |
394 | ||
395 | if (gelf_getehdr(elf, &ehdr) == NULL) { | |
396 | fprintf(stderr, "%s: cannot get elf header.\n", __func__); | |
397 | goto out_elf_end; | |
398 | } | |
399 | ||
8ce998d6 ACM |
400 | /* |
401 | * We need to check if we have a .dynsym, so that we can handle the | |
402 | * .plt, synthesizing its symbols, that aren't on the symtabs (be it | |
403 | * .dynsym or .symtab) | |
404 | */ | |
405 | sec_dynsym = elf_section_by_name(elf, &ehdr, &shdr, | |
406 | ".dynsym", &dynsym_idx); | |
407 | if (sec_dynsym != NULL) { | |
408 | nr = dso__synthesize_plt_symbols(self, elf, &ehdr, | |
409 | sec_dynsym, &shdr, | |
410 | dynsym_idx); | |
411 | if (nr < 0) | |
412 | goto out_elf_end; | |
413 | } | |
414 | ||
415 | /* | |
416 | * But if we have a full .symtab (that is a superset of .dynsym) we | |
417 | * should add the symbols not in the .dynsyn | |
418 | */ | |
a2928c42 | 419 | sec = elf_section_by_name(elf, &ehdr, &shdr, ".symtab", NULL); |
8ce998d6 ACM |
420 | if (sec == NULL) { |
421 | if (sec_dynsym == NULL) | |
422 | goto out_elf_end; | |
a2928c42 | 423 | |
8ce998d6 ACM |
424 | sec = sec_dynsym; |
425 | gelf_getshdr(sec, &shdr); | |
426 | } | |
a2928c42 ACM |
427 | |
428 | syms = elf_getdata(sec, NULL); | |
429 | if (syms == NULL) | |
430 | goto out_elf_end; | |
431 | ||
432 | sec = elf_getscn(elf, shdr.sh_link); | |
433 | if (sec == NULL) | |
434 | goto out_elf_end; | |
435 | ||
436 | symstrs = elf_getdata(sec, NULL); | |
437 | if (symstrs == NULL) | |
438 | goto out_elf_end; | |
439 | ||
440 | nr_syms = shdr.sh_size / shdr.sh_entsize; | |
441 | ||
442 | elf_symtab__for_each_symbol(syms, nr_syms, index, sym) { | |
443 | struct symbol *f; | |
444 | ||
445 | if (!elf_sym__is_function(&sym)) | |
446 | continue; | |
447 | ||
448 | sec = elf_getscn(elf, sym.st_shndx); | |
449 | if (!sec) | |
450 | goto out_elf_end; | |
451 | ||
452 | gelf_getshdr(sec, &shdr); | |
453 | sym.st_value -= shdr.sh_addr - shdr.sh_offset; | |
454 | ||
455 | f = symbol__new(sym.st_value, sym.st_size, | |
0085c954 ACM |
456 | elf_sym__name(&sym, symstrs), |
457 | self->sym_priv_size); | |
a2928c42 ACM |
458 | if (!f) |
459 | goto out_elf_end; | |
460 | ||
69ee69f6 ACM |
461 | if (filter && filter(self, f)) |
462 | symbol__delete(f, self->sym_priv_size); | |
463 | else { | |
464 | dso__insert_symbol(self, f); | |
465 | nr++; | |
466 | } | |
a2928c42 ACM |
467 | } |
468 | ||
469 | err = nr; | |
470 | out_elf_end: | |
471 | elf_end(elf); | |
472 | out_close: | |
473 | return err; | |
474 | } | |
475 | ||
69ee69f6 | 476 | int dso__load(struct dso *self, symbol_filter_t filter) |
a2928c42 ACM |
477 | { |
478 | int size = strlen(self->name) + sizeof("/usr/lib/debug%s.debug"); | |
479 | char *name = malloc(size); | |
480 | int variant = 0; | |
481 | int ret = -1; | |
482 | int fd; | |
483 | ||
484 | if (!name) | |
485 | return -1; | |
486 | ||
487 | more: | |
488 | do { | |
489 | switch (variant) { | |
490 | case 0: /* Fedora */ | |
491 | snprintf(name, size, "/usr/lib/debug%s.debug", self->name); | |
492 | break; | |
493 | case 1: /* Ubuntu */ | |
494 | snprintf(name, size, "/usr/lib/debug%s", self->name); | |
495 | break; | |
496 | case 2: /* Sane people */ | |
497 | snprintf(name, size, "%s", self->name); | |
498 | break; | |
499 | ||
500 | default: | |
501 | goto out; | |
502 | } | |
503 | variant++; | |
504 | ||
505 | fd = open(name, O_RDONLY); | |
506 | } while (fd < 0); | |
507 | ||
69ee69f6 | 508 | ret = dso__load_sym(self, fd, name, filter); |
a2928c42 ACM |
509 | close(fd); |
510 | ||
511 | /* | |
512 | * Some people seem to have debuginfo files _WITHOUT_ debug info!?!? | |
513 | */ | |
514 | if (!ret) | |
515 | goto more; | |
516 | ||
517 | out: | |
518 | free(name); | |
519 | return ret; | |
520 | } | |
521 | ||
69ee69f6 ACM |
522 | static int dso__load_vmlinux(struct dso *self, const char *vmlinux, |
523 | symbol_filter_t filter) | |
a2928c42 ACM |
524 | { |
525 | int err, fd = open(vmlinux, O_RDONLY); | |
526 | ||
527 | if (fd < 0) | |
528 | return -1; | |
529 | ||
69ee69f6 | 530 | err = dso__load_sym(self, fd, vmlinux, filter); |
a2928c42 ACM |
531 | close(fd); |
532 | ||
533 | return err; | |
534 | } | |
535 | ||
69ee69f6 | 536 | int dso__load_kernel(struct dso *self, const char *vmlinux, symbol_filter_t filter) |
a827c875 ACM |
537 | { |
538 | int err = -1; | |
539 | ||
540 | if (vmlinux) | |
69ee69f6 | 541 | err = dso__load_vmlinux(self, vmlinux, filter); |
a827c875 ACM |
542 | |
543 | if (err) | |
69ee69f6 | 544 | err = dso__load_kallsyms(self, filter); |
a827c875 ACM |
545 | |
546 | return err; | |
547 | } | |
548 | ||
a2928c42 ACM |
549 | void symbol__init(void) |
550 | { | |
551 | elf_version(EV_CURRENT); | |
552 | } |