blob: 06a7a61ad3b0b33a4124908a40c447aaa1af7215 [file] [log] [blame]
/*
* Copyright (c) 2014-2019, Intel Corporation
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* * Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
* * 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.
* * Neither the name of Intel Corporation nor the names of its contributors
* may be used to endorse or promote products derived from this software
* without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS 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 COPYRIGHT OWNER 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 "pevent.h"
#define pev_config_has(config, field) \
(config->size >= (offsetof(struct pev_config, field) + \
sizeof(config->field)))
int pev_time_to_tsc(uint64_t *tsc, uint64_t time,
const struct pev_config *config)
{
uint64_t quot, rem, time_zero;
uint16_t time_shift;
uint32_t time_mult;
if (!tsc || !config)
return -pte_internal;
if (!pev_config_has(config, time_zero))
return -pte_bad_config;
time_shift = config->time_shift;
time_mult = config->time_mult;
time_zero = config->time_zero;
if (!time_mult)
return -pte_bad_config;
time -= time_zero;
quot = time / time_mult;
rem = time % time_mult;
quot <<= time_shift;
rem <<= time_shift;
rem /= time_mult;
*tsc = quot + rem;
return 0;
}
int pev_time_from_tsc(uint64_t *time, uint64_t tsc,
const struct pev_config *config)
{
uint64_t quot, rem, time_zero;
uint16_t time_shift;
uint32_t time_mult;
if (!time || !config)
return -pte_internal;
if (!pev_config_has(config, time_zero))
return -pte_bad_config;
time_shift = config->time_shift;
time_mult = config->time_mult;
time_zero = config->time_zero;
if (!time_mult)
return -pte_bad_config;
quot = tsc >> time_shift;
rem = tsc & ((1ull << time_shift) - 1);
quot *= time_mult;
rem *= time_mult;
rem >>= time_shift;
*time = time_zero + quot + rem;
return 0;
}
static int pev_strlen(const char *begin, const void *end_arg)
{
const char *pos, *end;
if (!begin || !end_arg)
return -pte_internal;
end = (const char *) end_arg;
if (end < begin)
return -pte_internal;
for (pos = begin; pos < end; ++pos) {
if (!pos[0])
return (int) (pos - begin) + 1;
}
return -pte_bad_packet;
}
static int pev_read_samples(struct pev_event *event, const uint8_t *begin,
const uint8_t *end, const struct pev_config *config)
{
const uint8_t *pos;
uint64_t sample_type;
if (!event || !begin || !config)
return -pte_internal;
if (!pev_config_has(config, sample_type))
return -pte_bad_config;
sample_type = config->sample_type;
pos = begin;
if (sample_type & PERF_SAMPLE_TID) {
event->sample.pid = (const uint32_t *) &pos[0];
event->sample.tid = (const uint32_t *) &pos[4];
pos += 8;
}
if (sample_type & PERF_SAMPLE_TIME) {
int errcode;
event->sample.time = (const uint64_t *) pos;
pos += 8;
/* We're reading the time. Let's make sure the pointer lies
* inside the buffer.
*/
if (end < pos)
return -pte_nosync;
errcode = pev_time_to_tsc(&event->sample.tsc,
*event->sample.time, config);
if (errcode < 0)
return errcode;
}
if (sample_type & PERF_SAMPLE_ID) {
event->sample.id = (const uint64_t *) pos;
pos += 8;
}
if (sample_type & PERF_SAMPLE_STREAM_ID) {
event->sample.stream_id = (const uint64_t *) pos;
pos += 8;
}
if (sample_type & PERF_SAMPLE_CPU) {
event->sample.cpu = (const uint32_t *) pos;
pos += 8;
}
if (sample_type & PERF_SAMPLE_IDENTIFIER) {
event->sample.identifier = (const uint64_t *) pos;
pos += 8;
}
return (int) (pos - begin);
}
int pev_read(struct pev_event *event, const uint8_t *begin, const uint8_t *end,
const struct pev_config *config)
{
const struct perf_event_header *header;
const uint8_t *pos;
int size;
if (!event || !begin || end < begin)
return -pte_internal;
pos = begin;
if (end < (pos + sizeof(*header)))
return -pte_eos;
header = (const struct perf_event_header *) pos;
pos += sizeof(*header);
if (!header->type || (end < (begin + header->size)))
return -pte_eos;
/* Stay within the packet. */
end = begin + header->size;
memset(event, 0, sizeof(*event));
event->type = header->type;
event->misc = header->misc;
switch (event->type) {
default:
/* We don't provide samples.
*
* It would be possible since we know the event's total size
* as well as the sample size. But why?
*/
return (int) header->size;
case PERF_RECORD_MMAP: {
int slen;
event->record.mmap = (const struct pev_record_mmap *) pos;
slen = pev_strlen(event->record.mmap->filename, end);
if (slen < 0)
return slen;
slen = (slen + 7) & ~7;
pos += sizeof(*event->record.mmap);
pos += slen;
}
break;
case PERF_RECORD_LOST:
event->record.lost = (const struct pev_record_lost *) pos;
pos += sizeof(*event->record.lost);
break;
case PERF_RECORD_COMM: {
int slen;
event->record.comm = (const struct pev_record_comm *) pos;
slen = pev_strlen(event->record.comm->comm, end);
if (slen < 0)
return slen;
slen = (slen + 7) & ~7;
pos += sizeof(*event->record.comm);
pos += slen;
}
break;
case PERF_RECORD_EXIT:
event->record.exit = (const struct pev_record_exit *) pos;
pos += sizeof(*event->record.exit);
break;
case PERF_RECORD_THROTTLE:
case PERF_RECORD_UNTHROTTLE:
event->record.throttle =
(const struct pev_record_throttle *) pos;
pos += sizeof(*event->record.throttle);
break;
case PERF_RECORD_FORK:
event->record.fork = (const struct pev_record_fork *) pos;
pos += sizeof(*event->record.fork);
break;
case PERF_RECORD_MMAP2: {
int slen;
event->record.mmap2 = (const struct pev_record_mmap2 *) pos;
slen = pev_strlen(event->record.mmap2->filename, end);
if (slen < 0)
return slen;
slen = (slen + 7) & ~7;
pos += sizeof(*event->record.mmap2);
pos += slen;
}
break;
case PERF_RECORD_AUX:
event->record.aux = (const struct pev_record_aux *) pos;
pos += sizeof(*event->record.aux);
break;
case PERF_RECORD_ITRACE_START:
event->record.itrace_start =
(const struct pev_record_itrace_start *) pos;
pos += sizeof(*event->record.itrace_start);
break;
case PERF_RECORD_LOST_SAMPLES:
event->record.lost_samples =
(const struct pev_record_lost_samples *) pos;
pos += sizeof(*event->record.lost_samples);
break;
case PERF_RECORD_SWITCH:
break;
case PERF_RECORD_SWITCH_CPU_WIDE:
event->record.switch_cpu_wide =
(const struct pev_record_switch_cpu_wide *) pos;
pos += sizeof(*event->record.switch_cpu_wide);
break;
}
size = pev_read_samples(event, pos, end, config);
if (size < 0)
return size;
pos += size;
if (pos < begin)
return -pte_internal;
size = (int) (pos - begin);
if ((uint16_t) size != header->size)
return -pte_nosync;
return size;
}
static size_t sample_size(const struct pev_event *event)
{
size_t size;
if (!event)
return 0;
size = 0;
if (event->sample.tid) {
size += sizeof(*event->sample.pid);
size += sizeof(*event->sample.tid);
}
if (event->sample.time)
size += sizeof(*event->sample.time);
if (event->sample.id)
size += sizeof(*event->sample.id);
if (event->sample.stream_id)
size += sizeof(*event->sample.stream_id);
if (event->sample.cpu) {
size += sizeof(*event->sample.cpu);
size += sizeof(uint32_t);
}
if (event->sample.identifier)
size += sizeof(*event->sample.identifier);
return size;
}
static void write(uint8_t **stream, const void *object, size_t size)
{
memcpy(*stream, object, size);
*stream += size;
}
static void clear(uint8_t **stream, size_t size)
{
memset(*stream, 0, size);
*stream += size;
}
static int write_samples(uint8_t **stream, const struct pev_event *event,
const struct pev_config *config)
{
uint64_t sample_type;
if (!event || !config)
return -pte_internal;
if (!pev_config_has(config, sample_type))
return -pte_bad_config;
sample_type = config->sample_type;
if (sample_type & PERF_SAMPLE_TID) {
sample_type &= ~(uint64_t) PERF_SAMPLE_TID;
if (!event->sample.pid || !event->sample.tid)
return -pte_bad_packet;
write(stream, event->sample.pid, sizeof(*event->sample.pid));
write(stream, event->sample.tid, sizeof(*event->sample.tid));
}
if (sample_type & PERF_SAMPLE_TIME) {
sample_type &= ~(uint64_t) PERF_SAMPLE_TIME;
if (!event->sample.time)
return -pte_bad_packet;
write(stream, event->sample.time, sizeof(*event->sample.time));
}
if (sample_type & PERF_SAMPLE_ID) {
sample_type &= ~(uint64_t) PERF_SAMPLE_ID;
if (!event->sample.id)
return -pte_bad_packet;
write(stream, event->sample.id, sizeof(*event->sample.id));
}
if (sample_type & PERF_SAMPLE_STREAM_ID) {
sample_type &= ~(uint64_t) PERF_SAMPLE_STREAM_ID;
if (!event->sample.stream_id)
return -pte_bad_packet;
write(stream, event->sample.stream_id,
sizeof(*event->sample.stream_id));
}
if (sample_type & PERF_SAMPLE_CPU) {
sample_type &= ~(uint64_t) PERF_SAMPLE_CPU;
if (!event->sample.cpu)
return -pte_bad_packet;
write(stream, event->sample.cpu, sizeof(*event->sample.cpu));
*stream += sizeof(uint32_t);
}
if (sample_type & PERF_SAMPLE_IDENTIFIER) {
sample_type &= ~(uint64_t) PERF_SAMPLE_IDENTIFIER;
if (!event->sample.identifier)
return -pte_bad_packet;
write(stream, event->sample.identifier,
sizeof(*event->sample.identifier));
}
if (sample_type)
return -pte_bad_packet;
return 0;
}
int pev_write(const struct pev_event *event, uint8_t *begin, uint8_t *end,
const struct pev_config *config)
{
struct perf_event_header header;
uint8_t *pos;
size_t size;
int errcode;
if (!event || !begin || end < begin)
return -pte_internal;
pos = begin;
size = sizeof(header) + sample_size(event);
if (UINT16_MAX < size)
return -pte_internal;
header.type = event->type;
header.misc = event->misc;
switch (header.type) {
default:
return -pte_bad_opc;
case PERF_RECORD_MMAP: {
size_t slen, gap;
slen = strlen(event->record.mmap->filename) + 1;
gap = ((slen + 7u) & ~7u) - slen;
size += sizeof(*event->record.mmap) + slen + gap;
if (UINT16_MAX < size)
return -pte_bad_packet;
header.size = (uint16_t) size;
if (end < pos + header.size)
return -pte_eos;
write(&pos, &header, sizeof(header));
write(&pos, event->record.mmap, sizeof(*event->record.mmap));
write(&pos, event->record.mmap->filename, slen);
clear(&pos, gap);
}
break;
case PERF_RECORD_LOST:
size += sizeof(*event->record.lost);
if (UINT16_MAX < size)
return -pte_bad_packet;
header.size = (uint16_t) size;
if (end < pos + header.size)
return -pte_eos;
write(&pos, &header, sizeof(header));
write(&pos, event->record.lost, sizeof(*event->record.lost));
break;
case PERF_RECORD_COMM: {
size_t slen, gap;
slen = strlen(event->record.comm->comm) + 1;
gap = ((slen + 7u) & ~7u) - slen;
size += sizeof(*event->record.comm) + slen + gap;
if (UINT16_MAX < size)
return -pte_bad_packet;
header.size = (uint16_t) size;
if (end < pos + header.size)
return -pte_eos;
write(&pos, &header, sizeof(header));
write(&pos, event->record.comm, sizeof(*event->record.comm));
write(&pos, event->record.comm->comm, slen);
clear(&pos, gap);
}
break;
case PERF_RECORD_EXIT:
size += sizeof(*event->record.exit);
if (UINT16_MAX < size)
return -pte_bad_packet;
header.size = (uint16_t) size;
if (end < pos + header.size)
return -pte_eos;
write(&pos, &header, sizeof(header));
write(&pos, event->record.exit, sizeof(*event->record.exit));
break;
case PERF_RECORD_THROTTLE:
case PERF_RECORD_UNTHROTTLE:
size += sizeof(*event->record.throttle);
if (UINT16_MAX < size)
return -pte_bad_packet;
header.size = (uint16_t) size;
if (end < pos + header.size)
return -pte_eos;
write(&pos, &header, sizeof(header));
write(&pos, event->record.throttle,
sizeof(*event->record.throttle));
break;
case PERF_RECORD_FORK:
size += sizeof(*event->record.fork);
if (UINT16_MAX < size)
return -pte_bad_packet;
header.size = (uint16_t) size;
if (end < pos + header.size)
return -pte_eos;
write(&pos, &header, sizeof(header));
write(&pos, event->record.fork, sizeof(*event->record.fork));
break;
case PERF_RECORD_MMAP2: {
size_t slen, gap;
slen = strlen(event->record.mmap2->filename) + 1;
gap = ((slen + 7u) & ~7u) - slen;
size += sizeof(*event->record.mmap2) + slen + gap;
if (UINT16_MAX < size)
return -pte_bad_packet;
header.size = (uint16_t) size;
if (end < pos + header.size)
return -pte_eos;
write(&pos, &header, sizeof(header));
write(&pos, event->record.mmap2, sizeof(*event->record.mmap2));
write(&pos, event->record.mmap2->filename, slen);
clear(&pos, gap);
}
break;
case PERF_RECORD_AUX:
size += sizeof(*event->record.aux);
if (UINT16_MAX < size)
return -pte_bad_packet;
header.size = (uint16_t) size;
if (end < pos + header.size)
return -pte_eos;
write(&pos, &header, sizeof(header));
write(&pos, event->record.aux, sizeof(*event->record.aux));
break;
case PERF_RECORD_ITRACE_START:
size += sizeof(*event->record.itrace_start);
if (UINT16_MAX < size)
return -pte_bad_packet;
header.size = (uint16_t) size;
if (end < pos + header.size)
return -pte_eos;
write(&pos, &header, sizeof(header));
write(&pos, event->record.itrace_start,
sizeof(*event->record.itrace_start));
break;
case PERF_RECORD_LOST_SAMPLES:
size += sizeof(*event->record.lost_samples);
if (UINT16_MAX < size)
return -pte_bad_packet;
header.size = (uint16_t) size;
if (end < pos + header.size)
return -pte_eos;
write(&pos, &header, sizeof(header));
write(&pos, event->record.lost_samples,
sizeof(*event->record.lost_samples));
break;
case PERF_RECORD_SWITCH:
header.size = (uint16_t) size;
if (end < pos + header.size)
return -pte_eos;
write(&pos, &header, sizeof(header));
break;
case PERF_RECORD_SWITCH_CPU_WIDE:
size += sizeof(*event->record.switch_cpu_wide);
if (UINT16_MAX < size)
return -pte_bad_packet;
header.size = (uint16_t) size;
if (end < pos + header.size)
return -pte_eos;
write(&pos, &header, sizeof(header));
write(&pos, event->record.switch_cpu_wide,
sizeof(*event->record.switch_cpu_wide));
break;
}
errcode = write_samples(&pos, event, config);
if (errcode < 0)
return errcode;
return (int) (pos - begin);
}