Page MenuHomeFreeBSD

D60564.diff
No OneTemporary

D60564.diff

diff --git a/lib/libpmc/libpmc_json.cc b/lib/libpmc/libpmc_json.cc
--- a/lib/libpmc/libpmc_json.cc
+++ b/lib/libpmc/libpmc_json.cc
@@ -26,6 +26,7 @@
*
*/
+#include <sys/param.h>
#include <sys/types.h>
#include <sys/sysctl.h>
#include <assert.h>
@@ -256,7 +257,7 @@
{
char eventbuf[1024];
string result;
- uint32_t i;
+ uint32_t i, start;
string startent;
startent = startentry(ev);
@@ -266,12 +267,20 @@
startent.c_str(), ev->pl_u.pl_cc.pl_pmcid, ev->pl_u.pl_cc.pl_pid,
ev->pl_u.pl_cc.pl_tid, ev->pl_u.pl_cc.pl_cpuflags, ev->pl_u.pl_cc.pl_cpuflags2);
result = string(eventbuf);
- for (i = 0; i < ev->pl_u.pl_cc.pl_npc - 1; i++) {
+ start = 0;
+ if ((ev->pl_u.pl_cc.pl_cpuflags & PMC_CC_F_MULTIPART) != 0 &&
+ !pmclog_multipart_callchain_offset(ev->pl_u.pl_cc.pl_pc,
+ ev->pl_u.pl_cc.pl_npc, &start))
+ start = ev->pl_u.pl_cc.pl_npc;
+ for (i = start; i + 1 < ev->pl_u.pl_cc.pl_npc; i++) {
snprintf(eventbuf, sizeof(eventbuf), "\"0x%016jx\", ", (uintmax_t)ev->pl_u.pl_cc.pl_pc[i]);
result += string(eventbuf);
}
- snprintf(eventbuf, sizeof(eventbuf), "\"0x%016jx\"]}\n", (uintmax_t)ev->pl_u.pl_cc.pl_pc[i]);
- result += string(eventbuf);
+ if (i < ev->pl_u.pl_cc.pl_npc) {
+ snprintf(eventbuf, sizeof(eventbuf), "\"0x%016jx\"]}\n", (uintmax_t)ev->pl_u.pl_cc.pl_pc[i]);
+ result += string(eventbuf);
+ } else
+ result += string("]}\n");
return (result);
}
diff --git a/lib/libpmc/pmc.ibs.3 b/lib/libpmc/pmc.ibs.3
--- a/lib/libpmc/pmc.ibs.3
+++ b/lib/libpmc/pmc.ibs.3
@@ -48,6 +48,9 @@
contained in the callchain.
The first 8 bytes of the callchain contain four tuples with a one byte type and
a one byte length field.
+Length fields are expressed in native pointer-sized callchain slots, not in
+64-bit payload words; therefore each 64-bit IBS payload entry consumes one slot
+on 64-bit platforms and two slots on 32-bit platforms.
The regular PMC callchain can be found following the multipart payload.
.Pp
IBS only provides two events that analyze instruction fetches and instruction
diff --git a/lib/libpmc/pmclog.c b/lib/libpmc/pmclog.c
--- a/lib/libpmc/pmclog.c
+++ b/lib/libpmc/pmclog.c
@@ -150,7 +150,8 @@
h = PMCLOG_HEADER_FROM_SAVED_STATE(ps);
recordsize = PMCLOG_HEADER_TO_LENGTH(h);
- if (recordsize <= 0)
+ if (recordsize < (int)sizeof(struct pmclog_header) ||
+ recordsize > (int)sizeof(ps->ps_saved))
goto error;
if (recordsize <= avail) { /* full record available */
@@ -213,7 +214,8 @@
h = PMCLOG_HEADER_FROM_SAVED_STATE(ps);
recordsize = PMCLOG_HEADER_TO_LENGTH(h);
- if (recordsize <= 0)
+ if (recordsize < (int)sizeof(struct pmclog_header) ||
+ recordsize > (int)sizeof(ps->ps_saved))
goto error;
if (avail + ps->ps_svcount < recordsize) {
@@ -300,8 +302,13 @@
} while (0)
#define PMCLOG_GET_CALLCHAIN_SIZE(SZ,E) do { \
- (SZ) = ((E) - offsetof(struct pmclog_callchain, pl_pc)) \
- / sizeof(uintfptr_t); \
+ int _payload; \
+ _payload = (E) - offsetof(struct pmclog_callchain, pl_pc); \
+ if (_payload < 0 || _payload % sizeof(uintfptr_t) != 0) \
+ goto error; \
+ (SZ) = _payload / sizeof(uintfptr_t); \
+ if ((SZ) > PMC_CALLCHAIN_DEPTH_MAX) \
+ goto error; \
} while (0);
switch (ev->pl_type = PMCLOG_HEADER_TO_TYPE(h)) {
diff --git a/lib/libpmcstat/libpmcstat_logging.c b/lib/libpmcstat/libpmcstat_logging.c
--- a/lib/libpmcstat/libpmcstat_logging.c
+++ b/lib/libpmcstat/libpmcstat_logging.c
@@ -191,7 +191,7 @@
int *pmcstat_npmcs,
int *ps_samples_period)
{
- uint32_t cpu, cpuflags;
+ uint32_t cpu, cpuflags, noff;
pid_t pid;
struct pmcstat_image *image;
struct pmcstat_process *pp, *ppnew;
@@ -283,6 +283,19 @@
break;
}
+ /* Skip the header and payload of a multipart sample. */
+ noff = 0;
+ if ((cpuflags & PMC_CC_F_MULTIPART) != 0 &&
+ !pmclog_multipart_callchain_offset(ev.pl_u.pl_cc.pl_pc,
+ ev.pl_u.pl_cc.pl_npc, &noff)) {
+ pmcstat_stats->ps_samples_skipped++;
+ break;
+ }
+ if (noff >= ev.pl_u.pl_cc.pl_npc) {
+ pmcstat_stats->ps_samples_skipped++;
+ break;
+ }
+
pp = pmcstat_process_lookup(ev.pl_u.pl_cc.pl_pid,
PMCSTAT_ALLOCATE);
@@ -298,14 +311,14 @@
if (plugins[args->pa_pplugin].pl_process != NULL)
plugins[args->pa_pplugin].pl_process(
pp, pmcr,
- ev.pl_u.pl_cc.pl_npc,
- ev.pl_u.pl_cc.pl_pc,
+ ev.pl_u.pl_cc.pl_npc - noff,
+ ev.pl_u.pl_cc.pl_pc + noff,
PMC_CALLCHAIN_CPUFLAGS_TO_USERMODE(cpuflags),
cpu);
plugins[args->pa_plugin].pl_process(
pp, pmcr,
- ev.pl_u.pl_cc.pl_npc,
- ev.pl_u.pl_cc.pl_pc,
+ ev.pl_u.pl_cc.pl_npc - noff,
+ ev.pl_u.pl_cc.pl_pc + noff,
PMC_CALLCHAIN_CPUFLAGS_TO_USERMODE(cpuflags),
cpu);
break;
diff --git a/tests/sys/pmc/Makefile b/tests/sys/pmc/Makefile
--- a/tests/sys/pmc/Makefile
+++ b/tests/sys/pmc/Makefile
@@ -9,6 +9,7 @@
.endif
ATF_TESTS_C+= pmc_lifecycle_test
ATF_TESTS_C+= pmc_log_test
+ATF_TESTS_C+= pmc_multipart_test
ATF_TESTS_C+= pmc_wrap_test
TEST_METADATA.pmc_exterr_test+= required_user="root"
diff --git a/tests/sys/pmc/pmc_multipart_test.c b/tests/sys/pmc/pmc_multipart_test.c
new file mode 100644
--- /dev/null
+++ b/tests/sys/pmc/pmc_multipart_test.c
@@ -0,0 +1,315 @@
+/*-
+ * SPDX-License-Identifier: BSD-2-Clause
+ *
+ * Copyright (c) 2026 The FreeBSD Foundation
+ *
+ * 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 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.
+ */
+
+#include <sys/param.h>
+#include <sys/pmc.h>
+#include <sys/pmclog.h>
+
+#include <atf-c.h>
+#include <pmclog.h>
+#include <stdint.h>
+#include <stddef.h>
+#include <stdlib.h>
+#include <string.h>
+
+#define PMCLOG_TO_HEADER(T, L) \
+ ((PMCLOG_HEADER_MAGIC << 24) | ((T) << 16) | ((L) & 0xffff))
+
+static size_t
+callchain_record_size(uint32_t npc)
+{
+ return (offsetof(struct pmclog_callchain, pl_pc) +
+ npc * sizeof(uintfptr_t));
+}
+
+static void
+init_callchain_record(struct pmclog_callchain *rec, uint32_t npc,
+ uint32_t cpuflags)
+{
+ size_t len;
+
+ len = callchain_record_size(npc);
+ memset(rec, 0, len);
+ rec->pl_header = PMCLOG_TO_HEADER(PMCLOG_TYPE_CALLCHAIN, len);
+ rec->pl_tsc = 0x12345678;
+ rec->pl_pid = 42;
+ rec->pl_tid = 43;
+ rec->pl_pmcid = 44;
+ rec->pl_cpuflags = cpuflags;
+}
+
+static void
+parse_record(const struct pmclog_callchain *rec, uint32_t npc,
+ struct pmclog_ev *ev)
+{
+ void *parser;
+ size_t len;
+
+ parser = pmclog_open(PMCLOG_FD_NONE);
+ ATF_REQUIRE(parser != NULL);
+ len = callchain_record_size(npc);
+ ATF_REQUIRE_EQ(0, pmclog_feed(parser, (char *)(uintptr_t)rec, len));
+ ATF_REQUIRE_EQ(0, pmclog_read(parser, ev));
+ ATF_REQUIRE_EQ(PMCLOG_OK, ev->pl_state);
+ ATF_REQUIRE_EQ(PMCLOG_TYPE_CALLCHAIN, ev->pl_type);
+ pmclog_close(parser);
+}
+
+ATF_TC(multipart_word_arithmetic);
+ATF_TC_HEAD(multipart_word_arithmetic, tc)
+{
+ atf_tc_set_md_var(tc, "descr",
+ "multipart headers and 64-bit payloads are counted in native "
+ "pointer-sized callchain slots");
+}
+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(23, PMC_MULTIPART_SAMPLE_MIN_WORDS_FOR(4));
+ ATF_CHECK_EQ(12, PMC_MULTIPART_SAMPLE_MIN_WORDS_FOR(8));
+ ATF_CHECK_EQ(PMC_MULTIPART_HEADER_WORDS_FOR(sizeof(uintfptr_t)),
+ PMC_MULTIPART_HEADER_WORDS);
+}
+
+ATF_TC(multipart_offset_bounds);
+ATF_TC_HEAD(multipart_offset_bounds, tc)
+{
+ atf_tc_set_md_var(tc, "descr",
+ "multipart offset helper handles full payloads, empty records, and "
+ "truncated payloads");
+}
+ATF_TC_BODY(multipart_offset_bounds, tc)
+{
+ uint32_t off, payload_words;
+ uint8_t *hdr;
+ uintfptr_t pc[PMC_MULTIPART_SAMPLE_MIN_WORDS + 1];
+
+ 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[1] = payload_words;
+ hdr[2] = PMC_CC_MULTIPART_CALLCHAIN;
+ hdr[3] = 0;
+ ATF_REQUIRE(pmclog_multipart_callchain_offset(pc,
+ PMC_MULTIPART_SAMPLE_MIN_WORDS, &off));
+ ATF_CHECK_EQ(PMC_MULTIPART_SAMPLE_MIN_WORDS - 1, 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_TC(pmclog_callchain_single_pc);
+ATF_TC_HEAD(pmclog_callchain_single_pc, tc)
+{
+ atf_tc_set_md_var(tc, "descr",
+ "a callchain record without PMC_F_CALLCHAIN still carries its "
+ "single interrupt PC");
+}
+ATF_TC_BODY(pmclog_callchain_single_pc, tc)
+{
+ struct pmclog_callchain rec;
+ struct pmclog_ev ev;
+
+ init_callchain_record(&rec, 1, PMC_CALLCHAIN_TO_CPUFLAGS(3,
+ PMC_CC_F_USERSPACE));
+ rec.pl_pc[0] = (uintfptr_t)0x1234abcd;
+ parse_record(&rec, 1, &ev);
+ ATF_CHECK_EQ(1, ev.pl_u.pl_cc.pl_npc);
+ ATF_CHECK_EQ(3, PMC_CALLCHAIN_CPUFLAGS_TO_CPU(ev.pl_u.pl_cc.pl_cpuflags));
+ ATF_CHECK(PMC_CALLCHAIN_CPUFLAGS_TO_USERMODE(ev.pl_u.pl_cc.pl_cpuflags));
+ ATF_CHECK_EQ((uintfptr_t)0x1234abcd, ev.pl_u.pl_cc.pl_pc[0]);
+}
+
+ATF_TC(pmclog_callchain_kernel_and_full_depth);
+ATF_TC_HEAD(pmclog_callchain_kernel_and_full_depth, tc)
+{
+ atf_tc_set_md_var(tc, "descr",
+ "the pmclog parser accepts kernel and full-depth callchain records");
+}
+ATF_TC_BODY(pmclog_callchain_kernel_and_full_depth, tc)
+{
+ struct pmclog_callchain *rec;
+ struct pmclog_ev ev;
+ uint32_t i;
+
+ rec = calloc(1, sizeof(*rec));
+ ATF_REQUIRE(rec != NULL);
+ init_callchain_record(rec, PMC_CALLCHAIN_DEPTH_MAX,
+ PMC_CALLCHAIN_TO_CPUFLAGS(1, 0));
+ for (i = 0; i < PMC_CALLCHAIN_DEPTH_MAX; i++)
+ rec->pl_pc[i] = (uintfptr_t)(0x1000 + i);
+ parse_record(rec, PMC_CALLCHAIN_DEPTH_MAX, &ev);
+ ATF_CHECK_EQ(PMC_CALLCHAIN_DEPTH_MAX, ev.pl_u.pl_cc.pl_npc);
+ ATF_CHECK_EQ(1, PMC_CALLCHAIN_CPUFLAGS_TO_CPU(ev.pl_u.pl_cc.pl_cpuflags));
+ ATF_CHECK(!PMC_CALLCHAIN_CPUFLAGS_TO_USERMODE(ev.pl_u.pl_cc.pl_cpuflags));
+ ATF_CHECK_EQ((uintfptr_t)(0x1000 + PMC_CALLCHAIN_DEPTH_MAX - 1),
+ ev.pl_u.pl_cc.pl_pc[PMC_CALLCHAIN_DEPTH_MAX - 1]);
+ free(rec);
+}
+
+ATF_TC(pmclog_callchain_zero_pcs);
+ATF_TC_HEAD(pmclog_callchain_zero_pcs, tc)
+{
+ atf_tc_set_md_var(tc, "descr",
+ "the pmclog parser accepts an empty callchain record without "
+ "reading stale PCs");
+}
+ATF_TC_BODY(pmclog_callchain_zero_pcs, tc)
+{
+ struct pmclog_callchain rec;
+ struct pmclog_ev ev;
+
+ init_callchain_record(&rec, 0, PMC_CALLCHAIN_TO_CPUFLAGS(7, 0));
+ parse_record(&rec, 0, &ev);
+ ATF_CHECK_EQ(0, ev.pl_u.pl_cc.pl_npc);
+ ATF_CHECK_EQ(7, PMC_CALLCHAIN_CPUFLAGS_TO_CPU(ev.pl_u.pl_cc.pl_cpuflags));
+ ATF_CHECK_EQ((uintfptr_t)0, ev.pl_u.pl_cc.pl_pc[0]);
+}
+
+ATF_TC(pmclog_callchain_multipart_minimum_capacity);
+ATF_TC_HEAD(pmclog_callchain_multipart_minimum_capacity, tc)
+{
+ atf_tc_set_md_var(tc, "descr",
+ "a full multipart payload at the minimum sample depth leaves room "
+ "for one callchain PC");
+}
+ATF_TC_BODY(pmclog_callchain_multipart_minimum_capacity, tc)
+{
+ struct pmclog_callchain rec;
+ struct pmclog_ev ev;
+ uint32_t off, payload_words;
+ uint8_t *hdr;
+
+ init_callchain_record(&rec, PMC_MULTIPART_SAMPLE_MIN_WORDS,
+ PMC_CALLCHAIN_TO_CPUFLAGS(0, PMC_CC_F_MULTIPART));
+ hdr = (uint8_t *)rec.pl_pc;
+ payload_words = PMC_MULTIPART_PAYLOAD_WORDS(PMC_MULTIPART_MAX_PAYLOAD64);
+ hdr[0] = PMC_CC_MULTIPART_IBS_FETCH;
+ hdr[1] = payload_words;
+ hdr[2] = PMC_CC_MULTIPART_CALLCHAIN;
+ rec.pl_pc[PMC_MULTIPART_SAMPLE_MIN_WORDS - 1] = (uintfptr_t)0xfeedface;
+
+ parse_record(&rec, PMC_MULTIPART_SAMPLE_MIN_WORDS, &ev);
+ ATF_REQUIRE(pmclog_multipart_callchain_offset(ev.pl_u.pl_cc.pl_pc,
+ ev.pl_u.pl_cc.pl_npc, &off));
+ ATF_CHECK_EQ(PMC_MULTIPART_SAMPLE_MIN_WORDS - 1, off);
+ ATF_CHECK_EQ((uintfptr_t)0xfeedface, ev.pl_u.pl_cc.pl_pc[off]);
+}
+
+ATF_TC(pmclog_callchain_fragmented_record);
+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");
+}
+ATF_TC_BODY(pmclog_callchain_fragmented_record, tc)
+{
+ struct pmclog_callchain rec;
+ struct pmclog_ev ev;
+ void *parser;
+ size_t len;
+
+ 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);
+ parser = pmclog_open(PMCLOG_FD_NONE);
+ ATF_REQUIRE(parser != NULL);
+ ATF_REQUIRE_EQ(0, pmclog_feed(parser, (char *)&rec, len - 1));
+ 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_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]);
+ pmclog_close(parser);
+}
+
+ATF_TC(pmclog_rejects_malformed_lengths);
+ATF_TC_HEAD(pmclog_rejects_malformed_lengths, tc)
+{
+ atf_tc_set_md_var(tc, "descr",
+ "pmclog rejects zero, oversized, and unaligned record lengths");
+}
+ATF_TC_BODY(pmclog_rejects_malformed_lengths, tc)
+{
+ struct pmclog_callchain rec;
+ struct pmclog_ev ev;
+ void *parser;
+ size_t badlen;
+
+ memset(&rec, 0, sizeof(rec));
+ rec.pl_header = PMCLOG_TO_HEADER(PMCLOG_TYPE_CALLCHAIN, 0);
+ parser = pmclog_open(PMCLOG_FD_NONE);
+ ATF_REQUIRE(parser != NULL);
+ ATF_REQUIRE_EQ(0, pmclog_feed(parser, (char *)&rec, sizeof(uint32_t)));
+ ATF_CHECK_EQ(-1, pmclog_read(parser, &ev));
+ ATF_CHECK_EQ(PMCLOG_ERROR, ev.pl_state);
+ pmclog_close(parser);
+
+ memset(&rec, 0, sizeof(rec));
+ rec.pl_header = PMCLOG_TO_HEADER(PMCLOG_TYPE_CALLCHAIN,
+ sizeof(union pmclog_entry) + sizeof(uint32_t));
+ parser = pmclog_open(PMCLOG_FD_NONE);
+ ATF_REQUIRE(parser != NULL);
+ ATF_REQUIRE_EQ(0, pmclog_feed(parser, (char *)&rec, sizeof(uint32_t)));
+ ATF_CHECK_EQ(-1, pmclog_read(parser, &ev));
+ ATF_CHECK_EQ(PMCLOG_ERROR, ev.pl_state);
+ pmclog_close(parser);
+
+ badlen = offsetof(struct pmclog_callchain, pl_pc) + 1;
+ memset(&rec, 0, sizeof(rec));
+ rec.pl_header = PMCLOG_TO_HEADER(PMCLOG_TYPE_CALLCHAIN, badlen);
+ parser = pmclog_open(PMCLOG_FD_NONE);
+ ATF_REQUIRE(parser != NULL);
+ ATF_REQUIRE_EQ(0, pmclog_feed(parser, (char *)&rec, badlen));
+ ATF_CHECK_EQ(-1, pmclog_read(parser, &ev));
+ ATF_CHECK_EQ(PMCLOG_ERROR, ev.pl_state);
+ pmclog_close(parser);
+}
+
+ATF_TP_ADD_TCS(tp)
+{
+ ATF_TP_ADD_TC(tp, multipart_word_arithmetic);
+ ATF_TP_ADD_TC(tp, multipart_offset_bounds);
+ 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);
+ ATF_TP_ADD_TC(tp, pmclog_callchain_multipart_minimum_capacity);
+ ATF_TP_ADD_TC(tp, pmclog_callchain_fragmented_record);
+ ATF_TP_ADD_TC(tp, pmclog_rejects_malformed_lengths);
+
+ return (atf_no_error());
+}
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
@@ -370,12 +370,14 @@
}
#if defined(__amd64__) || defined(__i386__)
-static void
+static int
pmcstat_print_ibs_fetch(struct pmclog_ev_callchain *cc, int offset, int len64)
{
uint64_t *ibsbuf = (uint64_t *)&cc->pl_pc[offset];
uint64_t ctl, ctl2;
+ if (len64 <= PMC_MPIDX_FETCH_LINADDR)
+ return (0);
ctl = ibsbuf[PMC_MPIDX_FETCH_CTL];
PMCSTAT_PRINT_ENTRY("ibs-fetch", "%s%s%s%s",
(ctl & IBS_FETCH_CTL_ICMISS) ? "icmiss " : "",
@@ -387,6 +389,8 @@
PMCSTAT_PRINT_ENTRY("IBS", "Address %" PRIx64,
ibsbuf[PMC_MPIDX_FETCH_LINADDR]);
if ((ctl & IBS_FETCH_CTL_PHYSADDRVALID) != 0) {
+ if (len64 <= PMC_MPIDX_FETCH_PHYSADDR)
+ return (0);
PMCSTAT_PRINT_ENTRY("IBS", "Physical Address %" PRIx64,
ibsbuf[PMC_MPIDX_FETCH_PHYSADDR]);
}
@@ -402,19 +406,24 @@
(uint64_t)IBS_FETCH_CTL2_CTL_TO_LAT(ctl2));
}
}
+ return (1);
}
-static void
+static int
pmcstat_print_ibs_op(struct pmclog_ev_callchain *cc, int offset, int len64)
{
uint64_t *ibsbuf = (uint64_t *)&cc->pl_pc[offset];
uint64_t data, data2, data3, ctl2;
+ if (len64 <= PMC_MPIDX_OP_DATA3)
+ return (0);
data = ibsbuf[PMC_MPIDX_OP_DATA];
data2 = ibsbuf[PMC_MPIDX_OP_DATA2];
data3 = ibsbuf[PMC_MPIDX_OP_DATA3];
if ((data & IBS_OP_DATA_RIPINVALID) == 0) {
+ if (len64 <= PMC_MPIDX_OP_RIP)
+ return (0);
PMCSTAT_PRINT_ENTRY("ibs-op", "RIP %" PRIx64,
ibsbuf[PMC_MPIDX_OP_RIP]);
}
@@ -437,10 +446,14 @@
PMCSTAT_PRINT_ENTRY("ibs-op", "Latency %" PRIu64,
IBS_OP_DATA3_TO_DCLAT(data3));
if ((data3 & IBS_OP_DATA3_DCLINADDRVALID) != 0) {
+ if (len64 <= PMC_MPIDX_OP_DC_LINADDR)
+ return (0);
PMCSTAT_PRINT_ENTRY("ibs-op", "Address %" PRIx64,
ibsbuf[PMC_MPIDX_OP_DC_LINADDR]);
}
if ((data3 & IBS_OP_DATA3_DCPHYADDRVALID) != 0) {
+ if (len64 <= PMC_MPIDX_OP_DC_PHYSADDR)
+ return (0);
PMCSTAT_PRINT_ENTRY("ibs-op", "Physical Address %" PRIx64,
ibsbuf[PMC_MPIDX_OP_DC_PHYSADDR]);
}
@@ -453,6 +466,7 @@
if ((ctl2 & IBS_OP_CTL2_STRMSTFILTER) != 0)
PMCSTAT_PRINT_ENTRY("ibs-op", "streamstore");
}
+ return (1);
}
#endif
@@ -461,8 +475,13 @@
{
int i;
uint8_t *hdr = (uint8_t *)&cc->pl_pc[0];
- int offset = PMC_MULTIPART_HEADER_LENGTH / sizeof(uintptr_t);
+ uint32_t offset = PMC_MULTIPART_HEADER_WORDS;
+ uint32_t words_per_64 = sizeof(uint64_t) / sizeof(uintptr_t);
+ if (cc->pl_npc < offset) {
+ PMCSTAT_PRINT_ENTRY("truncated multipart record!");
+ return (cc->pl_npc);
+ }
for (i = 0; i < PMC_MULTIPART_HEADER_ENTRIES; i++) {
uint8_t type = hdr[2 * i];
uint8_t len = hdr[2 * i + 1];
@@ -471,13 +490,23 @@
break;
} else if (type == PMC_CC_MULTIPART_CALLCHAIN) {
return (offset);
+ } else if (len > cc->pl_npc - offset) {
+ PMCSTAT_PRINT_ENTRY("truncated multipart record!");
+ return (cc->pl_npc);
#if defined(__amd64__) || defined(__i386__)
} else if (type == PMC_CC_MULTIPART_IBS_FETCH) {
- pmcstat_print_ibs_fetch(cc, offset,
- len / (sizeof(uint64_t) / sizeof(uintptr_t)));
+ if (len % words_per_64 != 0 ||
+ !pmcstat_print_ibs_fetch(cc, offset,
+ len / words_per_64)) {
+ PMCSTAT_PRINT_ENTRY("truncated multipart record!");
+ return (cc->pl_npc);
+ }
} else if (type == PMC_CC_MULTIPART_IBS_OP) {
- pmcstat_print_ibs_op(cc, offset,
- len / (sizeof(uint64_t) / sizeof(uintptr_t)));
+ if (len % words_per_64 != 0 ||
+ !pmcstat_print_ibs_op(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
Mon, Oct 12, 12:53 AM (2 h, 41 m)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
40585653
Default Alt Text
D60564.diff (19 KB)

Event Timeline