summaryrefslogtreecommitdiff
path: root/tools/perf/util/dsos.c
diff options
context:
space:
mode:
Diffstat (limited to 'tools/perf/util/dsos.c')
-rw-r--r--tools/perf/util/dsos.c506
1 files changed, 506 insertions, 0 deletions
diff --git a/tools/perf/util/dsos.c b/tools/perf/util/dsos.c
new file mode 100644
index 000000000000..0a7645c7fae7
--- /dev/null
+++ b/tools/perf/util/dsos.c
@@ -0,0 +1,506 @@
+// SPDX-License-Identifier: GPL-2.0
+#include "debug.h"
+#include "dsos.h"
+#include "dso.h"
+#include "util.h"
+#include "vdso.h"
+#include "namespaces.h"
+#include <errno.h>
+#include <libgen.h>
+#include <stdlib.h>
+#include <string.h>
+#include <symbol.h> // filename__read_build_id
+#include <unistd.h>
+
+void dsos__init(struct dsos *dsos)
+{
+ init_rwsem(&dsos->lock);
+
+ dsos->cnt = 0;
+ dsos->allocated = 0;
+ dsos->dsos = NULL;
+ dsos->sorted = true;
+}
+
+static void dsos__purge(struct dsos *dsos)
+{
+ down_write(&dsos->lock);
+
+ for (unsigned int i = 0; i < dsos->cnt; i++) {
+ struct dso *dso = dsos->dsos[i];
+
+ dso__set_dsos(dso, NULL);
+ dso__put(dso);
+ }
+
+ zfree(&dsos->dsos);
+ dsos->cnt = 0;
+ dsos->allocated = 0;
+ dsos->sorted = true;
+
+ up_write(&dsos->lock);
+}
+
+void dsos__exit(struct dsos *dsos)
+{
+ dsos__purge(dsos);
+ exit_rwsem(&dsos->lock);
+}
+
+
+static int __dsos__for_each_dso(struct dsos *dsos,
+ int (*cb)(struct dso *dso, void *data),
+ void *data)
+{
+ for (unsigned int i = 0; i < dsos->cnt; i++) {
+ struct dso *dso = dsos->dsos[i];
+ int err;
+
+ err = cb(dso, data);
+ if (err)
+ return err;
+ }
+ return 0;
+}
+
+struct dsos__read_build_ids_cb_args {
+ bool with_hits;
+ bool have_build_id;
+};
+
+static int dsos__read_build_ids_cb(struct dso *dso, void *data)
+{
+ struct dsos__read_build_ids_cb_args *args = data;
+ struct nscookie nsc;
+ struct build_id bid = { .size = 0, };
+
+ if (args->with_hits && !dso__hit(dso) && !dso__is_vdso(dso))
+ return 0;
+ if (dso__has_build_id(dso)) {
+ args->have_build_id = true;
+ return 0;
+ }
+ nsinfo__mountns_enter(dso__nsinfo(dso), &nsc);
+ if (filename__read_build_id(dso__long_name(dso), &bid) > 0) {
+ dso__set_build_id(dso, &bid);
+ args->have_build_id = true;
+ } else if (errno == ENOENT && dso__nsinfo(dso)) {
+ char *new_name = dso__filename_with_chroot(dso, dso__long_name(dso));
+
+ if (new_name && filename__read_build_id(new_name, &bid) > 0) {
+ dso__set_build_id(dso, &bid);
+ args->have_build_id = true;
+ }
+ free(new_name);
+ }
+ nsinfo__mountns_exit(&nsc);
+ return 0;
+}
+
+bool dsos__read_build_ids(struct dsos *dsos, bool with_hits)
+{
+ struct dsos__read_build_ids_cb_args args = {
+ .with_hits = with_hits,
+ .have_build_id = false,
+ };
+
+ dsos__for_each_dso(dsos, dsos__read_build_ids_cb, &args);
+ return args.have_build_id;
+}
+
+static int __dso__cmp_long_name(const char *long_name, const struct dso_id *id,
+ const struct dso *b)
+{
+ int rc = strcmp(long_name, dso__long_name(b));
+ return rc ?: dso_id__cmp(id, dso__id_const(b));
+}
+
+static int __dso__cmp_short_name(const char *short_name, const struct dso_id *id,
+ const struct dso *b)
+{
+ int rc = strcmp(short_name, dso__short_name(b));
+ return rc ?: dso_id__cmp(id, dso__id_const(b));
+}
+
+static int dsos__cmp_long_name_id_short_name(const void *va, const void *vb)
+{
+ const struct dso *a = *((const struct dso **)va);
+ const struct dso *b = *((const struct dso **)vb);
+ int rc = strcmp(dso__long_name(a), dso__long_name(b));
+
+ if (!rc) {
+ rc = dso_id__cmp(dso__id_const(a), dso__id_const(b));
+ if (!rc)
+ rc = strcmp(dso__short_name(a), dso__short_name(b));
+ }
+ return rc;
+}
+
+struct dsos__key {
+ const char *long_name;
+ const struct dso_id *id;
+};
+
+static int dsos__cmp_key_long_name_id(const void *vkey, const void *vdso)
+{
+ const struct dsos__key *key = vkey;
+ const struct dso *dso = *((const struct dso **)vdso);
+
+ return __dso__cmp_long_name(key->long_name, key->id, dso);
+}
+
+/*
+ * Find a matching entry and/or link current entry to RB tree.
+ * Either one of the dso or name parameter must be non-NULL or the
+ * function will not work.
+ */
+static struct dso *__dsos__find_by_longname_id(struct dsos *dsos,
+ const char *name,
+ const struct dso_id *id,
+ bool write_locked)
+ SHARED_LOCKS_REQUIRED(dsos->lock)
+{
+ struct dsos__key key = {
+ .long_name = name,
+ .id = id,
+ };
+ struct dso **res;
+
+ if (dsos->dsos == NULL)
+ return NULL;
+
+ if (!dsos->sorted) {
+ if (!write_locked) {
+ struct dso *dso;
+
+ up_read(&dsos->lock);
+ down_write(&dsos->lock);
+ dso = __dsos__find_by_longname_id(dsos, name, id,
+ /*write_locked=*/true);
+ up_write(&dsos->lock);
+ down_read(&dsos->lock);
+ return dso;
+ }
+ qsort(dsos->dsos, dsos->cnt, sizeof(struct dso *),
+ dsos__cmp_long_name_id_short_name);
+ dsos->sorted = true;
+ }
+
+ res = bsearch(&key, dsos->dsos, dsos->cnt, sizeof(struct dso *),
+ dsos__cmp_key_long_name_id);
+ if (!res)
+ return NULL;
+
+ return dso__get(*res);
+}
+
+int __dsos__add(struct dsos *dsos, struct dso *dso)
+{
+ if (dsos->cnt == dsos->allocated) {
+ unsigned int to_allocate = 2;
+ struct dso **temp;
+
+ if (dsos->allocated > 0)
+ to_allocate = dsos->allocated * 2;
+ temp = realloc(dsos->dsos, sizeof(struct dso *) * to_allocate);
+ if (!temp)
+ return -ENOMEM;
+ dsos->dsos = temp;
+ dsos->allocated = to_allocate;
+ }
+ if (!dsos->sorted) {
+ dsos->dsos[dsos->cnt++] = dso__get(dso);
+ } else {
+ int low = 0, high = dsos->cnt - 1;
+ int insert = dsos->cnt; /* Default to inserting at the end. */
+
+ while (low <= high) {
+ int mid = low + (high - low) / 2;
+ int cmp = dsos__cmp_long_name_id_short_name(&dsos->dsos[mid], &dso);
+
+ if (cmp < 0) {
+ low = mid + 1;
+ } else {
+ high = mid - 1;
+ insert = mid;
+ }
+ }
+ memmove(&dsos->dsos[insert + 1], &dsos->dsos[insert],
+ (dsos->cnt - insert) * sizeof(struct dso *));
+ dsos->cnt++;
+ dsos->dsos[insert] = dso__get(dso);
+ }
+ dso__set_dsos(dso, dsos);
+ return 0;
+}
+
+int dsos__add(struct dsos *dsos, struct dso *dso)
+{
+ int ret;
+
+ down_write(&dsos->lock);
+ ret = __dsos__add(dsos, dso);
+ up_write(&dsos->lock);
+ return ret;
+}
+
+struct dsos__find_id_cb_args {
+ const char *name;
+ const struct dso_id *id;
+ struct dso *res;
+};
+
+static int dsos__find_id_cb(struct dso *dso, void *data)
+{
+ struct dsos__find_id_cb_args *args = data;
+
+ if (__dso__cmp_short_name(args->name, args->id, dso) == 0) {
+ args->res = dso__get(dso);
+ return 1;
+ }
+ return 0;
+
+}
+
+static struct dso *__dsos__find_id(struct dsos *dsos, const char *name, const struct dso_id *id,
+ bool cmp_short, bool write_locked)
+ SHARED_LOCKS_REQUIRED(dsos->lock)
+{
+ struct dso *res;
+
+ if (cmp_short) {
+ struct dsos__find_id_cb_args args = {
+ .name = name,
+ .id = id,
+ .res = NULL,
+ };
+
+ __dsos__for_each_dso(dsos, dsos__find_id_cb, &args);
+ return args.res;
+ }
+ res = __dsos__find_by_longname_id(dsos, name, id, write_locked);
+ return res;
+}
+
+struct dso *dsos__find(struct dsos *dsos, const char *name, bool cmp_short)
+{
+ struct dso *res;
+
+ down_read(&dsos->lock);
+ res = __dsos__find_id(dsos, name, &dso_id_empty, cmp_short, /*write_locked=*/false);
+ up_read(&dsos->lock);
+ return res;
+}
+
+static void dso__set_basename(struct dso *dso)
+{
+ char *base, *lname;
+ int tid;
+
+ if (perf_pid_map_tid(dso__long_name(dso), &tid)) {
+ if (asprintf(&base, "[JIT] tid %d", tid) < 0)
+ return;
+ } else {
+ /*
+ * basename() may modify path buffer, so we must pass
+ * a copy.
+ */
+ lname = strdup(dso__long_name(dso));
+ if (!lname)
+ return;
+
+ /*
+ * basename() may return a pointer to internal
+ * storage which is reused in subsequent calls
+ * so copy the result.
+ */
+ base = strdup(basename(lname));
+
+ free(lname);
+
+ if (!base)
+ return;
+ }
+ dso__set_short_name(dso, base, true);
+}
+
+static struct dso *__dsos__addnew_id(struct dsos *dsos, const char *name, const struct dso_id *id)
+{
+ struct dso *dso = dso__new_id(name, id);
+
+ if (dso != NULL) {
+ /*
+ * The dsos lock is held on entry, so rename the dso before
+ * adding it to avoid needing to take the dsos lock again to say
+ * the array isn't sorted.
+ */
+ dso__set_basename(dso);
+ __dsos__add(dsos, dso);
+ }
+ return dso;
+}
+
+static struct dso *__dsos__findnew_id(struct dsos *dsos, const char *name, const struct dso_id *id)
+ SHARED_LOCKS_REQUIRED(dsos->lock)
+{
+ struct dso *dso = __dsos__find_id(dsos, name, id, false, /*write_locked=*/true);
+
+ if (dso)
+ __dso__improve_id(dso, id);
+
+ return dso ? dso : __dsos__addnew_id(dsos, name, id);
+}
+
+struct dso *dsos__findnew_id(struct dsos *dsos, const char *name, const struct dso_id *id)
+{
+ struct dso *dso;
+ down_write(&dsos->lock);
+ dso = __dsos__findnew_id(dsos, name, id);
+ up_write(&dsos->lock);
+ return dso;
+}
+
+struct dsos__fprintf_buildid_cb_args {
+ FILE *fp;
+ bool (*skip)(struct dso *dso, int parm);
+ int parm;
+ size_t ret;
+};
+
+static int dsos__fprintf_buildid_cb(struct dso *dso, void *data)
+{
+ struct dsos__fprintf_buildid_cb_args *args = data;
+ char sbuild_id[SBUILD_ID_SIZE];
+
+ if (args->skip && args->skip(dso, args->parm))
+ return 0;
+ build_id__snprintf(dso__bid(dso), sbuild_id, sizeof(sbuild_id));
+ args->ret += fprintf(args->fp, "%-40s %s\n", sbuild_id, dso__long_name(dso));
+ return 0;
+}
+
+size_t dsos__fprintf_buildid(struct dsos *dsos, FILE *fp,
+ bool (*skip)(struct dso *dso, int parm), int parm)
+{
+ struct dsos__fprintf_buildid_cb_args args = {
+ .fp = fp,
+ .skip = skip,
+ .parm = parm,
+ .ret = 0,
+ };
+
+ dsos__for_each_dso(dsos, dsos__fprintf_buildid_cb, &args);
+ return args.ret;
+}
+
+struct dsos__fprintf_cb_args {
+ FILE *fp;
+ size_t ret;
+};
+
+static int dsos__fprintf_cb(struct dso *dso, void *data)
+{
+ struct dsos__fprintf_cb_args *args = data;
+
+ args->ret += dso__fprintf(dso, args->fp);
+ return 0;
+}
+
+size_t dsos__fprintf(struct dsos *dsos, FILE *fp)
+{
+ struct dsos__fprintf_cb_args args = {
+ .fp = fp,
+ .ret = 0,
+ };
+
+ dsos__for_each_dso(dsos, dsos__fprintf_cb, &args);
+ return args.ret;
+}
+
+static int dsos__hit_all_cb(struct dso *dso, void *data __maybe_unused)
+{
+ dso__set_hit(dso);
+ return 0;
+}
+
+int dsos__hit_all(struct dsos *dsos)
+{
+ return dsos__for_each_dso(dsos, dsos__hit_all_cb, NULL);
+}
+
+struct dso *dsos__findnew_module_dso(struct dsos *dsos,
+ struct machine *machine,
+ struct kmod_path *m,
+ const char *filename)
+{
+ struct dso *dso;
+
+ down_write(&dsos->lock);
+
+ dso = __dsos__find_id(dsos, m->name, &dso_id_empty, /*cmp_short=*/true,
+ /*write_locked=*/true);
+ if (dso) {
+ up_write(&dsos->lock);
+ return dso;
+ }
+ /*
+ * Failed to find the dso so create it. Change the name before adding it
+ * to the array, to avoid unnecessary sorts and potential locking
+ * issues.
+ */
+ dso = dso__new_id(m->name, /*id=*/NULL);
+ if (!dso) {
+ up_write(&dsos->lock);
+ return NULL;
+ }
+ dso__set_basename(dso);
+ dso__set_module_info(dso, m, machine);
+ dso__set_long_name(dso, strdup(filename), true);
+ dso__set_kernel(dso, DSO_SPACE__KERNEL);
+ __dsos__add(dsos, dso);
+
+ up_write(&dsos->lock);
+ return dso;
+}
+
+static int dsos__find_kernel_dso_cb(struct dso *dso, void *data)
+{
+ struct dso **res = data;
+ /*
+ * The cpumode passed to is_kernel_module is not the cpumode of *this*
+ * event. If we insist on passing correct cpumode to is_kernel_module,
+ * we should record the cpumode when we adding this dso to the linked
+ * list.
+ *
+ * However we don't really need passing correct cpumode. We know the
+ * correct cpumode must be kernel mode (if not, we should not link it
+ * onto kernel_dsos list).
+ *
+ * Therefore, we pass PERF_RECORD_MISC_CPUMODE_UNKNOWN.
+ * is_kernel_module() treats it as a kernel cpumode.
+ */
+ if (!dso__kernel(dso) ||
+ is_kernel_module(dso__long_name(dso), PERF_RECORD_MISC_CPUMODE_UNKNOWN))
+ return 0;
+
+ *res = dso__get(dso);
+ return 1;
+}
+
+struct dso *dsos__find_kernel_dso(struct dsos *dsos)
+{
+ struct dso *res = NULL;
+
+ dsos__for_each_dso(dsos, dsos__find_kernel_dso_cb, &res);
+ return res;
+}
+
+int dsos__for_each_dso(struct dsos *dsos, int (*cb)(struct dso *dso, void *data), void *data)
+{
+ int err;
+
+ down_read(&dsos->lock);
+ err = __dsos__for_each_dso(dsos, cb, data);
+ up_read(&dsos->lock);
+ return err;
+}