1147708Sjkoshy/*- 2174396Sjkoshy * Copyright (c) 2005-2007, Joseph Koshy 3174396Sjkoshy * Copyright (c) 2007 The FreeBSD Foundation 4147708Sjkoshy * All rights reserved. 5147708Sjkoshy * 6174396Sjkoshy * Portions of this software were developed by A. Joseph Koshy under 7174396Sjkoshy * sponsorship from the FreeBSD Foundation and Google, Inc. 8174396Sjkoshy * 9147708Sjkoshy * Redistribution and use in source and binary forms, with or without 10147708Sjkoshy * modification, are permitted provided that the following conditions 11147708Sjkoshy * are met: 12147708Sjkoshy * 1. Redistributions of source code must retain the above copyright 13147708Sjkoshy * notice, this list of conditions and the following disclaimer. 14147708Sjkoshy * 2. Redistributions in binary form must reproduce the above copyright 15147708Sjkoshy * notice, this list of conditions and the following disclaimer in the 16147708Sjkoshy * documentation and/or other materials provided with the distribution. 17147708Sjkoshy * 18147708Sjkoshy * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND 19147708Sjkoshy * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 20147708Sjkoshy * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 21147708Sjkoshy * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE 22147708Sjkoshy * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 23147708Sjkoshy * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 24147708Sjkoshy * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 25147708Sjkoshy * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 26147708Sjkoshy * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 27147708Sjkoshy * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 28147708Sjkoshy * SUCH DAMAGE. 29147708Sjkoshy */ 30147708Sjkoshy 31157144Sjkoshy/* 32157144Sjkoshy * Transform a hwpmc(4) log into human readable form, and into 33157144Sjkoshy * gprof(1) compatible profiles. 34157144Sjkoshy */ 35157144Sjkoshy 36147708Sjkoshy#include <sys/cdefs.h> 37147708Sjkoshy__FBSDID("$FreeBSD: releng/10.3/usr.sbin/pmcstat/pmcstat_log.c 283905 2015-06-02 08:03:28Z hiren $"); 38147708Sjkoshy 39147708Sjkoshy#include <sys/param.h> 40147708Sjkoshy#include <sys/endian.h> 41224698Sattilio#include <sys/cpuset.h> 42147708Sjkoshy#include <sys/gmon.h> 43147708Sjkoshy#include <sys/imgact_aout.h> 44147708Sjkoshy#include <sys/imgact_elf.h> 45147708Sjkoshy#include <sys/mman.h> 46147708Sjkoshy#include <sys/pmc.h> 47147708Sjkoshy#include <sys/queue.h> 48157406Sjkoshy#include <sys/socket.h> 49147708Sjkoshy#include <sys/stat.h> 50147708Sjkoshy#include <sys/wait.h> 51147708Sjkoshy 52147708Sjkoshy#include <netinet/in.h> 53147708Sjkoshy 54147708Sjkoshy#include <assert.h> 55203790Sfabient#include <curses.h> 56147708Sjkoshy#include <err.h> 57157406Sjkoshy#include <errno.h> 58147708Sjkoshy#include <fcntl.h> 59174396Sjkoshy#include <gelf.h> 60147708Sjkoshy#include <libgen.h> 61147708Sjkoshy#include <limits.h> 62157406Sjkoshy#include <netdb.h> 63147708Sjkoshy#include <pmc.h> 64147708Sjkoshy#include <pmclog.h> 65147708Sjkoshy#include <sysexits.h> 66147708Sjkoshy#include <stdint.h> 67147708Sjkoshy#include <stdio.h> 68147708Sjkoshy#include <stdlib.h> 69147708Sjkoshy#include <string.h> 70147708Sjkoshy#include <unistd.h> 71147708Sjkoshy 72147708Sjkoshy#include "pmcstat.h" 73203790Sfabient#include "pmcstat_log.h" 74203790Sfabient#include "pmcstat_top.h" 75147708Sjkoshy 76174396Sjkoshy#define PMCSTAT_ALLOCATE 1 77174396Sjkoshy 78147708Sjkoshy/* 79157144Sjkoshy * PUBLIC INTERFACES 80157144Sjkoshy * 81157144Sjkoshy * pmcstat_initialize_logging() initialize this module, called first 82157144Sjkoshy * pmcstat_shutdown_logging() orderly shutdown, called last 83157144Sjkoshy * pmcstat_open_log() open an eventlog for processing 84157144Sjkoshy * pmcstat_process_log() print/convert an event log 85203790Sfabient * pmcstat_display_log() top mode display for the log 86157144Sjkoshy * pmcstat_close_log() finish processing an event log 87157144Sjkoshy * 88174396Sjkoshy * IMPLEMENTATION NOTES 89157144Sjkoshy * 90174396Sjkoshy * We correlate each 'callchain' or 'sample' entry seen in the event 91174396Sjkoshy * log back to an executable object in the system. Executable objects 92174396Sjkoshy * include: 93157144Sjkoshy * - program executables, 94157144Sjkoshy * - shared libraries loaded by the runtime loader, 95157144Sjkoshy * - dlopen()'ed objects loaded by the program, 96157144Sjkoshy * - the runtime loader itself, 97157144Sjkoshy * - the kernel and kernel modules. 98157144Sjkoshy * 99157144Sjkoshy * Each process that we know about is treated as a set of regions that 100157144Sjkoshy * map to executable objects. Processes are described by 101157144Sjkoshy * 'pmcstat_process' structures. Executable objects are tracked by 102157144Sjkoshy * 'pmcstat_image' structures. The kernel and kernel modules are 103157144Sjkoshy * common to all processes (they reside at the same virtual addresses 104157144Sjkoshy * for all processes). Individual processes can have their text 105157144Sjkoshy * segments and shared libraries loaded at process-specific locations. 106157144Sjkoshy * 107157144Sjkoshy * A given executable object can be in use by multiple processes 108157144Sjkoshy * (e.g., libc.so) and loaded at a different address in each. 109157144Sjkoshy * pmcstat_pcmap structures track per-image mappings. 110157144Sjkoshy * 111157144Sjkoshy * The sample log could have samples from multiple PMCs; we 112157144Sjkoshy * generate one 'gmon.out' profile per PMC. 113174396Sjkoshy * 114174396Sjkoshy * IMPLEMENTATION OF GMON OUTPUT 115174396Sjkoshy * 116174396Sjkoshy * Each executable object gets one 'gmon.out' profile, per PMC in 117174396Sjkoshy * use. Creation of 'gmon.out' profiles is done lazily. The 118174396Sjkoshy * 'gmon.out' profiles generated for a given sampling PMC are 119174396Sjkoshy * aggregates of all the samples for that particular executable 120174396Sjkoshy * object. 121174396Sjkoshy * 122174396Sjkoshy * IMPLEMENTATION OF SYSTEM-WIDE CALLGRAPH OUTPUT 123174396Sjkoshy * 124174396Sjkoshy * Each active pmcid has its own callgraph structure, described by a 125174396Sjkoshy * 'struct pmcstat_callgraph'. Given a process id and a list of pc 126174396Sjkoshy * values, we map each pc value to a tuple (image, symbol), where 127174396Sjkoshy * 'image' denotes an executable object and 'symbol' is the closest 128174396Sjkoshy * symbol that precedes the pc value. Each pc value in the list is 129174396Sjkoshy * also given a 'rank' that reflects its depth in the call stack. 130147708Sjkoshy */ 131147708Sjkoshy 132203790Sfabientstruct pmcstat_pmcs pmcstat_pmcs = LIST_HEAD_INITIALIZER(pmcstat_pmcs); 133147708Sjkoshy 134147708Sjkoshy/* 135203790Sfabient * All image descriptors are kept in a hash table. 136147708Sjkoshy */ 137203790Sfabientstruct pmcstat_image_hash_list pmcstat_image_hash[PMCSTAT_NHASH]; 138147708Sjkoshy 139150069Sjkoshy/* 140203790Sfabient * All process descriptors are kept in a hash table. 141150069Sjkoshy */ 142203790Sfabientstruct pmcstat_process_hash_list pmcstat_process_hash[PMCSTAT_NHASH]; 143150069Sjkoshy 144203790Sfabientstruct pmcstat_stats pmcstat_stats; /* statistics */ 145241737Sedstatic int ps_samples_period; /* samples count between top refresh. */ 146147708Sjkoshy 147203790Sfabientstruct pmcstat_process *pmcstat_kernproc; /* kernel 'process' */ 148147708Sjkoshy 149203790Sfabient#include "pmcpl_gprof.h" 150203790Sfabient#include "pmcpl_callgraph.h" 151203790Sfabient#include "pmcpl_annotate.h" 152265604Sscottl#include "pmcpl_annotate_cg.h" 153203790Sfabient#include "pmcpl_calltree.h" 154147708Sjkoshy 155241737Sedstatic struct pmc_plugins { 156203790Sfabient const char *pl_name; /* name */ 157147708Sjkoshy 158203790Sfabient /* configure */ 159203790Sfabient int (*pl_configure)(char *opt); 160157144Sjkoshy 161203790Sfabient /* init and shutdown */ 162203790Sfabient int (*pl_init)(void); 163203790Sfabient void (*pl_shutdown)(FILE *mf); 164147708Sjkoshy 165203790Sfabient /* sample processing */ 166203790Sfabient void (*pl_process)(struct pmcstat_process *pp, 167203790Sfabient struct pmcstat_pmcrecord *pmcr, uint32_t nsamples, 168203790Sfabient uintfptr_t *cc, int usermode, uint32_t cpu); 169174396Sjkoshy 170203790Sfabient /* image */ 171203790Sfabient void (*pl_initimage)(struct pmcstat_image *pi); 172203790Sfabient void (*pl_shutdownimage)(struct pmcstat_image *pi); 173147708Sjkoshy 174203790Sfabient /* pmc */ 175203790Sfabient void (*pl_newpmc)(pmcstat_interned_string ps, 176203790Sfabient struct pmcstat_pmcrecord *pr); 177203790Sfabient 178203790Sfabient /* top display */ 179203790Sfabient void (*pl_topdisplay)(void); 180147708Sjkoshy 181203790Sfabient /* top keypress */ 182203790Sfabient int (*pl_topkeypress)(int c, WINDOW *w); 183147708Sjkoshy 184203790Sfabient} plugins[] = { 185203790Sfabient { 186203790Sfabient .pl_name = "none", 187203790Sfabient }, 188203790Sfabient { 189203790Sfabient .pl_name = "callgraph", 190203790Sfabient .pl_init = pmcpl_cg_init, 191203790Sfabient .pl_shutdown = pmcpl_cg_shutdown, 192203790Sfabient .pl_process = pmcpl_cg_process, 193203790Sfabient .pl_topkeypress = pmcpl_cg_topkeypress, 194203790Sfabient .pl_topdisplay = pmcpl_cg_topdisplay 195203790Sfabient }, 196203790Sfabient { 197203790Sfabient .pl_name = "gprof", 198203790Sfabient .pl_shutdown = pmcpl_gmon_shutdown, 199203790Sfabient .pl_process = pmcpl_gmon_process, 200203790Sfabient .pl_initimage = pmcpl_gmon_initimage, 201203790Sfabient .pl_shutdownimage = pmcpl_gmon_shutdownimage, 202203790Sfabient .pl_newpmc = pmcpl_gmon_newpmc 203203790Sfabient }, 204203790Sfabient { 205203790Sfabient .pl_name = "annotate", 206203790Sfabient .pl_process = pmcpl_annotate_process 207203790Sfabient }, 208203790Sfabient { 209203790Sfabient .pl_name = "calltree", 210203790Sfabient .pl_configure = pmcpl_ct_configure, 211203790Sfabient .pl_init = pmcpl_ct_init, 212203790Sfabient .pl_shutdown = pmcpl_ct_shutdown, 213203790Sfabient .pl_process = pmcpl_ct_process, 214203790Sfabient .pl_topkeypress = pmcpl_ct_topkeypress, 215203790Sfabient .pl_topdisplay = pmcpl_ct_topdisplay 216203790Sfabient }, 217203790Sfabient { 218265604Sscottl .pl_name = "annotate_cg", 219265604Sscottl .pl_process = pmcpl_annotate_cg_process 220265604Sscottl }, 221265604Sscottl 222265604Sscottl { 223203790Sfabient .pl_name = NULL 224203790Sfabient } 225147708Sjkoshy}; 226147708Sjkoshy 227241737Sedstatic int pmcstat_mergepmc; 228147708Sjkoshy 229203790Sfabientint pmcstat_pmcinfilter = 0; /* PMC filter for top mode. */ 230203790Sfabientfloat pmcstat_threshold = 0.5; /* Cost filter for top mode. */ 231147708Sjkoshy 232174396Sjkoshy/* 233147708Sjkoshy * Prototypes 234147708Sjkoshy */ 235147708Sjkoshy 236157144Sjkoshystatic struct pmcstat_image *pmcstat_image_from_path(pmcstat_interned_string 237157144Sjkoshy _path, int _iskernelmodule); 238203790Sfabientstatic void pmcstat_image_get_aout_params(struct pmcstat_image *_image); 239203790Sfabientstatic void pmcstat_image_get_elf_params(struct pmcstat_image *_image); 240147708Sjkoshystatic void pmcstat_image_link(struct pmcstat_process *_pp, 241157144Sjkoshy struct pmcstat_image *_i, uintfptr_t _lpc); 242147708Sjkoshy 243157144Sjkoshystatic void pmcstat_pmcid_add(pmc_id_t _pmcid, 244203790Sfabient pmcstat_interned_string _name); 245147708Sjkoshy 246157144Sjkoshystatic void pmcstat_process_aout_exec(struct pmcstat_process *_pp, 247203790Sfabient struct pmcstat_image *_image, uintfptr_t _entryaddr); 248157144Sjkoshystatic void pmcstat_process_elf_exec(struct pmcstat_process *_pp, 249203790Sfabient struct pmcstat_image *_image, uintfptr_t _entryaddr); 250150139Sjkoshystatic void pmcstat_process_exec(struct pmcstat_process *_pp, 251203790Sfabient pmcstat_interned_string _path, uintfptr_t _entryaddr); 252157144Sjkoshystatic struct pmcstat_process *pmcstat_process_lookup(pid_t _pid, 253157144Sjkoshy int _allocate); 254147708Sjkoshystatic int pmcstat_string_compute_hash(const char *_string); 255157144Sjkoshystatic void pmcstat_string_initialize(void); 256157144Sjkoshystatic int pmcstat_string_lookup_hash(pmcstat_interned_string _is); 257157144Sjkoshystatic void pmcstat_string_shutdown(void); 258210794Sfabientstatic void pmcstat_stats_reset(int _reset_global); 259147708Sjkoshy 260147708Sjkoshy/* 261157144Sjkoshy * A simple implementation of interned strings. Each interned string 262157144Sjkoshy * is assigned a unique address, so that subsequent string compares 263228990Suqs * can be done by a simple pointer comparison instead of using 264157144Sjkoshy * strcmp(). This speeds up hash table lookups and saves memory if 265157144Sjkoshy * duplicate strings are the norm. 266157144Sjkoshy */ 267157144Sjkoshystruct pmcstat_string { 268157144Sjkoshy LIST_ENTRY(pmcstat_string) ps_next; /* hash link */ 269157144Sjkoshy int ps_len; 270157144Sjkoshy int ps_hash; 271157144Sjkoshy char *ps_string; 272157144Sjkoshy}; 273157144Sjkoshy 274157144Sjkoshystatic LIST_HEAD(,pmcstat_string) pmcstat_string_hash[PMCSTAT_NHASH]; 275157144Sjkoshy 276157144Sjkoshy/* 277203790Sfabient * PMC count. 278203790Sfabient */ 279203790Sfabientint pmcstat_npmcs; 280203790Sfabient 281203790Sfabient/* 282203790Sfabient * PMC Top mode pause state. 283203790Sfabient */ 284241737Sedstatic int pmcstat_pause; 285203790Sfabient 286206090Sfabientstatic void 287210794Sfabientpmcstat_stats_reset(int reset_global) 288206090Sfabient{ 289206090Sfabient struct pmcstat_pmcrecord *pr; 290206090Sfabient 291206090Sfabient /* Flush PMCs stats. */ 292206090Sfabient LIST_FOREACH(pr, &pmcstat_pmcs, pr_next) { 293206090Sfabient pr->pr_samples = 0; 294206090Sfabient pr->pr_dubious_frames = 0; 295206090Sfabient } 296210794Sfabient ps_samples_period = 0; 297206090Sfabient 298206090Sfabient /* Flush global stats. */ 299210794Sfabient if (reset_global) 300210794Sfabient bzero(&pmcstat_stats, sizeof(struct pmcstat_stats)); 301206090Sfabient} 302206090Sfabient 303203790Sfabient/* 304157144Sjkoshy * Compute a 'hash' value for a string. 305157144Sjkoshy */ 306157144Sjkoshy 307157144Sjkoshystatic int 308157144Sjkoshypmcstat_string_compute_hash(const char *s) 309157144Sjkoshy{ 310266590Semaste unsigned hash; 311157144Sjkoshy 312266590Semaste for (hash = 2166136261; *s; s++) 313266590Semaste hash = (hash ^ *s) * 16777619; 314157144Sjkoshy 315157144Sjkoshy return (hash & PMCSTAT_HASH_MASK); 316157144Sjkoshy} 317157144Sjkoshy 318157144Sjkoshy/* 319157144Sjkoshy * Intern a copy of string 's', and return a pointer to the 320157144Sjkoshy * interned structure. 321157144Sjkoshy */ 322157144Sjkoshy 323203790Sfabientpmcstat_interned_string 324157144Sjkoshypmcstat_string_intern(const char *s) 325157144Sjkoshy{ 326157144Sjkoshy struct pmcstat_string *ps; 327157144Sjkoshy const struct pmcstat_string *cps; 328157144Sjkoshy int hash, len; 329157144Sjkoshy 330157144Sjkoshy if ((cps = pmcstat_string_lookup(s)) != NULL) 331157144Sjkoshy return (cps); 332157144Sjkoshy 333157144Sjkoshy hash = pmcstat_string_compute_hash(s); 334157144Sjkoshy len = strlen(s); 335157144Sjkoshy 336157144Sjkoshy if ((ps = malloc(sizeof(*ps))) == NULL) 337157144Sjkoshy err(EX_OSERR, "ERROR: Could not intern string"); 338157144Sjkoshy ps->ps_len = len; 339157144Sjkoshy ps->ps_hash = hash; 340157144Sjkoshy ps->ps_string = strdup(s); 341157144Sjkoshy LIST_INSERT_HEAD(&pmcstat_string_hash[hash], ps, ps_next); 342157144Sjkoshy return ((pmcstat_interned_string) ps); 343157144Sjkoshy} 344157144Sjkoshy 345203790Sfabientconst char * 346157144Sjkoshypmcstat_string_unintern(pmcstat_interned_string str) 347157144Sjkoshy{ 348157144Sjkoshy const char *s; 349157144Sjkoshy 350157144Sjkoshy s = ((const struct pmcstat_string *) str)->ps_string; 351157144Sjkoshy return (s); 352157144Sjkoshy} 353157144Sjkoshy 354203790Sfabientpmcstat_interned_string 355157144Sjkoshypmcstat_string_lookup(const char *s) 356157144Sjkoshy{ 357157144Sjkoshy struct pmcstat_string *ps; 358157144Sjkoshy int hash, len; 359157144Sjkoshy 360157144Sjkoshy hash = pmcstat_string_compute_hash(s); 361157144Sjkoshy len = strlen(s); 362157144Sjkoshy 363157144Sjkoshy LIST_FOREACH(ps, &pmcstat_string_hash[hash], ps_next) 364157144Sjkoshy if (ps->ps_len == len && ps->ps_hash == hash && 365157144Sjkoshy strcmp(ps->ps_string, s) == 0) 366157144Sjkoshy return (ps); 367157144Sjkoshy return (NULL); 368157144Sjkoshy} 369157144Sjkoshy 370157144Sjkoshystatic int 371157144Sjkoshypmcstat_string_lookup_hash(pmcstat_interned_string s) 372157144Sjkoshy{ 373157144Sjkoshy const struct pmcstat_string *ps; 374157144Sjkoshy 375157144Sjkoshy ps = (const struct pmcstat_string *) s; 376157144Sjkoshy return (ps->ps_hash); 377157144Sjkoshy} 378157144Sjkoshy 379157144Sjkoshy/* 380157144Sjkoshy * Initialize the string interning facility. 381157144Sjkoshy */ 382157144Sjkoshy 383157144Sjkoshystatic void 384157144Sjkoshypmcstat_string_initialize(void) 385157144Sjkoshy{ 386157144Sjkoshy int i; 387157144Sjkoshy 388157144Sjkoshy for (i = 0; i < PMCSTAT_NHASH; i++) 389157144Sjkoshy LIST_INIT(&pmcstat_string_hash[i]); 390157144Sjkoshy} 391157144Sjkoshy 392157144Sjkoshy/* 393157144Sjkoshy * Destroy the string table, free'ing up space. 394157144Sjkoshy */ 395157144Sjkoshy 396157144Sjkoshystatic void 397157144Sjkoshypmcstat_string_shutdown(void) 398157144Sjkoshy{ 399157144Sjkoshy int i; 400157144Sjkoshy struct pmcstat_string *ps, *pstmp; 401157144Sjkoshy 402157144Sjkoshy for (i = 0; i < PMCSTAT_NHASH; i++) 403157144Sjkoshy LIST_FOREACH_SAFE(ps, &pmcstat_string_hash[i], ps_next, 404157144Sjkoshy pstmp) { 405157144Sjkoshy LIST_REMOVE(ps, ps_next); 406157144Sjkoshy free(ps->ps_string); 407157144Sjkoshy free(ps); 408157144Sjkoshy } 409157144Sjkoshy} 410157144Sjkoshy 411157144Sjkoshy/* 412157144Sjkoshy * Determine whether a given executable image is an A.OUT object, and 413157144Sjkoshy * if so, fill in its parameters from the text file. 414157144Sjkoshy * Sets image->pi_type. 415157144Sjkoshy */ 416157144Sjkoshy 417147708Sjkoshystatic void 418203790Sfabientpmcstat_image_get_aout_params(struct pmcstat_image *image) 419147708Sjkoshy{ 420157144Sjkoshy int fd; 421157144Sjkoshy ssize_t nbytes; 422157144Sjkoshy struct exec ex; 423157144Sjkoshy const char *path; 424157144Sjkoshy char buffer[PATH_MAX]; 425157144Sjkoshy 426157144Sjkoshy path = pmcstat_string_unintern(image->pi_execpath); 427157144Sjkoshy assert(path != NULL); 428157144Sjkoshy 429157144Sjkoshy if (image->pi_iskernelmodule) 430227524Sobrien errx(EX_SOFTWARE, 431227524Sobrien "ERROR: a.out kernel modules are unsupported \"%s\"", path); 432157144Sjkoshy 433157144Sjkoshy (void) snprintf(buffer, sizeof(buffer), "%s%s", 434203790Sfabient args.pa_fsroot, path); 435157144Sjkoshy 436157144Sjkoshy if ((fd = open(buffer, O_RDONLY, 0)) < 0 || 437157144Sjkoshy (nbytes = read(fd, &ex, sizeof(ex))) < 0) { 438233611Sfabient if (args.pa_verbosity >= 2) 439233611Sfabient warn("WARNING: Cannot determine type of \"%s\"", 440233611Sfabient path); 441157144Sjkoshy image->pi_type = PMCSTAT_IMAGE_INDETERMINABLE; 442157144Sjkoshy if (fd != -1) 443157144Sjkoshy (void) close(fd); 444157144Sjkoshy return; 445157144Sjkoshy } 446157144Sjkoshy 447157144Sjkoshy (void) close(fd); 448157144Sjkoshy 449157144Sjkoshy if ((unsigned) nbytes != sizeof(ex) || 450157144Sjkoshy N_BADMAG(ex)) 451157144Sjkoshy return; 452157144Sjkoshy 453157144Sjkoshy image->pi_type = PMCSTAT_IMAGE_AOUT; 454157144Sjkoshy 455157144Sjkoshy /* TODO: the rest of a.out processing */ 456157144Sjkoshy 457157144Sjkoshy return; 458157144Sjkoshy} 459157144Sjkoshy 460157144Sjkoshy/* 461174396Sjkoshy * Helper function. 462174396Sjkoshy */ 463174396Sjkoshy 464174396Sjkoshystatic int 465174396Sjkoshypmcstat_symbol_compare(const void *a, const void *b) 466174396Sjkoshy{ 467174396Sjkoshy const struct pmcstat_symbol *sym1, *sym2; 468174396Sjkoshy 469174396Sjkoshy sym1 = (const struct pmcstat_symbol *) a; 470174396Sjkoshy sym2 = (const struct pmcstat_symbol *) b; 471174396Sjkoshy 472174396Sjkoshy if (sym1->ps_end <= sym2->ps_start) 473174396Sjkoshy return (-1); 474174396Sjkoshy if (sym1->ps_start >= sym2->ps_end) 475174396Sjkoshy return (1); 476174396Sjkoshy return (0); 477174396Sjkoshy} 478174396Sjkoshy 479174396Sjkoshy/* 480174396Sjkoshy * Map an address to a symbol in an image. 481174396Sjkoshy */ 482174396Sjkoshy 483203790Sfabientstruct pmcstat_symbol * 484174396Sjkoshypmcstat_symbol_search(struct pmcstat_image *image, uintfptr_t addr) 485174396Sjkoshy{ 486174396Sjkoshy struct pmcstat_symbol sym; 487174396Sjkoshy 488174396Sjkoshy if (image->pi_symbols == NULL) 489174396Sjkoshy return (NULL); 490174396Sjkoshy 491174396Sjkoshy sym.ps_name = NULL; 492174396Sjkoshy sym.ps_start = addr; 493174396Sjkoshy sym.ps_end = addr + 1; 494174396Sjkoshy 495174396Sjkoshy return (bsearch((void *) &sym, image->pi_symbols, 496174396Sjkoshy image->pi_symcount, sizeof(struct pmcstat_symbol), 497174396Sjkoshy pmcstat_symbol_compare)); 498174396Sjkoshy} 499174396Sjkoshy 500174396Sjkoshy/* 501174396Sjkoshy * Add the list of symbols in the given section to the list associated 502174396Sjkoshy * with the object. 503174396Sjkoshy */ 504174396Sjkoshystatic void 505174396Sjkoshypmcstat_image_add_symbols(struct pmcstat_image *image, Elf *e, 506174396Sjkoshy Elf_Scn *scn, GElf_Shdr *sh) 507174396Sjkoshy{ 508174396Sjkoshy int firsttime; 509174396Sjkoshy size_t n, newsyms, nshsyms, nfuncsyms; 510174396Sjkoshy struct pmcstat_symbol *symptr; 511174396Sjkoshy char *fnname; 512174396Sjkoshy GElf_Sym sym; 513174396Sjkoshy Elf_Data *data; 514174396Sjkoshy 515174396Sjkoshy if ((data = elf_getdata(scn, NULL)) == NULL) 516174396Sjkoshy return; 517174396Sjkoshy 518174396Sjkoshy /* 519174396Sjkoshy * Determine the number of functions named in this 520174396Sjkoshy * section. 521174396Sjkoshy */ 522174396Sjkoshy 523174396Sjkoshy nshsyms = sh->sh_size / sh->sh_entsize; 524174396Sjkoshy for (n = nfuncsyms = 0; n < nshsyms; n++) { 525174396Sjkoshy if (gelf_getsym(data, (int) n, &sym) != &sym) 526174396Sjkoshy return; 527174396Sjkoshy if (GELF_ST_TYPE(sym.st_info) == STT_FUNC) 528174396Sjkoshy nfuncsyms++; 529174396Sjkoshy } 530174396Sjkoshy 531174396Sjkoshy if (nfuncsyms == 0) 532174396Sjkoshy return; 533174396Sjkoshy 534174396Sjkoshy /* 535174396Sjkoshy * Allocate space for the new entries. 536174396Sjkoshy */ 537174396Sjkoshy firsttime = image->pi_symbols == NULL; 538174396Sjkoshy symptr = realloc(image->pi_symbols, 539174396Sjkoshy sizeof(*symptr) * (image->pi_symcount + nfuncsyms)); 540174396Sjkoshy if (symptr == image->pi_symbols) /* realloc() failed. */ 541174396Sjkoshy return; 542174396Sjkoshy image->pi_symbols = symptr; 543174396Sjkoshy 544174396Sjkoshy /* 545174396Sjkoshy * Append new symbols to the end of the current table. 546174396Sjkoshy */ 547174396Sjkoshy symptr += image->pi_symcount; 548174396Sjkoshy 549174396Sjkoshy for (n = newsyms = 0; n < nshsyms; n++) { 550174396Sjkoshy if (gelf_getsym(data, (int) n, &sym) != &sym) 551174396Sjkoshy return; 552174396Sjkoshy if (GELF_ST_TYPE(sym.st_info) != STT_FUNC) 553174396Sjkoshy continue; 554207731Sfabient if (sym.st_shndx == STN_UNDEF) 555207731Sfabient continue; 556174396Sjkoshy 557174396Sjkoshy if (!firsttime && pmcstat_symbol_search(image, sym.st_value)) 558174396Sjkoshy continue; /* We've seen this symbol already. */ 559174396Sjkoshy 560174396Sjkoshy if ((fnname = elf_strptr(e, sh->sh_link, sym.st_name)) 561174396Sjkoshy == NULL) 562174396Sjkoshy continue; 563236669Sfabient#ifdef __arm__ 564236669Sfabient /* Remove spurious ARM function name. */ 565236669Sfabient if (fnname[0] == '$' && 566236669Sfabient (fnname[1] == 'a' || fnname[1] == 't' || 567236669Sfabient fnname[1] == 'd') && 568236669Sfabient fnname[2] == '\0') 569236669Sfabient continue; 570236669Sfabient#endif 571174396Sjkoshy 572174396Sjkoshy symptr->ps_name = pmcstat_string_intern(fnname); 573174396Sjkoshy symptr->ps_start = sym.st_value - image->pi_vaddr; 574174396Sjkoshy symptr->ps_end = symptr->ps_start + sym.st_size; 575174396Sjkoshy symptr++; 576174396Sjkoshy 577174396Sjkoshy newsyms++; 578174396Sjkoshy } 579174396Sjkoshy 580174396Sjkoshy image->pi_symcount += newsyms; 581236634Sglebius if (image->pi_symcount == 0) 582236634Sglebius return; 583174396Sjkoshy 584174396Sjkoshy assert(newsyms <= nfuncsyms); 585174396Sjkoshy 586174396Sjkoshy /* 587174396Sjkoshy * Return space to the system if there were duplicates. 588174396Sjkoshy */ 589174396Sjkoshy if (newsyms < nfuncsyms) 590174396Sjkoshy image->pi_symbols = realloc(image->pi_symbols, 591174396Sjkoshy sizeof(*symptr) * image->pi_symcount); 592174396Sjkoshy 593174396Sjkoshy /* 594174396Sjkoshy * Keep the list of symbols sorted. 595174396Sjkoshy */ 596174396Sjkoshy qsort(image->pi_symbols, image->pi_symcount, sizeof(*symptr), 597174396Sjkoshy pmcstat_symbol_compare); 598174396Sjkoshy 599174396Sjkoshy /* 600174396Sjkoshy * Deal with function symbols that have a size of 'zero' by 601174396Sjkoshy * making them extend to the next higher address. These 602174396Sjkoshy * symbols are usually defined in assembly code. 603174396Sjkoshy */ 604174396Sjkoshy for (symptr = image->pi_symbols; 605174396Sjkoshy symptr < image->pi_symbols + (image->pi_symcount - 1); 606174396Sjkoshy symptr++) 607174396Sjkoshy if (symptr->ps_start == symptr->ps_end) 608174396Sjkoshy symptr->ps_end = (symptr+1)->ps_start; 609174396Sjkoshy} 610174396Sjkoshy 611174396Sjkoshy/* 612157144Sjkoshy * Examine an ELF file to determine the size of its text segment. 613157144Sjkoshy * Sets image->pi_type if anything conclusive can be determined about 614157144Sjkoshy * this image. 615157144Sjkoshy */ 616157144Sjkoshy 617157144Sjkoshystatic void 618203790Sfabientpmcstat_image_get_elf_params(struct pmcstat_image *image) 619157144Sjkoshy{ 620174396Sjkoshy int fd; 621174396Sjkoshy size_t i, nph, nsh; 622174396Sjkoshy const char *path, *elfbase; 623203790Sfabient char *p, *endp; 624147708Sjkoshy uintfptr_t minva, maxva; 625174396Sjkoshy Elf *e; 626174396Sjkoshy Elf_Scn *scn; 627174396Sjkoshy GElf_Ehdr eh; 628174396Sjkoshy GElf_Phdr ph; 629174396Sjkoshy GElf_Shdr sh; 630157144Sjkoshy enum pmcstat_image_type image_type; 631210797Sfabient char buffer[PATH_MAX]; 632147708Sjkoshy 633150139Sjkoshy assert(image->pi_type == PMCSTAT_IMAGE_UNKNOWN); 634150139Sjkoshy 635174396Sjkoshy image->pi_start = minva = ~(uintfptr_t) 0; 636174396Sjkoshy image->pi_end = maxva = (uintfptr_t) 0; 637174396Sjkoshy image->pi_type = image_type = PMCSTAT_IMAGE_INDETERMINABLE; 638174396Sjkoshy image->pi_isdynamic = 0; 639174396Sjkoshy image->pi_dynlinkerpath = NULL; 640174396Sjkoshy image->pi_vaddr = 0; 641174396Sjkoshy 642157144Sjkoshy path = pmcstat_string_unintern(image->pi_execpath); 643157144Sjkoshy assert(path != NULL); 644147708Sjkoshy 645157144Sjkoshy /* 646157144Sjkoshy * Look for kernel modules under FSROOT/KERNELPATH/NAME, 647157144Sjkoshy * and user mode executable objects under FSROOT/PATHNAME. 648157144Sjkoshy */ 649157144Sjkoshy if (image->pi_iskernelmodule) 650157144Sjkoshy (void) snprintf(buffer, sizeof(buffer), "%s%s/%s", 651203790Sfabient args.pa_fsroot, args.pa_kernel, path); 652157144Sjkoshy else 653157144Sjkoshy (void) snprintf(buffer, sizeof(buffer), "%s%s", 654203790Sfabient args.pa_fsroot, path); 655147708Sjkoshy 656174396Sjkoshy e = NULL; 657157144Sjkoshy if ((fd = open(buffer, O_RDONLY, 0)) < 0 || 658174396Sjkoshy (e = elf_begin(fd, ELF_C_READ, NULL)) == NULL || 659174396Sjkoshy (elf_kind(e) != ELF_K_ELF)) { 660233611Sfabient if (args.pa_verbosity >= 2) 661233611Sfabient warnx("WARNING: Cannot determine the type of \"%s\".", 662233611Sfabient buffer); 663174396Sjkoshy goto done; 664157144Sjkoshy } 665147708Sjkoshy 666174396Sjkoshy if (gelf_getehdr(e, &eh) != &eh) { 667227524Sobrien warnx( 668227524Sobrien "WARNING: Cannot retrieve the ELF Header for \"%s\": %s.", 669227524Sobrien buffer, elf_errmsg(-1)); 670174396Sjkoshy goto done; 671174396Sjkoshy } 672147708Sjkoshy 673174396Sjkoshy if (eh.e_type != ET_EXEC && eh.e_type != ET_DYN && 674174396Sjkoshy !(image->pi_iskernelmodule && eh.e_type == ET_REL)) { 675174396Sjkoshy warnx("WARNING: \"%s\" is of an unsupported ELF type.", 676174396Sjkoshy buffer); 677174396Sjkoshy goto done; 678174396Sjkoshy } 679147708Sjkoshy 680174396Sjkoshy image_type = eh.e_ident[EI_CLASS] == ELFCLASS32 ? 681174396Sjkoshy PMCSTAT_IMAGE_ELF32 : PMCSTAT_IMAGE_ELF64; 682174396Sjkoshy 683157144Sjkoshy /* 684174396Sjkoshy * Determine the virtual address where an executable would be 685174396Sjkoshy * loaded. Additionally, for dynamically linked executables, 686174396Sjkoshy * save the pathname to the runtime linker. 687157144Sjkoshy */ 688174396Sjkoshy if (eh.e_type == ET_EXEC) { 689174396Sjkoshy if (elf_getphnum(e, &nph) == 0) { 690227524Sobrien warnx( 691227524Sobrien"WARNING: Could not determine the number of program headers in \"%s\": %s.", 692227524Sobrien buffer, 693174396Sjkoshy elf_errmsg(-1)); 694174396Sjkoshy goto done; 695147708Sjkoshy } 696174396Sjkoshy for (i = 0; i < eh.e_phnum; i++) { 697174396Sjkoshy if (gelf_getphdr(e, i, &ph) != &ph) { 698227524Sobrien warnx( 699227524Sobrien"WARNING: Retrieval of PHDR entry #%ju in \"%s\" failed: %s.", 700227524Sobrien (uintmax_t) i, buffer, elf_errmsg(-1)); 701174396Sjkoshy goto done; 702174396Sjkoshy } 703174396Sjkoshy switch (ph.p_type) { 704174396Sjkoshy case PT_DYNAMIC: 705174396Sjkoshy image->pi_isdynamic = 1; 706174396Sjkoshy break; 707174396Sjkoshy case PT_INTERP: 708174396Sjkoshy if ((elfbase = elf_rawfile(e, NULL)) == NULL) { 709227524Sobrien warnx( 710227524Sobrien"WARNING: Cannot retrieve the interpreter for \"%s\": %s.", 711174396Sjkoshy buffer, elf_errmsg(-1)); 712174396Sjkoshy goto done; 713174396Sjkoshy } 714174396Sjkoshy image->pi_dynlinkerpath = 715210797Sfabient pmcstat_string_intern(elfbase + 716210797Sfabient ph.p_offset); 717174396Sjkoshy break; 718174396Sjkoshy case PT_LOAD: 719283905Shiren if ((ph.p_flags & PF_X) != 0 && 720283905Shiren (ph.p_offset & (-ph.p_align)) == 0) 721233317Sgonzo image->pi_vaddr = ph.p_vaddr & (-ph.p_align); 722174396Sjkoshy break; 723174396Sjkoshy } 724174396Sjkoshy } 725174396Sjkoshy } 726151434Sjkoshy 727174396Sjkoshy /* 728174396Sjkoshy * Get the min and max VA associated with this ELF object. 729174396Sjkoshy */ 730174396Sjkoshy if (elf_getshnum(e, &nsh) == 0) { 731227524Sobrien warnx( 732227524Sobrien"WARNING: Could not determine the number of sections for \"%s\": %s.", 733227524Sobrien buffer, elf_errmsg(-1)); 734174396Sjkoshy goto done; 735174396Sjkoshy } 736151434Sjkoshy 737174396Sjkoshy for (i = 0; i < nsh; i++) { 738174396Sjkoshy if ((scn = elf_getscn(e, i)) == NULL || 739174396Sjkoshy gelf_getshdr(scn, &sh) != &sh) { 740227524Sobrien warnx( 741227524Sobrien"WARNING: Could not retrieve section header #%ju in \"%s\": %s.", 742227524Sobrien (uintmax_t) i, buffer, elf_errmsg(-1)); 743174396Sjkoshy goto done; 744151434Sjkoshy } 745174396Sjkoshy if (sh.sh_flags & SHF_EXECINSTR) { 746174396Sjkoshy minva = min(minva, sh.sh_addr); 747174396Sjkoshy maxva = max(maxva, sh.sh_addr + sh.sh_size); 748174396Sjkoshy } 749174396Sjkoshy if (sh.sh_type == SHT_SYMTAB || sh.sh_type == SHT_DYNSYM) 750174396Sjkoshy pmcstat_image_add_symbols(image, e, scn, &sh); 751147708Sjkoshy } 752147708Sjkoshy 753151434Sjkoshy image->pi_start = minva; 754157144Sjkoshy image->pi_end = maxva; 755157144Sjkoshy image->pi_type = image_type; 756174396Sjkoshy image->pi_fullpath = pmcstat_string_intern(buffer); 757151434Sjkoshy 758203790Sfabient /* Build display name 759203790Sfabient */ 760203790Sfabient endp = buffer; 761203790Sfabient for (p = buffer; *p; p++) 762203790Sfabient if (*p == '/') 763203790Sfabient endp = p+1; 764203790Sfabient image->pi_name = pmcstat_string_intern(endp); 765203790Sfabient 766174396Sjkoshy done: 767174396Sjkoshy (void) elf_end(e); 768174396Sjkoshy if (fd >= 0) 769174396Sjkoshy (void) close(fd); 770157144Sjkoshy return; 771157144Sjkoshy} 772147708Sjkoshy 773157144Sjkoshy/* 774157144Sjkoshy * Given an image descriptor, determine whether it is an ELF, or AOUT. 775157144Sjkoshy * If no handler claims the image, set its type to 'INDETERMINABLE'. 776157144Sjkoshy */ 777157144Sjkoshy 778203790Sfabientvoid 779203790Sfabientpmcstat_image_determine_type(struct pmcstat_image *image) 780157144Sjkoshy{ 781157144Sjkoshy assert(image->pi_type == PMCSTAT_IMAGE_UNKNOWN); 782157144Sjkoshy 783157144Sjkoshy /* Try each kind of handler in turn */ 784157144Sjkoshy if (image->pi_type == PMCSTAT_IMAGE_UNKNOWN) 785203790Sfabient pmcstat_image_get_elf_params(image); 786157144Sjkoshy if (image->pi_type == PMCSTAT_IMAGE_UNKNOWN) 787203790Sfabient pmcstat_image_get_aout_params(image); 788157144Sjkoshy 789157144Sjkoshy /* 790157144Sjkoshy * Otherwise, remember that we tried to determine 791157144Sjkoshy * the object's type and had failed. 792157144Sjkoshy */ 793157144Sjkoshy if (image->pi_type == PMCSTAT_IMAGE_UNKNOWN) 794157144Sjkoshy image->pi_type = PMCSTAT_IMAGE_INDETERMINABLE; 795147708Sjkoshy} 796147708Sjkoshy 797147708Sjkoshy/* 798150139Sjkoshy * Locate an image descriptor given an interned path, adding a fresh 799150139Sjkoshy * descriptor to the cache if necessary. This function also finds a 800150139Sjkoshy * suitable name for this image's sample file. 801157144Sjkoshy * 802157144Sjkoshy * We defer filling in the file format specific parts of the image 803157144Sjkoshy * structure till the time we actually see a sample that would fall 804157144Sjkoshy * into this image. 805147708Sjkoshy */ 806147708Sjkoshy 807147708Sjkoshystatic struct pmcstat_image * 808157144Sjkoshypmcstat_image_from_path(pmcstat_interned_string internedpath, 809157144Sjkoshy int iskernelmodule) 810147708Sjkoshy{ 811203790Sfabient int hash; 812147708Sjkoshy struct pmcstat_image *pi; 813147708Sjkoshy 814157144Sjkoshy hash = pmcstat_string_lookup_hash(internedpath); 815147708Sjkoshy 816157144Sjkoshy /* First, look for an existing entry. */ 817147708Sjkoshy LIST_FOREACH(pi, &pmcstat_image_hash[hash], pi_next) 818157144Sjkoshy if (pi->pi_execpath == internedpath && 819174396Sjkoshy pi->pi_iskernelmodule == iskernelmodule) 820157144Sjkoshy return (pi); 821147708Sjkoshy 822147708Sjkoshy /* 823174396Sjkoshy * Allocate a new entry and place it at the head of the hash 824174396Sjkoshy * and LRU lists. 825147708Sjkoshy */ 826147708Sjkoshy pi = malloc(sizeof(*pi)); 827147708Sjkoshy if (pi == NULL) 828157144Sjkoshy return (NULL); 829147708Sjkoshy 830147708Sjkoshy pi->pi_type = PMCSTAT_IMAGE_UNKNOWN; 831157144Sjkoshy pi->pi_execpath = internedpath; 832147708Sjkoshy pi->pi_start = ~0; 833147708Sjkoshy pi->pi_end = 0; 834174396Sjkoshy pi->pi_entry = 0; 835174396Sjkoshy pi->pi_vaddr = 0; 836174396Sjkoshy pi->pi_isdynamic = 0; 837157144Sjkoshy pi->pi_iskernelmodule = iskernelmodule; 838174396Sjkoshy pi->pi_dynlinkerpath = NULL; 839174396Sjkoshy pi->pi_symbols = NULL; 840174396Sjkoshy pi->pi_symcount = 0; 841203790Sfabient pi->pi_addr2line = NULL; 842147708Sjkoshy 843203790Sfabient if (plugins[args.pa_pplugin].pl_initimage != NULL) 844203790Sfabient plugins[args.pa_pplugin].pl_initimage(pi); 845203790Sfabient if (plugins[args.pa_plugin].pl_initimage != NULL) 846203790Sfabient plugins[args.pa_plugin].pl_initimage(pi); 847147708Sjkoshy 848147708Sjkoshy LIST_INSERT_HEAD(&pmcstat_image_hash[hash], pi, pi_next); 849147708Sjkoshy 850157144Sjkoshy return (pi); 851147708Sjkoshy} 852147708Sjkoshy 853147708Sjkoshy/* 854157144Sjkoshy * Record the fact that PC values from 'start' to 'end' come from 855147708Sjkoshy * image 'image'. 856147708Sjkoshy */ 857147708Sjkoshy 858147708Sjkoshystatic void 859147708Sjkoshypmcstat_image_link(struct pmcstat_process *pp, struct pmcstat_image *image, 860157144Sjkoshy uintfptr_t start) 861147708Sjkoshy{ 862147708Sjkoshy struct pmcstat_pcmap *pcm, *pcmnew; 863157144Sjkoshy uintfptr_t offset; 864147708Sjkoshy 865157144Sjkoshy assert(image->pi_type != PMCSTAT_IMAGE_UNKNOWN && 866157144Sjkoshy image->pi_type != PMCSTAT_IMAGE_INDETERMINABLE); 867157144Sjkoshy 868147708Sjkoshy if ((pcmnew = malloc(sizeof(*pcmnew))) == NULL) 869157144Sjkoshy err(EX_OSERR, "ERROR: Cannot create a map entry"); 870147708Sjkoshy 871157144Sjkoshy /* 872157144Sjkoshy * Adjust the map entry to only cover the text portion 873157144Sjkoshy * of the object. 874157144Sjkoshy */ 875157144Sjkoshy 876157144Sjkoshy offset = start - image->pi_vaddr; 877157144Sjkoshy pcmnew->ppm_lowpc = image->pi_start + offset; 878157144Sjkoshy pcmnew->ppm_highpc = image->pi_end + offset; 879147708Sjkoshy pcmnew->ppm_image = image; 880147708Sjkoshy 881157144Sjkoshy assert(pcmnew->ppm_lowpc < pcmnew->ppm_highpc); 882157144Sjkoshy 883157144Sjkoshy /* Overlapped mmap()'s are assumed to never occur. */ 884147708Sjkoshy TAILQ_FOREACH(pcm, &pp->pp_map, ppm_next) 885157144Sjkoshy if (pcm->ppm_lowpc >= pcmnew->ppm_highpc) 886147708Sjkoshy break; 887147708Sjkoshy 888147708Sjkoshy if (pcm == NULL) 889147708Sjkoshy TAILQ_INSERT_TAIL(&pp->pp_map, pcmnew, ppm_next); 890147708Sjkoshy else 891147708Sjkoshy TAILQ_INSERT_BEFORE(pcm, pcmnew, ppm_next); 892147708Sjkoshy} 893147708Sjkoshy 894147708Sjkoshy/* 895157144Sjkoshy * Unmap images in the range [start..end) associated with process 896157144Sjkoshy * 'pp'. 897157144Sjkoshy */ 898157144Sjkoshy 899157144Sjkoshystatic void 900157144Sjkoshypmcstat_image_unmap(struct pmcstat_process *pp, uintfptr_t start, 901157144Sjkoshy uintfptr_t end) 902157144Sjkoshy{ 903157144Sjkoshy struct pmcstat_pcmap *pcm, *pcmtmp, *pcmnew; 904157144Sjkoshy 905157144Sjkoshy assert(pp != NULL); 906157144Sjkoshy assert(start < end); 907157144Sjkoshy 908157144Sjkoshy /* 909157144Sjkoshy * Cases: 910157144Sjkoshy * - we could have the range completely in the middle of an 911157144Sjkoshy * existing pcmap; in this case we have to split the pcmap 912157144Sjkoshy * structure into two (i.e., generate a 'hole'). 913157144Sjkoshy * - we could have the range covering multiple pcmaps; these 914157144Sjkoshy * will have to be removed. 915157144Sjkoshy * - we could have either 'start' or 'end' falling in the 916157144Sjkoshy * middle of a pcmap; in this case shorten the entry. 917157144Sjkoshy */ 918157144Sjkoshy TAILQ_FOREACH_SAFE(pcm, &pp->pp_map, ppm_next, pcmtmp) { 919157144Sjkoshy assert(pcm->ppm_lowpc < pcm->ppm_highpc); 920157144Sjkoshy if (pcm->ppm_highpc <= start) 921157144Sjkoshy continue; 922174396Sjkoshy if (pcm->ppm_lowpc >= end) 923157144Sjkoshy return; 924157144Sjkoshy if (pcm->ppm_lowpc >= start && pcm->ppm_highpc <= end) { 925157144Sjkoshy /* 926157144Sjkoshy * The current pcmap is completely inside the 927157144Sjkoshy * unmapped range: remove it entirely. 928157144Sjkoshy */ 929157144Sjkoshy TAILQ_REMOVE(&pp->pp_map, pcm, ppm_next); 930157144Sjkoshy free(pcm); 931157144Sjkoshy } else if (pcm->ppm_lowpc < start && pcm->ppm_highpc > end) { 932157144Sjkoshy /* 933157144Sjkoshy * Split this pcmap into two; curtail the 934157144Sjkoshy * current map to end at [start-1], and start 935157144Sjkoshy * the new one at [end]. 936157144Sjkoshy */ 937157144Sjkoshy if ((pcmnew = malloc(sizeof(*pcmnew))) == NULL) 938227524Sobrien err(EX_OSERR, 939227524Sobrien "ERROR: Cannot split a map entry"); 940157144Sjkoshy 941157144Sjkoshy pcmnew->ppm_image = pcm->ppm_image; 942157144Sjkoshy 943157144Sjkoshy pcmnew->ppm_lowpc = end; 944157144Sjkoshy pcmnew->ppm_highpc = pcm->ppm_highpc; 945157144Sjkoshy 946157144Sjkoshy pcm->ppm_highpc = start; 947157144Sjkoshy 948157144Sjkoshy TAILQ_INSERT_AFTER(&pp->pp_map, pcm, pcmnew, ppm_next); 949157144Sjkoshy 950157144Sjkoshy return; 951174396Sjkoshy } else if (pcm->ppm_lowpc < start && pcm->ppm_highpc <= end) 952174396Sjkoshy pcm->ppm_highpc = start; 953174396Sjkoshy else if (pcm->ppm_lowpc >= start && pcm->ppm_highpc > end) 954174396Sjkoshy pcm->ppm_lowpc = end; 955157144Sjkoshy else 956157144Sjkoshy assert(0); 957157144Sjkoshy } 958157144Sjkoshy} 959157144Sjkoshy 960157144Sjkoshy/* 961203790Sfabient * Resolve file name and line number for the given address. 962203790Sfabient */ 963203790Sfabientint 964203790Sfabientpmcstat_image_addr2line(struct pmcstat_image *image, uintfptr_t addr, 965203790Sfabient char *sourcefile, size_t sourcefile_len, unsigned *sourceline, 966203790Sfabient char *funcname, size_t funcname_len) 967203790Sfabient{ 968203790Sfabient static int addr2line_warn = 0; 969233611Sfabient unsigned l; 970203790Sfabient 971203790Sfabient char *sep, cmdline[PATH_MAX], imagepath[PATH_MAX]; 972203790Sfabient int fd; 973203790Sfabient 974203790Sfabient if (image->pi_addr2line == NULL) { 975210794Sfabient snprintf(imagepath, sizeof(imagepath), "%s%s.symbols", 976210794Sfabient args.pa_fsroot, 977203790Sfabient pmcstat_string_unintern(image->pi_fullpath)); 978203790Sfabient fd = open(imagepath, O_RDONLY); 979203790Sfabient if (fd < 0) { 980210794Sfabient snprintf(imagepath, sizeof(imagepath), "%s%s", 981210794Sfabient args.pa_fsroot, 982203790Sfabient pmcstat_string_unintern(image->pi_fullpath)); 983203790Sfabient } else 984203790Sfabient close(fd); 985233611Sfabient /* 986233611Sfabient * New addr2line support recursive inline function with -i 987233611Sfabient * but the format does not add a marker when no more entries 988233611Sfabient * are available. 989233611Sfabient */ 990203790Sfabient snprintf(cmdline, sizeof(cmdline), "addr2line -Cfe \"%s\"", 991203790Sfabient imagepath); 992203790Sfabient image->pi_addr2line = popen(cmdline, "r+"); 993203790Sfabient if (image->pi_addr2line == NULL) { 994203790Sfabient if (!addr2line_warn) { 995203790Sfabient addr2line_warn = 1; 996227524Sobrien warnx( 997227524Sobrien"WARNING: addr2line is needed for source code information." 998227524Sobrien ); 999203790Sfabient } 1000203790Sfabient return (0); 1001203790Sfabient } 1002203790Sfabient } 1003203790Sfabient 1004203790Sfabient if (feof(image->pi_addr2line) || ferror(image->pi_addr2line)) { 1005203790Sfabient warnx("WARNING: addr2line pipe error"); 1006203790Sfabient pclose(image->pi_addr2line); 1007203790Sfabient image->pi_addr2line = NULL; 1008203790Sfabient return (0); 1009203790Sfabient } 1010203790Sfabient 1011203790Sfabient fprintf(image->pi_addr2line, "%p\n", (void *)addr); 1012203790Sfabient 1013203790Sfabient if (fgets(funcname, funcname_len, image->pi_addr2line) == NULL) { 1014203790Sfabient warnx("WARNING: addr2line function name read error"); 1015203790Sfabient return (0); 1016203790Sfabient } 1017203790Sfabient sep = strchr(funcname, '\n'); 1018203790Sfabient if (sep != NULL) 1019203790Sfabient *sep = '\0'; 1020203790Sfabient 1021203790Sfabient if (fgets(sourcefile, sourcefile_len, image->pi_addr2line) == NULL) { 1022203790Sfabient warnx("WARNING: addr2line source file read error"); 1023203790Sfabient return (0); 1024203790Sfabient } 1025203790Sfabient sep = strchr(sourcefile, ':'); 1026203790Sfabient if (sep == NULL) { 1027203790Sfabient warnx("WARNING: addr2line source line separator missing"); 1028203790Sfabient return (0); 1029203790Sfabient } 1030203790Sfabient *sep = '\0'; 1031233611Sfabient l = atoi(sep+1); 1032233611Sfabient if (l == 0) 1033203790Sfabient return (0); 1034233611Sfabient *sourceline = l; 1035203790Sfabient return (1); 1036203790Sfabient} 1037203790Sfabient 1038203790Sfabient/* 1039147708Sjkoshy * Add a {pmcid,name} mapping. 1040147708Sjkoshy */ 1041147708Sjkoshy 1042147708Sjkoshystatic void 1043203790Sfabientpmcstat_pmcid_add(pmc_id_t pmcid, pmcstat_interned_string ps) 1044147708Sjkoshy{ 1045203790Sfabient struct pmcstat_pmcrecord *pr, *prm; 1046147708Sjkoshy 1047174396Sjkoshy /* Replace an existing name for the PMC. */ 1048203790Sfabient prm = NULL; 1049147708Sjkoshy LIST_FOREACH(pr, &pmcstat_pmcs, pr_next) 1050203790Sfabient if (pr->pr_pmcid == pmcid) { 1051203790Sfabient pr->pr_pmcname = ps; 1052203790Sfabient return; 1053203790Sfabient } else if (pr->pr_pmcname == ps) 1054203790Sfabient prm = pr; 1055147708Sjkoshy 1056174396Sjkoshy /* 1057203790Sfabient * Otherwise, allocate a new descriptor and call the 1058203790Sfabient * plugins hook. 1059174396Sjkoshy */ 1060147708Sjkoshy if ((pr = malloc(sizeof(*pr))) == NULL) 1061147708Sjkoshy err(EX_OSERR, "ERROR: Cannot allocate pmc record"); 1062147708Sjkoshy 1063147708Sjkoshy pr->pr_pmcid = pmcid; 1064157144Sjkoshy pr->pr_pmcname = ps; 1065203790Sfabient pr->pr_pmcin = pmcstat_npmcs++; 1066206090Sfabient pr->pr_samples = 0; 1067206090Sfabient pr->pr_dubious_frames = 0; 1068203790Sfabient pr->pr_merge = prm == NULL ? pr : prm; 1069203790Sfabient 1070147708Sjkoshy LIST_INSERT_HEAD(&pmcstat_pmcs, pr, pr_next); 1071147708Sjkoshy 1072203790Sfabient if (plugins[args.pa_pplugin].pl_newpmc != NULL) 1073203790Sfabient plugins[args.pa_pplugin].pl_newpmc(ps, pr); 1074203790Sfabient if (plugins[args.pa_plugin].pl_newpmc != NULL) 1075203790Sfabient plugins[args.pa_plugin].pl_newpmc(ps, pr); 1076147708Sjkoshy} 1077147708Sjkoshy 1078147708Sjkoshy/* 1079157144Sjkoshy * Given a pmcid in use, find its human-readable name. 1080147708Sjkoshy */ 1081147708Sjkoshy 1082203790Sfabientconst char * 1083147708Sjkoshypmcstat_pmcid_to_name(pmc_id_t pmcid) 1084147708Sjkoshy{ 1085147708Sjkoshy struct pmcstat_pmcrecord *pr; 1086147708Sjkoshy 1087147708Sjkoshy LIST_FOREACH(pr, &pmcstat_pmcs, pr_next) 1088147708Sjkoshy if (pr->pr_pmcid == pmcid) 1089157144Sjkoshy return (pmcstat_string_unintern(pr->pr_pmcname)); 1090147708Sjkoshy 1091203790Sfabient return NULL; 1092203790Sfabient} 1093147708Sjkoshy 1094203790Sfabient/* 1095203790Sfabient * Convert PMC index to name. 1096203790Sfabient */ 1097147708Sjkoshy 1098203790Sfabientconst char * 1099203790Sfabientpmcstat_pmcindex_to_name(int pmcin) 1100203790Sfabient{ 1101203790Sfabient struct pmcstat_pmcrecord *pr; 1102147708Sjkoshy 1103203790Sfabient LIST_FOREACH(pr, &pmcstat_pmcs, pr_next) 1104203790Sfabient if (pr->pr_pmcin == pmcin) 1105203790Sfabient return pmcstat_string_unintern(pr->pr_pmcname); 1106203790Sfabient 1107203790Sfabient return NULL; 1108147708Sjkoshy} 1109147708Sjkoshy 1110147708Sjkoshy/* 1111203790Sfabient * Return PMC record with given index. 1112203790Sfabient */ 1113203790Sfabient 1114203790Sfabientstruct pmcstat_pmcrecord * 1115203790Sfabientpmcstat_pmcindex_to_pmcr(int pmcin) 1116203790Sfabient{ 1117203790Sfabient struct pmcstat_pmcrecord *pr; 1118203790Sfabient 1119203790Sfabient LIST_FOREACH(pr, &pmcstat_pmcs, pr_next) 1120203790Sfabient if (pr->pr_pmcin == pmcin) 1121203790Sfabient return pr; 1122203790Sfabient 1123203790Sfabient return NULL; 1124203790Sfabient} 1125203790Sfabient 1126203790Sfabient/* 1127203790Sfabient * Get PMC record by id, apply merge policy. 1128203790Sfabient */ 1129203790Sfabient 1130203790Sfabientstatic struct pmcstat_pmcrecord * 1131203790Sfabientpmcstat_lookup_pmcid(pmc_id_t pmcid) 1132203790Sfabient{ 1133203790Sfabient struct pmcstat_pmcrecord *pr; 1134203790Sfabient 1135203790Sfabient LIST_FOREACH(pr, &pmcstat_pmcs, pr_next) { 1136203790Sfabient if (pr->pr_pmcid == pmcid) { 1137203790Sfabient if (pmcstat_mergepmc) 1138203790Sfabient return pr->pr_merge; 1139203790Sfabient return pr; 1140203790Sfabient } 1141203790Sfabient } 1142203790Sfabient 1143203790Sfabient return NULL; 1144203790Sfabient} 1145203790Sfabient 1146203790Sfabient/* 1147157144Sjkoshy * Associate an AOUT image with a process. 1148147708Sjkoshy */ 1149147708Sjkoshy 1150147708Sjkoshystatic void 1151157144Sjkoshypmcstat_process_aout_exec(struct pmcstat_process *pp, 1152203790Sfabient struct pmcstat_image *image, uintfptr_t entryaddr) 1153147708Sjkoshy{ 1154157144Sjkoshy (void) pp; 1155157144Sjkoshy (void) image; 1156157144Sjkoshy (void) entryaddr; 1157157144Sjkoshy /* TODO Implement a.out handling */ 1158157144Sjkoshy} 1159157144Sjkoshy 1160157144Sjkoshy/* 1161157144Sjkoshy * Associate an ELF image with a process. 1162157144Sjkoshy */ 1163157144Sjkoshy 1164157144Sjkoshystatic void 1165157144Sjkoshypmcstat_process_elf_exec(struct pmcstat_process *pp, 1166203790Sfabient struct pmcstat_image *image, uintfptr_t entryaddr) 1167157144Sjkoshy{ 1168147708Sjkoshy uintmax_t libstart; 1169157144Sjkoshy struct pmcstat_image *rtldimage; 1170147708Sjkoshy 1171157144Sjkoshy assert(image->pi_type == PMCSTAT_IMAGE_ELF32 || 1172157144Sjkoshy image->pi_type == PMCSTAT_IMAGE_ELF64); 1173147708Sjkoshy 1174150139Sjkoshy /* Create a map entry for the base executable. */ 1175157144Sjkoshy pmcstat_image_link(pp, image, image->pi_vaddr); 1176147708Sjkoshy 1177150139Sjkoshy /* 1178174396Sjkoshy * For dynamically linked executables we need to determine 1179174396Sjkoshy * where the dynamic linker was mapped to for this process, 1180174396Sjkoshy * Subsequent executable objects that are mapped in by the 1181174396Sjkoshy * dynamic linker will be tracked by log events of type 1182174396Sjkoshy * PMCLOG_TYPE_MAP_IN. 1183150139Sjkoshy */ 1184157144Sjkoshy 1185150139Sjkoshy if (image->pi_isdynamic) { 1186147708Sjkoshy 1187150139Sjkoshy /* 1188150139Sjkoshy * The runtime loader gets loaded just after the maximum 1189150139Sjkoshy * possible heap address. Like so: 1190150139Sjkoshy * 1191150139Sjkoshy * [ TEXT DATA BSS HEAP -->*RTLD SHLIBS <--STACK] 1192150139Sjkoshy * ^ ^ 1193150139Sjkoshy * 0 VM_MAXUSER_ADDRESS 1194157144Sjkoshy 1195150139Sjkoshy * 1196150139Sjkoshy * The exact address where the loader gets mapped in 1197150139Sjkoshy * will vary according to the size of the executable 1198150139Sjkoshy * and the limits on the size of the process'es data 1199150139Sjkoshy * segment at the time of exec(). The entry address 1200150139Sjkoshy * recorded at process exec time corresponds to the 1201150139Sjkoshy * 'start' address inside the dynamic linker. From 1202150139Sjkoshy * this we can figure out the address where the 1203150139Sjkoshy * runtime loader's file object had been mapped to. 1204150139Sjkoshy */ 1205203790Sfabient rtldimage = pmcstat_image_from_path(image->pi_dynlinkerpath, 0); 1206157144Sjkoshy if (rtldimage == NULL) { 1207157144Sjkoshy warnx("WARNING: Cannot find image for \"%s\".", 1208157144Sjkoshy pmcstat_string_unintern(image->pi_dynlinkerpath)); 1209157144Sjkoshy pmcstat_stats.ps_exec_errors++; 1210157144Sjkoshy return; 1211157144Sjkoshy } 1212157144Sjkoshy 1213150139Sjkoshy if (rtldimage->pi_type == PMCSTAT_IMAGE_UNKNOWN) 1214203790Sfabient pmcstat_image_get_elf_params(rtldimage); 1215147708Sjkoshy 1216157144Sjkoshy if (rtldimage->pi_type != PMCSTAT_IMAGE_ELF32 && 1217157144Sjkoshy rtldimage->pi_type != PMCSTAT_IMAGE_ELF64) { 1218157144Sjkoshy warnx("WARNING: rtld not an ELF object \"%s\".", 1219157144Sjkoshy pmcstat_string_unintern(image->pi_dynlinkerpath)); 1220157144Sjkoshy return; 1221147708Sjkoshy } 1222147708Sjkoshy 1223157144Sjkoshy libstart = entryaddr - rtldimage->pi_entry; 1224157144Sjkoshy pmcstat_image_link(pp, rtldimage, libstart); 1225147708Sjkoshy } 1226147708Sjkoshy} 1227147708Sjkoshy 1228147708Sjkoshy/* 1229147708Sjkoshy * Find the process descriptor corresponding to a PID. If 'allocate' 1230147708Sjkoshy * is zero, we return a NULL if a pid descriptor could not be found or 1231147708Sjkoshy * a process descriptor process. If 'allocate' is non-zero, then we 1232147708Sjkoshy * will attempt to allocate a fresh process descriptor. Zombie 1233147708Sjkoshy * process descriptors are only removed if a fresh allocation for the 1234147708Sjkoshy * same PID is requested. 1235147708Sjkoshy */ 1236147708Sjkoshy 1237147708Sjkoshystatic struct pmcstat_process * 1238147708Sjkoshypmcstat_process_lookup(pid_t pid, int allocate) 1239147708Sjkoshy{ 1240147708Sjkoshy uint32_t hash; 1241147708Sjkoshy struct pmcstat_pcmap *ppm, *ppmtmp; 1242147708Sjkoshy struct pmcstat_process *pp, *pptmp; 1243147708Sjkoshy 1244147708Sjkoshy hash = (uint32_t) pid & PMCSTAT_HASH_MASK; /* simplicity wins */ 1245147708Sjkoshy 1246147708Sjkoshy LIST_FOREACH_SAFE(pp, &pmcstat_process_hash[hash], pp_next, pptmp) 1247227526Sobrien if (pp->pp_pid == pid) { 1248227526Sobrien /* Found a descriptor, check and process zombies */ 1249227526Sobrien if (allocate && pp->pp_isactive == 0) { 1250227526Sobrien /* remove maps */ 1251227526Sobrien TAILQ_FOREACH_SAFE(ppm, &pp->pp_map, ppm_next, 1252227526Sobrien ppmtmp) { 1253227526Sobrien TAILQ_REMOVE(&pp->pp_map, ppm, 1254227526Sobrien ppm_next); 1255227526Sobrien free(ppm); 1256227526Sobrien } 1257227526Sobrien /* remove process entry */ 1258227526Sobrien LIST_REMOVE(pp, pp_next); 1259227526Sobrien free(pp); 1260227526Sobrien break; 1261227526Sobrien } 1262227526Sobrien return (pp); 1263227526Sobrien } 1264147708Sjkoshy 1265147708Sjkoshy if (!allocate) 1266157144Sjkoshy return (NULL); 1267147708Sjkoshy 1268147708Sjkoshy if ((pp = malloc(sizeof(*pp))) == NULL) 1269147708Sjkoshy err(EX_OSERR, "ERROR: Cannot allocate pid descriptor"); 1270147708Sjkoshy 1271147708Sjkoshy pp->pp_pid = pid; 1272147708Sjkoshy pp->pp_isactive = 1; 1273147708Sjkoshy 1274147708Sjkoshy TAILQ_INIT(&pp->pp_map); 1275147708Sjkoshy 1276147708Sjkoshy LIST_INSERT_HEAD(&pmcstat_process_hash[hash], pp, pp_next); 1277157144Sjkoshy return (pp); 1278147708Sjkoshy} 1279147708Sjkoshy 1280147708Sjkoshy/* 1281147708Sjkoshy * Associate an image and a process. 1282147708Sjkoshy */ 1283147708Sjkoshy 1284147708Sjkoshystatic void 1285157144Sjkoshypmcstat_process_exec(struct pmcstat_process *pp, 1286203790Sfabient pmcstat_interned_string path, uintfptr_t entryaddr) 1287147708Sjkoshy{ 1288147708Sjkoshy struct pmcstat_image *image; 1289147708Sjkoshy 1290157144Sjkoshy if ((image = pmcstat_image_from_path(path, 0)) == NULL) { 1291157144Sjkoshy pmcstat_stats.ps_exec_errors++; 1292147708Sjkoshy return; 1293157144Sjkoshy } 1294147708Sjkoshy 1295147708Sjkoshy if (image->pi_type == PMCSTAT_IMAGE_UNKNOWN) 1296203790Sfabient pmcstat_image_determine_type(image); 1297147708Sjkoshy 1298157144Sjkoshy assert(image->pi_type != PMCSTAT_IMAGE_UNKNOWN); 1299157144Sjkoshy 1300157144Sjkoshy switch (image->pi_type) { 1301157144Sjkoshy case PMCSTAT_IMAGE_ELF32: 1302157144Sjkoshy case PMCSTAT_IMAGE_ELF64: 1303157144Sjkoshy pmcstat_stats.ps_exec_elf++; 1304203790Sfabient pmcstat_process_elf_exec(pp, image, entryaddr); 1305147708Sjkoshy break; 1306147708Sjkoshy 1307147708Sjkoshy case PMCSTAT_IMAGE_AOUT: 1308157144Sjkoshy pmcstat_stats.ps_exec_aout++; 1309203790Sfabient pmcstat_process_aout_exec(pp, image, entryaddr); 1310147708Sjkoshy break; 1311147708Sjkoshy 1312157144Sjkoshy case PMCSTAT_IMAGE_INDETERMINABLE: 1313157144Sjkoshy pmcstat_stats.ps_exec_indeterminable++; 1314157144Sjkoshy break; 1315157144Sjkoshy 1316147708Sjkoshy default: 1317227524Sobrien err(EX_SOFTWARE, 1318227524Sobrien "ERROR: Unsupported executable type for \"%s\"", 1319227524Sobrien pmcstat_string_unintern(path)); 1320147708Sjkoshy } 1321147708Sjkoshy} 1322147708Sjkoshy 1323147708Sjkoshy 1324150139Sjkoshy/* 1325150139Sjkoshy * Find the map entry associated with process 'p' at PC value 'pc'. 1326150139Sjkoshy */ 1327147708Sjkoshy 1328203790Sfabientstruct pmcstat_pcmap * 1329150139Sjkoshypmcstat_process_find_map(struct pmcstat_process *p, uintfptr_t pc) 1330150139Sjkoshy{ 1331150139Sjkoshy struct pmcstat_pcmap *ppm; 1332150139Sjkoshy 1333157144Sjkoshy TAILQ_FOREACH(ppm, &p->pp_map, ppm_next) { 1334157144Sjkoshy if (pc >= ppm->ppm_lowpc && pc < ppm->ppm_highpc) 1335157144Sjkoshy return (ppm); 1336157144Sjkoshy if (pc < ppm->ppm_lowpc) 1337157144Sjkoshy return (NULL); 1338157144Sjkoshy } 1339150139Sjkoshy 1340157144Sjkoshy return (NULL); 1341150139Sjkoshy} 1342150139Sjkoshy 1343174396Sjkoshy/* 1344174396Sjkoshy * Convert a hwpmc(4) log to profile information. A system-wide 1345174396Sjkoshy * callgraph is generated if FLAG_DO_CALLGRAPHS is set. gmon.out 1346174396Sjkoshy * files usable by gprof(1) are created if FLAG_DO_GPROF is set. 1347174396Sjkoshy */ 1348174396Sjkoshystatic int 1349203790Sfabientpmcstat_analyze_log(void) 1350174396Sjkoshy{ 1351174396Sjkoshy uint32_t cpu, cpuflags; 1352203790Sfabient uintfptr_t pc; 1353157144Sjkoshy pid_t pid; 1354157144Sjkoshy struct pmcstat_image *image; 1355147708Sjkoshy struct pmcstat_process *pp, *ppnew; 1356147708Sjkoshy struct pmcstat_pcmap *ppm, *ppmtmp; 1357147708Sjkoshy struct pmclog_ev ev; 1358203790Sfabient struct pmcstat_pmcrecord *pmcr; 1359157144Sjkoshy pmcstat_interned_string image_path; 1360147708Sjkoshy 1361203790Sfabient assert(args.pa_flags & FLAG_DO_ANALYSIS); 1362174396Sjkoshy 1363174396Sjkoshy if (elf_version(EV_CURRENT) == EV_NONE) 1364174396Sjkoshy err(EX_UNAVAILABLE, "Elf library intialization failed"); 1365174396Sjkoshy 1366203790Sfabient while (pmclog_read(args.pa_logparser, &ev) == 0) { 1367147708Sjkoshy assert(ev.pl_state == PMCLOG_OK); 1368147708Sjkoshy 1369147708Sjkoshy switch (ev.pl_type) { 1370157144Sjkoshy case PMCLOG_TYPE_INITIALIZE: 1371157144Sjkoshy if ((ev.pl_u.pl_i.pl_version & 0xFF000000) != 1372203790Sfabient PMC_VERSION_MAJOR << 24 && args.pa_verbosity > 0) 1373227524Sobrien warnx( 1374227524Sobrien"WARNING: Log version 0x%x does not match compiled version 0x%x.", 1375227524Sobrien ev.pl_u.pl_i.pl_version, PMC_VERSION_MAJOR); 1376157144Sjkoshy break; 1377174396Sjkoshy 1378157144Sjkoshy case PMCLOG_TYPE_MAP_IN: 1379147708Sjkoshy /* 1380147708Sjkoshy * Introduce an address range mapping for a 1381157144Sjkoshy * userland process or the kernel (pid == -1). 1382157144Sjkoshy * 1383157144Sjkoshy * We always allocate a process descriptor so 1384157144Sjkoshy * that subsequent samples seen for this 1385157144Sjkoshy * address range are mapped to the current 1386157144Sjkoshy * object being mapped in. 1387147708Sjkoshy */ 1388157144Sjkoshy pid = ev.pl_u.pl_mi.pl_pid; 1389157144Sjkoshy if (pid == -1) 1390157144Sjkoshy pp = pmcstat_kernproc; 1391157144Sjkoshy else 1392157144Sjkoshy pp = pmcstat_process_lookup(pid, 1393157144Sjkoshy PMCSTAT_ALLOCATE); 1394157144Sjkoshy 1395157144Sjkoshy assert(pp != NULL); 1396157144Sjkoshy 1397157144Sjkoshy image_path = pmcstat_string_intern(ev.pl_u.pl_mi. 1398157144Sjkoshy pl_pathname); 1399157144Sjkoshy image = pmcstat_image_from_path(image_path, pid == -1); 1400157144Sjkoshy if (image->pi_type == PMCSTAT_IMAGE_UNKNOWN) 1401203790Sfabient pmcstat_image_determine_type(image); 1402157144Sjkoshy if (image->pi_type != PMCSTAT_IMAGE_INDETERMINABLE) 1403157144Sjkoshy pmcstat_image_link(pp, image, 1404157144Sjkoshy ev.pl_u.pl_mi.pl_start); 1405147708Sjkoshy break; 1406147708Sjkoshy 1407157144Sjkoshy case PMCLOG_TYPE_MAP_OUT: 1408157144Sjkoshy /* 1409157144Sjkoshy * Remove an address map. 1410157144Sjkoshy */ 1411157144Sjkoshy pid = ev.pl_u.pl_mo.pl_pid; 1412157144Sjkoshy if (pid == -1) 1413157144Sjkoshy pp = pmcstat_kernproc; 1414157144Sjkoshy else 1415157144Sjkoshy pp = pmcstat_process_lookup(pid, 0); 1416157144Sjkoshy 1417157144Sjkoshy if (pp == NULL) /* unknown process */ 1418157144Sjkoshy break; 1419157144Sjkoshy 1420157144Sjkoshy pmcstat_image_unmap(pp, ev.pl_u.pl_mo.pl_start, 1421157144Sjkoshy ev.pl_u.pl_mo.pl_end); 1422157144Sjkoshy break; 1423157144Sjkoshy 1424147708Sjkoshy case PMCLOG_TYPE_PCSAMPLE: 1425174396Sjkoshy /* 1426174396Sjkoshy * Note: the `PCSAMPLE' log entry is not 1427174396Sjkoshy * generated by hpwmc(4) after version 2. 1428174396Sjkoshy */ 1429147708Sjkoshy 1430147708Sjkoshy /* 1431147708Sjkoshy * We bring in the gmon file for the image 1432147708Sjkoshy * currently associated with the PMC & pid 1433147708Sjkoshy * pair and increment the appropriate entry 1434147708Sjkoshy * bin inside this. 1435147708Sjkoshy */ 1436157144Sjkoshy pmcstat_stats.ps_samples_total++; 1437210794Sfabient ps_samples_period++; 1438157144Sjkoshy 1439147708Sjkoshy pc = ev.pl_u.pl_s.pl_pc; 1440157144Sjkoshy pp = pmcstat_process_lookup(ev.pl_u.pl_s.pl_pid, 1441157144Sjkoshy PMCSTAT_ALLOCATE); 1442147708Sjkoshy 1443203790Sfabient /* Get PMC record. */ 1444203790Sfabient pmcr = pmcstat_lookup_pmcid(ev.pl_u.pl_s.pl_pmcid); 1445203790Sfabient assert(pmcr != NULL); 1446206090Sfabient pmcr->pr_samples++; 1447147708Sjkoshy 1448203790Sfabient /* 1449203790Sfabient * Call the plugins processing 1450203790Sfabient * TODO: move pmcstat_process_find_map inside plugins 1451203790Sfabient */ 1452203790Sfabient 1453203790Sfabient if (plugins[args.pa_pplugin].pl_process != NULL) 1454203790Sfabient plugins[args.pa_pplugin].pl_process( 1455203790Sfabient pp, pmcr, 1, &pc, 1456203790Sfabient pmcstat_process_find_map(pp, pc) != NULL, 0); 1457203790Sfabient plugins[args.pa_plugin].pl_process( 1458203790Sfabient pp, pmcr, 1, &pc, 1459203790Sfabient pmcstat_process_find_map(pp, pc) != NULL, 0); 1460147708Sjkoshy break; 1461147708Sjkoshy 1462174396Sjkoshy case PMCLOG_TYPE_CALLCHAIN: 1463174396Sjkoshy pmcstat_stats.ps_samples_total++; 1464210794Sfabient ps_samples_period++; 1465174396Sjkoshy 1466174396Sjkoshy cpuflags = ev.pl_u.pl_cc.pl_cpuflags; 1467174396Sjkoshy cpu = PMC_CALLCHAIN_CPUFLAGS_TO_CPU(cpuflags); 1468174396Sjkoshy 1469174396Sjkoshy /* Filter on the CPU id. */ 1470224698Sattilio if (!CPU_ISSET(cpu, &(args.pa_cpumask))) { 1471174396Sjkoshy pmcstat_stats.ps_samples_skipped++; 1472174396Sjkoshy break; 1473174396Sjkoshy } 1474174396Sjkoshy 1475174396Sjkoshy pp = pmcstat_process_lookup(ev.pl_u.pl_cc.pl_pid, 1476174396Sjkoshy PMCSTAT_ALLOCATE); 1477174396Sjkoshy 1478203790Sfabient /* Get PMC record. */ 1479203790Sfabient pmcr = pmcstat_lookup_pmcid(ev.pl_u.pl_cc.pl_pmcid); 1480203790Sfabient assert(pmcr != NULL); 1481206090Sfabient pmcr->pr_samples++; 1482174396Sjkoshy 1483203790Sfabient /* 1484203790Sfabient * Call the plugins processing 1485203790Sfabient */ 1486174396Sjkoshy 1487203790Sfabient if (plugins[args.pa_pplugin].pl_process != NULL) 1488203790Sfabient plugins[args.pa_pplugin].pl_process( 1489203790Sfabient pp, pmcr, 1490203790Sfabient ev.pl_u.pl_cc.pl_npc, 1491203790Sfabient ev.pl_u.pl_cc.pl_pc, 1492203790Sfabient PMC_CALLCHAIN_CPUFLAGS_TO_USERMODE(cpuflags), 1493203790Sfabient cpu); 1494203790Sfabient plugins[args.pa_plugin].pl_process( 1495203790Sfabient pp, pmcr, 1496203790Sfabient ev.pl_u.pl_cc.pl_npc, 1497203790Sfabient ev.pl_u.pl_cc.pl_pc, 1498203790Sfabient PMC_CALLCHAIN_CPUFLAGS_TO_USERMODE(cpuflags), 1499203790Sfabient cpu); 1500174396Sjkoshy break; 1501174396Sjkoshy 1502147708Sjkoshy case PMCLOG_TYPE_PMCALLOCATE: 1503147708Sjkoshy /* 1504147708Sjkoshy * Record the association pmc id between this 1505147708Sjkoshy * PMC and its name. 1506147708Sjkoshy */ 1507147708Sjkoshy pmcstat_pmcid_add(ev.pl_u.pl_a.pl_pmcid, 1508203790Sfabient pmcstat_string_intern(ev.pl_u.pl_a.pl_evname)); 1509147708Sjkoshy break; 1510147708Sjkoshy 1511233628Sfabient case PMCLOG_TYPE_PMCALLOCATEDYN: 1512233628Sfabient /* 1513233628Sfabient * Record the association pmc id between this 1514233628Sfabient * PMC and its name. 1515233628Sfabient */ 1516233628Sfabient pmcstat_pmcid_add(ev.pl_u.pl_ad.pl_pmcid, 1517233628Sfabient pmcstat_string_intern(ev.pl_u.pl_ad.pl_evname)); 1518233628Sfabient break; 1519233628Sfabient 1520147708Sjkoshy case PMCLOG_TYPE_PROCEXEC: 1521147708Sjkoshy 1522147708Sjkoshy /* 1523147708Sjkoshy * Change the executable image associated with 1524147708Sjkoshy * a process. 1525147708Sjkoshy */ 1526157144Sjkoshy pp = pmcstat_process_lookup(ev.pl_u.pl_x.pl_pid, 1527157144Sjkoshy PMCSTAT_ALLOCATE); 1528147708Sjkoshy 1529147708Sjkoshy /* delete the current process map */ 1530147708Sjkoshy TAILQ_FOREACH_SAFE(ppm, &pp->pp_map, ppm_next, ppmtmp) { 1531147708Sjkoshy TAILQ_REMOVE(&pp->pp_map, ppm, ppm_next); 1532147708Sjkoshy free(ppm); 1533147708Sjkoshy } 1534147708Sjkoshy 1535157144Sjkoshy /* associate this process image */ 1536147708Sjkoshy image_path = pmcstat_string_intern( 1537147708Sjkoshy ev.pl_u.pl_x.pl_pathname); 1538157144Sjkoshy assert(image_path != NULL); 1539150139Sjkoshy pmcstat_process_exec(pp, image_path, 1540203790Sfabient ev.pl_u.pl_x.pl_entryaddr); 1541147708Sjkoshy break; 1542147708Sjkoshy 1543147708Sjkoshy case PMCLOG_TYPE_PROCEXIT: 1544147708Sjkoshy 1545147708Sjkoshy /* 1546147708Sjkoshy * Due to the way the log is generated, the 1547147708Sjkoshy * last few samples corresponding to a process 1548147708Sjkoshy * may appear in the log after the process 1549147708Sjkoshy * exit event is recorded. Thus we keep the 1550147708Sjkoshy * process' descriptor and associated data 1551147708Sjkoshy * structures around, but mark the process as 1552147708Sjkoshy * having exited. 1553147708Sjkoshy */ 1554147708Sjkoshy pp = pmcstat_process_lookup(ev.pl_u.pl_e.pl_pid, 0); 1555147708Sjkoshy if (pp == NULL) 1556147708Sjkoshy break; 1557157144Sjkoshy pp->pp_isactive = 0; /* mark as a zombie */ 1558147708Sjkoshy break; 1559147708Sjkoshy 1560147708Sjkoshy case PMCLOG_TYPE_SYSEXIT: 1561147708Sjkoshy pp = pmcstat_process_lookup(ev.pl_u.pl_se.pl_pid, 0); 1562147708Sjkoshy if (pp == NULL) 1563147708Sjkoshy break; 1564147708Sjkoshy pp->pp_isactive = 0; /* make a zombie */ 1565147708Sjkoshy break; 1566147708Sjkoshy 1567147708Sjkoshy case PMCLOG_TYPE_PROCFORK: 1568147708Sjkoshy 1569147708Sjkoshy /* 1570157144Sjkoshy * Allocate a process descriptor for the new 1571157144Sjkoshy * (child) process. 1572147708Sjkoshy */ 1573157144Sjkoshy ppnew = 1574157144Sjkoshy pmcstat_process_lookup(ev.pl_u.pl_f.pl_newpid, 1575157144Sjkoshy PMCSTAT_ALLOCATE); 1576157144Sjkoshy 1577157144Sjkoshy /* 1578157144Sjkoshy * If we had been tracking the parent, clone 1579157144Sjkoshy * its address maps. 1580157144Sjkoshy */ 1581147708Sjkoshy pp = pmcstat_process_lookup(ev.pl_u.pl_f.pl_oldpid, 0); 1582147708Sjkoshy if (pp == NULL) 1583147708Sjkoshy break; 1584147708Sjkoshy TAILQ_FOREACH(ppm, &pp->pp_map, ppm_next) 1585147708Sjkoshy pmcstat_image_link(ppnew, ppm->ppm_image, 1586157144Sjkoshy ppm->ppm_lowpc); 1587147708Sjkoshy break; 1588147708Sjkoshy 1589147708Sjkoshy default: /* other types of entries are not relevant */ 1590147708Sjkoshy break; 1591147708Sjkoshy } 1592147708Sjkoshy } 1593147708Sjkoshy 1594147708Sjkoshy if (ev.pl_state == PMCLOG_EOF) 1595157144Sjkoshy return (PMCSTAT_FINISHED); 1596147708Sjkoshy else if (ev.pl_state == PMCLOG_REQUIRE_DATA) 1597157144Sjkoshy return (PMCSTAT_RUNNING); 1598147708Sjkoshy 1599227524Sobrien err(EX_DATAERR, 1600227524Sobrien "ERROR: event parsing failed (record %jd, offset 0x%jx)", 1601227524Sobrien (uintmax_t) ev.pl_count + 1, ev.pl_offset); 1602147708Sjkoshy} 1603147708Sjkoshy 1604147708Sjkoshy/* 1605147708Sjkoshy * Print log entries as text. 1606147708Sjkoshy */ 1607147708Sjkoshy 1608157144Sjkoshystatic int 1609203790Sfabientpmcstat_print_log(void) 1610147708Sjkoshy{ 1611147708Sjkoshy struct pmclog_ev ev; 1612174396Sjkoshy uint32_t npc; 1613147708Sjkoshy 1614203790Sfabient while (pmclog_read(args.pa_logparser, &ev) == 0) { 1615147708Sjkoshy assert(ev.pl_state == PMCLOG_OK); 1616147708Sjkoshy switch (ev.pl_type) { 1617174396Sjkoshy case PMCLOG_TYPE_CALLCHAIN: 1618203790Sfabient PMCSTAT_PRINT_ENTRY("callchain", 1619174396Sjkoshy "%d 0x%x %d %d %c", ev.pl_u.pl_cc.pl_pid, 1620174396Sjkoshy ev.pl_u.pl_cc.pl_pmcid, 1621174396Sjkoshy PMC_CALLCHAIN_CPUFLAGS_TO_CPU(ev.pl_u.pl_cc. \ 1622174396Sjkoshy pl_cpuflags), ev.pl_u.pl_cc.pl_npc, 1623174396Sjkoshy PMC_CALLCHAIN_CPUFLAGS_TO_USERMODE(ev.pl_u.pl_cc.\ 1624174396Sjkoshy pl_cpuflags) ? 'u' : 's'); 1625174396Sjkoshy for (npc = 0; npc < ev.pl_u.pl_cc.pl_npc; npc++) 1626203790Sfabient PMCSTAT_PRINT_ENTRY("...", "%p", 1627174396Sjkoshy (void *) ev.pl_u.pl_cc.pl_pc[npc]); 1628174396Sjkoshy break; 1629147708Sjkoshy case PMCLOG_TYPE_CLOSELOG: 1630203790Sfabient PMCSTAT_PRINT_ENTRY("closelog",); 1631147708Sjkoshy break; 1632147708Sjkoshy case PMCLOG_TYPE_DROPNOTIFY: 1633203790Sfabient PMCSTAT_PRINT_ENTRY("drop",); 1634147708Sjkoshy break; 1635147708Sjkoshy case PMCLOG_TYPE_INITIALIZE: 1636203790Sfabient PMCSTAT_PRINT_ENTRY("initlog","0x%x \"%s\"", 1637147708Sjkoshy ev.pl_u.pl_i.pl_version, 1638147708Sjkoshy pmc_name_of_cputype(ev.pl_u.pl_i.pl_arch)); 1639174209Sjkoshy if ((ev.pl_u.pl_i.pl_version & 0xFF000000) != 1640203790Sfabient PMC_VERSION_MAJOR << 24 && args.pa_verbosity > 0) 1641227524Sobrien warnx( 1642227524Sobrien"WARNING: Log version 0x%x != expected version 0x%x.", 1643227524Sobrien ev.pl_u.pl_i.pl_version, PMC_VERSION); 1644147708Sjkoshy break; 1645157144Sjkoshy case PMCLOG_TYPE_MAP_IN: 1646203790Sfabient PMCSTAT_PRINT_ENTRY("map-in","%d %p \"%s\"", 1647157144Sjkoshy ev.pl_u.pl_mi.pl_pid, 1648157144Sjkoshy (void *) ev.pl_u.pl_mi.pl_start, 1649157144Sjkoshy ev.pl_u.pl_mi.pl_pathname); 1650147708Sjkoshy break; 1651157144Sjkoshy case PMCLOG_TYPE_MAP_OUT: 1652203790Sfabient PMCSTAT_PRINT_ENTRY("map-out","%d %p %p", 1653157144Sjkoshy ev.pl_u.pl_mo.pl_pid, 1654157144Sjkoshy (void *) ev.pl_u.pl_mo.pl_start, 1655157144Sjkoshy (void *) ev.pl_u.pl_mo.pl_end); 1656157144Sjkoshy break; 1657147708Sjkoshy case PMCLOG_TYPE_PCSAMPLE: 1658203790Sfabient PMCSTAT_PRINT_ENTRY("sample","0x%x %d %p %c", 1659147708Sjkoshy ev.pl_u.pl_s.pl_pmcid, 1660147708Sjkoshy ev.pl_u.pl_s.pl_pid, 1661147708Sjkoshy (void *) ev.pl_u.pl_s.pl_pc, 1662147708Sjkoshy ev.pl_u.pl_s.pl_usermode ? 'u' : 's'); 1663147708Sjkoshy break; 1664147708Sjkoshy case PMCLOG_TYPE_PMCALLOCATE: 1665203790Sfabient PMCSTAT_PRINT_ENTRY("allocate","0x%x \"%s\" 0x%x", 1666147708Sjkoshy ev.pl_u.pl_a.pl_pmcid, 1667147708Sjkoshy ev.pl_u.pl_a.pl_evname, 1668147708Sjkoshy ev.pl_u.pl_a.pl_flags); 1669147708Sjkoshy break; 1670233628Sfabient case PMCLOG_TYPE_PMCALLOCATEDYN: 1671233628Sfabient PMCSTAT_PRINT_ENTRY("allocatedyn","0x%x \"%s\" 0x%x", 1672233628Sfabient ev.pl_u.pl_ad.pl_pmcid, 1673233628Sfabient ev.pl_u.pl_ad.pl_evname, 1674233628Sfabient ev.pl_u.pl_ad.pl_flags); 1675233628Sfabient break; 1676147708Sjkoshy case PMCLOG_TYPE_PMCATTACH: 1677203790Sfabient PMCSTAT_PRINT_ENTRY("attach","0x%x %d \"%s\"", 1678147708Sjkoshy ev.pl_u.pl_t.pl_pmcid, 1679147708Sjkoshy ev.pl_u.pl_t.pl_pid, 1680147708Sjkoshy ev.pl_u.pl_t.pl_pathname); 1681147708Sjkoshy break; 1682147708Sjkoshy case PMCLOG_TYPE_PMCDETACH: 1683203790Sfabient PMCSTAT_PRINT_ENTRY("detach","0x%x %d", 1684147708Sjkoshy ev.pl_u.pl_d.pl_pmcid, 1685147708Sjkoshy ev.pl_u.pl_d.pl_pid); 1686147708Sjkoshy break; 1687147708Sjkoshy case PMCLOG_TYPE_PROCCSW: 1688203790Sfabient PMCSTAT_PRINT_ENTRY("cswval","0x%x %d %jd", 1689147708Sjkoshy ev.pl_u.pl_c.pl_pmcid, 1690147708Sjkoshy ev.pl_u.pl_c.pl_pid, 1691147708Sjkoshy ev.pl_u.pl_c.pl_value); 1692147708Sjkoshy break; 1693147708Sjkoshy case PMCLOG_TYPE_PROCEXEC: 1694203790Sfabient PMCSTAT_PRINT_ENTRY("exec","0x%x %d %p \"%s\"", 1695147708Sjkoshy ev.pl_u.pl_x.pl_pmcid, 1696147708Sjkoshy ev.pl_u.pl_x.pl_pid, 1697147708Sjkoshy (void *) ev.pl_u.pl_x.pl_entryaddr, 1698147708Sjkoshy ev.pl_u.pl_x.pl_pathname); 1699147708Sjkoshy break; 1700147708Sjkoshy case PMCLOG_TYPE_PROCEXIT: 1701203790Sfabient PMCSTAT_PRINT_ENTRY("exitval","0x%x %d %jd", 1702147708Sjkoshy ev.pl_u.pl_e.pl_pmcid, 1703147708Sjkoshy ev.pl_u.pl_e.pl_pid, 1704147708Sjkoshy ev.pl_u.pl_e.pl_value); 1705147708Sjkoshy break; 1706147708Sjkoshy case PMCLOG_TYPE_PROCFORK: 1707203790Sfabient PMCSTAT_PRINT_ENTRY("fork","%d %d", 1708147708Sjkoshy ev.pl_u.pl_f.pl_oldpid, 1709147708Sjkoshy ev.pl_u.pl_f.pl_newpid); 1710147708Sjkoshy break; 1711147708Sjkoshy case PMCLOG_TYPE_USERDATA: 1712203790Sfabient PMCSTAT_PRINT_ENTRY("userdata","0x%x", 1713147708Sjkoshy ev.pl_u.pl_u.pl_userdata); 1714147708Sjkoshy break; 1715147708Sjkoshy case PMCLOG_TYPE_SYSEXIT: 1716203790Sfabient PMCSTAT_PRINT_ENTRY("exit","%d", 1717147708Sjkoshy ev.pl_u.pl_se.pl_pid); 1718147708Sjkoshy break; 1719147708Sjkoshy default: 1720203790Sfabient fprintf(args.pa_printfile, "unknown event (type %d).\n", 1721147708Sjkoshy ev.pl_type); 1722147708Sjkoshy } 1723147708Sjkoshy } 1724147708Sjkoshy 1725147708Sjkoshy if (ev.pl_state == PMCLOG_EOF) 1726157144Sjkoshy return (PMCSTAT_FINISHED); 1727147708Sjkoshy else if (ev.pl_state == PMCLOG_REQUIRE_DATA) 1728157144Sjkoshy return (PMCSTAT_RUNNING); 1729147708Sjkoshy 1730227524Sobrien errx(EX_DATAERR, 1731227524Sobrien "ERROR: event parsing failed (record %jd, offset 0x%jx).", 1732147708Sjkoshy (uintmax_t) ev.pl_count + 1, ev.pl_offset); 1733147708Sjkoshy /*NOTREACHED*/ 1734147708Sjkoshy} 1735147708Sjkoshy 1736147708Sjkoshy/* 1737157144Sjkoshy * Public Interfaces. 1738157144Sjkoshy */ 1739157144Sjkoshy 1740157144Sjkoshy/* 1741157144Sjkoshy * Close a logfile, after first flushing all in-module queued data. 1742157144Sjkoshy */ 1743157144Sjkoshy 1744157144Sjkoshyint 1745203790Sfabientpmcstat_close_log(void) 1746157144Sjkoshy{ 1747210794Sfabient /* If a local logfile is configured ask the kernel to stop 1748210794Sfabient * and flush data. Kernel will close the file when data is flushed 1749210794Sfabient * so keep the status to EXITING. 1750210794Sfabient */ 1751210794Sfabient if (args.pa_logfd != -1) { 1752226514Sfabient if (pmc_close_logfile() < 0) 1753210794Sfabient err(EX_OSERR, "ERROR: logging failed"); 1754210794Sfabient } 1755210794Sfabient 1756203790Sfabient return (args.pa_flags & FLAG_HAS_PIPE ? PMCSTAT_EXITING : 1757157144Sjkoshy PMCSTAT_FINISHED); 1758157144Sjkoshy} 1759157144Sjkoshy 1760157144Sjkoshy 1761157144Sjkoshy 1762157144Sjkoshy/* 1763157144Sjkoshy * Open a log file, for reading or writing. 1764157144Sjkoshy * 1765157144Sjkoshy * The function returns the fd of a successfully opened log or -1 in 1766157144Sjkoshy * case of failure. 1767157144Sjkoshy */ 1768157144Sjkoshy 1769157144Sjkoshyint 1770157144Sjkoshypmcstat_open_log(const char *path, int mode) 1771157144Sjkoshy{ 1772210794Sfabient int error, fd, cfd; 1773157406Sjkoshy size_t hlen; 1774157406Sjkoshy const char *p, *errstr; 1775157406Sjkoshy struct addrinfo hints, *res, *res0; 1776157406Sjkoshy char hostname[MAXHOSTNAMELEN]; 1777157144Sjkoshy 1778157406Sjkoshy errstr = NULL; 1779157406Sjkoshy fd = -1; 1780157406Sjkoshy 1781157144Sjkoshy /* 1782157144Sjkoshy * If 'path' is "-" then open one of stdin or stdout depending 1783157406Sjkoshy * on the value of 'mode'. 1784174396Sjkoshy * 1785157406Sjkoshy * If 'path' contains a ':' and does not start with a '/' or '.', 1786157406Sjkoshy * and is being opened for writing, treat it as a "host:port" 1787157406Sjkoshy * specification and open a network socket. 1788157406Sjkoshy * 1789157406Sjkoshy * Otherwise, treat 'path' as a file name and open that. 1790157144Sjkoshy */ 1791157144Sjkoshy if (path[0] == '-' && path[1] == '\0') 1792157144Sjkoshy fd = (mode == PMCSTAT_OPEN_FOR_READ) ? 0 : 1; 1793210794Sfabient else if (path[0] != '/' && 1794157406Sjkoshy path[0] != '.' && strchr(path, ':') != NULL) { 1795157406Sjkoshy 1796157406Sjkoshy p = strrchr(path, ':'); 1797157406Sjkoshy hlen = p - path; 1798157406Sjkoshy if (p == path || hlen >= sizeof(hostname)) { 1799157406Sjkoshy errstr = strerror(EINVAL); 1800157406Sjkoshy goto done; 1801157406Sjkoshy } 1802157406Sjkoshy 1803157406Sjkoshy assert(hlen < sizeof(hostname)); 1804157406Sjkoshy (void) strncpy(hostname, path, hlen); 1805157406Sjkoshy hostname[hlen] = '\0'; 1806157406Sjkoshy 1807157406Sjkoshy (void) memset(&hints, 0, sizeof(hints)); 1808157406Sjkoshy hints.ai_family = AF_UNSPEC; 1809157406Sjkoshy hints.ai_socktype = SOCK_STREAM; 1810157406Sjkoshy if ((error = getaddrinfo(hostname, p+1, &hints, &res0)) != 0) { 1811157406Sjkoshy errstr = gai_strerror(error); 1812157406Sjkoshy goto done; 1813157406Sjkoshy } 1814157406Sjkoshy 1815157406Sjkoshy fd = -1; 1816157406Sjkoshy for (res = res0; res; res = res->ai_next) { 1817157406Sjkoshy if ((fd = socket(res->ai_family, res->ai_socktype, 1818157406Sjkoshy res->ai_protocol)) < 0) { 1819157406Sjkoshy errstr = strerror(errno); 1820157406Sjkoshy continue; 1821157406Sjkoshy } 1822210794Sfabient if (mode == PMCSTAT_OPEN_FOR_READ) { 1823210794Sfabient if (bind(fd, res->ai_addr, res->ai_addrlen) < 0) { 1824210794Sfabient errstr = strerror(errno); 1825210794Sfabient (void) close(fd); 1826210794Sfabient fd = -1; 1827210794Sfabient continue; 1828210794Sfabient } 1829210794Sfabient listen(fd, 1); 1830210794Sfabient cfd = accept(fd, NULL, NULL); 1831157406Sjkoshy (void) close(fd); 1832210794Sfabient if (cfd < 0) { 1833210794Sfabient errstr = strerror(errno); 1834210794Sfabient fd = -1; 1835210794Sfabient break; 1836210794Sfabient } 1837210794Sfabient fd = cfd; 1838210794Sfabient } else { 1839210794Sfabient if (connect(fd, res->ai_addr, res->ai_addrlen) < 0) { 1840210794Sfabient errstr = strerror(errno); 1841210794Sfabient (void) close(fd); 1842210794Sfabient fd = -1; 1843210794Sfabient continue; 1844210794Sfabient } 1845157406Sjkoshy } 1846157406Sjkoshy errstr = NULL; 1847157406Sjkoshy break; 1848157406Sjkoshy } 1849157406Sjkoshy freeaddrinfo(res0); 1850157406Sjkoshy 1851157406Sjkoshy } else if ((fd = open(path, mode == PMCSTAT_OPEN_FOR_READ ? 1852157144Sjkoshy O_RDONLY : (O_WRONLY|O_CREAT|O_TRUNC), 1853157406Sjkoshy S_IRUSR|S_IWUSR|S_IRGRP|S_IROTH)) < 0) 1854157406Sjkoshy errstr = strerror(errno); 1855157144Sjkoshy 1856174396Sjkoshy done: 1857157406Sjkoshy if (errstr) 1858157406Sjkoshy errx(EX_OSERR, "ERROR: Cannot open \"%s\" for %s: %s.", path, 1859157406Sjkoshy (mode == PMCSTAT_OPEN_FOR_READ ? "reading" : "writing"), 1860157406Sjkoshy errstr); 1861157406Sjkoshy 1862157144Sjkoshy return (fd); 1863157144Sjkoshy} 1864157144Sjkoshy 1865157144Sjkoshy/* 1866147708Sjkoshy * Process a log file in offline analysis mode. 1867147708Sjkoshy */ 1868147708Sjkoshy 1869147863Sjkoshyint 1870203790Sfabientpmcstat_process_log(void) 1871147708Sjkoshy{ 1872147708Sjkoshy 1873147708Sjkoshy /* 1874174396Sjkoshy * If analysis has not been asked for, just print the log to 1875174396Sjkoshy * the current output file. 1876147708Sjkoshy */ 1877203790Sfabient if (args.pa_flags & FLAG_DO_PRINT) 1878203790Sfabient return (pmcstat_print_log()); 1879147708Sjkoshy else 1880203790Sfabient return (pmcstat_analyze_log()); 1881147708Sjkoshy} 1882147708Sjkoshy 1883157144Sjkoshy/* 1884203790Sfabient * Refresh top display. 1885203790Sfabient */ 1886203790Sfabient 1887203790Sfabientstatic void 1888203790Sfabientpmcstat_refresh_top(void) 1889203790Sfabient{ 1890206090Sfabient int v_attrs; 1891206090Sfabient float v; 1892203790Sfabient char pmcname[40]; 1893206090Sfabient struct pmcstat_pmcrecord *pmcpr; 1894203790Sfabient 1895203790Sfabient /* If in pause mode do not refresh display. */ 1896203790Sfabient if (pmcstat_pause) 1897203790Sfabient return; 1898203790Sfabient 1899205809Sfabient /* Wait until PMC pop in the log. */ 1900206090Sfabient pmcpr = pmcstat_pmcindex_to_pmcr(pmcstat_pmcinfilter); 1901206090Sfabient if (pmcpr == NULL) 1902205809Sfabient return; 1903205809Sfabient 1904203790Sfabient /* Format PMC name. */ 1905203790Sfabient if (pmcstat_mergepmc) 1906206090Sfabient snprintf(pmcname, sizeof(pmcname), "[%s]", 1907206090Sfabient pmcstat_string_unintern(pmcpr->pr_pmcname)); 1908203790Sfabient else 1909203790Sfabient snprintf(pmcname, sizeof(pmcname), "%s.%d", 1910206090Sfabient pmcstat_string_unintern(pmcpr->pr_pmcname), 1911206090Sfabient pmcstat_pmcinfilter); 1912203790Sfabient 1913206090Sfabient /* Format samples count. */ 1914210794Sfabient if (ps_samples_period > 0) 1915210794Sfabient v = (pmcpr->pr_samples * 100.0) / ps_samples_period; 1916206090Sfabient else 1917206090Sfabient v = 0.; 1918206090Sfabient v_attrs = PMCSTAT_ATTRPERCENT(v); 1919206090Sfabient 1920203790Sfabient PMCSTAT_PRINTBEGIN(); 1921206090Sfabient PMCSTAT_PRINTW("PMC: %s Samples: %u ", 1922203790Sfabient pmcname, 1923206090Sfabient pmcpr->pr_samples); 1924206090Sfabient PMCSTAT_ATTRON(v_attrs); 1925206090Sfabient PMCSTAT_PRINTW("(%.1f%%) ", v); 1926206090Sfabient PMCSTAT_ATTROFF(v_attrs); 1927206090Sfabient PMCSTAT_PRINTW(", %u unresolved\n\n", 1928206090Sfabient pmcpr->pr_dubious_frames); 1929203790Sfabient if (plugins[args.pa_plugin].pl_topdisplay != NULL) 1930203790Sfabient plugins[args.pa_plugin].pl_topdisplay(); 1931203790Sfabient PMCSTAT_PRINTEND(); 1932203790Sfabient} 1933203790Sfabient 1934203790Sfabient/* 1935203790Sfabient * Find the next pmc index to display. 1936203790Sfabient */ 1937203790Sfabient 1938203790Sfabientstatic void 1939203790Sfabientpmcstat_changefilter(void) 1940203790Sfabient{ 1941203790Sfabient int pmcin; 1942203790Sfabient struct pmcstat_pmcrecord *pmcr; 1943203790Sfabient 1944203790Sfabient /* 1945203790Sfabient * Find the next merge target. 1946203790Sfabient */ 1947203790Sfabient if (pmcstat_mergepmc) { 1948203790Sfabient pmcin = pmcstat_pmcinfilter; 1949203790Sfabient 1950203790Sfabient do { 1951203790Sfabient pmcr = pmcstat_pmcindex_to_pmcr(pmcstat_pmcinfilter); 1952210794Sfabient if (pmcr == NULL || pmcr == pmcr->pr_merge) 1953203790Sfabient break; 1954203790Sfabient 1955203790Sfabient pmcstat_pmcinfilter++; 1956203790Sfabient if (pmcstat_pmcinfilter >= pmcstat_npmcs) 1957203790Sfabient pmcstat_pmcinfilter = 0; 1958203790Sfabient 1959203790Sfabient } while (pmcstat_pmcinfilter != pmcin); 1960203790Sfabient } 1961203790Sfabient} 1962203790Sfabient 1963203790Sfabient/* 1964203790Sfabient * Top mode keypress. 1965203790Sfabient */ 1966203790Sfabient 1967203790Sfabientint 1968203790Sfabientpmcstat_keypress_log(void) 1969203790Sfabient{ 1970203790Sfabient int c, ret = 0; 1971203790Sfabient WINDOW *w; 1972203790Sfabient 1973203790Sfabient w = newwin(1, 0, 1, 0); 1974203790Sfabient c = wgetch(w); 1975203790Sfabient wprintw(w, "Key: %c => ", c); 1976203790Sfabient switch (c) { 1977203790Sfabient case 'c': 1978203790Sfabient wprintw(w, "enter mode 'd' or 'a' => "); 1979203790Sfabient c = wgetch(w); 1980203790Sfabient if (c == 'd') { 1981203790Sfabient args.pa_topmode = PMCSTAT_TOP_DELTA; 1982203790Sfabient wprintw(w, "switching to delta mode"); 1983203790Sfabient } else { 1984203790Sfabient args.pa_topmode = PMCSTAT_TOP_ACCUM; 1985203790Sfabient wprintw(w, "switching to accumulation mode"); 1986203790Sfabient } 1987203790Sfabient break; 1988203790Sfabient case 'm': 1989203790Sfabient pmcstat_mergepmc = !pmcstat_mergepmc; 1990203790Sfabient /* 1991203790Sfabient * Changing merge state require data reset. 1992203790Sfabient */ 1993203790Sfabient if (plugins[args.pa_plugin].pl_shutdown != NULL) 1994203790Sfabient plugins[args.pa_plugin].pl_shutdown(NULL); 1995210794Sfabient pmcstat_stats_reset(0); 1996203790Sfabient if (plugins[args.pa_plugin].pl_init != NULL) 1997203790Sfabient plugins[args.pa_plugin].pl_init(); 1998203790Sfabient 1999203790Sfabient /* Update filter to be on a merge target. */ 2000203790Sfabient pmcstat_changefilter(); 2001203790Sfabient wprintw(w, "merge PMC %s", pmcstat_mergepmc ? "on" : "off"); 2002203790Sfabient break; 2003203790Sfabient case 'n': 2004203790Sfabient /* Close current plugin. */ 2005203790Sfabient if (plugins[args.pa_plugin].pl_shutdown != NULL) 2006203790Sfabient plugins[args.pa_plugin].pl_shutdown(NULL); 2007203790Sfabient 2008203790Sfabient /* Find next top display available. */ 2009203790Sfabient do { 2010203790Sfabient args.pa_plugin++; 2011203790Sfabient if (plugins[args.pa_plugin].pl_name == NULL) 2012203790Sfabient args.pa_plugin = 0; 2013203790Sfabient } while (plugins[args.pa_plugin].pl_topdisplay == NULL); 2014203790Sfabient 2015203790Sfabient /* Open new plugin. */ 2016210794Sfabient pmcstat_stats_reset(0); 2017203790Sfabient if (plugins[args.pa_plugin].pl_init != NULL) 2018203790Sfabient plugins[args.pa_plugin].pl_init(); 2019203790Sfabient wprintw(w, "switching to plugin %s", 2020203790Sfabient plugins[args.pa_plugin].pl_name); 2021203790Sfabient break; 2022203790Sfabient case 'p': 2023203790Sfabient pmcstat_pmcinfilter++; 2024203790Sfabient if (pmcstat_pmcinfilter >= pmcstat_npmcs) 2025203790Sfabient pmcstat_pmcinfilter = 0; 2026203790Sfabient pmcstat_changefilter(); 2027203790Sfabient wprintw(w, "switching to PMC %s.%d", 2028203790Sfabient pmcstat_pmcindex_to_name(pmcstat_pmcinfilter), 2029203790Sfabient pmcstat_pmcinfilter); 2030203790Sfabient break; 2031203790Sfabient case ' ': 2032203790Sfabient pmcstat_pause = !pmcstat_pause; 2033203790Sfabient if (pmcstat_pause) 2034203790Sfabient wprintw(w, "pause => press space again to continue"); 2035203790Sfabient break; 2036203790Sfabient case 'q': 2037203790Sfabient wprintw(w, "exiting..."); 2038203790Sfabient ret = 1; 2039208858Sfabient break; 2040203790Sfabient default: 2041203790Sfabient if (plugins[args.pa_plugin].pl_topkeypress != NULL) 2042203790Sfabient if (plugins[args.pa_plugin].pl_topkeypress(c, w)) 2043203790Sfabient ret = 1; 2044203790Sfabient } 2045203790Sfabient 2046203790Sfabient wrefresh(w); 2047203790Sfabient delwin(w); 2048203790Sfabient return ret; 2049203790Sfabient} 2050203790Sfabient 2051203790Sfabient 2052203790Sfabient/* 2053203790Sfabient * Top mode display. 2054203790Sfabient */ 2055203790Sfabient 2056203790Sfabientvoid 2057203790Sfabientpmcstat_display_log(void) 2058203790Sfabient{ 2059203790Sfabient 2060203790Sfabient pmcstat_refresh_top(); 2061203790Sfabient 2062203790Sfabient /* Reset everythings if delta mode. */ 2063203790Sfabient if (args.pa_topmode == PMCSTAT_TOP_DELTA) { 2064203790Sfabient if (plugins[args.pa_plugin].pl_shutdown != NULL) 2065203790Sfabient plugins[args.pa_plugin].pl_shutdown(NULL); 2066210794Sfabient pmcstat_stats_reset(0); 2067203790Sfabient if (plugins[args.pa_plugin].pl_init != NULL) 2068203790Sfabient plugins[args.pa_plugin].pl_init(); 2069203790Sfabient } 2070203790Sfabient 2071203790Sfabient} 2072203790Sfabient 2073203790Sfabient/* 2074203790Sfabient * Configure a plugins. 2075203790Sfabient */ 2076203790Sfabient 2077203790Sfabientvoid 2078203790Sfabientpmcstat_pluginconfigure_log(char *opt) 2079203790Sfabient{ 2080203790Sfabient 2081203790Sfabient if (strncmp(opt, "threshold=", 10) == 0) { 2082203790Sfabient pmcstat_threshold = atof(opt+10); 2083203790Sfabient } else { 2084203790Sfabient if (plugins[args.pa_plugin].pl_configure != NULL) { 2085203790Sfabient if (!plugins[args.pa_plugin].pl_configure(opt)) 2086203790Sfabient err(EX_USAGE, 2087203790Sfabient "ERROR: unknown option <%s>.", opt); 2088203790Sfabient } 2089203790Sfabient } 2090203790Sfabient} 2091203790Sfabient 2092203790Sfabient/* 2093157144Sjkoshy * Initialize module. 2094157144Sjkoshy */ 2095157144Sjkoshy 2096147708Sjkoshyvoid 2097203790Sfabientpmcstat_initialize_logging(void) 2098147708Sjkoshy{ 2099150139Sjkoshy int i; 2100147708Sjkoshy 2101147708Sjkoshy /* use a convenient format for 'ldd' output */ 2102153710Sjkoshy if (setenv("LD_TRACE_LOADED_OBJECTS_FMT1","%o \"%p\" %x\n",1) != 0) 2103157144Sjkoshy err(EX_OSERR, "ERROR: Cannot setenv"); 2104147708Sjkoshy 2105147708Sjkoshy /* Initialize hash tables */ 2106157144Sjkoshy pmcstat_string_initialize(); 2107147708Sjkoshy for (i = 0; i < PMCSTAT_NHASH; i++) { 2108147708Sjkoshy LIST_INIT(&pmcstat_image_hash[i]); 2109147708Sjkoshy LIST_INIT(&pmcstat_process_hash[i]); 2110147708Sjkoshy } 2111147708Sjkoshy 2112157144Sjkoshy /* 2113157144Sjkoshy * Create a fake 'process' entry for the kernel with pid -1. 2114157144Sjkoshy * hwpmc(4) will subsequently inform us about where the kernel 2115157144Sjkoshy * and any loaded kernel modules are mapped. 2116157144Sjkoshy */ 2117157144Sjkoshy if ((pmcstat_kernproc = pmcstat_process_lookup((pid_t) -1, 2118157144Sjkoshy PMCSTAT_ALLOCATE)) == NULL) 2119157144Sjkoshy err(EX_OSERR, "ERROR: Cannot initialize logging"); 2120203790Sfabient 2121203790Sfabient /* PMC count. */ 2122203790Sfabient pmcstat_npmcs = 0; 2123203790Sfabient 2124203790Sfabient /* Merge PMC with same name. */ 2125203790Sfabient pmcstat_mergepmc = args.pa_mergepmc; 2126203790Sfabient 2127203790Sfabient /* 2128203790Sfabient * Initialize plugins 2129203790Sfabient */ 2130203790Sfabient 2131203790Sfabient if (plugins[args.pa_pplugin].pl_init != NULL) 2132203790Sfabient plugins[args.pa_pplugin].pl_init(); 2133203790Sfabient if (plugins[args.pa_plugin].pl_init != NULL) 2134203790Sfabient plugins[args.pa_plugin].pl_init(); 2135157144Sjkoshy} 2136147708Sjkoshy 2137157144Sjkoshy/* 2138157144Sjkoshy * Shutdown module. 2139157144Sjkoshy */ 2140147708Sjkoshy 2141147708Sjkoshyvoid 2142203790Sfabientpmcstat_shutdown_logging(void) 2143147708Sjkoshy{ 2144147708Sjkoshy int i; 2145157144Sjkoshy FILE *mf; 2146147708Sjkoshy struct pmcstat_image *pi, *pitmp; 2147147708Sjkoshy struct pmcstat_process *pp, *pptmp; 2148203790Sfabient struct pmcstat_pcmap *ppm, *ppmtmp; 2149147708Sjkoshy 2150157144Sjkoshy /* determine where to send the map file */ 2151157144Sjkoshy mf = NULL; 2152203790Sfabient if (args.pa_mapfilename != NULL) 2153203790Sfabient mf = (strcmp(args.pa_mapfilename, "-") == 0) ? 2154203790Sfabient args.pa_printfile : fopen(args.pa_mapfilename, "w"); 2155157144Sjkoshy 2156203790Sfabient if (mf == NULL && args.pa_flags & FLAG_DO_GPROF && 2157203790Sfabient args.pa_verbosity >= 2) 2158203790Sfabient mf = args.pa_printfile; 2159157144Sjkoshy 2160157144Sjkoshy if (mf) 2161157144Sjkoshy (void) fprintf(mf, "MAP:\n"); 2162157144Sjkoshy 2163174396Sjkoshy /* 2164203790Sfabient * Shutdown the plugins 2165174396Sjkoshy */ 2166157536Sjkoshy 2167203790Sfabient if (plugins[args.pa_plugin].pl_shutdown != NULL) 2168203790Sfabient plugins[args.pa_plugin].pl_shutdown(mf); 2169203790Sfabient if (plugins[args.pa_pplugin].pl_shutdown != NULL) 2170203790Sfabient plugins[args.pa_pplugin].pl_shutdown(mf); 2171157536Sjkoshy 2172174396Sjkoshy for (i = 0; i < PMCSTAT_NHASH; i++) { 2173203790Sfabient LIST_FOREACH_SAFE(pi, &pmcstat_image_hash[i], pi_next, 2174203790Sfabient pitmp) { 2175203790Sfabient if (plugins[args.pa_plugin].pl_shutdownimage != NULL) 2176203790Sfabient plugins[args.pa_plugin].pl_shutdownimage(pi); 2177203790Sfabient if (plugins[args.pa_pplugin].pl_shutdownimage != NULL) 2178203790Sfabient plugins[args.pa_pplugin].pl_shutdownimage(pi); 2179174396Sjkoshy 2180203790Sfabient free(pi->pi_symbols); 2181203790Sfabient if (pi->pi_addr2line != NULL) 2182203790Sfabient pclose(pi->pi_addr2line); 2183147708Sjkoshy LIST_REMOVE(pi, pi_next); 2184147708Sjkoshy free(pi); 2185147708Sjkoshy } 2186174396Sjkoshy 2187147708Sjkoshy LIST_FOREACH_SAFE(pp, &pmcstat_process_hash[i], pp_next, 2188147708Sjkoshy pptmp) { 2189203790Sfabient TAILQ_FOREACH_SAFE(ppm, &pp->pp_map, ppm_next, ppmtmp) { 2190203790Sfabient TAILQ_REMOVE(&pp->pp_map, ppm, ppm_next); 2191203790Sfabient free(ppm); 2192203790Sfabient } 2193147708Sjkoshy LIST_REMOVE(pp, pp_next); 2194147708Sjkoshy free(pp); 2195147708Sjkoshy } 2196147708Sjkoshy } 2197157144Sjkoshy 2198157144Sjkoshy pmcstat_string_shutdown(); 2199157144Sjkoshy 2200157144Sjkoshy /* 2201157144Sjkoshy * Print errors unless -q was specified. Print all statistics 2202157144Sjkoshy * if verbosity > 1. 2203157144Sjkoshy */ 2204203790Sfabient#define PRINT(N,V) do { \ 2205203790Sfabient if (pmcstat_stats.ps_##V || args.pa_verbosity >= 2) \ 2206203790Sfabient (void) fprintf(args.pa_printfile, " %-40s %d\n",\ 2207157144Sjkoshy N, pmcstat_stats.ps_##V); \ 2208157144Sjkoshy } while (0) 2209157144Sjkoshy 2210210794Sfabient if (args.pa_verbosity >= 1 && (args.pa_flags & FLAG_DO_ANALYSIS)) { 2211203790Sfabient (void) fprintf(args.pa_printfile, "CONVERSION STATISTICS:\n"); 2212203790Sfabient PRINT("#exec/a.out", exec_aout); 2213203790Sfabient PRINT("#exec/elf", exec_elf); 2214203790Sfabient PRINT("#exec/unknown", exec_indeterminable); 2215203790Sfabient PRINT("#exec handling errors", exec_errors); 2216203790Sfabient PRINT("#samples/total", samples_total); 2217203790Sfabient PRINT("#samples/unclaimed", samples_unknown_offset); 2218203790Sfabient PRINT("#samples/unknown-object", samples_indeterminable); 2219212176Sfabient PRINT("#samples/unknown-function", samples_unknown_function); 2220203790Sfabient PRINT("#callchain/dubious-frames", callchain_dubious_frames); 2221157144Sjkoshy } 2222157144Sjkoshy 2223157144Sjkoshy if (mf) 2224157144Sjkoshy (void) fclose(mf); 2225147708Sjkoshy} 2226