1 /*-
2 * SPDX-License-Identifier: BSD-2-Clause
3 *
4 * Copyright (c) 2026 Alexander Leidinger <netchild@FreeBSD.org>
5 *
6 * Redistribution and use in source and binary forms, with or without
7 * modification, are permitted provided that the following conditions
8 * are met:
9 * 1. Redistributions of source code must retain the above copyright
10 * notice, this list of conditions and the following disclaimer.
11 * 2. Redistributions in binary form must reproduce the above copyright
12 * notice, this list of conditions and the following disclaimer in the
13 * documentation and/or other advertising materials provided with the
14 * distribution.
15 *
16 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
17 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
18 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
19 * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
20 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
21 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
22 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
23 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
24 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
25 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
26 * SUCH DAMAGE.
27 */
28
29 /**
30 * @file
31 * Tests for hwpmc(4)'s process-attachment lifecycle: what happens as a PMC
32 * is attached, inherited, and torn down, especially when the teardown does
33 * not happen in the tidy order the common path assumes.
34 *
35 * The orderings driven here are a target that exits before it is
36 * detached, an owner that exits before its target (which reaches the
37 * REMOVE side of pmc_find_process_descriptor()),
38 * a release of a still-running attached PMC, row exhaustion released out
39 * of order, and PMC_F_DESCENDANTS, where the teardown has to account for a
40 * target the owner never attached by hand.
41 *
42 * All PMCs are SOFT-class, so nothing here needs a hardware counter.
43 */
44
45 #include <sys/param.h>
46 #include <sys/wait.h>
47
48 #include <errno.h>
49 #include <pmc.h>
50 #include <signal.h>
51 #include <stdint.h>
52 #include <stdio.h>
53 #include <stdlib.h>
54 #include <string.h>
55 #include <unistd.h>
56
57 #include <atf-c.h>
58
59 static void
require_hwpmc(void)60 require_hwpmc(void)
61 {
62
63 if (pmc_init() != 0)
64 atf_tc_skip("hwpmc(4) is not available");
65 }
66
67 /**
68 * @internal
69 * Allocate the first SOFT event this mode and flag set accept. Which event
70 * it is does not matter; that one exists is what the case needs.
71 */
72 static pmc_id_t
allocate_soft_pmc_flags(enum pmc_mode mode,uint32_t flags)73 allocate_soft_pmc_flags(enum pmc_mode mode, uint32_t flags)
74 {
75 const char **names;
76 char spec[128];
77 pmc_id_t id;
78 int nnames, i;
79
80 if (pmc_event_names_of_class(PMC_CLASS_SOFT, &names, &nnames) != 0)
81 return (PMC_ID_INVALID);
82 for (i = 0; i < nnames; i++) {
83 (void)snprintf(spec, sizeof(spec), "SOFT-%s", names[i]);
84 if (pmc_allocate(spec, mode, flags, PMC_CPU_ANY, &id, 0) == 0)
85 return (id);
86 }
87 return (PMC_ID_INVALID);
88 }
89
90 static pmc_id_t
require_soft_pmc(enum pmc_mode mode)91 require_soft_pmc(enum pmc_mode mode)
92 {
93 pmc_id_t id;
94
95 if ((id = allocate_soft_pmc_flags(mode, 0)) == PMC_ID_INVALID)
96 atf_tc_skip("no SOFT-class PMC is allocatable");
97 return (id);
98 }
99
100 /**
101 * @internal
102 * A child that does nothing until killed, for use as an attach target.
103 */
104 static pid_t
spawn_idle_child(void)105 spawn_idle_child(void)
106 {
107 pid_t pid;
108
109 pid = fork();
110 if (pid == 0) {
111 for (;;)
112 (void)pause();
113 }
114 return (pid);
115 }
116
117 static void
reap(pid_t pid)118 reap(pid_t pid)
119 {
120 int status;
121
122 (void)kill(pid, SIGKILL);
123 (void)waitpid(pid, &status, 0);
124 }
125
126 /**
127 * @internal
128 * The bookkeeping around attach and detach.
129 */
130 ATF_TC_WITHOUT_HEAD(attach_detach_bookkeeping);
ATF_TC_BODY(attach_detach_bookkeeping,tc)131 ATF_TC_BODY(attach_detach_bookkeeping, tc)
132 {
133 pmc_id_t id;
134 pid_t other;
135
136 require_hwpmc();
137 id = require_soft_pmc(PMC_MODE_TC);
138 other = spawn_idle_child();
139 ATF_REQUIRE(other > 0);
140
141 ATF_REQUIRE_MSG(pmc_attach(id, getpid()) == 0, "pmc_attach: %s",
142 strerror(errno));
143
144 /* A second attach of the same target must be refused, not doubled. */
145 errno = 0;
146 ATF_CHECK_MSG(pmc_attach(id, getpid()) != 0,
147 "a second attach of the same target succeeded");
148
149 /* Detaching a pid that was never attached must be refused. */
150 errno = 0;
151 ATF_CHECK_MSG(pmc_detach(id, other) != 0,
152 "detached a target that was never attached");
153
154 /* One detach undoes the one attach. */
155 ATF_CHECK_MSG(pmc_detach(id, getpid()) == 0, "pmc_detach: %s",
156 strerror(errno));
157
158 /* A second detach must now be refused. */
159 errno = 0;
160 ATF_CHECK_MSG(pmc_detach(id, getpid()) != 0,
161 "a second detach of the same target succeeded");
162
163 ATF_CHECK_MSG(pmc_release(id) == 0, "pmc_release: %s",
164 strerror(errno));
165 reap(other);
166 }
167
168 /**
169 * @internal
170 * Detach a target that has already exited and been reaped. The
171 * kernel's own eventhandler removes an exiting process's descriptor, so by
172 * the time we detach there is nothing there; it must say so cleanly, and
173 * the PMC must still release.
174 */
175 ATF_TC_WITHOUT_HEAD(detach_after_target_reaped);
ATF_TC_BODY(detach_after_target_reaped,tc)176 ATF_TC_BODY(detach_after_target_reaped, tc)
177 {
178 pmc_id_t id;
179 pid_t target;
180
181 require_hwpmc();
182 id = require_soft_pmc(PMC_MODE_TC);
183 target = spawn_idle_child();
184 ATF_REQUIRE(target > 0);
185
186 ATF_REQUIRE_MSG(pmc_attach(id, target) == 0, "pmc_attach: %s",
187 strerror(errno));
188
189 /* Kill and fully reap the target before detaching. */
190 reap(target);
191
192 errno = 0;
193 ATF_CHECK_MSG(pmc_detach(id, target) != 0,
194 "detached a target that had exited and been reaped");
195
196 ATF_CHECK_MSG(pmc_release(id) == 0, "pmc_release: %s",
197 strerror(errno));
198 }
199
200 /**
201 * @internal
202 * Release a PMC that is still attached and still running, with no
203 * prior detach or stop: the release path has to reclaim the reference
204 * the running PMC still holds rather than wedge.
205 */
206 ATF_TC_WITHOUT_HEAD(release_running_attached);
ATF_TC_BODY(release_running_attached,tc)207 ATF_TC_BODY(release_running_attached, tc)
208 {
209 pmc_id_t id;
210
211 require_hwpmc();
212 id = require_soft_pmc(PMC_MODE_TC);
213
214 ATF_REQUIRE_MSG(pmc_attach(id, getpid()) == 0, "pmc_attach: %s",
215 strerror(errno));
216 ATF_REQUIRE_MSG(pmc_start(id) == 0, "pmc_start: %s", strerror(errno));
217
218 /* Run a little, so the PMC is actually loaded on a CPU. */
219 {
220 volatile unsigned long s = 0;
221 int i;
222
223 for (i = 0; i < 5000000; i++)
224 s += i;
225 }
226 (void)usleep(100000);
227
228 /* Straight to release: no stop, no detach. */
229 ATF_CHECK_MSG(pmc_release(id) == 0,
230 "release of a running, attached PMC failed: %s", strerror(errno));
231
232 /* The driver is still usable afterwards. */
233 {
234 pmc_id_t again = allocate_soft_pmc_flags(PMC_MODE_TC, 0);
235
236 ATF_CHECK_MSG(again != PMC_ID_INVALID,
237 "hwpmc is unusable after the release");
238 if (again != PMC_ID_INVALID)
239 (void)pmc_release(again);
240 }
241 }
242
243 /**
244 * @internal
245 * The owner exits before the target. A child process is the owner:
246 * it allocates a PMC and attaches it to a sibling that outlives it, then
247 * exits without releasing. The kernel tears the owner's PMCs down through
248 * the REMOVE side of pmc_find_process_descriptor() - the other of its two
249 * unlink mechanisms, which no test exercised. The parent then confirms
250 * the driver is still usable and the target still alive.
251 */
252 ATF_TC_WITHOUT_HEAD(owner_exit_before_target);
ATF_TC_BODY(owner_exit_before_target,tc)253 ATF_TC_BODY(owner_exit_before_target, tc)
254 {
255 pmc_id_t id;
256 pid_t target, owner;
257 int status;
258
259 require_hwpmc();
260
261 target = spawn_idle_child();
262 ATF_REQUIRE(target > 0);
263
264 owner = fork();
265 ATF_REQUIRE(owner >= 0);
266 if (owner == 0) {
267 /* Owner: attach to the sibling target, then just exit. */
268 if (pmc_init() != 0)
269 _exit(10);
270 id = allocate_soft_pmc_flags(PMC_MODE_TC, 0);
271 if (id == PMC_ID_INVALID)
272 _exit(11);
273 if (pmc_attach(id, target) != 0)
274 _exit(12);
275 if (pmc_start(id) != 0)
276 _exit(13);
277 /* Exit with the PMC still allocated, attached and running. */
278 _exit(0);
279 }
280
281 ATF_REQUIRE(waitpid(owner, &status, 0) == owner);
282 ATF_REQUIRE_MSG(WIFEXITED(status),
283 "the owner did not exit normally (it may have panicked the "
284 "kernel; status 0x%x)", status);
285 ATF_REQUIRE_MSG(WEXITSTATUS(status) == 0,
286 "the owner's setup failed (exit %d): 10=init 11=alloc 12=attach "
287 "13=start", WEXITSTATUS(status));
288
289 /*
290 * The owner is gone. Its descriptor teardown must have run without
291 * wedging: prove the driver still works and the target is still here.
292 */
293 id = allocate_soft_pmc_flags(PMC_MODE_TC, 0);
294 ATF_CHECK_MSG(id != PMC_ID_INVALID,
295 "hwpmc is unusable after the owner exited with a live target");
296 if (id != PMC_ID_INVALID) {
297 ATF_CHECK(pmc_attach(id, getpid()) == 0);
298 ATF_CHECK(pmc_detach(id, getpid()) == 0);
299 ATF_CHECK(pmc_release(id) == 0);
300 }
301 ATF_CHECK_MSG(kill(target, 0) == 0,
302 "the target did not survive the owner's exit");
303 reap(target);
304 }
305
306 /**
307 * @internal
308 * Allocate SOFT PMCs until the rows are exhausted, then release them
309 * in an order other than allocation order. Exercises the row free-list
310 * against out-of-order frees.
311 */
312 ATF_TC_WITHOUT_HEAD(exhaust_then_release_reverse);
ATF_TC_BODY(exhaust_then_release_reverse,tc)313 ATF_TC_BODY(exhaust_then_release_reverse, tc)
314 {
315 pmc_id_t ids[64];
316 int n, i;
317
318 require_hwpmc();
319
320 n = 0;
321 while (n < (int)nitems(ids)) {
322 pmc_id_t id = allocate_soft_pmc_flags(PMC_MODE_TC, 0);
323
324 if (id == PMC_ID_INVALID)
325 break;
326 ids[n++] = id;
327 }
328 ATF_REQUIRE_MSG(n > 0, "not one SOFT PMC could be allocated");
329
330 /* At least one allocation should have failed at exhaustion. */
331 ATF_CHECK_MSG(n < (int)nitems(ids),
332 "allocation never hit a limit in %zu tries", nitems(ids));
333
334 /* Release in reverse order; every one must succeed. */
335 for (i = n - 1; i >= 0; i--)
336 ATF_CHECK_MSG(pmc_release(ids[i]) == 0,
337 "reverse release of id 0x%08x (slot %d) failed: %s",
338 ids[i], i, strerror(errno));
339 }
340
341 /**
342 * @internal
343 * A PMC_F_DESCENDANTS counter, whose child inherits the attachment.
344 * The owner attaches to itself with descendants set, forks a child that
345 * does a little work and exits, reaps it, and releases. The teardown has
346 * to account for the child the owner never attached to by hand.
347 */
348 ATF_TC_WITHOUT_HEAD(descendants_inherit_and_release);
ATF_TC_BODY(descendants_inherit_and_release,tc)349 ATF_TC_BODY(descendants_inherit_and_release, tc)
350 {
351 pmc_id_t id;
352 pid_t child;
353 int status;
354
355 require_hwpmc();
356 id = allocate_soft_pmc_flags(PMC_MODE_TC, PMC_F_DESCENDANTS);
357 if (id == PMC_ID_INVALID)
358 atf_tc_skip("no SOFT PMC allocatable with PMC_F_DESCENDANTS");
359
360 ATF_REQUIRE_MSG(pmc_attach(id, getpid()) == 0, "pmc_attach: %s",
361 strerror(errno));
362 ATF_REQUIRE_MSG(pmc_start(id) == 0, "pmc_start: %s", strerror(errno));
363
364 child = fork();
365 ATF_REQUIRE(child >= 0);
366 if (child == 0) {
367 volatile unsigned long s = 0;
368 int i;
369
370 for (i = 0; i < 2000000; i++)
371 s += i;
372 _exit(0);
373 }
374 ATF_REQUIRE(waitpid(child, &status, 0) == child);
375 ATF_CHECK_MSG(WIFEXITED(status) && WEXITSTATUS(status) == 0,
376 "the descendant did not exit cleanly (status 0x%x)", status);
377
378 ATF_CHECK(pmc_stop(id) == 0);
379 ATF_CHECK_MSG(pmc_release(id) == 0,
380 "release of a descendants PMC failed: %s", strerror(errno));
381 }
382
383 /**
384 * @internal
385 * The fork-storm variant: descendants set, then many short-lived children,
386 * so several inherit-and-exit teardowns overlap, then release.
387 */
388 ATF_TC_WITHOUT_HEAD(descendants_fork_storm);
ATF_TC_BODY(descendants_fork_storm,tc)389 ATF_TC_BODY(descendants_fork_storm, tc)
390 {
391 pmc_id_t id;
392 int i, status;
393
394 require_hwpmc();
395 id = allocate_soft_pmc_flags(PMC_MODE_TC, PMC_F_DESCENDANTS);
396 if (id == PMC_ID_INVALID)
397 atf_tc_skip("no SOFT PMC allocatable with PMC_F_DESCENDANTS");
398
399 ATF_REQUIRE_MSG(pmc_attach(id, getpid()) == 0, "pmc_attach: %s",
400 strerror(errno));
401 ATF_REQUIRE_MSG(pmc_start(id) == 0, "pmc_start: %s", strerror(errno));
402
403 for (i = 0; i < 32; i++) {
404 pid_t c = fork();
405
406 if (c == 0) {
407 volatile unsigned long s = 0;
408 int j;
409
410 for (j = 0; j < 200000; j++)
411 s += j;
412 _exit(0);
413 }
414 if (c > 0)
415 (void)waitpid(c, &status, 0);
416 }
417
418 ATF_CHECK(pmc_stop(id) == 0);
419 ATF_CHECK_MSG(pmc_release(id) == 0,
420 "release after a descendants fork storm failed: %s",
421 strerror(errno));
422 }
423
ATF_TP_ADD_TCS(tp)424 ATF_TP_ADD_TCS(tp)
425 {
426
427 ATF_TP_ADD_TC(tp, attach_detach_bookkeeping);
428 ATF_TP_ADD_TC(tp, detach_after_target_reaped);
429 ATF_TP_ADD_TC(tp, release_running_attached);
430 ATF_TP_ADD_TC(tp, owner_exit_before_target);
431 ATF_TP_ADD_TC(tp, exhaust_then_release_reverse);
432 ATF_TP_ADD_TC(tp, descendants_inherit_and_release);
433 ATF_TP_ADD_TC(tp, descendants_fork_storm);
434
435 return (atf_no_error());
436 }
437