1 | /* Low-level statistical profiling support function. Mach/Hurd version. |
2 | Copyright (C) 1995-2024 Free Software Foundation, Inc. |
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 <sys/types.h> |
20 | #include <unistd.h> |
21 | #include <errno.h> |
22 | #include <hurd.h> |
23 | #include <mach/mach4.h> |
24 | #include <mach/pc_sample.h> |
25 | #include <mach/setup-thread.h> |
26 | #include <lock-intern.h> |
27 | #include <assert.h> |
28 | #include <libc-internal.h> |
29 | |
30 | |
31 | #define MAX_PC_SAMPLES 512 /* XXX ought to be exported in kernel hdr */ |
32 | |
33 | static thread_t profile_thread = MACH_PORT_NULL; |
34 | static u_short *samples; |
35 | static size_t maxsamples; |
36 | static size_t pc_offset; |
37 | static size_t sample_scale; |
38 | static sampled_pc_seqno_t seqno; |
39 | static spin_lock_t lock = SPIN_LOCK_INITIALIZER; |
40 | static mach_msg_timeout_t collector_timeout; /* ms between collections. */ |
41 | static int profile_tick; |
42 | |
43 | /* Reply port used by profiler thread */ |
44 | static mach_port_t profil_reply_port = MACH_PORT_NULL; |
45 | |
46 | /* Forwards */ |
47 | static kern_return_t profil_task_get_sampled_pcs (mach_port_t, |
48 | sampled_pc_seqno_t *, |
49 | sampled_pc_array_t, |
50 | mach_msg_type_number_t *); |
51 | static void fetch_samples (void); |
52 | static void profile_waiter (void); |
53 | |
54 | /* Enable statistical profiling, writing samples of the PC into at most |
55 | SIZE bytes of SAMPLE_BUFFER; every processor clock tick while profiling |
56 | is enabled, the system examines the user PC and increments |
57 | SAMPLE_BUFFER[((PC - OFFSET) / 2) * SCALE / 65536]. If SCALE is zero, |
58 | disable profiling. Returns zero on success, -1 on error. */ |
59 | |
60 | static error_t |
61 | update_waiter (u_short *sample_buffer, size_t size, size_t offset, u_int scale) |
62 | { |
63 | error_t err; |
64 | |
65 | if (profile_thread == MACH_PORT_NULL) |
66 | { |
67 | if (profil_reply_port == MACH_PORT_NULL) |
68 | profil_reply_port = __mach_reply_port (); |
69 | /* Set up the profiling collector thread. */ |
70 | err = __thread_create (__mach_task_self (), &profile_thread); |
71 | if (! err) |
72 | err = __mach_setup_thread_call (__mach_task_self (), profile_thread, |
73 | &profile_waiter, NULL, NULL); |
74 | if (! err) |
75 | err = __mach_setup_tls(profile_thread); |
76 | } |
77 | else |
78 | err = 0; |
79 | |
80 | if (! err) |
81 | { |
82 | err = __task_enable_pc_sampling (__mach_task_self (), &profile_tick, |
83 | SAMPLED_PC_PERIODIC); |
84 | if (!err && sample_scale == 0) |
85 | /* Profiling was not turned on, so the collector thread was |
86 | suspended. Resume it. */ |
87 | err = __thread_resume (profile_thread); |
88 | if (! err) |
89 | { |
90 | samples = sample_buffer; |
91 | maxsamples = size / sizeof *sample_buffer; |
92 | pc_offset = offset; |
93 | sample_scale = scale; |
94 | /* Calculate a good period for the collector thread. From TICK |
95 | and the kernel buffer size we get the length of time it takes |
96 | to fill the buffer; translate that to milliseconds for |
97 | mach_msg, and chop it in half for general lag factor. */ |
98 | collector_timeout = MAX_PC_SAMPLES * profile_tick / 1000 / 2; |
99 | } |
100 | } |
101 | |
102 | return err; |
103 | } |
104 | |
105 | int |
106 | __profile_frequency (void) |
107 | { |
108 | return 1000000 / profile_tick; |
109 | } |
110 | libc_hidden_def (__profile_frequency) |
111 | |
112 | int |
113 | __profil (u_short *sample_buffer, size_t size, size_t offset, u_int scale) |
114 | { |
115 | error_t err; |
116 | |
117 | __spin_lock (&lock); |
118 | |
119 | if (scale == 0) |
120 | { |
121 | /* Disable profiling. */ |
122 | int count; |
123 | |
124 | if (profile_thread != MACH_PORT_NULL) |
125 | __thread_suspend (profile_thread); |
126 | |
127 | /* Fetch the last set of samples */ |
128 | if (sample_scale) |
129 | fetch_samples (); |
130 | |
131 | err = __task_disable_pc_sampling (__mach_task_self (), &count); |
132 | sample_scale = 0; |
133 | seqno = 0; |
134 | } |
135 | else |
136 | err = update_waiter (sample_buffer, size, offset, scale); |
137 | |
138 | __spin_unlock (&lock); |
139 | |
140 | return err ? __hurd_fail (err) : 0; |
141 | } |
142 | weak_alias (__profil, profil) |
143 | |
144 | static volatile error_t special_profil_failure; |
145 | |
146 | /* Fetch PC samples. This function must be very careful not to depend |
147 | on Hurd TLS variables. We arrange that by using a special |
148 | stub arranged for at the end of this file. */ |
149 | static void |
150 | fetch_samples (void) |
151 | { |
152 | sampled_pc_t pc_samples[MAX_PC_SAMPLES]; |
153 | mach_msg_type_number_t nsamples, i; |
154 | error_t err; |
155 | |
156 | nsamples = MAX_PC_SAMPLES; |
157 | |
158 | err = profil_task_get_sampled_pcs (__mach_task_self (), &seqno, |
159 | pc_samples, &nsamples); |
160 | if (err) |
161 | { |
162 | static volatile int a, b; |
163 | |
164 | special_profil_failure = err; |
165 | a = 1; |
166 | b = 0; |
167 | while (1) |
168 | a = a / b; |
169 | } |
170 | |
171 | for (i = 0; i < nsamples; ++i) |
172 | { |
173 | /* Do arithmetic in long long to avoid overflow problems. */ |
174 | long long pc_difference = pc_samples[i].pc - pc_offset; |
175 | size_t idx = ((pc_difference / 2) * sample_scale) / 65536; |
176 | if (idx < maxsamples) |
177 | ++samples[idx]; |
178 | } |
179 | } |
180 | |
181 | |
182 | /* This function must be very careful not to depend on Hurd TLS |
183 | variables. We arrange that by using special stubs arranged for at the |
184 | end of this file. */ |
185 | static void |
186 | profile_waiter (void) |
187 | { |
188 | mach_msg_header_t msg; |
189 | mach_port_t timeout_reply_port; |
190 | |
191 | timeout_reply_port = __mach_reply_port (); |
192 | |
193 | while (1) |
194 | { |
195 | __spin_lock (&lock); |
196 | |
197 | fetch_samples (); |
198 | |
199 | __spin_unlock (&lock); |
200 | |
201 | __mach_msg (&msg, MACH_RCV_MSG|MACH_RCV_TIMEOUT, 0, sizeof msg, |
202 | timeout_reply_port, collector_timeout, MACH_PORT_NULL); |
203 | } |
204 | } |
205 | |
206 | /* Fork interaction */ |
207 | |
208 | /* Before fork, lock the interlock so that we are in a clean state. */ |
209 | static void |
210 | fork_profil_prepare (void) |
211 | { |
212 | __spin_lock (&lock); |
213 | } |
214 | text_set_element (_hurd_fork_prepare_hook, fork_profil_prepare); |
215 | |
216 | /* In the parent, unlock the interlock once fork is complete. */ |
217 | static void |
218 | fork_profil_parent (void) |
219 | { |
220 | __spin_unlock (&lock); |
221 | } |
222 | text_set_element (_hurd_fork_parent_hook, fork_profil_parent); |
223 | |
224 | /* In the child, unlock the interlock, and start a profiling thread up |
225 | if necessary. */ |
226 | static void |
227 | fork_profil_child (void) |
228 | { |
229 | u_short *sb; |
230 | size_t n, o, ss; |
231 | error_t err; |
232 | |
233 | __spin_unlock (&lock); |
234 | |
235 | if (profile_thread != MACH_PORT_NULL) |
236 | { |
237 | __mach_port_deallocate (__mach_task_self (), profile_thread); |
238 | profile_thread = MACH_PORT_NULL; |
239 | } |
240 | |
241 | sb = samples; |
242 | samples = NULL; |
243 | n = maxsamples; |
244 | maxsamples = 0; |
245 | o = pc_offset; |
246 | pc_offset = 0; |
247 | ss = sample_scale; |
248 | sample_scale = 0; |
249 | |
250 | if (ss != 0) |
251 | { |
252 | err = update_waiter (sample_buffer: sb, size: n * sizeof *sb, offset: o, scale: ss); |
253 | assert_perror (err); |
254 | } |
255 | } |
256 | text_set_element (_hurd_fork_child_hook, fork_profil_child); |
257 | |
258 | |
259 | |
260 | |
261 | /* Special RPC stubs for profile_waiter are made by including the normal |
262 | source code, with special CPP state to prevent it from doing the |
263 | usual thing. */ |
264 | |
265 | /* Include these first; then our #define's will take full effect, not |
266 | being overridden. */ |
267 | #include <mach/mig_support.h> |
268 | |
269 | /* This need not do anything; it is always associated with errors, which |
270 | are fatal in profile_waiter anyhow. */ |
271 | #define __mig_put_reply_port(foo) |
272 | |
273 | /* Use our static variable instead of the usual TLS mechanism for |
274 | this. */ |
275 | #define __mig_get_reply_port() profil_reply_port |
276 | |
277 | /* Make the functions show up as static */ |
278 | #define mig_external static |
279 | |
280 | /* Turn off the attempt to generate ld aliasing records. */ |
281 | #undef weak_alias |
282 | #define weak_alias(a,b) |
283 | |
284 | /* And change their names to avoid confusing disasters. */ |
285 | #define __vm_deallocate_rpc profil_vm_deallocate |
286 | #define __task_get_sampled_pcs profil_task_get_sampled_pcs |
287 | |
288 | /* And include the source code */ |
289 | #include <../mach/RPC_task_get_sampled_pcs.c> |
290 | |