// 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 #include #include #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; }