Page MenuHomeFreeBSD

D60571.id189326.diff
No OneTemporary

D60571.id189326.diff

diff --git a/lib/libpmc/Makefile b/lib/libpmc/Makefile
--- a/lib/libpmc/Makefile
+++ b/lib/libpmc/Makefile
@@ -77,6 +77,7 @@
MAN+= pmc.ibs.3
MAN+= pmc.ivybridge.3
MAN+= pmc.ivybridgexeon.3
+MAN+= pmc.lbr.3
MAN+= pmc.perf.3
MAN+= pmc.rapl.3
MAN+= pmc.sandybridge.3
diff --git a/lib/libpmc/libpmc.c b/lib/libpmc/libpmc.c
--- a/lib/libpmc/libpmc.c
+++ b/lib/libpmc/libpmc.c
@@ -550,6 +550,7 @@
#define K8_KW_COUNT "count"
#define K8_KW_EDGE "edge"
#define K8_KW_INV "inv"
+#define K8_KW_LBR "lbr"
#define K8_KW_MASK "mask"
#define K8_KW_OS "os"
#define K8_KW_USR "usr"
@@ -658,6 +659,8 @@
pmc_config->pm_caps |= PMC_CAP_EDGE;
} else if (KWMATCH(p, K8_KW_INV)) {
pmc_config->pm_caps |= PMC_CAP_INVERT;
+ } else if (KWMATCH(p, K8_KW_LBR)) {
+ pmc_config->pm_caps |= PMC_CAP_LBR;
} else if (KWPREFIXMATCH(p, K8_KW_MASK "=")) {
if ((n = pmc_parse_mask(pmask, p, &evmask)) < 0)
return (-1);
@@ -1262,12 +1265,13 @@
uint64_t count)
{
size_t n;
- int retval;
- char *r, *spec_copy;
+ int lbr, retval, unknown_qualifier;
+ uint32_t caps;
+ char *p, *r, *spec_copy;
const char *ctrname;
const struct pmc_event_descr *ev;
const struct pmc_event_alias *alias;
- struct pmc_op_pmcallocate pmc_config;
+ struct pmc_op_pmcallocate pmc_config, pmu_config;
const struct pmc_class_descr *pcd;
spec_copy = NULL;
@@ -1292,11 +1296,47 @@
* continue with searching the regular event tables.
*/
r = spec_copy = strdup(ctrspec);
+ if (spec_copy == NULL)
+ goto out;
ctrname = strsep(&r, ",");
if (pmc_pmu_enabled()) {
- errno = pmc_pmu_pmcallocate(ctrname, &pmc_config);
- if (errno == 0)
+ /*
+ * Keep the original configuration for legacy event lookup:
+ * pmc_pmu_pmcallocate() clears capabilities on failure.
+ *
+ * pmu-events specifiers historically ignore qualifiers. Only
+ * an LBR request honors "usr" and "os", which also select the
+ * branch filter (and thereby the privilege needed), and
+ * rejects anything else instead of silently ignoring it.
+ */
+ pmu_config = pmc_config;
+ lbr = unknown_qualifier = 0;
+ caps = 0;
+ while ((p = strsep(&r, ",")) != NULL) {
+ if (KWMATCH(p, "lbr"))
+ lbr = 1;
+ else if (KWMATCH(p, "usr"))
+ caps |= PMC_CAP_USER;
+ else if (KWMATCH(p, "os"))
+ caps |= PMC_CAP_SYSTEM;
+ else if (KWPREFIXMATCH(p, "lbr=")) {
+ errno = EINVAL;
+ goto out;
+ } else
+ unknown_qualifier = 1;
+ }
+ if (lbr)
+ pmu_config.pm_caps |= caps | PMC_CAP_LBR;
+ errno = pmc_pmu_pmcallocate(ctrname, &pmu_config);
+ if (errno == 0) {
+ /* Legacy event names keep their own qualifiers. */
+ if (lbr && unknown_qualifier) {
+ errno = EINVAL;
+ goto out;
+ }
+ pmc_config = pmu_config;
goto found;
+ }
if (errno == EOPNOTSUPP)
goto out;
}
@@ -1313,6 +1353,8 @@
if (spec_copy == NULL)
spec_copy = strdup(ctrspec);
+ if (spec_copy == NULL)
+ goto out;
r = spec_copy;
ctrname = strsep(&r, ",");
diff --git a/lib/libpmc/pmc.3 b/lib/libpmc/pmc.3
--- a/lib/libpmc/pmc.3
+++ b/lib/libpmc/pmc.3
@@ -21,7 +21,7 @@
.\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
.\" SUCH DAMAGE.
.\"
-.Dd June 23, 2023
+.Dd October 9, 2026
.Dt PMC 3
.Os
.Sh NAME
@@ -114,6 +114,7 @@
System scope sampling PMCs.
.It
Process scope sampling PMCs.
+This includes self-attached PMCs that request AMD LBR capture.
.It
Process scope counting PMCs that have been configured to report PMC
readings on process context switches or process exits.
@@ -282,6 +283,10 @@
.It Li PMC_CAP_INVERT
The ability to invert the sense of the hardware conditions being
measured.
+.It Li PMC_CAP_LBR
+The ability to capture last-branch records with a sampling PMC.
+AMD LBR v2 capture is supported on amd64 as described in
+.Xr pmc.lbr 3 .
.It Li PMC_CAP_PRECISE
The ability to perform precise sampling.
.It Li PMC_CAP_QUALIFIER
@@ -509,7 +514,7 @@
.It Li PMC_CLASS_IAF Ta Xr pmc.iaf 3
.It Li PMC_CLASS_IAP Ta Xr pmc.atom 3 , Xr pmc.core 3 , Xr pmc.core2 3
.It Li PMC_CLASS_IBS Ta Xr pmc.ibs 3
-.It Li PMC_CLASS_K8 Ta Xr pmc.amd 3
+.It Li PMC_CLASS_K8 Ta Xr pmc.amd 3 , Xr pmc.lbr 3
.It Li PMC_CLASS_RAPL Ta Xr pmc.rapl 3
.It Li PMC_CLASS_PERF Ta Xr pmc.perf 3
.It Li PMC_CLASS_TSC Ta Xr pmc.tsc 3
@@ -565,6 +570,7 @@
.Xr pmc.ibs 3 ,
.Xr pmc.ivybridge 3 ,
.Xr pmc.ivybridgexeon 3 ,
+.Xr pmc.lbr 3 ,
.Xr pmc.perf 3 ,
.Xr pmc.rapl 3 ,
.Xr pmc.sandybridge 3 ,
diff --git a/lib/libpmc/pmc.lbr.3 b/lib/libpmc/pmc.lbr.3
new file mode 100644
--- /dev/null
+++ b/lib/libpmc/pmc.lbr.3
@@ -0,0 +1,187 @@
+.\" SPDX-License-Identifier: BSD-2-Clause
+.\"
+.\" Copyright (c) 2026 Advanced Micro Devices, Inc.
+.\"
+.Dd October 9, 2026
+.Dt PMC.LBR 3
+.Os
+.Sh NAME
+.Nm pmc.lbr
+.Nd Last Branch Record capture for AMD CPUs
+.Sh LIBRARY
+.Lb libpmc
+.Sh SYNOPSIS
+.In pmc.h
+.Sh DESCRIPTION
+AMD Last Branch Record version 2 (LBR v2) records taken branches in a
+per-CPU ring of From/To address pairs.
+Capture is supported only on amd64 kernels, on processors that advertise
+PerfMonV2 and LbrExtV2 with an LBR depth from 1 through 16.
+The ring freezes when a core counter overflows on processors with
+LbrAndPmcFreeze, so the records describe the branches leading up to each
+sampling interrupt.
+.Pp
+LBR capture is not a PMC class of its own.
+It is requested with the
+.Li lbr
+event qualifier, which requests
+.Dv PMC_CAP_LBR ,
+on a core counter allocated in thread-sampling
+.Pq Dv PMC_MODE_TS
+or system-sampling
+.Pq Dv PMC_MODE_SS
+mode.
+For example:
+.Bd -literal -offset indent
+pmcstat -P ex_ret_brn_tkn,lbr,usr -O sample.out command
+pmcstat -R sample.out
+.Ed
+.Pp
+The qualifier takes no value.
+For events named by the pmu-events tables, it may be combined with
+.Li usr
+and
+.Li os ;
+other qualifiers on these LBR requests are rejected.
+Legacy AMD event names retain the qualifiers documented in
+.Xr pmc.amd 3 .
+.Pp
+Allocation fails on unsupported processors or kernels, for counting modes,
+and for PMCs other than AMD core counters, including IBS, L3, and Data Fabric
+PMCs.
+The
+.Li usr
+and
+.Li os
+qualifiers also scope the branch records:
+.Li usr
+alone suppresses branches ending in CPL 0, and
+.Li os
+alone suppresses branches ending in CPL greater than 0.
+Omitting both qualifiers or specifying both selects the same normalized
+filter, which records both user and kernel branches.
+.Pp
+Branch records that may contain kernel addresses disclose kernel layout and
+control flow.
+Unless
+.Va security.bsd.unprivileged_syspmcs
+is set, an LBR PMC without the
+.Li usr
+qualifier alone requires the same privilege as a system-wide PMC
+.Pq Dv PRIV_PMC_SYSTEM ,
+even in thread-sampling mode, and is never available inside a jail.
+Allocation fails with
+.Er EPERM
+otherwise.
+User-only LBR PMCs follow the ordinary process-mode rules.
+.Pp
+The LBR_SELECT register is shared by all counters on a CPU, and a
+thread-sampling PMC can run on any CPU.
+Consequently all allocated thread-sampling LBR PMCs must use the same
+normalized filter.
+All allocated system-sampling LBR PMCs on a CPU must also use the same filter.
+If any thread-sampling LBR PMC is allocated, every system-sampling LBR PMC
+on every CPU must match its filter.
+System-sampling LBR PMCs on disjoint CPUs may use different filters when no
+thread-sampling LBR PMC is allocated.
+Filters are reserved at allocation and held until release, including while
+the PMC is stopped.
+.Pp
+Samples taken with LBR active set the
+.Dv PMC_CC_F_MULTIPART
+flag and embed a payload of type
+.Dv PMC_CC_MULTIPART_LBR
+in the callchain record, using the layout
+described in
+.Xr pmc.ibs 3 .
+The eight-byte multipart header gives payload lengths in native pointer-sized
+slots; each LBR record contains two 64-bit words.
+The payload holds up to 16 valid branch records, most recent branch first.
+Each record contains the raw From then To register image.
+The instruction pointer is bits [57:0] of each word, sign-extended from bit 57.
+The kernel rewrites these bits from the processor's implemented linear-address
+width, so the result is a canonical address on every processor.
+Bit 63 of the From word flags a mispredicted branch; bit 63 of the To word
+flags a valid (retired) record and bit 62 a speculative one.
+Invalid records are dropped in the kernel and do not appear in the payload.
+.Pp
+Every sample of an LBR PMC carries an LBR payload part, but the payload
+is empty when the ring could not be read for that interrupt: shortly after
+a context switch or exec reset, while hwpmc updates the recording state, or
+for an overflow of a PMC that was not yet active on the CPU.
+On processors with LbrAndPmcFreeze, a delayed interrupt whose frozen ring
+was invalidated by a reset also carries an empty payload.
+The
+.Va kern.hwpmc.amd_lbr_empty_samples
+sysctl counts samples that were logged empty for these reasons; an
+empty payload after a reset with no branches since is not counted.
+The
+.Va kern.hwpmc.amd_lbr_depth
+sysctl reports the ring depth, or 0 when LBR capture is unavailable.
+.Pp
+The textual log decoder in
+.Xr pmcstat 8
+prints an
+.Li lbr
+entry for each From/To pair and marks mispredicted and speculative records.
+Callchain analysis and JSON callchain output use the sampled instruction
+pointers following the payload, without treating branch records as stack frames.
+Disabling callchain capture with
+.Fl N
+in
+.Xr pmcstat 8
+retains the LBR payload and the sampled instruction pointer.
+.Pp
+A self-attached thread-sampling LBR PMC requires a configured log file before it
+can be started.
+Applications must call
+.Xr pmc_configure_logfile 3
+before
+.Xr pmc_start 3 .
+These samples are written to the PMC log rather than the
+.Xr profil 2
+buffer, so that the branch payload is retained.
+.Sh SEE ALSO
+.Xr pmc 3 ,
+.Xr pmc.amd 3 ,
+.Xr pmc.ibs 3 ,
+.Xr pmc_configure_logfile 3 ,
+.Xr pmc_start 3 ,
+.Xr pmclog 3 ,
+.Xr hwpmc 4 ,
+.Xr pmcstat 8
+.Pp
+The record format is described in the
+.Rs
+.%B "Processor Programming Reference (PPR) for AMD Family 19h Model 11h"
+.%N "Publication No. 55901"
+.%Q "Advanced Micro Devices, Inc."
+.Re
+.Sh HISTORY
+LBR v2 capture support was first introduced in
+.Fx 16.0 .
+.Sh CAVEATS
+LBR records are not tagged with an address-space identifier.
+When any LBR PMC is active, hwpmc resets the ring across context switches and
+exec to avoid attributing stale branch addresses to the new address space.
+On CPUs without LbrAndPmcFreeze the ring is stopped while the interrupt handler
+reads it, but records may still include branches taken during interrupt entry.
+.Pp
+Instruction fusion can make the hardware report a From address that points
+several bytes before the actual branch, for example to the comparison in a
+fused compare-and-branch pair.
+Records are exported without adjusting these addresses.
+.Pp
+User-only sampling drops records whose From or To address is a kernel
+address, in addition to using the hardware CPL filter, which does not
+suppress returns from the kernel to user mode.
+Native i386 kernels do not support AMD LBR allocation.
+.Pp
+LBR capture is disabled, and the rest of hwpmc remains usable, when the LBR
+registers cannot be read or are already enabled when the module loads,
+for example by firmware or a hypervisor.
+.Pp
+Branches taken by a
+.Xr bhyve 8
+guest are recorded in the host ring unless the hypervisor virtualizes LBR,
+and are attributed to the thread running the virtual CPU.
diff --git a/tests/sys/pmc/pmc_multipart_test.c b/tests/sys/pmc/pmc_multipart_test.c
--- a/tests/sys/pmc/pmc_multipart_test.c
+++ b/tests/sys/pmc/pmc_multipart_test.c
@@ -88,14 +88,38 @@
}
ATF_TC_BODY(multipart_word_arithmetic, tc)
{
- ATF_CHECK_EQ(2, PMC_MULTIPART_HEADER_WORDS_FOR(4));
- ATF_CHECK_EQ(1, PMC_MULTIPART_HEADER_WORDS_FOR(8));
- ATF_CHECK_EQ(2, PMC_MULTIPART_64BIT_WORDS_FOR(1, 4));
- ATF_CHECK_EQ(1, PMC_MULTIPART_64BIT_WORDS_FOR(1, 8));
- ATF_CHECK_EQ(67, PMC_MULTIPART_SAMPLE_MIN_WORDS_FOR(4));
- ATF_CHECK_EQ(34, PMC_MULTIPART_SAMPLE_MIN_WORDS_FOR(8));
- ATF_CHECK_EQ(PMC_MULTIPART_HEADER_WORDS_FOR(sizeof(uintfptr_t)),
- PMC_MULTIPART_HEADER_WORDS);
+ struct {
+ uint8_t header[8];
+ uint64_t branches[16][2];
+ uint8_t pc[8];
+ } wire;
+ const uint8_t *bytes;
+ size_t width, begin, end, pc, i;
+
+ /*
+ * Use an independent byte-layout fixture. Both widths must locate the
+ * entire final To value and the PC after it, not overlap either one.
+ */
+ memset(&wire, 0, sizeof(wire));
+ wire.header[7] = 0xa5;
+ wire.branches[0][0] = UINT64_C(0x8200000012340000);
+ wire.branches[15][1] = UINT64_C(0xc00000005678000f);
+ for (i = 0; i < sizeof(wire.pc); i++)
+ wire.pc[i] = 0x5a + i;
+ bytes = (const uint8_t *)&wire;
+ for (width = 4; width <= 8; width *= 2) {
+ begin = PMC_MULTIPART_HEADER_WORDS_FOR(width) * width;
+ end = begin + PMC_MULTIPART_64BIT_WORDS_FOR(32, width) * width;
+ pc = (PMC_MULTIPART_SAMPLE_MIN_WORDS_FOR(width) - 1) * width;
+ ATF_REQUIRE(begin + sizeof(uint64_t) <= sizeof(wire));
+ ATF_REQUIRE(end >= sizeof(uint64_t) && end <= sizeof(wire));
+ ATF_REQUIRE(pc + width <= sizeof(wire));
+ ATF_CHECK_EQ(0, memcmp(bytes + begin, &wire.branches[0][0],
+ sizeof(uint64_t)));
+ ATF_CHECK_EQ(0, memcmp(bytes + end - sizeof(uint64_t),
+ &wire.branches[15][1], sizeof(uint64_t)));
+ ATF_CHECK_EQ(0, memcmp(bytes + pc, wire.pc, width));
+ }
}
ATF_TC(multipart_offset_bounds);
@@ -114,7 +138,7 @@
memset(pc, 0, sizeof(pc));
hdr = (uint8_t *)pc;
payload_words = PMC_MULTIPART_PAYLOAD_WORDS(PMC_MULTIPART_MAX_PAYLOAD64);
- hdr[0] = PMC_CC_MULTIPART_IBS_OP;
+ hdr[0] = PMC_CC_MULTIPART_LBR;
hdr[1] = payload_words;
hdr[2] = PMC_CC_MULTIPART_CALLCHAIN;
hdr[3] = 0;
@@ -122,11 +146,28 @@
PMC_MULTIPART_SAMPLE_MIN_WORDS, &off));
ATF_CHECK_EQ(PMC_MULTIPART_SAMPLE_MIN_WORDS - 1, off);
+ /* A complete payload with no PC is valid; callers must test off < npc. */
+ ATF_REQUIRE(pmclog_multipart_callchain_offset(pc,
+ PMC_MULTIPART_SAMPLE_MIN_WORDS - 1, &off));
+ ATF_CHECK_EQ(PMC_MULTIPART_SAMPLE_MIN_WORDS - 1, off);
+ ATF_CHECK(!pmclog_multipart_callchain_offset(NULL, 0, &off));
+ ATF_CHECK_EQ(0, off);
ATF_CHECK(!pmclog_multipart_callchain_offset(pc, 0, &off));
ATF_CHECK(!pmclog_multipart_callchain_offset(pc,
PMC_MULTIPART_HEADER_WORDS - 1, &off));
ATF_CHECK(!pmclog_multipart_callchain_offset(pc,
PMC_MULTIPART_SAMPLE_MIN_WORDS - 2, &off));
+ ATF_CHECK_EQ(0, off);
+
+ /* Empty LBR data and an entirely empty multipart header are both safe. */
+ hdr[1] = 0;
+ ATF_REQUIRE(pmclog_multipart_callchain_offset(pc,
+ PMC_MULTIPART_HEADER_WORDS, &off));
+ ATF_CHECK_EQ(PMC_MULTIPART_HEADER_WORDS, off);
+ memset(pc, 0, sizeof(pc));
+ ATF_REQUIRE(pmclog_multipart_callchain_offset(pc,
+ PMC_MULTIPART_HEADER_WORDS, &off));
+ ATF_CHECK_EQ(PMC_MULTIPART_HEADER_WORDS, off);
}
ATF_TC(pmclog_callchain_single_pc);
@@ -197,6 +238,124 @@
ATF_CHECK_EQ((uintfptr_t)0, ev.pl_u.pl_cc.pl_pc[0]);
}
+static void
+check_lbr_roundtrip(uint32_t nbranch, uint32_t flags)
+{
+ struct pmclog_callchain rec;
+ struct pmclog_ev ev;
+ uint64_t raw[32], last_to;
+ uint32_t i, npc, off, payload_words;
+ uint8_t *hdr;
+
+ ATF_REQUIRE(nbranch > 0 && nbranch <= nitems(raw) / 2);
+ for (i = 0; i < nbranch; i++) {
+ /* Raw metadata bits and distinct high/low halves must survive. */
+ raw[2 * i] = UINT64_C(0x8200000012340000) + i;
+ raw[2 * i + 1] = UINT64_C(0xc000000056780000) + i;
+ }
+ payload_words = PMC_MULTIPART_PAYLOAD_WORDS(nbranch * 2);
+ npc = nbranch == 16 ? PMC_MULTIPART_SAMPLE_MIN_WORDS :
+ PMC_MULTIPART_HEADER_WORDS + payload_words + 1;
+ ATF_REQUIRE(npc <= PMC_CALLCHAIN_DEPTH_MAX);
+ init_callchain_record(&rec, npc,
+ PMC_CALLCHAIN_TO_CPUFLAGS(2, flags | PMC_CC_F_MULTIPART));
+ hdr = (uint8_t *)rec.pl_pc;
+ hdr[0] = PMC_CC_MULTIPART_LBR;
+ hdr[1] = payload_words;
+ hdr[2] = PMC_CC_MULTIPART_CALLCHAIN;
+ memcpy(hdr + PMC_MULTIPART_HEADER_LENGTH, raw,
+ nbranch * 2 * sizeof(uint64_t));
+ rec.pl_pc[npc - 1] = (uintfptr_t)0xfeedface;
+
+ parse_record(&rec, npc, &ev);
+ ATF_REQUIRE_EQ(npc, ev.pl_u.pl_cc.pl_npc);
+ ATF_CHECK_EQ(rec.pl_cpuflags, ev.pl_u.pl_cc.pl_cpuflags);
+ ATF_CHECK_EQ(0, memcmp(rec.pl_pc, ev.pl_u.pl_cc.pl_pc,
+ PMC_MULTIPART_HEADER_LENGTH));
+ ATF_REQUIRE(pmclog_multipart_callchain_offset(ev.pl_u.pl_cc.pl_pc,
+ ev.pl_u.pl_cc.pl_npc, &off));
+ ATF_REQUIRE_EQ(npc - 1, off);
+ hdr = (uint8_t *)ev.pl_u.pl_cc.pl_pc;
+ ATF_CHECK_EQ(0, memcmp(hdr + PMC_MULTIPART_HEADER_LENGTH, raw,
+ nbranch * 2 * sizeof(uint64_t)));
+ memcpy(&last_to, hdr + PMC_MULTIPART_HEADER_LENGTH +
+ (nbranch * 2 - 1) * sizeof(uint64_t), sizeof(last_to));
+ ATF_CHECK_EQ(raw[nbranch * 2 - 1], last_to);
+ ATF_CHECK_EQ((uintfptr_t)0xfeedface, ev.pl_u.pl_cc.pl_pc[off]);
+
+#if defined(AMD_LBR_IP)
+ /* These public MD helpers are not available on non-x86 builds. */
+ ATF_CHECK_EQ(UINT64_C(0x56780000) + nbranch - 1, AMD_LBR_IP(last_to));
+ memcpy(&last_to, hdr + PMC_MULTIPART_HEADER_LENGTH, sizeof(last_to));
+ ATF_CHECK_EQ(UINT64_C(0xfe00000012340000), AMD_LBR_IP(last_to));
+#endif
+}
+
+ATF_TC(pmclog_lbr_short_payload);
+ATF_TC_HEAD(pmclog_lbr_short_payload, tc)
+{
+ atf_tc_set_md_var(tc, "descr",
+ "one raw LBR From/To pair and its interrupt PC survive pmclog");
+}
+ATF_TC_BODY(pmclog_lbr_short_payload, tc)
+{
+ check_lbr_roundtrip(1, 0);
+ check_lbr_roundtrip(1, PMC_CC_F_USERSPACE);
+}
+
+ATF_TC(pmclog_lbr_full_payload);
+ATF_TC_HEAD(pmclog_lbr_full_payload, tc)
+{
+ atf_tc_set_md_var(tc, "descr",
+ "all 16 raw LBR pairs, including the final To and interrupt PC, "
+ "survive at the minimum multipart depth");
+}
+ATF_TC_BODY(pmclog_lbr_full_payload, tc)
+{
+ check_lbr_roundtrip(16, 0);
+ check_lbr_roundtrip(16, PMC_CC_F_USERSPACE);
+}
+
+ATF_TC(pmclog_lbr_empty_and_truncated);
+ATF_TC_HEAD(pmclog_lbr_empty_and_truncated, tc)
+{
+ atf_tc_set_md_var(tc, "descr",
+ "multipart bounds distinguish empty LBR data from a missing To word");
+}
+ATF_TC_BODY(pmclog_lbr_empty_and_truncated, tc)
+{
+ struct pmclog_callchain rec;
+ struct pmclog_ev ev;
+ uint32_t npc, off;
+ uint8_t *hdr;
+
+ npc = PMC_MULTIPART_HEADER_WORDS + 1;
+ init_callchain_record(&rec, npc,
+ PMC_CALLCHAIN_TO_CPUFLAGS(0, PMC_CC_F_MULTIPART));
+ hdr = (uint8_t *)rec.pl_pc;
+ hdr[0] = PMC_CC_MULTIPART_LBR;
+ hdr[2] = PMC_CC_MULTIPART_CALLCHAIN;
+ rec.pl_pc[npc - 1] = (uintfptr_t)0x12345678;
+ parse_record(&rec, npc, &ev);
+ ATF_REQUIRE(pmclog_multipart_callchain_offset(ev.pl_u.pl_cc.pl_pc,
+ ev.pl_u.pl_cc.pl_npc, &off));
+ ATF_REQUIRE_EQ(npc - 1, off);
+ ATF_CHECK_EQ((uintfptr_t)0x12345678, ev.pl_u.pl_cc.pl_pc[off]);
+
+ npc = PMC_MULTIPART_HEADER_WORDS + PMC_MULTIPART_PAYLOAD_WORDS(2) - 1;
+ init_callchain_record(&rec, npc,
+ PMC_CALLCHAIN_TO_CPUFLAGS(0, PMC_CC_F_MULTIPART));
+ hdr[0] = PMC_CC_MULTIPART_LBR;
+ hdr[1] = PMC_MULTIPART_PAYLOAD_WORDS(2);
+ hdr[2] = PMC_CC_MULTIPART_CALLCHAIN;
+ parse_record(&rec, npc, &ev);
+ /* The outer record is complete; its advertised multipart data is not. */
+ off = UINT32_MAX;
+ ATF_CHECK(!pmclog_multipart_callchain_offset(ev.pl_u.pl_cc.pl_pc,
+ ev.pl_u.pl_cc.pl_npc, &off));
+ ATF_CHECK_EQ(0, off);
+}
+
ATF_TC(pmclog_callchain_multipart_minimum_capacity);
ATF_TC_HEAD(pmclog_callchain_multipart_minimum_capacity, tc)
{
@@ -231,29 +390,50 @@
ATF_TC_HEAD(pmclog_callchain_fragmented_record, tc)
{
atf_tc_set_md_var(tc, "descr",
- "the pmclog parser waits for the remainder of a truncated record");
+ "the pmclog parser waits for a split LBR To value and preserves "
+ "the subsequent interrupt PC");
}
ATF_TC_BODY(pmclog_callchain_fragmented_record, tc)
{
struct pmclog_callchain rec;
struct pmclog_ev ev;
+ const uint64_t raw[2] = {
+ UINT64_C(0x8200000012345678), UINT64_C(0xc000000076543210)
+ };
void *parser;
- size_t len;
+ size_t len, split;
+ uint32_t npc, off;
+ uint8_t *hdr;
- init_callchain_record(&rec, 1, PMC_CALLCHAIN_TO_CPUFLAGS(2,
- PMC_CC_F_USERSPACE));
- rec.pl_pc[0] = (uintfptr_t)0xabcdef;
- len = callchain_record_size(1);
+ npc = PMC_MULTIPART_HEADER_WORDS + PMC_MULTIPART_PAYLOAD_WORDS(2) + 1;
+ init_callchain_record(&rec, npc, PMC_CALLCHAIN_TO_CPUFLAGS(2,
+ PMC_CC_F_USERSPACE | PMC_CC_F_MULTIPART));
+ hdr = (uint8_t *)rec.pl_pc;
+ hdr[0] = PMC_CC_MULTIPART_LBR;
+ hdr[1] = PMC_MULTIPART_PAYLOAD_WORDS(2);
+ hdr[2] = PMC_CC_MULTIPART_CALLCHAIN;
+ memcpy(hdr + PMC_MULTIPART_HEADER_LENGTH, raw, sizeof(raw));
+ rec.pl_pc[npc - 1] = (uintfptr_t)0xabcdef;
+ len = callchain_record_size(npc);
+ split = offsetof(struct pmclog_callchain, pl_pc) +
+ PMC_MULTIPART_HEADER_LENGTH + sizeof(raw) - sizeof(uint32_t);
parser = pmclog_open(PMCLOG_FD_NONE);
ATF_REQUIRE(parser != NULL);
- ATF_REQUIRE_EQ(0, pmclog_feed(parser, (char *)&rec, len - 1));
+ ATF_REQUIRE_EQ(0, pmclog_feed(parser, (char *)&rec, split));
ATF_CHECK_EQ(-1, pmclog_read(parser, &ev));
ATF_CHECK_EQ(PMCLOG_REQUIRE_DATA, ev.pl_state);
- ATF_REQUIRE_EQ(0, pmclog_feed(parser, ((char *)&rec) + len - 1, 1));
+ ATF_REQUIRE_EQ(0, pmclog_feed(parser, ((char *)&rec) + split,
+ len - split));
ATF_REQUIRE_EQ(0, pmclog_read(parser, &ev));
ATF_REQUIRE_EQ(PMCLOG_OK, ev.pl_state);
- ATF_CHECK_EQ(1, ev.pl_u.pl_cc.pl_npc);
- ATF_CHECK_EQ((uintfptr_t)0xabcdef, ev.pl_u.pl_cc.pl_pc[0]);
+ ATF_CHECK_EQ(npc, ev.pl_u.pl_cc.pl_npc);
+ hdr = (uint8_t *)ev.pl_u.pl_cc.pl_pc;
+ ATF_CHECK_EQ(0, memcmp(hdr + PMC_MULTIPART_HEADER_LENGTH, raw,
+ sizeof(raw)));
+ ATF_REQUIRE(pmclog_multipart_callchain_offset(ev.pl_u.pl_cc.pl_pc,
+ ev.pl_u.pl_cc.pl_npc, &off));
+ ATF_REQUIRE_EQ(npc - 1, off);
+ ATF_CHECK_EQ((uintfptr_t)0xabcdef, ev.pl_u.pl_cc.pl_pc[off]);
pmclog_close(parser);
}
@@ -304,6 +484,9 @@
{
ATF_TP_ADD_TC(tp, multipart_word_arithmetic);
ATF_TP_ADD_TC(tp, multipart_offset_bounds);
+ ATF_TP_ADD_TC(tp, pmclog_lbr_short_payload);
+ ATF_TP_ADD_TC(tp, pmclog_lbr_full_payload);
+ ATF_TP_ADD_TC(tp, pmclog_lbr_empty_and_truncated);
ATF_TP_ADD_TC(tp, pmclog_callchain_single_pc);
ATF_TP_ADD_TC(tp, pmclog_callchain_kernel_and_full_depth);
ATF_TP_ADD_TC(tp, pmclog_callchain_zero_pcs);
diff --git a/usr.sbin/pmcstat/pmcstat.8 b/usr.sbin/pmcstat/pmcstat.8
--- a/usr.sbin/pmcstat/pmcstat.8
+++ b/usr.sbin/pmcstat/pmcstat.8
@@ -23,7 +23,7 @@
.\" out of the use of this software, even if advised of the possibility of
.\" such damage.
.\"
-.Dd August 14, 2026
+.Dd October 9, 2026
.Dt PMCSTAT 8
.Os
.Sh NAME
@@ -120,6 +120,41 @@
documented in
.Xr pmc 3 .
.Pp
+On amd64 kernels with a processor supporting AMD LBR v2, the
+.Li lbr
+event qualifier captures last-branch From/To pairs with core sampling PMCs
+selected by
+.Fl P
+or
+.Fl S .
+It is not supported for counting PMCs selected by
+.Fl p
+or
+.Fl s .
+The
+.Li usr
+and
+.Li os
+qualifiers select the branch privilege filter.
+Omitting both or specifying both selects the same filter.
+Unless the
+.Li usr
+qualifier is given alone, kernel branches may be recorded, and the
+allocation requires the same privilege as a system-wide PMC even with
+.Fl P .
+.Pp
+All allocated process mode LBR PMCs share one normalized filter.
+System mode LBR PMCs on the same CPU must also share a filter.
+When any process mode LBR PMC is allocated, system mode LBR PMCs on every CPU
+must use that filter.
+System mode LBR PMCs on disjoint CPUs may differ when no process mode LBR PMC
+is allocated.
+A filter reservation is retained while its PMC is stopped and ends only when
+the PMC is released.
+See
+.Xr pmc.lbr 3
+for the supported hardware, log format, and filtering restrictions.
+.Pp
A process-mode PMC may be configured to be inheritable by the target
process' current and future children.
.Sh OPTIONS
@@ -186,6 +221,8 @@
.It Fl N
Toggle capturing callchain information for subsequent sampling PMCs.
The default is for sampling PMCs to capture callchain information.
+For LBR sampling, disabling callchain capture retains the branch records and
+the sampled instruction pointer.
.It Fl O Ar logfilename
Send logging output to file
.Ar logfilename .
@@ -481,6 +518,15 @@
If callgraph capture is not desired use:
.Dl "pmcstat -N -S instructions -O /tmp/sample.out"
.Pp
+To record user-mode branches for a command on a supported AMD LBR v2 processor:
+.Dl "pmcstat -P ex_ret_brn_tkn,lbr,usr -O /tmp/lbr.out command"
+To print the log, including the decoded
+.Li lbr
+entries:
+.Dl "pmcstat -R /tmp/lbr.out"
+Each branch entry shows its From and To addresses and any misprediction or
+speculation flags.
+.Pp
To send the generated event log to a remote machine use:
.Dl "pmcstat -S instructions -O remotehost:port"
On the remote machine, the sample log can be collected using
@@ -549,6 +595,7 @@
.Xr nc 1 ,
.Xr execvp 3 ,
.Xr pmc 3 ,
+.Xr pmc.lbr 3 ,
.Xr pmclog 3 ,
.Xr hwpmc 4 ,
.Xr pmccontrol 8 ,
diff --git a/usr.sbin/pmcstat/pmcstat_log.c b/usr.sbin/pmcstat/pmcstat_log.c
--- a/usr.sbin/pmcstat/pmcstat_log.c
+++ b/usr.sbin/pmcstat/pmcstat_log.c
@@ -470,13 +470,41 @@
}
#endif
+#if defined(__amd64__) || defined(__i386__)
+static int
+pmcstat_print_lbr(struct pmclog_ev_callchain *cc, uint32_t offset,
+ uint32_t len64)
+{
+ const char *lbrbuf;
+ uint64_t from, to;
+ uint32_t i;
+
+ if ((len64 & 1) != 0 || len64 > 2 * AMD_LBR_MAX_DEPTH)
+ return (0);
+
+ lbrbuf = (const char *)&cc->pl_pc[offset];
+ /* Raw From/To pairs, top of stack first (see PMC_CC_MULTIPART_LBR). */
+ for (i = 0; i < len64; i += 2) {
+ memcpy(&from, lbrbuf + i * sizeof(from), sizeof(from));
+ memcpy(&to, lbrbuf + (i + 1) * sizeof(to), sizeof(to));
+ PMCSTAT_PRINT_ENTRY("lbr", "%#jx -> %#jx%s%s",
+ (uintmax_t)AMD_LBR_IP(from), (uintmax_t)AMD_LBR_IP(to),
+ (from & AMD_LBR_FROM_MISPREDICT) ? " mispredicted" : "",
+ (to & AMD_LBR_TO_SPEC) ? " speculative" : "");
+ }
+ return (1);
+}
+#endif
+
static int
pmcstat_print_multipart(struct pmclog_ev_callchain *cc)
{
int i;
uint8_t *hdr = (uint8_t *)&cc->pl_pc[0];
uint32_t offset = PMC_MULTIPART_HEADER_WORDS;
- uint32_t words_per_64 = sizeof(uint64_t) / sizeof(uintptr_t);
+#if defined(__amd64__) || defined(__i386__)
+ uint32_t words_per_64 = PMC_MULTIPART_PAYLOAD_WORDS(1);
+#endif
if (cc->pl_npc < offset) {
PMCSTAT_PRINT_ENTRY("truncated multipart record!");
@@ -507,6 +535,12 @@
PMCSTAT_PRINT_ENTRY("truncated multipart record!");
return (cc->pl_npc);
}
+ } else if (type == PMC_CC_MULTIPART_LBR) {
+ if (len % words_per_64 != 0 ||
+ !pmcstat_print_lbr(cc, offset, len / words_per_64)) {
+ PMCSTAT_PRINT_ENTRY("truncated multipart record!");
+ return (cc->pl_npc);
+ }
#endif
} else {
PMCSTAT_PRINT_ENTRY("unsupported multipart type!");

File Metadata

Mime Type
text/plain
Expires
Tue, Oct 20, 11:12 AM (11 h, 25 m)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
41024410
Default Alt Text
D60571.id189326.diff (26 KB)

Event Timeline