LCOV - code coverage report
Current view: top level - src - stackprof.cxx (source / functions) Coverage Total Hit
Test: elfutils-0.196 Lines: 13.8 % 890 123
Test Date: 2026-08-14 21:41:58 Functions: 13.8 % 58 8
Legend: Lines:     hit not hit
Branches: + taken - not taken # not executed
Branches: 7.6 % 761 58

             Branch data     Line data    Source code
       1                 :             : /* Collect stack-trace profiles of running program(s).
       2                 :             :    Copyright (C) 2025-2026 Red Hat, Inc.
       3                 :             :    This file is part of elfutils.
       4                 :             : 
       5                 :             :    This file is free software; you can redistribute it and/or modify
       6                 :             :    it under the terms of the GNU General Public License as published by
       7                 :             :    the Free Software Foundation; either version 3 of the License, or
       8                 :             :    (at your option) any later version.
       9                 :             : 
      10                 :             :    elfutils is distributed in the hope that it will be useful, but
      11                 :             :    WITHOUT ANY WARRANTY; without even the implied warranty of
      12                 :             :    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
      13                 :             :    GNU General Public License for more details.
      14                 :             : 
      15                 :             :    You should have received a copy of the GNU General Public License
      16                 :             :    along with this program.  If not, see <http://www.gnu.org/licenses/>.  */
      17                 :             : 
      18                 :             : #ifdef HAVE_CONFIG_H
      19                 :             : # include <config.h>
      20                 :             : #endif
      21                 :             : 
      22                 :             : #include "printversion.h"
      23                 :             : 
      24                 :             : #include <string>
      25                 :             : #include <memory>
      26                 :             : #include <iomanip>
      27                 :             : #include <map>
      28                 :             : #include <unordered_map>
      29                 :             : #include <vector>
      30                 :             : #include <bitset>
      31                 :             : #include <stdexcept>
      32                 :             : #include <cstring>
      33                 :             : #include <csignal>
      34                 :             : #include <cassert>
      35                 :             : #include <chrono>
      36                 :             : #include <iostream>
      37                 :             : #include <fstream>
      38                 :             : #include <sstream>
      39                 :             : #include <cinttypes>
      40                 :             : #include <format>
      41                 :             : #include <filesystem>
      42                 :             : 
      43                 :             : #include <sys/utsname.h>
      44                 :             : 
      45                 :             : #include <sys/syscall.h>
      46                 :             : #include <sys/ioctl.h>
      47                 :             : #include <sys/mman.h>
      48                 :             : #include <sys/wait.h>
      49                 :             : #include <poll.h>
      50                 :             : #ifdef HAVE_LINUX_PERF_EVENT_H
      51                 :             : #include <linux/perf_event.h>
      52                 :             : #endif
      53                 :             : #include <argp.h>
      54                 :             : #include <fcntl.h>
      55                 :             : #include <dirent.h>
      56                 :             : 
      57                 :             : #include <system.h>
      58                 :             : 
      59                 :             : #ifdef HAVE_PERFMON_PFMLIB_PERF_EVENT_H
      60                 :             : #include <perfmon/pfmlib_perf_event.h>
      61                 :             : #endif
      62                 :             : 
      63                 :             : #include <json-c/json.h>
      64                 :             : 
      65                 :             : #include <gelf.h>
      66                 :             : #include <dwarf.h>
      67                 :             : #include <libdwfl.h>
      68                 :             : #include <libdwfl_stacktrace.h>
      69                 :             : #include <libdw.h>
      70                 :             : #include "../libebl/libebl.h"
      71                 :             : 
      72                 :             : // optional debug code
      73                 :             : //#define STACKPROF_STATS_DEBUG
      74                 :             : 
      75                 :             : using namespace std;
      76                 :             : 
      77                 :             : ////////////////////////////////////////////////////////////////////////
      78                 :             : // find_debuginfo callbacks
      79                 :             : 
      80                 :             : #ifdef FIND_DEBUGINFO
      81                 :             : 
      82                 :             : static char *debuginfo_path = NULL;
      83                 :             : 
      84                 :             : static const Dwfl_Callbacks dwfl_cfi_callbacks =
      85                 :             : {
      86                 :             :   .find_elf = dwflst_tracker_linux_proc_find_elf,
      87                 :             :   .find_debuginfo = dwfl_standard_find_debuginfo,
      88                 :             :   .debuginfo_path = &debuginfo_path,
      89                 :             : };
      90                 :             : 
      91                 :             : #else
      92                 :             : 
      93                 :             : int
      94                 :           0 : nop_find_debuginfo (Dwfl_Module *mod __attribute__((unused)),
      95                 :             :                     void **userdata __attribute__((unused)),
      96                 :             :                     const char *modname __attribute__((unused)),
      97                 :             :                     GElf_Addr base __attribute__((unused)),
      98                 :             :                     const char *file_name __attribute__((unused)),
      99                 :             :                     const char *debuglink_file __attribute__((unused)),
     100                 :             :                     GElf_Word debuglink_crc __attribute__((unused)),
     101                 :             :                     char **debuginfo_file_name __attribute__((unused)))
     102                 :             : {
     103                 :             : #ifdef DEBUG_MODULES
     104                 :             :   cerr << format("nop_find_debuginfo: modname={} file_name={} debuglink_file={}\n", modname, file_name, debuglink_file);
     105                 :             : #endif
     106                 :           0 :   return -1;
     107                 :             : }
     108                 :             : 
     109                 :             : static const Dwfl_Callbacks dwfl_cfi_callbacks =
     110                 :             : {
     111                 :             :   .find_elf = dwflst_tracker_linux_proc_find_elf,
     112                 :             :   .find_debuginfo = nop_find_debuginfo, /* work with CFI only */
     113                 :             : };
     114                 :             : 
     115                 :             : #endif /* FIND_DEBUGINFO */
     116                 :             : 
     117                 :             : 
     118                 :             : ////////////////////////////////////////////////////////////////////////
     119                 :             : // class decls
     120                 :             : 
     121                 :             : // Unwind statistics for a Dwfl and associated process.
     122                 :           0 : struct UnwindDwflStats {
     123                 :             :   Dwfl *dwfl;
     124                 :             :   string comm;
     125                 :             :   int max_frames; /* for diagnostic purposes */
     126                 :             :   int total_samples; /* for diagnostic purposes */
     127                 :             :   int lost_samples; /* for diagnostic purposes */
     128                 :             :   int shown_errors; /* for diagnostic purposes */
     129                 :             :   Dwfl_Unwound_Source last_unwound; /* track CFI source, for diagnostic purposes */
     130                 :             :   Dwfl_Unwound_Source worst_unwound; /* track CFI source, for diagnostic purposes */
     131                 :             : };
     132                 :             : 
     133                 :             : struct hash_arc {
     134                 :             :   template <class T1, class T2>
     135                 :           0 :   size_t operator()(const pair<T1, T2> &p) const {
     136   [ #  #  #  # ]:           0 :     return hash<T1>()(p.first) ^ hash<T2>()(p.second);
     137                 :             :   }
     138                 :             : };
     139                 :             : 
     140                 :             : // Unwind statistics for a single module identified by build-id.
     141                 :           0 : struct UnwindModuleStats {
     142                 :             :   map<uint64_t, uint32_t> histogram; /* sorted by pc */
     143                 :             :   unordered_map<pair<uint64_t, uint64_t>, uint32_t, hash_arc> callgraph;
     144                 :             : 
     145                 :           0 :   void record_pc(Dwarf_Addr pc) {
     146         [ #  # ]:           0 :     if (histogram.count(pc) == 0)
     147                 :           0 :       histogram[pc]=1;
     148                 :             :     else
     149                 :           0 :       histogram[pc]++;
     150                 :           0 :   }
     151                 :           0 :   void record_callgraph_arc(Dwarf_Addr from, Dwarf_Addr to) {
     152         [ #  # ]:           0 :     pair<uint64_t, uint64_t> arc(from, to);
     153         [ #  # ]:           0 :     if (callgraph.count(arc) == 0)
     154                 :           0 :       callgraph[arc]=1;
     155                 :             :     else
     156                 :           0 :       callgraph[arc]++;
     157                 :           0 :   }
     158                 :             : };
     159                 :             : 
     160                 :             : struct UnwindStatsTable
     161                 :             : {
     162                 :             :   unordered_map<pid_t, UnwindDwflStats> dwfl_tab;
     163                 :             :   unordered_map<string, UnwindModuleStats> buildid_tab;
     164                 :             :   typedef map<string, UnwindModuleStats> buildid_map_t;
     165                 :             : 
     166                 :           8 :   UnwindStatsTable () {}
     167                 :          16 :   ~UnwindStatsTable () {}
     168                 :             : 
     169                 :             :   UnwindDwflStats *pid_find_or_create(pid_t pid);
     170                 :             :   string pid_find_comm(pid_t pid);
     171                 :             :   Dwfl *pid_find_dwfl(pid_t pid);
     172                 :             :   void pid_store_dwfl(pid_t pid, Dwfl *dwfl);
     173                 :             : 
     174                 :             :   UnwindModuleStats *buildid_find(string buildid);
     175                 :             :   UnwindModuleStats *buildid_find_or_create(string buildid, Dwfl_Module *mod);
     176                 :             : 
     177                 :             :   void print_summary() const;
     178                 :             : };
     179                 :             : 
     180                 :             : class PerfConsumer;
     181                 :             : 
     182                 :             : // A PerfReader creates perf_events fds, monitors for events,
     183                 :             : // and dispatches decoded forms to a PerfConsumer.
     184                 :             : class PerfReader
     185                 :             : {
     186                 :             : private:
     187                 :             :   /* Sized by number of CPUs or threads: */
     188                 :             :   vector<int> perf_fds;
     189                 :             :   vector<perf_event_mmap_page *> perf_headers;
     190                 :             :   vector<pollfd> pollfds;
     191                 :             : 
     192                 :             :   PerfConsumer *consumer; // XXX: pluralize
     193                 :             :   Ebl *default_ebl;
     194                 :             :   uint64_t sample_regs_user;
     195                 :             :   int sample_regs_count;
     196                 :             :   bool enabled;
     197                 :             :   int page_size;
     198                 :             :   int page_count;
     199                 :             :   int mmap_size;
     200                 :             :   vector<uint8_t> event_wraparound_temp; // for events straddling ring buffer end
     201                 :             : 
     202                 :             :   void decode_event(const perf_event_header* ehdr);
     203                 :             : 
     204                 :             : public:
     205                 :             :   // PerfReader(perf_event_attr* attr, int pid, PerfConsumer* consumer); // TODO attach to process hierarchy; may modify *attr
     206                 :             :   PerfReader(perf_event_attr* attr, PerfConsumer* consumer, int pid=-1); // TODO systemwide; may modify *attr
     207                 :             :   ~PerfReader();
     208                 :             : 
     209                 :             :   void process_some(); // run briefly, relay decoded perf_events to consumer
     210                 :           0 :   uint64_t regs_mask() { return this->sample_regs_user; }
     211                 :           0 :   Ebl *ebl() { return this->default_ebl; }
     212                 :             : };
     213                 :             : 
     214                 :             : // A PerfConsumer receives both raw and decoded (fields split out into function parameters)
     215                 :             : // perf event records from a PerfReader.  Pure interface.
     216                 :             : class PerfConsumer
     217                 :             : {
     218                 :             : protected:
     219                 :             :   PerfReader *reader; /* access sample_regs_user etc. metadata */
     220                 :             : 
     221                 :             : public:
     222                 :           8 :   PerfConsumer() {}
     223                 :             :   PerfConsumer(PerfReader *reader) : reader(reader) {}
     224                 :           8 :   void set_reader(PerfReader *reader) { this->reader = reader; }
     225                 :             : 
     226                 :           8 :   virtual ~PerfConsumer() {}
     227                 :           0 :   virtual void process(const perf_event_header* sample) {}
     228                 :             : 
     229                 :           0 :   virtual void process_comm(const perf_event_header* sample,
     230                 :           0 :                             uint32_t pid, uint32_t tid, bool exec, const string& comm) {}
     231                 :           0 :   virtual void process_exit(const perf_event_header* sample,
     232                 :             :                             uint32_t pid, uint32_t ppid,
     233                 :           0 :                             uint32_t tid, uint32_t ptid) {}
     234                 :           0 :   virtual void process_fork(const perf_event_header* sample,
     235                 :             :                             uint32_t pid, uint32_t ppid,
     236                 :           0 :                             uint32_t tid, uint32_t ptid) {}
     237                 :           0 :   virtual void process_sample(const perf_event_header* sample,
     238                 :             :                               uint64_t ip,
     239                 :             :                               uint32_t pid, uint32_t tid,
     240                 :             :                               uint64_t time,
     241                 :             :                               uint64_t abi,
     242                 :             :                               uint32_t nregs, const uint64_t *regs,
     243                 :           0 :                               uint64_t data_size, const uint8_t *data) {}
     244                 :           0 :   virtual void process_mmap2(const perf_event_header* sample,
     245                 :             :                              uint32_t pid, uint32_t tid,
     246                 :             :                              uint64_t addr, uint64_t len, uint64_t pgoff,
     247                 :             :                              uint8_t build_id_size, const uint8_t *build_id,
     248                 :           0 :                              const char *filename) {}
     249                 :             : };
     250                 :             : 
     251                 :             : // A StatsPerfConsumer collects basic stats about perf event records.
     252                 :             : class StatsPerfConsumer: public PerfConsumer
     253                 :             : {
     254                 :             :   unordered_map<int,unsigned> event_type_counts;
     255                 :             : 
     256                 :             : public:
     257                 :             :   StatsPerfConsumer() {}
     258                 :             :   ~StatsPerfConsumer(); // report to clog
     259                 :             :   void process_comm(const perf_event_header* sample,
     260                 :             :                     uint32_t pid, uint32_t tid, bool exec, const string& comm);
     261                 :             :   void process_exit(const perf_event_header* sample,
     262                 :             :                     uint32_t pid, uint32_t ppid,
     263                 :             :                     uint32_t tid, uint32_t ptid);
     264                 :             :   void process_fork(const perf_event_header* sample,
     265                 :             :                     uint32_t pid, uint32_t ppid,
     266                 :             :                     uint32_t tid, uint32_t ptid);
     267                 :             :   void process_sample(const perf_event_header* sample,
     268                 :             :                       uint64_t ip,
     269                 :             :                       uint32_t pid, uint32_t tid,
     270                 :             :                       uint64_t time,
     271                 :             :                       uint64_t abi,
     272                 :             :                       uint32_t nregs, const uint64_t *regs,
     273                 :             :                       uint64_t data_size, const uint8_t *data);
     274                 :             :   void process_mmap2(const perf_event_header* sample,
     275                 :             :                      uint32_t pid, uint32_t tid,
     276                 :             :                      uint64_t addr, uint64_t len, uint64_t pgoff,
     277                 :             :                      uint8_t build_id_size, const uint8_t *build_id,
     278                 :             :                      const char *filename);
     279                 :             :   void process(const perf_event_header* sample);
     280                 :             : };
     281                 :             : 
     282                 :             : // An UnwindSample records an unwound call stack from a perf sample.
     283                 :          24 : struct UnwindSample
     284                 :             : {
     285                 :             :   const perf_event_header *event;
     286                 :             :   Dwfl *dwfl;
     287                 :             :   uint32_t pid, tid;
     288                 :             :   vector<Dwarf_Addr> addrs;
     289                 :             :   int elfclass;
     290                 :             : 
     291                 :             :   Dwarf_Addr base; /* for diagnostic purposes */
     292                 :             :   Dwarf_Addr sp; /* for diagnostic purposes */
     293                 :             : };
     294                 :             : 
     295                 :             : class UnwindSampleConsumer;
     296                 :             : 
     297                 :             : // A PerfConsumerUnwinder accepts decoded perf events, and relays
     298                 :             : // UnwindSample objects to an UnwindSampleConsumer.
     299                 :             : class PerfConsumerUnwinder: public PerfConsumer
     300                 :             : {
     301                 :             :   UnwindSampleConsumer *consumer;
     302                 :             :   UnwindSample last_us; // XXX: why & is this safe to hang onto?
     303                 :             :   Dwflst_Process_Tracker *tracker;
     304                 :             :   UnwindStatsTable *stats;
     305                 :             :   unsigned maxframes;
     306                 :             : 
     307                 :             :   int find_procfile(Dwfl *dwfl, pid_t *pid, Elf **elf, int *elf_fd);
     308                 :             :   Dwfl *find_dwfl(pid_t pid, const uint64_t *regs, uint32_t nregs,
     309                 :             :                   Elf **elf, bool *cached);
     310                 :             : 
     311                 :             : public:
     312                 :             :   PerfConsumerUnwinder(UnwindSampleConsumer* usc, UnwindStatsTable *ust);
     313                 :             :   PerfConsumerUnwinder(UnwindSampleConsumer* usc, UnwindStatsTable *ust, PerfReader *reader);
     314                 :             :   ~PerfConsumerUnwinder();
     315                 :             : 
     316                 :             :   /* libdwfl{st} callbacks */
     317                 :             :   Dwfl *init_dwfl(pid_t pid);
     318                 :             :   int unwind_frame_cb(Dwfl_Frame *state);
     319                 :             : 
     320                 :             :   void process_comm(const perf_event_header* sample,
     321                 :             :                     uint32_t pid, uint32_t tid, bool exec, const string& comm);
     322                 :             :   void process_exit(const perf_event_header* sample,
     323                 :             :                     uint32_t pid, uint32_t ppid,
     324                 :             :                     uint32_t tid, uint32_t ptid);
     325                 :             :   void process_fork(const perf_event_header* sample,
     326                 :             :                     uint32_t pid, uint32_t ppid,
     327                 :             :                     uint32_t tid, uint32_t ptid);
     328                 :             :   void process_sample(const perf_event_header* sample,
     329                 :             :                       uint64_t ip,
     330                 :             :                       uint32_t pid, uint32_t tid,
     331                 :             :                       uint64_t time,
     332                 :             :                       uint64_t abi,
     333                 :             :                       uint32_t nregs, const uint64_t *regs,
     334                 :             :                       uint64_t data_size, const uint8_t *data);
     335                 :             :   void process_mmap2(const perf_event_header* sample,
     336                 :             :                      uint32_t pid, uint32_t tid,
     337                 :             :                      uint64_t addr, uint64_t len, uint64_t pgoff,
     338                 :             :                      uint8_t build_id_size, const uint8_t *build_id,
     339                 :             :                      const char *filename);
     340                 :             : };
     341                 :             : 
     342                 :             : // An UnwindSampleConsumer receives an UnwindSample from a PerfConsumerUnwinder.
     343                 :             : // Pure abstract.
     344                 :             : class UnwindSampleConsumer
     345                 :             : {
     346                 :             : public:
     347                 :           8 :   UnwindSampleConsumer() {}
     348                 :           0 :   virtual ~UnwindSampleConsumer() {}
     349                 :             :   virtual void process(const UnwindSample* sample) = 0;
     350                 :             :   virtual int maxframes() = 0;
     351                 :             : };
     352                 :             : 
     353                 :             : // An UnwindStatsConsumer collects basic stats about
     354                 :             : // a received stream of UnwindSamples.
     355                 :             : class UnwindStatsConsumer: public UnwindSampleConsumer
     356                 :             : {
     357                 :             :   UnwindStatsTable *stats;
     358                 :             : 
     359                 :             : public:
     360                 :           8 :   UnwindStatsConsumer(UnwindStatsTable *usc) : stats(usc) {}
     361                 :             :   ~UnwindStatsConsumer();
     362                 :             :   void process(const UnwindSample* sample);
     363                 :             :   int maxframes();
     364                 :             : };
     365                 :             : 
     366                 :             : // A GprofUnwindSampleConsumer instance consumes UnwindSamples and tabulates
     367                 :             : // them by buildid, for eventual writing out into gmon.out format files.
     368                 :             : class GprofUnwindSampleConsumer: public UnwindSampleConsumer
     369                 :             : {
     370                 :             :   UnwindStatsTable *stats;
     371                 :             :   unordered_map<string, string> buildid_to_mainfile;
     372                 :             :   unordered_map<string, string> buildid_to_debugfile;
     373                 :             :   void record_gmon_hist(ostream &of, map<uint64_t, uint32_t> &histogram, uint64_t low_pc, uint64_t high_pc, uint64_t alignment);
     374                 :             : 
     375                 :             : public:
     376                 :           0 :   GprofUnwindSampleConsumer(UnwindStatsTable *usc) : stats(usc) {}
     377                 :             :   ~GprofUnwindSampleConsumer(); // writes out all the gmon.$BUILDID.out files
     378                 :             :   void record_gmon_out(const string& buildid, UnwindModuleStats& m); // write out one gmon.$BUILDID.out file
     379                 :             :   void process(const UnwindSample* sample); // accumulate hits / callgraph edges (need maxdepth=1 only)
     380                 :             :   int maxframes();
     381                 :             : };
     382                 :             : 
     383                 :             : // hypothetical: FlamegraphUnwindSampleConsumer, taking in a bigger maxdepth
     384                 :             : // hypothetical: PprofUnwindSampleConsumer, https://github.com/google/pprof
     385                 :             : 
     386                 :             : 
     387                 :             : ////////////////////////////////////////////////////////////////////////
     388                 :             : // command line parsing and main()
     389                 :             : 
     390                 :             : /* Name, version, and bug report address.  */
     391                 :             : ARGP_PROGRAM_VERSION_HOOK_DEF = print_version;
     392                 :             : ARGP_PROGRAM_BUG_ADDRESS_DEF = PACKAGE_BUGREPORT;
     393                 :             : 
     394                 :             : #define HIST_SPLIT_OPTS "none/even/flex"
     395                 :             : 
     396                 :             : static const struct argp_option options[] =
     397                 :             : {
     398                 :             :   { NULL, 0, NULL, OPTION_DOC, N_("Output options:"), 1 },
     399                 :             :   { "verbose", 'v', NULL, 0, N_("Increase verbosity of logging messages (modules/samples/frames/more)."), 0 },
     400                 :             :   /* TODO: Add "quiet" option suppressing summary table. */
     401                 :             :   { "gmon", 'g', NULL, 0, N_("Generate gmon.BUILDID.out files for each binary."), 0 },
     402                 :             :   { "hist-split", 'G', HIST_SPLIT_OPTS, 0, N_("Split gmon histogram output into even or flexible chunks, default 'even'."), 0 },
     403                 :             :   { "maxframes", 'n', "MAXFRAMES", 0, N_("Maximum number of frames to unwind, default 1 with --gmon, 256 otherwise."), 0 },
     404                 :             :   { "output", 'o', "DIR", 0, N_("Output directory for gmon.BUILDID.out files."), 0 },
     405                 :             :   { "force", 'f', NULL, 0, N_("Unlink output files to force writing as new."), 0 },
     406                 :             :   { "pid", 'p', "PID", 0, N_("Profile given PID, and its future children."), 0 },
     407                 :             : #ifdef HAVE_PERFMON_PFMLIB_PERF_EVENT_H
     408                 :             :   { "event", 'e', "EVENT", 0, N_("Sample given LIBPFM event specification."), 0 },
     409                 :             : #define ARGP_KEY_EVENT_LIST 0x1000
     410                 :             :   { "event-list", ARGP_KEY_EVENT_LIST, NULL, 0, N_("Sample given LIBPFM event specification."), 0 },
     411                 :             : #endif
     412                 :             :   { NULL, 0, NULL, 0, NULL, 0 }
     413                 :             : };
     414                 :             : 
     415                 :             : static error_t parse_opt (int key, char *arg, struct argp_state *state);
     416                 :             : static const struct argp argp =
     417                 :             :   {
     418                 :             :     options, parse_opt, "[--] [CMD]...", N_("Collect systemwide stack-trace profiles."),
     419                 :             :     NULL, NULL, NULL
     420                 :             :   };
     421                 :             : 
     422                 :             : // How to divide the program counter histograms in gmon output:
     423                 :             : enum hist_split_method {
     424                 :             :   HIST_SPLIT_NONE = 0, /* one histogram for the entire executable */
     425                 :             :   HIST_SPLIT_EVEN = 1, /* all histograms the same size */
     426                 :             :   HIST_SPLIT_FLEX = 2, /* variable-size histograms */
     427                 :             : };
     428                 :             : 
     429                 :             : // Globals for command line options:
     430                 :             : static unsigned verbose;
     431                 :             : static bool gmon;
     432                 :             : static hist_split_method gmon_hist_split = HIST_SPLIT_EVEN;
     433                 :             : static string output_dir = ".";
     434                 :             : static bool output_force = false; // overwrite preexisting output files?
     435                 :             : static int pid;
     436                 :             : static int opt_maxframes = -1; // set to >= 0 to override default maxframes in consumer
     437                 :             : static string libpfm_event;
     438                 :             : static string libpfm_event_decoded;
     439                 :             : static perf_event_attr attr;
     440                 :             : static bool branch_record = false; // use accurate branch recording for call-graph arcs rather than backtrace heuristics
     441                 :             : 
     442                 :             : // Verbosity categories:
     443                 :             : static bool show_summary = true; /* XXX could suppress with --quiet */
     444                 :             : static bool show_modules = false; /* -> first sample for each module */
     445                 :             : static bool show_samples = false; /* -> every sample */
     446                 :             : static bool show_frames = false;
     447                 :             : static bool show_debugfile = false;
     448                 :             : static bool show_tmi = false; /* -> perf, cfi details */
     449                 :             : 
     450                 :             : static error_t
     451                 :          96 : parse_opt (int key, char *arg, struct argp_state *state)
     452                 :             : {
     453   [ +  +  -  -  :          96 :   switch (key)
             -  -  -  -  
                      + ]
     454                 :             :     {
     455                 :             :     case ARGP_KEY_INIT:
     456                 :             :       break;
     457                 :             : 
     458                 :          16 :     case 'v':
     459                 :          16 :       verbose ++;
     460                 :          16 :       break;
     461                 :             : 
     462                 :           0 :     case 'g':
     463                 :           0 :       gmon = true;
     464                 :           0 :       break;
     465                 :             : 
     466                 :           0 :     case 'G':
     467                 :           0 :       gmon = true; // Automatically enable gmon mode since a gmon-related option was set.
     468         [ #  # ]:           0 :       if (std::string_view(arg) == "none")
     469                 :           0 :         gmon_hist_split = HIST_SPLIT_NONE;
     470         [ #  # ]:           0 :       else if (std::string_view(arg) == "even")
     471                 :           0 :         gmon_hist_split = HIST_SPLIT_EVEN;
     472         [ #  # ]:           0 :       else if (std::string_view(arg) == "flex")
     473                 :           0 :         gmon_hist_split = HIST_SPLIT_FLEX;
     474                 :             :       break;
     475                 :             : 
     476                 :           0 :     case 'o':
     477                 :           0 :       gmon = true;
     478                 :           0 :       output_dir = arg;
     479                 :           0 :       break;
     480                 :             : 
     481                 :           0 :     case 'p':
     482                 :           0 :       pid = atoi(arg);
     483         [ #  # ]:           0 :       if (pid == 0)
     484                 :           0 :         argp_error (state, N_("-p PID should be a positive process id."));
     485                 :             :       break;
     486                 :             : 
     487                 :           0 :     case 'n':
     488                 :           0 :       opt_maxframes = atoi(arg);
     489         [ #  # ]:           0 :       if (opt_maxframes < 0)
     490                 :             :         {
     491                 :           0 :           argp_error (state, N_("-n MAXFRAMES should be 0 or higher."));
     492                 :           0 :           return EINVAL;
     493                 :             :         }
     494                 :             :       break;
     495                 :             : 
     496                 :           0 :     case 'f':
     497                 :           0 :       output_force = true;
     498                 :           0 :       break;
     499                 :             : 
     500                 :             : #ifdef HAVE_PERFMON_PFMLIB_PERF_EVENT_H
     501                 :             :     case 'e':
     502                 :             :       libpfm_event = arg;
     503                 :             :       break;
     504                 :             : 
     505                 :             :     case ARGP_KEY_EVENT_LIST:
     506                 :             :       {
     507                 :             :         pfm_pmu_info_t pinfo;
     508                 :             :         pfm_event_info_t info;
     509                 :             : 
     510                 :             :         pfm_err_t rc = pfm_initialize();
     511                 :             :         if (rc != PFM_SUCCESS)
     512                 :             :           {
     513                 :             :             cerr << format("ERROR: pfm_initialize failed: {}\n", pfm_strerror(rc));
     514                 :             :             exit(1);
     515                 :             :           }
     516                 :             : 
     517                 :             :         memset(&pinfo, 0, sizeof(pinfo));
     518                 :             :         memset(&info, 0, sizeof(info));
     519                 :             :         pinfo.size = sizeof(pinfo);
     520                 :             :         info.size = sizeof(info);
     521                 :             : 
     522                 :             :         for (int j = PFM_PMU_NONE; j < PFM_PMU_MAX; j++)
     523                 :             :           {
     524                 :             :             pfm_err_t ret = pfm_get_pmu_info((pfm_pmu_t)j, &pinfo);
     525                 :             :             if (ret != PFM_SUCCESS)
     526                 :             :               continue;
     527                 :             :             if (! pinfo.is_present)
     528                 :             :               continue;
     529                 :             :             for (int i = pinfo.first_event; i != -1; i = pfm_get_event_next(i))
     530                 :             :               {
     531                 :             :                 ret = pfm_get_event_info(i, PFM_OS_PERF_EVENT_EXT, &info);
     532                 :             :                 if (ret == PFM_SUCCESS)
     533                 :             :                   clog << format("{}::{}\n", pinfo.name, info.name);
     534                 :             :               }
     535                 :             :           }
     536                 :             :       }
     537                 :             :       exit(0);
     538                 :             : #endif
     539                 :             : 
     540                 :             :     default:
     541                 :             :       return ARGP_ERR_UNKNOWN;
     542                 :             :     }
     543                 :             :   return 0;
     544                 :             : }
     545                 :             : 
     546                 :             : sig_atomic_t interrupted;
     547                 :             : 
     548                 :           0 : void sigint_handler (int sig)
     549                 :             : {
     550                 :           0 :   interrupted ++;
     551         [ #  # ]:           0 :   if (interrupted > 1)
     552                 :           0 :     _exit(1);
     553                 :           0 : }
     554                 :             : 
     555                 :          16 : int main (int argc, char *argv[])
     556                 :             : {
     557                 :          16 :   int remaining;
     558                 :          16 :   int pipefd[2] = {-1, -1}; // for post-fork sync with CMD child process
     559                 :          16 :   bool has_cmd = false;
     560                 :          16 :   (void) argp_parse (&argp, argc, argv, 0, &remaining, NULL);
     561                 :             : 
     562                 :             :   /* show_summary is true by default */
     563         [ +  - ]:          16 :   if (verbose > 0) show_modules = true;
     564         [ -  + ]:          16 :   if (verbose > 1) show_samples = true;
     565         [ -  + ]:          16 :   if (verbose > 2) show_frames = true;
     566         [ -  + ]:          16 :   if (verbose > 3) show_debugfile = true;
     567         [ -  + ]:          16 :   if (verbose > 4) show_tmi = true;
     568                 :             : 
     569   [ -  +  -  - ]:          16 :   if (pid > 0 && remaining < argc) // got a pid AND a cmd? reject
     570                 :             :     {
     571                 :           0 :       cerr << format("ERROR: Must not specify both -p PID and CMD\n");
     572                 :           0 :       exit(1);
     573                 :             :     }
     574                 :             : 
     575                 :          16 :   bool systemwide = (pid == 0) || (remaining == argc);
     576                 :          16 :   (void) systemwide;
     577                 :             : 
     578                 :          16 :   PerfReader *pr = nullptr;
     579                 :          16 :   UnwindStatsTable *tab = nullptr;
     580                 :          16 :   UnwindSampleConsumer *usc = nullptr;
     581                 :          16 :   PerfConsumerUnwinder *pcu = nullptr;
     582                 :          16 :   StatsPerfConsumer *spc = nullptr;
     583                 :             : 
     584                 :          16 :   try
     585                 :             :     {
     586         [ +  - ]:          16 :       memset(&attr, 0, sizeof(attr));
     587                 :          16 :       attr.size = sizeof(attr);
     588                 :             : 
     589         [ +  - ]:          16 :       if (libpfm_event != "")
     590                 :             :         {
     591                 :             : #if HAVE_PERFMON_PFMLIB_PERF_EVENT_H
     592                 :             :           pfm_err_t rc = pfm_initialize();
     593                 :             :           if (rc != PFM_SUCCESS)
     594                 :             :             {
     595                 :             :               cerr << format("ERROR: pfm_initialize failed: {}\n", pfm_strerror(rc));
     596                 :             :               exit(1);
     597                 :             :             }
     598                 :             :           char *fstr = nullptr;
     599                 :             :           pfm_perf_encode_arg_t arg = { .attr = &attr, .fstr = &fstr, .size = sizeof(arg) };
     600                 :             :           rc = pfm_get_os_event_encoding(libpfm_event.c_str(),
     601                 :             :                                          PFM_PLM3, /* userspace, whether systemwide or not */
     602                 :             :                                          PFM_OS_PERF_EVENT_EXT, &arg);
     603                 :             :           if (rc != PFM_SUCCESS)
     604                 :             :             {
     605                 :             :               cerr << format("ERROR: pfm_get_os_event_encoding failed: {}\n", pfm_strerror(rc));
     606                 :             :               exit(1);
     607                 :             :             }
     608                 :             :           if (verbose)
     609                 :             :             {
     610                 :             :               clog << format("libpfm expanded {} to {}\n", libpfm_event, fstr);
     611                 :             :             }
     612                 :             :           libpfm_event_decoded = fstr; // overwrite
     613                 :             :           free(fstr);
     614                 :             : #endif
     615                 :             :         }
     616                 :             :       else
     617                 :             :         {
     618                 :             :           // same as: -e perf::CPU-CLOCK:freq=1000
     619                 :          16 :           attr.type = PERF_TYPE_SOFTWARE;
     620                 :          16 :           attr.config = PERF_COUNT_SW_CPU_CLOCK;
     621                 :          16 :           attr.sample_freq = 1000;
     622                 :          16 :           attr.freq = 1;
     623                 :          16 :           attr.exclude_kernel = 1;
     624                 :          16 :           attr.exclude_hv = 1;
     625                 :          16 :           attr.exclude_guest = 1;
     626                 :             :         }
     627                 :             : 
     628         [ +  - ]:          16 :       if (show_summary)
     629                 :             :         {
     630                 :           0 :           clog << format("perf_event_attr configuration type={:x} config={:x} {}{}\n",
     631                 :             :                          attr.type, attr.config,
     632   [ -  +  +  - ]:          16 :                          (attr.freq ? "sample_freq=" : "sample_period="),
     633                 :          16 :                          (attr.freq ? attr.sample_freq : attr.sample_period));
     634         [ +  - ]:          16 :           clog << endl;
     635                 :             :         }
     636                 :             : 
     637         [ +  - ]:          16 :       if (remaining < argc) // got a CMD... suffix?  ok start it
     638                 :             :         {
     639                 :          16 :           has_cmd = true;
     640                 :          16 :           int rc = pipe(pipefd);
     641         [ -  + ]:          16 :           if (rc < 0)
     642                 :             :             {
     643         [ #  # ]:           0 :               cerr << format("ERROR: pipe failed: {}\n", strerror(errno));
     644                 :           0 :               exit(1);
     645                 :             :             }
     646                 :             : 
     647                 :          16 :           pid = fork();
     648         [ +  + ]:          16 :           if (pid == 0) // in child
     649                 :             :             {
     650         [ +  - ]:           8 :               close(pipefd[1]); // close write end
     651                 :           8 :               char dummy;
     652         [ +  - ]:           8 :               int rc = read(pipefd[0], &dummy, 1); // block until parent is ready
     653         [ +  - ]:           8 :               if (rc != 1)
     654                 :             :                 {
     655         [ +  - ]:          16 :                   cerr << format("ERROR: child sync read failed: {}\n", strerror(errno));
     656                 :           8 :                   exit(1);
     657                 :             :                 }
     658         [ #  # ]:           0 :               close(pipefd[0]);
     659                 :           0 :               execvp(argv[remaining], &argv[remaining] /* including child argv[0] */);
     660                 :             :               // fallthrough
     661         [ #  # ]:           0 :               cerr << format("ERROR: execvp failed: {}\n", strerror(errno));
     662                 :           0 :               exit(1);
     663                 :             :             }
     664         [ +  - ]:           8 :           else if (pid > 0) // in parent
     665                 :             :             {
     666         [ +  - ]:           8 :               close(pipefd[0]); // close read end
     667                 :             :               // will write to pipefd[1] after perfreader sicced at child
     668                 :             :             }
     669                 :             :           else // error
     670                 :             :             {
     671         [ #  # ]:           0 :               cerr << format("ERROR: fork failed: {}\n", strerror(errno));
     672                 :           0 :               exit(1);
     673                 :             :             }
     674                 :             :         }
     675                 :             : 
     676                 :             :       // Create the perf processing pipeline:
     677         [ -  + ]:           8 :       if (gmon)
     678                 :             :         {
     679   [ #  #  #  # ]:           0 :           tab = new UnwindStatsTable();
     680   [ #  #  #  # ]:           0 :           usc = new GprofUnwindSampleConsumer(tab);
     681   [ #  #  #  # ]:           0 :           pcu = new PerfConsumerUnwinder(usc, tab);
     682   [ #  #  #  # ]:           0 :           pr = new PerfReader(&attr, pcu, pid);
     683                 :             :         }
     684                 :             :       else
     685                 :             :         {
     686                 :             : #ifndef STACKPROF_STATS_DEBUG
     687   [ +  -  +  - ]:           8 :           tab = new UnwindStatsTable();
     688         [ +  - ]:           8 :           usc = new UnwindStatsConsumer(tab);
     689   [ +  -  +  - ]:           8 :           pcu = new PerfConsumerUnwinder(usc, tab);
     690   [ +  -  -  + ]:           8 :           pr = new PerfReader(&attr, pcu, pid);
     691                 :             : #else
     692                 :             :           /* early debug mode */
     693                 :             :           spc = new StatsPerfConsumer();
     694                 :             :           pr = new PerfReader(&attr, spc, pid);
     695                 :             : #endif
     696                 :             :         }
     697                 :             : 
     698                 :           0 :       signal(SIGINT, sigint_handler);
     699                 :           0 :       signal(SIGTERM, sigint_handler);
     700                 :             : 
     701   [ #  #  #  # ]:           0 :       if (pid > 0 && has_cmd) // need to release child CMD process?
     702                 :             :         {
     703         [ #  # ]:           0 :           int rc = write(pipefd[1], "x", 1); // unblock child
     704         [ #  # ]:           0 :           assert (rc == 1); // XXX
     705         [ #  # ]:           0 :           close(pipefd[1]);
     706                 :             :         }
     707                 :             : 
     708         [ #  # ]:           0 :       if (show_summary) // TODO: move before child CMD release
     709                 :             :         {
     710         [ #  # ]:           0 :           clog << "Starting stack profile collection ";
     711   [ #  #  #  # ]:           0 :           if (pid) clog << format("pid {}", pid);
     712         [ #  # ]:           0 :           else clog << "systemwide";
     713         [ #  # ]:           0 :           clog << "\n"; // TODO: replace with endl throughout?
     714                 :             :         }
     715                 :             : 
     716                 :           0 :       while (true) // main loop
     717                 :             :         {
     718         [ #  # ]:           0 :           if (interrupted) break;
     719   [ #  #  #  # ]:           0 :           if (pid > 0) waitpid(pid, NULL, WNOHANG); // reap dead child to allow kill(pid, 0) to signal death
     720   [ #  #  #  # ]:           0 :           if (pid > 0 && kill(pid, 0) != 0) break; // exit if child or targeted non-child process died
     721         [ #  # ]:           0 :           pr->process_some();
     722                 :             :         }
     723                 :             : 
     724                 :           0 :       delete pr;
     725                 :           0 :       delete usc;
     726                 :           0 :       delete pcu;
     727                 :           0 :       delete spc;
     728                 :           0 :       delete tab;
     729                 :             :       // reporting done in various destructors
     730                 :             :     }
     731         [ -  + ]:           8 :   catch (const exception& e)
     732                 :             :     {
     733         [ +  - ]:          16 :       cerr << format("{}\n", e.what());
     734                 :             :       // XXX for now, call only the destructors that do not print anything:
     735         [ -  + ]:           8 :       delete pr;
     736         [ +  - ]:           8 :       delete pcu;
     737         [ +  - ]:           8 :       delete tab;
     738                 :           8 :       return 1;
     739                 :           8 :     }
     740                 :             : 
     741                 :           0 :   return 0;
     742                 :             : }
     743                 :             : 
     744                 :             : 
     745                 :             : ////////////////////////////////////////////////////////////////////////
     746                 :             : // perf reader
     747                 :             : 
     748         [ +  - ]:           8 : PerfReader::PerfReader(perf_event_attr* attr, PerfConsumer* consumer, int pid)
     749                 :             : {
     750                 :           8 :   this->page_size = getpagesize();
     751                 :           8 :   this->page_count = 64; /* XXX May want to verify if this is a large-enough power-of-2.  */
     752                 :           8 :   this->mmap_size = this->page_size * (this->page_count + 1); // total mmap size, including header page
     753         [ +  - ]:           8 :   this->event_wraparound_temp.resize(this->mmap_size); // NB: never resize again!
     754                 :           8 :   this->consumer = consumer;
     755                 :           8 :   this->consumer->set_reader(this);
     756                 :           8 :   this->enabled = false;
     757                 :             : 
     758                 :           8 :   struct utsname u;
     759                 :           8 :   uname(&u);
     760                 :             :   /* XXX Possibly could be a libdwfl api, but can't be libebl since it
     761                 :             :      must be accessible by external tools.  */
     762         [ +  - ]:           8 :   int em = dwflst_arch_from_uname(u.machine);
     763         [ -  + ]:           8 :   if (em == EM_NONE) {
     764         [ #  # ]:           0 :     cerr << format("ERROR: Unsupported architecture: {}\n", u.machine);
     765                 :           0 :     exit(1);
     766                 :             :   }
     767         [ +  - ]:           8 :   this->default_ebl = ebl_openbackend_machine(em);
     768         [ +  - ]:           8 :   this->sample_regs_user = ebl_perf_frame_regs_mask (this->default_ebl);
     769         [ +  - ]:           8 :   this->sample_regs_count = bitset<64>(this->sample_regs_user).count();
     770                 :             : 
     771                 :           8 :   attr->sample_regs_user = this->sample_regs_user;
     772                 :           8 :   attr->sample_stack_user = 8192; // enough?
     773                 :           8 :   attr->sample_type = (PERF_SAMPLE_IP | PERF_SAMPLE_TID | PERF_SAMPLE_TIME);
     774                 :           8 :   attr->sample_type |= PERF_SAMPLE_REGS_USER;
     775                 :           8 :   attr->sample_type |= PERF_SAMPLE_STACK_USER;
     776                 :             :   // XXX Maybe: ask for PERF_SAMPLE_CALLCHAIN, in case kernel can
     777                 :             :   // unwind for us?  Would want an option to control this, to allow
     778                 :             :   // eu-stackprof to exercise our own unwinding functionality when
     779                 :             :   // testing.
     780                 :           8 :   attr->mmap = 1;
     781                 :           8 :   attr->mmap2 = 1;
     782                 :           8 :   attr->exclude_kernel = 1; /* in-kernel unwinding not relevant for our usecase */
     783                 :           8 :   attr->disabled = 1; /* will get enabled soon */
     784                 :           8 :   attr->task = 1; // catch FORK/EXIT
     785                 :           8 :   attr->comm = 1; // catch EXEC
     786                 :           8 :   attr->comm_exec = 1; // catch EXEC
     787                 :             :   // attr->precise_ip = 2; // request 0 skid ... but that conflicts with PERF_COUNT_HW_BRANCH_INSTRUCTIONS:freq=4000
     788                 :           8 :   attr->build_id = 1; // request build ids in MMAP2 events
     789                 :             : 
     790         [ +  - ]:           8 :   if (pid > 0) // actually only once, to allow break in case of error
     791                 :           8 :     attr->inherit = 1; // propagate to child processes
     792                 :             : 
     793         [ -  + ]:           8 :   if (show_tmi)
     794                 :             :     {
     795         [ #  # ]:           0 :       clog << "perf_event_attr hexdump: ";
     796                 :             :       auto bytes = (unsigned char *)attr;
     797         [ #  # ]:           0 :       for (size_t x = 0; x < sizeof(*attr); x++)
     798                 :           0 :         clog << ((x % 8) ? "" : " ")
     799                 :           0 :              << ((x % 32) ? "" : "\n")
     800   [ #  #  #  #  :           0 :              << format("{:02x}", (unsigned)bytes[x]);
          #  #  #  #  #  
                      # ]
     801         [ #  # ]:           0 :       clog << "\n";
     802                 :             :     }
     803                 :             : 
     804                 :             :   // Iterate over all cpus to handle possible concurrency, even if
     805                 :             :   // attaching to a single pid, because we set ->inherit=1.
     806                 :           8 :   int ncpus = sysconf(_SC_NPROCESSORS_CONF);
     807         [ +  + ]:          40 :   for (int cpu = 0; cpu < ncpus; cpu++)
     808                 :             :     {
     809         [ -  + ]:          32 :       int fd = syscall(__NR_perf_event_open, attr,
     810                 :             :                        (pid > 0 ? pid : -1), cpu, -1,
     811                 :          32 :                        PERF_FLAG_FD_CLOEXEC);
     812         [ +  - ]:          32 :       if (fd < 0)
     813                 :             :         {
     814         [ +  - ]:          64 :           cerr << format("WARNING: unable to open perf fd for cpu {}: {}\n", cpu, strerror(errno));
     815                 :          32 :           continue;
     816                 :             :         }
     817                 :           0 :       void *buf = mmap(NULL, this->mmap_size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
     818         [ #  # ]:           0 :       if (buf == MAP_FAILED)
     819                 :             :         {
     820         [ #  # ]:           0 :           cerr << format("ERROR: perf event mmap failed: {}\n", strerror(errno));
     821         [ #  # ]:           0 :           close(fd);
     822                 :           0 :           continue;
     823                 :             :         }
     824         [ #  # ]:           0 :       this->perf_fds.push_back(fd);
     825         [ #  # ]:           0 :       this->perf_headers.push_back((perf_event_mmap_page *)buf);
     826                 :           0 :       struct pollfd pfd = { .fd = fd, .events = POLLIN };
     827         [ #  # ]:           0 :       this->pollfds.push_back(pfd);
     828                 :             :     }
     829                 :             : 
     830         [ +  - ]:           8 :   if (this->perf_fds.size() == 0)
     831         [ +  - ]:           8 :     throw runtime_error("ERROR: no perf fds opened");
     832                 :           8 : }
     833                 :             : 
     834                 :           0 : PerfReader::~PerfReader()
     835                 :             : {
     836         [ #  # ]:           0 :   for (auto fd : this->perf_fds)
     837                 :           0 :     close(fd);
     838         [ #  # ]:           0 :   for (auto m : this->perf_headers)
     839                 :           0 :     munmap((void *)m, this->mmap_size);
     840                 :           0 :   ebl_closebackend (this->default_ebl);
     841                 :           0 : }
     842                 :             : 
     843                 :           0 : uint64_t millis_monotonic()
     844                 :             : {
     845         [ #  # ]:           0 :   return chrono::duration_cast<chrono::milliseconds>(chrono::steady_clock::now().time_since_epoch()).count();
     846                 :             : }
     847                 :             : 
     848                 :             : static inline uint64_t
     849                 :           0 : ring_buffer_read_head(volatile struct perf_event_mmap_page *base)
     850                 :             : {
     851                 :           0 :   uint64_t head = base->data_head;
     852                 :           0 :   asm volatile("" ::: "memory"); // memory fence
     853                 :           0 :   return head;
     854                 :             : }
     855                 :             : 
     856                 :             : static inline void
     857                 :           0 : ring_buffer_write_tail(volatile struct perf_event_mmap_page *base,
     858                 :             :                        uint64_t tail)
     859                 :             : {
     860                 :           0 :   asm volatile("" ::: "memory"); // memory fence
     861                 :           0 :   base->data_tail = tail;
     862                 :             : }
     863                 :             : 
     864                 :             : // TODO: diagnostics to see how well the buffer processing keeps from overflow
     865                 :           0 : void PerfReader::process_some()
     866                 :             : {
     867         [ #  # ]:           0 :   if (!this->enabled)
     868                 :             :     {
     869         [ #  # ]:           0 :       for (auto fd : this->perf_fds)
     870                 :           0 :         ioctl(fd, PERF_EVENT_IOC_ENABLE, 0 /* value ignored */);
     871                 :           0 :       this->enabled = true;
     872                 :             :     }
     873                 :             : 
     874                 :           0 :   uint64_t starttime = millis_monotonic();
     875                 :           0 :   uint64_t endtime = starttime + 1000; // run at most for one second
     876                 :           0 :   uint64_t ring_buffer_size = this->page_size * this->page_count; // just the ring buffer size
     877                 :             : 
     878         [ #  # ]:           0 :   while (!interrupted)
     879                 :             :     {
     880                 :           0 :       uint64_t now = millis_monotonic();
     881         [ #  # ]:           0 :       if (endtime < now)
     882                 :             :         break;
     883         [ #  # ]:           0 :       int ready = poll(this->pollfds.data(), this->pollfds.size(), (int)(endtime-now)); // wait a little while
     884         [ #  # ]:           0 :       if (ready < 0)
     885                 :             :         break;
     886                 :             : 
     887         [ #  # ]:           0 :       for (size_t i = 0; i < pollfds.size(); i++)
     888         [ #  # ]:           0 :         if (this->pollfds[i].revents & POLLIN) // found an fd with fresh yummy events
     889                 :             :           {
     890                 :           0 :             perf_event_mmap_page *header = perf_headers[i];
     891                 :           0 :             uint64_t data_head = ring_buffer_read_head(header);
     892                 :           0 :             uint64_t data_tail = header->data_tail;
     893                 :           0 :             uint8_t *base = ((uint8_t *)header) + this->page_size;
     894                 :           0 :             struct perf_event_header *ehdr;
     895                 :           0 :             size_t ehdr_size;
     896                 :             : 
     897         [ #  # ]:           0 :             while (data_head != data_tail) // consume all packets in ring buffer XXX why?
     898                 :             :               {
     899                 :           0 :                 ehdr = (perf_event_header *) (base + (data_tail & (ring_buffer_size - 1)));
     900                 :           0 :                 ehdr_size = ehdr->size;
     901         [ #  # ]:           0 :                 if (show_tmi)
     902                 :           0 :                   clog << format("perf head={:p} tail={:p} ehdr={:p} size={:d}{:x}\n",
     903                 :           0 :                                  (void *)data_head, (void *)data_tail, (void *)ehdr, ehdr_size, 0);
     904                 :             : 
     905         [ #  # ]:           0 :                 if (((uint8_t *)ehdr) + ehdr_size > base + ring_buffer_size) // mmap region wraparound?
     906                 :             :                   {
     907                 :             :                     // need to copy it to a contiguous temporary
     908                 :           0 :                     uint8_t *copy_start = (uint8_t *)ehdr;
     909                 :           0 :                     size_t len_first = base + ring_buffer_size - copy_start;
     910                 :           0 :                     size_t len_secnd = ehdr_size - len_first;
     911                 :           0 :                     uint8_t *event_temp = this->event_wraparound_temp.data();
     912                 :           0 :                     memcpy(event_temp, copy_start, len_first);       // part at end of mmap'd region
     913                 :           0 :                     memcpy(event_temp + len_first, base, len_secnd); // part at beginning of mmap'd region
     914                 :           0 :                     ehdr = (perf_event_header *)event_temp;
     915                 :             :                   }
     916                 :             : 
     917                 :           0 :                 this->decode_event(ehdr);
     918                 :           0 :                 data_tail += ehdr_size;
     919                 :             :               }
     920                 :             : 
     921                 :           0 :             ring_buffer_write_tail(header, data_tail);
     922                 :             :           }
     923                 :             :     }
     924                 :           0 : }
     925                 :             : 
     926                 :           0 : void PerfReader::decode_event(const perf_event_header* ehdr)
     927                 :             : {
     928                 :           0 :   consumer->process(ehdr); // allow general processing
     929                 :             : 
     930                 :             :   // ... and decode into individual event types
     931   [ #  #  #  #  :           0 :   switch (ehdr->type)
                   #  # ]
     932                 :             :     {
     933                 :           0 :     case PERF_RECORD_SAMPLE:
     934                 :           0 :       {
     935                 :           0 :         const uint8_t *data = reinterpret_cast<const uint8_t *>(ehdr) + sizeof(perf_event_header);
     936                 :           0 :         uint64_t ip = *reinterpret_cast<const uint64_t *>(data); data += sizeof(uint64_t);
     937                 :           0 :         uint32_t pid = *reinterpret_cast<const uint32_t *>(data); data += sizeof(uint32_t);
     938                 :           0 :         uint32_t tid = *reinterpret_cast<const uint32_t *>(data); data += sizeof(uint32_t);
     939                 :           0 :         uint64_t time = *reinterpret_cast<const uint64_t *>(data); data += sizeof(uint64_t);
     940                 :             :         // PERF_SAMPLE_CALLCHAIN would be here if requested
     941                 :           0 :         uint64_t abi = *reinterpret_cast<const uint64_t *>(data); data += sizeof(uint64_t);
     942                 :           0 :         uint32_t nregs = this->sample_regs_count;
     943                 :           0 :         const uint64_t *regs = reinterpret_cast<const uint64_t *>(data); data += nregs * sizeof(uint64_t);
     944                 :           0 :         uint64_t data_size = *reinterpret_cast<const uint64_t *>(data); data += sizeof(uint64_t);
     945                 :           0 :         const uint8_t *stack_data = data;
     946                 :           0 :         consumer->process_sample(ehdr, ip, pid, tid, time, abi, nregs, regs, data_size, stack_data);
     947                 :           0 :         break;
     948                 :             :       }
     949                 :           0 :     case PERF_RECORD_COMM:
     950                 :           0 :       {
     951                 :           0 :         const uint8_t *data = reinterpret_cast<const uint8_t *>(ehdr) + sizeof(perf_event_header);
     952                 :           0 :         uint32_t pid = *reinterpret_cast<const uint32_t *>(data); data += sizeof(uint32_t);
     953                 :           0 :         uint32_t tid = *reinterpret_cast<const uint32_t *>(data); data += sizeof(uint32_t);
     954                 :           0 :         const char *comm = reinterpret_cast<const char *>(data);
     955         [ #  # ]:           0 :         consumer->process_comm(ehdr, pid, tid, (ehdr->misc & PERF_RECORD_MISC_COMM_EXEC)/* XXX why? */, comm);
     956                 :           0 :         break;
     957                 :             :       }
     958                 :           0 :     case PERF_RECORD_EXIT:
     959                 :           0 :       {
     960                 :           0 :         const uint8_t *data = reinterpret_cast<const uint8_t *>(ehdr) + sizeof(perf_event_header);
     961                 :           0 :         uint32_t pid = *reinterpret_cast<const uint32_t *>(data); data += sizeof(uint32_t);
     962                 :           0 :         uint32_t ppid = *reinterpret_cast<const uint32_t *>(data); data += sizeof(uint32_t);
     963                 :           0 :         uint32_t tid = *reinterpret_cast<const uint32_t *>(data); data += sizeof(uint32_t);
     964                 :           0 :         uint32_t ptid = *reinterpret_cast<const uint32_t *>(data);
     965                 :           0 :         consumer->process_exit(ehdr, pid, ppid, tid, ptid);
     966                 :           0 :         break;
     967                 :             :       }
     968                 :           0 :     case PERF_RECORD_FORK:
     969                 :           0 :       {
     970                 :           0 :         const uint8_t *data = reinterpret_cast<const uint8_t *>(ehdr) + sizeof(perf_event_header);
     971                 :           0 :         uint32_t pid = *reinterpret_cast<const uint32_t *>(data); data += sizeof(uint32_t);
     972                 :           0 :         uint32_t ppid = *reinterpret_cast<const uint32_t *>(data); data += sizeof(uint32_t);
     973                 :           0 :         uint32_t tid = *reinterpret_cast<const uint32_t *>(data); data += sizeof(uint32_t);
     974                 :           0 :         uint32_t ptid = *reinterpret_cast<const uint32_t *>(data);
     975                 :           0 :         consumer->process_fork(ehdr, pid, ppid, tid, ptid);
     976                 :           0 :         break;
     977                 :             :       }
     978                 :           0 :     case PERF_RECORD_MMAP2:
     979                 :           0 :       {
     980                 :           0 :         const uint8_t *data = reinterpret_cast<const uint8_t *>(ehdr) + sizeof(perf_event_header);
     981                 :           0 :         uint32_t pid = *reinterpret_cast<const uint32_t *>(data); data += sizeof(uint32_t);
     982                 :           0 :         uint32_t tid = *reinterpret_cast<const uint32_t *>(data); data += sizeof(uint32_t);
     983                 :           0 :         uint64_t addr = *reinterpret_cast<const uint64_t *>(data); data += sizeof(uint64_t);
     984                 :           0 :         uint64_t len = *reinterpret_cast<const uint64_t *>(data); data += sizeof(uint64_t);
     985                 :           0 :         uint64_t pgoff = *reinterpret_cast<const uint64_t *>(data); data += sizeof(uint64_t);
     986                 :           0 :         uint8_t build_id_size = 0;
     987                 :           0 :         const uint8_t *build_id = nullptr;
     988         [ #  # ]:           0 :         if (ehdr->misc & PERF_RECORD_MISC_MMAP_BUILD_ID)
     989                 :             :           {
     990                 :           0 :             build_id_size = *reinterpret_cast<const uint8_t *>(data); data += sizeof(uint8_t);
     991                 :           0 :             data += sizeof(uint8_t) + sizeof(uint16_t); // skip padding
     992                 :           0 :             build_id = reinterpret_cast<const uint8_t *>(data);
     993                 :           0 :             data += build_id_size;
     994                 :             :           }
     995                 :             :         else
     996                 :             :           {
     997                 :           0 :             data += 4 + 4 + 8 + 8; // skip maj, min, ino, ino_generation
     998                 :             :           }
     999                 :           0 :         data += 2 * sizeof(uint32_t); // skip prot, flags
    1000                 :           0 :         const char *filename = reinterpret_cast<const char *>(data);
    1001                 :           0 :         consumer->process_mmap2(ehdr, pid, tid, addr, len, pgoff, build_id_size, build_id, filename);
    1002                 :           0 :         break;
    1003                 :             :       }
    1004                 :             :     default:
    1005                 :             :       break;
    1006                 :             :     }
    1007                 :           0 : }
    1008                 :             : 
    1009                 :             : 
    1010                 :             : ////////////////////////////////////////////////////////////////////////
    1011                 :             : // perf event consumers
    1012                 :             : 
    1013                 :           0 : void StatsPerfConsumer::process_comm(const perf_event_header *sample,
    1014                 :             :                                      uint32_t pid, uint32_t tid, bool exec, const string &comm)
    1015                 :             : {
    1016         [ #  # ]:           0 :   if (show_modules)
    1017                 :             :     {
    1018                 :           0 :       clog << format("process_comm: pid={} tid={} exec={} comm={}\n", pid, tid, exec, comm);
    1019                 :             :     }
    1020                 :           0 : }
    1021                 :             : 
    1022                 :           0 : void StatsPerfConsumer::process_exit(const perf_event_header *sample,
    1023                 :             :                                      uint32_t pid, uint32_t ppid,
    1024                 :             :                                      uint32_t tid, uint32_t ptid)
    1025                 :             : {
    1026         [ #  # ]:           0 :   if (show_modules)
    1027                 :             :     {
    1028                 :           0 :       clog << format("process_exit: pid={} ppid={} tid={} ptid={}\n", pid, ppid, tid, ptid);
    1029                 :             :     }
    1030                 :           0 : }
    1031                 :             : 
    1032                 :           0 : void StatsPerfConsumer::process_fork(const perf_event_header *sample,
    1033                 :             :                                      uint32_t pid, uint32_t ppid,
    1034                 :             :                                      uint32_t tid, uint32_t ptid)
    1035                 :             : {
    1036         [ #  # ]:           0 :   if (show_modules)
    1037                 :             :     {
    1038                 :           0 :       clog << format("process_fork: pid={} ppid={} tid={} ptid={}\n", pid, ppid, tid, ptid);
    1039                 :             :     }
    1040                 :           0 : }
    1041                 :             : 
    1042                 :           0 : void StatsPerfConsumer::process_sample(const perf_event_header *sample,
    1043                 :             :                                        uint64_t ip,
    1044                 :             :                                        uint32_t pid, uint32_t tid,
    1045                 :             :                                        uint64_t time,
    1046                 :             :                                        uint64_t abi,
    1047                 :             :                                        uint32_t nregs, const uint64_t *regs,
    1048                 :             :                                        uint64_t data_size, const uint8_t *data)
    1049                 :             : {
    1050         [ #  # ]:           0 :   if (show_samples)
    1051                 :             :     {
    1052                 :           0 :       clog << format("process_sample: pid={:d} tid={:d} ip={:x} time={:d} abi={:d} nregs={:d} data_size={:d}\n",
    1053                 :           0 :                      pid, tid, ip, time, abi, nregs, data_size);
    1054                 :             :     }
    1055                 :           0 : }
    1056                 :             : 
    1057                 :           0 : void StatsPerfConsumer::process_mmap2(const perf_event_header *sample,
    1058                 :             :                                       uint32_t pid, uint32_t tid,
    1059                 :             :                                       uint64_t addr, uint64_t len, uint64_t pgoff,
    1060                 :             :                                       uint8_t build_id_size, const uint8_t *build_id,
    1061                 :             :                                       const char *filename)
    1062                 :             : {
    1063         [ #  # ]:           0 :   if (show_modules)
    1064                 :             :     {
    1065                 :           0 :       clog << format("process_mmap2: pid={:d} tid={:d} addr={:x} len={:x} pgoff={:x} build_id_size={:d} filename={:s}\n",
    1066                 :           0 :                      pid, tid, addr, len, pgoff, (unsigned)build_id_size, filename);
    1067                 :             :     }
    1068                 :           0 : }
    1069                 :             : 
    1070                 :           0 : StatsPerfConsumer::~StatsPerfConsumer()
    1071                 :             : {
    1072         [ #  # ]:           0 :   for (const auto& kv : this->event_type_counts)
    1073                 :             :     {
    1074                 :             :       // TODO: Decode event type.
    1075                 :           0 :       clog << format("event type {} count {}\n", kv.first, kv.second);
    1076                 :             :     }
    1077                 :           0 : }
    1078                 :             : 
    1079                 :           0 : void StatsPerfConsumer::process(const perf_event_header* ehdr)
    1080                 :             : {
    1081                 :           0 :   this->event_type_counts[ehdr->type] ++;
    1082                 :           0 : }
    1083                 :             : 
    1084                 :             : 
    1085                 :             : ////////////////////////////////////////////////////////////////////////
    1086                 :             : // unwind stats table for PerfConsumerUnwinder + downstream consumers
    1087                 :             : 
    1088                 :           0 : UnwindDwflStats *UnwindStatsTable::pid_find_or_create (pid_t pid)
    1089                 :             : {
    1090         [ #  # ]:           0 :   if (this->dwfl_tab.count(pid) == 0)
    1091         [ #  # ]:           0 :     this->dwfl_tab.emplace(pid, UnwindDwflStats());
    1092                 :           0 :   return &this->dwfl_tab[pid];
    1093                 :             : }
    1094                 :             : 
    1095                 :             : static const string unknown_comm = "<unknown>";
    1096                 :             : 
    1097                 :           0 : string UnwindStatsTable::pid_find_comm (pid_t pid)
    1098                 :             : {
    1099                 :           0 :   UnwindDwflStats *entry = this->pid_find_or_create(pid);
    1100                 :           0 :   if (entry == NULL)
    1101                 :             :     return unknown_comm;
    1102         [ #  # ]:           0 :   if (!entry->comm.empty())
    1103                 :           0 :     return entry->comm;
    1104                 :           0 :   string name = format("/proc/{}/comm", pid);
    1105         [ #  # ]:           0 :   ifstream procfile(name);
    1106         [ #  # ]:           0 :   string buf;
    1107   [ #  #  #  #  :           0 :   if (!procfile || !getline(procfile, buf))
                   #  # ]
    1108         [ #  # ]:           0 :     entry->comm = unknown_comm;
    1109                 :             :   else
    1110         [ #  # ]:           0 :     entry->comm = buf;
    1111                 :             : 
    1112         [ #  # ]:           0 :   return entry->comm;
    1113                 :           0 : }
    1114                 :             : 
    1115                 :           0 : Dwfl *UnwindStatsTable::pid_find_dwfl (pid_t pid)
    1116                 :             : {
    1117         [ #  # ]:           0 :   if (this->dwfl_tab.count(pid) == 0)
    1118                 :           0 :     return NULL;
    1119                 :           0 :   return this->dwfl_tab[pid].dwfl;
    1120                 :             : }
    1121                 :             : 
    1122                 :           0 : void UnwindStatsTable::pid_store_dwfl (pid_t pid, Dwfl *dwfl)
    1123                 :             : {
    1124                 :           0 :   UnwindDwflStats *entry = this->pid_find_or_create(pid);
    1125                 :           0 :   if (entry == NULL)
    1126                 :             :     return;
    1127                 :           0 :   entry->dwfl = dwfl;
    1128         [ #  # ]:           0 :   if (show_summary)
    1129                 :           0 :     this->pid_find_comm(pid);
    1130                 :             :   return;
    1131                 :             : }
    1132                 :             : 
    1133                 :           0 : UnwindModuleStats *UnwindStatsTable::buildid_find (string buildid)
    1134                 :             : {
    1135                 :           0 :   if (this->buildid_tab.count(buildid) == 0)
    1136                 :           0 :     return NULL;
    1137                 :           0 :   return &this->buildid_tab[buildid];
    1138                 :             : }
    1139                 :             : 
    1140                 :           0 : UnwindModuleStats *UnwindStatsTable::buildid_find_or_create (string buildid, Dwfl_Module *mod)
    1141                 :             : {
    1142                 :           0 :   if (this->buildid_tab.count(buildid) == 0)
    1143                 :             :     {
    1144         [ #  # ]:           0 :       this->buildid_tab.emplace(buildid, UnwindModuleStats());
    1145                 :             :       /* TODO: Guess text range for mod? */
    1146                 :           0 :       (void)mod;
    1147                 :             :     }
    1148                 :           0 :   return &this->buildid_tab[buildid];
    1149                 :             : }
    1150                 :             : 
    1151                 :           0 : void UnwindStatsTable::print_summary () const
    1152                 :             : {
    1153                 :             : #define PERCENT(x,tot) ((x+tot == 0)?0.0:((double)x)/((double)tot)*100.0)
    1154                 :           0 :   int total_samples = 0;
    1155                 :           0 :   int total_lost_samples = 0;
    1156                 :           0 :   clog << "\n=== pid / sample counts ===\n";
    1157         [ #  # ]:           0 :   for (auto& p : this->dwfl_tab)
    1158                 :             :     {
    1159                 :           0 :       pid_t pid = p.first;
    1160                 :           0 :       const UnwindDwflStats& d = p.second;
    1161                 :           0 :       clog << format(N_("{} {} -- max {} frames, received {} samples, lost {} samples ({:.1f}%) (last {}, worst {})\n"),
    1162                 :           0 :                      pid, d.comm, d.max_frames,
    1163                 :           0 :                      d.total_samples, d.lost_samples,
    1164                 :           0 :                      PERCENT(d.lost_samples, d.total_samples),
    1165         [ #  # ]:           0 :                      dwfl_unwound_source_str(d.last_unwound),
    1166                 :           0 :                      dwfl_unwound_source_str(d.worst_unwound));
    1167                 :           0 :       total_samples += d.total_samples;
    1168                 :           0 :       total_lost_samples += d.lost_samples;
    1169                 :             :     }
    1170                 :           0 :   clog << "===\n";
    1171                 :           0 :   clog << format(N_("TOTAL -- received {} samples, lost {} samples, loaded {} processes\n"),
    1172                 :             :                  total_samples, total_lost_samples,
    1173                 :           0 :                  this->dwfl_tab.size() /* TODO: If implementing eviction, need to maintain a separate count of evicted pids. */);
    1174                 :           0 :   clog << "\n";
    1175                 :             : #undef PERCENT
    1176                 :           0 : }
    1177                 :             : 
    1178                 :             : 
    1179                 :             : ////////////////////////////////////////////////////////////////////////
    1180                 :             : // real perf consumer: unwind helpers
    1181                 :             : 
    1182                 :           8 : PerfConsumerUnwinder::PerfConsumerUnwinder(UnwindSampleConsumer *usc, UnwindStatsTable *ust)
    1183         [ +  - ]:           8 :   : consumer(usc), stats(ust)
    1184                 :             : {
    1185         [ +  - ]:           8 :   maxframes = usc->maxframes();
    1186         [ +  - ]:           8 :   this->tracker = dwflst_tracker_begin(&dwfl_cfi_callbacks);
    1187                 :           8 : }
    1188                 :             : 
    1189                 :           0 : PerfConsumerUnwinder::PerfConsumerUnwinder(UnwindSampleConsumer *usc, UnwindStatsTable *ust, PerfReader *reader)
    1190         [ #  # ]:           0 :   : consumer(usc), stats(ust)
    1191                 :             : {
    1192         [ #  # ]:           0 :   maxframes = usc->maxframes();
    1193                 :           0 :   this->reader = reader;
    1194         [ #  # ]:           0 :   this->tracker = dwflst_tracker_begin(&dwfl_cfi_callbacks);
    1195                 :           0 : }
    1196                 :             : 
    1197                 :          16 : PerfConsumerUnwinder::~PerfConsumerUnwinder()
    1198                 :             : {
    1199                 :           8 :   dwflst_tracker_end(this->tracker);
    1200                 :          16 : }
    1201                 :             : 
    1202                 :             : /* TODO: Could be relocated to libdwfl/linux-pid-attach.c
    1203                 :             :    to remove some duplication of existing linux-pid-attach code.  */
    1204                 :           0 : int PerfConsumerUnwinder::find_procfile(Dwfl *dwfl, pid_t *pid, Elf **elf, int *elf_fd)
    1205                 :             : {
    1206                 :           0 :   int err = 0; /* The errno to return. XXX libdwfl would also set this for dwfl->attacherr.  */
    1207                 :             : 
    1208                 :             :   /* Make sure to report the actual PID (thread group leader) to
    1209                 :             :      dwfl_attach_state.  */
    1210                 :           0 :   string buffer = format("/proc/{}/status", *pid);
    1211         [ #  # ]:           0 :   ifstream procfile(buffer);
    1212         [ #  # ]:           0 :   if (!procfile)
    1213                 :             :     {
    1214                 :           0 :       err = errno;
    1215                 :           0 :     fail:
    1216                 :           0 :       return err;
    1217                 :             :     }
    1218                 :             : 
    1219                 :           0 :   string line;
    1220   [ #  #  #  # ]:           0 :   while (getline (procfile, line))
    1221         [ #  # ]:           0 :     if (startswith (line.c_str(), "Tgid:"))
    1222                 :             :       {
    1223                 :           0 :         errno = 0;
    1224                 :           0 :         char *endptr;
    1225                 :           0 :         long val = strtol (&line.c_str()[5], &endptr, 10);
    1226   [ #  #  #  # ]:           0 :         if ((errno == ERANGE && val == LONG_MAX)
    1227         [ #  # ]:           0 :             || (*endptr != 0 && *endptr != '\n')
    1228                 :             :                /* <- getline(3) ambiguous on what ends the string */
    1229   [ #  #  #  # ]:           0 :             || val < 0 || val != (pid_t) val)
    1230                 :           0 :           *pid = 0;
    1231                 :             :         else
    1232                 :           0 :           *pid = (pid_t) val;
    1233                 :           0 :         break;
    1234                 :             :       }
    1235                 :             : 
    1236         [ #  # ]:           0 :   if (*pid == 0)
    1237                 :             :     {
    1238                 :           0 :       err = ESRCH;
    1239                 :           0 :       goto fail;
    1240                 :             :     }
    1241                 :             : 
    1242                 :           0 :   {
    1243         [ #  # ]:           0 :     string name = format("/proc/{}/task", *pid);
    1244         [ #  # ]:           0 :     DIR *dir = opendir(name.c_str());
    1245         [ #  # ]:           0 :     if (dir == NULL)
    1246                 :             :       {
    1247                 :           0 :         err = errno;
    1248                 :           0 :         goto fail;
    1249                 :             :       }
    1250                 :             :     else
    1251         [ #  # ]:           0 :       closedir(dir);
    1252                 :           0 :   }
    1253                 :             : 
    1254                 :           0 :   {
    1255         [ #  # ]:           0 :     string name = format("/proc/{}/exe", *pid);
    1256         [ #  # ]:           0 :     *elf_fd = open(name.c_str(), O_RDONLY);
    1257                 :           0 :   }
    1258                 :             : 
    1259         [ #  # ]:           0 :   if (*elf_fd >= 0)
    1260                 :             :     {
    1261         [ #  # ]:           0 :       *elf = elf_begin(*elf_fd, ELF_C_READ_MMAP, NULL);
    1262         [ #  # ]:           0 :       if (*elf == NULL)
    1263                 :             :         {
    1264                 :             :           /* Just ignore, dwfl_attach_state will fall back to trying
    1265                 :             :              to associate the Dwfl with one of the existing Dwfl_Module
    1266                 :             :              ELF images (to know the machine/class backend to use).  */
    1267         [ #  # ]:           0 :           if (verbose)
    1268         [ #  # ]:           0 :             cerr << format(N_("WARNING: find_procfile pid {}: elf not found\n"), (long long) *pid);
    1269         [ #  # ]:           0 :           close(*elf_fd);
    1270                 :           0 :           *elf_fd = -1;
    1271                 :             :         }
    1272                 :             :     }
    1273                 :             :   else
    1274                 :           0 :     *elf = NULL;
    1275                 :           0 :   return 0;
    1276                 :           0 : }
    1277                 :             : 
    1278                 :           0 : Dwfl *PerfConsumerUnwinder::init_dwfl(pid_t pid)
    1279                 :             : {
    1280                 :           0 :   Dwfl *dwfl = dwflst_tracker_dwfl_begin(this->tracker);
    1281                 :             : 
    1282                 :           0 :   int err = dwfl_linux_proc_report(dwfl, pid);
    1283         [ #  # ]:           0 :   if (err < 0)
    1284                 :             :     {
    1285         [ #  # ]:           0 :       if (verbose)
    1286                 :           0 :         cerr << format("WARNING: dwfl_linux_proc_report pid {}: {}\n", (long long) pid, dwfl_errmsg(-1));
    1287                 :           0 :       return NULL;
    1288                 :             :     }
    1289                 :           0 :   err = dwfl_report_end(dwfl, NULL, NULL);
    1290         [ #  # ]:           0 :   if (err != 0)
    1291                 :             :     {
    1292         [ #  # ]:           0 :       if (verbose)
    1293                 :           0 :         cerr << format("WARNING: dwfl_report_end pid {}: {}\n", (long long) pid, dwfl_errmsg(-1));
    1294                 :           0 :       return NULL;
    1295                 :             :     }
    1296                 :             : 
    1297                 :             :   return dwfl;
    1298                 :             : }
    1299                 :             : 
    1300                 :           0 : Dwfl *pcu_init_dwfl_cb (Dwflst_Process_Tracker *cb_tracker __attribute__ ((unused)),
    1301                 :             :                         pid_t pid,
    1302                 :             :                         void *arg)
    1303                 :             : {
    1304                 :           0 :   PerfConsumerUnwinder *pcu = (PerfConsumerUnwinder *)arg;
    1305                 :           0 :   return pcu->init_dwfl(pid);
    1306                 :             : }
    1307                 :             : 
    1308                 :           0 : Dwfl *PerfConsumerUnwinder::find_dwfl(pid_t pid, const uint64_t *regs, uint32_t nregs,
    1309                 :             :                                       Elf **out_elf, bool *cached)
    1310                 :             : {
    1311                 :           0 :   int machine = ebl_get_elfmachine(this->reader->ebl());
    1312         [ #  # ]:           0 :   if (nregs < dwflst_arch_expected_frame_nregs(machine))
    1313                 :             :     {
    1314         [ #  # ]:           0 :       if (verbose)
    1315                 :           0 :         cerr << format(N_("WARNING: find_dwfl: nregs={}, expected at least {}\n"), nregs, ebl_frame_nregs(this->reader->ebl()));
    1316                 :           0 :       return NULL;
    1317                 :             :     }
    1318                 :             : 
    1319                 :           0 :   Elf *elf = NULL;
    1320                 :           0 :   Dwfl *dwfl = dwflst_tracker_find_pid(this->tracker, pid, pcu_init_dwfl_cb, this);
    1321                 :           0 :   int elf_fd = -1;
    1322                 :           0 :   int err;
    1323   [ #  #  #  # ]:           0 :   if (dwfl != NULL && dwfl_pid(dwfl) != -1 /* dwfl is attached */)
    1324                 :             :     {
    1325                 :           0 :       *cached = true;
    1326                 :           0 :       goto reuse;
    1327                 :             :     }
    1328                 :           0 :   err = this->find_procfile(dwfl, &pid, &elf, &elf_fd);
    1329         [ #  # ]:           0 :   if (err != 0)
    1330                 :             :     {
    1331         [ #  # ]:           0 :       if (verbose)
    1332                 :           0 :         cerr << format("WARNING: find_procfile pid {}: {}\n", (long long) pid, dwfl_errmsg(-1));
    1333                 :           0 :       return NULL;
    1334                 :             :     }
    1335                 :             : 
    1336                 :           0 :  reuse:
    1337                 :           0 :   bool is_abi32 = this->last_us.elfclass == ELFCLASS32;
    1338                 :           0 :   int user_regs_sp = dwflst_arch_sp_perf_reg(machine, this->reader->regs_mask(), is_abi32);
    1339                 :             :   /* Bounds check, unlikely to fail:  */
    1340         [ #  # ]:           0 :   this->last_us.sp = user_regs_sp >= 0 ? regs[user_regs_sp] : 0;
    1341                 :           0 :   this->last_us.base = this->last_us.sp;
    1342                 :             : 
    1343         [ #  # ]:           0 :   if (!*cached)
    1344                 :           0 :     this->stats->pid_store_dwfl(pid, dwfl);
    1345                 :           0 :   *out_elf = elf;
    1346                 :           0 :   return dwfl;
    1347                 :             : }
    1348                 :             : 
    1349                 :           0 : int PerfConsumerUnwinder::unwind_frame_cb(Dwfl_Frame *state)
    1350                 :             : {
    1351                 :           0 :   Dwarf_Addr pc;
    1352                 :           0 :   bool isactivation;
    1353         [ #  # ]:           0 :   if (! dwfl_frame_pc(state, &pc, &isactivation))
    1354                 :             :     {
    1355         [ #  # ]:           0 :       if (verbose)
    1356                 :           0 :         cerr << format("WARNING: dwfl_frame_pc: {}\n", dwfl_errmsg(-1));
    1357                 :           0 :       return DWARF_CB_ABORT;
    1358                 :             :     }
    1359                 :             : 
    1360         [ #  # ]:           0 :   Dwarf_Addr pc_adjusted = pc - (isactivation ? 0 : 1);
    1361                 :           0 :   Dwarf_Addr sp;
    1362                 :             : 
    1363                 :           0 :   int is_abi32 = (this->last_us.elfclass == ELFCLASS32);
    1364                 :           0 :   int m = ebl_get_elfmachine(this->reader->ebl());
    1365                 :           0 :   int user_regs_sp = dwflst_arch_sp_dwarf_reg(m, is_abi32);
    1366                 :             :   /* Bounds check, unlikely to fail:  */
    1367         [ #  # ]:           0 :   int rc = user_regs_sp >= 0 ? dwfl_frame_reg(state, user_regs_sp, &sp) : -1;
    1368         [ #  # ]:           0 :   if (rc < 0)
    1369                 :             :     {
    1370   [ #  #  #  # ]:           0 :       if (verbose && user_regs_sp < 0)
    1371                 :           0 :         cerr << "WARNING: dwflst_arch_sp_dwarf_reg: arch unsupported\n";
    1372         [ #  # ]:           0 :       else if (verbose)
    1373                 :           0 :         cerr << format("WARNING: dwfl_frame_reg: {}\n", dwfl_errmsg(-1));
    1374                 :           0 :       return DWARF_CB_ABORT;
    1375                 :             :     }
    1376                 :             : 
    1377                 :           0 :   UnwindDwflStats *dwfl_ent = this->stats->pid_find_or_create(this->last_us.pid);
    1378                 :           0 :   if (dwfl_ent != NULL)
    1379                 :             :     {
    1380                 :           0 :       Dwfl_Unwound_Source unwound_source = dwfl_frame_unwound_source(state);
    1381         [ #  # ]:           0 :       if (unwound_source > dwfl_ent->worst_unwound)
    1382                 :           0 :         dwfl_ent->worst_unwound = unwound_source;
    1383                 :           0 :       dwfl_ent->last_unwound = unwound_source;
    1384         [ #  # ]:           0 :       if (show_frames)
    1385                 :             :         {
    1386                 :           0 :           Dwfl_Module *m = dwfl_addrmodule(this->last_us.dwfl, pc);
    1387                 :           0 :           uint64_t rel_pc = pc_adjusted;
    1388                 :           0 :           int j = dwfl_module_relocate_address(m, &rel_pc);
    1389                 :           0 :           (void) j;
    1390                 :           0 :           clog << format("* frame {:d}: rel_pc={:x} raw_pc={:x} sp={:x}+{:x} [{}]\n",
    1391                 :           0 :                          this->last_us.addrs.size(), rel_pc, pc_adjusted, this->last_us.base, (sp - this->last_us.base), dwfl_unwound_source_str(unwound_source));
    1392                 :             :         }
    1393                 :             :     }
    1394                 :             :   else
    1395                 :             :     {
    1396                 :             :       if (show_frames)
    1397                 :             :         {
    1398                 :             :           Dwfl_Module *m = dwfl_addrmodule(this->last_us.dwfl, pc);
    1399                 :             :           uint64_t rel_pc = pc_adjusted;
    1400                 :             :           int j = dwfl_module_relocate_address(m, &rel_pc);
    1401                 :             :           (void) j;
    1402                 :             :           clog << format(N_("* frame {:d}: rel_pc={:x} raw_pc={:x} sp={:x}+{:x} [dwfl_ent not found]\n"),
    1403                 :             :                          this->last_us.addrs.size(), rel_pc, pc_adjusted, this->last_us.base, (sp - this->last_us.base));
    1404                 :             :         }
    1405                 :             :     }
    1406         [ #  # ]:           0 :   if (show_debugfile)
    1407                 :             :     {
    1408                 :           0 :       Dwfl_Module *m = dwfl_addrmodule(this->last_us.dwfl, pc);
    1409         [ #  # ]:           0 :       if (m == NULL)
    1410                 :             :         {
    1411                 :           0 :           clog << format("* pid {:d} pc={:x} -> MODULE NOT FOUND\n",
    1412                 :           0 :                          this->last_us.pid, pc);
    1413                 :             :         }
    1414                 :             :       else
    1415                 :             :         {
    1416                 :           0 :           const unsigned char *desc;
    1417                 :           0 :           GElf_Addr vaddr;
    1418                 :           0 :           int build_id_len = dwfl_module_build_id(m, &desc, &vaddr);
    1419                 :           0 :           clog << format("* pid {:d} build_id=", this->last_us.pid);
    1420         [ #  # ]:           0 :           for (int i = 0; i < build_id_len; ++i)
    1421                 :           0 :             clog << format("{:02x}", static_cast<int>(desc[i]));
    1422                 :             : 
    1423                 :           0 :           const char *mainfile;
    1424                 :           0 :           const char *debugfile;
    1425                 :           0 :           const char *modname = dwfl_module_info(m, NULL, NULL, NULL, NULL,
    1426                 :           0 :                                                  NULL, &mainfile, &debugfile);
    1427                 :           0 :           clog << format("module={} mainfile={} debugfile={}\n",
    1428                 :             :                          modname,
    1429                 :           0 :                          mainfile ? mainfile : "<none>",
    1430   [ #  #  #  # ]:           0 :                          debugfile ? debugfile : "<none>");
    1431                 :             :           /* TODO: Also store this data to avoid repeated extraction for
    1432                 :             :              the final buildid summary?  */
    1433                 :             : #ifdef DEBUG_MODULES
    1434                 :             :           Dwarf_Addr bias;
    1435                 :             :           Dwarf_CFI *cfi_eh = dwfl_module_eh_cfi(m, &bias);
    1436                 :             :           if (cfi_eh == NULL)
    1437                 :             :             clog << format("* pc={:x} -> NO EH_CFI\n", pc);
    1438                 :             : #endif
    1439                 :             :         }
    1440                 :             :     }
    1441                 :             : 
    1442                 :           0 :   this->last_us.sp = sp;
    1443                 :           0 :   this->last_us.addrs.push_back(pc);
    1444                 :             : 
    1445                 :             :   /* e.g. gmon callgraphs only requires maxframes=1
    1446                 :             :      (initial pc + one frame for caller ID only)  */
    1447         [ #  # ]:           0 :   if (this->last_us.addrs.size() > this->maxframes)
    1448                 :             :     {
    1449                 :             :       /* XXX without maxframes, very rarely, the unwinder can loop
    1450                 :             :          infinitely; worth investigating?  */
    1451                 :             :       return DWARF_CB_ABORT;
    1452                 :             :     }
    1453                 :             :   return DWARF_CB_OK;
    1454                 :             : }
    1455                 :             : 
    1456                 :           0 : int pcu_unwind_frame_cb(Dwfl_Frame *state, void *arg)
    1457                 :             : {
    1458                 :           0 :   PerfConsumerUnwinder *pcu = (PerfConsumerUnwinder *)arg;
    1459                 :           0 :   return pcu->unwind_frame_cb(state);
    1460                 :             : }
    1461                 :             : 
    1462                 :             : 
    1463                 :             : ////////////////////////////////////////////////////////////////////////
    1464                 :             : // real perf consumer: event handler callbacks
    1465                 :             : 
    1466                 :           0 : void PerfConsumerUnwinder::process_comm(const perf_event_header *sample,
    1467                 :             :                                         uint32_t pid, uint32_t tid, bool exec, const string &comm)
    1468                 :             : {
    1469                 :             :   // NB: Could have dwflst ditch data for process and start anew, if EXEC.
    1470                 :             :   // XXX: is this needed to avoid gradual memory leaks or pid reuse?
    1471                 :           0 : }
    1472                 :             : 
    1473                 :           0 : void PerfConsumerUnwinder::process_exit(const perf_event_header *sample,
    1474                 :             :                                         uint32_t pid, uint32_t ppid,
    1475                 :             :                                         uint32_t tid, uint32_t ptid)
    1476                 :             : {
    1477                 :             :   // NB: Could have dwflst ditch data for process.
    1478                 :             :   // XXX: is this needed to avoid gradual memory leaks of pid reuse?
    1479                 :           0 : }
    1480                 :             : 
    1481                 :           0 : void PerfConsumerUnwinder::process_fork(const perf_event_header *sample,
    1482                 :             :                                         uint32_t pid, uint32_t ppid,
    1483                 :             :                                         uint32_t tid, uint32_t ptid)
    1484                 :             : {
    1485                 :             :   // NB: Could have dwflst begin tracking a new process, but
    1486                 :             :   // this will likely happen automatically when a packet is received
    1487                 :             :   // from it.  The short duration between fork/exec typically means
    1488                 :             :   // elfutils will pick up on the post-exec process -- we would have
    1489                 :             :   // to work hard to replicate a situation where
    1490                 :             :   // process_fork/process_comm handling are needed.
    1491                 :           0 : }
    1492                 :             : 
    1493                 :           0 : void PerfConsumerUnwinder::process_sample(const perf_event_header *sample,
    1494                 :             :                                           uint64_t ip,
    1495                 :             :                                           uint32_t pid, uint32_t tid,
    1496                 :             :                                           uint64_t time,
    1497                 :             :                                           uint64_t abi,
    1498                 :             :                                           uint32_t nregs, const uint64_t *regs,
    1499                 :             :                                           uint64_t data_size, const uint8_t *data)
    1500                 :             : {
    1501         [ #  # ]:           0 :   string comm;
    1502         [ #  # ]:           0 :   if (show_summary)
    1503         [ #  # ]:           0 :     comm = this->stats->pid_find_comm(pid);
    1504                 :             : 
    1505         [ #  # ]:           0 :   if (show_frames)
    1506         [ #  # ]:           0 :     clog << "\n"; /* extra newline for padding */
    1507                 :             : 
    1508                 :           0 :   Elf *elf = NULL; // Released during dwflst_tracker_end
    1509                 :           0 :   bool cached = false;
    1510         [ #  # ]:           0 :   Dwfl *dwfl = this->find_dwfl(pid, regs, nregs, &elf, &cached);
    1511                 :           0 :   UnwindDwflStats *dwfl_ent = NULL;
    1512                 :           0 :   bool first_load = false; /* -> for show_modules: pid is loaded first time */
    1513   [ #  #  #  #  :           0 :   if (verbose || show_summary || show_modules)
                   #  # ]
    1514                 :             :     {
    1515                 :           0 :       if (dwfl_ent == NULL)
    1516         [ #  # ]:           0 :         dwfl_ent = this->stats->pid_find_or_create(pid);
    1517         [ #  # ]:           0 :       if (dwfl_ent->total_samples == 0)
    1518                 :           0 :         first_load = true;
    1519                 :             :     }
    1520         [ #  # ]:           0 :   if (dwfl == NULL)
    1521                 :             :     {
    1522   [ #  #  #  # ]:           0 :       if (show_summary || show_modules)
    1523                 :             :         {
    1524                 :             :           /* dwfl_ent loaded above */
    1525                 :           0 :           dwfl_ent->total_samples++;
    1526                 :           0 :           dwfl_ent->lost_samples++;
    1527                 :             :         }
    1528   [ #  #  #  # ]:           0 :       if (verbose && show_summary)
    1529                 :             :         {
    1530         [ #  # ]:           0 :           cerr << format("WARNING: find_dwfl pid {} ({}) (failed)\n", (long long)pid, comm);
    1531                 :             :         }
    1532         [ #  # ]:           0 :       else if (verbose) // XXX
    1533                 :             :         {
    1534         [ #  # ]:           0 :           cerr << format("WARNING: find_dwfl pid {} (failed)\n", (long long)pid);
    1535                 :             :         }
    1536                 :           0 :       return;
    1537                 :             :     }
    1538                 :             : 
    1539   [ #  #  #  #  :           0 :   if (show_samples || (first_load && show_modules))
                   #  # ]
    1540                 :             :     {
    1541                 :           0 :       bool is_abi32 = (abi == PERF_SAMPLE_REGS_ABI_32);
    1542                 :           0 :       clog << format("find_dwfl {}pid {:d} {}({}): hdr_size={:d} size={:d}{} pc={:x} sp={:x}+{:d}\n",
    1543         [ #  # ]:           0 :                      first_load ? "newly seen " : "", (long long)pid,
    1544         [ #  # ]:           0 :                      (cached ? "(cached) " : ""), comm,
    1545                 :           0 :                      sample->size, data_size,
    1546         [ #  # ]:           0 :                      (is_abi32 ? " (32-bit)" : ""), ip,
    1547         [ #  # ]:           0 :                      this->last_us.base, 0);
    1548                 :             :     }
    1549                 :             : 
    1550         [ #  # ]:           0 :   this->last_us.addrs.clear();
    1551         [ #  # ]:           0 :   this->last_us.elfclass = (abi == PERF_SAMPLE_REGS_ABI_32 ? ELFCLASS32 : ELFCLASS64);
    1552                 :           0 :   this->last_us.dwfl = dwfl;
    1553                 :           0 :   this->last_us.pid = pid;
    1554                 :           0 :   int rc = dwflst_perf_sample_getframes(dwfl, elf, pid, tid,
    1555                 :             :                                         data, data_size,
    1556                 :             :                                         regs, nregs,
    1557         [ #  # ]:           0 :                                         this->reader->regs_mask(), abi,
    1558                 :             :                                         pcu_unwind_frame_cb, this);
    1559         [ #  # ]:           0 :   if (rc < 0)
    1560                 :             :     {
    1561                 :             :       /* dwfl_ent loaded above */
    1562   [ #  #  #  # ]:           0 :       if (verbose && dwfl_ent->shown_errors < 10)
    1563                 :             :         {
    1564                 :           0 :           dwfl_ent->shown_errors ++;
    1565                 :           0 :           cerr << format("WARNING: dwflst_perf_sample_getframes pid {}: {}{}\n",
    1566         [ #  # ]:           0 :                          (long long)pid, dwfl_errmsg(-1),
    1567   [ #  #  #  # ]:           0 :                          dwfl_ent->shown_errors >= 10 ?
    1568                 :           0 :                          " (...suppressing further warnings for this pid)" : "");
    1569                 :             :         }
    1570                 :             :     }
    1571         [ #  # ]:           0 :   if (show_summary)
    1572                 :             :     {
    1573                 :             :       /* For final diagnostics.  dwfl_ent loaded above */
    1574         [ #  # ]:           0 :       if (this->last_us.addrs.size() > (unsigned long)dwfl_ent->max_frames)
    1575                 :           0 :         dwfl_ent->max_frames = this->last_us.addrs.size();
    1576                 :           0 :       dwfl_ent->total_samples++;
    1577   [ #  #  #  # ]:           0 :       if (this->maxframes > 2 && this->last_us.addrs.size() <= 2)
    1578                 :           0 :         dwfl_ent->lost_samples++;
    1579                 :             :     }
    1580                 :             : 
    1581         [ #  # ]:           0 :   this->consumer->process(&this->last_us);
    1582                 :             :   return;
    1583                 :           0 : }
    1584                 :             : 
    1585                 :           0 : void PerfConsumerUnwinder::process_mmap2(const perf_event_header *sample,
    1586                 :             :                                          uint32_t pid, uint32_t tid,
    1587                 :             :                                          uint64_t addr, uint64_t len, uint64_t pgoff,
    1588                 :             :                                          uint8_t build_id_size, const uint8_t *build_id,
    1589                 :             :                                          const char *filename)
    1590                 :             : {
    1591                 :           0 :   Dwfl *dwfl = this->stats->pid_find_dwfl(pid);
    1592         [ #  # ]:           0 :   if (dwfl != NULL)
    1593                 :             :     {
    1594                 :           0 :       dwfl_report_begin_add(dwfl);
    1595                 :           0 :       dwfl_report_module(dwfl, filename, /*start*/ addr, /*end*/ addr + len);
    1596                 :           0 :       dwfl_report_end(dwfl, NULL, NULL);
    1597                 :             :     }
    1598                 :           0 : }
    1599                 :             : 
    1600                 :             : 
    1601                 :             : ////////////////////////////////////////////////////////////////////////
    1602                 :             : // unwind data consumers: basic statistics
    1603                 :             : 
    1604                 :           0 : UnwindStatsConsumer::~UnwindStatsConsumer()
    1605                 :             : {
    1606                 :           0 :   this->stats->print_summary();
    1607                 :           0 : }
    1608                 :             : 
    1609                 :           0 : void UnwindStatsConsumer::process(const UnwindSample *sample)
    1610                 :             : {
    1611                 :             :   /* Most of the logic is handled by UnwindStatsTable.  */
    1612                 :           0 : }
    1613                 :             : 
    1614                 :           8 : int UnwindStatsConsumer::maxframes()
    1615                 :             : {
    1616         [ -  + ]:           8 :   return opt_maxframes >= 0 ? opt_maxframes : 256;
    1617                 :             : }
    1618                 :             : 
    1619                 :             : 
    1620                 :             : ////////////////////////////////////////////////////////////////////////
    1621                 :             : // unwind data consumers: gprof
    1622                 :             : 
    1623                 :             : /* gmon.out file format bits */
    1624                 :             : #define GMON_MAGIC "gmon"
    1625                 :             : #define GMON_VERSION 1
    1626                 :             : 
    1627                 :             : struct gmon_hdr {
    1628                 :             :   char cookie[4];
    1629                 :             :   char version[4];
    1630                 :             :   char spare[3 * 4];
    1631                 :             : };
    1632                 :             : 
    1633                 :             : enum gmon_entry_tag {
    1634                 :             :   GMON_TAG_TIME_HIST = 0,
    1635                 :             :   GMON_TAG_CG_ARC = 1,
    1636                 :             :   GMON_TAG_BB_COUNT = 2,
    1637                 :             : };
    1638                 :             : 
    1639                 :             : struct gmon_hist_hdr {
    1640                 :             :   uint8_t tag; /* GMON_TAG_TIME_HIST */
    1641                 :             :   uint8_t unused[3];
    1642                 :             :   uint64_t low_pc;
    1643                 :             :   uint64_t high_pc;
    1644                 :             :   uint32_t num_buckets;
    1645                 :             :   uint32_t prof_rate;
    1646                 :             :   char _dimension_string[16];
    1647                 :             : };
    1648                 :             : 
    1649                 :           0 : void GprofUnwindSampleConsumer::record_gmon_hist(ostream &of,
    1650                 :             :                                                  map<uint64_t, uint32_t> &histogram,
    1651                 :             :                                                  uint64_t low_pc, uint64_t high_pc,
    1652                 :             :                                                  uint64_t alignment)
    1653                 :             : {
    1654                 :             :   // write one histogram from low_pc ... high_pc
    1655                 :           0 :   uint32_t num_buckets = (high_pc-low_pc)/alignment + 1;
    1656                 :           0 :   double result_scale = (double)((high_pc-low_pc)/sizeof(uint16_t))/num_buckets;
    1657         [ #  # ]:           0 :   if (verbose > 5)
    1658                 :             :     /* It's the @scale value that must be kept within 0.000001 of 0.5 to
    1659                 :             :        keep gprof from complaining.  */
    1660                 :           0 :     clog << format("DEBUG +hist {:x}..{:x} (alignment {}) of {} buckets @scale {}\n",
    1661                 :           0 :                    low_pc, high_pc, alignment, num_buckets, result_scale);
    1662                 :             : 
    1663                 :             :   // write histogram record header
    1664                 :           0 :   unsigned char tag = GMON_TAG_TIME_HIST;
    1665                 :           0 :   of.write(reinterpret_cast<const char *>(&tag), sizeof(tag));
    1666                 :           0 :   int wordsize = (sizeof (void *) == 8) ? 8 : 4;
    1667                 :           0 :   if (wordsize == 4) {
    1668                 :             :     uint32_t addr = low_pc;
    1669                 :             :     of.write(reinterpret_cast<const char *>(&addr), sizeof(addr));
    1670                 :             :     addr = high_pc;
    1671                 :             :     of.write(reinterpret_cast<const char *>(&addr), sizeof(addr));
    1672                 :             :   } else {
    1673                 :           0 :     of.write(reinterpret_cast<const char *>(&low_pc), sizeof(low_pc));
    1674                 :           0 :     of.write(reinterpret_cast<const char *>(&high_pc), sizeof(high_pc));
    1675                 :             :   }
    1676                 :           0 :   of.write(reinterpret_cast<const char *>(&num_buckets), sizeof(num_buckets));
    1677                 :           0 :   uint32_t prof_rate = attr.sample_freq;
    1678                 :           0 :   of.write(reinterpret_cast<const char *>(&prof_rate), sizeof(prof_rate));
    1679                 :             :   // dimension string is 15 chars long (not null terminated)
    1680         [ #  # ]:           0 :   std::string dimension_base = libpfm_event.empty() ? "ticks" :
    1681                 :           0 :     libpfm_event.substr(0, 15);
    1682         [ #  # ]:           0 :   dimension_base.resize(15, '\0'); // ensure exactly 15 bytes
    1683         [ #  # ]:           0 :   of.write(dimension_base.data(), 15);
    1684                 :             :   // dimension character abbreviation: just take the first char of above
    1685         [ #  # ]:           0 :   of.write(dimension_base.data(), 1);
    1686                 :             : 
    1687                 :             :   // write histogram buckets
    1688                 :           0 :   uint64_t bucket_addr = low_pc;
    1689                 :           0 :   int n_overflows = 0, max_overflows = 5; // limit 'bucket overflow' spam
    1690         [ #  # ]:           0 :   for (uint32_t bucket = 0; bucket < num_buckets; bucket++)
    1691                 :             :     {
    1692                 :           0 :       uint16_t count = 0;
    1693                 :           0 :       for (auto it = histogram.lower_bound(bucket_addr);
    1694         [ #  # ]:           0 :            it != histogram.upper_bound(bucket_addr+alignment-1);
    1695                 :           0 :            it ++)
    1696                 :             :         {
    1697         [ #  # ]:           0 :           if (numeric_limits<uint16_t>::max() <= (int) count + (int) it->second)
    1698                 :             :             {
    1699                 :           0 :               count = numeric_limits<uint16_t>::max();
    1700                 :             :               // XXX: a provisional error message to give a sense of
    1701                 :             :               // whether this happens often-enough to do something
    1702                 :             :               // more complex, such as adjusting the histogram
    1703                 :             :               // granularity:
    1704         [ #  # ]:           0 :               if (n_overflows >= max_overflows) break;
    1705                 :           0 :               n_overflows++;
    1706                 :           0 :               cerr << format("WARNING: histogram bucket overflow at {:x}{}",
    1707                 :             :                              bucket_addr,
    1708   [ #  #  #  # ]:           0 :                              n_overflows >= max_overflows ?
    1709         [ #  # ]:           0 :                              " (... suppressing further warnings for this histogram)" : "")
    1710                 :           0 :                    << endl;
    1711                 :           0 :               break;
    1712                 :             :             }
    1713                 :           0 :           count += it->second;
    1714                 :             :         }
    1715                 :           0 :       bucket_addr += alignment;
    1716         [ #  # ]:           0 :       of.write(reinterpret_cast<const char *>(&count), sizeof(count));
    1717                 :             :     }
    1718                 :           0 : }
    1719                 :             : 
    1720                 :           0 : void GprofUnwindSampleConsumer::record_gmon_out(const string& buildid, UnwindModuleStats& m)
    1721                 :             : {
    1722         [ #  # ]:           0 :   string filename = output_dir + "/" + "gmon." + buildid + ".out";
    1723   [ #  #  #  # ]:           0 :   string exe_symlink_path = output_dir + "/" + "gmon." + buildid + ".exe";
    1724   [ #  #  #  # ]:           0 :   string json_path = output_dir + "/" + "gmon." + buildid + ".json";
    1725                 :             : 
    1726         [ #  # ]:           0 :   if (output_force) {
    1727   [ #  #  #  # ]:           0 :     filesystem::remove(filename);
    1728   [ #  #  #  # ]:           0 :     filesystem::remove(exe_symlink_path);
    1729   [ #  #  #  # ]:           0 :     filesystem::remove(json_path);
    1730                 :             :   }
    1731                 :             : 
    1732   [ #  #  #  # ]:           0 :   string target_path = buildid_to_mainfile[buildid];
    1733         [ #  # ]:           0 :   if (target_path != unknown_comm) // skip .exe symlink if there's no path
    1734         [ #  # ]:           0 :     if (symlink(target_path.c_str(), exe_symlink_path.c_str()) == -1) {
    1735                 :             :       // Handle error, e.g., print errno or throw exception
    1736         [ #  # ]:           0 :       cerr << format("WARNING: symlink failed: {}\n", strerror(errno));
    1737                 :             :       // NB: no return needed here; proceed to write out other bits.
    1738                 :             :       // A smart enough consumer will make do with buildid based executable lookup.
    1739                 :             :     }
    1740                 :             : 
    1741         [ #  # ]:           0 :   json_object *metadata = json_object_new_object();
    1742         [ #  # ]:           0 :   if (!metadata) {
    1743                 :           0 :   json_fail:
    1744         [ #  # ]:           0 :     cerr << format("ERROR: json allocation failed: {}\n", strerror(errno));
    1745                 :           0 :     return;
    1746                 :             :   }
    1747         [ #  # ]:           0 :   json_object *buildid_js = json_object_new_string(buildid.c_str());
    1748         [ #  # ]:           0 :   if (NULL == buildid_js) goto json_fail;
    1749         [ #  # ]:           0 :   json_object_object_add(metadata, "buildid", buildid_js);
    1750                 :           0 :   if (buildid_to_mainfile.count(buildid) != 0) {
    1751         [ #  # ]:           0 :     const string &mainfile = buildid_to_mainfile[buildid];
    1752         [ #  # ]:           0 :     json_object *mainfile_js = json_object_new_string(mainfile.c_str());
    1753         [ #  # ]:           0 :     if (NULL == mainfile_js) goto json_fail;
    1754         [ #  # ]:           0 :     json_object_object_add(metadata, "mainfile", mainfile_js);
    1755                 :             :   }
    1756                 :           0 :   if (buildid_to_debugfile.count(buildid) != 0) {
    1757         [ #  # ]:           0 :     const string &debugfile = buildid_to_debugfile[buildid];
    1758         [ #  # ]:           0 :     json_object *debugfile_js = json_object_new_string(debugfile.c_str());
    1759         [ #  # ]:           0 :     if (NULL == debugfile_js) goto json_fail;
    1760         [ #  # ]:           0 :     json_object_object_add(metadata, "debugfile", debugfile_js);
    1761                 :             :   }
    1762         [ #  # ]:           0 :   if (libpfm_event != "") {
    1763         [ #  # ]:           0 :     json_object *event_js = json_object_new_string(libpfm_event.c_str());
    1764         [ #  # ]:           0 :     if (NULL == event_js) goto json_fail;
    1765         [ #  # ]:           0 :     json_object_object_add(metadata, "libpfm-event", event_js);
    1766                 :             :   }
    1767         [ #  # ]:           0 :   if (libpfm_event_decoded != "") {
    1768         [ #  # ]:           0 :     json_object *event_js = json_object_new_string(libpfm_event_decoded.c_str());
    1769         [ #  # ]:           0 :     if (NULL == event_js) goto json_fail;
    1770         [ #  # ]:           0 :     json_object_object_add(metadata, "libpfm-event-decoded", event_js);
    1771                 :             :   }
    1772                 :           0 :   {
    1773         [ #  # ]:           0 :     json_object *br_js = json_object_new_boolean(branch_record);
    1774         [ #  # ]:           0 :     if (NULL == br_js) goto json_fail;
    1775         [ #  # ]:           0 :     json_object_object_add(metadata, "branch-record", br_js);
    1776                 :             :   }
    1777                 :             : 
    1778         [ #  # ]:           0 :   const char *metadata_str = json_object_to_json_string(metadata);
    1779         [ #  # ]:           0 :   if (!metadata_str) goto json_fail;
    1780         [ #  # ]:           0 :   ofstream of_js(json_path);
    1781         [ #  # ]:           0 :   of_js << metadata_str;
    1782         [ #  # ]:           0 :   of_js.close();
    1783         [ #  # ]:           0 :   json_object_put(metadata);
    1784                 :             : 
    1785         [ #  # ]:           0 :   ofstream of(filename, ios::binary);
    1786         [ #  # ]:           0 :   if (!of)
    1787                 :             :     {
    1788         [ #  # ]:           0 :       cerr << format(N_("ERROR: buildid {} -- could not open '{}' for writing\n"), buildid, filename);
    1789                 :           0 :       return;
    1790                 :             :     }
    1791                 :             : 
    1792                 :             :   /* Write gmon header.  It and other headers mostly hold
    1793                 :             :      native-endian and fixed (or native) bitwidth values.  In
    1794                 :             :      principle, we should get the bitwidth/endianness from the
    1795                 :             :      particular executable associated with the buildid.  But, being a
    1796                 :             :      live profiler, we don't really have to deal with CROSS
    1797                 :             :      architecture work, and for now can just hard-code the bitwidth to
    1798                 :             :      match this host program. XXX
    1799                 :             :   */
    1800                 :           0 :   int wordsize = (sizeof(void *) == 8) ? 8 : 4;
    1801                 :           0 :   struct gmon_hdr ghdr;
    1802         [ #  # ]:           0 :   memcpy(&ghdr.cookie[0], GMON_MAGIC, 4);
    1803                 :           0 :   uint32_t version = GMON_VERSION;
    1804                 :           0 :   memcpy(&ghdr.version[0], reinterpret_cast<const char *>(&version), 4);
    1805         [ #  # ]:           0 :   memset(&ghdr.spare[0], 0, sizeof(ghdr.spare));
    1806         [ #  # ]:           0 :   of.write(reinterpret_cast<const char *>(&ghdr), sizeof(ghdr));
    1807                 :             : 
    1808         [ #  # ]:           0 :   if (m.histogram.size() > 0)
    1809                 :             :     {
    1810         [ #  # ]:           0 :       uint64_t low_pc = m.histogram.begin()->first;
    1811         [ #  # ]:           0 :       uint64_t high_pc = m.histogram.rbegin()->first;
    1812                 :           0 :       uint64_t alignment = (high_pc - low_pc + 1) / UINT_MAX + 1;
    1813                 :             : 
    1814         [ #  # ]:           0 :       if (gmon_hist_split == HIST_SPLIT_NONE)
    1815                 :             :         {
    1816                 :             :           /* Put everything into one histogram.  */
    1817         [ #  # ]:           0 :           this->record_gmon_hist(of, m.histogram, low_pc, high_pc, alignment);
    1818                 :             :         }
    1819         [ #  # ]:           0 :       else if (gmon_hist_split == HIST_SPLIT_EVEN)
    1820                 :             :         {
    1821                 :             :           /* This option attempts to satisfy gprof's histogram scale
    1822                 :             :              consistency check, which requires all values
    1823                 :             :              '(double)(high_pc-low_pc)/num_buckets' to fall within
    1824                 :             :              EPSILON.  In practice, we can only be sure of this if we
    1825                 :             :              cover the address space with histograms all one size.  */
    1826                 :             : 
    1827                 :             :           /* Keep the search for 'optimal' size simple -- we just need
    1828                 :             :              a plausible order of magnitude.  XXX Some rechecking of
    1829                 :             :              correctness needed.  */
    1830                 :             :           //uint64_t min_size = 1; // this is 'optimal' much of the time
    1831                 :             :           uint64_t min_size = 1024;
    1832                 :             :           uint64_t max_size = high_pc - low_pc;
    1833                 :             :           uint64_t opt_size = min_size;
    1834                 :             :           uint64_t opt_est = 0;
    1835                 :             :           uint64_t next_size = opt_size;
    1836         [ #  # ]:           0 :           while (next_size < max_size)
    1837                 :             :             {
    1838                 :           0 :               uint64_t size_inc = sizeof(struct gmon_hdr) + next_size;
    1839                 :           0 :               uint64_t size_est = size_inc;
    1840                 :           0 :               uint64_t pc = low_pc;
    1841         [ #  # ]:           0 :               while (pc + size_est < high_pc)
    1842                 :             :                 {
    1843                 :           0 :                   auto it = m.histogram.upper_bound(pc + size_est/alignment);
    1844         [ #  # ]:           0 :                   if (it == m.histogram.end())
    1845                 :             :                     break;
    1846                 :           0 :                   pc = it->first;
    1847                 :           0 :                   size_est += sizeof(struct gmon_hdr) + next_size;
    1848                 :             :                 }
    1849         [ #  # ]:           0 :               if (opt_est == 0 || size_est < opt_est)
    1850                 :             :                 {
    1851                 :           0 :                   opt_size = next_size;
    1852                 :           0 :                   opt_est = size_est;
    1853                 :             :                 }
    1854                 :             :               // if (opt_est > prev_est) break; /* XXX: We've hit the lowest point.  */
    1855                 :           0 :               next_size = 2 * next_size;
    1856                 :             :             }
    1857                 :             : 
    1858                 :             :           /* Partition into histograms of opt_size.
    1859                 :             :              TODO: Need to check if low_pc must be aligned.  */
    1860                 :           0 :           uint64_t prev_pc = low_pc;
    1861                 :           0 :           uint64_t pc = prev_pc;
    1862         [ #  # ]:           0 :           for (const auto& p : m.histogram)
    1863                 :             :             {
    1864                 :           0 :               pc = p.first;
    1865         [ #  # ]:           0 :               if (pc - low_pc > opt_size)
    1866                 :             :                 {
    1867                 :             :                   /* Record a histogram from low_pc to low_pc+opt_size.  */
    1868                 :           0 :                   this->record_gmon_hist(of, m.histogram,
    1869         [ #  # ]:           0 :                                          low_pc, low_pc+opt_size-1 /* >= prev_pc */,
    1870                 :             :                                          alignment);
    1871                 :             :                   low_pc = pc;
    1872                 :             :                 }
    1873                 :           0 :               prev_pc = pc;
    1874                 :             :             }
    1875                 :             :           /* Record a final histogram from low_pc to low_pc+opt_size.
    1876                 :             :              TODO: Edge case -- adjust for overflow of
    1877                 :             :              low_pc+opt_size at end of address space.  */
    1878                 :           0 :           this->record_gmon_hist(of, m.histogram,
    1879         [ #  # ]:           0 :                                  low_pc, low_pc+opt_size-1 /* >= prev_pc */,
    1880                 :             :                                  alignment);
    1881                 :             :         }
    1882         [ #  # ]:           0 :       else if (gmon_hist_split == HIST_SPLIT_FLEX)
    1883                 :             :         {
    1884                 :             :           /* Allow variable-size histograms to save on storage space.
    1885                 :             :              Will fail gprof's input consistency checks, XXX but ok
    1886                 :             :              for profiledb purposes?  */
    1887                 :             :           uint64_t prev_pc = low_pc;
    1888                 :             :           uint64_t pc = prev_pc;
    1889                 :             :           /* Iterate histogram ascending by key, faster than by addr
    1890                 :             :              when we just need to scan for gaps.  */
    1891         [ #  # ]:           0 :           for (const auto& p : m.histogram)
    1892                 :             :             {
    1893                 :           0 :               pc = p.first;
    1894                 :           0 :               uint64_t bin_dist = (pc - prev_pc) / alignment;
    1895         [ #  # ]:           0 :               if (bin_dist > sizeof(struct gmon_hist_hdr))
    1896                 :             :                 /* XXX If we add '&& low_pc != prev_pc && pc != high_pc',
    1897                 :             :                    this avoids producing a histogram with only 1 entry,
    1898                 :             :                    but this is still not enough to satisfy gprof's
    1899                 :             :                    histogram scale calculation.  */
    1900                 :             :                 {
    1901                 :             :                   /* Record a histogram from low_pc to prev_pc.  */
    1902         [ #  # ]:           0 :                   this->record_gmon_hist(of, m.histogram, low_pc, prev_pc, alignment);
    1903                 :             :                   low_pc = pc;
    1904                 :             :                 }
    1905                 :           0 :               prev_pc = pc;
    1906                 :             :             }
    1907                 :             :           /* Record a final histogram from low_pc to pc.  */
    1908         [ #  # ]:           0 :           this->record_gmon_hist(of, m.histogram, low_pc, pc, alignment);
    1909                 :             :         }
    1910                 :             :     }
    1911                 :             : 
    1912                 :             :   /* Write call graph arcs.  */
    1913         [ #  # ]:           0 :   for (auto& p : m.callgraph)
    1914                 :             :     {
    1915                 :           0 :       unsigned char tag = GMON_TAG_CG_ARC;
    1916         [ #  # ]:           0 :       of.write(reinterpret_cast<const char *>(&tag), sizeof(tag));
    1917                 :             :       /* p is (from,to) -> count */
    1918                 :           0 :       if (wordsize == 4) {
    1919                 :             :         uint32_t addr = p.first.first;
    1920                 :             :         of.write(reinterpret_cast<const char *>(&addr), sizeof(addr));
    1921                 :             :         addr = p.first.second;
    1922                 :             :         of.write(reinterpret_cast<const char *>(&addr), sizeof(addr));
    1923                 :             :       } else {
    1924                 :           0 :         uint64_t addr = p.first.first;
    1925         [ #  # ]:           0 :         of.write(reinterpret_cast<const char *>(&addr), sizeof(addr));
    1926                 :           0 :         addr = p.first.second;
    1927         [ #  # ]:           0 :         of.write(reinterpret_cast<const char *>(&addr), sizeof(addr));
    1928                 :             :       }
    1929                 :           0 :       uint32_t count = p.second;
    1930         [ #  # ]:           0 :       of.write(reinterpret_cast<const char *>(&count), sizeof(count));
    1931                 :             :     }
    1932                 :             : 
    1933         [ #  # ]:           0 :   of.close();
    1934                 :           0 : }
    1935                 :             : 
    1936                 :           0 : GprofUnwindSampleConsumer::~GprofUnwindSampleConsumer()
    1937                 :             : {
    1938         [ #  # ]:           0 :   if (show_summary)
    1939                 :             :     {
    1940                 :           0 :       this->stats->print_summary();
    1941                 :           0 :       clog << "=== buildid / sample counts ===\n";
    1942                 :             :     }
    1943                 :             : 
    1944                 :           0 :   UnwindStatsTable::buildid_map_t sorted_map(this->stats->buildid_tab.begin(), this->stats->buildid_tab.end());
    1945         [ #  # ]:           0 :   for (auto& p : sorted_map) // traverse in sorted order
    1946                 :             :     {
    1947                 :           0 :       const string& buildid = p.first;
    1948                 :           0 :       UnwindModuleStats& module_stats = p.second;
    1949                 :           0 :       this->record_gmon_out(buildid, module_stats);
    1950         [ #  # ]:           0 :       if (show_summary)
    1951                 :             :         {
    1952                 :             :           /* In record_gmon_out we will write the buildid->path mapping
    1953                 :             :              to a json metadata file.  That makes for a reasonable hint;
    1954                 :             :              debuginfod-find can be used as a mostly-functional fallback
    1955                 :             :              (for packaged rather than locally-built executables) if the
    1956                 :             :              results are moved to another system.  */
    1957                 :           0 :           string mainfile = "<unknown>";
    1958                 :           0 :           if (buildid_to_mainfile.count(buildid) != 0)
    1959                 :           0 :             mainfile = buildid_to_mainfile[buildid];
    1960                 :           0 :           string debugfile = "";
    1961                 :           0 :           if (buildid_to_debugfile.count(buildid) != 0)
    1962                 :           0 :             debugfile = buildid_to_debugfile[buildid];
    1963                 :           0 :           clog << format(N_("buildid {} ({}{}{}) -- received {} distinct pcs, {} callgraph arcs\n"), /* TODO also count samples / estimated histogram size? */
    1964                 :             :                          buildid,
    1965                 :             :                          mainfile,
    1966         [ #  # ]:           0 :                          debugfile.empty() ? "" : " +debugfile ",
    1967                 :             :                          debugfile,
    1968         [ #  # ]:           0 :                          module_stats.histogram.size(),
    1969         [ #  # ]:           0 :                          module_stats.callgraph.size());
    1970                 :           0 :         }
    1971                 :             :     }
    1972         [ #  # ]:           0 :   if (show_summary)
    1973                 :             :     {
    1974                 :           0 :       clog << "===\n";
    1975                 :           0 :       clog << format(N_("TOTAL -- received {} buildids\n"), this->stats->buildid_tab.size());
    1976                 :             :     }
    1977                 :           0 :   clog << "\n";
    1978                 :           0 : }
    1979                 :             : 
    1980                 :           0 : int GprofUnwindSampleConsumer::maxframes()
    1981                 :             : {
    1982                 :             :   // gprof only needs one level of backtracing,
    1983                 :             :   // but user can override consumer's preference
    1984                 :             :   // with --maxframes option:
    1985         [ #  # ]:           0 :   return opt_maxframes >= 0 ? opt_maxframes : 1;
    1986                 :             : }
    1987                 :             : 
    1988                 :           0 : void GprofUnwindSampleConsumer::process(const UnwindSample *sample)
    1989                 :             : {
    1990         [ #  # ]:           0 :   if (sample->addrs.size() < 1)
    1991                 :           0 :     return; // edge case -- no pc or callgraph arc
    1992                 :             : 
    1993                 :           0 :   Dwarf_Addr pc = sample->addrs[0];
    1994         [ #  # ]:           0 :   Dwarf_Addr pc2 = sample->addrs.size() < 2 ? 0 : sample->addrs[1];
    1995                 :             : 
    1996                 :           0 :   Dwfl_Module *mod = dwfl_addrmodule(sample->dwfl, pc);
    1997         [ #  # ]:           0 :   if (mod == NULL)
    1998                 :             :     return;
    1999                 :             : 
    2000                 :           0 :   Dwfl_Module *mod2 = dwfl_addrmodule(sample->dwfl, pc2);
    2001                 :             :   // XXX: allowing mod2 == NULL -- callgraph arc will be skipped
    2002                 :             : 
    2003                 :             :   // extract buildid for pc (hit callee)
    2004                 :           0 :   const unsigned char *desc = nullptr;
    2005                 :           0 :   GElf_Addr vaddr;
    2006                 :           0 :   int build_id_len = dwfl_module_build_id(mod, &desc, &vaddr);
    2007         [ #  # ]:           0 :   if (build_id_len <= 0)
    2008                 :             :     return; // TODO: report/tabulate hit outside known modules
    2009                 :             : 
    2010                 :             :   // possible optimization would be to use the unconverted build_id_desc as hash key
    2011                 :           0 :   string buildid;
    2012         [ #  # ]:           0 :   for (int i = 0; i < build_id_len; ++i) {
    2013         [ #  # ]:           0 :     buildid += format("{:02x}", static_cast<int>(desc[i]));
    2014                 :             :   }
    2015                 :             : 
    2016                 :           0 :   const char *mainfile_cstr;
    2017                 :           0 :   const char *debugfile_cstr;
    2018                 :           0 :   Dwarf_Addr low_addr;
    2019                 :           0 :   Dwarf_Addr high_addr;
    2020         [ #  # ]:           0 :   dwfl_module_info(mod, NULL, &low_addr, &high_addr, NULL,
    2021                 :             :                    NULL, &mainfile_cstr, &debugfile_cstr);
    2022   [ #  #  #  # ]:           0 :   string mainfile = mainfile_cstr ? mainfile_cstr : "<unknown>";
    2023   [ #  #  #  # ]:           0 :   string debugfile = debugfile_cstr ? debugfile_cstr : "";
    2024                 :           0 :   if (!buildid_to_mainfile.count(buildid))
    2025   [ #  #  #  # ]:           0 :     buildid_to_mainfile[buildid] = mainfile;
    2026                 :           0 :   if (!buildid_to_debugfile.count(buildid))
    2027   [ #  #  #  # ]:           0 :     buildid_to_debugfile[buildid] = debugfile;
    2028                 :             :   // TODO: Also monitor for collisions here.
    2029                 :             : 
    2030   [ #  #  #  # ]:           0 :   UnwindModuleStats *buildid_ent = this->stats->buildid_find_or_create(buildid, mod);
    2031                 :             : 
    2032                 :           0 :   uint64_t last_pc = pc;
    2033         [ #  # ]:           0 :   int i = dwfl_module_relocate_address(mod, &pc);
    2034                 :             :   /* XXX: Out-of-range address seen with ld-linux.so, not useful for profiledb purposes:  */
    2035   [ #  #  #  # ]:           0 :   if (last_pc < low_addr || last_pc > high_addr)
    2036                 :             :     {
    2037         [ #  # ]:           0 :       if (verbose)
    2038         [ #  # ]:           0 :         clog << format(N_("{}: Skipping pc={:x} raw_pc={:x} outside module range start={:x}..end={:x}\n"),
    2039                 :           0 :                        mainfile, pc, last_pc, low_addr, high_addr);
    2040                 :           0 :       return;
    2041                 :             :     }
    2042                 :           0 :   (void) i;
    2043                 :             :   // XXX: could get dwfl_module_relocation_info(mod, i, NULL), but no need?
    2044         [ #  # ]:           0 :   buildid_ent->record_pc(pc);
    2045                 :             : 
    2046                 :             :   // If caller & callee are in different modules, this is a cross-shared-library
    2047                 :             :   // call, so we can't track it as a call-graph arc.  TODO: at least count them
    2048   [ #  #  #  # ]:           0 :   if (sample->addrs.size() >= 2 && mod == mod2) // intra-module call
    2049                 :             :     {
    2050                 :           0 :       last_pc = pc2;
    2051         [ #  # ]:           0 :       int j = dwfl_module_relocate_address(mod, &pc2); // map pc2 also
    2052   [ #  #  #  # ]:           0 :       if (last_pc < low_addr || last_pc > high_addr)
    2053                 :             :         {
    2054         [ #  # ]:           0 :           if (verbose)
    2055         [ #  # ]:           0 :             clog << format(N_("{}: Skipping pc={:x} raw_pc={:x} outside module range start={:x}..end={:x}\n"),
    2056                 :           0 :                            mainfile, pc2, last_pc, low_addr, high_addr);
    2057                 :           0 :           return;
    2058                 :             :         }
    2059                 :           0 :       (void) j;
    2060         [ #  # ]:           0 :       buildid_ent->record_callgraph_arc(pc2, pc);
    2061                 :             :     }
    2062                 :           0 : }
        

Generated by: LCOV version 2.0-1