| // SPDX-License-Identifier: GPL-2.0 | |
| #include <elf.h> | |
| #include <errno.h> | |
| #include <fcntl.h> | |
| #include <stdlib.h> | |
| #include <stdio.h> | |
| #include <string.h> | |
| #include <linux/kernel.h> | |
| #include <linux/zalloc.h> | |
| #include "dso.h" | |
| #include "session.h" | |
| #include "thread.h" | |
| #include "thread-stack.h" | |
| #include "debug.h" | |
| #include "namespaces.h" | |
| #include "comm.h" | |
| #include "map.h" | |
| #include "symbol.h" | |
| #include "unwind.h" | |
| #include "callchain.h" | |
| #include "dwarf-regs.h" | |
| #include <api/fs/fs.h> | |
| int thread__init_maps(struct thread *thread, struct machine *machine) | |
| { | |
| pid_t pid = thread__pid(thread); | |
| if (pid == thread__tid(thread) || pid == -1) { | |
| thread__set_maps(thread, maps__new(machine)); | |
| } else { | |
| struct thread *leader = machine__findnew_thread(machine, pid, pid); | |
| if (leader) { | |
| thread__set_maps(thread, maps__get(thread__maps(leader))); | |
| thread__put(leader); | |
| } | |
| } | |
| return thread__maps(thread) ? 0 : -1; | |
| } | |
| struct thread *thread__new(pid_t pid, pid_t tid) | |
| NO_THREAD_SAFETY_ANALYSIS /* Allocation/creation is inherently single threaded. */ | |
| { | |
| RC_STRUCT(thread) *_thread = zalloc(sizeof(*_thread)); | |
| struct thread *thread; | |
| if (ADD_RC_CHK(thread, _thread) != NULL) { | |
| struct comm *comm; | |
| char comm_str[32]; | |
| thread__set_pid(thread, pid); | |
| thread__set_tid(thread, tid); | |
| thread__set_ppid(thread, -1); | |
| thread__set_cpu(thread, -1); | |
| thread__set_guest_cpu(thread, -1); | |
| thread__set_e_machine(thread, EM_NONE); | |
| thread__set_e_is_big_endian(thread, false); | |
| thread__set_lbr_stitch_enable(thread, false); | |
| INIT_LIST_HEAD(thread__namespaces_list(thread)); | |
| INIT_LIST_HEAD(thread__comm_list(thread)); | |
| init_rwsem(thread__namespaces_lock(thread)); | |
| init_rwsem(thread__comm_lock(thread)); | |
| snprintf(comm_str, sizeof(comm_str), ":%d", tid); | |
| comm = comm__new(comm_str, 0, false); | |
| if (!comm) | |
| goto err_thread; | |
| list_add(&comm->list, thread__comm_list(thread)); | |
| refcount_set(thread__refcnt(thread), 1); | |
| /* Thread holds first ref to nsdata. */ | |
| RC_CHK_ACCESS(thread)->nsinfo = nsinfo__new(pid); | |
| srccode_state_init(thread__srccode_state(thread)); | |
| } | |
| return thread; | |
| err_thread: | |
| thread__delete(thread); | |
| return NULL; | |
| } | |
| static void (*thread__priv_destructor)(void *priv); | |
| void thread__set_priv_destructor(void (*destructor)(void *priv)) | |
| { | |
| assert(thread__priv_destructor == NULL); | |
| thread__priv_destructor = destructor; | |
| } | |
| void thread__delete(struct thread *thread) | |
| { | |
| struct namespaces *namespaces, *tmp_namespaces; | |
| struct comm *comm, *tmp_comm; | |
| thread_stack__free(thread); | |
| if (thread__maps(thread)) { | |
| maps__put(thread__maps(thread)); | |
| thread__set_maps(thread, NULL); | |
| } | |
| down_write(thread__namespaces_lock(thread)); | |
| list_for_each_entry_safe(namespaces, tmp_namespaces, | |
| thread__namespaces_list(thread), list) { | |
| list_del_init(&namespaces->list); | |
| namespaces__free(namespaces); | |
| } | |
| up_write(thread__namespaces_lock(thread)); | |
| down_write(thread__comm_lock(thread)); | |
| list_for_each_entry_safe(comm, tmp_comm, thread__comm_list(thread), list) { | |
| list_del_init(&comm->list); | |
| comm__free(comm); | |
| } | |
| up_write(thread__comm_lock(thread)); | |
| nsinfo__zput(RC_CHK_ACCESS(thread)->nsinfo); | |
| srccode_state_free(thread__srccode_state(thread)); | |
| exit_rwsem(thread__namespaces_lock(thread)); | |
| exit_rwsem(thread__comm_lock(thread)); | |
| thread__free_stitch_list(thread); | |
| if (thread__priv_destructor) | |
| thread__priv_destructor(thread__priv(thread)); | |
| RC_CHK_FREE(thread); | |
| } | |
| struct thread *thread__get(struct thread *thread) | |
| { | |
| struct thread *result; | |
| if (RC_CHK_GET(result, thread)) | |
| refcount_inc(thread__refcnt(thread)); | |
| return result; | |
| } | |
| void thread__put(struct thread *thread) | |
| { | |
| if (thread && refcount_dec_and_test(thread__refcnt(thread))) | |
| thread__delete(thread); | |
| else | |
| RC_CHK_PUT(thread); | |
| } | |
| static struct namespaces *__thread__namespaces(struct thread *thread) | |
| { | |
| if (list_empty(thread__namespaces_list(thread))) | |
| return NULL; | |
| return list_first_entry(thread__namespaces_list(thread), struct namespaces, list); | |
| } | |
| struct namespaces *thread__namespaces(struct thread *thread) | |
| { | |
| struct namespaces *ns; | |
| down_read(thread__namespaces_lock(thread)); | |
| ns = __thread__namespaces(thread); | |
| up_read(thread__namespaces_lock(thread)); | |
| return ns; | |
| } | |
| static int __thread__set_namespaces(struct thread *thread, u64 timestamp, | |
| struct perf_record_namespaces *event) | |
| { | |
| struct namespaces *new, *curr = __thread__namespaces(thread); | |
| new = namespaces__new(event); | |
| if (!new) | |
| return -ENOMEM; | |
| list_add(&new->list, thread__namespaces_list(thread)); | |
| if (timestamp && curr) { | |
| /* | |
| * setns syscall must have changed few or all the namespaces | |
| * of this thread. Update end time for the namespaces | |
| * previously used. | |
| */ | |
| curr = list_next_entry(new, list); | |
| curr->end_time = timestamp; | |
| } | |
| return 0; | |
| } | |
| int thread__set_namespaces(struct thread *thread, u64 timestamp, | |
| struct perf_record_namespaces *event) | |
| { | |
| int ret; | |
| down_write(thread__namespaces_lock(thread)); | |
| ret = __thread__set_namespaces(thread, timestamp, event); | |
| up_write(thread__namespaces_lock(thread)); | |
| return ret; | |
| } | |
| static struct comm *__thread__comm(struct thread *thread) | |
| SHARED_LOCKS_REQUIRED(thread__comm_lock(thread)) | |
| { | |
| if (list_empty(thread__comm_list(thread))) | |
| return NULL; | |
| return list_first_entry(thread__comm_list(thread), struct comm, list); | |
| } | |
| struct comm *thread__comm(struct thread *thread) | |
| { | |
| struct comm *res = NULL; | |
| down_read(thread__comm_lock(thread)); | |
| res = __thread__comm(thread); | |
| up_read(thread__comm_lock(thread)); | |
| return res; | |
| } | |
| struct comm *thread__exec_comm(struct thread *thread) | |
| { | |
| struct comm *comm, *last = NULL, *second_last = NULL; | |
| down_read(thread__comm_lock(thread)); | |
| list_for_each_entry(comm, thread__comm_list(thread), list) { | |
| if (comm->exec) { | |
| up_read(thread__comm_lock(thread)); | |
| return comm; | |
| } | |
| second_last = last; | |
| last = comm; | |
| } | |
| up_read(thread__comm_lock(thread)); | |
| /* | |
| * 'last' with no start time might be the parent's comm of a synthesized | |
| * thread (created by processing a synthesized fork event). For a main | |
| * thread, that is very probably wrong. Prefer a later comm to avoid | |
| * that case. | |
| */ | |
| if (second_last && !last->start && thread__pid(thread) == thread__tid(thread)) | |
| return second_last; | |
| return last; | |
| } | |
| static int ____thread__set_comm(struct thread *thread, const char *str, | |
| u64 timestamp, bool exec) | |
| EXCLUSIVE_LOCKS_REQUIRED(thread__comm_lock(thread)) | |
| { | |
| struct comm *new, *curr = __thread__comm(thread); | |
| /* Override the default :tid entry */ | |
| if (!thread__comm_set(thread)) { | |
| int err = comm__override(curr, str, timestamp, exec); | |
| if (err) | |
| return err; | |
| } else { | |
| new = comm__new(str, timestamp, exec); | |
| if (!new) | |
| return -ENOMEM; | |
| list_add(&new->list, thread__comm_list(thread)); | |
| if (exec) | |
| unwind__flush_access(thread__maps(thread)); | |
| } | |
| thread__set_comm_set(thread, true); | |
| return 0; | |
| } | |
| int __thread__set_comm(struct thread *thread, const char *str, u64 timestamp, | |
| bool exec) | |
| { | |
| int ret; | |
| down_write(thread__comm_lock(thread)); | |
| ret = ____thread__set_comm(thread, str, timestamp, exec); | |
| up_write(thread__comm_lock(thread)); | |
| return ret; | |
| } | |
| int thread__set_comm_from_proc(struct thread *thread) | |
| { | |
| char path[64]; | |
| char *comm = NULL; | |
| size_t sz; | |
| int err = -1; | |
| if (!(snprintf(path, sizeof(path), "%d/task/%d/comm", | |
| thread__pid(thread), thread__tid(thread)) >= (int)sizeof(path)) && | |
| procfs__read_str(path, &comm, &sz) == 0) { | |
| /* sz==0: read got nothing, e.g. race during exit teardown */ | |
| if (sz == 0) { | |
| free(comm); | |
| return -1; | |
| } | |
| comm[sz - 1] = '\0'; | |
| err = thread__set_comm(thread, comm, 0); | |
| } | |
| return err; | |
| } | |
| static const char *__thread__comm_str(struct thread *thread) | |
| SHARED_LOCKS_REQUIRED(thread__comm_lock(thread)) | |
| { | |
| const struct comm *comm = __thread__comm(thread); | |
| if (!comm) | |
| return NULL; | |
| return comm__str(comm); | |
| } | |
| const char *thread__comm_str(struct thread *thread) | |
| { | |
| const char *str; | |
| down_read(thread__comm_lock(thread)); | |
| str = __thread__comm_str(thread); | |
| up_read(thread__comm_lock(thread)); | |
| return str; | |
| } | |
| static int __thread__comm_len(struct thread *thread, const char *comm) | |
| { | |
| if (!comm) | |
| return 0; | |
| thread__set_comm_len(thread, strlen(comm)); | |
| return thread__var_comm_len(thread); | |
| } | |
| /* CHECKME: it should probably better return the max comm len from its comm list */ | |
| int thread__comm_len(struct thread *thread) | |
| { | |
| int comm_len = thread__var_comm_len(thread); | |
| if (!comm_len) { | |
| const char *comm; | |
| down_read(thread__comm_lock(thread)); | |
| comm = __thread__comm_str(thread); | |
| comm_len = __thread__comm_len(thread, comm); | |
| up_read(thread__comm_lock(thread)); | |
| } | |
| return comm_len; | |
| } | |
| size_t thread__fprintf(struct thread *thread, FILE *fp) | |
| { | |
| return fprintf(fp, "Thread %d %s\n", thread__tid(thread), thread__comm_str(thread)) + | |
| maps__fprintf(thread__maps(thread), fp); | |
| } | |
| int thread__insert_map(struct thread *thread, struct map *map) | |
| { | |
| int ret; | |
| uint16_t e_machine; | |
| ret = maps__fixup_overlap_and_insert(thread__maps(thread), map); | |
| if (ret) | |
| return ret; | |
| e_machine = thread__e_machine(thread, /*machine=*/NULL, /*e_flags=*/NULL); | |
| return unwind__prepare_access(thread__maps(thread), e_machine); | |
| } | |
| static int thread__prepare_access(struct thread *thread) | |
| { | |
| uint16_t e_machine = thread__e_machine(thread, /*machine=*/NULL, /*e_flags=*/NULL); | |
| return unwind__prepare_access(thread__maps(thread), e_machine); | |
| } | |
| static int thread__clone_maps(struct thread *thread, struct thread *parent, bool do_maps_clone) | |
| { | |
| /* This is new thread, we share map groups for process. */ | |
| if (thread__pid(thread) == thread__pid(parent)) | |
| return thread__prepare_access(thread); | |
| if (maps__equal(thread__maps(thread), thread__maps(parent))) { | |
| pr_debug("broken map groups on thread %d/%d parent %d/%d\n", | |
| thread__pid(thread), thread__tid(thread), | |
| thread__pid(parent), thread__tid(parent)); | |
| return 0; | |
| } | |
| /* But this one is new process, copy maps. */ | |
| return do_maps_clone ? maps__copy_from(thread__maps(thread), thread__maps(parent)) : 0; | |
| } | |
| int thread__fork(struct thread *thread, struct thread *parent, u64 timestamp, bool do_maps_clone) | |
| { | |
| if (thread__comm_set(parent)) { | |
| const char *comm = thread__comm_str(parent); | |
| int err; | |
| if (!comm) | |
| return -ENOMEM; | |
| err = thread__set_comm(thread, comm, timestamp); | |
| if (err) | |
| return err; | |
| } | |
| thread__set_ppid(thread, thread__tid(parent)); | |
| return thread__clone_maps(thread, parent, do_maps_clone); | |
| } | |
| void thread__find_cpumode_addr_location(struct thread *thread, u64 addr, | |
| bool symbols, struct addr_location *al) | |
| { | |
| size_t i; | |
| const u8 cpumodes[] = { | |
| PERF_RECORD_MISC_USER, | |
| PERF_RECORD_MISC_KERNEL, | |
| PERF_RECORD_MISC_GUEST_USER, | |
| PERF_RECORD_MISC_GUEST_KERNEL | |
| }; | |
| for (i = 0; i < ARRAY_SIZE(cpumodes); i++) { | |
| if (symbols) | |
| thread__find_symbol(thread, cpumodes[i], addr, al); | |
| else | |
| thread__find_map(thread, cpumodes[i], addr, al); | |
| if (al->map) | |
| break; | |
| } | |
| } | |
| static uint16_t read_proc_e_machine_for_pid(pid_t pid, uint32_t *e_flags, bool *is_big_endian) | |
| { | |
| char path[6 /* "/proc/" */ + 11 /* max length of pid */ + 5 /* "/exe\0" */]; | |
| int fd; | |
| uint16_t e_machine = EM_NONE; | |
| snprintf(path, sizeof(path), "/proc/%d/exe", pid); | |
| fd = open(path, O_RDONLY); | |
| if (fd >= 0) { | |
| e_machine = dso__read_e_machine_endian(/*optional_dso=*/NULL, fd, e_flags, | |
| is_big_endian); | |
| close(fd); | |
| } | |
| return e_machine; | |
| } | |
| struct thread__e_machine_callback_args { | |
| struct machine *machine; | |
| uint32_t e_flags; | |
| uint16_t e_machine; | |
| bool is_big_endian; | |
| }; | |
| static int thread__e_machine_callback(struct map *map, void *_args) | |
| { | |
| struct thread__e_machine_callback_args *args = _args; | |
| struct dso *dso = map__dso(map); | |
| if (!dso) | |
| return 0; // No dso, continue search. | |
| args->e_machine = | |
| dso__e_machine_endian(dso, args->machine, &args->e_flags, &args->is_big_endian); | |
| return args->e_machine != EM_NONE ? 1 /* stop search */ : 0 /* continue search */; | |
| } | |
| uint16_t thread__e_machine_endian(struct thread *thread, struct machine *machine, uint32_t *e_flags, | |
| bool *is_big_endian) | |
| { | |
| pid_t tid, pid; | |
| uint16_t e_machine; | |
| uint32_t local_e_flags = 0; | |
| struct thread__e_machine_callback_args args; | |
| if (!thread) { | |
| if (is_big_endian) { | |
| *is_big_endian = perf_arch_is_big_endian( | |
| machine && machine->env ? perf_env__arch(machine->env) : NULL); | |
| } | |
| return perf_env__e_machine(machine ? machine->env : NULL, e_flags); | |
| } | |
| e_machine = RC_CHK_ACCESS(thread)->e_machine; | |
| args.machine = machine; | |
| args.e_flags = 0; | |
| args.e_machine = EM_NONE; | |
| args.is_big_endian = false; | |
| if (e_machine != EM_NONE) { | |
| if (e_flags) | |
| *e_flags = thread__e_flags(thread); | |
| if (is_big_endian) | |
| *is_big_endian = thread__e_is_big_endian(thread); | |
| return e_machine; | |
| } | |
| if (machine == NULL) { | |
| struct maps *maps = thread__maps(thread); | |
| machine = maps__machine(maps); | |
| args.machine = machine; | |
| } | |
| tid = thread__tid(thread); | |
| pid = thread__pid(thread); | |
| if (pid != tid) { | |
| struct thread *parent = machine__findnew_thread(machine, pid, pid); | |
| if (parent) { | |
| e_machine = thread__e_machine_endian(parent, machine, &local_e_flags, | |
| &args.is_big_endian); | |
| thread__put(parent); | |
| goto out; | |
| } | |
| /* Something went wrong, fallback. */ | |
| } | |
| /* Reading on the PID thread. First try to find from the maps. */ | |
| maps__for_each_map(thread__maps(thread), thread__e_machine_callback, &args); | |
| if (args.e_machine != EM_NONE) { | |
| e_machine = args.e_machine; | |
| local_e_flags = args.e_flags; | |
| } else { | |
| /* Maps failed, perhaps we're live with map events disabled. */ | |
| bool is_live = machine->machines == NULL; | |
| if (!is_live) { | |
| /* Check if the session has a data file. */ | |
| struct perf_session *session = container_of(machine->machines, | |
| struct perf_session, | |
| machines); | |
| is_live = !!session->data; | |
| } | |
| /* Read from /proc/pid/exe if live. */ | |
| if (is_live) { | |
| e_machine = read_proc_e_machine_for_pid(pid, &local_e_flags, | |
| &args.is_big_endian); | |
| } else if (machine && machine->env) { | |
| /* Offline analysis: fallback to environment metadata. */ | |
| e_machine = perf_env__e_machine(machine->env, &local_e_flags); | |
| args.is_big_endian = perf_arch_is_big_endian(perf_env__arch(machine->env)); | |
| } | |
| } | |
| out: | |
| if (e_machine != EM_NONE) { | |
| thread__set_e_flags(thread, local_e_flags); | |
| thread__set_e_is_big_endian(thread, args.is_big_endian); | |
| thread__set_e_machine(thread, e_machine); | |
| if (is_big_endian) | |
| *is_big_endian = args.is_big_endian; | |
| } else { | |
| e_machine = EM_HOST; | |
| local_e_flags = EF_HOST; | |
| if (is_big_endian) | |
| *is_big_endian = (__BYTE_ORDER__ == __ORDER_BIG_ENDIAN__); | |
| } | |
| if (e_flags) | |
| *e_flags = local_e_flags; | |
| return e_machine; | |
| } | |
| struct thread *thread__main_thread(struct machine *machine, struct thread *thread) | |
| { | |
| if (thread__pid(thread) == thread__tid(thread)) | |
| return thread__get(thread); | |
| if (thread__pid(thread) == -1) | |
| return NULL; | |
| return machine__find_thread(machine, thread__pid(thread), thread__pid(thread)); | |
| } | |
| int thread__memcpy(struct thread *thread, struct machine *machine, | |
| void *buf, u64 ip, int len, bool *is64bit) | |
| { | |
| u8 cpumode = PERF_RECORD_MISC_USER; | |
| struct addr_location al; | |
| struct dso *dso; | |
| long offset; | |
| if (machine__kernel_ip(machine, ip)) | |
| cpumode = PERF_RECORD_MISC_KERNEL; | |
| addr_location__init(&al); | |
| if (!thread__find_map(thread, cpumode, ip, &al)) { | |
| addr_location__exit(&al); | |
| return -1; | |
| } | |
| dso = map__dso(al.map); | |
| if (!dso || dso__data(dso)->status == DSO_DATA_STATUS_ERROR || map__load(al.map) < 0) { | |
| addr_location__exit(&al); | |
| return -1; | |
| } | |
| offset = map__map_ip(al.map, ip); | |
| if (is64bit) | |
| *is64bit = dso__is_64_bit(dso); | |
| addr_location__exit(&al); | |
| return dso__data_read_offset(dso, machine, offset, buf, len); | |
| } | |
| void thread__free_stitch_list(struct thread *thread) | |
| { | |
| struct lbr_stitch *lbr_stitch = thread__lbr_stitch(thread); | |
| struct stitch_list *pos, *tmp; | |
| if (!lbr_stitch) | |
| return; | |
| list_for_each_entry_safe(pos, tmp, &lbr_stitch->lists, node) { | |
| map_symbol__exit(&pos->cursor.ms); | |
| list_del_init(&pos->node); | |
| free(pos); | |
| } | |
| list_for_each_entry_safe(pos, tmp, &lbr_stitch->free_lists, node) { | |
| list_del_init(&pos->node); | |
| free(pos); | |
| } | |
| for (unsigned int i = 0 ; i < lbr_stitch->prev_lbr_cursor_size; i++) | |
| map_symbol__exit(&lbr_stitch->prev_lbr_cursor[i].ms); | |
| zfree(&lbr_stitch->prev_lbr_cursor); | |
| free(thread__lbr_stitch(thread)); | |
| thread__set_lbr_stitch(thread, NULL); | |
| } |