Author | Tokens | Token Proportion | Commits | Commit Proportion |
---|---|---|---|---|
Adrian Hunter | 5544 | 96.28% | 25 | 53.19% |
Arnaldo Carvalho de Melo | 102 | 1.77% | 10 | 21.28% |
Ian Rogers | 61 | 1.06% | 2 | 4.26% |
Frédéric Weisbecker | 27 | 0.47% | 3 | 6.38% |
David Ahern | 12 | 0.21% | 2 | 4.26% |
Ingo Molnar | 6 | 0.10% | 1 | 2.13% |
Jiri Olsa | 2 | 0.03% | 1 | 2.13% |
Thomas Gleixner | 2 | 0.03% | 1 | 2.13% |
Chris Phlipot | 1 | 0.02% | 1 | 2.13% |
Petr Pavlu | 1 | 0.02% | 1 | 2.13% |
Total | 5758 | 47 |
// SPDX-License-Identifier: GPL-2.0-only /* * thread-stack.c: Synthesize a thread's stack using call / return events * Copyright (c) 2014, Intel Corporation. */ #include <linux/rbtree.h> #include <linux/list.h> #include <linux/log2.h> #include <linux/zalloc.h> #include <errno.h> #include <stdlib.h> #include <string.h> #include "thread.h" #include "event.h" #include "machine.h" #include "env.h" #include "debug.h" #include "symbol.h" #include "comm.h" #include "call-path.h" #include "thread-stack.h" #define STACK_GROWTH 2048 /* * State of retpoline detection. * * RETPOLINE_NONE: no retpoline detection * X86_RETPOLINE_POSSIBLE: x86 retpoline possible * X86_RETPOLINE_DETECTED: x86 retpoline detected */ enum retpoline_state_t { RETPOLINE_NONE, X86_RETPOLINE_POSSIBLE, X86_RETPOLINE_DETECTED, }; /** * struct thread_stack_entry - thread stack entry. * @ret_addr: return address * @timestamp: timestamp (if known) * @ref: external reference (e.g. db_id of sample) * @branch_count: the branch count when the entry was created * @insn_count: the instruction count when the entry was created * @cyc_count the cycle count when the entry was created * @db_id: id used for db-export * @cp: call path * @no_call: a 'call' was not seen * @trace_end: a 'call' but trace ended * @non_call: a branch but not a 'call' to the start of a different symbol */ struct thread_stack_entry { u64 ret_addr; u64 timestamp; u64 ref; u64 branch_count; u64 insn_count; u64 cyc_count; u64 db_id; struct call_path *cp; bool no_call; bool trace_end; bool non_call; }; /** * struct thread_stack - thread stack constructed from 'call' and 'return' * branch samples. * @stack: array that holds the stack * @cnt: number of entries in the stack * @sz: current maximum stack size * @trace_nr: current trace number * @branch_count: running branch count * @insn_count: running instruction count * @cyc_count running cycle count * @kernel_start: kernel start address * @last_time: last timestamp * @crp: call/return processor * @comm: current comm * @arr_sz: size of array if this is the first element of an array * @rstate: used to detect retpolines * @br_stack_rb: branch stack (ring buffer) * @br_stack_sz: maximum branch stack size * @br_stack_pos: current position in @br_stack_rb * @mispred_all: mark all branches as mispredicted */ struct thread_stack { struct thread_stack_entry *stack; size_t cnt; size_t sz; u64 trace_nr; u64 branch_count; u64 insn_count; u64 cyc_count; u64 kernel_start; u64 last_time; struct call_return_processor *crp; struct comm *comm; unsigned int arr_sz; enum retpoline_state_t rstate; struct branch_stack *br_stack_rb; unsigned int br_stack_sz; unsigned int br_stack_pos; bool mispred_all; }; /* * Assume pid == tid == 0 identifies the idle task as defined by * perf_session__register_idle_thread(). The idle task is really 1 task per cpu, * and therefore requires a stack for each cpu. */ static inline bool thread_stack__per_cpu(struct thread *thread) { return !(thread__tid(thread) || thread__pid(thread)); } static int thread_stack__grow(struct thread_stack *ts) { struct thread_stack_entry *new_stack; size_t sz, new_sz; new_sz = ts->sz + STACK_GROWTH; sz = new_sz * sizeof(struct thread_stack_entry); new_stack = realloc(ts->stack, sz); if (!new_stack) return -ENOMEM; ts->stack = new_stack; ts->sz = new_sz; return 0; } static int thread_stack__init(struct thread_stack *ts, struct thread *thread, struct call_return_processor *crp, bool callstack, unsigned int br_stack_sz) { int err; if (callstack) { err = thread_stack__grow(ts); if (err) return err; } if (br_stack_sz) { size_t sz = sizeof(struct branch_stack); sz += br_stack_sz * sizeof(struct branch_entry); ts->br_stack_rb = zalloc(sz); if (!ts->br_stack_rb) return -ENOMEM; ts->br_stack_sz = br_stack_sz; } if (thread__maps(thread) && maps__machine(thread__maps(thread))) { struct machine *machine = maps__machine(thread__maps(thread)); const char *arch = perf_env__arch(machine->env); ts->kernel_start = machine__kernel_start(machine); if (!strcmp(arch, "x86")) ts->rstate = X86_RETPOLINE_POSSIBLE; } else { ts->kernel_start = 1ULL << 63; } ts->crp = crp; return 0; } static struct thread_stack *thread_stack__new(struct thread *thread, int cpu, struct call_return_processor *crp, bool callstack, unsigned int br_stack_sz) { struct thread_stack *ts = thread__ts(thread), *new_ts; unsigned int old_sz = ts ? ts->arr_sz : 0; unsigned int new_sz = 1; if (thread_stack__per_cpu(thread) && cpu > 0) new_sz = roundup_pow_of_two(cpu + 1); if (!ts || new_sz > old_sz) { new_ts = calloc(new_sz, sizeof(*ts)); if (!new_ts) return NULL; if (ts) memcpy(new_ts, ts, old_sz * sizeof(*ts)); new_ts->arr_sz = new_sz; free(thread__ts(thread)); thread__set_ts(thread, new_ts); ts = new_ts; } if (thread_stack__per_cpu(thread) && cpu > 0 && (unsigned int)cpu < ts->arr_sz) ts += cpu; if (!ts->stack && thread_stack__init(ts, thread, crp, callstack, br_stack_sz)) return NULL; return ts; } static struct thread_stack *thread__cpu_stack(struct thread *thread, int cpu) { struct thread_stack *ts = thread__ts(thread); if (cpu < 0) cpu = 0; if (!ts || (unsigned int)cpu >= ts->arr_sz) return NULL; ts += cpu; if (!ts->stack) return NULL; return ts; } static inline struct thread_stack *thread__stack(struct thread *thread, int cpu) { if (!thread) return NULL; if (thread_stack__per_cpu(thread)) return thread__cpu_stack(thread, cpu); return thread__ts(thread); } static int thread_stack__push(struct thread_stack *ts, u64 ret_addr, bool trace_end) { int err = 0; if (ts->cnt == ts->sz) { err = thread_stack__grow(ts); if (err) { pr_warning("Out of memory: discarding thread stack\n"); ts->cnt = 0; } } ts->stack[ts->cnt].trace_end = trace_end; ts->stack[ts->cnt++].ret_addr = ret_addr; return err; } static void thread_stack__pop(struct thread_stack *ts, u64 ret_addr) { size_t i; /* * In some cases there may be functions which are not seen to return. * For example when setjmp / longjmp has been used. Or the perf context * switch in the kernel which doesn't stop and start tracing in exactly * the same code path. When that happens the return address will be * further down the stack. If the return address is not found at all, * we assume the opposite (i.e. this is a return for a call that wasn't * seen for some reason) and leave the stack alone. */ for (i = ts->cnt; i; ) { if (ts->stack[--i].ret_addr == ret_addr) { ts->cnt = i; return; } } } static void thread_stack__pop_trace_end(struct thread_stack *ts) { size_t i; for (i = ts->cnt; i; ) { if (ts->stack[--i].trace_end) ts->cnt = i; else return; } } static bool thread_stack__in_kernel(struct thread_stack *ts) { if (!ts->cnt) return false; return ts->stack[ts->cnt - 1].cp->in_kernel; } static int thread_stack__call_return(struct thread *thread, struct thread_stack *ts, size_t idx, u64 timestamp, u64 ref, bool no_return) { struct call_return_processor *crp = ts->crp; struct thread_stack_entry *tse; struct call_return cr = { .thread = thread, .comm = ts->comm, .db_id = 0, }; u64 *parent_db_id; tse = &ts->stack[idx]; cr.cp = tse->cp; cr.call_time = tse->timestamp; cr.return_time = timestamp; cr.branch_count = ts->branch_count - tse->branch_count; cr.insn_count = ts->insn_count - tse->insn_count; cr.cyc_count = ts->cyc_count - tse->cyc_count; cr.db_id = tse->db_id; cr.call_ref = tse->ref; cr.return_ref = ref; if (tse->no_call) cr.flags |= CALL_RETURN_NO_CALL; if (no_return) cr.flags |= CALL_RETURN_NO_RETURN; if (tse->non_call) cr.flags |= CALL_RETURN_NON_CALL; /* * The parent db_id must be assigned before exporting the child. Note * it is not possible to export the parent first because its information * is not yet complete because its 'return' has not yet been processed. */ parent_db_id = idx ? &(tse - 1)->db_id : NULL; return crp->process(&cr, parent_db_id, crp->data); } static int __thread_stack__flush(struct thread *thread, struct thread_stack *ts) { struct call_return_processor *crp = ts->crp; int err; if (!crp) { ts->cnt = 0; ts->br_stack_pos = 0; if (ts->br_stack_rb) ts->br_stack_rb->nr = 0; return 0; } while (ts->cnt) { err = thread_stack__call_return(thread, ts, --ts->cnt, ts->last_time, 0, true); if (err) { pr_err("Error flushing thread stack!\n"); ts->cnt = 0; return err; } } return 0; } int thread_stack__flush(struct thread *thread) { struct thread_stack *ts = thread__ts(thread); unsigned int pos; int err = 0; if (ts) { for (pos = 0; pos < ts->arr_sz; pos++) { int ret = __thread_stack__flush(thread, ts + pos); if (ret) err = ret; } } return err; } static void thread_stack__update_br_stack(struct thread_stack *ts, u32 flags, u64 from_ip, u64 to_ip) { struct branch_stack *bs = ts->br_stack_rb; struct branch_entry *be; if (!ts->br_stack_pos) ts->br_stack_pos = ts->br_stack_sz; ts->br_stack_pos -= 1; be = &bs->entries[ts->br_stack_pos]; be->from = from_ip; be->to = to_ip; be->flags.value = 0; be->flags.abort = !!(flags & PERF_IP_FLAG_TX_ABORT); be->flags.in_tx = !!(flags & PERF_IP_FLAG_IN_TX); /* No support for mispredict */ be->flags.mispred = ts->mispred_all; if (bs->nr < ts->br_stack_sz) bs->nr += 1; } int thread_stack__event(struct thread *thread, int cpu, u32 flags, u64 from_ip, u64 to_ip, u16 insn_len, u64 trace_nr, bool callstack, unsigned int br_stack_sz, bool mispred_all) { struct thread_stack *ts = thread__stack(thread, cpu); if (!thread) return -EINVAL; if (!ts) { ts = thread_stack__new(thread, cpu, NULL, callstack, br_stack_sz); if (!ts) { pr_warning("Out of memory: no thread stack\n"); return -ENOMEM; } ts->trace_nr = trace_nr; ts->mispred_all = mispred_all; } /* * When the trace is discontinuous, the trace_nr changes. In that case * the stack might be completely invalid. Better to report nothing than * to report something misleading, so flush the stack. */ if (trace_nr != ts->trace_nr) { if (ts->trace_nr) __thread_stack__flush(thread, ts); ts->trace_nr = trace_nr; } if (br_stack_sz) thread_stack__update_br_stack(ts, flags, from_ip, to_ip); /* * Stop here if thread_stack__process() is in use, or not recording call * stack. */ if (ts->crp || !callstack) return 0; if (flags & PERF_IP_FLAG_CALL) { u64 ret_addr; if (!to_ip) return 0; ret_addr = from_ip + insn_len; if (ret_addr == to_ip) return 0; /* Zero-length calls are excluded */ return thread_stack__push(ts, ret_addr, flags & PERF_IP_FLAG_TRACE_END); } else if (flags & PERF_IP_FLAG_TRACE_BEGIN) { /* * If the caller did not change the trace number (which would * have flushed the stack) then try to make sense of the stack. * Possibly, tracing began after returning to the current * address, so try to pop that. Also, do not expect a call made * when the trace ended, to return, so pop that. */ thread_stack__pop(ts, to_ip); thread_stack__pop_trace_end(ts); } else if ((flags & PERF_IP_FLAG_RETURN) && from_ip) { thread_stack__pop(ts, to_ip); } return 0; } void thread_stack__set_trace_nr(struct thread *thread, int cpu, u64 trace_nr) { struct thread_stack *ts = thread__stack(thread, cpu); if (!ts) return; if (trace_nr != ts->trace_nr) { if (ts->trace_nr) __thread_stack__flush(thread, ts); ts->trace_nr = trace_nr; } } static void __thread_stack__free(struct thread *thread, struct thread_stack *ts) { __thread_stack__flush(thread, ts); zfree(&ts->stack); zfree(&ts->br_stack_rb); } static void thread_stack__reset(struct thread *thread, struct thread_stack *ts) { unsigned int arr_sz = ts->arr_sz; __thread_stack__free(thread, ts); memset(ts, 0, sizeof(*ts)); ts->arr_sz = arr_sz; } void thread_stack__free(struct thread *thread) { struct thread_stack *ts = thread__ts(thread); unsigned int pos; if (ts) { for (pos = 0; pos < ts->arr_sz; pos++) __thread_stack__free(thread, ts + pos); free(thread__ts(thread)); thread__set_ts(thread, NULL); } } static inline u64 callchain_context(u64 ip, u64 kernel_start) { return ip < kernel_start ? PERF_CONTEXT_USER : PERF_CONTEXT_KERNEL; } void thread_stack__sample(struct thread *thread, int cpu, struct ip_callchain *chain, size_t sz, u64 ip, u64 kernel_start) { struct thread_stack *ts = thread__stack(thread, cpu); u64 context = callchain_context(ip, kernel_start); u64 last_context; size_t i, j; if (sz < 2) { chain->nr = 0; return; } chain->ips[0] = context; chain->ips[1] = ip; if (!ts) { chain->nr = 2; return; } last_context = context; for (i = 2, j = 1; i < sz && j <= ts->cnt; i++, j++) { ip = ts->stack[ts->cnt - j].ret_addr; context = callchain_context(ip, kernel_start); if (context != last_context) { if (i >= sz - 1) break; chain->ips[i++] = context; last_context = context; } chain->ips[i] = ip; } chain->nr = i; } /* * Hardware sample records, created some time after the event occurred, need to * have subsequent addresses removed from the call chain. */ void thread_stack__sample_late(struct thread *thread, int cpu, struct ip_callchain *chain, size_t sz, u64 sample_ip, u64 kernel_start) { struct thread_stack *ts = thread__stack(thread, cpu); u64 sample_context = callchain_context(sample_ip, kernel_start); u64 last_context, context, ip; size_t nr = 0, j; if (sz < 2) { chain->nr = 0; return; } if (!ts) goto out; /* * When tracing kernel space, kernel addresses occur at the top of the * call chain after the event occurred but before tracing stopped. * Skip them. */ for (j = 1; j <= ts->cnt; j++) { ip = ts->stack[ts->cnt - j].ret_addr; context = callchain_context(ip, kernel_start); if (context == PERF_CONTEXT_USER || (context == sample_context && ip == sample_ip)) break; } last_context = sample_ip; /* Use sample_ip as an invalid context */ for (; nr < sz && j <= ts->cnt; nr++, j++) { ip = ts->stack[ts->cnt - j].ret_addr; context = callchain_context(ip, kernel_start); if (context != last_context) { if (nr >= sz - 1) break; chain->ips[nr++] = context; last_context = context; } chain->ips[nr] = ip; } out: if (nr) { chain->nr = nr; } else { chain->ips[0] = sample_context; chain->ips[1] = sample_ip; chain->nr = 2; } } void thread_stack__br_sample(struct thread *thread, int cpu, struct branch_stack *dst, unsigned int sz) { struct thread_stack *ts = thread__stack(thread, cpu); const size_t bsz = sizeof(struct branch_entry); struct branch_stack *src; struct branch_entry *be; unsigned int nr; dst->nr = 0; if (!ts) return; src = ts->br_stack_rb; if (!src->nr) return; dst->nr = min((unsigned int)src->nr, sz); be = &dst->entries[0]; nr = min(ts->br_stack_sz - ts->br_stack_pos, (unsigned int)dst->nr); memcpy(be, &src->entries[ts->br_stack_pos], bsz * nr); if (src->nr >= ts->br_stack_sz) { sz -= nr; be = &dst->entries[nr]; nr = min(ts->br_stack_pos, sz); memcpy(be, &src->entries[0], bsz * ts->br_stack_pos); } } /* Start of user space branch entries */ static bool us_start(struct branch_entry *be, u64 kernel_start, bool *start) { if (!*start) *start = be->to && be->to < kernel_start; return *start; } /* * Start of branch entries after the ip fell in between 2 branches, or user * space branch entries. */ static bool ks_start(struct branch_entry *be, u64 sample_ip, u64 kernel_start, bool *start, struct branch_entry *nb) { if (!*start) { *start = (nb && sample_ip >= be->to && sample_ip <= nb->from) || be->from < kernel_start || (be->to && be->to < kernel_start); } return *start; } /* * Hardware sample records, created some time after the event occurred, need to * have subsequent addresses removed from the branch stack. */ void thread_stack__br_sample_late(struct thread *thread, int cpu, struct branch_stack *dst, unsigned int sz, u64 ip, u64 kernel_start) { struct thread_stack *ts = thread__stack(thread, cpu); struct branch_entry *d, *s, *spos, *ssz; struct branch_stack *src; unsigned int nr = 0; bool start = false; dst->nr = 0; if (!ts) return; src = ts->br_stack_rb; if (!src->nr) return; spos = &src->entries[ts->br_stack_pos]; ssz = &src->entries[ts->br_stack_sz]; d = &dst->entries[0]; s = spos; if (ip < kernel_start) { /* * User space sample: start copying branch entries when the * branch is in user space. */ for (s = spos; s < ssz && nr < sz; s++) { if (us_start(s, kernel_start, &start)) { *d++ = *s; nr += 1; } } if (src->nr >= ts->br_stack_sz) { for (s = &src->entries[0]; s < spos && nr < sz; s++) { if (us_start(s, kernel_start, &start)) { *d++ = *s; nr += 1; } } } } else { struct branch_entry *nb = NULL; /* * Kernel space sample: start copying branch entries when the ip * falls in between 2 branches (or the branch is in user space * because then the start must have been missed). */ for (s = spos; s < ssz && nr < sz; s++) { if (ks_start(s, ip, kernel_start, &start, nb)) { *d++ = *s; nr += 1; } nb = s; } if (src->nr >= ts->br_stack_sz) { for (s = &src->entries[0]; s < spos && nr < sz; s++) { if (ks_start(s, ip, kernel_start, &start, nb)) { *d++ = *s; nr += 1; } nb = s; } } } dst->nr = nr; } struct call_return_processor * call_return_processor__new(int (*process)(struct call_return *cr, u64 *parent_db_id, void *data), void *data) { struct call_return_processor *crp; crp = zalloc(sizeof(struct call_return_processor)); if (!crp) return NULL; crp->cpr = call_path_root__new(); if (!crp->cpr) goto out_free; crp->process = process; crp->data = data; return crp; out_free: free(crp); return NULL; } void call_return_processor__free(struct call_return_processor *crp) { if (crp) { call_path_root__free(crp->cpr); free(crp); } } static int thread_stack__push_cp(struct thread_stack *ts, u64 ret_addr, u64 timestamp, u64 ref, struct call_path *cp, bool no_call, bool trace_end) { struct thread_stack_entry *tse; int err; if (!cp) return -ENOMEM; if (ts->cnt == ts->sz) { err = thread_stack__grow(ts); if (err) return err; } tse = &ts->stack[ts->cnt++]; tse->ret_addr = ret_addr; tse->timestamp = timestamp; tse->ref = ref; tse->branch_count = ts->branch_count; tse->insn_count = ts->insn_count; tse->cyc_count = ts->cyc_count; tse->cp = cp; tse->no_call = no_call; tse->trace_end = trace_end; tse->non_call = false; tse->db_id = 0; return 0; } static int thread_stack__pop_cp(struct thread *thread, struct thread_stack *ts, u64 ret_addr, u64 timestamp, u64 ref, struct symbol *sym) { int err; if (!ts->cnt) return 1; if (ts->cnt == 1) { struct thread_stack_entry *tse = &ts->stack[0]; if (tse->cp->sym == sym) return thread_stack__call_return(thread, ts, --ts->cnt, timestamp, ref, false); } if (ts->stack[ts->cnt - 1].ret_addr == ret_addr && !ts->stack[ts->cnt - 1].non_call) { return thread_stack__call_return(thread, ts, --ts->cnt, timestamp, ref, false); } else { size_t i = ts->cnt - 1; while (i--) { if (ts->stack[i].ret_addr != ret_addr || ts->stack[i].non_call) continue; i += 1; while (ts->cnt > i) { err = thread_stack__call_return(thread, ts, --ts->cnt, timestamp, ref, true); if (err) return err; } return thread_stack__call_return(thread, ts, --ts->cnt, timestamp, ref, false); } } return 1; } static int thread_stack__bottom(struct thread_stack *ts, struct perf_sample *sample, struct addr_location *from_al, struct addr_location *to_al, u64 ref) { struct call_path_root *cpr = ts->crp->cpr; struct call_path *cp; struct symbol *sym; u64 ip; if (sample->ip) { ip = sample->ip; sym = from_al->sym; } else if (sample->addr) { ip = sample->addr; sym = to_al->sym; } else { return 0; } cp = call_path__findnew(cpr, &cpr->call_path, sym, ip, ts->kernel_start); return thread_stack__push_cp(ts, ip, sample->time, ref, cp, true, false); } static int thread_stack__pop_ks(struct thread *thread, struct thread_stack *ts, struct perf_sample *sample, u64 ref) { u64 tm = sample->time; int err; /* Return to userspace, so pop all kernel addresses */ while (thread_stack__in_kernel(ts)) { err = thread_stack__call_return(thread, ts, --ts->cnt, tm, ref, true); if (err) return err; } return 0; } static int thread_stack__no_call_return(struct thread *thread, struct thread_stack *ts, struct perf_sample *sample, struct addr_location *from_al, struct addr_location *to_al, u64 ref) { struct call_path_root *cpr = ts->crp->cpr; struct call_path *root = &cpr->call_path; struct symbol *fsym = from_al->sym; struct symbol *tsym = to_al->sym; struct call_path *cp, *parent; u64 ks = ts->kernel_start; u64 addr = sample->addr; u64 tm = sample->time; u64 ip = sample->ip; int err; if (ip >= ks && addr < ks) { /* Return to userspace, so pop all kernel addresses */ err = thread_stack__pop_ks(thread, ts, sample, ref); if (err) return err; /* If the stack is empty, push the userspace address */ if (!ts->cnt) { cp = call_path__findnew(cpr, root, tsym, addr, ks); return thread_stack__push_cp(ts, 0, tm, ref, cp, true, false); } } else if (thread_stack__in_kernel(ts) && ip < ks) { /* Return to userspace, so pop all kernel addresses */ err = thread_stack__pop_ks(thread, ts, sample, ref); if (err) return err; } if (ts->cnt) parent = ts->stack[ts->cnt - 1].cp; else parent = root; if (parent->sym == from_al->sym) { /* * At the bottom of the stack, assume the missing 'call' was * before the trace started. So, pop the current symbol and push * the 'to' symbol. */ if (ts->cnt == 1) { err = thread_stack__call_return(thread, ts, --ts->cnt, tm, ref, false); if (err) return err; } if (!ts->cnt) { cp = call_path__findnew(cpr, root, tsym, addr, ks); return thread_stack__push_cp(ts, addr, tm, ref, cp, true, false); } /* * Otherwise assume the 'return' is being used as a jump (e.g. * retpoline) and just push the 'to' symbol. */ cp = call_path__findnew(cpr, parent, tsym, addr, ks); err = thread_stack__push_cp(ts, 0, tm, ref, cp, true, false); if (!err) ts->stack[ts->cnt - 1].non_call = true; return err; } /* * Assume 'parent' has not yet returned, so push 'to', and then push and * pop 'from'. */ cp = call_path__findnew(cpr, parent, tsym, addr, ks); err = thread_stack__push_cp(ts, addr, tm, ref, cp, true, false); if (err) return err; cp = call_path__findnew(cpr, cp, fsym, ip, ks); err = thread_stack__push_cp(ts, ip, tm, ref, cp, true, false); if (err) return err; return thread_stack__call_return(thread, ts, --ts->cnt, tm, ref, false); } static int thread_stack__trace_begin(struct thread *thread, struct thread_stack *ts, u64 timestamp, u64 ref) { struct thread_stack_entry *tse; int err; if (!ts->cnt) return 0; /* Pop trace end */ tse = &ts->stack[ts->cnt - 1]; if (tse->trace_end) { err = thread_stack__call_return(thread, ts, --ts->cnt, timestamp, ref, false); if (err) return err; } return 0; } static int thread_stack__trace_end(struct thread_stack *ts, struct perf_sample *sample, u64 ref) { struct call_path_root *cpr = ts->crp->cpr; struct call_path *cp; u64 ret_addr; /* No point having 'trace end' on the bottom of the stack */ if (!ts->cnt || (ts->cnt == 1 && ts->stack[0].ref == ref)) return 0; cp = call_path__findnew(cpr, ts->stack[ts->cnt - 1].cp, NULL, 0, ts->kernel_start); ret_addr = sample->ip + sample->insn_len; return thread_stack__push_cp(ts, ret_addr, sample->time, ref, cp, false, true); } static bool is_x86_retpoline(const char *name) { return strstr(name, "__x86_indirect_thunk_") == name; } /* * x86 retpoline functions pollute the call graph. This function removes them. * This does not handle function return thunks, nor is there any improvement * for the handling of inline thunks or extern thunks. */ static int thread_stack__x86_retpoline(struct thread_stack *ts, struct perf_sample *sample, struct addr_location *to_al) { struct thread_stack_entry *tse = &ts->stack[ts->cnt - 1]; struct call_path_root *cpr = ts->crp->cpr; struct symbol *sym = tse->cp->sym; struct symbol *tsym = to_al->sym; struct call_path *cp; if (sym && is_x86_retpoline(sym->name)) { /* * This is a x86 retpoline fn. It pollutes the call graph by * showing up everywhere there is an indirect branch, but does * not itself mean anything. Here the top-of-stack is removed, * by decrementing the stack count, and then further down, the * resulting top-of-stack is replaced with the actual target. * The result is that the retpoline functions will no longer * appear in the call graph. Note this only affects the call * graph, since all the original branches are left unchanged. */ ts->cnt -= 1; sym = ts->stack[ts->cnt - 2].cp->sym; if (sym && sym == tsym && to_al->addr != tsym->start) { /* * Target is back to the middle of the symbol we came * from so assume it is an indirect jmp and forget it * altogether. */ ts->cnt -= 1; return 0; } } else if (sym && sym == tsym) { /* * Target is back to the symbol we came from so assume it is an * indirect jmp and forget it altogether. */ ts->cnt -= 1; return 0; } cp = call_path__findnew(cpr, ts->stack[ts->cnt - 2].cp, tsym, sample->addr, ts->kernel_start); if (!cp) return -ENOMEM; /* Replace the top-of-stack with the actual target */ ts->stack[ts->cnt - 1].cp = cp; return 0; } int thread_stack__process(struct thread *thread, struct comm *comm, struct perf_sample *sample, struct addr_location *from_al, struct addr_location *to_al, u64 ref, struct call_return_processor *crp) { struct thread_stack *ts = thread__stack(thread, sample->cpu); enum retpoline_state_t rstate; int err = 0; if (ts && !ts->crp) { /* Supersede thread_stack__event() */ thread_stack__reset(thread, ts); ts = NULL; } if (!ts) { ts = thread_stack__new(thread, sample->cpu, crp, true, 0); if (!ts) return -ENOMEM; ts->comm = comm; } rstate = ts->rstate; if (rstate == X86_RETPOLINE_DETECTED) ts->rstate = X86_RETPOLINE_POSSIBLE; /* Flush stack on exec */ if (ts->comm != comm && thread__pid(thread) == thread__tid(thread)) { err = __thread_stack__flush(thread, ts); if (err) return err; ts->comm = comm; } /* If the stack is empty, put the current symbol on the stack */ if (!ts->cnt) { err = thread_stack__bottom(ts, sample, from_al, to_al, ref); if (err) return err; } ts->branch_count += 1; ts->insn_count += sample->insn_cnt; ts->cyc_count += sample->cyc_cnt; ts->last_time = sample->time; if (sample->flags & PERF_IP_FLAG_CALL) { bool trace_end = sample->flags & PERF_IP_FLAG_TRACE_END; struct call_path_root *cpr = ts->crp->cpr; struct call_path *cp; u64 ret_addr; if (!sample->ip || !sample->addr) return 0; ret_addr = sample->ip + sample->insn_len; if (ret_addr == sample->addr) return 0; /* Zero-length calls are excluded */ cp = call_path__findnew(cpr, ts->stack[ts->cnt - 1].cp, to_al->sym, sample->addr, ts->kernel_start); err = thread_stack__push_cp(ts, ret_addr, sample->time, ref, cp, false, trace_end); /* * A call to the same symbol but not the start of the symbol, * may be the start of a x86 retpoline. */ if (!err && rstate == X86_RETPOLINE_POSSIBLE && to_al->sym && from_al->sym == to_al->sym && to_al->addr != to_al->sym->start) ts->rstate = X86_RETPOLINE_DETECTED; } else if (sample->flags & PERF_IP_FLAG_RETURN) { if (!sample->addr) { u32 return_from_kernel = PERF_IP_FLAG_SYSCALLRET | PERF_IP_FLAG_INTERRUPT; if (!(sample->flags & return_from_kernel)) return 0; /* Pop kernel stack */ return thread_stack__pop_ks(thread, ts, sample, ref); } if (!sample->ip) return 0; /* x86 retpoline 'return' doesn't match the stack */ if (rstate == X86_RETPOLINE_DETECTED && ts->cnt > 2 && ts->stack[ts->cnt - 1].ret_addr != sample->addr) return thread_stack__x86_retpoline(ts, sample, to_al); err = thread_stack__pop_cp(thread, ts, sample->addr, sample->time, ref, from_al->sym); if (err) { if (err < 0) return err; err = thread_stack__no_call_return(thread, ts, sample, from_al, to_al, ref); } } else if (sample->flags & PERF_IP_FLAG_TRACE_BEGIN) { err = thread_stack__trace_begin(thread, ts, sample->time, ref); } else if (sample->flags & PERF_IP_FLAG_TRACE_END) { err = thread_stack__trace_end(ts, sample, ref); } else if (sample->flags & PERF_IP_FLAG_BRANCH && from_al->sym != to_al->sym && to_al->sym && to_al->addr == to_al->sym->start) { struct call_path_root *cpr = ts->crp->cpr; struct call_path *cp; /* * The compiler might optimize a call/ret combination by making * it a jmp. Make that visible by recording on the stack a * branch to the start of a different symbol. Note, that means * when a ret pops the stack, all jmps must be popped off first. */ cp = call_path__findnew(cpr, ts->stack[ts->cnt - 1].cp, to_al->sym, sample->addr, ts->kernel_start); err = thread_stack__push_cp(ts, 0, sample->time, ref, cp, false, false); if (!err) ts->stack[ts->cnt - 1].non_call = true; } return err; } size_t thread_stack__depth(struct thread *thread, int cpu) { struct thread_stack *ts = thread__stack(thread, cpu); if (!ts) return 0; return ts->cnt; }
Information contained on this website is for historical information purposes only and does not indicate or represent copyright ownership.
Created with Cregit http://github.com/cregit/cregit
Version 2.0-RC1