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); | |
0085c954 | 61 | symbol__delete(pos, self->sym_priv_size); |
a2928c42 ACM |
62 | } |
63 | } | |
64 | ||
65 | void dso__delete(struct dso *self) | |
66 | { | |
67 | dso__delete_symbols(self); | |
68 | free(self); | |
69 | } | |
70 | ||
71 | static void dso__insert_symbol(struct dso *self, struct symbol *sym) | |
72 | { | |
73 | struct rb_node **p = &self->syms.rb_node; | |
74 | struct rb_node *parent = NULL; | |
75 | const uint64_t ip = sym->start; | |
76 | struct symbol *s; | |
77 | ||
78 | while (*p != NULL) { | |
79 | parent = *p; | |
80 | s = rb_entry(parent, struct symbol, rb_node); | |
81 | if (ip < s->start) | |
82 | p = &(*p)->rb_left; | |
83 | else | |
84 | p = &(*p)->rb_right; | |
85 | } | |
86 | rb_link_node(&sym->rb_node, parent, p); | |
87 | rb_insert_color(&sym->rb_node, &self->syms); | |
88 | } | |
89 | ||
90 | struct symbol *dso__find_symbol(struct dso *self, uint64_t ip) | |
91 | { | |
92 | struct rb_node *n; | |
93 | ||
94 | if (self == NULL) | |
95 | return NULL; | |
96 | ||
97 | n = self->syms.rb_node; | |
98 | ||
99 | while (n) { | |
100 | struct symbol *s = rb_entry(n, struct symbol, rb_node); | |
101 | ||
102 | if (ip < s->start) | |
103 | n = n->rb_left; | |
104 | else if (ip > s->end) | |
105 | n = n->rb_right; | |
106 | else | |
107 | return s; | |
108 | } | |
109 | ||
110 | return NULL; | |
111 | } | |
112 | ||
113 | size_t dso__fprintf(struct dso *self, FILE *fp) | |
114 | { | |
115 | size_t ret = fprintf(fp, "dso: %s\n", self->name); | |
116 | ||
117 | struct rb_node *nd; | |
118 | for (nd = rb_first(&self->syms); nd; nd = rb_next(nd)) { | |
119 | struct symbol *pos = rb_entry(nd, struct symbol, rb_node); | |
120 | ret += symbol__fprintf(pos, fp); | |
121 | } | |
122 | ||
123 | return ret; | |
124 | } | |
125 | ||
69ee69f6 | 126 | static int dso__load_kallsyms(struct dso *self, symbol_filter_t filter) |
a2928c42 ACM |
127 | { |
128 | struct rb_node *nd, *prevnd; | |
129 | char *line = NULL; | |
130 | size_t n; | |
131 | FILE *file = fopen("/proc/kallsyms", "r"); | |
132 | ||
133 | if (file == NULL) | |
134 | goto out_failure; | |
135 | ||
136 | while (!feof(file)) { | |
a0055ae2 | 137 | __u64 start; |
a2928c42 ACM |
138 | struct symbol *sym; |
139 | int line_len, len; | |
140 | char symbol_type; | |
141 | ||
142 | line_len = getline(&line, &n, file); | |
143 | if (line_len < 0) | |
144 | break; | |
145 | ||
146 | if (!line) | |
147 | goto out_failure; | |
148 | ||
149 | line[--line_len] = '\0'; /* \n */ | |
150 | ||
a0055ae2 | 151 | len = hex2u64(line, &start); |
a2928c42 ACM |
152 | |
153 | len++; | |
154 | if (len + 2 >= line_len) | |
155 | continue; | |
156 | ||
157 | symbol_type = toupper(line[len]); | |
158 | /* | |
159 | * We're interested only in code ('T'ext) | |
160 | */ | |
161 | if (symbol_type != 'T' && symbol_type != 'W') | |
162 | continue; | |
163 | /* | |
164 | * Well fix up the end later, when we have all sorted. | |
165 | */ | |
0085c954 ACM |
166 | sym = symbol__new(start, 0xdead, line + len + 2, |
167 | self->sym_priv_size); | |
a2928c42 ACM |
168 | |
169 | if (sym == NULL) | |
170 | goto out_delete_line; | |
171 | ||
69ee69f6 ACM |
172 | if (filter && filter(self, sym)) |
173 | symbol__delete(sym, self->sym_priv_size); | |
174 | else | |
175 | dso__insert_symbol(self, sym); | |
a2928c42 ACM |
176 | } |
177 | ||
178 | /* | |
179 | * Now that we have all sorted out, just set the ->end of all | |
180 | * symbols | |
181 | */ | |
182 | prevnd = rb_first(&self->syms); | |
183 | ||
184 | if (prevnd == NULL) | |
185 | goto out_delete_line; | |
186 | ||
187 | for (nd = rb_next(prevnd); nd; nd = rb_next(nd)) { | |
188 | struct symbol *prev = rb_entry(prevnd, struct symbol, rb_node), | |
189 | *curr = rb_entry(nd, struct symbol, rb_node); | |
190 | ||
191 | prev->end = curr->start - 1; | |
192 | prevnd = nd; | |
193 | } | |
194 | ||
195 | free(line); | |
196 | fclose(file); | |
197 | ||
198 | return 0; | |
199 | ||
200 | out_delete_line: | |
201 | free(line); | |
202 | out_failure: | |
203 | return -1; | |
204 | } | |
205 | ||
206 | /** | |
207 | * elf_symtab__for_each_symbol - iterate thru all the symbols | |
208 | * | |
209 | * @self: struct elf_symtab instance to iterate | |
210 | * @index: uint32_t index | |
211 | * @sym: GElf_Sym iterator | |
212 | */ | |
213 | #define elf_symtab__for_each_symbol(syms, nr_syms, index, sym) \ | |
214 | for (index = 0, gelf_getsym(syms, index, &sym);\ | |
215 | index < nr_syms; \ | |
216 | index++, gelf_getsym(syms, index, &sym)) | |
217 | ||
218 | static inline uint8_t elf_sym__type(const GElf_Sym *sym) | |
219 | { | |
220 | return GELF_ST_TYPE(sym->st_info); | |
221 | } | |
222 | ||
223 | static inline int elf_sym__is_function(const GElf_Sym *sym) | |
224 | { | |
225 | return elf_sym__type(sym) == STT_FUNC && | |
226 | sym->st_name != 0 && | |
227 | sym->st_shndx != SHN_UNDEF && | |
228 | sym->st_size != 0; | |
229 | } | |
230 | ||
231 | static inline const char *elf_sym__name(const GElf_Sym *sym, | |
232 | const Elf_Data *symstrs) | |
233 | { | |
234 | return symstrs->d_buf + sym->st_name; | |
235 | } | |
236 | ||
237 | static Elf_Scn *elf_section_by_name(Elf *elf, GElf_Ehdr *ep, | |
238 | GElf_Shdr *shp, const char *name, | |
239 | size_t *index) | |
240 | { | |
241 | Elf_Scn *sec = NULL; | |
242 | size_t cnt = 1; | |
243 | ||
244 | while ((sec = elf_nextscn(elf, sec)) != NULL) { | |
245 | char *str; | |
246 | ||
247 | gelf_getshdr(sec, shp); | |
248 | str = elf_strptr(elf, ep->e_shstrndx, shp->sh_name); | |
249 | if (!strcmp(name, str)) { | |
250 | if (index) | |
251 | *index = cnt; | |
252 | break; | |
253 | } | |
254 | ++cnt; | |
255 | } | |
256 | ||
257 | return sec; | |
258 | } | |
259 | ||
69ee69f6 ACM |
260 | static int dso__load_sym(struct dso *self, int fd, const char *name, |
261 | symbol_filter_t filter) | |
a2928c42 ACM |
262 | { |
263 | Elf_Data *symstrs; | |
264 | uint32_t nr_syms; | |
265 | int err = -1; | |
266 | uint32_t index; | |
267 | GElf_Ehdr ehdr; | |
268 | GElf_Shdr shdr; | |
269 | Elf_Data *syms; | |
270 | GElf_Sym sym; | |
271 | Elf_Scn *sec; | |
272 | Elf *elf; | |
273 | int nr = 0; | |
274 | ||
275 | elf = elf_begin(fd, ELF_C_READ_MMAP, NULL); | |
276 | if (elf == NULL) { | |
277 | fprintf(stderr, "%s: cannot read %s ELF file.\n", | |
278 | __func__, name); | |
279 | goto out_close; | |
280 | } | |
281 | ||
282 | if (gelf_getehdr(elf, &ehdr) == NULL) { | |
283 | fprintf(stderr, "%s: cannot get elf header.\n", __func__); | |
284 | goto out_elf_end; | |
285 | } | |
286 | ||
287 | sec = elf_section_by_name(elf, &ehdr, &shdr, ".symtab", NULL); | |
288 | if (sec == NULL) | |
289 | sec = elf_section_by_name(elf, &ehdr, &shdr, ".dynsym", NULL); | |
290 | ||
291 | if (sec == NULL) | |
292 | goto out_elf_end; | |
293 | ||
294 | syms = elf_getdata(sec, NULL); | |
295 | if (syms == NULL) | |
296 | goto out_elf_end; | |
297 | ||
298 | sec = elf_getscn(elf, shdr.sh_link); | |
299 | if (sec == NULL) | |
300 | goto out_elf_end; | |
301 | ||
302 | symstrs = elf_getdata(sec, NULL); | |
303 | if (symstrs == NULL) | |
304 | goto out_elf_end; | |
305 | ||
306 | nr_syms = shdr.sh_size / shdr.sh_entsize; | |
307 | ||
308 | elf_symtab__for_each_symbol(syms, nr_syms, index, sym) { | |
309 | struct symbol *f; | |
310 | ||
311 | if (!elf_sym__is_function(&sym)) | |
312 | continue; | |
313 | ||
314 | sec = elf_getscn(elf, sym.st_shndx); | |
315 | if (!sec) | |
316 | goto out_elf_end; | |
317 | ||
318 | gelf_getshdr(sec, &shdr); | |
319 | sym.st_value -= shdr.sh_addr - shdr.sh_offset; | |
320 | ||
321 | f = symbol__new(sym.st_value, sym.st_size, | |
0085c954 ACM |
322 | elf_sym__name(&sym, symstrs), |
323 | self->sym_priv_size); | |
a2928c42 ACM |
324 | if (!f) |
325 | goto out_elf_end; | |
326 | ||
69ee69f6 ACM |
327 | if (filter && filter(self, f)) |
328 | symbol__delete(f, self->sym_priv_size); | |
329 | else { | |
330 | dso__insert_symbol(self, f); | |
331 | nr++; | |
332 | } | |
a2928c42 ACM |
333 | } |
334 | ||
335 | err = nr; | |
336 | out_elf_end: | |
337 | elf_end(elf); | |
338 | out_close: | |
339 | return err; | |
340 | } | |
341 | ||
69ee69f6 | 342 | int dso__load(struct dso *self, symbol_filter_t filter) |
a2928c42 ACM |
343 | { |
344 | int size = strlen(self->name) + sizeof("/usr/lib/debug%s.debug"); | |
345 | char *name = malloc(size); | |
346 | int variant = 0; | |
347 | int ret = -1; | |
348 | int fd; | |
349 | ||
350 | if (!name) | |
351 | return -1; | |
352 | ||
353 | more: | |
354 | do { | |
355 | switch (variant) { | |
356 | case 0: /* Fedora */ | |
357 | snprintf(name, size, "/usr/lib/debug%s.debug", self->name); | |
358 | break; | |
359 | case 1: /* Ubuntu */ | |
360 | snprintf(name, size, "/usr/lib/debug%s", self->name); | |
361 | break; | |
362 | case 2: /* Sane people */ | |
363 | snprintf(name, size, "%s", self->name); | |
364 | break; | |
365 | ||
366 | default: | |
367 | goto out; | |
368 | } | |
369 | variant++; | |
370 | ||
371 | fd = open(name, O_RDONLY); | |
372 | } while (fd < 0); | |
373 | ||
69ee69f6 | 374 | ret = dso__load_sym(self, fd, name, filter); |
a2928c42 ACM |
375 | close(fd); |
376 | ||
377 | /* | |
378 | * Some people seem to have debuginfo files _WITHOUT_ debug info!?!? | |
379 | */ | |
380 | if (!ret) | |
381 | goto more; | |
382 | ||
383 | out: | |
384 | free(name); | |
385 | return ret; | |
386 | } | |
387 | ||
69ee69f6 ACM |
388 | static int dso__load_vmlinux(struct dso *self, const char *vmlinux, |
389 | symbol_filter_t filter) | |
a2928c42 ACM |
390 | { |
391 | int err, fd = open(vmlinux, O_RDONLY); | |
392 | ||
393 | if (fd < 0) | |
394 | return -1; | |
395 | ||
69ee69f6 | 396 | err = dso__load_sym(self, fd, vmlinux, filter); |
a2928c42 ACM |
397 | close(fd); |
398 | ||
399 | return err; | |
400 | } | |
401 | ||
69ee69f6 | 402 | int dso__load_kernel(struct dso *self, const char *vmlinux, symbol_filter_t filter) |
a827c875 ACM |
403 | { |
404 | int err = -1; | |
405 | ||
406 | if (vmlinux) | |
69ee69f6 | 407 | err = dso__load_vmlinux(self, vmlinux, filter); |
a827c875 ACM |
408 | |
409 | if (err) | |
69ee69f6 | 410 | err = dso__load_kallsyms(self, filter); |
a827c875 ACM |
411 | |
412 | return err; | |
413 | } | |
414 | ||
a2928c42 ACM |
415 | void symbol__init(void) |
416 | { | |
417 | elf_version(EV_CURRENT); | |
418 | } |