1 /*-
2 * SPDX-License-Identifier: BSD-2-Clause
3 *
4 * Copyright (c) 2026, Netflix, Inc.
5 *
6 * This software was developed by Ali Mashtizadeh under the sponsorship from
7 * Netflix, Inc.
8 *
9 * Redistribution and use in source and binary forms, with or without
10 * modification, are permitted provided that the following conditions
11 * are met:
12 * 1. Redistributions of source code must retain the above copyright
13 * notice, this list of conditions and the following disclaimer.
14 * 2. Redistributions in binary form must reproduce the above copyright
15 * notice, this list of conditions and the following disclaimer in the
16 * documentation and/or other materials provided with the distribution.
17 *
18 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
19 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
20 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
21 * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
22 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
23 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
24 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
25 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
26 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
27 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
28 * SUCH DAMAGE.
29 *
30 */
31
32 #include <sys/types.h>
33 #include <sys/param.h>
34
35 #include <assert.h>
36 #include <err.h>
37 #include <fcntl.h>
38 #include <gelf.h>
39 #include <inttypes.h>
40 #include <libelf.h>
41 #include <pmclog.h>
42 #include <sysexits.h>
43 #include <unistd.h>
44
45 #include <cxxabi.h>
46 #include <cstring>
47 #include <iostream>
48 #include <map>
49 #include <set>
50 #include <string>
51 #include <sstream>
52 #include <unordered_map>
53 #include <unordered_set>
54
55 #include <dev/hwpmc/hwpmc_ibs.h>
56 #include "util.hh"
57 #include "view.hh"
58
59 std::string
to_string(bool show_line)60 syminfo::to_string(bool show_line)
61 {
62 int status;
63 char *demangled;
64 std::stringstream ss;
65
66 if (name == "") {
67 ss << "0x" << std::hex << offset;
68 } else if (name.length() >= 2 && name[0] == '_' && name[1] == 'Z') {
69 demangled = abi::__cxa_demangle(name.c_str(), nullptr, nullptr, &status);
70 if (status == 0) {
71 ss << demangled;
72 free(demangled);
73 } else {
74 ss << name;
75 }
76 } else {
77 ss << name;
78 }
79
80 if (!show_line)
81 return ss.str();
82
83 if (line) {
84 ss << ":" << std::dec << line;
85 } else if (funcoff) {
86 ss << "+0x" << std::hex << funcoff;
87 }
88
89 return ss.str();
90 }
91
pmcview()92 pmcview::pmcview() : tscfreq(0), pageshift(0), pmcid(), pmcinfo(), procs(),
93 tidtopid(), images(), sysroot(""), filter()
94 {
95 char *root;
96
97 root = getenv("SYSROOT");
98 if (root) {
99 sysroot = root;
100 }
101 }
102
~pmcview()103 pmcview::~pmcview()
104 {
105 }
106
107 void
setfilter(const pmcfilter & pmcfilter)108 pmcview::setfilter(const pmcfilter &pmcfilter)
109 {
110 filter = pmcfilter;
111 }
112
113 static int
readlog(int logfd,void * buf,size_t len)114 readlog(int logfd, void *buf, size_t len)
115 {
116 int status;
117 char *cur = (char *)buf;
118 size_t left = len;
119
120 while (left != 0) {
121 status = read(logfd, cur, left);
122 if (status < 0) {
123 if (errno == EINTR || errno == EAGAIN)
124 continue;
125 else
126 return status;
127 }
128 if (status == 0)
129 return status;
130
131 cur += status;
132 left -= status;
133 }
134
135 return len;
136 }
137
138 int
process_sysinfo(int logfd,const pmchdr_infohdr & infohdr)139 pmcview::process_sysinfo(int logfd, const pmchdr_infohdr &infohdr)
140 {
141 int status;
142 pmchdr_sysinfo sysinfo;
143
144 if (infohdr.length != sizeof(sysinfo))
145 errx(EX_IOERR, "PMC log headers have an unexpected size");
146
147 status = readlog(logfd, &sysinfo, sizeof(sysinfo));
148 if (status < 0 || status != infohdr.length)
149 errx(EX_IOERR, "readlog");
150
151 cpumodel = sysinfo.cpumodel;
152 osrelease = sysinfo.osrelease;
153 buildid = sysinfo.buildid;
154
155 return 0;
156 }
157
158 int
process_pmcinfo(int logfd,const pmchdr_infohdr & infohdr)159 pmcview::process_pmcinfo(int logfd, const pmchdr_infohdr &infohdr)
160 {
161 int status;
162 union {
163 pmchdr_pmcinfo pmcinfo;
164 __unused char reserve_max_size[256];
165 };
166
167 status = readlog(logfd, &pmcinfo, infohdr.length);
168 if (status < 0 || status != infohdr.length)
169 errx(EX_IOERR, "readlog");
170
171 extpmcinfo.emplace_back(pmcinfo.rate, std::string(pmcinfo.pmc));
172
173 return 0;
174 }
175
176 int
process_cpuidinfo(int logfd,const pmchdr_infohdr & infohdr)177 pmcview::process_cpuidinfo(int logfd, const pmchdr_infohdr &infohdr)
178 {
179 int status;
180 uint32_t *cpuidinfo;
181 int offset, len;
182 uint32_t root, maxleaf, count;
183
184 len = infohdr.length / 4;
185 cpuidinfo = new uint32_t[len];
186
187 status = readlog(logfd, cpuidinfo, infohdr.length);
188 if (status < 0 || status != infohdr.length)
189 errx(EX_IOERR, "readlog");
190
191 offset = 0;
192
193 while (offset < len) {
194 maxleaf = cpuidinfo[offset];
195
196 /*
197 * In x86 the roots contains the maximum leaf number present.
198 */
199 root = maxleaf & 0xFFFF0000;
200 count = maxleaf & 0x0000FFFF;
201
202 for (uint32_t i = 0; i <= count; i++) {
203 cpuid[root + i] = { cpuidinfo[4 * i + offset],
204 cpuidinfo[4 * i + 1 + offset],
205 cpuidinfo[4 * i + 2 + offset],
206 cpuidinfo[4 * i + 3 + offset] };
207 }
208
209 offset += 4 * (count + 1);
210 }
211
212 delete[] cpuidinfo;
213
214 return 0;
215 }
216
217 void
process(int logfd)218 pmcview::process(int logfd)
219 {
220 int status;
221 pmchdr_header hdr;
222 struct pmclog_parse_state *ps;
223
224 // Read header
225 status = readlog(logfd, &hdr, sizeof(hdr));
226 if (status < 0 || status != sizeof(hdr))
227 err(EX_IOERR, "readlog");
228 if (hdr.magic != PMC_HEADER_MAGIC)
229 errx(EX_DATAERR, "PMC log magic mismatch!");
230 if (hdr.version > PMC_HEADER_VERSION)
231 errx(EX_DATAERR, "PMC log version newer than supported!");
232
233 // Save architecture
234 arch = hdr.arch;
235
236 while (1) {
237 pmchdr_infohdr infohdr;
238
239 status = readlog(logfd, &infohdr, sizeof(infohdr));
240 if (status < 0 || status != sizeof(infohdr))
241 errx(EX_IOERR, "readlog");
242
243 if (infohdr.type == INFOHDR_TYPE_DONE) {
244 break;
245 } else if (infohdr.type == INFOHDR_TYPE_SYSINFO) {
246 process_sysinfo(logfd, infohdr);
247 } else if (infohdr.type == INFOHDR_TYPE_PMCINFO) {
248 process_pmcinfo(logfd, infohdr);
249 } else if (infohdr.type == INFOHDR_TYPE_CPUID) {
250 process_cpuidinfo(logfd, infohdr);
251 }
252 }
253
254 ps = static_cast<struct pmclog_parse_state*>(pmclog_open(logfd));
255 if (ps == NULL) {
256 errx(EX_OSERR, "ERROR: Cannot allocate pmclog parse state: %s\n",
257 strerror(errno));
258 }
259
260 process(ps);
261
262 pmclog_close(ps);
263 }
264
265 void
process(struct pmclog_ev & p)266 pmcview::process(struct pmclog_ev &p)
267 {
268 switch (p.pl_type) {
269 case PMCLOG_TYPE_INITIALIZE:
270 process(p.pl_u.pl_i);
271 return;
272 case PMCLOG_TYPE_CLOSELOG:
273 process(p.pl_u.pl_cl);
274 return;
275 case PMCLOG_TYPE_PMCALLOCATE:
276 process(p.pl_u.pl_a);
277 return;
278 case PMCLOG_TYPE_PMCALLOCATEDYN:
279 process(p.pl_u.pl_ad);
280 return;
281 case PMCLOG_TYPE_PROC_CREATE:
282 process(p.pl_u.pl_pc);
283 return;
284 case PMCLOG_TYPE_PROCEXIT:
285 process(p.pl_u.pl_e);
286 return;
287 case PMCLOG_TYPE_PROCEXEC:
288 process(p.pl_u.pl_x);
289 return;
290 case PMCLOG_TYPE_PROCFORK:
291 process(p.pl_u.pl_f);
292 return;
293 case PMCLOG_TYPE_SYSEXIT:
294 process(p.pl_u.pl_se);
295 return;
296 case PMCLOG_TYPE_MAP_IN:
297 process(p.pl_u.pl_mi);
298 return;
299 case PMCLOG_TYPE_MAP_OUT:
300 process(p.pl_u.pl_mo);
301 return;
302 case PMCLOG_TYPE_THR_CREATE:
303 process(p.pl_u.pl_tc);
304 return;
305 case PMCLOG_TYPE_THR_EXIT:
306 process(p.pl_u.pl_te);
307 return;
308 case PMCLOG_TYPE_CALLCHAIN:
309 process(p.pl_u.pl_cc);
310 return;
311 case PMCLOG_TYPE_PMCATTACH: [[fallthrough]];
312 case PMCLOG_TYPE_PMCDETACH:
313 case PMCLOG_TYPE_USERDATA:
314 case PMCLOG_TYPE_PROCCSW:
315 case PMCLOG_TYPE_DROPNOTIFY:
316 return;
317 };
318 }
319
320 void
process(struct pmclog_parse_state * p)321 pmcview::process(struct pmclog_parse_state *p)
322 {
323 struct pmclog_ev ev;
324
325 while (pmclog_read(p, &ev) == 0) {
326 process(ev);
327 }
328 }
329
330 void
process(struct pmclog_ev_initialize & p)331 pmcview::process(struct pmclog_ev_initialize &p)
332 {
333 tscfreq = p.pl_tsc_freq;
334 }
335
336 void
process(__unused struct pmclog_ev_closelog & p)337 pmcview::process(__unused struct pmclog_ev_closelog &p)
338 {
339 }
340
341 void
process(struct pmclog_ev_pmcallocate & p)342 pmcview::process(struct pmclog_ev_pmcallocate &p)
343 {
344 pmcid[p.pl_pmcid] = p.pl_event;
345 if (pmcinfo.find(p.pl_event) == pmcinfo.end()) {
346 pmcinfo.emplace(p.pl_event, p);
347 }
348 }
349
350 void
process(struct pmclog_ev_pmcallocatedyn & p)351 pmcview::process(struct pmclog_ev_pmcallocatedyn &p)
352 {
353 pmcid[p.pl_pmcid] = p.pl_event;
354 if (pmcinfo.find(p.pl_event) == pmcinfo.end()) {
355 pmcinfo.emplace(p.pl_event, p);
356 }
357 }
358
359 void
process(struct pmclog_ev_proccreate & p)360 pmcview::process(struct pmclog_ev_proccreate &p)
361 {
362 procs[p.pl_pid] = procinfo(p);
363
364 proccreate(p.pl_pid);
365 }
366
367 void
process(struct pmclog_ev_procexit & p)368 pmcview::process(struct pmclog_ev_procexit &p)
369 {
370 /*
371 * XXX: Seems we can recieve samples after the process exits we need
372 * some way to delay cleanup, then after the delayed cleanup discard
373 * delayed events.
374 */
375 procs.erase(p.pl_pid);
376
377 procexit(p.pl_pid);
378 }
379
380 void
process(struct pmclog_ev_procexec & p)381 pmcview::process(struct pmclog_ev_procexec &p)
382 {
383 procs[p.pl_pid].map.clear();
384 procs[p.pl_pid].name = basename(p.pl_pathname);
385 procs[p.pl_pid].fullpath = p.pl_pathname;
386 procs[p.pl_pid].baseaddr = p.pl_baseaddr;
387 procs[p.pl_pid].dynaddr = p.pl_dynaddr;
388
389 image im = loadimage(p.pl_pathname);
390
391 mapimage(p.pl_pid, im, im.vaddr + p.pl_dynaddr);
392
393 /*
394 * Map the dynamic runtime loader
395 */
396 if (im.isdynamic) {
397 image rtldim = loadimage(im.loader);
398
399 mapimage(p.pl_pid, rtldim, p.pl_baseaddr);
400 }
401
402 procexec(p.pl_pid);
403 }
404
405 void
process(struct pmclog_ev_procfork & p)406 pmcview::process(struct pmclog_ev_procfork &p)
407 {
408 procs[p.pl_newpid] = procs[p.pl_oldpid];
409
410 proccreate(p.pl_newpid);
411 }
412
413 void
process(struct pmclog_ev_sysexit & p)414 pmcview::process(struct pmclog_ev_sysexit &p)
415 {
416 procs.erase(p.pl_pid);
417 }
418
419 void
process(struct pmclog_ev_threadcreate & p)420 pmcview::process(struct pmclog_ev_threadcreate &p)
421 {
422 procs[p.pl_pid].threads[p.pl_tid] = threadinfo(p.pl_tdname);
423 tidtopid[p.pl_tid] = p.pl_pid;
424 }
425
426 void
process(struct pmclog_ev_threadexit & p)427 pmcview::process(struct pmclog_ev_threadexit &p)
428 {
429 pid_t pid = tidtopid[p.pl_tid];
430 procs[pid].threads.erase(p.pl_tid);
431 tidtopid.erase(p.pl_tid);
432 }
433
434 /*
435 * Load the ELF file to compute the values needed for the memory map and check
436 * if the dwarf symbols are available.
437 */
438 image
loadimage(const std::string & path,bool iskernel)439 pmcview::loadimage(const std::string &path, bool iskernel)
440 {
441 std::string fullpath;
442 image im;
443 GElf_Ehdr eh;
444 Elf *e;
445 const char *elf;
446 uint64_t start;
447 uint64_t end;
448 size_t shstrndx;
449 int fd, i;
450 bool foundexec;
451
452 if (images.find(path) != images.end())
453 return images[path];
454
455 im = image();
456
457 if (path == "unknown") {
458 return (im);
459 }
460
461 fullpath = sysroot + path;
462
463 if (access(fullpath.c_str(), R_OK) != 0)
464 return (im);
465
466 fd = open(fullpath.c_str(), O_RDONLY, 0);
467 if (fd < 0) {
468 warnx("WARNING: Cannot open \"%s\".",
469 fullpath.c_str());
470 return (im);
471 }
472
473 e = elf_begin(fd, ELF_C_READ, NULL);
474 if (e == NULL) {
475 warnx("WARNING: Cannot read \"%s\".",
476 fullpath.c_str());
477 close(fd);
478 return (im);
479 }
480
481 if (gelf_getehdr(e, &eh) != &eh) {
482 warnx("WARNING: Cannot read the ELF header for \"%s\": %s.",
483 fullpath.c_str(), elf_errmsg(-1));
484 goto done;
485 }
486
487 if (eh.e_type != ET_EXEC && eh.e_type != ET_DYN && eh.e_type != ET_REL) {
488 warnx("WARNING: ELF file type is unsupported for \"%s\".",
489 fullpath.c_str());;
490 goto done;
491 }
492
493 elf = elf_rawfile(e, NULL);
494 if (elf == NULL) {
495 warnx("WARNING: Cannot read the ELF file for \"%s\": %s.",
496 fullpath.c_str(), elf_errmsg(-1));
497 goto done;
498 }
499
500 if (eh.e_type != ET_REL) {
501 foundexec = false;
502 for (i = 0; i < eh.e_phnum; i++) {
503 GElf_Phdr ph;
504
505 if (gelf_getphdr(e, i, &ph) != &ph) {
506 warnx("WARNING: Cannot read program header for \"%s\": %s.",
507 fullpath.c_str(), elf_errmsg(-1));
508 goto done;
509 }
510
511 if (ph.p_type == PT_DYNAMIC) {
512 im.isdynamic = 1;
513 continue;
514 }
515
516 if (ph.p_type == PT_INTERP) {
517 im.loader = elf + ph.p_offset;
518 }
519
520 if (ph.p_type == PT_LOAD) {
521 if ((ph.p_flags & PF_X) != 0 && !foundexec) {
522 uint64_t alignment;
523
524 alignment = iskernel ? ph.p_align :
525 (pageshift != 0 ? 1ULL << pageshift :
526 (uint64_t)getpagesize());
527 im.vaddr = rounddown2(ph.p_vaddr, alignment);
528 foundexec = true;
529 }
530 }
531 }
532 }
533
534 elf_getshdrstrndx(e, &shstrndx);
535
536 start = ~(0x0ULL);
537 end = 0;
538 for (i = 0; i < eh.e_shnum; i++) {
539 GElf_Shdr sh;
540 Elf_Scn *scn;
541 char *scname;
542
543 scn = elf_getscn(e, i);
544 if (scn == NULL) {
545 warnx("WARNING: Could not retrieve section descriptor for \"%s\".",
546 fullpath.c_str());
547 goto done;
548 }
549
550 if (gelf_getshdr(scn, &sh) != &sh) {
551 warnx("WARNING: Could not retrieve section header for \"%s\".",
552 fullpath.c_str());
553 goto done;
554 }
555
556 if (sh.sh_flags & SHF_EXECINSTR) {
557 start = std::min(start, sh.sh_addr);
558 end = std::max(end, sh.sh_addr + sh.sh_size);
559 }
560
561 // Check if dwarf is embedded
562 scname = elf_strptr(e, shstrndx, sh.sh_name);
563 if (scname != NULL && strcmp(scname, ".debug_info") == 0)
564 im.dwarf = fullpath;
565 }
566
567 im.start = start;
568 im.end = end;
569 im.binary = path;
570 im.path = fullpath;
571 im.name = basename(fullpath);
572
573 /*
574 * If the dwarf symbols aren't embedded check:
575 * 1. SYSROOT + /path + .debug
576 * 2. SYSROOT + /usr/lib/debug + /path + .debug
577 */
578 if (im.dwarf == "") {
579 std::string sympath = fullpath + ".debug";
580 if (access(sympath.c_str(), R_OK) == 0) {
581 im.dwarf = sympath;
582 }
583 }
584 if (im.dwarf == "") {
585 std::string sympath = sysroot + "/usr/lib/debug" + path + ".debug";
586 if (access(sympath.c_str(), R_OK) == 0) {
587 im.dwarf = sympath;
588 }
589 }
590
591 images[path] = im;
592
593 done:
594 elf_end(e);
595 close(fd);
596
597 return (im);
598 }
599
600 void
loadsymboltable(image * im,Elf * e,Elf_Scn * scn,GElf_Shdr * sh)601 pmcview::loadsymboltable(image *im, Elf *e, Elf_Scn *scn, GElf_Shdr *sh)
602 {
603 size_t n, nsyms;
604 char *fname;
605 GElf_Sym sym;
606 Elf_Data *data;
607 syminfo si;
608
609 si = syminfo();
610
611 if ((data = elf_getdata(scn, nullptr)) == nullptr)
612 return;
613
614 nsyms = sh->sh_size / sh->sh_entsize;
615
616 for (n = 0; n < nsyms; n++) {
617 if (gelf_getsym(data, (int) n, &sym) != &sym)
618 return;
619
620 // XXX: We should load globals as well
621 if (GELF_ST_TYPE(sym.st_info) != STT_FUNC)
622 continue;
623
624 if (sym.st_shndx == STN_UNDEF)
625 continue;
626
627 if ((fname = elf_strptr(e, sh->sh_link, sym.st_name)) == NULL)
628 continue;
629
630 // XXX: Extra checks to make sure we don't get corrupted
631 for (int i = 0; fname[i] != 0 && i < 32; i++) {
632 if (!isascii(fname[i])) {
633 printf("EEEK SYMBOL\n");
634 printf("%s\n", fname);
635 assert(false);
636 }
637 }
638
639 si.offset = sym.st_value;
640 si.length = sym.st_size;
641 si.binary = im->name;
642 si.name = fname;
643
644 im->symbols[si.offset] = si;
645 }
646 }
647
648 void
loadsymbols(image * im)649 pmcview::loadsymbols(image *im)
650 {
651 int i;
652 int fd;
653 Elf *e;
654 Elf_Scn *scn;
655 GElf_Ehdr eh;
656 GElf_Shdr sh;
657
658 if (access(im->path.c_str(), R_OK) != 0)
659 return;
660
661 fd = open(im->path.c_str(), O_RDONLY, 0);
662 if (fd < 0) {
663 warnx("WARNING: Cannot open \"%s\".",
664 im->path.c_str());
665 return;
666 }
667
668 e = elf_begin(fd, ELF_C_READ, NULL);
669 if (e == NULL) {
670 warnx("WARNING: Cannot read \"%s\".",
671 im->path.c_str());
672 close(fd);
673 return;
674 }
675
676 if (gelf_getehdr(e, &eh) != &eh) {
677 warnx("WARNING: Cannot read the ELF header for \"%s\": %s.",
678 im->path.c_str(), elf_errmsg(-1));
679 goto done;
680 }
681
682 for (i = 0; i < eh.e_shnum; i++) {
683 scn = elf_getscn(e, i);
684 if (scn == NULL) {
685 warnx("WARNING: Could not retrieve section descriptor for \"%s\".",
686 im->path.c_str());
687 goto done;
688 }
689
690 if (gelf_getshdr(scn, &sh) != &sh) {
691 warnx("WARNING: Could not retrieve section header for \"%s\".",
692 im->path.c_str());
693 goto done;
694 }
695
696 if (sh.sh_type == SHT_SYMTAB || sh.sh_type == SHT_DYNSYM) {
697 loadsymboltable(im, e, scn, &sh);
698 }
699 }
700
701 done:
702 elf_end(e);
703 close(fd);
704 }
705
706 void
mapimage(int pid,const image & im,uint64_t start)707 pmcview::mapimage(int pid, const image &im, uint64_t start)
708 {
709 uint64_t offset;
710 vmmap map;
711
712 // Ignore empty images
713 if (im.start == 0 && im.end == 0)
714 return;
715
716 /*
717 * XXX: Need to adjust the address for the PowerPC kernel that is
718 * dynamic. Wonder if we can fix this elsewhere, because we should need
719 * a way to deal with this for KASLR?.
720 */
721
722 offset = start - im.vaddr;
723 map.lowpc = im.start + offset;
724 map.highpc = im.end + offset;
725 map.image = im.binary;
726
727 procs[pid].map[start] = map;
728 }
729
730 void
process(struct pmclog_ev_map_in & p)731 pmcview::process(struct pmclog_ev_map_in &p)
732 {
733 // Kernel map-in events should be mapped to pid 0
734 pid_t pid = (p.pl_pid == -1) ? 0 : p.pl_pid;
735 pageshift = p.pl_pageshift;
736
737 image im = loadimage(p.pl_pathname, pid == 0);
738
739 mapimage(pid, im, p.pl_start);
740 }
741
742 void
process(struct pmclog_ev_map_out & p)743 pmcview::process(struct pmclog_ev_map_out &p)
744 {
745 // Kernel map-in events should be mapped to pid 0
746 pid_t pid = (p.pl_pid == -1) ? 0 : p.pl_pid;
747 procinfo &proc = procs[pid];
748
749 /*
750 * XXX: We should handle all the mmap/munmap cases but only executable
751 * file mappings are included.
752 */
753 proc.map.erase(p.pl_start);
754 }
755
756 void
process(struct pmclog_ev_callchain & p)757 pmcview::process(struct pmclog_ev_callchain &p)
758 {
759 int i;
760 uint8_t *hdr = (uint8_t *)&p.pl_pc[0];
761 uint8_t type, len;
762 uintfptr_t *cc = &p.pl_pc[1];
763 ibsfetchinfo ibsf;
764 ibsopinfo ibso;
765
766 /*
767 * Callchain events are always attributed to one of the kernel
768 * processes. Make sure nobody breaks our assumption.
769 */
770 assert(p.pl_pid != ~(uint32_t)0);
771
772 ibsf.len = 0;
773 ibso.len = 0;
774
775 if (p.pl_cpuflags & PMC_CC_F_MULTIPART) {
776 for (i = 0; i < 4; i++) {
777 type = hdr[2 * i];
778 len = hdr[2 * i + 1];
779
780 switch (type) {
781 case PMC_CC_MULTIPART_IBS_FETCH:
782 ibsf.len = len;
783 ibsf.ctl = cc[PMC_MPIDX_FETCH_CTL];
784 ibsf.extctl = cc[PMC_MPIDX_FETCH_EXTCTL];
785 ibsf.linaddr = cc[PMC_MPIDX_FETCH_LINADDR];
786 ibsf.physaddr = cc[PMC_MPIDX_FETCH_PHYSADDR];
787 break;
788 case PMC_CC_MULTIPART_IBS_OP:
789 ibso.len = len;
790 ibso.ctl = cc[PMC_MPIDX_OP_CTL];
791 ibso.rip = cc[PMC_MPIDX_OP_RIP];
792 ibso.data = cc[PMC_MPIDX_OP_DATA];
793 ibso.data2 = cc[PMC_MPIDX_OP_DATA2];
794 ibso.data3 = cc[PMC_MPIDX_OP_DATA3];
795 ibso.linaddr = cc[PMC_MPIDX_OP_DC_LINADDR];
796 ibso.physaddr = cc[PMC_MPIDX_OP_DC_PHYSADDR];
797 ibso.tgtrip = cc[PMC_MPIDX_OP_TGT_RIP];
798 ibso.data4 = cc[PMC_MPIDX_OP_DATA4];
799 break;
800 }
801
802 cc += len;
803 }
804 }
805
806 /*
807 * Discard delayed events so we do not get unattributed samples.
808 */
809 auto pinfo = procs.find(p.pl_pid);
810 if (pinfo == procs.end())
811 return;
812
813 // Filter on pid, tid, program, thread and events
814 if (filter.filterpid && filter.pids.count(p.pl_pid) == 0)
815 return;
816 if (filter.filtertid && filter.tids.count(p.pl_tid) == 0)
817 return;
818 if (filter.filterprogram && filter.programs.count(pinfo->second.name) == 0)
819 return;
820 if (filter.filterthread) {
821 auto tinfo = pinfo->second.threads.find(p.pl_tid);
822 if (tinfo == pinfo->second.threads.end())
823 return;
824 if (filter.threads.count(tinfo->second.name) == 0)
825 return;
826 }
827 if (filter.filterevent) {
828 auto pmc = pmcid.find(p.pl_pmcid);
829 if (pmc == pmcid.end())
830 return;
831 if (filter.events.count(pmcinfo[pmc->second].name) == 0)
832 return;
833 }
834
835 // Filter on cpuset
836 int cpunum = PMC_CALLCHAIN_CPUFLAGS_TO_CPU(p.pl_cpuflags);
837 if (filter.filtercpu && !CPU_ISSET(cpunum, &filter.cpus)) {
838 return;
839 }
840
841 // Filter on user/kernel mode
842 int usermode = PMC_CALLCHAIN_CPUFLAGS_TO_USERMODE(p.pl_cpuflags);
843 if (filter.useronly && usermode == 0) {
844 return;
845 }
846 if (filter.kernelonly && usermode != 0) {
847 return;
848 }
849
850 // Advanced filters for AMD IBS
851 if (ibsf.len) {
852 if (filter.ibs_ldlat > IBS_FETCH_CTL_TO_LAT(ibsf.ctl))
853 return;
854 callchain(p, ibsf, cc, len);
855 } else if (ibso.len) {
856 if (filter.ibs_oplat > IBS_OP_DATA_TO_COMPTORET(ibso.data))
857 return;
858 if (filter.ibs_ldlat > IBS_OP_DATA3_TO_DCLAT(ibso.data3))
859 return;
860 if (filter.ibs_mmio) {
861 if (((ibso.data3 & IBS_OP_DATA3_STORE) == 0) &&
862 ((ibso.data3 & IBS_OP_DATA3_LOAD) == 0))
863 return;
864 if (((ibso.data3 & IBS_OP_DATA3_UCMEMACCESS) == 0) &&
865 ((ibso.data3 & IBS_OP_DATA3_WCMEMACCESS) == 0))
866 return;
867 }
868 callchain(p, ibso, cc, len);
869 } else {
870 callchain(p, cc, len);
871 }
872 }
873
874 uint32_t
pmcidtoeventid(uint32_t id)875 pmcview::pmcidtoeventid(uint32_t id)
876 {
877 return pmcid[id];
878 }
879
880 std::string
pidtoname(pid_t pid)881 pmcview::pidtoname(pid_t pid)
882 {
883 auto p = procs.find(pid);
884 if (p == procs.end())
885 return ("");
886 else
887 return (p->second.name);
888 }
889
890 syminfo
addrtosymbol(pid_t pid,uint64_t addr)891 pmcview::addrtosymbol(pid_t pid, uint64_t addr)
892 {
893 syminfo si;
894 procinfo &proc = procs[pid];
895 vmmap *vm;
896 image *im;
897
898 im = nullptr;
899 auto v = proc.map.upper_bound(addr);
900 if (v != proc.map.begin()) {
901 v--;
902 if (v->second.lowpc <= addr && v->second.highpc >= addr) {
903 vm = &v->second;
904 im = &images[v->second.image];
905 }
906 }
907
908 if (im == nullptr) {
909 #ifdef DEBUG_VIEW
910 printf("Symbol not found for 0x%lx in %s\n", addr, proc.name.c_str());
911 printvm(pid);
912 #endif
913 return syminfo();
914 }
915
916 // Adjust address into the ELF's virtual address space
917 addr -= vm->lowpc - im->start;
918
919 // Load symbols if they haven't been loaded
920 if (im->symbols.size() == 0) {
921 loadsymbols(im);
922 }
923
924 // Look for the first symbol
925 auto s = im->symbols.upper_bound(addr);
926 if (s != im->symbols.begin())
927 s--;
928 if (s->second.offset <= addr &&
929 (s->second.offset + s->second.length + 1) >= addr) {
930 si = s->second;
931 si.funcoff = addr - si.offset;
932 return si;
933 }
934
935 // Special case for assembly functions in the kernel
936 if (s->second.offset <= addr && s->second.length == 0 && pid == 0) {
937 si = s->second;
938 si.funcoff = addr - si.offset;
939 return si;
940 }
941
942 #ifdef DEBUG_VIEW
943 printf("Symbol not found in image %lx\n", addr);
944 printf("%lx %lx %s\n", s->second.offset, s->second.s_length, s->second.s_name.c_str());
945 printf("%lx\n", addr);
946 #endif
947
948 si = syminfo();
949 si.binary = im->name;
950 std::stringstream str = std::stringstream();
951 str << "0x" << std::hex << addr;
952 si.name = str.str();
953 return si;
954 }
955
956 void
printvm(pid_t pid)957 pmcview::printvm(pid_t pid)
958 {
959 procinfo &proc = procs[pid];
960
961 printf("%-18s %-18s %s\n", "Low PC", "High PC", "Image");
962 for (auto &i : proc.map) {
963 printf("0x%08" PRIx64 " 0x%08" PRIx64 " %s\n", i.second.lowpc,
964 i.second.highpc, i.second.image.c_str());
965 }
966 }
967