73702c3980
libpmc already returns an end address that is after the end of the last instruction of a function (on both amd64 and arm64) as the end address written to the annotate map file is computed as the start address of the symbol plus the size. Adding one could result in a curious failure where an entire function's contents in assembly was reduced instead to only the first instruction. The reason is that when the end instruction is bumped by one, objdump -d can append the first instruction of the next function in its output. However, since pmcannotate concatenates all of the objdump -d output from various functions into a single file which it then searches to find the assembly for a given file, if this additional trailer was earlier in the file than the full function, the trailer was chosen to represent the entire function resulting in the truncated listing of the function. Sponsored by: University of Cambridge, Google, Inc. Differential Revision: https://reviews.freebsd.org/D35399
814 lines
19 KiB
C
814 lines
19 KiB
C
/*-
|
|
* SPDX-License-Identifier: BSD-2-Clause-FreeBSD
|
|
*
|
|
* Copyright (c) 2008 Nokia Corporation
|
|
* All rights reserved.
|
|
*
|
|
* This software was developed by Attilio Rao for the IPSO project under
|
|
* contract to Nokia Corporation.
|
|
*
|
|
* Redistribution and use in source and binary forms, with or without
|
|
* modification, are permitted provided that the following conditions
|
|
* are met:
|
|
* 1. Redistributions of source code must retain the above copyright
|
|
* notice unmodified, this list of conditions, and the following
|
|
* disclaimer.
|
|
* 2. Redistributions in binary form must reproduce the above copyright
|
|
* notice, this list of conditions and the following disclaimer in the
|
|
* documentation and/or other materials provided with the distribution.
|
|
*
|
|
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
|
|
* IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
|
|
* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
|
|
* IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
|
|
* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
|
|
* NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
|
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
|
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
|
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
|
|
* THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
*
|
|
*/
|
|
|
|
#include <sys/cdefs.h>
|
|
__FBSDID("$FreeBSD$");
|
|
|
|
#include <sys/param.h>
|
|
#include <sys/queue.h>
|
|
|
|
#include <ctype.h>
|
|
#include <paths.h>
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
|
|
#include <unistd.h>
|
|
|
|
/* NB: Make sure FNBUFF is as large as LNBUFF, otherwise it could overflow */
|
|
#define FNBUFF 512
|
|
#define LNBUFF 512
|
|
|
|
#define TMPNAME "pmcannotate.XXXXXX"
|
|
|
|
#define FATAL(ptr, x ...) do { \
|
|
fqueue_deleteall(); \
|
|
general_deleteall(); \
|
|
if ((ptr) != NULL) \
|
|
perror(ptr); \
|
|
fprintf(stderr, ##x); \
|
|
remove(tbfl); \
|
|
remove(tofl); \
|
|
exit(EXIT_FAILURE); \
|
|
} while (0)
|
|
|
|
#define PERCSAMP(x) ((x) * 100 / totalsamples)
|
|
|
|
struct entry {
|
|
TAILQ_ENTRY(entry) en_iter;
|
|
char *en_name;
|
|
uintptr_t en_pc;
|
|
uintptr_t en_ostart;
|
|
uintptr_t en_oend;
|
|
u_int en_nsamples;
|
|
};
|
|
|
|
struct aggent {
|
|
TAILQ_ENTRY(aggent) ag_fiter;
|
|
long ag_offset;
|
|
uintptr_t ag_ostart;
|
|
uintptr_t ag_oend;
|
|
char *ag_name;
|
|
u_int ag_nsamples;
|
|
};
|
|
|
|
static struct aggent *agg_create(const char *name, u_int nsamples,
|
|
uintptr_t start, uintptr_t end);
|
|
static void agg_destroy(struct aggent *agg) __unused;
|
|
static void asmparse(FILE *fp);
|
|
static int cparse(FILE *fp);
|
|
static void entry_acqref(struct entry *entry);
|
|
static struct entry *entry_create(const char *name, uintptr_t pc,
|
|
uintptr_t start, uintptr_t end);
|
|
static void entry_destroy(struct entry *entry) __unused;
|
|
static void fqueue_compact(float th);
|
|
static void fqueue_deleteall(void);
|
|
static struct aggent *fqueue_findent_by_name(const char *name);
|
|
static int fqueue_getall(const char *bin, char *temp, int asmf);
|
|
static int fqueue_insertent(struct entry *entry);
|
|
static int fqueue_insertgen(void);
|
|
static void general_deleteall(void);
|
|
static struct entry *general_findent(uintptr_t pc);
|
|
static void general_insertent(struct entry *entry);
|
|
static void general_printasm(FILE *fp, struct aggent *agg);
|
|
static int general_printc(FILE *fp, struct aggent *agg);
|
|
static int printblock(FILE *fp, struct aggent *agg);
|
|
static void usage(const char *progname) __dead2;
|
|
|
|
static TAILQ_HEAD(, entry) mainlst = TAILQ_HEAD_INITIALIZER(mainlst);
|
|
static TAILQ_HEAD(, aggent) fqueue = TAILQ_HEAD_INITIALIZER(fqueue);
|
|
|
|
/*
|
|
* Use a float value in order to automatically promote operations
|
|
* to return a float value rather than use casts.
|
|
*/
|
|
static float totalsamples;
|
|
|
|
/*
|
|
* Identifies a string cointaining objdump's assembly printout.
|
|
*/
|
|
static inline int
|
|
isasminline(const char *str)
|
|
{
|
|
void *ptr;
|
|
int nbytes;
|
|
|
|
if (sscanf(str, " %p%n", &ptr, &nbytes) != 1)
|
|
return (0);
|
|
if (str[nbytes] != ':' || isspace(str[nbytes + 1]) == 0)
|
|
return (0);
|
|
return (1);
|
|
}
|
|
|
|
/*
|
|
* Identifies a string containing objdump's assembly printout
|
|
* for a new function.
|
|
*/
|
|
static inline int
|
|
newfunction(const char *str)
|
|
{
|
|
char fname[FNBUFF];
|
|
void *ptr;
|
|
int nbytes;
|
|
|
|
if (isspace(str[0]))
|
|
return (0);
|
|
if (sscanf(str, "%p <%[^>:]>:%n", &ptr, fname, &nbytes) != 2)
|
|
return (0);
|
|
return (nbytes);
|
|
}
|
|
|
|
/*
|
|
* Create a new first-level aggregation object for a specified
|
|
* function.
|
|
*/
|
|
static struct aggent *
|
|
agg_create(const char *name, u_int nsamples, uintptr_t start, uintptr_t end)
|
|
{
|
|
struct aggent *agg;
|
|
|
|
agg = calloc(1, sizeof(struct aggent));
|
|
if (agg == NULL)
|
|
return (NULL);
|
|
agg->ag_name = strdup(name);
|
|
if (agg->ag_name == NULL) {
|
|
free(agg);
|
|
return (NULL);
|
|
}
|
|
agg->ag_nsamples = nsamples;
|
|
agg->ag_ostart = start;
|
|
agg->ag_oend = end;
|
|
return (agg);
|
|
}
|
|
|
|
/*
|
|
* Destroy a first-level aggregation object for a specified
|
|
* function.
|
|
*/
|
|
static void
|
|
agg_destroy(struct aggent *agg)
|
|
{
|
|
|
|
free(agg->ag_name);
|
|
free(agg);
|
|
}
|
|
|
|
/*
|
|
* Analyze the "objdump -d" output, locate functions and start
|
|
* printing out the assembly functions content.
|
|
* We do not use newfunction() because we actually need the
|
|
* function name in available form, but the heurstic used is
|
|
* the same.
|
|
*/
|
|
static void
|
|
asmparse(FILE *fp)
|
|
{
|
|
char buffer[LNBUFF], fname[FNBUFF];
|
|
struct aggent *agg;
|
|
void *ptr;
|
|
|
|
while (fgets(buffer, LNBUFF, fp) != NULL) {
|
|
if (isspace(buffer[0]))
|
|
continue;
|
|
if (sscanf(buffer, "%p <%[^>:]>:", &ptr, fname) != 2)
|
|
continue;
|
|
agg = fqueue_findent_by_name(fname);
|
|
if (agg == NULL)
|
|
continue;
|
|
agg->ag_offset = ftell(fp);
|
|
}
|
|
|
|
TAILQ_FOREACH(agg, &fqueue, ag_fiter) {
|
|
if (fseek(fp, agg->ag_offset, SEEK_SET) == -1)
|
|
return;
|
|
printf("Profile trace for function: %s() [%.2f%%]\n",
|
|
agg->ag_name, PERCSAMP(agg->ag_nsamples));
|
|
general_printasm(fp, agg);
|
|
printf("\n");
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Analyze the "objdump -S" output, locate functions and start
|
|
* printing out the C functions content.
|
|
* We do not use newfunction() because we actually need the
|
|
* function name in available form, but the heurstic used is
|
|
* the same.
|
|
* In order to maintain the printout sorted, on the first pass it
|
|
* simply stores the file offsets in order to fastly moved later
|
|
* (when the file is hot-cached also) when the real printout will
|
|
* happen.
|
|
*/
|
|
static int
|
|
cparse(FILE *fp)
|
|
{
|
|
char buffer[LNBUFF], fname[FNBUFF];
|
|
struct aggent *agg;
|
|
void *ptr;
|
|
|
|
while (fgets(buffer, LNBUFF, fp) != NULL) {
|
|
if (isspace(buffer[0]))
|
|
continue;
|
|
if (sscanf(buffer, "%p <%[^>:]>:", &ptr, fname) != 2)
|
|
continue;
|
|
agg = fqueue_findent_by_name(fname);
|
|
if (agg == NULL)
|
|
continue;
|
|
agg->ag_offset = ftell(fp);
|
|
}
|
|
|
|
TAILQ_FOREACH(agg, &fqueue, ag_fiter) {
|
|
if (fseek(fp, agg->ag_offset, SEEK_SET) == -1)
|
|
return (-1);
|
|
printf("Profile trace for function: %s() [%.2f%%]\n",
|
|
agg->ag_name, PERCSAMP(agg->ag_nsamples));
|
|
if (general_printc(fp, agg) == -1)
|
|
return (-1);
|
|
printf("\n");
|
|
}
|
|
return (0);
|
|
}
|
|
|
|
/*
|
|
* Bump the number of samples for any raw entry.
|
|
*/
|
|
static void
|
|
entry_acqref(struct entry *entry)
|
|
{
|
|
|
|
entry->en_nsamples++;
|
|
}
|
|
|
|
/*
|
|
* Create a new raw entry object for a specified function.
|
|
*/
|
|
static struct entry *
|
|
entry_create(const char *name, uintptr_t pc, uintptr_t start, uintptr_t end)
|
|
{
|
|
struct entry *obj;
|
|
|
|
obj = calloc(1, sizeof(struct entry));
|
|
if (obj == NULL)
|
|
return (NULL);
|
|
obj->en_name = strdup(name);
|
|
if (obj->en_name == NULL) {
|
|
free(obj);
|
|
return (NULL);
|
|
}
|
|
obj->en_pc = pc;
|
|
obj->en_ostart = start;
|
|
obj->en_oend = end;
|
|
obj->en_nsamples = 1;
|
|
return (obj);
|
|
}
|
|
|
|
/*
|
|
* Destroy a raw entry object for a specified function.
|
|
*/
|
|
static void
|
|
entry_destroy(struct entry *entry)
|
|
{
|
|
|
|
free(entry->en_name);
|
|
free(entry);
|
|
}
|
|
|
|
/*
|
|
* Specify a lower bound in percentage and drop from the
|
|
* first-level aggregation queue all the objects with a
|
|
* smaller impact.
|
|
*/
|
|
static void
|
|
fqueue_compact(float th)
|
|
{
|
|
u_int thi;
|
|
struct aggent *agg, *tmpagg;
|
|
|
|
if (totalsamples == 0)
|
|
return;
|
|
|
|
/* Revert the percentage calculation. */
|
|
thi = th * totalsamples / 100;
|
|
TAILQ_FOREACH_SAFE(agg, &fqueue, ag_fiter, tmpagg)
|
|
if (agg->ag_nsamples < thi)
|
|
TAILQ_REMOVE(&fqueue, agg, ag_fiter);
|
|
}
|
|
|
|
/*
|
|
* Flush the first-level aggregates queue.
|
|
*/
|
|
static void
|
|
fqueue_deleteall(void)
|
|
{
|
|
struct aggent *agg;
|
|
|
|
while (TAILQ_EMPTY(&fqueue) == 0) {
|
|
agg = TAILQ_FIRST(&fqueue);
|
|
TAILQ_REMOVE(&fqueue, agg, ag_fiter);
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Insert a raw entry into the aggregations queue.
|
|
* If the respective first-level aggregation object
|
|
* does not exist create it and maintain it sorted
|
|
* in respect of the number of samples.
|
|
*/
|
|
static int
|
|
fqueue_insertent(struct entry *entry)
|
|
{
|
|
struct aggent *obj, *tmp;
|
|
int found;
|
|
|
|
found = 0;
|
|
TAILQ_FOREACH(obj, &fqueue, ag_fiter)
|
|
if (!strcmp(obj->ag_name, entry->en_name)) {
|
|
found = 1;
|
|
obj->ag_nsamples += entry->en_nsamples;
|
|
break;
|
|
}
|
|
|
|
/*
|
|
* If the first-level aggregation object already exists,
|
|
* just aggregate the samples and, if needed, resort
|
|
* it.
|
|
*/
|
|
if (found) {
|
|
TAILQ_REMOVE(&fqueue, obj, ag_fiter);
|
|
found = 0;
|
|
TAILQ_FOREACH(tmp, &fqueue, ag_fiter)
|
|
if (obj->ag_nsamples > tmp->ag_nsamples) {
|
|
found = 1;
|
|
break;
|
|
}
|
|
if (found)
|
|
TAILQ_INSERT_BEFORE(tmp, obj, ag_fiter);
|
|
else
|
|
TAILQ_INSERT_TAIL(&fqueue, obj, ag_fiter);
|
|
return (0);
|
|
}
|
|
|
|
/*
|
|
* If the first-level aggregation object does not
|
|
* exist, create it and put in the sorted queue.
|
|
* If this is the first object, we need to set the
|
|
* head of the queue.
|
|
*/
|
|
obj = agg_create(entry->en_name, entry->en_nsamples, entry->en_ostart,
|
|
entry->en_oend);
|
|
if (obj == NULL)
|
|
return (-1);
|
|
if (TAILQ_EMPTY(&fqueue) != 0) {
|
|
TAILQ_INSERT_HEAD(&fqueue, obj, ag_fiter);
|
|
return (0);
|
|
}
|
|
TAILQ_FOREACH(tmp, &fqueue, ag_fiter)
|
|
if (obj->ag_nsamples > tmp->ag_nsamples) {
|
|
found = 1;
|
|
break;
|
|
}
|
|
if (found)
|
|
TAILQ_INSERT_BEFORE(tmp, obj, ag_fiter);
|
|
else
|
|
TAILQ_INSERT_TAIL(&fqueue, obj, ag_fiter);
|
|
return (0);
|
|
}
|
|
|
|
/*
|
|
* Lookup a first-level aggregation object by name.
|
|
*/
|
|
static struct aggent *
|
|
fqueue_findent_by_name(const char *name)
|
|
{
|
|
struct aggent *obj;
|
|
|
|
TAILQ_FOREACH(obj, &fqueue, ag_fiter)
|
|
if (!strcmp(obj->ag_name, name))
|
|
return (obj);
|
|
return (NULL);
|
|
}
|
|
|
|
/*
|
|
* Return the number of object in the first-level aggregations queue.
|
|
*/
|
|
static int
|
|
fqueue_getall(const char *bin, char *temp, int asmf)
|
|
{
|
|
char tmpf[MAXPATHLEN * 2 + 50];
|
|
struct aggent *agg;
|
|
uintptr_t start, end;
|
|
|
|
if (mkstemp(temp) == -1)
|
|
return (-1);
|
|
TAILQ_FOREACH(agg, &fqueue, ag_fiter) {
|
|
bzero(tmpf, sizeof(tmpf));
|
|
start = agg->ag_ostart;
|
|
end = agg->ag_oend;
|
|
|
|
if (asmf)
|
|
snprintf(tmpf, sizeof(tmpf),
|
|
"objdump --start-address=%p "
|
|
"--stop-address=%p -d %s >> %s", (void *)start,
|
|
(void *)end, bin, temp);
|
|
else
|
|
snprintf(tmpf, sizeof(tmpf),
|
|
"objdump --start-address=%p "
|
|
"--stop-address=%p -S %s >> %s", (void *)start,
|
|
(void *)end, bin, temp);
|
|
if (system(tmpf) != 0)
|
|
return (-1);
|
|
}
|
|
return (0);
|
|
}
|
|
|
|
/*
|
|
* Insert all the raw entries present in the general queue
|
|
* into the first-level aggregations queue.
|
|
*/
|
|
static int
|
|
fqueue_insertgen(void)
|
|
{
|
|
struct entry *obj;
|
|
|
|
TAILQ_FOREACH(obj, &mainlst, en_iter)
|
|
if (fqueue_insertent(obj) == -1)
|
|
return (-1);
|
|
return (0);
|
|
}
|
|
|
|
/*
|
|
* Flush the raw entries general queue.
|
|
*/
|
|
static void
|
|
general_deleteall(void)
|
|
{
|
|
struct entry *obj;
|
|
|
|
while (TAILQ_EMPTY(&mainlst) == 0) {
|
|
obj = TAILQ_FIRST(&mainlst);
|
|
TAILQ_REMOVE(&mainlst, obj, en_iter);
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Lookup a raw entry by the PC.
|
|
*/
|
|
static struct entry *
|
|
general_findent(uintptr_t pc)
|
|
{
|
|
struct entry *obj;
|
|
|
|
TAILQ_FOREACH(obj, &mainlst, en_iter)
|
|
if (obj->en_pc == pc)
|
|
return (obj);
|
|
return (NULL);
|
|
}
|
|
|
|
/*
|
|
* Insert a new raw entry in the general queue.
|
|
*/
|
|
static void
|
|
general_insertent(struct entry *entry)
|
|
{
|
|
|
|
TAILQ_INSERT_TAIL(&mainlst, entry, en_iter);
|
|
}
|
|
|
|
/*
|
|
* Printout the body of an "objdump -d" assembly function.
|
|
* It does simply stops when a new function is encountered,
|
|
* bringing back the file position in order to not mess up
|
|
* subsequent analysis.
|
|
* C lines and others not recognized are simply skipped.
|
|
*/
|
|
static void
|
|
general_printasm(FILE *fp, struct aggent *agg)
|
|
{
|
|
char buffer[LNBUFF];
|
|
struct entry *obj;
|
|
int nbytes;
|
|
void *ptr;
|
|
|
|
while (fgets(buffer, LNBUFF, fp) != NULL) {
|
|
if ((nbytes = newfunction(buffer)) != 0) {
|
|
fseek(fp, nbytes * -1, SEEK_CUR);
|
|
break;
|
|
}
|
|
if (!isasminline(buffer))
|
|
continue;
|
|
if (sscanf(buffer, " %p:", &ptr) != 1)
|
|
continue;
|
|
obj = general_findent((uintptr_t)ptr);
|
|
if (obj == NULL)
|
|
printf("\t| %s", buffer);
|
|
else
|
|
printf("%.2f%%\t| %s",
|
|
(float)obj->en_nsamples * 100 / agg->ag_nsamples,
|
|
buffer);
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Printout the body of an "objdump -S" function.
|
|
* It does simply stops when a new function is encountered,
|
|
* bringing back the file position in order to not mess up
|
|
* subsequent analysis.
|
|
* It expect from the starting to the end to find, always, valid blocks
|
|
* (see below for an explanation of the "block" concept).
|
|
*/
|
|
static int
|
|
general_printc(FILE *fp, struct aggent *agg)
|
|
{
|
|
char buffer[LNBUFF];
|
|
|
|
while (fgets(buffer, LNBUFF, fp) != NULL) {
|
|
fseek(fp, strlen(buffer) * -1, SEEK_CUR);
|
|
if (newfunction(buffer) != 0)
|
|
break;
|
|
if (printblock(fp, agg) == -1)
|
|
return (-1);
|
|
}
|
|
return (0);
|
|
}
|
|
|
|
/*
|
|
* Printout a single block inside an "objdump -S" function.
|
|
* The block is composed of a first part in C and subsequent translation
|
|
* in assembly.
|
|
* This code also operates a second-level aggregation packing together
|
|
* samples relative to PCs into a (lower bottom) block with their
|
|
* C (higher half) counterpart.
|
|
*/
|
|
static int
|
|
printblock(FILE *fp, struct aggent *agg)
|
|
{
|
|
char buffer[LNBUFF];
|
|
long lstart;
|
|
struct entry *obj;
|
|
u_int tnsamples;
|
|
int done, nbytes, sentinel;
|
|
void *ptr;
|
|
|
|
/*
|
|
* We expect the first thing of the block is C code, so simply give
|
|
* up if asm line is found.
|
|
*/
|
|
lstart = ftell(fp);
|
|
sentinel = 0;
|
|
for (;;) {
|
|
if (fgets(buffer, LNBUFF, fp) == NULL)
|
|
return (0);
|
|
if (isasminline(buffer) != 0)
|
|
break;
|
|
sentinel = 1;
|
|
nbytes = newfunction(buffer);
|
|
if (nbytes != 0) {
|
|
if (fseek(fp, nbytes * -1, SEEK_CUR) == -1)
|
|
return (-1);
|
|
return (0);
|
|
}
|
|
}
|
|
|
|
/*
|
|
* If the sentinel is not set, it means it did not match any
|
|
* "high half" for this code so simply give up.
|
|
* Operates the second-level aggregation.
|
|
*/
|
|
tnsamples = 0;
|
|
do {
|
|
if (sentinel == 0)
|
|
return (-1);
|
|
if (sscanf(buffer, " %p:", &ptr) != 1)
|
|
return (-1);
|
|
obj = general_findent((uintptr_t)ptr);
|
|
if (obj != NULL)
|
|
tnsamples += obj->en_nsamples;
|
|
} while (fgets(buffer, LNBUFF, fp) != NULL && isasminline(buffer) != 0);
|
|
|
|
/* Rewind to the start of the block in order to start the printout. */
|
|
if (fseek(fp, lstart, SEEK_SET) == -1)
|
|
return (-1);
|
|
|
|
/* Again the high half of the block rappresenting the C part. */
|
|
done = 0;
|
|
while (fgets(buffer, LNBUFF, fp) != NULL && isasminline(buffer) == 0) {
|
|
if (tnsamples == 0 || done != 0)
|
|
printf("\t| %s", buffer);
|
|
else {
|
|
done = 1;
|
|
printf("%.2f%%\t| %s",
|
|
(float)tnsamples * 100 / agg->ag_nsamples, buffer);
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Again the low half of the block rappresenting the asm
|
|
* translation part.
|
|
*/
|
|
for (;;) {
|
|
if (fgets(buffer, LNBUFF, fp) == NULL)
|
|
return (0);
|
|
if (isasminline(buffer) == 0)
|
|
break;
|
|
nbytes = newfunction(buffer);
|
|
if (nbytes != 0) {
|
|
if (fseek(fp, nbytes * -1, SEEK_CUR) == -1)
|
|
return (-1);
|
|
return (0);
|
|
}
|
|
}
|
|
if (fseek(fp, strlen(buffer) * -1, SEEK_CUR) == -1)
|
|
return (-1);
|
|
return (0);
|
|
}
|
|
|
|
/*
|
|
* Helper printout functions.
|
|
*/
|
|
static void
|
|
usage(const char *progname)
|
|
{
|
|
|
|
fprintf(stderr,
|
|
"usage: %s [-a] [-h] [-k kfile] [-l lb] pmcraw.out binary\n",
|
|
progname);
|
|
exit(EXIT_SUCCESS);
|
|
}
|
|
|
|
int
|
|
main(int argc, char *argv[])
|
|
{
|
|
char buffer[LNBUFF], fname[FNBUFF];
|
|
char *tbfl, *tofl, *tmpdir;
|
|
char tmpf[MAXPATHLEN * 2 + 50];
|
|
float limit;
|
|
char *bin, *exec, *kfile, *ofile;
|
|
struct entry *obj;
|
|
FILE *gfp, *bfp;
|
|
void *ptr, *hstart, *hend;
|
|
uintptr_t tmppc, ostart, oend;
|
|
int cget, asmsrc;
|
|
|
|
exec = argv[0];
|
|
ofile = NULL;
|
|
bin = NULL;
|
|
kfile = NULL;
|
|
asmsrc = 0;
|
|
limit = 0.5;
|
|
while ((cget = getopt(argc, argv, "ahl:k:")) != -1)
|
|
switch(cget) {
|
|
case 'a':
|
|
asmsrc = 1;
|
|
break;
|
|
case 'k':
|
|
kfile = optarg;
|
|
break;
|
|
case 'l':
|
|
limit = (float)atof(optarg);
|
|
break;
|
|
case 'h':
|
|
case '?':
|
|
default:
|
|
usage(exec);
|
|
}
|
|
argc -= optind;
|
|
argv += optind;
|
|
if (argc != 2)
|
|
usage(exec);
|
|
ofile = argv[0];
|
|
bin = argv[1];
|
|
|
|
if (access(bin, R_OK | F_OK) == -1)
|
|
FATAL(exec, "%s: Impossible to locate the binary file\n",
|
|
exec);
|
|
if (access(ofile, R_OK | F_OK) == -1)
|
|
FATAL(exec, "%s: Impossible to locate the pmcstat file\n",
|
|
exec);
|
|
if (kfile != NULL && access(kfile, R_OK | F_OK) == -1)
|
|
FATAL(exec, "%s: Impossible to locate the kernel file\n",
|
|
exec);
|
|
|
|
bzero(tmpf, sizeof(tmpf));
|
|
tmpdir = getenv("TMPDIR");
|
|
if (tmpdir == NULL) {
|
|
asprintf(&tbfl, "%s/%s", _PATH_TMP, TMPNAME);
|
|
asprintf(&tofl, "%s/%s", _PATH_TMP, TMPNAME);
|
|
} else {
|
|
asprintf(&tbfl, "%s/%s", tmpdir, TMPNAME);
|
|
asprintf(&tofl, "%s/%s", tmpdir, TMPNAME);
|
|
}
|
|
if (tofl == NULL || tbfl == NULL)
|
|
FATAL(exec, "%s: Cannot create tempfile templates\n",
|
|
exec);
|
|
if (mkstemp(tofl) == -1)
|
|
FATAL(exec, "%s: Impossible to create the tmp file\n",
|
|
exec);
|
|
if (kfile != NULL)
|
|
snprintf(tmpf, sizeof(tmpf), "pmcstat -k %s -R %s -m %s",
|
|
kfile, ofile, tofl);
|
|
else
|
|
snprintf(tmpf, sizeof(tmpf), "pmcstat -R %s -m %s", ofile,
|
|
tofl);
|
|
if (system(tmpf) != 0)
|
|
FATAL(exec, "%s: Impossible to create the tmp file\n",
|
|
exec);
|
|
|
|
gfp = fopen(tofl, "r");
|
|
if (gfp == NULL)
|
|
FATAL(exec, "%s: Impossible to open the map file\n",
|
|
exec);
|
|
|
|
/*
|
|
* Make the collection of raw entries from a pmcstat mapped file.
|
|
* The heuristic here wants strings in the form:
|
|
* "addr funcname startfaddr endfaddr".
|
|
*/
|
|
while (fgets(buffer, LNBUFF, gfp) != NULL) {
|
|
if (isspace(buffer[0]))
|
|
continue;
|
|
if (sscanf(buffer, "%p %s %p %p\n", &ptr, fname,
|
|
&hstart, &hend) != 4)
|
|
FATAL(NULL,
|
|
"%s: Invalid scan of function in the map file\n",
|
|
exec);
|
|
ostart = (uintptr_t)hstart;
|
|
oend = (uintptr_t)hend;
|
|
tmppc = (uintptr_t)ptr;
|
|
totalsamples++;
|
|
obj = general_findent(tmppc);
|
|
if (obj != NULL) {
|
|
entry_acqref(obj);
|
|
continue;
|
|
}
|
|
obj = entry_create(fname, tmppc, ostart, oend);
|
|
if (obj == NULL)
|
|
FATAL(exec,
|
|
"%s: Impossible to create a new object\n", exec);
|
|
general_insertent(obj);
|
|
}
|
|
if (fclose(gfp) == EOF)
|
|
FATAL(exec, "%s: Impossible to close the filedesc\n",
|
|
exec);
|
|
if (remove(tofl) == -1)
|
|
FATAL(exec, "%s: Impossible to remove the tmpfile\n",
|
|
exec);
|
|
|
|
/*
|
|
* Remove the loose end objects and feed the first-level aggregation
|
|
* queue.
|
|
*/
|
|
if (fqueue_insertgen() == -1)
|
|
FATAL(exec, "%s: Impossible to generate an analysis\n",
|
|
exec);
|
|
fqueue_compact(limit);
|
|
if (fqueue_getall(bin, tbfl, asmsrc) == -1)
|
|
FATAL(exec, "%s: Impossible to create the tmp file\n",
|
|
exec);
|
|
|
|
bfp = fopen(tbfl, "r");
|
|
if (bfp == NULL)
|
|
FATAL(exec, "%s: Impossible to open the binary file\n",
|
|
exec);
|
|
|
|
if (asmsrc != 0)
|
|
asmparse(bfp);
|
|
else if (cparse(bfp) == -1)
|
|
FATAL(NULL, "%s: Invalid format for the C file\n", exec);
|
|
if (fclose(bfp) == EOF)
|
|
FATAL(exec, "%s: Impossible to close the filedesc\n",
|
|
exec);
|
|
if (remove(tbfl) == -1)
|
|
FATAL(exec, "%s: Impossible to remove the tmpfile\n",
|
|
exec);
|
|
return (0);
|
|
}
|