1 /* Copyright (C) 2001-2022 Free Software Foundation, Inc.
2 This file is part of the GNU C Library.
3
4 The GNU C Library is free software; you can redistribute it and/or
5 modify it under the terms of the GNU Lesser General Public
6 License as published by the Free Software Foundation; either
7 version 2.1 of the License, or (at your option) any later version.
8
9 The GNU C Library is distributed in the hope that it will be useful,
10 but WITHOUT ANY WARRANTY; without even the implied warranty of
11 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12 Lesser General Public License for more details.
13
14 You should have received a copy of the GNU Lesser General Public
15 License along with the GNU C Library; if not, see
16 <https://www.gnu.org/licenses/>. */
17
18 #include <assert.h>
19 #include <signal.h>
20 #include <stdlib.h>
21 #include <string.h>
22 #include <unistd.h>
23 #include <sigsetops.h>
24
25 #include <sys/time.h>
26 #include <sys/profil.h>
27
28 #ifndef SIGPROF
29 # include <gmon/sprofil.c>
30 #else
31
32 #include <libc-internal.h>
33
34 struct region
35 {
36 size_t offset;
37 size_t nsamples;
38 unsigned int scale;
39 union
40 {
41 void *vp;
42 unsigned short *us;
43 unsigned int *ui;
44 }
45 sample;
46 size_t start;
47 size_t end;
48 };
49
50 struct prof_info
51 {
52 unsigned int num_regions;
53 struct region *region;
54 struct region *last, *overflow;
55 struct itimerval saved_timer;
56 struct sigaction saved_action;
57 };
58
59 static unsigned int overflow_counter;
60
61 static struct region default_overflow_region =
62 {
63 .offset = 0,
64 .nsamples = 1,
65 .scale = 2,
66 .sample = { &overflow_counter },
67 .start = 0,
68 .end = ~(size_t) 0
69 };
70
71 static struct prof_info prof_info;
72
73 static unsigned long int
pc_to_index(size_t pc,size_t offset,unsigned int scale,int prof_uint)74 pc_to_index (size_t pc, size_t offset, unsigned int scale, int prof_uint)
75 {
76 size_t i = (pc - offset) / (prof_uint ? sizeof (int) : sizeof (short));
77
78 if (sizeof (unsigned long long int) > sizeof (size_t))
79 return (unsigned long long int) i * scale / 65536;
80 else
81 return i / 65536 * scale + i % 65536 * scale / 65536;
82 }
83
84 static inline size_t
index_to_pc(unsigned long int n,size_t offset,unsigned int scale,int prof_uint)85 index_to_pc (unsigned long int n, size_t offset, unsigned int scale,
86 int prof_uint)
87 {
88 size_t pc, bin_size = (prof_uint ? sizeof (int) : sizeof (short));
89
90 if (sizeof (unsigned long long int) > sizeof (size_t))
91 pc = offset + (unsigned long long int) n * bin_size * 65536ull / scale;
92 else
93 pc = (offset + n * bin_size / scale * 65536
94 + n * bin_size % scale * 65536 / scale);
95
96 if (pc_to_index (pc, offset, scale, prof_uint) < n)
97 /* Adjust for rounding error. */
98 ++pc;
99
100 assert (pc_to_index (pc - 1, offset, scale, prof_uint) < n
101 && pc_to_index (pc, offset, scale, prof_uint) >= n);
102
103 return pc;
104 }
105
106 static void
profil_count(uintptr_t pcp,int prof_uint)107 profil_count (uintptr_t pcp, int prof_uint)
108 {
109 struct region *region, *r = prof_info.last;
110 size_t lo, hi, mid, pc = pcp;
111 unsigned long int i;
112
113 /* Fast path: pc is in same region as before. */
114 if (pc >= r->start && pc < r->end)
115 region = r;
116 else
117 {
118 /* Slow path: do a binary search for the right region. */
119 lo = 0; hi = prof_info.num_regions - 1;
120 while (lo <= hi)
121 {
122 mid = (lo + hi) / 2;
123
124 r = prof_info.region + mid;
125 if (pc >= r->start && pc < r->end)
126 {
127 prof_info.last = r;
128 region = r;
129 break;
130 }
131
132 if (pc < r->start)
133 hi = mid - 1;
134 else
135 lo = mid + 1;
136 }
137
138 /* No matching region: increment overflow count. There is no point
139 in updating the cache here, as it won't hit anyhow. */
140 region = prof_info.overflow;
141 }
142
143 i = pc_to_index (pc, region->offset, region->scale, prof_uint);
144 if (i < r->nsamples)
145 {
146 if (prof_uint)
147 {
148 if (r->sample.ui[i] < (unsigned int) ~0)
149 ++r->sample.ui[i];
150 }
151 else
152 {
153 if (r->sample.us[i] < (unsigned short) ~0)
154 ++r->sample.us[i];
155 }
156 }
157 else
158 {
159 if (prof_uint)
160 ++prof_info.overflow->sample.ui[0];
161 else
162 ++prof_info.overflow->sample.us[0];
163 }
164 }
165
166 /* Get the machine-dependent definition of `__profil_counter', the signal
167 handler for SIGPROF. It calls `profil_count' (above) with the PC of the
168 interrupted code. */
169 #define __profil_counter __profil_counter_ushort
170 #define profil_count(pc) profil_count (pc, 0)
171 #include <profil-counter.h>
172
173 #undef __profil_counter
174 #undef profil_count
175
176 #define __profil_counter __profil_counter_uint
177 #define profil_count(pc) profil_count (pc, 1)
178 #include <profil-counter.h>
179
180 static int
insert(int i,unsigned long int start,unsigned long int end,struct prof * p,int prof_uint)181 insert (int i, unsigned long int start, unsigned long int end, struct prof *p,
182 int prof_uint)
183 {
184 struct region *r;
185 size_t to_copy;
186
187 if (start >= end)
188 return 0; /* don't bother with empty regions */
189
190 if (prof_info.num_regions == 0)
191 r = malloc (sizeof (*r));
192 else
193 r = realloc (prof_info.region, (prof_info.num_regions + 1) * sizeof (*r));
194 if (r == NULL)
195 return -1;
196
197 to_copy = prof_info.num_regions - i;
198 if (to_copy > 0)
199 memmove (r + i + 1, r + i, to_copy * sizeof (*r));
200
201 r[i].offset = p->pr_off;
202 r[i].nsamples = p->pr_size / (prof_uint ? sizeof (int) : sizeof (short));
203 r[i].scale = p->pr_scale;
204 r[i].sample.vp = p->pr_base;
205 r[i].start = start;
206 r[i].end = end;
207
208 prof_info.region = r;
209 ++prof_info.num_regions;
210
211 if (p->pr_off == 0 && p->pr_scale == 2)
212 prof_info.overflow = r;
213
214 return 0;
215 }
216
217 /* Add a new profiling region. If the new region overlaps with
218 existing ones, this may add multiple subregions so that the final
219 data structure is free of overlaps. The absence of overlaps makes
220 it possible to use a binary search in profil_count(). Note that
221 this function depends on new regions being presented in DECREASING
222 ORDER of starting address. */
223
224 static int
add_region(struct prof * p,int prof_uint)225 add_region (struct prof *p, int prof_uint)
226 {
227 unsigned long int nsamples;
228 size_t start, end;
229 unsigned int i;
230
231 if (p->pr_scale < 2)
232 return 0;
233
234 nsamples = p->pr_size / (prof_uint ? sizeof (int) : sizeof (short));
235
236 start = p->pr_off;
237 end = index_to_pc (nsamples, p->pr_off, p->pr_scale, prof_uint);
238
239 /* Merge with existing regions. */
240 for (i = 0; i < prof_info.num_regions; ++i)
241 {
242 if (start < prof_info.region[i].start)
243 {
244 if (end < prof_info.region[i].start)
245 break;
246 else if (insert (i, start, prof_info.region[i].start, p, prof_uint)
247 < 0)
248 return -1;
249 }
250 start = prof_info.region[i].end;
251 }
252 return insert (i, start, end, p, prof_uint);
253 }
254
255 static int
pcmp(const void * left,const void * right)256 pcmp (const void *left, const void *right)
257 {
258 struct prof *l = *(struct prof **) left;
259 struct prof *r = *(struct prof **) right;
260
261 if (l->pr_off < r->pr_off)
262 return 1;
263 else if (l->pr_off > r->pr_off)
264 return -1;
265 return 0;
266 }
267
268 int
__sprofil(struct prof * profp,int profcnt,struct timeval * tvp,unsigned int flags)269 __sprofil (struct prof *profp, int profcnt, struct timeval *tvp,
270 unsigned int flags)
271 {
272 struct prof *p[profcnt];
273 struct itimerval timer;
274 struct sigaction act;
275 int i;
276
277 if (tvp != NULL)
278 {
279 /* Return profiling period. */
280 unsigned long int t = 1000000 / __profile_frequency ();
281 tvp->tv_sec = t / 1000000;
282 tvp->tv_usec = t % 1000000;
283 }
284
285 if (prof_info.num_regions > 0)
286 {
287 /* Disable profiling. */
288 if (__setitimer (ITIMER_PROF, &prof_info.saved_timer, NULL) < 0)
289 return -1;
290
291 if (__sigaction (SIGPROF, &prof_info.saved_action, NULL) < 0)
292 return -1;
293
294 free (prof_info.region);
295 return 0;
296 }
297
298 prof_info.num_regions = 0;
299 prof_info.region = NULL;
300 prof_info.overflow = &default_overflow_region;
301
302 for (i = 0; i < profcnt; ++i)
303 p[i] = profp + i;
304
305 /* Sort in order of decreasing starting address: */
306 qsort (p, profcnt, sizeof (p[0]), pcmp);
307
308 /* Add regions in order of decreasing starting address: */
309 for (i = 0; i < profcnt; ++i)
310 if (add_region (p[i], (flags & PROF_UINT) != 0) < 0)
311 {
312 free (prof_info.region);
313 prof_info.num_regions = 0;
314 prof_info.region = NULL;
315 return -1;
316 }
317
318 if (prof_info.num_regions == 0)
319 return 0;
320
321 prof_info.last = prof_info.region;
322
323 /* Install SIGPROF handler. */
324 #ifdef SA_SIGINFO
325 act.sa_sigaction= flags & PROF_UINT
326 ? __profil_counter_uint
327 : __profil_counter_ushort;
328 act.sa_flags = SA_SIGINFO;
329 #else
330 act.sa_handler = flags & PROF_UINT
331 ? (sighandler_t) __profil_counter_uint
332 : (sighandler_t) __profil_counter_ushort;
333 act.sa_flags = 0;
334 #endif
335 act.sa_flags |= SA_RESTART;
336 __sigfillset (&act.sa_mask);
337 if (__sigaction (SIGPROF, &act, &prof_info.saved_action) < 0)
338 return -1;
339
340 /* Setup profiling timer. */
341 timer.it_value.tv_sec = 0;
342 timer.it_value.tv_usec = 1;
343 timer.it_interval = timer.it_value;
344 return __setitimer (ITIMER_PROF, &timer, &prof_info.saved_timer);
345 }
346
347 weak_alias (__sprofil, sprofil)
348
349 #endif /* SIGPROF */
350