#include <sys/types.h>
#include <sys/param.h>
#include <assert.h>
#include <err.h>
#include <fcntl.h>
#include <gelf.h>
#include <inttypes.h>
#include <libelf.h>
#include <pmclog.h>
#include <sysexits.h>
#include <unistd.h>
#include <cxxabi.h>
#include <iostream>
#include <map>
#include <set>
#include <string>
#include <sstream>
#include <unordered_map>
#include <unordered_set>
#include <dev/hwpmc/hwpmc_ibs.h>
#include "util.hh"
#include "view.hh"
std::string
syminfo::to_string(bool show_line)
{
int status;
char *demangled;
std::stringstream ss;
if (name == "") {
ss << "0x" << std::hex << offset;
} else if (name.length() >= 2 && name[0] == '_' && name[1] == 'Z') {
demangled = abi::__cxa_demangle(name.c_str(), nullptr, nullptr, &status);
if (status == 0) {
ss << demangled;
free(demangled);
} else {
ss << name;
}
} else {
ss << name;
}
if (!show_line)
return ss.str();
if (line) {
ss << ":" << std::dec << line;
} else if (funcoff) {
ss << "+0x" << std::hex << funcoff;
}
return ss.str();
}
pmcview::pmcview() : tscfreq(0), pmcid(), pmcinfo(), procs(), tidtopid(),
images(), sysroot(""), filter()
{
char *root;
root = getenv("SYSROOT");
if (root) {
sysroot = root;
}
}
pmcview::~pmcview()
{
}
void
pmcview::setfilter(const pmcfilter &pmcfilter)
{
filter = pmcfilter;
}
static int
readlog(int logfd, void *buf, size_t len)
{
int status;
char *cur = (char *)buf;
size_t left = len;
while (left != 0) {
status = read(logfd, cur, left);
if (status < 0) {
if (errno == EINTR || errno == EAGAIN)
continue;
else
return status;
}
if (status == 0)
return status;
cur += status;
left -= status;
}
return len;
}
int
pmcview::process_sysinfo(int logfd, const pmchdr_infohdr &infohdr)
{
int status;
pmchdr_sysinfo sysinfo;
if (infohdr.length != sizeof(sysinfo))
errx(EX_IOERR, "PMC log headers have an unexpected size");
status = readlog(logfd, &sysinfo, sizeof(sysinfo));
if (status < 0 || status != infohdr.length)
errx(EX_IOERR, "readlog");
cpumodel = sysinfo.cpumodel;
osrelease = sysinfo.osrelease;
buildid = sysinfo.buildid;
return 0;
}
int
pmcview::process_pmcinfo(int logfd, const pmchdr_infohdr &infohdr)
{
int status;
union {
pmchdr_pmcinfo pmcinfo;
__unused char reserve_max_size[256];
};
status = readlog(logfd, &pmcinfo, infohdr.length);
if (status < 0 || status != infohdr.length)
errx(EX_IOERR, "readlog");
extpmcinfo.emplace_back(pmcinfo.rate, std::string(pmcinfo.pmc));
return 0;
}
int
pmcview::process_cpuidinfo(int logfd, const pmchdr_infohdr &infohdr)
{
int status;
pmchdr_cpuidinfo *cpuidinfo;
int offset, len;
uint32_t root, maxleaf, count;
len = infohdr.length / 4;
cpuidinfo = (pmchdr_cpuidinfo *)new uint32_t[len];
status = readlog(logfd, cpuidinfo, infohdr.length);
if (status < 0 || status != infohdr.length)
errx(EX_IOERR, "readlog");
offset = 0;
while (offset < len) {
maxleaf = cpuidinfo->cpuid[offset];
root = maxleaf & 0xFFFF0000;
count = maxleaf & 0x0000FFFF;
for (uint32_t i = 0; i <= count; i++) {
cpuid[root + i] = { cpuidinfo->cpuid[4 * i + offset],
cpuidinfo->cpuid[4 * i + 1 + offset],
cpuidinfo->cpuid[4 * i + 2 + offset],
cpuidinfo->cpuid[4 * i + 3 + offset] };
}
offset += 4 * (count + 1);
}
delete[] cpuidinfo;
return 0;
}
void
pmcview::process(int logfd)
{
int status;
pmchdr_header hdr;
struct pmclog_parse_state *ps;
status = readlog(logfd, &hdr, sizeof(hdr));
if (status < 0 || status != sizeof(hdr))
err(EX_IOERR, "readlog");
if (hdr.magic != PMC_HEADER_MAGIC)
errx(EX_DATAERR, "PMC log magic mismatch!");
if (hdr.version > PMC_HEADER_VERSION)
errx(EX_DATAERR, "PMC log version newer than supported!");
arch = hdr.arch;
while (1) {
pmchdr_infohdr infohdr;
status = readlog(logfd, &infohdr, sizeof(infohdr));
if (status < 0 || status != sizeof(infohdr))
errx(EX_IOERR, "readlog");
if (infohdr.type == INFOHDR_TYPE_DONE) {
break;
} else if (infohdr.type == INFOHDR_TYPE_SYSINFO) {
process_sysinfo(logfd, infohdr);
} else if (infohdr.type == INFOHDR_TYPE_PMCINFO) {
process_pmcinfo(logfd, infohdr);
} else if (infohdr.type == INFOHDR_TYPE_CPUID) {
process_cpuidinfo(logfd, infohdr);
}
}
ps = static_cast<struct pmclog_parse_state*>(pmclog_open(logfd));
if (ps == NULL) {
errx(EX_OSERR, "ERROR: Cannot allocate pmclog parse state: %s\n",
strerror(errno));
}
process(ps);
pmclog_close(ps);
}
void
pmcview::process(struct pmclog_ev &p)
{
switch (p.pl_type) {
case PMCLOG_TYPE_INITIALIZE:
process(p.pl_u.pl_i);
return;
case PMCLOG_TYPE_CLOSELOG:
process(p.pl_u.pl_cl);
return;
case PMCLOG_TYPE_PMCALLOCATE:
process(p.pl_u.pl_a);
return;
case PMCLOG_TYPE_PMCALLOCATEDYN:
process(p.pl_u.pl_ad);
return;
case PMCLOG_TYPE_PROC_CREATE:
process(p.pl_u.pl_pc);
return;
case PMCLOG_TYPE_PROCEXIT:
process(p.pl_u.pl_e);
return;
case PMCLOG_TYPE_PROCEXEC:
process(p.pl_u.pl_x);
return;
case PMCLOG_TYPE_PROCFORK:
process(p.pl_u.pl_f);
return;
case PMCLOG_TYPE_SYSEXIT:
process(p.pl_u.pl_se);
return;
case PMCLOG_TYPE_MAP_IN:
process(p.pl_u.pl_mi);
return;
case PMCLOG_TYPE_MAP_OUT:
process(p.pl_u.pl_mo);
return;
case PMCLOG_TYPE_THR_CREATE:
process(p.pl_u.pl_tc);
return;
case PMCLOG_TYPE_THR_EXIT:
process(p.pl_u.pl_te);
return;
case PMCLOG_TYPE_CALLCHAIN:
process(p.pl_u.pl_cc);
return;
case PMCLOG_TYPE_PMCATTACH: [[fallthrough]];
case PMCLOG_TYPE_PMCDETACH:
case PMCLOG_TYPE_USERDATA:
case PMCLOG_TYPE_PROCCSW:
case PMCLOG_TYPE_DROPNOTIFY:
return;
};
}
void
pmcview::process(struct pmclog_parse_state *p)
{
struct pmclog_ev ev;
while (pmclog_read(p, &ev) == 0) {
process(ev);
}
}
void
pmcview::process(struct pmclog_ev_initialize &p)
{
tscfreq = p.pl_tsc_freq;
}
void
pmcview::process(__unused struct pmclog_ev_closelog &p)
{
}
void
pmcview::process(struct pmclog_ev_pmcallocate &p)
{
pmcid[p.pl_pmcid] = p.pl_event;
if (pmcinfo.find(p.pl_event) == pmcinfo.end()) {
pmcinfo.emplace(p.pl_event, p);
}
}
void
pmcview::process(struct pmclog_ev_pmcallocatedyn &p)
{
pmcid[p.pl_pmcid] = p.pl_event;
if (pmcinfo.find(p.pl_event) == pmcinfo.end()) {
pmcinfo.emplace(p.pl_event, p);
}
}
void
pmcview::process(struct pmclog_ev_proccreate &p)
{
procs[p.pl_pid] = procinfo(p);
proccreate(p.pl_pid);
}
void
pmcview::process(struct pmclog_ev_procexit &p)
{
procs.erase(p.pl_pid);
procexit(p.pl_pid);
}
void
pmcview::process(struct pmclog_ev_procexec &p)
{
procs[p.pl_pid].map.clear();
procs[p.pl_pid].name = basename(p.pl_pathname);
procs[p.pl_pid].fullpath = p.pl_pathname;
procs[p.pl_pid].baseaddr = p.pl_baseaddr;
procs[p.pl_pid].dynaddr = p.pl_dynaddr;
image im = loadimage(p.pl_pathname);
mapimage(p.pl_pid, im, im.vaddr + p.pl_dynaddr);
if (im.isdynamic) {
image rtldim = loadimage(im.loader);
mapimage(p.pl_pid, rtldim, p.pl_baseaddr);
}
procexec(p.pl_pid);
}
void
pmcview::process(struct pmclog_ev_procfork &p)
{
procs[p.pl_newpid] = procs[p.pl_oldpid];
proccreate(p.pl_newpid);
}
void
pmcview::process(struct pmclog_ev_sysexit &p)
{
procs.erase(p.pl_pid);
}
void
pmcview::process(struct pmclog_ev_threadcreate &p)
{
procs[p.pl_pid].threads[p.pl_tid] = threadinfo(p.pl_tdname);
tidtopid[p.pl_tid] = p.pl_pid;
}
void
pmcview::process(struct pmclog_ev_threadexit &p)
{
pid_t pid = tidtopid[p.pl_tid];
procs[pid].threads.erase(p.pl_tid);
tidtopid.erase(p.pl_tid);
}
image
pmcview::loadimage(const std::string &path)
{
std::string fullpath;
image im;
GElf_Ehdr eh;
Elf *e;
const char *elf;
uint64_t start;
uint64_t end;
size_t shstrndx;
int fd, i;
bool foundexec;
if (images.find(path) != images.end())
return images[path];
im = image();
if (path == "unknown") {
return (im);
}
fullpath = sysroot + path;
if (access(fullpath.c_str(), R_OK) != 0)
return (im);
fd = open(fullpath.c_str(), O_RDONLY, 0);
if (fd < 0) {
warnx("WARNING: Cannot open \"%s\".",
fullpath.c_str());
return (im);
}
e = elf_begin(fd, ELF_C_READ, NULL);
if (e == NULL) {
warnx("WARNING: Cannot read \"%s\".",
fullpath.c_str());
close(fd);
return (im);
}
if (gelf_getehdr(e, &eh) != &eh) {
warnx("WARNING: Cannot read the ELF header for \"%s\": %s.",
fullpath.c_str(), elf_errmsg(-1));
goto done;
}
if (eh.e_type != ET_EXEC && eh.e_type != ET_DYN && eh.e_type != ET_REL) {
warnx("WARNING: ELF file type is unsupported for \"%s\".",
fullpath.c_str());;
goto done;
}
elf = elf_rawfile(e, NULL);
if (elf == NULL) {
warnx("WARNING: Cannot read the ELF file for \"%s\": %s.",
fullpath.c_str(), elf_errmsg(-1));
goto done;
}
if (eh.e_type != ET_REL) {
foundexec = false;
for (i = 0; i < eh.e_phnum; i++) {
GElf_Phdr ph;
if (gelf_getphdr(e, i, &ph) != &ph) {
warnx("WARNING: Cannot read program header for \"%s\": %s.",
fullpath.c_str(), elf_errmsg(-1));
goto done;
}
if (ph.p_type == PT_DYNAMIC) {
im.isdynamic = 1;
continue;
}
if (ph.p_type == PT_INTERP) {
im.loader = elf + ph.p_offset;
}
if (ph.p_type == PT_LOAD) {
if ((ph.p_flags & PF_X) != 0 && !foundexec) {
im.vaddr = ph.p_vaddr & ~(ph.p_align - 1);
foundexec = true;
}
}
}
}
elf_getshdrstrndx(e, &shstrndx);
start = ~(0x0ULL);
end = 0;
for (i = 0; i < eh.e_shnum; i++) {
GElf_Shdr sh;
Elf_Scn *scn;
char *scname;
scn = elf_getscn(e, i);
if (scn == NULL) {
warnx("WARNING: Could not retrieve section descriptor for \"%s\".",
fullpath.c_str());
goto done;
}
if (gelf_getshdr(scn, &sh) != &sh) {
warnx("WARNING: Could not retrieve section header for \"%s\".",
fullpath.c_str());
goto done;
}
if (sh.sh_flags & SHF_EXECINSTR) {
start = std::min(start, sh.sh_addr);
end = std::max(end, sh.sh_addr + sh.sh_size);
}
scname = elf_strptr(e, shstrndx, sh.sh_name);
if (scname != NULL && strcmp(scname, ".debug_info") == 0)
im.dwarf = fullpath;
}
im.start = start;
im.end = end;
im.binary = path;
im.path = fullpath;
im.name = basename(fullpath);
if (im.dwarf == "") {
std::string sympath = fullpath + ".debug";
if (access(sympath.c_str(), R_OK) == 0) {
im.dwarf = sympath;
}
}
if (im.dwarf == "") {
std::string sympath = sysroot + "/usr/lib/debug" + path + ".debug";
if (access(sympath.c_str(), R_OK) == 0) {
im.dwarf = sympath;
}
}
images[path] = im;
done:
elf_end(e);
close(fd);
return (im);
}
void
pmcview::loadsymboltable(image *im, Elf *e, Elf_Scn *scn, GElf_Shdr *sh)
{
size_t n, nsyms;
char *fname;
GElf_Sym sym;
Elf_Data *data;
syminfo si;
si = syminfo();
if ((data = elf_getdata(scn, nullptr)) == nullptr)
return;
nsyms = sh->sh_size / sh->sh_entsize;
for (n = 0; n < nsyms; n++) {
if (gelf_getsym(data, (int) n, &sym) != &sym)
return;
if (GELF_ST_TYPE(sym.st_info) != STT_FUNC)
continue;
if (sym.st_shndx == STN_UNDEF)
continue;
if ((fname = elf_strptr(e, sh->sh_link, sym.st_name)) == NULL)
continue;
for (int i = 0; fname[i] != 0 && i < 32; i++) {
if (fname[i] < 0) {
printf("EEEK SYMBOL\n");
printf("%s\n", fname);
assert(false);
}
}
si.offset = sym.st_value;
si.length = sym.st_size;
si.binary = im->name;
si.name = fname;
im->symbols[si.offset] = si;
}
}
void
pmcview::loadsymbols(image *im)
{
int i;
int fd;
Elf *e;
Elf_Scn *scn;
GElf_Ehdr eh;
GElf_Shdr sh;
if (access(im->path.c_str(), R_OK) != 0)
return;
fd = open(im->path.c_str(), O_RDONLY, 0);
if (fd < 0) {
warnx("WARNING: Cannot open \"%s\".",
im->path.c_str());
return;
}
e = elf_begin(fd, ELF_C_READ, NULL);
if (e == NULL) {
warnx("WARNING: Cannot read \"%s\".",
im->path.c_str());
close(fd);
return;
}
if (gelf_getehdr(e, &eh) != &eh) {
warnx("WARNING: Cannot read the ELF header for \"%s\": %s.",
im->path.c_str(), elf_errmsg(-1));
goto done;
}
for (i = 0; i < eh.e_shnum; i++) {
scn = elf_getscn(e, i);
if (scn == NULL) {
warnx("WARNING: Could not retrieve section descriptor for \"%s\".",
im->path.c_str());
goto done;
}
if (gelf_getshdr(scn, &sh) != &sh) {
warnx("WARNING: Could not retrieve section header for \"%s\".",
im->path.c_str());
goto done;
}
if (sh.sh_type == SHT_SYMTAB || sh.sh_type == SHT_DYNSYM) {
loadsymboltable(im, e, scn, &sh);
}
}
done:
elf_end(e);
close(fd);
}
void
pmcview::mapimage(int pid, const image &im, uint64_t start)
{
uint64_t offset;
vmmap map;
if (im.start == 0 && im.end == 0)
return;
offset = start - im.vaddr;
map.lowpc = im.start + offset;
map.highpc = im.end + offset;
map.image = im.binary;
procs[pid].map[start] = map;
}
void
pmcview::process(struct pmclog_ev_map_in &p)
{
pid_t pid = (p.pl_pid == -1) ? 0 : p.pl_pid;
image im = loadimage(p.pl_pathname);
mapimage(pid, im, p.pl_start);
}
void
pmcview::process(struct pmclog_ev_map_out &p)
{
pid_t pid = (p.pl_pid == -1) ? 0 : p.pl_pid;
procinfo &proc = procs[pid];
proc.map.erase(p.pl_start);
}
void
pmcview::process(struct pmclog_ev_callchain &p)
{
int i;
uint8_t *hdr = (uint8_t *)&p.pl_pc[0];
uint8_t type, len;
uintfptr_t *cc = &p.pl_pc[1];
ibsfetchinfo ibsf;
ibsopinfo ibso;
assert(p.pl_pid != ~(uint32_t)0);
ibsf.len = 0;
ibso.len = 0;
if (p.pl_cpuflags & PMC_CC_F_MULTIPART) {
for (i = 0; i < 4; i++) {
type = hdr[2 * i];
len = hdr[2 * i + 1];
switch (type) {
case PMC_CC_MULTIPART_IBS_FETCH:
ibsf.len = len;
ibsf.ctl = cc[PMC_MPIDX_FETCH_CTL];
ibsf.extctl = cc[PMC_MPIDX_FETCH_EXTCTL];
ibsf.linaddr = cc[PMC_MPIDX_FETCH_LINADDR];
ibsf.physaddr = cc[PMC_MPIDX_FETCH_PHYSADDR];
break;
case PMC_CC_MULTIPART_IBS_OP:
ibso.len = len;
ibso.ctl = cc[PMC_MPIDX_OP_CTL];
ibso.rip = cc[PMC_MPIDX_OP_RIP];
ibso.data = cc[PMC_MPIDX_OP_DATA];
ibso.data2 = cc[PMC_MPIDX_OP_DATA2];
ibso.data3 = cc[PMC_MPIDX_OP_DATA3];
ibso.linaddr = cc[PMC_MPIDX_OP_DC_LINADDR];
ibso.physaddr = cc[PMC_MPIDX_OP_DC_PHYSADDR];
ibso.tgtrip = cc[PMC_MPIDX_OP_TGT_RIP];
ibso.data4 = cc[PMC_MPIDX_OP_DATA4];
break;
}
cc += len;
}
}
auto pinfo = procs.find(p.pl_pid);
if (pinfo == procs.end())
return;
if (filter.filterpid && filter.pids.count(p.pl_pid) == 0)
return;
if (filter.filtertid && filter.tids.count(p.pl_tid) == 0)
return;
if (filter.filterprogram && filter.programs.count(pinfo->second.name) == 0)
return;
if (filter.filterthread) {
auto tinfo = pinfo->second.threads.find(p.pl_tid);
if (tinfo == pinfo->second.threads.end())
return;
if (filter.threads.count(tinfo->second.name) == 0)
return;
}
if (filter.filterevent) {
auto pmc = pmcid.find(p.pl_pmcid);
if (pmc == pmcid.end())
return;
if (filter.events.count(pmcinfo[pmc->second].name) == 0)
return;
}
int cpunum = PMC_CALLCHAIN_CPUFLAGS_TO_CPU(p.pl_cpuflags);
if (filter.filtercpu && !CPU_ISSET(cpunum, &filter.cpus)) {
return;
}
int usermode = PMC_CALLCHAIN_CPUFLAGS_TO_USERMODE(p.pl_cpuflags);
if (filter.useronly && usermode == 0) {
return;
}
if (filter.kernelonly && usermode != 0) {
return;
}
if (ibsf.len) {
if (filter.ibs_ldlat > IBS_FETCH_CTL_TO_LAT(ibsf.ctl))
return;
callchain(p, ibsf, cc, len);
} else if (ibso.len) {
if (filter.ibs_oplat > IBS_OP_DATA_TO_COMPTORET(ibso.data))
return;
if (filter.ibs_ldlat > IBS_OP_DATA3_TO_DCLAT(ibso.data3))
return;
if (filter.ibs_mmio) {
if (((ibso.data3 & IBS_OP_DATA3_STORE) == 0) &&
((ibso.data3 & IBS_OP_DATA3_LOAD) == 0))
return;
if (((ibso.data3 & IBS_OP_DATA3_UCMEMACCESS) == 0) &&
((ibso.data3 & IBS_OP_DATA3_WCMEMACCESS) == 0))
return;
}
callchain(p, ibso, cc, len);
} else {
callchain(p, cc, len);
}
}
uint32_t
pmcview::pmcidtoeventid(uint32_t id)
{
return pmcid[id];
}
std::string
pmcview::pidtoname(pid_t pid)
{
auto p = procs.find(pid);
if (p == procs.end())
return ("");
else
return (p->second.name);
}
syminfo
pmcview::addrtosymbol(pid_t pid, uint64_t addr)
{
syminfo si;
procinfo &proc = procs[pid];
vmmap *vm;
image *im;
im = nullptr;
auto v = proc.map.upper_bound(addr);
if (v != proc.map.begin()) {
v--;
if (v->second.lowpc <= addr && v->second.highpc >= addr) {
vm = &v->second;
im = &images[v->second.image];
}
}
if (im == nullptr) {
#ifdef DEBUG_VIEW
printf("Symbol not found for 0x%lx in %s\n", addr, proc.name.c_str());
printvm(pid);
#endif
return syminfo();
}
addr -= vm->lowpc - im->start;
if (im->symbols.size() == 0) {
loadsymbols(im);
}
auto s = im->symbols.upper_bound(addr);
if (s != im->symbols.begin())
s--;
if (s->second.offset <= addr &&
(s->second.offset + s->second.length + 1) >= addr) {
si = s->second;
si.funcoff = addr - si.offset;
return si;
}
if (s->second.offset <= addr && s->second.length == 0 && pid == 0) {
si = s->second;
si.funcoff = addr - si.offset;
return si;
}
#ifdef DEBUG_VIEW
printf("Symbol not found in image %lx\n", addr);
printf("%lx %lx %s\n", s->second.offset, s->second.s_length, s->second.s_name.c_str());
printf("%lx\n", addr);
#endif
si = syminfo();
si.binary = im->name;
std::stringstream str = std::stringstream();
str << "0x" << std::hex << addr;
si.name = str.str();
return si;
}
void
pmcview::printvm(pid_t pid)
{
procinfo &proc = procs[pid];
printf("%-18s %-18s %s\n", "Low PC", "High PC", "Image");
for (auto &i : proc.map) {
printf("0x%08" PRIx64 " 0x%08" PRIx64 " %s\n", i.second.lowpc,
i.second.highpc, i.second.image.c_str());
}
}