diff --git a/README.md b/README.md index cd12e6e..8f42c39 100644 --- a/README.md +++ b/README.md @@ -105,8 +105,14 @@ If the CPU reads a byte that is not an instruction, it goes to the fault handler | `-o ` | Write the output to this path. | | `-I ` | Look in this directory for included files. May be given more than once. | | `-M ` | Write out which source files the output depends on, as a make rule. | +| `-S ` | Write every label and the address it was given, in address order. | | `-h`, `--help` | Show help and usage information. | +`-S` is the only thing that knows what a program's addresses are called. A program on the +disk is bytes; the machine's own monitor can disassemble it but has no idea what any of it +is named. So a count of which addresses get called says a great deal and names nothing, and +this is what turns such a count into a list of routine names. + Without `-o` the output takes the source file's name, in the directory you called the assembler from, with the extension the format asks for: `.bin` for a boot image and `.sbx` for a loadable program. Included files are looked for beside the file that includes them, and then along the directories given with `-I`. ## Managing Disks: SplitDisk diff --git a/Source/Assembler/Assembler.c b/Source/Assembler/Assembler.c index 9753ea0..a1d446d 100644 --- a/Source/Assembler/Assembler.c +++ b/Source/Assembler/Assembler.c @@ -102,6 +102,7 @@ void printUsage(const char *programName) { printf(" -o Write the output to this path instead of alongside the source.\n"); printf(" -I Look in this directory for included files. May be given more than once.\n"); printf(" -M Write the source files this output depends on, as a make rule.\n"); + printf(" -S Write every label and the address it was given, in address order.\n"); printf(" -h, --help Display this help message.\n"); } @@ -171,15 +172,17 @@ int main(int argc, char *argv[]) { {"output", required_argument, 0, 'o'}, {"include", required_argument, 0, 'I'}, {"depend", required_argument, 0, 'M'}, + {"symbols", required_argument, 0, 'S'}, {"help", no_argument, 0, 'h'}, {0, 0, 0, 0 } }; char *outputFileName = NULL; char *dependencyFileName = NULL; + char *symbolFileName = NULL; int option_index = 0; int opt; - while ((opt = getopt_long(argc, argv, "o:I:M:h", long_options, &option_index)) != -1) { + while ((opt = getopt_long(argc, argv, "o:I:M:S:h", long_options, &option_index)) != -1) { switch (opt) { case 'o': outputFileName = strdup(optarg); @@ -190,6 +193,9 @@ int main(int argc, char *argv[]) { case 'M': dependencyFileName = strdup(optarg); break; + case 'S': + symbolFileName = strdup(optarg); + break; case 'h': printUsage(argv[0]); return 0; @@ -249,6 +255,11 @@ int main(int argc, char *argv[]) { writeDependencyFile(dependencyFileName, outputFileName); free(dependencyFileName); } + // Before the cleanup, which is what frees the label table this reads. + if (symbolFileName) { + writeSymbolFile(symbolFileName); + free(symbolFileName); + } assemblerCleanup(intermediateArray, index, outputFileName); return 0; } diff --git a/Source/Assembler/secondPass.c b/Source/Assembler/secondPass.c index 365a775..38a124a 100644 --- a/Source/Assembler/secondPass.c +++ b/Source/Assembler/secondPass.c @@ -21,6 +21,48 @@ int debugSecondPass = 0; Label labelArray[MAX_LABELS]; int labelCount = 0; +// ---- Where everything ended up ---- +// +// Every label and the address it was given, in address order. The assembler knows this and +// nothing else does: a program on the disk is bytes, and the machine's own monitor can +// disassemble it but has no idea what any of it is called. +// +// WHAT IT IS FOR is telling where a program spends its time. Counting which addresses get +// called says a great deal and names nothing, so the answer arrives as a list of numbers +// and somebody has to work out by hand which routine each one is inside. With this, a +// tally of call targets becomes a list of routine names. +// +// Sorted by address rather than by name, because the question asked of it is always "what +// is at this address", and a label table is small enough that sorting it is free. +static int byAddress(const void *left, const void *right) { + const Label *a = left, *b = right; + if (a->address != b->address) { + return a->address < b->address ? -1 : 1; + } + return strcmp(a->label, b->label); +} + +void writeSymbolFile(const char *path) { + FILE *file = fopen(path, "w"); + if (!file) { + fprintf(stderr, RED "Error: Could not open file \"%s\" for writing.\n" RESET, path); + exit(1); + } + Label *sorted = malloc((size_t)labelCount * sizeof(Label)); + if (!sorted) { + fprintf(stderr, RED "Error: Out of memory writing the symbol file.\n" RESET); + fclose(file); + exit(1); + } + memcpy(sorted, labelArray, (size_t)labelCount * sizeof(Label)); + qsort(sorted, (size_t)labelCount, sizeof(Label), byAddress); + for (int i = 0; i < labelCount; i++) { + fprintf(file, "%04X %s\n", sorted[i].address, sorted[i].label); + } + free(sorted); + fclose(file); +} + void freeLabelList() { for (int i = 0; i < labelCount; i++) { if (labelArray[i].label) { diff --git a/Source/Assembler/secondPass.h b/Source/Assembler/secondPass.h index 6f4e07a..f689349 100644 --- a/Source/Assembler/secondPass.h +++ b/Source/Assembler/secondPass.h @@ -24,6 +24,10 @@ typedef struct { int type; } Label; +// Every label and the address it was given, in address order, so that a tally of +// addresses can be turned back into a list of routine names. +void writeSymbolFile(const char *path); + // One line of the Vector Segment, once it has been worked out. typedef struct { char* name; // What it was called, or NULL for a device, which is named by its port.