2 * SPDX-License-Identifier: BSD-2-Clause-FreeBSD
4 * Copyright (c) 2008 Nokia Corporation
7 * This software was developed by Attilio Rao for the IPSO project under
8 * contract to Nokia Corporation.
10 * Redistribution and use in source and binary forms, with or without
11 * modification, are permitted provided that the following conditions
13 * 1. Redistributions of source code must retain the above copyright
14 * notice unmodified, this list of conditions, and the following
16 * 2. Redistributions in binary form must reproduce the above copyright
17 * notice, this list of conditions and the following disclaimer in the
18 * documentation and/or other materials provided with the distribution.
20 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
21 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
22 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
23 * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
24 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
25 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
26 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
27 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
28 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
29 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
33 #include <sys/cdefs.h>
34 __FBSDID("$FreeBSD$");
36 #include <sys/param.h>
37 #include <sys/queue.h>
47 /* NB: Make sure FNBUFF is as large as LNBUFF, otherwise it could overflow */
51 #define TMPNAME "pmcannotate.XXXXXX"
53 #define FATAL(ptr, x ...) do { \
55 general_deleteall(); \
58 fprintf(stderr, ##x); \
64 #define PERCSAMP(x) ((x) * 100 / totalsamples)
67 TAILQ_ENTRY(entry) en_iter;
76 TAILQ_ENTRY(aggent) ag_fiter;
84 static struct aggent *agg_create(const char *name, u_int nsamples,
85 uintptr_t start, uintptr_t end);
86 static void agg_destroy(struct aggent *agg) __unused;
87 static void asmparse(FILE *fp);
88 static int cparse(FILE *fp);
89 static void entry_acqref(struct entry *entry);
90 static struct entry *entry_create(const char *name, uintptr_t pc,
91 uintptr_t start, uintptr_t end);
92 static void entry_destroy(struct entry *entry) __unused;
93 static void fqueue_compact(float th);
94 static void fqueue_deleteall(void);
95 static struct aggent *fqueue_findent_by_name(const char *name);
96 static int fqueue_getall(const char *bin, char *temp, int asmf);
97 static int fqueue_insertent(struct entry *entry);
98 static int fqueue_insertgen(void);
99 static void general_deleteall(void);
100 static struct entry *general_findent(uintptr_t pc);
101 static void general_insertent(struct entry *entry);
102 static void general_printasm(FILE *fp, struct aggent *agg);
103 static int general_printc(FILE *fp, struct aggent *agg);
104 static int printblock(FILE *fp, struct aggent *agg);
105 static void usage(const char *progname) __dead2;
107 static TAILQ_HEAD(, entry) mainlst = TAILQ_HEAD_INITIALIZER(mainlst);
108 static TAILQ_HEAD(, aggent) fqueue = TAILQ_HEAD_INITIALIZER(fqueue);
111 * Use a float value in order to automatically promote operations
112 * to return a float value rather than use casts.
114 static float totalsamples;
117 * Identifies a string cointaining objdump's assembly printout.
120 isasminline(const char *str)
125 if (sscanf(str, " %p%n", &ptr, &nbytes) != 1)
127 if (str[nbytes] != ':' || isspace(str[nbytes + 1]) == 0)
133 * Identifies a string containing objdump's assembly printout
134 * for a new function.
137 newfunction(const char *str)
145 if (sscanf(str, "%p <%[^>:]>:%n", &ptr, fname, &nbytes) != 2)
151 * Create a new first-level aggregation object for a specified
154 static struct aggent *
155 agg_create(const char *name, u_int nsamples, uintptr_t start, uintptr_t end)
159 agg = calloc(1, sizeof(struct aggent));
162 agg->ag_name = strdup(name);
163 if (agg->ag_name == NULL) {
167 agg->ag_nsamples = nsamples;
168 agg->ag_ostart = start;
174 * Destroy a first-level aggregation object for a specified
178 agg_destroy(struct aggent *agg)
186 * Analyze the "objdump -d" output, locate functions and start
187 * printing out the assembly functions content.
188 * We do not use newfunction() because we actually need the
189 * function name in available form, but the heurstic used is
195 char buffer[LNBUFF], fname[FNBUFF];
199 while (fgets(buffer, LNBUFF, fp) != NULL) {
200 if (isspace(buffer[0]))
202 if (sscanf(buffer, "%p <%[^>:]>:", &ptr, fname) != 2)
204 agg = fqueue_findent_by_name(fname);
207 agg->ag_offset = ftell(fp);
210 TAILQ_FOREACH(agg, &fqueue, ag_fiter) {
211 if (fseek(fp, agg->ag_offset, SEEK_SET) == -1)
213 printf("Profile trace for function: %s() [%.2f%%]\n",
214 agg->ag_name, PERCSAMP(agg->ag_nsamples));
215 general_printasm(fp, agg);
221 * Analyze the "objdump -S" output, locate functions and start
222 * printing out the C functions content.
223 * We do not use newfunction() because we actually need the
224 * function name in available form, but the heurstic used is
226 * In order to maintain the printout sorted, on the first pass it
227 * simply stores the file offsets in order to fastly moved later
228 * (when the file is hot-cached also) when the real printout will
234 char buffer[LNBUFF], fname[FNBUFF];
238 while (fgets(buffer, LNBUFF, fp) != NULL) {
239 if (isspace(buffer[0]))
241 if (sscanf(buffer, "%p <%[^>:]>:", &ptr, fname) != 2)
243 agg = fqueue_findent_by_name(fname);
246 agg->ag_offset = ftell(fp);
249 TAILQ_FOREACH(agg, &fqueue, ag_fiter) {
250 if (fseek(fp, agg->ag_offset, SEEK_SET) == -1)
252 printf("Profile trace for function: %s() [%.2f%%]\n",
253 agg->ag_name, PERCSAMP(agg->ag_nsamples));
254 if (general_printc(fp, agg) == -1)
262 * Bump the number of samples for any raw entry.
265 entry_acqref(struct entry *entry)
268 entry->en_nsamples++;
272 * Create a new raw entry object for a specified function.
274 static struct entry *
275 entry_create(const char *name, uintptr_t pc, uintptr_t start, uintptr_t end)
279 obj = calloc(1, sizeof(struct entry));
282 obj->en_name = strdup(name);
283 if (obj->en_name == NULL) {
288 obj->en_ostart = start;
290 obj->en_nsamples = 1;
295 * Destroy a raw entry object for a specified function.
298 entry_destroy(struct entry *entry)
301 free(entry->en_name);
306 * Specify a lower bound in percentage and drop from the
307 * first-level aggregation queue all the objects with a
311 fqueue_compact(float th)
314 struct aggent *agg, *tmpagg;
316 if (totalsamples == 0)
319 /* Revert the percentage calculation. */
320 thi = th * totalsamples / 100;
321 TAILQ_FOREACH_SAFE(agg, &fqueue, ag_fiter, tmpagg)
322 if (agg->ag_nsamples < thi)
323 TAILQ_REMOVE(&fqueue, agg, ag_fiter);
327 * Flush the first-level aggregates queue.
330 fqueue_deleteall(void)
334 while (TAILQ_EMPTY(&fqueue) == 0) {
335 agg = TAILQ_FIRST(&fqueue);
336 TAILQ_REMOVE(&fqueue, agg, ag_fiter);
341 * Insert a raw entry into the aggregations queue.
342 * If the respective first-level aggregation object
343 * does not exist create it and maintain it sorted
344 * in respect of the number of samples.
347 fqueue_insertent(struct entry *entry)
349 struct aggent *obj, *tmp;
353 TAILQ_FOREACH(obj, &fqueue, ag_fiter)
354 if (!strcmp(obj->ag_name, entry->en_name)) {
356 obj->ag_nsamples += entry->en_nsamples;
361 * If the first-level aggregation object already exists,
362 * just aggregate the samples and, if needed, resort
366 TAILQ_REMOVE(&fqueue, obj, ag_fiter);
368 TAILQ_FOREACH(tmp, &fqueue, ag_fiter)
369 if (obj->ag_nsamples > tmp->ag_nsamples) {
374 TAILQ_INSERT_BEFORE(tmp, obj, ag_fiter);
376 TAILQ_INSERT_TAIL(&fqueue, obj, ag_fiter);
381 * If the first-level aggregation object does not
382 * exist, create it and put in the sorted queue.
383 * If this is the first object, we need to set the
386 obj = agg_create(entry->en_name, entry->en_nsamples, entry->en_ostart,
390 if (TAILQ_EMPTY(&fqueue) != 0) {
391 TAILQ_INSERT_HEAD(&fqueue, obj, ag_fiter);
394 TAILQ_FOREACH(tmp, &fqueue, ag_fiter)
395 if (obj->ag_nsamples > tmp->ag_nsamples) {
400 TAILQ_INSERT_BEFORE(tmp, obj, ag_fiter);
402 TAILQ_INSERT_TAIL(&fqueue, obj, ag_fiter);
407 * Lookup a first-level aggregation object by name.
409 static struct aggent *
410 fqueue_findent_by_name(const char *name)
414 TAILQ_FOREACH(obj, &fqueue, ag_fiter)
415 if (!strcmp(obj->ag_name, name))
421 * Return the number of object in the first-level aggregations queue.
424 fqueue_getall(const char *bin, char *temp, int asmf)
426 char tmpf[MAXPATHLEN * 2 + 50];
428 uintptr_t start, end;
430 if (mkstemp(temp) == -1)
432 TAILQ_FOREACH(agg, &fqueue, ag_fiter) {
433 bzero(tmpf, sizeof(tmpf));
434 start = agg->ag_ostart;
438 * Fix-up the end address in order to show it in the objdump's
443 snprintf(tmpf, sizeof(tmpf),
444 "objdump --start-address=%p "
445 "--stop-address=%p -d %s >> %s", (void *)start,
446 (void *)end, bin, temp);
448 snprintf(tmpf, sizeof(tmpf),
449 "objdump --start-address=%p "
450 "--stop-address=%p -S %s >> %s", (void *)start,
451 (void *)end, bin, temp);
452 if (system(tmpf) != 0)
459 * Insert all the raw entries present in the general queue
460 * into the first-level aggregations queue.
463 fqueue_insertgen(void)
467 TAILQ_FOREACH(obj, &mainlst, en_iter)
468 if (fqueue_insertent(obj) == -1)
474 * Flush the raw entries general queue.
477 general_deleteall(void)
481 while (TAILQ_EMPTY(&mainlst) == 0) {
482 obj = TAILQ_FIRST(&mainlst);
483 TAILQ_REMOVE(&mainlst, obj, en_iter);
488 * Lookup a raw entry by the PC.
490 static struct entry *
491 general_findent(uintptr_t pc)
495 TAILQ_FOREACH(obj, &mainlst, en_iter)
496 if (obj->en_pc == pc)
502 * Insert a new raw entry in the general queue.
505 general_insertent(struct entry *entry)
508 TAILQ_INSERT_TAIL(&mainlst, entry, en_iter);
512 * Printout the body of an "objdump -d" assembly function.
513 * It does simply stops when a new function is encountered,
514 * bringing back the file position in order to not mess up
515 * subsequent analysis.
516 * C lines and others not recognized are simply skipped.
519 general_printasm(FILE *fp, struct aggent *agg)
526 while (fgets(buffer, LNBUFF, fp) != NULL) {
527 if ((nbytes = newfunction(buffer)) != 0) {
528 fseek(fp, nbytes * -1, SEEK_CUR);
531 if (!isasminline(buffer))
533 if (sscanf(buffer, " %p:", &ptr) != 1)
535 obj = general_findent((uintptr_t)ptr);
537 printf("\t| %s", buffer);
539 printf("%.2f%%\t| %s",
540 (float)obj->en_nsamples * 100 / agg->ag_nsamples,
546 * Printout the body of an "objdump -S" function.
547 * It does simply stops when a new function is encountered,
548 * bringing back the file position in order to not mess up
549 * subsequent analysis.
550 * It expect from the starting to the end to find, always, valid blocks
551 * (see below for an explanation of the "block" concept).
554 general_printc(FILE *fp, struct aggent *agg)
558 while (fgets(buffer, LNBUFF, fp) != NULL) {
559 fseek(fp, strlen(buffer) * -1, SEEK_CUR);
560 if (newfunction(buffer) != 0)
562 if (printblock(fp, agg) == -1)
569 * Printout a single block inside an "objdump -S" function.
570 * The block is composed of a first part in C and subsequent translation
572 * This code also operates a second-level aggregation packing together
573 * samples relative to PCs into a (lower bottom) block with their
574 * C (higher half) counterpart.
577 printblock(FILE *fp, struct aggent *agg)
583 int done, nbytes, sentinel;
587 * We expect the first thing of the block is C code, so simply give
588 * up if asm line is found.
593 if (fgets(buffer, LNBUFF, fp) == NULL)
595 if (isasminline(buffer) != 0)
598 nbytes = newfunction(buffer);
600 if (fseek(fp, nbytes * -1, SEEK_CUR) == -1)
607 * If the sentinel is not set, it means it did not match any
608 * "high half" for this code so simply give up.
609 * Operates the second-level aggregation.
615 if (sscanf(buffer, " %p:", &ptr) != 1)
617 obj = general_findent((uintptr_t)ptr);
619 tnsamples += obj->en_nsamples;
620 } while (fgets(buffer, LNBUFF, fp) != NULL && isasminline(buffer) != 0);
622 /* Rewind to the start of the block in order to start the printout. */
623 if (fseek(fp, lstart, SEEK_SET) == -1)
626 /* Again the high half of the block rappresenting the C part. */
628 while (fgets(buffer, LNBUFF, fp) != NULL && isasminline(buffer) == 0) {
629 if (tnsamples == 0 || done != 0)
630 printf("\t| %s", buffer);
633 printf("%.2f%%\t| %s",
634 (float)tnsamples * 100 / agg->ag_nsamples, buffer);
639 * Again the low half of the block rappresenting the asm
643 if (fgets(buffer, LNBUFF, fp) == NULL)
645 if (isasminline(buffer) == 0)
647 nbytes = newfunction(buffer);
649 if (fseek(fp, nbytes * -1, SEEK_CUR) == -1)
654 if (fseek(fp, strlen(buffer) * -1, SEEK_CUR) == -1)
660 * Helper printout functions.
663 usage(const char *progname)
667 "usage: %s [-a] [-h] [-k kfile] [-l lb] pmcraw.out binary\n",
673 main(int argc, char *argv[])
675 char buffer[LNBUFF], fname[FNBUFF];
676 char *tbfl, *tofl, *tmpdir;
677 char tmpf[MAXPATHLEN * 2 + 50];
679 char *bin, *exec, *kfile, *ofile;
682 void *ptr, *hstart, *hend;
683 uintptr_t tmppc, ostart, oend;
692 while ((cget = getopt(argc, argv, "ahl:k:")) != -1)
701 limit = (float)atof(optarg);
715 if (access(bin, R_OK | F_OK) == -1)
716 FATAL(exec, "%s: Impossible to locate the binary file\n",
718 if (access(ofile, R_OK | F_OK) == -1)
719 FATAL(exec, "%s: Impossible to locate the pmcstat file\n",
721 if (kfile != NULL && access(kfile, R_OK | F_OK) == -1)
722 FATAL(exec, "%s: Impossible to locate the kernel file\n",
725 bzero(tmpf, sizeof(tmpf));
726 tmpdir = getenv("TMPDIR");
727 if (tmpdir == NULL) {
728 asprintf(&tbfl, "%s/%s", _PATH_TMP, TMPNAME);
729 asprintf(&tofl, "%s/%s", _PATH_TMP, TMPNAME);
731 asprintf(&tbfl, "%s/%s", tmpdir, TMPNAME);
732 asprintf(&tofl, "%s/%s", tmpdir, TMPNAME);
734 if (tofl == NULL || tbfl == NULL)
735 FATAL(exec, "%s: Cannot create tempfile templates\n",
737 if (mkstemp(tofl) == -1)
738 FATAL(exec, "%s: Impossible to create the tmp file\n",
741 snprintf(tmpf, sizeof(tmpf), "pmcstat -k %s -R %s -m %s",
744 snprintf(tmpf, sizeof(tmpf), "pmcstat -R %s -m %s", ofile,
746 if (system(tmpf) != 0)
747 FATAL(exec, "%s: Impossible to create the tmp file\n",
750 gfp = fopen(tofl, "r");
752 FATAL(exec, "%s: Impossible to open the map file\n",
756 * Make the collection of raw entries from a pmcstat mapped file.
757 * The heuristic here wants strings in the form:
758 * "addr funcname startfaddr endfaddr".
760 while (fgets(buffer, LNBUFF, gfp) != NULL) {
761 if (isspace(buffer[0]))
763 if (sscanf(buffer, "%p %s %p %p\n", &ptr, fname,
764 &hstart, &hend) != 4)
766 "%s: Invalid scan of function in the map file\n",
768 ostart = (uintptr_t)hstart;
769 oend = (uintptr_t)hend;
770 tmppc = (uintptr_t)ptr;
772 obj = general_findent(tmppc);
777 obj = entry_create(fname, tmppc, ostart, oend);
780 "%s: Impossible to create a new object\n", exec);
781 general_insertent(obj);
783 if (fclose(gfp) == EOF)
784 FATAL(exec, "%s: Impossible to close the filedesc\n",
786 if (remove(tofl) == -1)
787 FATAL(exec, "%s: Impossible to remove the tmpfile\n",
791 * Remove the loose end objects and feed the first-level aggregation
794 if (fqueue_insertgen() == -1)
795 FATAL(exec, "%s: Impossible to generate an analysis\n",
797 fqueue_compact(limit);
798 if (fqueue_getall(bin, tbfl, asmsrc) == -1)
799 FATAL(exec, "%s: Impossible to create the tmp file\n",
802 bfp = fopen(tbfl, "r");
804 FATAL(exec, "%s: Impossible to open the binary file\n",
809 else if (cparse(bfp) == -1)
810 FATAL(NULL, "%s: Invalid format for the C file\n", exec);
811 if (fclose(bfp) == EOF)
812 FATAL(exec, "%s: Impossible to close the filedesc\n",
814 if (remove(tbfl) == -1)
815 FATAL(exec, "%s: Impossible to remove the tmpfile\n",