cen64/util/cen64-profile2callgrind.cpp
2020-12-21 19:05:07 +02:00

187 lines
4 KiB
C++

//
// cen64-profile2callgrind: Convert a cen64 profile file into a callgrind one.
//
// CEN64: Cycle-Accurate Nintendo 64 Emulator.
// Copyright (C) 2018 Lauri Kasanen
//
// This file is subject to the terms and conditions defined in
// 'LICENSE', which is part of this source code package.
//
#define PACKAGE "" // work around bfd.h requirement
#include <bfd.h>
#include <limits.h>
#include <stdarg.h>
#include <stdio.h>
#include <stdint.h>
#include <stdlib.h>
#include <string.h>
#include <map>
#include <string>
using namespace std;
static void die(const char fmt[], ...) {
va_list ap;
va_start(ap, fmt);
vfprintf(stderr, fmt, ap);
va_end(ap);
exit(1);
}
static asymbol **syms = NULL;
static uint32_t num_syms = 0;
static bool found;
static const char *filename, *funcname;
static unsigned lineno, discriminator;
static void findsym(bfd *bin, asection *section, void *addrv) {
bfd_vma vma;
bfd_size_type size;
if (found)
return;
if ((bfd_get_section_flags(bin, section) & SEC_ALLOC) == 0)
return;
vma = bfd_get_section_vma(bin, section);
const uint32_t addr = *(uint32_t *) addrv;
if (addr < vma)
return;
size = bfd_get_section_size(section);
if (addr >= vma + size)
return;
found = bfd_find_nearest_line_discriminator(bin, section, syms, addr - vma,
&filename, &funcname,
&lineno, &discriminator);
}
int main(int argc, char **argv) {
string s;
map<uint32_t, string> funcs;
char buf[PATH_MAX];
if (argc != 3) {
die("Usage: %s my.z64.profile my.elf\n", argv[0]);
}
bfd_init();
//bfd_set_default_target("elf32-big");
bfd *bin = bfd_openr(argv[2], NULL);
if (!bin) die("Failed to open elf file\n");
bin->flags |= BFD_DECOMPRESS;
if (bfd_check_format(bin, bfd_archive) ||
!bfd_check_format_matches(bin, bfd_object, NULL))
die("Unable to get addresses from elf file\n");
const uint32_t storage = bfd_get_symtab_upper_bound(bin);
if (storage) {
syms = (asymbol **) malloc(storage);
num_syms = bfd_canonicalize_symtab(bin, syms);
}
s = "nm ";
s += argv[2];
s += " | grep -i \" t \"";
FILE *f = popen(s.c_str(), "r");
if (!f) die("Can't get functions\n");
// Add the bootloader
funcs[0x80000000] = "PIF bootloader";
while (fgets(buf, PATH_MAX, f)) {
uint32_t tmp;
if (sscanf(buf, "%x", &tmp) != 1)
die("Failed getting functions\n");
char *ptr = strrchr(buf, ' ');
if (!ptr)
die("Failed getting functions\n");
ptr++;
uint32_t len = strlen(ptr);
if (ptr[len - 1] == '\n')
ptr[len - 1] = '\0';
// Skip internal/libc magic parts
if (ptr[0] == '_' && ptr[1] == '_')
continue;
funcs[tmp] = ptr;
}
pclose(f);
strcpy(buf, argv[1]);
char *ptr = strstr(buf, ".profile");
if (ptr) {
*ptr = '\0';
}
strcat(buf, ".callgrind");
FILE *out = fopen(buf, "w");
if (!out) die("Can't open '%s' for output\n", buf);
fprintf(out, "# callgrind format\n");
fprintf(out, "cmd: %s\n", argv[2]);
fprintf(out, "events: instructions l1d-misses\n\n");
fprintf(out, "ob=%s\n\n", argv[2]);
uint64_t summary = 0, l1d_summary = 0;
// We have a list of functions, now turn the samples into C line info
f = fopen(argv[1], "r");
if (!f) die("Can't open profile file\n");
while (fgets(buf, PATH_MAX, f)) {
uint32_t addr;
uint64_t num, l1d_num;
if (sscanf(buf, "%x %lu %lu", &addr, &num, &l1d_num) != 3)
die("Malformed profile file\n");
summary += num;
l1d_summary += l1d_num;
found = false;
bfd_map_over_sections(bin, findsym, &addr);
if (found) {
const char *ptr = filename ? strrchr(filename, '/') : NULL;
if (ptr) {
ptr++;
fprintf(out, "fl=%s\n", ptr);
} else {
fprintf(out, "fl=??\n");
}
fprintf(out, "fn=%s\n", funcname);
fprintf(out, "%u %lu %lu\n\n", lineno, num, l1d_num);
} else {
fprintf(out, "fl=??\n");
map<uint32_t, string>::const_iterator it = funcs.lower_bound(addr);
it--;
fprintf(out, "fn=%s\n", it->second.c_str());
fprintf(out, "0 %lu %lu\n\n", num, l1d_num);
}
}
bfd_close(bin);
fprintf(out, "totals: %lu %lu\n", summary, l1d_summary);
fclose(f);
fclose(out);
free(syms);
return 0;
}