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