xref: /freebsd/usr.sbin/pmcannotate/pmcannotate.c (revision e2afbc45258f2fa4bdcf126e959ac660e76fc802)
1 /*-
2  * SPDX-License-Identifier: BSD-2-Clause
3  *
4  * Copyright (c) 2008 Nokia Corporation
5  * All rights reserved.
6  *
7  * This software was developed by Attilio Rao for the IPSO project under
8  * contract to Nokia Corporation.
9  *
10  * Redistribution and use in source and binary forms, with or without
11  * modification, are permitted provided that the following conditions
12  * are met:
13  * 1. Redistributions of source code must retain the above copyright
14  *    notice unmodified, this list of conditions, and the following
15  *    disclaimer.
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.
19  *
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.
30  *
31  */
32 
33 #include <sys/param.h>
34 #include <sys/queue.h>
35 
36 #include <ctype.h>
37 #include <err.h>
38 #include <paths.h>
39 #include <stdio.h>
40 #include <stdlib.h>
41 #include <string.h>
42 
43 #include <unistd.h>
44 
45 /* NB: Make sure FNBUFF is as large as LNBUFF, otherwise it could overflow */
46 #define	FNBUFF	512
47 #define	LNBUFF	512
48 
49 #define	TMPNAME	"pmcannotate.XXXXXX"
50 
51 #define	FATAL(ptr, x ...) do {						\
52 	fqueue_deleteall();						\
53 	general_deleteall();						\
54 	if ((ptr) != NULL)						\
55 		perror(ptr);						\
56 	fprintf(stderr, ##x);						\
57 	if (tbfl != NULL)						\
58 		remove(tbfl);						\
59 	if (tofl != NULL)						\
60 		remove(tofl);						\
61 	exit(EXIT_FAILURE);						\
62 } while (0)
63 
64 #define	PERCSAMP(x)	((float)(x) * 100 / totalsamples)
65 
66 struct entry {
67         TAILQ_ENTRY(entry)	en_iter;
68         char		*en_name;
69 	uintptr_t	en_pc;
70 	uintptr_t	en_ostart;
71 	uintptr_t	en_oend;
72 	u_int		en_nsamples;
73 };
74 
75 struct aggent {
76 	TAILQ_ENTRY(aggent)	ag_fiter;
77 	long		ag_offset;
78 	uintptr_t	ag_ostart;
79 	uintptr_t	ag_oend;
80 	char		*ag_name;
81 	u_int		ag_nsamples;
82 };
83 
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;
106 
107 static TAILQ_HEAD(, entry) mainlst = TAILQ_HEAD_INITIALIZER(mainlst);
108 static TAILQ_HEAD(, aggent) fqueue = TAILQ_HEAD_INITIALIZER(fqueue);
109 
110 /*
111  * Use a float value in order to automatically promote operations
112  * to return a float value rather than use casts.
113  */
114 static u_int totalsamples;
115 
116 static enum { RAW, BLOCK_PERCENT, GLOBAL_PERCENT } print_mode;
117 
118 /*
119  * Identifies a string cointaining objdump's assembly printout.
120  */
121 static inline int
122 isasminline(const char *str)
123 {
124 	void *ptr;
125 	int nbytes;
126 
127 	if (sscanf(str, " %p%n", &ptr, &nbytes) != 1)
128 		return (0);
129 	if (str[nbytes] != ':' || isspace(str[nbytes + 1]) == 0)
130 		return (0);
131 	return (1);
132 }
133 
134 /*
135  * Identifies a string containing objdump's assembly printout
136  * for a new function.
137  */
138 static inline int
139 newfunction(const char *str)
140 {
141 	char fname[FNBUFF];
142 	void *ptr;
143 	int nbytes;
144 
145 	if (isspace(str[0]))
146 		return (0);
147 	if (sscanf(str, "%p <%[^>:]>:%n", &ptr, fname, &nbytes) != 2)
148 		return (0);
149 	return (nbytes);
150 }
151 
152 /*
153  * Create a new first-level aggregation object for a specified
154  * function.
155  */
156 static struct aggent *
157 agg_create(const char *name, u_int nsamples, uintptr_t start, uintptr_t end)
158 {
159 	struct aggent *agg;
160 
161 	agg = calloc(1, sizeof(struct aggent));
162 	if (agg == NULL)
163 		return (NULL);
164 	agg->ag_name = strdup(name);
165 	if (agg->ag_name == NULL) {
166 		free(agg);
167 		return (NULL);
168 	}
169 	agg->ag_nsamples = nsamples;
170 	agg->ag_ostart = start;
171 	agg->ag_oend = end;
172 	return (agg);
173 }
174 
175 /*
176  * Destroy a first-level aggregation object for a specified
177  * function.
178  */
179 static void
180 agg_destroy(struct aggent *agg)
181 {
182 
183 	free(agg->ag_name);
184 	free(agg);
185 }
186 
187 /*
188  * Analyze the "objdump -d" output, locate functions and start
189  * printing out the assembly functions content.
190  * We do not use newfunction() because we actually need the
191  * function name in available form, but the heurstic used is
192  * the same.
193  */
194 static void
195 asmparse(FILE *fp)
196 {
197 	char buffer[LNBUFF], fname[FNBUFF];
198 	struct aggent *agg;
199 	void *ptr;
200 
201 	while (fgets(buffer, LNBUFF, fp) != NULL) {
202 		if (isspace(buffer[0]))
203 			continue;
204 		if (sscanf(buffer, "%p <%[^>:]>:", &ptr, fname) != 2)
205 			continue;
206 		agg = fqueue_findent_by_name(fname);
207 		if (agg == NULL)
208 			continue;
209 		agg->ag_offset = ftell(fp);
210 	}
211 
212 	TAILQ_FOREACH(agg, &fqueue, ag_fiter) {
213 		if (fseek(fp, agg->ag_offset, SEEK_SET) == -1)
214 			return;
215 		printf("Profile trace for function: %s() [%.2f%%]\n",
216 		    agg->ag_name, PERCSAMP(agg->ag_nsamples));
217 		general_printasm(fp, agg);
218 		printf("\n");
219 	}
220 }
221 
222 /*
223  * Analyze the "objdump -S" output, locate functions and start
224  * printing out the C functions content.
225  * We do not use newfunction() because we actually need the
226  * function name in available form, but the heurstic used is
227  * the same.
228  * In order to maintain the printout sorted, on the first pass it
229  * simply stores the file offsets in order to fastly moved later
230  * (when the file is hot-cached also) when the real printout will
231  * happen.
232  */
233 static int
234 cparse(FILE *fp)
235 {
236 	char buffer[LNBUFF], fname[FNBUFF];
237 	struct aggent *agg;
238 	void *ptr;
239 
240 	while (fgets(buffer, LNBUFF, fp) != NULL) {
241 		if (isspace(buffer[0]))
242 			continue;
243 		if (sscanf(buffer, "%p <%[^>:]>:", &ptr, fname) != 2)
244 			continue;
245 		agg = fqueue_findent_by_name(fname);
246 		if (agg == NULL)
247 			continue;
248 		agg->ag_offset = ftell(fp);
249 	}
250 
251 	TAILQ_FOREACH(agg, &fqueue, ag_fiter) {
252 		if (fseek(fp, agg->ag_offset, SEEK_SET) == -1)
253 			return (-1);
254 		printf("Profile trace for function: %s() [%.2f%%]\n",
255 		    agg->ag_name, PERCSAMP(agg->ag_nsamples));
256 		if (general_printc(fp, agg) == -1)
257 			return (-1);
258 		printf("\n");
259 	}
260 	return (0);
261 }
262 
263 /*
264  * Bump the number of samples for any raw entry.
265  */
266 static void
267 entry_acqref(struct entry *entry)
268 {
269 
270 	entry->en_nsamples++;
271 }
272 
273 /*
274  * Create a new raw entry object for a specified function.
275  */
276 static struct entry *
277 entry_create(const char *name, uintptr_t pc, uintptr_t start, uintptr_t end)
278 {
279 	struct entry *obj;
280 
281 	obj = calloc(1, sizeof(struct entry));
282 	if (obj == NULL)
283 		return (NULL);
284 	obj->en_name = strdup(name);
285 	if (obj->en_name == NULL) {
286 		free(obj);
287 		return (NULL);
288 	}
289 	obj->en_pc = pc;
290 	obj->en_ostart = start;
291 	obj->en_oend = end;
292 	obj->en_nsamples = 1;
293 	return (obj);
294 }
295 
296 /*
297  * Destroy a raw entry object for a specified function.
298  */
299 static void
300 entry_destroy(struct entry *entry)
301 {
302 
303 	free(entry->en_name);
304 	free(entry);
305 }
306 
307 /*
308  * Specify a lower bound in percentage and drop from the
309  * first-level aggregation queue all the objects with a
310  * smaller impact.
311  */
312 static void
313 fqueue_compact(float th)
314 {
315 	u_int thi;
316 	struct aggent *agg, *tmpagg;
317 
318 	if (totalsamples == 0)
319 		return;
320 
321 	/* Revert the percentage calculation. */
322 	thi = th * totalsamples / 100;
323 	TAILQ_FOREACH_SAFE(agg, &fqueue, ag_fiter, tmpagg)
324 		if (agg->ag_nsamples < thi)
325 			TAILQ_REMOVE(&fqueue, agg, ag_fiter);
326 }
327 
328 /*
329  * Flush the first-level aggregates queue.
330  */
331 static void
332 fqueue_deleteall(void)
333 {
334 	struct aggent *agg;
335 
336 	while (TAILQ_EMPTY(&fqueue) == 0) {
337 		agg = TAILQ_FIRST(&fqueue);
338 		TAILQ_REMOVE(&fqueue, agg, ag_fiter);
339 	}
340 }
341 
342 /*
343  * Insert a raw entry into the aggregations queue.
344  * If the respective first-level aggregation object
345  * does not exist create it and maintain it sorted
346  * in respect of the number of samples.
347  */
348 static int
349 fqueue_insertent(struct entry *entry)
350 {
351 	struct aggent *obj, *tmp;
352 	int found;
353 
354 	found = 0;
355 	TAILQ_FOREACH(obj, &fqueue, ag_fiter)
356 		if (!strcmp(obj->ag_name, entry->en_name)) {
357 			found = 1;
358 			obj->ag_nsamples += entry->en_nsamples;
359 			break;
360 		}
361 
362 	/*
363 	 * If the first-level aggregation object already exists,
364 	 * just aggregate the samples and, if needed, resort
365 	 * it.
366 	 */
367 	if (found) {
368 		TAILQ_REMOVE(&fqueue, obj, ag_fiter);
369 		found = 0;
370 		TAILQ_FOREACH(tmp, &fqueue, ag_fiter)
371 			if (obj->ag_nsamples > tmp->ag_nsamples) {
372 				found = 1;
373 				break;
374 			}
375 		if (found)
376 			TAILQ_INSERT_BEFORE(tmp, obj, ag_fiter);
377 		else
378 			TAILQ_INSERT_TAIL(&fqueue, obj, ag_fiter);
379 		return (0);
380 	}
381 
382 	/*
383 	 * If the first-level aggregation object does not
384 	 * exist, create it and put in the sorted queue.
385 	 * If this is the first object, we need to set the
386 	 * head of the queue.
387 	 */
388 	obj = agg_create(entry->en_name, entry->en_nsamples, entry->en_ostart,
389 	    entry->en_oend);
390 	if (obj == NULL)
391 		return (-1);
392 	if (TAILQ_EMPTY(&fqueue) != 0) {
393 		TAILQ_INSERT_HEAD(&fqueue, obj, ag_fiter);
394 		return (0);
395 	}
396 	TAILQ_FOREACH(tmp, &fqueue, ag_fiter)
397 		if (obj->ag_nsamples > tmp->ag_nsamples) {
398 			found = 1;
399 			break;
400 		}
401 	if (found)
402 		TAILQ_INSERT_BEFORE(tmp, obj, ag_fiter);
403 	else
404 		TAILQ_INSERT_TAIL(&fqueue, obj, ag_fiter);
405 	return (0);
406 }
407 
408 /*
409  * Lookup a first-level aggregation object by name.
410  */
411 static struct aggent *
412 fqueue_findent_by_name(const char *name)
413 {
414 	struct aggent *obj;
415 
416 	TAILQ_FOREACH(obj, &fqueue, ag_fiter)
417 		if (!strcmp(obj->ag_name, name))
418 			return (obj);
419 	return (NULL);
420 }
421 
422 /*
423  * Return the number of object in the first-level aggregations queue.
424  */
425 static int
426 fqueue_getall(const char *bin, char *temp, int asmf)
427 {
428 	char tmpf[MAXPATHLEN * 2 + 50];
429 	struct aggent *agg;
430 	uintptr_t start, end;
431 
432 	if (mkstemp(temp) == -1)
433 		return (-1);
434 	TAILQ_FOREACH(agg, &fqueue, ag_fiter) {
435 		bzero(tmpf, sizeof(tmpf));
436 		start = agg->ag_ostart;
437 		end = agg->ag_oend;
438 
439 		if (asmf)
440 			snprintf(tmpf, sizeof(tmpf),
441 			    "objdump --start-address=%p "
442 			    "--stop-address=%p -d %s >> %s", (void *)start,
443 			    (void *)end, bin, temp);
444 		else
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)
450 			return (-1);
451 	}
452 	return (0);
453 }
454 
455 /*
456  * Insert all the raw entries present in the general queue
457  * into the first-level aggregations queue.
458  */
459 static int
460 fqueue_insertgen(void)
461 {
462 	struct entry *obj;
463 
464 	TAILQ_FOREACH(obj, &mainlst, en_iter)
465 		if (fqueue_insertent(obj) == -1)
466 			return (-1);
467 	return (0);
468 }
469 
470 /*
471  * Flush the raw entries general queue.
472  */
473 static void
474 general_deleteall(void)
475 {
476 	struct entry *obj;
477 
478 	while (TAILQ_EMPTY(&mainlst) == 0) {
479 		obj = TAILQ_FIRST(&mainlst);
480 		TAILQ_REMOVE(&mainlst, obj, en_iter);
481 	}
482 }
483 
484 /*
485  * Lookup a raw entry by the PC.
486  */
487 static struct entry *
488 general_findent(uintptr_t pc)
489 {
490 	struct entry *obj;
491 
492 	TAILQ_FOREACH(obj, &mainlst, en_iter)
493 		if (obj->en_pc == pc)
494 			return (obj);
495 	return (NULL);
496 }
497 
498 /*
499  * Insert a new raw entry in the general queue.
500  */
501 static void
502 general_insertent(struct entry *entry)
503 {
504 
505 	TAILQ_INSERT_TAIL(&mainlst, entry, en_iter);
506 }
507 
508 /*
509  * Return a string either holding a percentage or the raw count value.
510  */
511 static const char *
512 print_count(u_int nsamples, u_int totsamples)
513 {
514 	static char buf[16];
515 
516 	switch (print_mode) {
517 	case RAW:
518 		snprintf(buf, sizeof(buf), "%u", nsamples);
519 		break;
520 	case BLOCK_PERCENT:
521 		snprintf(buf, sizeof(buf), "%.2f%%", (float)nsamples * 100 /
522 		    totsamples);
523 		break;
524 	case GLOBAL_PERCENT:
525 		snprintf(buf, sizeof(buf), "%.2f%%", (float)nsamples * 100 /
526 		    totalsamples);
527 		break;
528 	}
529 	return (buf);
530 }
531 
532 /*
533  * Printout the body of an "objdump -d" assembly function.
534  * It does simply stops when a new function is encountered,
535  * bringing back the file position in order to not mess up
536  * subsequent analysis.
537  * C lines and others not recognized are simply skipped.
538  */
539 static void
540 general_printasm(FILE *fp, struct aggent *agg)
541 {
542 	char buffer[LNBUFF];
543 	struct entry *obj;
544 	int nbytes;
545 	void *ptr;
546 
547 	while (fgets(buffer, LNBUFF, fp) != NULL) {
548 		if ((nbytes = newfunction(buffer)) != 0) {
549 			fseek(fp, nbytes * -1, SEEK_CUR);
550 			break;
551 		}
552 		if (!isasminline(buffer))
553 			continue;
554 		if (sscanf(buffer, " %p:", &ptr) != 1)
555 			continue;
556 		obj = general_findent((uintptr_t)ptr);
557 		if (obj == NULL)
558 			printf("\t| %s", buffer);
559 		else
560 			printf("%7s | %s",
561 			    print_count(obj->en_nsamples, agg->ag_nsamples),
562 			    buffer);
563 	}
564 }
565 
566 /*
567  * Printout the body of an "objdump -S" function.
568  * It does simply stops when a new function is encountered,
569  * bringing back the file position in order to not mess up
570  * subsequent analysis.
571  * It expect from the starting to the end to find, always, valid blocks
572  * (see below for an explanation of the "block" concept).
573  */
574 static int
575 general_printc(FILE *fp, struct aggent *agg)
576 {
577 	char buffer[LNBUFF];
578 
579 	while (fgets(buffer, LNBUFF, fp) != NULL) {
580 		fseek(fp, strlen(buffer) * -1, SEEK_CUR);
581 		if (newfunction(buffer) != 0)
582 			break;
583 		if (printblock(fp, agg) == -1)
584 			return (-1);
585 	}
586 	return (0);
587 }
588 
589 /*
590  * Printout a single block inside an "objdump -S" function.
591  * The block is composed of a first part in C and subsequent translation
592  * in assembly.
593  * This code also operates a second-level aggregation packing together
594  * samples relative to PCs into a (lower bottom) block with their
595  * C (higher half) counterpart.
596  */
597 static int
598 printblock(FILE *fp, struct aggent *agg)
599 {
600 	char buffer[LNBUFF];
601 	long lstart;
602 	struct entry *obj;
603 	u_int tnsamples;
604 	int done, nbytes, sentinel;
605 	void *ptr;
606 
607 	/*
608 	 * We expect the first thing of the block is C code, so simply give
609 	 * up if asm line is found.
610 	 */
611 	lstart = ftell(fp);
612 	sentinel = 0;
613 	for (;;) {
614 		if (fgets(buffer, LNBUFF, fp) == NULL)
615 			return (0);
616 		if (isasminline(buffer) != 0)
617 			break;
618 		sentinel = 1;
619 		nbytes = newfunction(buffer);
620 		if (nbytes != 0) {
621 			if (fseek(fp, nbytes * -1, SEEK_CUR) == -1)
622 				return (-1);
623 			return (0);
624 		}
625 	}
626 
627 	/*
628 	 * If the sentinel is not set, it means it did not match any
629 	 * "high half" for this code so simply give up.
630 	 * Operates the second-level aggregation.
631 	 */
632 	tnsamples = 0;
633 	do {
634 		if (sentinel == 0)
635 			return (-1);
636 		if (sscanf(buffer, " %p:", &ptr) != 1)
637 			return (-1);
638 		obj = general_findent((uintptr_t)ptr);
639 		if (obj != NULL)
640 			tnsamples += obj->en_nsamples;
641 	} while (fgets(buffer, LNBUFF, fp) != NULL && isasminline(buffer) != 0);
642 
643 	/* Rewind to the start of the block in order to start the printout. */
644 	if (fseek(fp, lstart, SEEK_SET) == -1)
645 		return (-1);
646 
647 	/* Again the high half of the block rappresenting the C part. */
648 	done = 0;
649 	while (fgets(buffer, LNBUFF, fp) != NULL && isasminline(buffer) == 0) {
650 		if (tnsamples == 0 || done != 0)
651 			printf("\t| %s", buffer);
652 		else {
653 			done = 1;
654 			printf("%7s | %s",
655 			    print_count(tnsamples, agg->ag_nsamples), buffer);
656 		}
657 	}
658 
659 	/*
660 	 * Again the low half of the block rappresenting the asm
661 	 * translation part.
662 	 */
663 	for (;;) {
664 		if (fgets(buffer, LNBUFF, fp) == NULL)
665 			return (0);
666 		if (isasminline(buffer) == 0)
667 			break;
668 		nbytes = newfunction(buffer);
669 		if (nbytes != 0) {
670 			if (fseek(fp, nbytes * -1, SEEK_CUR) == -1)
671 				return (-1);
672 			return (0);
673 		}
674 	}
675 	if (fseek(fp, strlen(buffer) * -1, SEEK_CUR) == -1)
676 		return (-1);
677 	return (0);
678 }
679 
680 /*
681  * Helper printout functions.
682  */
683 static void
684 usage(const char *progname)
685 {
686 
687 	fprintf(stderr,
688 	    "usage: %s [-a] [-h] [-k kfile] [-l lb] [-m mode] pmcraw.out binary\n",
689 	    progname);
690 	exit(EXIT_SUCCESS);
691 }
692 
693 int
694 main(int argc, char *argv[])
695 {
696 	char buffer[LNBUFF], fname[FNBUFF];
697 	char *tbfl, *tofl, *tmpdir;
698 	char tmpf[MAXPATHLEN * 2 + 50];
699 	float limit;
700 	char *bin, *exec, *kfile, *ofile;
701 	struct entry *obj;
702 	FILE *gfp, *bfp;
703 	void *ptr, *hstart, *hend;
704 	uintptr_t tmppc, ostart, oend;
705 	int cget, asmsrc;
706 
707 	tbfl = NULL;
708 	tofl = NULL;
709 	exec = argv[0];
710 	ofile = NULL;
711 	bin = NULL;
712 	kfile = NULL;
713 	asmsrc = 0;
714 	limit = 0.5;
715 	print_mode = BLOCK_PERCENT;
716 	while ((cget = getopt(argc, argv, "ahl:m:k:")) != -1)
717 		switch(cget) {
718 		case 'a':
719 			asmsrc = 1;
720 			break;
721 		case 'k':
722 			kfile = optarg;
723 			break;
724 		case 'l':
725 			limit = (float)atof(optarg);
726 			break;
727 		case 'm':
728 			if (strcasecmp(optarg, "raw") == 0)
729 				print_mode = RAW;
730 			else if (strcasecmp(optarg, "global") == 0)
731 				print_mode = GLOBAL_PERCENT;
732 			else if (strcasecmp(optarg, "block") == 0)
733 				print_mode = BLOCK_PERCENT;
734 			else
735 				errx(1, "Invalid mode %s", optarg);
736 			break;
737 		case 'h':
738 		case '?':
739 		default:
740 			usage(exec);
741 		}
742 	argc -= optind;
743 	argv += optind;
744 	if (argc != 2)
745 		usage(exec);
746 	ofile = argv[0];
747 	bin = argv[1];
748 
749 	if (access(bin, R_OK | F_OK) == -1)
750 		FATAL(exec, "%s: Impossible to locate the binary file\n",
751 		    exec);
752 	if (access(ofile, R_OK | F_OK) == -1)
753 		FATAL(exec, "%s: Impossible to locate the pmcstat file\n",
754 		    exec);
755 	if (kfile != NULL && access(kfile, R_OK | F_OK) == -1)
756 		FATAL(exec, "%s: Impossible to locate the kernel file\n",
757 		    exec);
758 
759 	bzero(tmpf, sizeof(tmpf));
760 	tmpdir = getenv("TMPDIR");
761 	if (tmpdir == NULL) {
762 		asprintf(&tbfl, "%s/%s", _PATH_TMP, TMPNAME);
763 		asprintf(&tofl, "%s/%s", _PATH_TMP, TMPNAME);
764 	} else {
765 		asprintf(&tbfl, "%s/%s", tmpdir, TMPNAME);
766 		asprintf(&tofl, "%s/%s", tmpdir, TMPNAME);
767 	}
768 	if (tofl == NULL || tbfl == NULL)
769 		FATAL(exec, "%s: Cannot create tempfile templates\n",
770 		    exec);
771 	if (mkstemp(tofl) == -1)
772 		FATAL(exec, "%s: Impossible to create the tmp file\n",
773 		    exec);
774 	if (kfile != NULL)
775 		snprintf(tmpf, sizeof(tmpf), "pmcstat -k %s -R %s -m %s",
776 		    kfile, ofile, tofl);
777 	else
778 		snprintf(tmpf, sizeof(tmpf), "pmcstat -R %s -m %s", ofile,
779 		    tofl);
780 	if (system(tmpf) != 0)
781 		FATAL(exec, "%s: Impossible to create the tmp file\n",
782 		    exec);
783 
784 	gfp = fopen(tofl, "r");
785 	if (gfp == NULL)
786 		FATAL(exec, "%s: Impossible to open the map file\n",
787 		    exec);
788 
789 	/*
790 	 * Make the collection of raw entries from a pmcstat mapped file.
791 	 * The heuristic here wants strings in the form:
792 	 * "addr funcname startfaddr endfaddr".
793 	 */
794 	while (fgets(buffer, LNBUFF, gfp) != NULL) {
795 		if (isspace(buffer[0]))
796 			continue;
797 		if (sscanf(buffer, "%p %s %p %p\n", &ptr, fname,
798 		    &hstart, &hend) != 4)
799 			FATAL(NULL,
800 			    "%s: Invalid scan of function in the map file\n",
801 			    exec);
802 		ostart = (uintptr_t)hstart;
803 		oend = (uintptr_t)hend;
804 		tmppc = (uintptr_t)ptr;
805 		totalsamples++;
806 		obj = general_findent(tmppc);
807 		if (obj != NULL) {
808 			entry_acqref(obj);
809 			continue;
810 		}
811 		obj = entry_create(fname, tmppc, ostart, oend);
812 		if (obj == NULL)
813 			FATAL(exec,
814 			    "%s: Impossible to create a new object\n", exec);
815 		general_insertent(obj);
816 	}
817 	if (fclose(gfp) == EOF)
818 		FATAL(exec, "%s: Impossible to close the filedesc\n",
819 		    exec);
820 	if (remove(tofl) == -1)
821                 FATAL(exec, "%s: Impossible to remove the tmpfile\n",
822                     exec);
823 
824 	/*
825 	 * Remove the loose end objects and feed the first-level aggregation
826 	 * queue.
827 	 */
828 	if (fqueue_insertgen() == -1)
829 		FATAL(exec, "%s: Impossible to generate an analysis\n",
830 		    exec);
831 	fqueue_compact(limit);
832 	if (fqueue_getall(bin, tbfl, asmsrc) == -1)
833 		FATAL(exec, "%s: Impossible to create the tmp file\n",
834 		    exec);
835 
836 	bfp = fopen(tbfl, "r");
837 	if (bfp == NULL)
838 		FATAL(exec, "%s: Impossible to open the binary file\n",
839 		    exec);
840 
841 	if (asmsrc != 0)
842 		asmparse(bfp);
843 	else if (cparse(bfp) == -1)
844 		FATAL(NULL, "%s: Invalid format for the C file\n", exec);
845 	if (fclose(bfp) == EOF)
846                 FATAL(exec, "%s: Impossible to close the filedesc\n",
847                     exec);
848 	if (remove(tbfl) == -1)
849                 FATAL(exec, "%s: Impossible to remove the tmpfile\n",
850                     exec);
851 	return (0);
852 }
853