]>
Commit | Line | Data |
---|---|---|
00447ccd AH |
1 | /* |
2 | * thread-stack.c: Synthesize a thread's stack using call / return events | |
3 | * Copyright (c) 2014, Intel Corporation. | |
4 | * | |
5 | * This program is free software; you can redistribute it and/or modify it | |
6 | * under the terms and conditions of the GNU General Public License, | |
7 | * version 2, as published by the Free Software Foundation. | |
8 | * | |
9 | * This program is distributed in the hope it will be useful, but WITHOUT | |
10 | * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or | |
11 | * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for | |
12 | * more details. | |
13 | * | |
14 | */ | |
15 | ||
16 | #include "thread.h" | |
17 | #include "event.h" | |
18 | #include "util.h" | |
19 | #include "debug.h" | |
20 | #include "thread-stack.h" | |
21 | ||
22 | #define STACK_GROWTH 4096 | |
23 | ||
24 | struct thread_stack_entry { | |
25 | u64 ret_addr; | |
26 | }; | |
27 | ||
28 | struct thread_stack { | |
29 | struct thread_stack_entry *stack; | |
30 | size_t cnt; | |
31 | size_t sz; | |
32 | u64 trace_nr; | |
33 | }; | |
34 | ||
35 | static int thread_stack__grow(struct thread_stack *ts) | |
36 | { | |
37 | struct thread_stack_entry *new_stack; | |
38 | size_t sz, new_sz; | |
39 | ||
40 | new_sz = ts->sz + STACK_GROWTH; | |
41 | sz = new_sz * sizeof(struct thread_stack_entry); | |
42 | ||
43 | new_stack = realloc(ts->stack, sz); | |
44 | if (!new_stack) | |
45 | return -ENOMEM; | |
46 | ||
47 | ts->stack = new_stack; | |
48 | ts->sz = new_sz; | |
49 | ||
50 | return 0; | |
51 | } | |
52 | ||
53 | static struct thread_stack *thread_stack__new(void) | |
54 | { | |
55 | struct thread_stack *ts; | |
56 | ||
57 | ts = zalloc(sizeof(struct thread_stack)); | |
58 | if (!ts) | |
59 | return NULL; | |
60 | ||
61 | if (thread_stack__grow(ts)) { | |
62 | free(ts); | |
63 | return NULL; | |
64 | } | |
65 | ||
66 | return ts; | |
67 | } | |
68 | ||
69 | static int thread_stack__push(struct thread_stack *ts, u64 ret_addr) | |
70 | { | |
71 | int err = 0; | |
72 | ||
73 | if (ts->cnt == ts->sz) { | |
74 | err = thread_stack__grow(ts); | |
75 | if (err) { | |
76 | pr_warning("Out of memory: discarding thread stack\n"); | |
77 | ts->cnt = 0; | |
78 | } | |
79 | } | |
80 | ||
81 | ts->stack[ts->cnt++].ret_addr = ret_addr; | |
82 | ||
83 | return err; | |
84 | } | |
85 | ||
86 | static void thread_stack__pop(struct thread_stack *ts, u64 ret_addr) | |
87 | { | |
88 | size_t i; | |
89 | ||
90 | /* | |
91 | * In some cases there may be functions which are not seen to return. | |
92 | * For example when setjmp / longjmp has been used. Or the perf context | |
93 | * switch in the kernel which doesn't stop and start tracing in exactly | |
94 | * the same code path. When that happens the return address will be | |
95 | * further down the stack. If the return address is not found at all, | |
96 | * we assume the opposite (i.e. this is a return for a call that wasn't | |
97 | * seen for some reason) and leave the stack alone. | |
98 | */ | |
99 | for (i = ts->cnt; i; ) { | |
100 | if (ts->stack[--i].ret_addr == ret_addr) { | |
101 | ts->cnt = i; | |
102 | return; | |
103 | } | |
104 | } | |
105 | } | |
106 | ||
107 | int thread_stack__event(struct thread *thread, u32 flags, u64 from_ip, | |
108 | u64 to_ip, u16 insn_len, u64 trace_nr) | |
109 | { | |
110 | if (!thread) | |
111 | return -EINVAL; | |
112 | ||
113 | if (!thread->ts) { | |
114 | thread->ts = thread_stack__new(); | |
115 | if (!thread->ts) { | |
116 | pr_warning("Out of memory: no thread stack\n"); | |
117 | return -ENOMEM; | |
118 | } | |
119 | thread->ts->trace_nr = trace_nr; | |
120 | } | |
121 | ||
122 | /* | |
123 | * When the trace is discontinuous, the trace_nr changes. In that case | |
124 | * the stack might be completely invalid. Better to report nothing than | |
125 | * to report something misleading, so reset the stack count to zero. | |
126 | */ | |
127 | if (trace_nr != thread->ts->trace_nr) { | |
128 | thread->ts->trace_nr = trace_nr; | |
129 | thread->ts->cnt = 0; | |
130 | } | |
131 | ||
132 | if (flags & PERF_IP_FLAG_CALL) { | |
133 | u64 ret_addr; | |
134 | ||
135 | if (!to_ip) | |
136 | return 0; | |
137 | ret_addr = from_ip + insn_len; | |
138 | if (ret_addr == to_ip) | |
139 | return 0; /* Zero-length calls are excluded */ | |
140 | return thread_stack__push(thread->ts, ret_addr); | |
141 | } else if (flags & PERF_IP_FLAG_RETURN) { | |
142 | if (!from_ip) | |
143 | return 0; | |
144 | thread_stack__pop(thread->ts, to_ip); | |
145 | } | |
146 | ||
147 | return 0; | |
148 | } | |
149 | ||
150 | void thread_stack__free(struct thread *thread) | |
151 | { | |
152 | if (thread->ts) { | |
153 | zfree(&thread->ts->stack); | |
154 | zfree(&thread->ts); | |
155 | } | |
156 | } | |
157 | ||
158 | void thread_stack__sample(struct thread *thread, struct ip_callchain *chain, | |
159 | size_t sz, u64 ip) | |
160 | { | |
161 | size_t i; | |
162 | ||
163 | if (!thread || !thread->ts) | |
164 | chain->nr = 1; | |
165 | else | |
166 | chain->nr = min(sz, thread->ts->cnt + 1); | |
167 | ||
168 | chain->ips[0] = ip; | |
169 | ||
170 | for (i = 1; i < chain->nr; i++) | |
171 | chain->ips[i] = thread->ts->stack[thread->ts->cnt - i].ret_addr; | |
172 | } |