2 * Copyright (c) 2008 Nokia Corporation
5 * This software was developed by Attilio Rao for the IPSO project under
6 * contract to Nokia Corporation.
8 * Redistribution and use in source and binary forms, with or without
9 * modification, are permitted provided that the following conditions
11 * 1. Redistributions of source code must retain the above copyright
12 * notice unmodified, this list of conditions, and the following
14 * 2. Redistributions in binary form must reproduce the above copyright
15 * notice, this list of conditions and the following disclaimer in the
16 * documentation and/or other materials provided with the distribution.
18 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
19 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
20 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
21 * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
22 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
23 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
24 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
25 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
26 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
27 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
31 #include <sys/cdefs.h>
32 __FBSDID("$FreeBSD$");
34 #include <sys/param.h>
35 #include <sys/queue.h>
44 /* NB: Make sure FNBUFF is as large as LNBUFF, otherwise it could overflow */
48 #define TMPPATH "/tmp/pmcannotate.XXXXXX"
50 #define FATAL(ptr, x ...) do { \
52 general_deleteall(); \
55 fprintf(stderr, ##x); \
61 #define PERCSAMP(x) ((x) * 100 / totalsamples)
64 TAILQ_ENTRY(entry) en_iter;
73 TAILQ_ENTRY(aggent) ag_fiter;
81 static struct aggent *agg_create(const char *name, u_int nsamples,
82 uintptr_t start, uintptr_t end);
83 static void agg_destroy(struct aggent *agg) __unused;
84 static void asmparse(FILE *fp);
85 static int cparse(FILE *fp);
86 static void entry_acqref(struct entry *entry);
87 static struct entry *entry_create(const char *name, uintptr_t pc,
88 uintptr_t start, uintptr_t end);
89 static void entry_destroy(struct entry *entry) __unused;
90 static void fqueue_compact(float th);
91 static void fqueue_deleteall(void);
92 static struct aggent *fqueue_findent_by_name(const char *name);
93 static int fqueue_getall(const char *bin, char *temp, int asmf);
94 static int fqueue_insertent(struct entry *entry);
95 static int fqueue_insertgen(void);
96 static void general_deleteall(void);
97 static struct entry *general_findent(uintptr_t pc);
98 static void general_insertent(struct entry *entry);
99 static void general_printasm(FILE *fp, struct aggent *agg);
100 static int general_printc(FILE *fp, struct aggent *agg);
101 static int printblock(FILE *fp, struct aggent *agg);
102 static void usage(const char *progname) __dead2;
104 static TAILQ_HEAD(, entry) mainlst = TAILQ_HEAD_INITIALIZER(mainlst);
105 static TAILQ_HEAD(, aggent) fqueue = TAILQ_HEAD_INITIALIZER(fqueue);
108 * Use a float value in order to automatically promote operations
109 * to return a float value rather than use casts.
111 static float totalsamples;
114 * Identifies a string cointaining objdump's assembly printout.
117 isasminline(const char *str)
122 if (sscanf(str, " %p%n", &ptr, &nbytes) != 1)
124 if (str[nbytes] != ':' || isspace(str[nbytes + 1]) == 0)
130 * Identifies a string containing objdump's assembly printout
131 * for a new function.
134 newfunction(const char *str)
142 if (sscanf(str, "%p <%[^>:]>:%n", &ptr, fname, &nbytes) != 2)
148 * Create a new first-level aggregation object for a specified
151 static struct aggent *
152 agg_create(const char *name, u_int nsamples, uintptr_t start, uintptr_t end)
156 agg = calloc(1, sizeof(struct aggent));
159 agg->ag_name = strdup(name);
160 if (agg->ag_name == NULL) {
164 agg->ag_nsamples = nsamples;
165 agg->ag_ostart = start;
171 * Destroy a first-level aggregation object for a specified
175 agg_destroy(struct aggent *agg)
183 * Analyze the "objdump -d" output, locate functions and start
184 * printing out the assembly functions content.
185 * We do not use newfunction() because we actually need the
186 * function name in available form, but the heurstic used is
192 char buffer[LNBUFF], fname[FNBUFF];
196 while (fgets(buffer, LNBUFF, fp) != NULL) {
197 if (isspace(buffer[0]))
199 if (sscanf(buffer, "%p <%[^>:]>:", &ptr, fname) != 2)
201 agg = fqueue_findent_by_name(fname);
204 agg->ag_offset = ftell(fp);
207 TAILQ_FOREACH(agg, &fqueue, ag_fiter) {
208 if (fseek(fp, agg->ag_offset, SEEK_SET) == -1)
210 printf("Profile trace for function: %s() [%.2f%%]\n",
211 agg->ag_name, PERCSAMP(agg->ag_nsamples));
212 general_printasm(fp, agg);
218 * Analyze the "objdump -S" output, locate functions and start
219 * printing out the C functions content.
220 * We do not use newfunction() because we actually need the
221 * function name in available form, but the heurstic used is
223 * In order to maintain the printout sorted, on the first pass it
224 * simply stores the file offsets in order to fastly moved later
225 * (when the file is hot-cached also) when the real printout will
231 char buffer[LNBUFF], fname[FNBUFF];
235 while (fgets(buffer, LNBUFF, fp) != NULL) {
236 if (isspace(buffer[0]))
238 if (sscanf(buffer, "%p <%[^>:]>:", &ptr, fname) != 2)
240 agg = fqueue_findent_by_name(fname);
243 agg->ag_offset = ftell(fp);
246 TAILQ_FOREACH(agg, &fqueue, ag_fiter) {
247 if (fseek(fp, agg->ag_offset, SEEK_SET) == -1)
249 printf("Profile trace for function: %s() [%.2f%%]\n",
250 agg->ag_name, PERCSAMP(agg->ag_nsamples));
251 if (general_printc(fp, agg) == -1)
259 * Bump the number of samples for any raw entry.
262 entry_acqref(struct entry *entry)
265 entry->en_nsamples++;
269 * Create a new raw entry object for a specified function.
271 static struct entry *
272 entry_create(const char *name, uintptr_t pc, uintptr_t start, uintptr_t end)
276 obj = calloc(1, sizeof(struct entry));
279 obj->en_name = strdup(name);
280 if (obj->en_name == NULL) {
285 obj->en_ostart = start;
287 obj->en_nsamples = 1;
292 * Destroy a raw entry object for a specified function.
295 entry_destroy(struct entry *entry)
298 free(entry->en_name);
303 * Specify a lower bound in percentage and drop from the
304 * first-level aggregation queue all the objects with a
308 fqueue_compact(float th)
311 struct aggent *agg, *tmpagg;
313 if (totalsamples == 0)
316 /* Revert the percentage calculation. */
317 thi = th * totalsamples / 100;
318 TAILQ_FOREACH_SAFE(agg, &fqueue, ag_fiter, tmpagg)
319 if (agg->ag_nsamples < thi)
320 TAILQ_REMOVE(&fqueue, agg, ag_fiter);
324 * Flush the first-level aggregates queue.
327 fqueue_deleteall(void)
331 while (TAILQ_EMPTY(&fqueue) == 0) {
332 agg = TAILQ_FIRST(&fqueue);
333 TAILQ_REMOVE(&fqueue, agg, ag_fiter);
338 * Insert a raw entry into the aggregations queue.
339 * If the respective first-level aggregation object
340 * does not exist create it and maintain it sorted
341 * in respect of the number of samples.
344 fqueue_insertent(struct entry *entry)
346 struct aggent *obj, *tmp;
350 TAILQ_FOREACH(obj, &fqueue, ag_fiter)
351 if (!strcmp(obj->ag_name, entry->en_name)) {
353 obj->ag_nsamples += entry->en_nsamples;
358 * If the first-level aggregation object already exists,
359 * just aggregate the samples and, if needed, resort
363 TAILQ_REMOVE(&fqueue, obj, ag_fiter);
365 TAILQ_FOREACH(tmp, &fqueue, ag_fiter)
366 if (obj->ag_nsamples > tmp->ag_nsamples) {
371 TAILQ_INSERT_BEFORE(tmp, obj, ag_fiter);
373 TAILQ_INSERT_TAIL(&fqueue, obj, ag_fiter);
378 * If the first-level aggregation object does not
379 * exist, create it and put in the sorted queue.
380 * If this is the first object, we need to set the
383 obj = agg_create(entry->en_name, entry->en_nsamples, entry->en_ostart,
387 if (TAILQ_EMPTY(&fqueue) != 0) {
388 TAILQ_INSERT_HEAD(&fqueue, obj, ag_fiter);
391 TAILQ_FOREACH(tmp, &fqueue, ag_fiter)
392 if (obj->ag_nsamples > tmp->ag_nsamples) {
397 TAILQ_INSERT_BEFORE(tmp, obj, ag_fiter);
399 TAILQ_INSERT_TAIL(&fqueue, obj, ag_fiter);
404 * Lookup a first-level aggregation object by name.
406 static struct aggent *
407 fqueue_findent_by_name(const char *name)
411 TAILQ_FOREACH(obj, &fqueue, ag_fiter)
412 if (!strcmp(obj->ag_name, name))
418 * Return the number of object in the first-level aggregations queue.
421 fqueue_getall(const char *bin, char *temp, int asmf)
423 char tmpf[MAXPATHLEN * 2 + 50];
425 uintptr_t start, end;
427 if (mkstemp(temp) == -1)
429 TAILQ_FOREACH(agg, &fqueue, ag_fiter) {
430 bzero(tmpf, sizeof(tmpf));
431 start = agg->ag_ostart;
435 * Fix-up the end address in order to show it in the objdump's
440 snprintf(tmpf, sizeof(tmpf),
441 "objdump --start-address=%p "
442 "--stop-address=%p -d %s >> %s", (void *)start,
443 (void *)end, bin, temp);
445 snprintf(tmpf, sizeof(tmpf),
446 "objdump --start-address=%p "
447 "--stop-address=%p -S %s >> %s", (void *)start,
448 (void *)end, bin, temp);
449 if (system(tmpf) != 0)
456 * Insert all the raw entries present in the general queue
457 * into the first-level aggregations queue.
460 fqueue_insertgen(void)
464 TAILQ_FOREACH(obj, &mainlst, en_iter)
465 if (fqueue_insertent(obj) == -1)
471 * Flush the raw entries general queue.
474 general_deleteall(void)
478 while (TAILQ_EMPTY(&mainlst) == 0) {
479 obj = TAILQ_FIRST(&mainlst);
480 TAILQ_REMOVE(&mainlst, obj, en_iter);
485 * Lookup a raw entry by the PC.
487 static struct entry *
488 general_findent(uintptr_t pc)
492 TAILQ_FOREACH(obj, &mainlst, en_iter)
493 if (obj->en_pc == pc)
499 * Insert a new raw entry in the general queue.
502 general_insertent(struct entry *entry)
505 TAILQ_INSERT_TAIL(&mainlst, entry, en_iter);
509 * Printout the body of an "objdump -d" assembly function.
510 * It does simply stops when a new function is encountered,
511 * bringing back the file position in order to not mess up
512 * subsequent analysis.
513 * C lines and others not recognized are simply skipped.
516 general_printasm(FILE *fp, struct aggent *agg)
523 while (fgets(buffer, LNBUFF, fp) != NULL) {
524 if ((nbytes = newfunction(buffer)) != 0) {
525 fseek(fp, nbytes * -1, SEEK_CUR);
528 if (!isasminline(buffer))
530 if (sscanf(buffer, " %p:", &ptr) != 1)
532 obj = general_findent((uintptr_t)ptr);
534 printf("\t| %s", buffer);
536 printf("%.2f%%\t| %s",
537 (float)obj->en_nsamples * 100 / agg->ag_nsamples,
543 * Printout the body of an "objdump -S" function.
544 * It does simply stops when a new function is encountered,
545 * bringing back the file position in order to not mess up
546 * subsequent analysis.
547 * It expect from the starting to the end to find, always, valid blocks
548 * (see below for an explanation of the "block" concept).
551 general_printc(FILE *fp, struct aggent *agg)
555 while (fgets(buffer, LNBUFF, fp) != NULL) {
556 fseek(fp, strlen(buffer) * -1, SEEK_CUR);
557 if (newfunction(buffer) != 0)
559 if (printblock(fp, agg) == -1)
566 * Printout a single block inside an "objdump -S" function.
567 * The block is composed of a first part in C and subsequent translation
569 * This code also operates a second-level aggregation packing together
570 * samples relative to PCs into a (lower bottom) block with their
571 * C (higher half) counterpart.
574 printblock(FILE *fp, struct aggent *agg)
580 int done, nbytes, sentinel;
584 * We expect the first thing of the block is C code, so simply give
585 * up if asm line is found.
590 if (fgets(buffer, LNBUFF, fp) == NULL)
592 if (isasminline(buffer) != 0)
595 nbytes = newfunction(buffer);
597 if (fseek(fp, nbytes * -1, SEEK_CUR) == -1)
604 * If the sentinel is not set, it means it did not match any
605 * "high half" for this code so simply give up.
606 * Operates the second-level aggregation.
612 if (sscanf(buffer, " %p:", &ptr) != 1)
614 obj = general_findent((uintptr_t)ptr);
616 tnsamples += obj->en_nsamples;
617 } while (fgets(buffer, LNBUFF, fp) != NULL && isasminline(buffer) != 0);
619 /* Rewind to the start of the block in order to start the printout. */
620 if (fseek(fp, lstart, SEEK_SET) == -1)
623 /* Again the high half of the block rappresenting the C part. */
625 while (fgets(buffer, LNBUFF, fp) != NULL && isasminline(buffer) == 0) {
626 if (tnsamples == 0 || done != 0)
627 printf("\t| %s", buffer);
630 printf("%.2f%%\t| %s",
631 (float)tnsamples * 100 / agg->ag_nsamples, buffer);
636 * Again the low half of the block rappresenting the asm
640 if (fgets(buffer, LNBUFF, fp) == NULL)
642 if (isasminline(buffer) == 0)
644 nbytes = newfunction(buffer);
646 if (fseek(fp, nbytes * -1, SEEK_CUR) == -1)
651 if (fseek(fp, strlen(buffer) * -1, SEEK_CUR) == -1)
657 * Helper printout functions.
660 usage(const char *progname)
664 "usage: %s [-a] [-h] [-k kfile] [-l lb] pmcraw.out binary\n",
670 main(int argc, char *argv[])
672 char buffer[LNBUFF], fname[FNBUFF], tbfl[] = TMPPATH, tofl[] = TMPPATH;
673 char tmpf[MAXPATHLEN * 2 + 50];
675 char *bin, *exec, *kfile, *ofile;
678 void *ptr, *hstart, *hend;
679 uintptr_t tmppc, ostart, oend;
688 while ((cget = getopt(argc, argv, "ahl:k:")) != -1)
697 limit = (float)atof(optarg);
711 if (access(bin, R_OK | F_OK) == -1)
712 FATAL(exec, "%s: Impossible to locate the binary file\n",
714 if (access(ofile, R_OK | F_OK) == -1)
715 FATAL(exec, "%s: Impossible to locate the pmcstat file\n",
717 if (kfile != NULL && access(kfile, R_OK | F_OK) == -1)
718 FATAL(exec, "%s: Impossible to locate the kernel file\n",
721 bzero(tmpf, sizeof(tmpf));
722 if (mkstemp(tofl) == -1)
723 FATAL(exec, "%s: Impossible to create the tmp file\n",
726 snprintf(tmpf, sizeof(tmpf), "pmcstat -k %s -R %s -m %s",
729 snprintf(tmpf, sizeof(tmpf), "pmcstat -R %s -m %s", ofile,
731 if (system(tmpf) != 0)
732 FATAL(exec, "%s: Impossible to create the tmp file\n",
735 gfp = fopen(tofl, "r");
737 FATAL(exec, "%s: Impossible to open the map file\n",
741 * Make the collection of raw entries from a pmcstat mapped file.
742 * The heuristic here wants strings in the form:
743 * "addr funcname startfaddr endfaddr".
745 while (fgets(buffer, LNBUFF, gfp) != NULL) {
746 if (isspace(buffer[0]))
748 if (sscanf(buffer, "%p %s %p %p\n", &ptr, fname,
749 &hstart, &hend) != 4)
751 "%s: Invalid scan of function in the map file\n",
753 ostart = (uintptr_t)hstart;
754 oend = (uintptr_t)hend;
755 tmppc = (uintptr_t)ptr;
757 obj = general_findent(tmppc);
762 obj = entry_create(fname, tmppc, ostart, oend);
765 "%s: Impossible to create a new object\n", exec);
766 general_insertent(obj);
768 if (fclose(gfp) == EOF)
769 FATAL(exec, "%s: Impossible to close the filedesc\n",
771 if (remove(tofl) == -1)
772 FATAL(exec, "%s: Impossible to remove the tmpfile\n",
776 * Remove the loose end objects and feed the first-level aggregation
779 if (fqueue_insertgen() == -1)
780 FATAL(exec, "%s: Impossible to generate an analysis\n",
782 fqueue_compact(limit);
783 if (fqueue_getall(bin, tbfl, asmsrc) == -1)
784 FATAL(exec, "%s: Impossible to create the tmp file\n",
787 bfp = fopen(tbfl, "r");
789 FATAL(exec, "%s: Impossible to open the binary file\n",
794 else if (cparse(bfp) == -1)
795 FATAL(NULL, "%s: Invalid format for the C file\n", exec);
796 if (fclose(bfp) == EOF)
797 FATAL(exec, "%s: Impossible to close the filedesc\n",
799 if (remove(tbfl) == -1)
800 FATAL(exec, "%s: Impossible to remove the tmpfile\n",