422 lines
15 KiB
C
422 lines
15 KiB
C
// Part of JoeyLib, Copyright (c) 2026 Scott Duensing, under the MIT License.
|
|
// See LICENSE.md for license information.
|
|
// SPDX-License-Identifier: MIT
|
|
//
|
|
// iigsProfile.c - name the IIgs profiler's PC samples, for
|
|
// scripts/profile-iigs.sh.
|
|
//
|
|
// iigsProfile SAMPLES MAP TOP
|
|
//
|
|
// SAMPLES holds the MAME script's lines: "S ADDRESS COUNT" (a sampled
|
|
// 24-bit pc and how often), "F BANK BYTES" (the first bytes of a bank as
|
|
// it ran, in hex), and "T TOTAL". MAP is link816's map: its global and
|
|
// local symbols by link address (a global wins an address both name);
|
|
// beside it MAP's base name with ".json" (the segments' link banks, else
|
|
// one segment at bank 0) and ".segN.bin" (each segment's image).
|
|
//
|
|
// GS/OS loads segments wherever it likes, so each running bank is matched
|
|
// to the segment whose image its first bytes best agree with (85% at
|
|
// least); a sample in such a bank is named by the symbol below it in that
|
|
// segment, else as segN+OFFSET, and one elsewhere by the bank it is in
|
|
// (ROM, the firmware and vectors, or GS/OS, the tools, and NTP). Prints
|
|
// the matches and the TOP places most sampled.
|
|
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
|
|
#include "gateArg.h"
|
|
#include "toolFile.h"
|
|
#include "toolJson.h"
|
|
|
|
#define TOOL_NAME "iigsProfile"
|
|
#define LINE_MAX_CHARS 8192u
|
|
#define NAME_MAX_CHARS 128u
|
|
#define WHERE_CHARS 160u
|
|
#define PATH_CHARS 4096u
|
|
// Room in a path for what is added to the map's base name.
|
|
#define SUFFIX_ROOM 32u
|
|
#define BASE_CHARS (PATH_CHARS - SUFFIX_ROOM)
|
|
#define BANKS 256u
|
|
#define BANK_SHIFT 16u
|
|
#define OFFSET_MASK 0xFFFFu
|
|
#define ADDRESS_DIGITS 6u
|
|
#define MATCH_BYTES 16u
|
|
#define MATCH_SCORE 0.85
|
|
#define PERCENT 100.0
|
|
#define ROM_FIRST 0xFCu
|
|
#define FIRMWARE_FIRST 0xE0u
|
|
#define FIRMWARE_LAST 0xE1u
|
|
#define HEX 16
|
|
#define START_COUNT 256u
|
|
#define MAP_SUFFIX ".map"
|
|
|
|
typedef struct SymbolT {
|
|
uint32_t address;
|
|
char name[NAME_MAX_CHARS];
|
|
bool global;
|
|
} SymbolT;
|
|
|
|
typedef struct SegmentT {
|
|
uint32_t number;
|
|
uint32_t linkBank;
|
|
uint8_t *image;
|
|
size_t len;
|
|
} SegmentT;
|
|
|
|
// A place samples were counted at, in the order first met.
|
|
typedef struct PlaceT {
|
|
char where[WHERE_CHARS];
|
|
uint32_t count;
|
|
} PlaceT;
|
|
|
|
// The samples: each address's count (the last line for it wins, in the
|
|
// place its first line had), the banks' fingerprints, and the total.
|
|
typedef struct SamplesT {
|
|
uint32_t *addresses;
|
|
uint32_t *counts;
|
|
size_t count;
|
|
size_t cap;
|
|
uint8_t *prints[BANKS];
|
|
size_t printLen[BANKS];
|
|
uint32_t total;
|
|
} SamplesT;
|
|
|
|
// What a running bank was matched to.
|
|
typedef struct BankT {
|
|
bool matched;
|
|
uint32_t segment;
|
|
uint32_t linkBank;
|
|
double score;
|
|
} BankT;
|
|
|
|
static int bySymbol(const void *a, const void *b);
|
|
static void placeAdd(PlaceT **places, size_t *count, const char *where, uint32_t n);
|
|
static void samplesRead(const char *path, SamplesT *samples);
|
|
static void segmentsRead(const char *base, SegmentT **segments, size_t *count);
|
|
static void symbolsRead(const char *path, SymbolT **symbols, size_t *count);
|
|
static void whereOf(uint32_t address, const BankT *banks, const SymbolT *symbols, size_t symbolCount, const SegmentT *segments, char *where);
|
|
static void *xrealloc(void *p, size_t bytes);
|
|
|
|
|
|
static int bySymbol(const void *a, const void *b) {
|
|
const SymbolT *x = (const SymbolT *)a;
|
|
const SymbolT *y = (const SymbolT *)b;
|
|
|
|
return x->address < y->address ? -1 : (x->address > y->address ? 1 : 0);
|
|
}
|
|
|
|
|
|
// One sample count to its place's total (a new place goes last).
|
|
static void placeAdd(PlaceT **places, size_t *count, const char *where, uint32_t n) {
|
|
size_t i;
|
|
|
|
for (i = 0; i < *count && strcmp((*places)[i].where, where) != 0; i++) {
|
|
}
|
|
if (i == *count) {
|
|
*places = (PlaceT *)xrealloc(*places, (*count + 1u) * sizeof(PlaceT));
|
|
snprintf((*places)[i].where, WHERE_CHARS, "%s", where);
|
|
(*places)[i].count = 0;
|
|
(*count)++;
|
|
}
|
|
(*places)[i].count += n;
|
|
}
|
|
|
|
|
|
static void samplesRead(const char *path, SamplesT *samples) {
|
|
FILE *f;
|
|
char line[LINE_MAX_CHARS];
|
|
char *at;
|
|
char *stop;
|
|
uint32_t address;
|
|
uint32_t n;
|
|
uint32_t bank;
|
|
size_t len;
|
|
size_t i;
|
|
|
|
memset(samples, 0, sizeof(*samples));
|
|
f = fopen(path, "rb");
|
|
if (f == NULL) {
|
|
toolFail("cannot read %s", path);
|
|
}
|
|
while (fgets(line, sizeof(line), f) != NULL) {
|
|
if (line[0] == 'S' && line[1] == ' ') {
|
|
address = (uint32_t)strtoul(line + 2, &stop, HEX);
|
|
n = (uint32_t)strtoul(stop, NULL, 10);
|
|
for (i = 0; i < samples->count && samples->addresses[i] != address; i++) {
|
|
}
|
|
if (i == samples->count) {
|
|
if (samples->count == samples->cap) {
|
|
samples->cap = samples->cap == 0 ? START_COUNT : samples->cap * 2u;
|
|
samples->addresses = (uint32_t *)xrealloc(samples->addresses, samples->cap * sizeof(uint32_t));
|
|
samples->counts = (uint32_t *)xrealloc(samples->counts, samples->cap * sizeof(uint32_t));
|
|
}
|
|
samples->addresses[samples->count++] = address;
|
|
}
|
|
samples->counts[i] = n;
|
|
} else if (line[0] == 'F' && line[1] == ' ') {
|
|
bank = (uint32_t)strtoul(line + 2, &stop, HEX) & (BANKS - 1u);
|
|
for (at = stop; *at == ' '; at++) {
|
|
}
|
|
len = strspn(at, "0123456789abcdefABCDEF") / 2u;
|
|
free(samples->prints[bank]);
|
|
samples->prints[bank] = (uint8_t *)xrealloc(NULL, len + 1u);
|
|
samples->printLen[bank] = len;
|
|
for (i = 0; i < len; i++) {
|
|
samples->prints[bank][i] = (uint8_t)strtoul((char[]){ at[2u * i], at[2u * i + 1u], 0 }, NULL, HEX);
|
|
}
|
|
} else if (line[0] == 'T' && line[1] == ' ') {
|
|
samples->total = (uint32_t)strtoul(line + 2, NULL, 10);
|
|
}
|
|
}
|
|
fclose(f);
|
|
}
|
|
|
|
|
|
// The segments the manifest names (base.json), with their images
|
|
// (base.segN.bin); without a manifest, one segment at bank 0.
|
|
static void segmentsRead(const char *base, SegmentT **segments, size_t *count) {
|
|
char path[PATH_CHARS];
|
|
char err[TOOL_JSON_ERROR_BYTES];
|
|
uint8_t *text;
|
|
size_t len;
|
|
size_t i;
|
|
ToolJsonT *doc;
|
|
const ToolJsonT *list;
|
|
const ToolJsonT *num;
|
|
const ToolJsonT *at;
|
|
|
|
*segments = NULL;
|
|
*count = 0;
|
|
snprintf(path, sizeof(path), "%s.json", base);
|
|
doc = NULL;
|
|
if (toolReadFile(path, &text, &len)) {
|
|
doc = toolJsonParse((const char *)text, len, err);
|
|
if (doc == NULL) {
|
|
toolFail("%s: %s", path, err);
|
|
}
|
|
free(text);
|
|
}
|
|
list = doc == NULL ? NULL : toolJsonGet(doc, "segments");
|
|
if (doc == NULL) {
|
|
*segments = (SegmentT *)xrealloc(NULL, sizeof(SegmentT));
|
|
(*segments)[0].number = 1;
|
|
(*segments)[0].linkBank = 0;
|
|
*count = 1;
|
|
} else {
|
|
*segments = (SegmentT *)xrealloc(NULL, (list == NULL ? 0u : list->count) * sizeof(SegmentT) + 1u);
|
|
for (i = 0; list != NULL && i < list->count; i++) {
|
|
num = toolJsonGet(list->items[i], "num");
|
|
at = toolJsonGet(list->items[i], "base");
|
|
if (num == NULL || at == NULL || at->type != TOOL_JSON_STRING) {
|
|
toolFail("%s: a segment without num and base", path);
|
|
}
|
|
(*segments)[*count].number = (uint32_t)num->number;
|
|
(*segments)[*count].linkBank = (uint32_t)(strtoul(at->string, NULL, HEX) >> BANK_SHIFT);
|
|
(*count)++;
|
|
}
|
|
toolJsonFree(doc);
|
|
}
|
|
for (i = 0; i < *count; i++) {
|
|
snprintf(path, sizeof(path), "%s.seg%u.bin", base, (*segments)[i].number);
|
|
if (!toolReadFile(path, &(*segments)[i].image, &(*segments)[i].len)) {
|
|
toolFail("cannot read %s", path);
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
// The map's symbols, sorted by address: the globals, and the locals where
|
|
// no global names the address.
|
|
static void symbolsRead(const char *path, SymbolT **symbols, size_t *count) {
|
|
FILE *f;
|
|
char line[LINE_MAX_CHARS];
|
|
char name[NAME_MAX_CHARS];
|
|
char *at;
|
|
char *end;
|
|
size_t cap;
|
|
size_t i;
|
|
size_t n;
|
|
uint32_t address;
|
|
int section;
|
|
|
|
*symbols = NULL;
|
|
*count = 0;
|
|
cap = 0;
|
|
section = 0;
|
|
f = fopen(path, "rb");
|
|
if (f == NULL) {
|
|
toolFail("cannot read %s", path);
|
|
}
|
|
while (fgets(line, sizeof(line), f) != NULL) {
|
|
if (line[0] == '#') {
|
|
section = strncmp(line, "# global symbols", strlen("# global symbols")) == 0 ? 1 : (strncmp(line, "# local symbols", strlen("# local symbols")) == 0 ? 2 : 0);
|
|
continue;
|
|
}
|
|
for (at = line; *at == ' ' || *at == '\t'; at++) {
|
|
}
|
|
for (end = at + strlen(at); end > at && (end[-1] == ' ' || end[-1] == '\t' || end[-1] == '\r' || end[-1] == '\n'); end--) {
|
|
}
|
|
*end = 0;
|
|
if (section == 0 || at[0] != '0' || at[1] != 'x' || strspn(at + 2, "0123456789abcdefABCDEF") != ADDRESS_DIGITS || (at[2 + ADDRESS_DIGITS] != ' ' && at[2 + ADDRESS_DIGITS] != '\t')) {
|
|
continue;
|
|
}
|
|
address = (uint32_t)strtoul(at + 2, NULL, HEX);
|
|
for (at += 2 + ADDRESS_DIGITS; *at == ' ' || *at == '\t'; at++) {
|
|
}
|
|
n = strcspn(at, " \t");
|
|
if (n == 0 || at[n] != 0 || n >= NAME_MAX_CHARS) {
|
|
continue;
|
|
}
|
|
memcpy(name, at, n + 1u);
|
|
for (i = 0; i < *count && (*symbols)[i].address != address; i++) {
|
|
}
|
|
if (i == *count) {
|
|
if (*count == cap) {
|
|
cap = cap == 0 ? START_COUNT : cap * 2u;
|
|
*symbols = (SymbolT *)xrealloc(*symbols, cap * sizeof(SymbolT));
|
|
}
|
|
(*count)++;
|
|
} else if (section != 1) {
|
|
continue;
|
|
}
|
|
(*symbols)[i].address = address;
|
|
memcpy((*symbols)[i].name, name, n + 1u);
|
|
}
|
|
fclose(f);
|
|
qsort(*symbols, *count, sizeof(SymbolT), bySymbol);
|
|
}
|
|
|
|
|
|
// Where an address is: a matched bank's symbol (the one at or below it in
|
|
// the same link bank) or segment offset, else the kind of bank.
|
|
static void whereOf(uint32_t address, const BankT *banks, const SymbolT *symbols, size_t symbolCount, const SegmentT *segments, char *where) {
|
|
const BankT *bank;
|
|
uint32_t link;
|
|
size_t lo;
|
|
size_t hi;
|
|
size_t mid;
|
|
|
|
bank = &banks[address >> BANK_SHIFT];
|
|
if (bank->matched) {
|
|
link = (bank->linkBank << BANK_SHIFT) | (address & OFFSET_MASK);
|
|
lo = 0;
|
|
hi = symbolCount;
|
|
while (lo < hi) {
|
|
mid = (lo + hi) / 2u;
|
|
if (symbols[mid].address <= link) {
|
|
lo = mid + 1u;
|
|
} else {
|
|
hi = mid;
|
|
}
|
|
}
|
|
if (lo > 0 && (symbols[lo - 1u].address >> BANK_SHIFT) == (link >> BANK_SHIFT)) {
|
|
snprintf(where, WHERE_CHARS, "%s", symbols[lo - 1u].name);
|
|
return;
|
|
}
|
|
snprintf(where, WHERE_CHARS, "seg%u+%04X", segments[bank->segment].number, address & OFFSET_MASK);
|
|
return;
|
|
}
|
|
if ((address >> BANK_SHIFT) >= ROM_FIRST) {
|
|
snprintf(where, WHERE_CHARS, "ROM $%02X", address >> BANK_SHIFT);
|
|
} else if ((address >> BANK_SHIFT) >= FIRMWARE_FIRST && (address >> BANK_SHIFT) <= FIRMWARE_LAST) {
|
|
snprintf(where, WHERE_CHARS, "bank $%02X (firmware/vectors)", address >> BANK_SHIFT);
|
|
} else {
|
|
snprintf(where, WHERE_CHARS, "bank $%02X (GS/OS, tools, NTP)", address >> BANK_SHIFT);
|
|
}
|
|
}
|
|
|
|
|
|
static void *xrealloc(void *p, size_t bytes) {
|
|
p = realloc(p, bytes == 0 ? 1u : bytes);
|
|
if (p == NULL) {
|
|
toolFail("out of memory");
|
|
}
|
|
return p;
|
|
}
|
|
|
|
|
|
int main(int argc, char **argv) {
|
|
SamplesT samples;
|
|
SegmentT *segments;
|
|
SymbolT *symbols;
|
|
PlaceT *places;
|
|
PlaceT held;
|
|
BankT banks[BANKS];
|
|
char base[BASE_CHARS];
|
|
char where[WHERE_CHARS];
|
|
size_t segmentCount;
|
|
size_t symbolCount;
|
|
size_t placeCount;
|
|
size_t len;
|
|
size_t same;
|
|
size_t i;
|
|
size_t j;
|
|
uint32_t bank;
|
|
long top;
|
|
double score;
|
|
|
|
toolFileName(TOOL_NAME);
|
|
if (argc != 4) {
|
|
fprintf(stderr, "usage: %s SAMPLES MAP TOP\n", TOOL_NAME);
|
|
return GATE_USAGE_EXIT;
|
|
}
|
|
top = gateArgLong("top", argv[3]);
|
|
len = strlen(argv[2]);
|
|
snprintf(base, sizeof(base), "%.*s", (int)(len >= strlen(MAP_SUFFIX) && strcmp(argv[2] + len - strlen(MAP_SUFFIX), MAP_SUFFIX) == 0 ? len - strlen(MAP_SUFFIX) : len), argv[2]);
|
|
segmentsRead(base, &segments, &segmentCount);
|
|
symbolsRead(argv[2], &symbols, &symbolCount);
|
|
samplesRead(argv[1], &samples);
|
|
|
|
// Each running bank to the segment its first bytes best match.
|
|
memset(banks, 0, sizeof(banks));
|
|
for (bank = 0; bank < BANKS; bank++) {
|
|
if (samples.prints[bank] == NULL) {
|
|
continue;
|
|
}
|
|
for (i = 0; i < segmentCount; i++) {
|
|
len = samples.printLen[bank] < segments[i].len ? samples.printLen[bank] : segments[i].len;
|
|
if (len < MATCH_BYTES) {
|
|
continue;
|
|
}
|
|
same = 0;
|
|
for (j = 0; j < len; j++) {
|
|
same += samples.prints[bank][j] == segments[i].image[j] ? 1u : 0u;
|
|
}
|
|
score = (double)same / (double)len;
|
|
if (!banks[bank].matched || score > banks[bank].score) {
|
|
banks[bank].matched = true;
|
|
banks[bank].segment = (uint32_t)i;
|
|
banks[bank].linkBank = segments[i].linkBank;
|
|
banks[bank].score = score;
|
|
}
|
|
}
|
|
banks[bank].matched = banks[bank].matched && banks[bank].score >= MATCH_SCORE;
|
|
}
|
|
|
|
places = NULL;
|
|
placeCount = 0;
|
|
for (i = 0; i < samples.count; i++) {
|
|
whereOf(samples.addresses[i], banks, symbols, symbolCount, segments, where);
|
|
placeAdd(&places, &placeCount, where, samples.counts[i]);
|
|
}
|
|
// Most samples first; equal ones in the order first met.
|
|
for (i = 1; i < placeCount; i++) {
|
|
held = places[i];
|
|
for (j = i; j > 0 && places[j - 1u].count < held.count; j--) {
|
|
places[j] = places[j - 1u];
|
|
}
|
|
places[j] = held;
|
|
}
|
|
printf("profile-iigs: %u samples\n", samples.total);
|
|
for (bank = 0; bank < BANKS; bank++) {
|
|
if (banks[bank].matched) {
|
|
printf(" bank $%02X = segment %u (link bank $%02X, %.0f%% of %zu bytes match)\n", bank, segments[banks[bank].segment].number, banks[bank].linkBank, banks[bank].score * PERCENT, samples.printLen[bank]);
|
|
}
|
|
}
|
|
printf("%7s %6s %s\n", "samples", "pct", "where");
|
|
for (i = 0; i < placeCount && (long)i < top; i++) {
|
|
printf("%7u %5.1f%% %s\n", places[i].count, PERCENT * places[i].count / (samples.total > 0 ? samples.total : 1u), places[i].where);
|
|
}
|
|
return 0;
|
|
}
|