1 | // SPDX-License-Identifier: GPL-2.0 |
2 | #include "debug.h" |
3 | #include "dsos.h" |
4 | #include "dso.h" |
5 | #include "util.h" |
6 | #include "vdso.h" |
7 | #include "namespaces.h" |
8 | #include <errno.h> |
9 | #include <libgen.h> |
10 | #include <stdlib.h> |
11 | #include <string.h> |
12 | #include <symbol.h> // filename__read_build_id |
13 | #include <unistd.h> |
14 | |
15 | static int __dso_id__cmp(struct dso_id *a, struct dso_id *b) |
16 | { |
17 | if (a->maj > b->maj) return -1; |
18 | if (a->maj < b->maj) return 1; |
19 | |
20 | if (a->min > b->min) return -1; |
21 | if (a->min < b->min) return 1; |
22 | |
23 | if (a->ino > b->ino) return -1; |
24 | if (a->ino < b->ino) return 1; |
25 | |
26 | /* |
27 | * Synthesized MMAP events have zero ino_generation, avoid comparing |
28 | * them with MMAP events with actual ino_generation. |
29 | * |
30 | * I found it harmful because the mismatch resulted in a new |
31 | * dso that did not have a build ID whereas the original dso did have a |
32 | * build ID. The build ID was essential because the object was not found |
33 | * otherwise. - Adrian |
34 | */ |
35 | if (a->ino_generation && b->ino_generation) { |
36 | if (a->ino_generation > b->ino_generation) return -1; |
37 | if (a->ino_generation < b->ino_generation) return 1; |
38 | } |
39 | |
40 | return 0; |
41 | } |
42 | |
43 | static bool dso_id__empty(struct dso_id *id) |
44 | { |
45 | if (!id) |
46 | return true; |
47 | |
48 | return !id->maj && !id->min && !id->ino && !id->ino_generation; |
49 | } |
50 | |
51 | static void dso__inject_id(struct dso *dso, struct dso_id *id) |
52 | { |
53 | dso->id.maj = id->maj; |
54 | dso->id.min = id->min; |
55 | dso->id.ino = id->ino; |
56 | dso->id.ino_generation = id->ino_generation; |
57 | } |
58 | |
59 | static int dso_id__cmp(struct dso_id *a, struct dso_id *b) |
60 | { |
61 | /* |
62 | * The second is always dso->id, so zeroes if not set, assume passing |
63 | * NULL for a means a zeroed id |
64 | */ |
65 | if (dso_id__empty(id: a) || dso_id__empty(id: b)) |
66 | return 0; |
67 | |
68 | return __dso_id__cmp(a, b); |
69 | } |
70 | |
71 | int dso__cmp_id(struct dso *a, struct dso *b) |
72 | { |
73 | return __dso_id__cmp(a: &a->id, b: &b->id); |
74 | } |
75 | |
76 | bool __dsos__read_build_ids(struct list_head *head, bool with_hits) |
77 | { |
78 | bool have_build_id = false; |
79 | struct dso *pos; |
80 | struct nscookie nsc; |
81 | |
82 | list_for_each_entry(pos, head, node) { |
83 | if (with_hits && !pos->hit && !dso__is_vdso(dso: pos)) |
84 | continue; |
85 | if (pos->has_build_id) { |
86 | have_build_id = true; |
87 | continue; |
88 | } |
89 | nsinfo__mountns_enter(nsi: pos->nsinfo, nc: &nsc); |
90 | if (filename__read_build_id(filename: pos->long_name, id: &pos->bid) > 0) { |
91 | have_build_id = true; |
92 | pos->has_build_id = true; |
93 | } else if (errno == ENOENT && pos->nsinfo) { |
94 | char *new_name = dso__filename_with_chroot(dso: pos, filename: pos->long_name); |
95 | |
96 | if (new_name && filename__read_build_id(filename: new_name, |
97 | id: &pos->bid) > 0) { |
98 | have_build_id = true; |
99 | pos->has_build_id = true; |
100 | } |
101 | free(new_name); |
102 | } |
103 | nsinfo__mountns_exit(nc: &nsc); |
104 | } |
105 | |
106 | return have_build_id; |
107 | } |
108 | |
109 | static int __dso__cmp_long_name(const char *long_name, struct dso_id *id, struct dso *b) |
110 | { |
111 | int rc = strcmp(long_name, b->long_name); |
112 | return rc ?: dso_id__cmp(a: id, b: &b->id); |
113 | } |
114 | |
115 | static int __dso__cmp_short_name(const char *short_name, struct dso_id *id, struct dso *b) |
116 | { |
117 | int rc = strcmp(short_name, b->short_name); |
118 | return rc ?: dso_id__cmp(a: id, b: &b->id); |
119 | } |
120 | |
121 | static int dso__cmp_short_name(struct dso *a, struct dso *b) |
122 | { |
123 | return __dso__cmp_short_name(short_name: a->short_name, id: &a->id, b); |
124 | } |
125 | |
126 | /* |
127 | * Find a matching entry and/or link current entry to RB tree. |
128 | * Either one of the dso or name parameter must be non-NULL or the |
129 | * function will not work. |
130 | */ |
131 | struct dso *__dsos__findnew_link_by_longname_id(struct rb_root *root, struct dso *dso, |
132 | const char *name, struct dso_id *id) |
133 | { |
134 | struct rb_node **p = &root->rb_node; |
135 | struct rb_node *parent = NULL; |
136 | |
137 | if (!name) |
138 | name = dso->long_name; |
139 | /* |
140 | * Find node with the matching name |
141 | */ |
142 | while (*p) { |
143 | struct dso *this = rb_entry(*p, struct dso, rb_node); |
144 | int rc = __dso__cmp_long_name(long_name: name, id, b: this); |
145 | |
146 | parent = *p; |
147 | if (rc == 0) { |
148 | /* |
149 | * In case the new DSO is a duplicate of an existing |
150 | * one, print a one-time warning & put the new entry |
151 | * at the end of the list of duplicates. |
152 | */ |
153 | if (!dso || (dso == this)) |
154 | return this; /* Find matching dso */ |
155 | /* |
156 | * The core kernel DSOs may have duplicated long name. |
157 | * In this case, the short name should be different. |
158 | * Comparing the short names to differentiate the DSOs. |
159 | */ |
160 | rc = dso__cmp_short_name(a: dso, b: this); |
161 | if (rc == 0) { |
162 | pr_err("Duplicated dso name: %s\n" , name); |
163 | return NULL; |
164 | } |
165 | } |
166 | if (rc < 0) |
167 | p = &parent->rb_left; |
168 | else |
169 | p = &parent->rb_right; |
170 | } |
171 | if (dso) { |
172 | /* Add new node and rebalance tree */ |
173 | rb_link_node(node: &dso->rb_node, parent, rb_link: p); |
174 | rb_insert_color(&dso->rb_node, root); |
175 | dso->root = root; |
176 | } |
177 | return NULL; |
178 | } |
179 | |
180 | void __dsos__add(struct dsos *dsos, struct dso *dso) |
181 | { |
182 | list_add_tail(new: &dso->node, head: &dsos->head); |
183 | __dsos__findnew_link_by_longname_id(root: &dsos->root, dso, NULL, id: &dso->id); |
184 | /* |
185 | * It is now in the linked list, grab a reference, then garbage collect |
186 | * this when needing memory, by looking at LRU dso instances in the |
187 | * list with atomic_read(&dso->refcnt) == 1, i.e. no references |
188 | * anywhere besides the one for the list, do, under a lock for the |
189 | * list: remove it from the list, then a dso__put(), that probably will |
190 | * be the last and will then call dso__delete(), end of life. |
191 | * |
192 | * That, or at the end of the 'struct machine' lifetime, when all |
193 | * 'struct dso' instances will be removed from the list, in |
194 | * dsos__exit(), if they have no other reference from some other data |
195 | * structure. |
196 | * |
197 | * E.g.: after processing a 'perf.data' file and storing references |
198 | * to objects instantiated while processing events, we will have |
199 | * references to the 'thread', 'map', 'dso' structs all from 'struct |
200 | * hist_entry' instances, but we may not need anything not referenced, |
201 | * so we might as well call machines__exit()/machines__delete() and |
202 | * garbage collect it. |
203 | */ |
204 | dso__get(dso); |
205 | } |
206 | |
207 | void dsos__add(struct dsos *dsos, struct dso *dso) |
208 | { |
209 | down_write(sem: &dsos->lock); |
210 | __dsos__add(dsos, dso); |
211 | up_write(sem: &dsos->lock); |
212 | } |
213 | |
214 | static struct dso *__dsos__findnew_by_longname_id(struct rb_root *root, const char *name, struct dso_id *id) |
215 | { |
216 | return __dsos__findnew_link_by_longname_id(root, NULL, name, id); |
217 | } |
218 | |
219 | static struct dso *__dsos__find_id(struct dsos *dsos, const char *name, struct dso_id *id, bool cmp_short) |
220 | { |
221 | struct dso *pos; |
222 | |
223 | if (cmp_short) { |
224 | list_for_each_entry(pos, &dsos->head, node) |
225 | if (__dso__cmp_short_name(short_name: name, id, b: pos) == 0) |
226 | return pos; |
227 | return NULL; |
228 | } |
229 | return __dsos__findnew_by_longname_id(root: &dsos->root, name, id); |
230 | } |
231 | |
232 | struct dso *__dsos__find(struct dsos *dsos, const char *name, bool cmp_short) |
233 | { |
234 | return __dsos__find_id(dsos, name, NULL, cmp_short); |
235 | } |
236 | |
237 | static void dso__set_basename(struct dso *dso) |
238 | { |
239 | char *base, *lname; |
240 | int tid; |
241 | |
242 | if (sscanf(dso->long_name, "/tmp/perf-%d.map" , &tid) == 1) { |
243 | if (asprintf(&base, "[JIT] tid %d" , tid) < 0) |
244 | return; |
245 | } else { |
246 | /* |
247 | * basename() may modify path buffer, so we must pass |
248 | * a copy. |
249 | */ |
250 | lname = strdup(dso->long_name); |
251 | if (!lname) |
252 | return; |
253 | |
254 | /* |
255 | * basename() may return a pointer to internal |
256 | * storage which is reused in subsequent calls |
257 | * so copy the result. |
258 | */ |
259 | base = strdup(basename(lname)); |
260 | |
261 | free(lname); |
262 | |
263 | if (!base) |
264 | return; |
265 | } |
266 | dso__set_short_name(dso, name: base, name_allocated: true); |
267 | } |
268 | |
269 | static struct dso *__dsos__addnew_id(struct dsos *dsos, const char *name, struct dso_id *id) |
270 | { |
271 | struct dso *dso = dso__new_id(name, id); |
272 | |
273 | if (dso != NULL) { |
274 | __dsos__add(dsos, dso); |
275 | dso__set_basename(dso); |
276 | /* Put dso here because __dsos_add already got it */ |
277 | dso__put(dso); |
278 | } |
279 | return dso; |
280 | } |
281 | |
282 | struct dso *__dsos__addnew(struct dsos *dsos, const char *name) |
283 | { |
284 | return __dsos__addnew_id(dsos, name, NULL); |
285 | } |
286 | |
287 | static struct dso *__dsos__findnew_id(struct dsos *dsos, const char *name, struct dso_id *id) |
288 | { |
289 | struct dso *dso = __dsos__find_id(dsos, name, id, cmp_short: false); |
290 | |
291 | if (dso && dso_id__empty(id: &dso->id) && !dso_id__empty(id)) |
292 | dso__inject_id(dso, id); |
293 | |
294 | return dso ? dso : __dsos__addnew_id(dsos, name, id); |
295 | } |
296 | |
297 | struct dso *dsos__findnew_id(struct dsos *dsos, const char *name, struct dso_id *id) |
298 | { |
299 | struct dso *dso; |
300 | down_write(sem: &dsos->lock); |
301 | dso = dso__get(dso: __dsos__findnew_id(dsos, name, id)); |
302 | up_write(sem: &dsos->lock); |
303 | return dso; |
304 | } |
305 | |
306 | size_t __dsos__fprintf_buildid(struct list_head *head, FILE *fp, |
307 | bool (skip)(struct dso *dso, int parm), int parm) |
308 | { |
309 | struct dso *pos; |
310 | size_t ret = 0; |
311 | |
312 | list_for_each_entry(pos, head, node) { |
313 | char sbuild_id[SBUILD_ID_SIZE]; |
314 | |
315 | if (skip && skip(pos, parm)) |
316 | continue; |
317 | build_id__sprintf(build_id: &pos->bid, bf: sbuild_id); |
318 | ret += fprintf(fp, "%-40s %s\n" , sbuild_id, pos->long_name); |
319 | } |
320 | return ret; |
321 | } |
322 | |
323 | size_t __dsos__fprintf(struct list_head *head, FILE *fp) |
324 | { |
325 | struct dso *pos; |
326 | size_t ret = 0; |
327 | |
328 | list_for_each_entry(pos, head, node) { |
329 | ret += dso__fprintf(pos, fp); |
330 | } |
331 | |
332 | return ret; |
333 | } |
334 | |