Page Menu
Home
FreeBSD
Search
Configure Global Search
Log In
Files
F176510681
D60571.diff
No One
Temporary
Actions
View File
Edit File
Delete File
View Transforms
Subscribe
Mute Notifications
Flag For Later
Award Token
Size
26 KB
Referenced Files
None
Subscribers
None
D60571.diff
View Options
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
Details
Attached
Mime Type
text/plain
Expires
Tue, Oct 20, 10:37 AM (14 h, 38 m)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
41024410
Default Alt Text
D60571.diff (26 KB)
Attached To
Mode
D60571: libpmc, pmcstat: add the lbr qualifier and decode LBR records
Attached
Detach File
Event Timeline
Log In to Comment