From nobody Thu Aug 27 14:03:19 2026 X-Original-To: dev-commits-src-main@mlmmj.nyi.freebsd.org Received: from mx1.freebsd.org (mx1.freebsd.org [IPv6:2610:1c1:1:606c::19:1]) by mlmmj.nyi.freebsd.org (Postfix) with ESMTP id 4hW3Bw40tdz6q1lQ for ; Thu, 27 Aug 2026 14:03:20 +0000 (UTC) (envelope-from git@FreeBSD.org) Received: from mxrelay.nyi.freebsd.org (mxrelay.nyi.freebsd.org [IPv6:2610:1c1:1:606c::19:3]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature RSA-PSS (4096 bits) server-digest SHA256 client-signature RSA-PSS (4096 bits) client-digest SHA256) (Client CN "mxrelay.nyi.freebsd.org", Issuer "YR2" (not verified)) by mx1.freebsd.org (Postfix) with ESMTPS id 4hW3Bv6Gxlz3fZv for ; Thu, 27 Aug 2026 14:03:19 +0000 (UTC) (envelope-from git@FreeBSD.org) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=freebsd.org; s=dkim; t=1787839399; h=from:from:reply-to:subject:subject:date:date:message-id:message-id: to:to:cc:mime-version:mime-version:content-type:content-type: content-transfer-encoding:content-transfer-encoding; bh=jBdFswpyCBTDK2dKrzueMAHhV5qXZdDd+QKf6v8WcaM=; b=EC0SOuJqjc1+8Tc0NESZbamDgvLwqNh5x4ohNXstqVl3j7UbsX4O8DaHd1E6MO5U+0navL jLFugAqLi+v4FXYFz9XOi/6/JePiQne9g5ndhjjoRFyNlwvbGPOyndzP15EHdQDI9DpUxv S0FMaDkN/OL6eOTlcUup4Zawj6SI6vqBvWx4v0xdETf2I5fUtqLHj/ZNWsg8rgRV20IJgw OciEsCk+rQ4e0Td/xuGD4SvgA4GoOEpFjqHSEepSg1HHU0sYNxBczoZYoKGQxTXJYzU2/Z /OypYnYbO8YzjTtIpZ4VRUBpxyHXer+6younMnb4hl4g4ugXxkKs9cnVdeRxCQ== ARC-Seal: i=1; s=dkim; d=freebsd.org; t=1787839399; a=rsa-sha256; cv=none; b=ww4GKlAJn9bLYwGGW5Os1E6bbkQK2G8COqhw4z0A5YP79AKRsOg97vbfhJpYhrdNebLV9L ktrxfKFJeGmfCRrOuAlbWtaX2S9cshJE4rDdNDdS5wMyWu2HEB8AN0iK85xxxjmeCrQGS6 cgtuiTxU6lMJUpCC72VFi/tKb87yjLM4Rtfn6Fb+kZV86EO26PvdE2+2MuMLf+NN9BhhM4 OFkIub5es1s6+BywQOnVUdF88/4MYSw3GvzaxddOBz0kRYItjT9DuPbFUV/cRzzHRBUDJm SuOPMtEhogxf28i7eoyOLNDFsBuwx9lLkqCoSi2YDJsFmm3fHpW6iIzfk+S3Wg== ARC-Authentication-Results: i=1; mx1.freebsd.org; none ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=freebsd.org; s=dkim; t=1787839399; h=from:from:reply-to:subject:subject:date:date:message-id:message-id: to:to:cc:mime-version:mime-version:content-type:content-type: content-transfer-encoding:content-transfer-encoding; bh=jBdFswpyCBTDK2dKrzueMAHhV5qXZdDd+QKf6v8WcaM=; b=ifxs53WrrPbFe6AJeDfGe5jYNHxSWGhouY8YzIRPtLkEOAlIdEQKcJmYWDsvDdmIEz/wx+ mJjXz8ocaPql51NCU/0faEOCSacOVw32pI/eqpJtyDP5nDa0/Yt7dF8q0/mZ4IT4I4eNwv MJgE/vJsBs3wTVaoZ9DJ3im0k4NLdcL3Ve7/ea+BwJtKauCfFt6uR78Dqehm31IEoVEtCn 7XdQc0oqnXlSdzm3humu7b3SajWQ+diW4SlEa6Fw/rRDlJPMB20GO2T+94a3b7bLpaxARK lzIF3Yfpm2fr1wLdAGVOq2PawxKFZHCAs5dw2VPPunp0akuBMfJpjUdSNaftwQ== Received: from gitrepo.freebsd.org (gitrepo.freebsd.org [IPv6:2610:1c1:1:6068::e6a:5]) by mxrelay.nyi.freebsd.org (Postfix) with ESMTP id 4hW3Bv0sFpzB6y for ; Thu, 27 Aug 2026 14:03:19 +0000 (UTC) (envelope-from git@FreeBSD.org) Received: from git (uid 1279) (envelope-from git@FreeBSD.org) id 27e92 by gitrepo.freebsd.org (DragonFly Mail Agent v0.13+ on gitrepo.freebsd.org); Thu, 27 Aug 2026 14:03:19 +0000 To: src-committers@FreeBSD.org, dev-commits-src-all@FreeBSD.org, dev-commits-src-main@FreeBSD.org From: Alexander Leidinger Subject: git: d00da14532bc - main - hwpmc tests: process-attachment lifecycle and ownership cases List-Id: Commit messages for the main branch of the src repository List-Archive: https://lists.freebsd.org/archives/dev-commits-src-main List-Help: List-Post: List-Subscribe: List-Unsubscribe: X-BeenThere: dev-commits-src-main@freebsd.org Sender: owner-dev-commits-src-main@FreeBSD.org List-Id: List-Post: List-Help: List-Subscribe: List-Unsubscribe: List-Owner: Precedence: list MIME-Version: 1.0 Content-Type: text/plain; charset=utf-8 Content-Transfer-Encoding: 8bit X-Git-Committer: netchild X-Git-Repository: src X-Git-Refname: refs/heads/main X-Git-Reftype: branch X-Git-Commit: d00da14532bc4408f3e66535c88276d00905af7c Auto-Submitted: auto-generated Date: Thu, 27 Aug 2026 14:03:19 +0000 Message-Id: <6a9043a7.27e92.6a5a5f9b@gitrepo.freebsd.org> The branch main has been updated by netchild: URL: https://cgit.FreeBSD.org/src/commit/?id=d00da14532bc4408f3e66535c88276d00905af7c commit d00da14532bc4408f3e66535c88276d00905af7c Author: Alexander Leidinger AuthorDate: 2026-08-25 18:55:24 +0000 Commit: Alexander Leidinger CommitDate: 2026-08-27 14:02:42 +0000 hwpmc tests: process-attachment lifecycle and ownership cases Seven ATF cases covering process-attachment teardown orderings: a target that exits before it is detached, the owner that exits before its target (hwpmc's other unlink path), releasing a still-running attached PMC, row exhaustion with out-of-order release, and PMC_F_DESCENDANTS inheritance including a fork storm. All pass on a debug (INVARIANTS+WITNESS) and a KASAN kernel. MFC after: 1 month MFC to: stable/15 MFC to: stable/14 Assisted-by: Claude Code (Opus 5) --- tests/sys/pmc/Makefile | 1 + tests/sys/pmc/pmc_lifecycle_test.c | 436 +++++++++++++++++++++++++++++++++++++ 2 files changed, 437 insertions(+) diff --git a/tests/sys/pmc/Makefile b/tests/sys/pmc/Makefile index c4fc98e3b6d4..b0b8d2108ff1 100644 --- a/tests/sys/pmc/Makefile +++ b/tests/sys/pmc/Makefile @@ -3,6 +3,7 @@ TESTSDIR= ${TESTSBASE}/sys/pmc ATF_TESTS_C+= pmc_api_test ATF_TESTS_C+= pmc_detach_test ATF_TESTS_C+= pmc_exec_test +ATF_TESTS_C+= pmc_lifecycle_test ATF_TESTS_C+= pmc_wrap_test LIBADD+= pmc diff --git a/tests/sys/pmc/pmc_lifecycle_test.c b/tests/sys/pmc/pmc_lifecycle_test.c new file mode 100644 index 000000000000..2107c772b08e --- /dev/null +++ b/tests/sys/pmc/pmc_lifecycle_test.c @@ -0,0 +1,436 @@ +/*- + * SPDX-License-Identifier: BSD-2-Clause + * + * Copyright (c) 2026 Alexander Leidinger + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other advertising materials provided with the + * distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +/** + * @file + * Tests for hwpmc(4)'s process-attachment lifecycle: what happens as a PMC + * is attached, inherited, and torn down, especially when the teardown does + * not happen in the tidy order the common path assumes. + * + * The orderings driven here are a target that exits before it is + * detached, an owner that exits before its target (which reaches the + * REMOVE side of pmc_find_process_descriptor()), + * a release of a still-running attached PMC, row exhaustion released out + * of order, and PMC_F_DESCENDANTS, where the teardown has to account for a + * target the owner never attached by hand. + * + * All PMCs are SOFT-class, so nothing here needs a hardware counter. + */ + +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +static void +require_hwpmc(void) +{ + + if (pmc_init() != 0) + atf_tc_skip("hwpmc(4) is not available"); +} + +/** + * @internal + * Allocate the first SOFT event this mode and flag set accept. Which event + * it is does not matter; that one exists is what the case needs. + */ +static pmc_id_t +allocate_soft_pmc_flags(enum pmc_mode mode, uint32_t flags) +{ + const char **names; + char spec[128]; + pmc_id_t id; + int nnames, i; + + if (pmc_event_names_of_class(PMC_CLASS_SOFT, &names, &nnames) != 0) + return (PMC_ID_INVALID); + for (i = 0; i < nnames; i++) { + (void)snprintf(spec, sizeof(spec), "SOFT-%s", names[i]); + if (pmc_allocate(spec, mode, flags, PMC_CPU_ANY, &id, 0) == 0) + return (id); + } + return (PMC_ID_INVALID); +} + +static pmc_id_t +require_soft_pmc(enum pmc_mode mode) +{ + pmc_id_t id; + + if ((id = allocate_soft_pmc_flags(mode, 0)) == PMC_ID_INVALID) + atf_tc_skip("no SOFT-class PMC is allocatable"); + return (id); +} + +/** + * @internal + * A child that does nothing until killed, for use as an attach target. + */ +static pid_t +spawn_idle_child(void) +{ + pid_t pid; + + pid = fork(); + if (pid == 0) { + for (;;) + (void)pause(); + } + return (pid); +} + +static void +reap(pid_t pid) +{ + int status; + + (void)kill(pid, SIGKILL); + (void)waitpid(pid, &status, 0); +} + +/** + * @internal + * The bookkeeping around attach and detach. + */ +ATF_TC_WITHOUT_HEAD(attach_detach_bookkeeping); +ATF_TC_BODY(attach_detach_bookkeeping, tc) +{ + pmc_id_t id; + pid_t other; + + require_hwpmc(); + id = require_soft_pmc(PMC_MODE_TC); + other = spawn_idle_child(); + ATF_REQUIRE(other > 0); + + ATF_REQUIRE_MSG(pmc_attach(id, getpid()) == 0, "pmc_attach: %s", + strerror(errno)); + + /* A second attach of the same target must be refused, not doubled. */ + errno = 0; + ATF_CHECK_MSG(pmc_attach(id, getpid()) != 0, + "a second attach of the same target succeeded"); + + /* Detaching a pid that was never attached must be refused. */ + errno = 0; + ATF_CHECK_MSG(pmc_detach(id, other) != 0, + "detached a target that was never attached"); + + /* One detach undoes the one attach. */ + ATF_CHECK_MSG(pmc_detach(id, getpid()) == 0, "pmc_detach: %s", + strerror(errno)); + + /* A second detach must now be refused. */ + errno = 0; + ATF_CHECK_MSG(pmc_detach(id, getpid()) != 0, + "a second detach of the same target succeeded"); + + ATF_CHECK_MSG(pmc_release(id) == 0, "pmc_release: %s", + strerror(errno)); + reap(other); +} + +/** + * @internal + * Detach a target that has already exited and been reaped. The + * kernel's own eventhandler removes an exiting process's descriptor, so by + * the time we detach there is nothing there; it must say so cleanly, and + * the PMC must still release. + */ +ATF_TC_WITHOUT_HEAD(detach_after_target_reaped); +ATF_TC_BODY(detach_after_target_reaped, tc) +{ + pmc_id_t id; + pid_t target; + + require_hwpmc(); + id = require_soft_pmc(PMC_MODE_TC); + target = spawn_idle_child(); + ATF_REQUIRE(target > 0); + + ATF_REQUIRE_MSG(pmc_attach(id, target) == 0, "pmc_attach: %s", + strerror(errno)); + + /* Kill and fully reap the target before detaching. */ + reap(target); + + errno = 0; + ATF_CHECK_MSG(pmc_detach(id, target) != 0, + "detached a target that had exited and been reaped"); + + ATF_CHECK_MSG(pmc_release(id) == 0, "pmc_release: %s", + strerror(errno)); +} + +/** + * @internal + * Release a PMC that is still attached and still running, with no + * prior detach or stop: the release path has to reclaim the reference + * the running PMC still holds rather than wedge. + */ +ATF_TC_WITHOUT_HEAD(release_running_attached); +ATF_TC_BODY(release_running_attached, tc) +{ + pmc_id_t id; + + require_hwpmc(); + id = require_soft_pmc(PMC_MODE_TC); + + ATF_REQUIRE_MSG(pmc_attach(id, getpid()) == 0, "pmc_attach: %s", + strerror(errno)); + ATF_REQUIRE_MSG(pmc_start(id) == 0, "pmc_start: %s", strerror(errno)); + + /* Run a little, so the PMC is actually loaded on a CPU. */ + { + volatile unsigned long s = 0; + int i; + + for (i = 0; i < 5000000; i++) + s += i; + } + (void)usleep(100000); + + /* Straight to release: no stop, no detach. */ + ATF_CHECK_MSG(pmc_release(id) == 0, + "release of a running, attached PMC failed: %s", strerror(errno)); + + /* The driver is still usable afterwards. */ + { + pmc_id_t again = allocate_soft_pmc_flags(PMC_MODE_TC, 0); + + ATF_CHECK_MSG(again != PMC_ID_INVALID, + "hwpmc is unusable after the release"); + if (again != PMC_ID_INVALID) + (void)pmc_release(again); + } +} + +/** + * @internal + * The owner exits before the target. A child process is the owner: + * it allocates a PMC and attaches it to a sibling that outlives it, then + * exits without releasing. The kernel tears the owner's PMCs down through + * the REMOVE side of pmc_find_process_descriptor() - the other of its two + * unlink mechanisms, which no test exercised. The parent then confirms + * the driver is still usable and the target still alive. + */ +ATF_TC_WITHOUT_HEAD(owner_exit_before_target); +ATF_TC_BODY(owner_exit_before_target, tc) +{ + pmc_id_t id; + pid_t target, owner; + int status; + + require_hwpmc(); + + target = spawn_idle_child(); + ATF_REQUIRE(target > 0); + + owner = fork(); + ATF_REQUIRE(owner >= 0); + if (owner == 0) { + /* Owner: attach to the sibling target, then just exit. */ + if (pmc_init() != 0) + _exit(10); + id = allocate_soft_pmc_flags(PMC_MODE_TC, 0); + if (id == PMC_ID_INVALID) + _exit(11); + if (pmc_attach(id, target) != 0) + _exit(12); + if (pmc_start(id) != 0) + _exit(13); + /* Exit with the PMC still allocated, attached and running. */ + _exit(0); + } + + ATF_REQUIRE(waitpid(owner, &status, 0) == owner); + ATF_REQUIRE_MSG(WIFEXITED(status), + "the owner did not exit normally (it may have panicked the " + "kernel; status 0x%x)", status); + ATF_REQUIRE_MSG(WEXITSTATUS(status) == 0, + "the owner's setup failed (exit %d): 10=init 11=alloc 12=attach " + "13=start", WEXITSTATUS(status)); + + /* + * The owner is gone. Its descriptor teardown must have run without + * wedging: prove the driver still works and the target is still here. + */ + id = allocate_soft_pmc_flags(PMC_MODE_TC, 0); + ATF_CHECK_MSG(id != PMC_ID_INVALID, + "hwpmc is unusable after the owner exited with a live target"); + if (id != PMC_ID_INVALID) { + ATF_CHECK(pmc_attach(id, getpid()) == 0); + ATF_CHECK(pmc_detach(id, getpid()) == 0); + ATF_CHECK(pmc_release(id) == 0); + } + ATF_CHECK_MSG(kill(target, 0) == 0, + "the target did not survive the owner's exit"); + reap(target); +} + +/** + * @internal + * Allocate SOFT PMCs until the rows are exhausted, then release them + * in an order other than allocation order. Exercises the row free-list + * against out-of-order frees. + */ +ATF_TC_WITHOUT_HEAD(exhaust_then_release_reverse); +ATF_TC_BODY(exhaust_then_release_reverse, tc) +{ + pmc_id_t ids[64]; + int n, i; + + require_hwpmc(); + + n = 0; + while (n < (int)nitems(ids)) { + pmc_id_t id = allocate_soft_pmc_flags(PMC_MODE_TC, 0); + + if (id == PMC_ID_INVALID) + break; + ids[n++] = id; + } + ATF_REQUIRE_MSG(n > 0, "not one SOFT PMC could be allocated"); + + /* At least one allocation should have failed at exhaustion. */ + ATF_CHECK_MSG(n < (int)nitems(ids), + "allocation never hit a limit in %zu tries", nitems(ids)); + + /* Release in reverse order; every one must succeed. */ + for (i = n - 1; i >= 0; i--) + ATF_CHECK_MSG(pmc_release(ids[i]) == 0, + "reverse release of id 0x%08x (slot %d) failed: %s", + ids[i], i, strerror(errno)); +} + +/** + * @internal + * A PMC_F_DESCENDANTS counter, whose child inherits the attachment. + * The owner attaches to itself with descendants set, forks a child that + * does a little work and exits, reaps it, and releases. The teardown has + * to account for the child the owner never attached to by hand. + */ +ATF_TC_WITHOUT_HEAD(descendants_inherit_and_release); +ATF_TC_BODY(descendants_inherit_and_release, tc) +{ + pmc_id_t id; + pid_t child; + int status; + + require_hwpmc(); + id = allocate_soft_pmc_flags(PMC_MODE_TC, PMC_F_DESCENDANTS); + if (id == PMC_ID_INVALID) + atf_tc_skip("no SOFT PMC allocatable with PMC_F_DESCENDANTS"); + + ATF_REQUIRE_MSG(pmc_attach(id, getpid()) == 0, "pmc_attach: %s", + strerror(errno)); + ATF_REQUIRE_MSG(pmc_start(id) == 0, "pmc_start: %s", strerror(errno)); + + child = fork(); + ATF_REQUIRE(child >= 0); + if (child == 0) { + volatile unsigned long s = 0; + int i; + + for (i = 0; i < 2000000; i++) + s += i; + _exit(0); + } + ATF_REQUIRE(waitpid(child, &status, 0) == child); + ATF_CHECK_MSG(WIFEXITED(status) && WEXITSTATUS(status) == 0, + "the descendant did not exit cleanly (status 0x%x)", status); + + ATF_CHECK(pmc_stop(id) == 0); + ATF_CHECK_MSG(pmc_release(id) == 0, + "release of a descendants PMC failed: %s", strerror(errno)); +} + +/** + * @internal + * The fork-storm variant: descendants set, then many short-lived children, + * so several inherit-and-exit teardowns overlap, then release. + */ +ATF_TC_WITHOUT_HEAD(descendants_fork_storm); +ATF_TC_BODY(descendants_fork_storm, tc) +{ + pmc_id_t id; + int i, status; + + require_hwpmc(); + id = allocate_soft_pmc_flags(PMC_MODE_TC, PMC_F_DESCENDANTS); + if (id == PMC_ID_INVALID) + atf_tc_skip("no SOFT PMC allocatable with PMC_F_DESCENDANTS"); + + ATF_REQUIRE_MSG(pmc_attach(id, getpid()) == 0, "pmc_attach: %s", + strerror(errno)); + ATF_REQUIRE_MSG(pmc_start(id) == 0, "pmc_start: %s", strerror(errno)); + + for (i = 0; i < 32; i++) { + pid_t c = fork(); + + if (c == 0) { + volatile unsigned long s = 0; + int j; + + for (j = 0; j < 200000; j++) + s += j; + _exit(0); + } + if (c > 0) + (void)waitpid(c, &status, 0); + } + + ATF_CHECK(pmc_stop(id) == 0); + ATF_CHECK_MSG(pmc_release(id) == 0, + "release after a descendants fork storm failed: %s", + strerror(errno)); +} + +ATF_TP_ADD_TCS(tp) +{ + + ATF_TP_ADD_TC(tp, attach_detach_bookkeeping); + ATF_TP_ADD_TC(tp, detach_after_target_reaped); + ATF_TP_ADD_TC(tp, release_running_attached); + ATF_TP_ADD_TC(tp, owner_exit_before_target); + ATF_TP_ADD_TC(tp, exhaust_then_release_reverse); + ATF_TP_ADD_TC(tp, descendants_inherit_and_release); + ATF_TP_ADD_TC(tp, descendants_fork_storm); + + return (atf_no_error()); +}