Page MenuHomeFreeBSD

D58926.diff
No OneTemporary

D58926.diff

diff --git a/usr.sbin/pmc/cmd_pmc_annotate.cc b/usr.sbin/pmc/cmd_pmc_annotate.cc
new file mode 100644
--- /dev/null
+++ b/usr.sbin/pmc/cmd_pmc_annotate.cc
@@ -0,0 +1,303 @@
+/*-
+ * SPDX-License-Identifier: BSD-2-Clause
+ *
+ * Copyright (c) 2026, Netflix, Inc.
+ *
+ * This software was developed by Ali Mashtizadeh under the sponsorship from
+ * Netflix, Inc.
+ *
+ * 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/cpuset.h>
+#include <sys/event.h>
+#include <sys/queue.h>
+#include <sys/socket.h>
+#include <sys/stat.h>
+#include <sys/sysctl.h>
+#include <sys/time.h>
+#include <sys/ttycom.h>
+#include <sys/user.h>
+#include <sys/wait.h>
+
+#include <assert.h>
+#include <curses.h>
+#include <err.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <getopt.h>
+#include <kvm.h>
+#include <libgen.h>
+#include <limits.h>
+#include <locale.h>
+#include <math.h>
+#include <pmc.h>
+#include <pmclog.h>
+#include <regex.h>
+#include <signal.h>
+#include <stdarg.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <stddef.h>
+#include <string.h>
+#include <sysexits.h>
+#include <unistd.h>
+
+#include "cmd_pmc.h"
+
+#include <iostream>
+#include <map>
+#include <set>
+#include <sstream>
+#include <string>
+#include <unordered_map>
+#include <unordered_set>
+#include <vector>
+
+#include <dev/hwpmc/hwpmc_ibs.h>
+#include "display.hh"
+#include "objdump.hh"
+#include "view.hh"
+
+class source_sample
+{
+public:
+ source_sample() : cycles(0), count(0), misp(0),
+ l1miss(0), l2miss(0), l3miss(0),
+ l1tlbmiss(0), l2tlbmiss(0) { }
+ ~source_sample() { }
+
+ int64_t cycles;
+ int64_t count;
+
+ int64_t misp;
+
+ int64_t l1miss;
+ int64_t l2miss;
+ int64_t l3miss;
+
+ int64_t l1tlbmiss;
+ int64_t l2tlbmiss;
+};
+
+class annotate_view : public pmcview
+{
+public:
+ annotate_view(const std::string &binpath, const std::string &func)
+ : binpath(binpath), func(func), samples()
+ {
+ source = disassemble_symbol(binpath, func);
+
+ for (auto &s : source) {
+ if (s.addr != 0) {
+ base = s.addr;
+ break;
+ }
+ }
+ }
+ ~annotate_view() { }
+
+ virtual void
+ callchain(struct pmclog_ev_callchain &p, ibsopinfo &o,
+ __unused uintfptr_t *cc, __unused int len)
+ {
+ bool usermode = (o.rip >> 63) == 0;
+
+ syminfo sym = addrtosymbol(usermode ? p.pl_pid : 0, o.rip);
+ if (sym.binary == "")
+ return;
+
+ if (binpath != sym.binary)
+ return;
+ if (func != sym.name)
+ return;
+
+ auto s = samples.find(base + sym.funcoff);
+ if (s == samples.end()) {
+ samples[base + sym.funcoff] = source_sample();
+ s = samples.find(base + sym.funcoff);
+ }
+
+ s->second.cycles += IBS_OP_DATA_TO_TAGTORET(o.data);
+ s->second.count += 1;
+
+ if (o.data & IBS_OP_DATA_BRANCHMISPREDICTED)
+ s->second.misp += 1;
+
+ if (o.data3 & IBS_OP_DATA3_DCMISS)
+ s->second.l1miss += 1;
+ if (o.data3 & IBS_OP_DATA3_L2MISS)
+ s->second.l2miss += 1;
+ if ((o.data3 & IBSOPDATA2_VALIDMASK) == IBSOPDATA2_VALIDMASK) {
+ uint64_t datasrc = IBS_OP_DATA2_DATASRC(o.data2);
+ if ((datasrc == IBS_DATASRC_NEARFARCACHE_NEAR) ||
+ (datasrc == IBS_DATASRC_NEARFARCACHE_FAR))
+ s->second.l3miss += 1;
+ }
+
+ if (o.data3 & IBS_OP_DATA3_DCL1TLBMISS)
+ s->second.l1tlbmiss += 1;
+ }
+
+ virtual void
+ print()
+ {
+ // Print header
+ table t = table();
+ t.addcolumn("Address", true);
+ t.addcolumn("Source", true);
+ t.addcolumn("Latency");
+ t.addcolumn("Count");
+ t.addcolumn("Misp.");
+ t.addcolumn("Cache L1/2/3");
+ t.addcolumn("TLB");
+
+ for (auto &s : source) {
+ std::vector<field> r;
+ std::stringstream addr = std::stringstream();
+
+ if (s.addr != 0)
+ addr << "0x" << std::hex << s.addr;
+ r.emplace_back(addr.str());
+ if (s.type == DisassemblyLine::Assembly)
+ r.emplace_back(" " + s.line);
+ if (s.type == DisassemblyLine::Label)
+ r.emplace_back(s.line + ":");
+ if (s.type == DisassemblyLine::Source)
+ r.emplace_back("; " + s.line);
+
+ auto samp = samples.find(s.addr);
+ if (samp != samples.end()) {
+ r.emplace_back(samp->second.cycles / samp->second.count);
+ r.emplace_back(samp->second.count);
+
+ if (samp->second.misp)
+ r.emplace_back(samp->second.misp);
+ else
+ r.emplace_back(std::string());
+
+ std::string cache = "";
+ if (samp->second.l1miss) {
+ cache = std::to_string(samp->second.l1miss)
+ + " / " + std::to_string(samp->second.l2miss)
+ + " / " + std::to_string(samp->second.l3miss);
+ }
+ r.emplace_back(cache);
+
+ if (samp->second.l1tlbmiss)
+ r.emplace_back(samp->second.l1tlbmiss);
+ else
+ r.emplace_back(std::string());
+ } else {
+ r.emplace_back(std::string());
+ r.emplace_back(std::string());
+ r.emplace_back(std::string());
+ r.emplace_back(std::string());
+ r.emplace_back(std::string());
+ }
+
+ t.addrow(r);
+ }
+
+ /* Checking to see if we have any unaligned samples in x86 */
+ for (auto &s : samples) {
+ bool found = false;
+
+ for (auto &l : source) {
+ if (s.first == l.addr)
+ found = true;
+ }
+
+ if (!found) {
+ printf("MAY BE A BUG! Sample does not match address: 0x%lu\n", s.first);
+ }
+ }
+
+ t.print();
+ }
+protected:
+ std::string binpath;
+ std::string func;
+ uint64_t base;
+ std::vector<DisassemblyLine> source;
+ std::unordered_map<uint64_t, source_sample> samples;
+};
+
+
+static struct option longopts[] = {
+ PMCFILTER_LOPTS,
+ { NULL, 0, NULL, 0 }
+};
+
+static void
+usage(void)
+{
+ printf("Usage: pmc annotate [options] [binary] [function] [pmclog]\n\n");
+ printf("Display annotated assembly\n\n");
+ printf("Options:\n");
+ PMCFILTER_PRINTOPTS();
+}
+
+int
+cmd_pmc_annotate(int argc, char **argv)
+{
+ struct pmcfilter filter = pmcfilter();
+ const char *logfile = "default.log";
+ int option, logfd;
+
+ while ((option = getopt_long(argc, argv, PMCFILTER_SOPTS, longopts, NULL)) != -1) {
+ switch (option) {
+ PMCFILTER_CASE(filter);
+ case '?':
+ default:
+ usage();
+ exit(EX_USAGE);
+ }
+ }
+ argc -= optind;
+ argv += optind;
+ if (argc != 2 && argc != 3) {
+ usage();
+ exit(EX_USAGE);
+ }
+ if (argc == 3)
+ logfile = argv[2];
+
+ setup_screen();
+
+ if ((logfd = open(logfile, O_RDONLY)) < 0) {
+ errx(EX_OSERR, "ERROR: Cannot open \"%s\" for reading: %s.", argv[0],
+ strerror(errno));
+ }
+
+ annotate_view v = annotate_view(argv[0], argv[1]);
+ v.setfilter(filter);
+ v.process(logfd);
+ v.print();
+
+ close(logfd);
+
+ return (EX_OK);
+}
diff --git a/usr.sbin/pmc/objdump.hh b/usr.sbin/pmc/objdump.hh
new file mode 100644
--- /dev/null
+++ b/usr.sbin/pmc/objdump.hh
@@ -0,0 +1,30 @@
+#ifndef OBJDUMP_PARSER_HPP
+#define OBJDUMP_PARSER_HPP
+
+#include <string>
+#include <vector>
+#include <utility>
+#include <cstdint>
+
+class DisassemblyLine
+{
+public:
+ enum Type {
+ Assembly,
+ Source,
+ Label
+ };
+ DisassemblyLine(Type type, uint64_t addr, std::string line) : type(type), addr(addr), line(line) { }
+ ~DisassemblyLine() { }
+ Type type;
+ uint64_t addr;
+ std::string line;
+};
+
+/*
+ * Run objdump and extract assembly of a single function.
+ */
+std::vector<DisassemblyLine> disassemble_symbol(
+ const std::string& binary_path, const std::string& function_name);
+
+#endif // OBJDUMP_PARSER_HPP
diff --git a/usr.sbin/pmc/objdump.cc b/usr.sbin/pmc/objdump.cc
new file mode 100644
--- /dev/null
+++ b/usr.sbin/pmc/objdump.cc
@@ -0,0 +1,173 @@
+
+#include <iostream>
+#include <sstream>
+#include <algorithm>
+#include <cctype>
+#include <memory>
+#include <array>
+#include <stdexcept>
+
+#include "objdump.hh"
+
+// Helper to escape shell arguments to prevent injection
+static std::string shell_escape(const std::string& arg) {
+ std::string escaped = "'";
+ for (char c : arg) {
+ if (c == '\'') {
+ escaped += "'\\''";
+ } else {
+ escaped += c;
+ }
+ }
+ escaped += "'";
+ return escaped;
+}
+
+static std::string
+trim(const std::string &str)
+{
+ size_t first = str.find_first_not_of(" \t\r\n");
+ if (first == std::string::npos) {
+ return "";
+ }
+
+ size_t last = str.find_last_not_of(" \t\r\n");
+
+ return str.substr(first, (last - first + 1));
+}
+
+static bool
+is_hex_string(const std::string &s)
+{
+ if (s.empty())
+ return false;
+
+ for (char c : s) {
+ if (!std::isxdigit(static_cast<unsigned char>(c))) {
+ return false;
+ }
+ }
+
+ return true;
+}
+
+// Exec command using popen
+static std::string exec_command(const std::string& cmd) {
+ std::array<char, 256> buffer;
+ std::string result;
+ std::unique_ptr<FILE, decltype(&pclose)> pipe(popen(cmd.c_str(), "r"), pclose);
+
+ if (!pipe) {
+ throw std::runtime_error("popen() failed!");
+ }
+
+ while (fgets(buffer.data(), buffer.size(), pipe.get()) != nullptr) {
+ result += buffer.data();
+ }
+
+ return result;
+}
+
+static std::string
+expandtabs(const std::string& input) {
+ const int tab_width = 8;
+ int column = 0;
+ std::string result;
+
+ for (char c : input) {
+ if (c == '\t') {
+ int spaces = tab_width - (column % tab_width);
+
+ result.append(spaces, ' ');
+ column += spaces;
+ } else if (c == '\n' || c == '\r') {
+ result += c;
+ column = 0;
+ } else {
+ result += c;
+ ++column;
+ }
+ }
+
+ return result;
+}
+
+std::vector<DisassemblyLine>
+disassemble_symbol(const std::string& binary_path,
+ const std::string& function_name)
+{
+ std::vector<DisassemblyLine> results;
+ std::string escaped_bin = shell_escape(binary_path);
+ std::string escaped_func = shell_escape(function_name);
+
+ std::string cmd = "objdump --print-imm-hex --symbolize-operands --demangle -S --disassemble-symbols=" + escaped_func + " " + escaped_bin + " 2>/dev/null";
+
+ std::string output;
+ try {
+ output = exec_command(cmd);
+ } catch (...) {
+ return results; // Return empty vector if execution fails
+ }
+
+ std::stringstream ss(output);
+ std::string line;
+
+ while (std::getline(ss, line)) {
+ if (line.empty())
+ continue;
+
+ if (line[0] == ';') {
+ results.emplace_back(DisassemblyLine::Source, 0, expandtabs(line.substr(1,-1)));
+ continue;
+ }
+
+ if (line[0] == '<') {
+ size_t lblend_pos = line.rfind('>');
+
+ if (lblend_pos == std::string::npos)
+ continue;
+
+ results.emplace_back(DisassemblyLine::Label,
+ 0, line.substr(1, lblend_pos - 1));
+
+ continue;
+ }
+
+ // Find the first colon which follows the hex address
+ size_t colon_pos = line.find(':');
+ if (colon_pos == std::string::npos) {
+ continue;
+ }
+
+ // Substring before the colon must be a valid hex address
+ std::string addr_part = trim(line.substr(0, colon_pos));
+ if (!is_hex_string(addr_part)) {
+ continue;
+ }
+
+ uint64_t address = 0;
+ try {
+ address = std::stoull(addr_part, nullptr, 16);
+ } catch (...) {
+ continue;
+ }
+
+ // Disassembly after the colon
+ std::string after_colon = line.substr(colon_pos + 1);
+
+ // Disassembly mnemonic and arguments are separated from instruction bytes by a tab '\t'
+ size_t tab_pos = after_colon.find('\t');
+ std::string disassembly;
+ if (tab_pos != std::string::npos) {
+ disassembly = trim(after_colon.substr(tab_pos + 1));
+ } else {
+ disassembly = trim(after_colon);
+ }
+
+ disassembly = expandtabs(disassembly);
+
+ results.emplace_back(DisassemblyLine::Assembly, address, disassembly);
+ }
+
+ return results;
+}

File Metadata

Mime Type
text/plain
Expires
Thu, Sep 3, 9:05 PM (15 h, 40 m)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
37542910
Default Alt Text
D58926.diff (12 KB)

Event Timeline