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
34struct 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
50struct 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
59static unsigned int overflow_counter;
60
61static 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
71static struct prof_info prof_info;
72
73static unsigned long int
74pc_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
84static inline size_t
85index_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
106static void
107profil_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, offset: region->offset, scale: 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
166static inline void
167profil_count_ushort (uintptr_t pcp)
168{
169 profil_count (pcp, prof_uint: 0);
170}
171
172static inline void
173profil_count_uint (uintptr_t pcp)
174{
175 profil_count (pcp, prof_uint: 1);
176}
177
178/* Get the machine-dependent definition of `__profil_counter', the signal
179 handler for SIGPROF. It calls `profil_count' (above) with the PC of the
180 interrupted code. */
181#define __profil_counter __profil_counter_ushort
182#define profil_count(pc) profil_count (pc, 0)
183#include <profil-counter.h>
184
185#undef __profil_counter
186#undef profil_count
187
188#define __profil_counter __profil_counter_uint
189#define profil_count(pc) profil_count (pc, 1)
190#include <profil-counter.h>
191
192static int
193insert (int i, unsigned long int start, unsigned long int end, struct prof *p,
194 int prof_uint)
195{
196 struct region *r;
197 size_t to_copy;
198
199 if (start >= end)
200 return 0; /* don't bother with empty regions */
201
202 if (prof_info.num_regions == 0)
203 r = malloc (size: sizeof (*r));
204 else
205 r = realloc (ptr: prof_info.region, size: (prof_info.num_regions + 1) * sizeof (*r));
206 if (r == NULL)
207 return -1;
208
209 to_copy = prof_info.num_regions - i;
210 if (to_copy > 0)
211 memmove (r + i + 1, r + i, to_copy * sizeof (*r));
212
213 r[i].offset = p->pr_off;
214 r[i].nsamples = p->pr_size / (prof_uint ? sizeof (int) : sizeof (short));
215 r[i].scale = p->pr_scale;
216 r[i].sample.vp = p->pr_base;
217 r[i].start = start;
218 r[i].end = end;
219
220 prof_info.region = r;
221 ++prof_info.num_regions;
222
223 if (p->pr_off == 0 && p->pr_scale == 2)
224 prof_info.overflow = r;
225
226 return 0;
227}
228
229/* Add a new profiling region. If the new region overlaps with
230 existing ones, this may add multiple subregions so that the final
231 data structure is free of overlaps. The absence of overlaps makes
232 it possible to use a binary search in profil_count(). Note that
233 this function depends on new regions being presented in DECREASING
234 ORDER of starting address. */
235
236static int
237add_region (struct prof *p, int prof_uint)
238{
239 unsigned long int nsamples;
240 size_t start, end;
241 unsigned int i;
242
243 if (p->pr_scale < 2)
244 return 0;
245
246 nsamples = p->pr_size / (prof_uint ? sizeof (int) : sizeof (short));
247
248 start = p->pr_off;
249 end = index_to_pc (n: nsamples, offset: p->pr_off, scale: p->pr_scale, prof_uint);
250
251 /* Merge with existing regions. */
252 for (i = 0; i < prof_info.num_regions; ++i)
253 {
254 if (start < prof_info.region[i].start)
255 {
256 if (end < prof_info.region[i].start)
257 break;
258 else if (insert (i, start, end: prof_info.region[i].start, p, prof_uint)
259 < 0)
260 return -1;
261 }
262 start = prof_info.region[i].end;
263 }
264 return insert (i, start, end, p, prof_uint);
265}
266
267static int
268pcmp (const void *left, const void *right)
269{
270 struct prof *l = *(struct prof **) left;
271 struct prof *r = *(struct prof **) right;
272
273 if (l->pr_off < r->pr_off)
274 return 1;
275 else if (l->pr_off > r->pr_off)
276 return -1;
277 return 0;
278}
279
280int
281__sprofil (struct prof *profp, int profcnt, struct timeval *tvp,
282 unsigned int flags)
283{
284 struct prof *p[profcnt];
285 struct itimerval timer;
286 struct sigaction act;
287 int i;
288
289 if (tvp != NULL)
290 {
291 /* Return profiling period. */
292 unsigned long int t = 1000000 / __profile_frequency ();
293 tvp->tv_sec = t / 1000000;
294 tvp->tv_usec = t % 1000000;
295 }
296
297 if (prof_info.num_regions > 0)
298 {
299 /* Disable profiling. */
300 if (__setitimer (ITIMER_PROF, new: &prof_info.saved_timer, NULL) < 0)
301 return -1;
302
303 if (__sigaction (SIGPROF, &prof_info.saved_action, NULL) < 0)
304 return -1;
305
306 free (ptr: prof_info.region);
307 return 0;
308 }
309
310 prof_info.num_regions = 0;
311 prof_info.region = NULL;
312 prof_info.overflow = &default_overflow_region;
313
314 for (i = 0; i < profcnt; ++i)
315 p[i] = profp + i;
316
317 /* Sort in order of decreasing starting address: */
318 qsort (p, profcnt, sizeof (p[0]), pcmp);
319
320 /* Add regions in order of decreasing starting address: */
321 for (i = 0; i < profcnt; ++i)
322 if (add_region (p: p[i], prof_uint: (flags & PROF_UINT) != 0) < 0)
323 {
324 free (ptr: prof_info.region);
325 prof_info.num_regions = 0;
326 prof_info.region = NULL;
327 return -1;
328 }
329
330 if (prof_info.num_regions == 0)
331 return 0;
332
333 prof_info.last = prof_info.region;
334
335 /* Install SIGPROF handler. */
336#ifdef SA_SIGINFO
337 act.sa_sigaction= flags & PROF_UINT
338 ? __profil_counter_uint
339 : __profil_counter_ushort;
340 act.sa_flags = SA_SIGINFO;
341#else
342 act.sa_handler = flags & PROF_UINT
343 ? (sighandler_t) __profil_counter_uint
344 : (sighandler_t) __profil_counter_ushort;
345 act.sa_flags = 0;
346#endif
347 act.sa_flags |= SA_RESTART;
348 __sigfillset (set: &act.sa_mask);
349 if (__sigaction (SIGPROF, &act, &prof_info.saved_action) < 0)
350 return -1;
351
352 /* Setup profiling timer. */
353 timer.it_value.tv_sec = 0;
354 timer.it_value.tv_usec = 1;
355 timer.it_interval = timer.it_value;
356 return __setitimer (ITIMER_PROF, new: &timer, old: &prof_info.saved_timer);
357}
358
359weak_alias (__sprofil, sprofil)
360
361#endif /* SIGPROF */
362

source code of glibc/sysdeps/posix/sprofil.c