mirror of
https://github.com/gbdev/rgbds.git
synced 2025-11-22 03:02:06 +00:00
Rewrite RGBLINK entirely
The goal was to improve readability, but along the way a few things were gained. - Sorted sym and map files - Infrastructure for supporting multiple .o versions - Valgrind-proof, as far as my testing goes anyways - Improved verbosity messages - Added error checking - Performance improvements, see end of commit message The readability improvement was spurred while trying to make sense of the old code while trying to implement features such as sorted sym and map files. I also did my best to remove hardcoded logic, such that modifications should be doable; for example, "RAM loading" sections, which are linked against a different location than the one they're stored at. Some work remains to be done, see the "TODO:" and "FIXME:" comments. Further, while regression tests pass, this new linker should be tested on different codebases (ideally while instrumented with `make develop` and under valgrind). The few errors spotted in the man pages (alignment) need to be corrected. Finally, documentation comments need to be written, I have written a lot of them but not all. This also provides a significant performance boost (benchmarked with a 51994-symbol project): Current master RGBLINK: 2.02user 0.03system 0:02.06elapsed 99%CPU (0avgtext+0avgdata 84336maxresident)k 0inputs+11584outputs (0major+20729minor)pagefaults 0swaps Rewritten RGBLINK: 0.19user 0.06system 0:00.63elapsed 40%CPU (0avgtext+0avgdata 32460maxresident)k 23784inputs+11576outputs (0major+7672minor)pagefaults 0swaps
This commit is contained in:
216
src/link/main.c
216
src/link/main.c
@@ -1,141 +1,177 @@
|
||||
/*
|
||||
* This file is part of RGBDS.
|
||||
*
|
||||
* Copyright (c) 1997-2018, Carsten Sorensen and RGBDS contributors.
|
||||
* Copyright (c) 1997-2019, Carsten Sorensen and RGBDS contributors.
|
||||
*
|
||||
* SPDX-License-Identifier: MIT
|
||||
*/
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include "extern/err.h"
|
||||
#include <stdio.h>
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
#include "link/object.h"
|
||||
#include "link/output.h"
|
||||
#include "link/symbol.h"
|
||||
#include "link/section.h"
|
||||
#include "link/assign.h"
|
||||
#include "link/patch.h"
|
||||
#include "link/mylink.h"
|
||||
#include "link/mapfile.h"
|
||||
#include "link/main.h"
|
||||
#include "link/library.h"
|
||||
#include "link/output.h"
|
||||
|
||||
#include "extern/err.h"
|
||||
#include "version.h"
|
||||
|
||||
enum eBlockType {
|
||||
BLOCK_COMMENT,
|
||||
BLOCK_OBJECTS,
|
||||
BLOCK_LIBRARIES,
|
||||
BLOCK_OUTPUT
|
||||
};
|
||||
bool isDmgMode; /* -d */
|
||||
FILE *linkerScript; /* -l */
|
||||
FILE *mapFile; /* -m */
|
||||
FILE *symFile; /* -n */
|
||||
FILE *overlayFile; /* -O */
|
||||
FILE *outputFile; /* -o */
|
||||
uint8_t padValue; /* -p */
|
||||
bool is32kMode; /* -t */
|
||||
bool beVerbose; /* -v */
|
||||
bool isWRA0Mode; /* -w */
|
||||
|
||||
int32_t options;
|
||||
int32_t fillchar;
|
||||
char *smartlinkstartsymbol;
|
||||
|
||||
/*
|
||||
* Print the usagescreen
|
||||
/**
|
||||
* Prints the program's usage to stdout.
|
||||
*/
|
||||
|
||||
static void print_usage(void)
|
||||
static void printUsage(void)
|
||||
{
|
||||
printf(
|
||||
"usage: rgblink [-dtVw] [-l linkerscript] [-m mapfile] [-n symfile] [-O overlay]\n"
|
||||
" [-o outfile] [-p pad_value] [-s symbol] file [...]\n");
|
||||
exit(1);
|
||||
puts("usage: rgblink [-dtVvw] [-l linkerscript] [-m mapfile] [-n symfile] [-O overlay]\n"
|
||||
" [-o outfile] [-p pad_value] [-s symbol] file [...]");
|
||||
}
|
||||
|
||||
/*
|
||||
* The main routine
|
||||
/**
|
||||
* Helper function for `main`'s argument parsing.
|
||||
* For use with options which take a file name to operate on
|
||||
* If the file fails to be opened, an error message will be printed,
|
||||
* and the function `exit`s.
|
||||
* @param mode The mode to open the file in
|
||||
* @param failureMessage A format string that will be printed on failure.
|
||||
* A single (string) argument is given, the file name.
|
||||
* @return What `fopen` returned; this cannot be NULL.
|
||||
*/
|
||||
static FILE *openArgFile(char const *mode, char const *failureMessage)
|
||||
{
|
||||
FILE *file = fopen(optarg, mode);
|
||||
|
||||
if (!file)
|
||||
err(1, failureMessage, optarg);
|
||||
return file;
|
||||
}
|
||||
|
||||
/**
|
||||
* Cleans up what has been done
|
||||
* Mostly here to please tools such as `valgrind` so actual errors can be seen
|
||||
*/
|
||||
static void cleanup(void)
|
||||
{
|
||||
if (linkerScript)
|
||||
fclose(linkerScript);
|
||||
if (mapFile)
|
||||
fclose(mapFile);
|
||||
if (symFile)
|
||||
fclose(symFile);
|
||||
if (overlayFile)
|
||||
fclose(overlayFile);
|
||||
if (outputFile)
|
||||
fclose(outputFile);
|
||||
|
||||
obj_Cleanup();
|
||||
}
|
||||
|
||||
int main(int argc, char *argv[])
|
||||
{
|
||||
int ch;
|
||||
char *ep;
|
||||
char optionChar;
|
||||
char *endptr; /* For error checking with `strtol` */
|
||||
unsigned long value; /* For storing `strtoul`'s return value */
|
||||
|
||||
if (argc == 1)
|
||||
print_usage();
|
||||
|
||||
while ((ch = getopt(argc, argv, "dl:m:n:O:o:p:s:tVw")) != -1) {
|
||||
switch (ch) {
|
||||
/* Parse options */
|
||||
while ((optionChar = getopt(argc, argv, "dl:m:n:O:o:p:s:tVvw")) != -1) {
|
||||
switch (optionChar) {
|
||||
case 'd':
|
||||
isDmgMode = true;
|
||||
isWRA0Mode = true;
|
||||
break;
|
||||
case 'l':
|
||||
SetLinkerscriptName(optarg);
|
||||
linkerScript = openArgFile("r", "Could not open linker script file \"%s\"");
|
||||
break;
|
||||
case 'm':
|
||||
SetMapfileName(optarg);
|
||||
mapFile = openArgFile("w", "Could not open map file \"%s\"");
|
||||
break;
|
||||
case 'n':
|
||||
SetSymfileName(optarg);
|
||||
break;
|
||||
case 'o':
|
||||
out_Setname(optarg);
|
||||
symFile = openArgFile("w", "Could not open sym file \"%s\"");
|
||||
break;
|
||||
case 'O':
|
||||
out_SetOverlayname(optarg);
|
||||
options |= OPT_OVERLAY;
|
||||
overlayFile = openArgFile("r+b", "Could not open overlay file \"%s\"");
|
||||
break;
|
||||
case 'o':
|
||||
outputFile = openArgFile("wb", "Could not open output file \"%s\"");
|
||||
break;
|
||||
case 'p':
|
||||
fillchar = strtoul(optarg, &ep, 0);
|
||||
if (optarg[0] == '\0' || *ep != '\0')
|
||||
value = strtoul(optarg, &endptr, 0);
|
||||
if (optarg[0] == '\0' || *endptr != '\0')
|
||||
errx(1, "Invalid argument for option 'p'");
|
||||
if (fillchar < 0 || fillchar > 0xFF)
|
||||
errx(1, "Argument for option 'p' must be between 0 and 0xFF");
|
||||
if (value > 0xFF)
|
||||
errx(1, "Argument for 'p' must be a byte (between 0 and 0xFF)");
|
||||
padValue = value;
|
||||
break;
|
||||
case 's':
|
||||
options |= OPT_SMART_C_LINK;
|
||||
smartlinkstartsymbol = optarg;
|
||||
/* FIXME: nobody knows what this does, figure it out */
|
||||
(void)optarg;
|
||||
warnx("Nobody has any idea what `-s` does");
|
||||
break;
|
||||
case 't':
|
||||
options |= OPT_TINY;
|
||||
break;
|
||||
case 'd':
|
||||
/*
|
||||
* Set to set WRAM as a single continuous block as on
|
||||
* DMG. All WRAM sections must be WRAM0 as bankable WRAM
|
||||
* sections do not exist in this mode. A WRAMX section
|
||||
* will raise an error. VRAM bank 1 can't be used if
|
||||
* this option is enabled either.
|
||||
*
|
||||
* This option implies OPT_CONTWRAM.
|
||||
*/
|
||||
options |= OPT_DMG_MODE;
|
||||
/* FALLTHROUGH */
|
||||
case 'w':
|
||||
/*
|
||||
* Set to set WRAM as a single continuous block as on
|
||||
* DMG. All WRAM sections must be WRAM0 as bankable WRAM
|
||||
* sections do not exist in this mode. A WRAMX section
|
||||
* will raise an error.
|
||||
*/
|
||||
options |= OPT_CONTWRAM;
|
||||
is32kMode = true;
|
||||
break;
|
||||
case 'V':
|
||||
printf("rgblink %s\n", get_package_version_string());
|
||||
exit(0);
|
||||
case 'v':
|
||||
beVerbose = true;
|
||||
break;
|
||||
case 'w':
|
||||
isWRA0Mode = true;
|
||||
break;
|
||||
default:
|
||||
print_usage();
|
||||
/* NOTREACHED */
|
||||
printUsage();
|
||||
exit(1);
|
||||
}
|
||||
}
|
||||
argc -= optind;
|
||||
argv += optind;
|
||||
|
||||
if (argc == 0)
|
||||
print_usage();
|
||||
int curArgIndex = optind;
|
||||
|
||||
for (int32_t i = 0; i < argc; ++i)
|
||||
obj_Readfile(argv[i]);
|
||||
/* If no input files were specified, the user must have screwed up */
|
||||
if (curArgIndex == argc) {
|
||||
fprintf(stderr, "No input files");
|
||||
printUsage();
|
||||
exit(1);
|
||||
}
|
||||
|
||||
AddNeededModules();
|
||||
AssignSections();
|
||||
CreateSymbolTable();
|
||||
Patch();
|
||||
Output();
|
||||
CloseMapfile();
|
||||
/* Patch the size array depending on command-line options */
|
||||
if (is32kMode)
|
||||
maxsize[SECTTYPE_ROM0] = 0x8000;
|
||||
if (isWRA0Mode)
|
||||
maxsize[SECTTYPE_WRAM0] = 0x2000;
|
||||
|
||||
return 0;
|
||||
/* Patch the bank ranges array depending on command-line options */
|
||||
if (isDmgMode)
|
||||
bankranges[SECTTYPE_VRAM][1] = BANK_MIN_VRAM;
|
||||
|
||||
/* Read all object files first, */
|
||||
while (curArgIndex < argc)
|
||||
obj_ReadFile(argv[curArgIndex++]);
|
||||
|
||||
/* then process them, */
|
||||
obj_DoSanityChecks();
|
||||
assign_AssignSections();
|
||||
assign_Cleanup();
|
||||
|
||||
/* and finally output the result. */
|
||||
patch_ApplyPatches();
|
||||
out_WriteFiles();
|
||||
|
||||
/* Do cleanup before quitting, though. */
|
||||
cleanup();
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user