diff --git a/Makefile b/Makefile index 49946300..c53aa152 100644 --- a/Makefile +++ b/Makefile @@ -91,6 +91,7 @@ rgblink_obj := \ src/link/output.o \ src/link/patch.o \ src/link/script.o \ + src/link/sdas_obj.o \ src/link/section.o \ src/link/symbol.o \ src/extern/getopt.o \ diff --git a/include/link/script.h b/include/link/script.h index 57399e81..a635e8b6 100644 --- a/include/link/script.h +++ b/include/link/script.h @@ -11,11 +11,13 @@ #define RGBDS_LINK_SCRIPT_H #include +#include "linkdefs.h" extern FILE * linkerScript; struct SectionPlacement { struct Section *section; + enum SectionType type; uint16_t org; uint32_t bank; }; diff --git a/include/link/sdas_obj.h b/include/link/sdas_obj.h new file mode 100644 index 00000000..fee29e07 --- /dev/null +++ b/include/link/sdas_obj.h @@ -0,0 +1,19 @@ +/* + * This file is part of RGBDS. + * + * Copyright (c) 2022, Eldred Habert and RGBDS contributors. + * + * SPDX-License-Identifier: MIT + */ + +/* Assigning all sections a place */ +#ifndef RGBDS_LINK_SDAS_OBJ_H +#define RGBDS_LINK_SDAS_OBJ_H + +#include + +struct FileStackNode; + +void sdobj_ReadFile(struct FileStackNode const *fileName, FILE *file); + +#endif /* RGBDS_LINK_SDAS_OBJ_H */ diff --git a/include/link/section.h b/include/link/section.h index dea0889a..beb9f034 100644 --- a/include/link/section.h +++ b/include/link/section.h @@ -48,6 +48,8 @@ struct Section { enum SectionType type; enum SectionModifier modifier; bool isAddressFixed; + // This `struct`'s address in ROM. + // Importantly for fragments, this does not include `offset`! uint16_t org; bool isBankFixed; uint32_t bank; @@ -60,7 +62,7 @@ struct Section { /* Extra info computed during linking */ struct Symbol **fileSymbols; uint32_t nbSymbols; - struct Symbol const **symbols; + struct Symbol **symbols; struct Section *nextu; /* The next "component" of this unionized sect */ }; diff --git a/include/link/symbol.h b/include/link/symbol.h index d0085795..76305dfa 100644 --- a/include/link/symbol.h +++ b/include/link/symbol.h @@ -27,6 +27,7 @@ struct Symbol { int32_t lineNo; int32_t sectionID; union { + // Both types must be identical int32_t offset; int32_t value; }; diff --git a/include/linkdefs.h b/include/linkdefs.h index 590bfa76..cb6ce489 100644 --- a/include/linkdefs.h +++ b/include/linkdefs.h @@ -9,6 +9,7 @@ #ifndef RGBDS_LINKDEFS_H #define RGBDS_LINKDEFS_H +#include #include #include @@ -74,6 +75,8 @@ enum SectionType { SECTTYPE_SRAM, SECTTYPE_OAM, + // In RGBLINK, this is used for "indeterminate" sections; this is primarily for SDCC + // areas, which do not carry any section type info and must be told from the linker script SECTTYPE_INVALID }; @@ -93,6 +96,7 @@ extern char const * const sectionModNames[]; */ static inline bool sect_HasData(enum SectionType type) { + assert(type != SECTTYPE_INVALID); return type == SECTTYPE_ROM0 || type == SECTTYPE_ROMX; } diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 131837f6..bf1afd35 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -81,6 +81,7 @@ set(rgblink_src "link/output.c" "link/patch.c" "link/script.c" + "link/sdas_obj.c" "link/section.c" "link/symbol.c" "hashmap.c" diff --git a/src/link/main.c b/src/link/main.c index 0b8a8ddf..189de2a8 100644 --- a/src/link/main.c +++ b/src/link/main.c @@ -29,6 +29,7 @@ #include "extern/getopt.h" #include "error.h" +#include "linkdefs.h" #include "platform.h" #include "version.h" @@ -350,6 +351,12 @@ next: } } +_Noreturn void reportErrors(void) { + fprintf(stderr, "Linking failed with %" PRIu32 " error%s\n", + nbErrors, nbErrors == 1 ? "" : "s"); + exit(1); +} + int main(int argc, char *argv[]) { int optionChar; @@ -457,7 +464,15 @@ int main(int argc, char *argv[]) while ((placement = script_NextSection())) { struct Section *section = placement->section; + assert(section->offset == 0); /* Check if this doesn't conflict with what the code says */ + if (section->type == SECTTYPE_INVALID) { + for (struct Section *sect = section; sect; sect = sect->nextu) + sect->type = placement->type; // SDCC "unknown" sections + } else if (section->type != placement->type) { + error(NULL, 0, "Linker script contradicts \"%s\"'s type", + section->name); + } if (section->isBankFixed && placement->bank != section->bank) error(NULL, 0, "Linker script contradicts \"%s\"'s bank placement", section->name); @@ -479,22 +494,25 @@ int main(int argc, char *argv[]) fclose(linkerScript); script_Cleanup(); + + // If the linker script produced any errors, some sections may be in an invalid state + if (nbErrors != 0) + reportErrors(); } /* then process them, */ obj_DoSanityChecks(); + if (nbErrors != 0) + reportErrors(); assign_AssignSections(); obj_CheckAssertions(); assign_Cleanup(); /* and finally output the result. */ patch_ApplyPatches(); - if (nbErrors) { - fprintf(stderr, "Linking failed with %" PRIu32 " error%s\n", - nbErrors, nbErrors == 1 ? "" : "s"); - exit(1); - } + if (nbErrors != 0) + reportErrors(); out_WriteFiles(); /* Do cleanup before quitting, though. */ diff --git a/src/link/object.c b/src/link/object.c index 07a81b42..85fef2b8 100644 --- a/src/link/object.c +++ b/src/link/object.c @@ -18,6 +18,7 @@ #include "link/main.h" #include "link/object.h" #include "link/patch.h" +#include "link/sdas_obj.h" #include "link/section.h" #include "link/symbol.h" @@ -408,7 +409,7 @@ static void readSection(FILE *file, struct Section *section, char const *fileNam * @param symbol The symbol to link * @param section The section to link */ -static void linkSymToSect(struct Symbol const *symbol, struct Section *section) +static void linkSymToSect(struct Symbol *symbol, struct Section *section) { uint32_t a = 0, b = section->nbSymbols; @@ -421,7 +422,7 @@ static void linkSymToSect(struct Symbol const *symbol, struct Section *section) a = c + 1; } - struct Symbol const *tmp = symbol; + struct Symbol *tmp = symbol; for (uint32_t i = a; i <= section->nbSymbols; i++) { symbol = tmp; @@ -466,6 +467,39 @@ void obj_ReadFile(char const *fileName, unsigned int fileID) if (!file) err("Could not open file %s", fileName); + // First, check if the object is a RGBDS object or a SDCC one. If the first byte is 'R', + // we'll assume it's a RGBDS object file, and otherwise, that it's a SDCC object file. + int c = getc(file); + + ungetc(c, file); // Guaranteed to work + switch (c) { + case EOF: + fatal(NULL, 0, "File \"%s\" is empty!", fileName); + + case 'R': + break; + + default: // This is (probably) a SDCC object file, defer the rest of detection to it + // Since SDCC does not provide line info, everything will be reported as coming from the + // object file. It's better than nothing. + nodes[fileID].nbNodes = 1; + nodes[fileID].nodes = malloc(sizeof(nodes[fileID].nodes[0]) * nodes[fileID].nbNodes); + if (!nodes[fileID].nodes) + err("Failed to get memory for %s's nodes", fileName); + struct FileStackNode *where = &nodes[fileID].nodes[0]; + + if (!where) + fatal(NULL, 0, "Failed to alloc fstack node for \"%s\": %s", fileName, strerror(errno)); + where->parent = NULL; + where->type = NODE_FILE; + where->name = strdup(fileName); + if (!where->name) + fatal(NULL, 0, "Failed to duplicate \"%s\"'s name: %s", fileName, strerror(errno)); + + sdobj_ReadFile(where, file); + return; + } + /* Begin by reading the magic bytes and version number */ unsigned versionNumber; int matchedElems = fscanf(file, RGBDS_OBJECT_VERSION_STRING, @@ -508,8 +542,7 @@ void obj_ReadFile(char const *fileName, unsigned int fileID) readFileStackNode(file, nodes[fileID].nodes, i, fileName); /* This file's symbols, kept to link sections to them */ - struct Symbol **fileSymbols = - malloc(sizeof(*fileSymbols) * nbSymbols + 1); + struct Symbol **fileSymbols = malloc(sizeof(*fileSymbols) * nbSymbols + 1); if (!fileSymbols) err("Failed to get memory for %s's symbols", fileName); diff --git a/src/link/output.c b/src/link/output.c index 03d3fb59..a7794e6c 100644 --- a/src/link/output.c +++ b/src/link/output.c @@ -9,6 +9,7 @@ #include #include #include +#include #include #include "link/output.h" @@ -199,6 +200,7 @@ static void writeBank(struct SortedSection *bankSections, uint16_t baseOffset, while (bankSections) { struct Section const *section = bankSections->section; + assert(section->offset == 0); /* Output padding up to the next SECTION */ while (offset + baseOffset < section->org) { putc(overlayFile ? getc(overlayFile) : padValue, @@ -381,6 +383,7 @@ static uint16_t writeMapBank(struct SortedSections const *sectList, used += sect->size; + assert(sect->offset == 0); if (sect->size != 0) fprintf(mapFile, " SECTION: $%04" PRIx16 "-$%04x ($%04" PRIx16 " byte%s) [\"%s\"]\n", @@ -394,6 +397,7 @@ static uint16_t writeMapBank(struct SortedSections const *sectList, uint16_t org = sect->org; while (sect) { + fprintf(mapFile, " ; New %s\n", sect->modifier == SECTION_FRAGMENT ? "fragment": "union"); for (size_t i = 0; i < sect->nbSymbols; i++) fprintf(mapFile, " $%04" PRIx32 " = %s\n", sect->symbols[i]->offset + org, diff --git a/src/link/patch.c b/src/link/patch.c index c8391288..2b9fd326 100644 --- a/src/link/patch.c +++ b/src/link/patch.c @@ -354,6 +354,7 @@ static int32_t computeRPNExpr(struct Patch const *patch, ; sect = sect_GetSection(name); + assert(sect->offset == 0); if (!sect) { error(patch->src, patch->lineNo, @@ -497,9 +498,6 @@ void patch_CheckAssertions(struct Assertion *assert) */ static void applyFilePatches(struct Section *section, struct Section *dataSection) { - if (!sect_HasData(section->type)) - return; - verbosePrint("Patching section \"%s\"...\n", section->name); for (uint32_t patchID = 0; patchID < section->nbPatches; patchID++) { struct Patch *patch = §ion->patches[patchID]; @@ -512,8 +510,7 @@ static void applyFilePatches(struct Section *section, struct Section *dataSectio if (patch->type == PATCHTYPE_JR) { // Offset is relative to the byte *after* the operand // PC as operand to `jr` is lower than reference PC by 2 - uint16_t address = patch->pcSection->org - + patch->pcOffset + 2; + uint16_t address = patch->pcSection->org + patch->pcOffset + 2; int16_t jumpOffset = value - address; if (!isError && (jumpOffset < -128 || jumpOffset > 127)) @@ -555,6 +552,9 @@ static void applyFilePatches(struct Section *section, struct Section *dataSectio */ static void applyPatches(struct Section *section, void *arg) { + if (!sect_HasData(section->type)) + return; + (void)arg; struct Section *dataSection = section; diff --git a/src/link/script.c b/src/link/script.c index 33311447..08c6dc62 100644 --- a/src/link/script.c +++ b/src/link/script.c @@ -365,8 +365,7 @@ static enum LinkerScriptParserState parserState = PARSER_FIRSTTIME; struct SectionPlacement *script_NextSection(void) { - static struct SectionPlacement section; - static enum SectionType type; + static struct SectionPlacement placement; static uint32_t bank; static uint32_t bankID; @@ -380,7 +379,7 @@ struct SectionPlacement *script_NextSection(void) curaddr[i][b] = startaddr[i]; } - type = SECTTYPE_INVALID; + placement.type = SECTTYPE_INVALID; parserState = PARSER_LINESTART; } @@ -392,15 +391,15 @@ struct SectionPlacement *script_NextSection(void) bool hasArg; uint32_t arg; - if (type != SECTTYPE_INVALID) { - if (curaddr[type][bankID] > endaddr(type) + 1) + if (placement.type != SECTTYPE_INVALID) { + if (curaddr[placement.type][bankID] > endaddr(placement.type) + 1) errx("%s(%" PRIu32 "): Sections would extend past the end of %s ($%04" PRIx16 " > $%04" PRIx16 ")", - linkerScriptName, lineNo, typeNames[type], - curaddr[type][bankID], endaddr(type)); - if (curaddr[type][bankID] < startaddr[type]) + linkerScriptName, lineNo, typeNames[placement.type], + curaddr[placement.type][bankID], endaddr(placement.type)); + if (curaddr[placement.type][bankID] < startaddr[placement.type]) errx("%s(%" PRIu32 "): PC underflowed ($%04" PRIx16 " < $%04" PRIx16 ")", linkerScriptName, lineNo, - curaddr[type][bankID], startaddr[type]); + curaddr[placement.type][bankID], startaddr[placement.type]); } switch (parserState) { @@ -431,21 +430,21 @@ struct SectionPlacement *script_NextSection(void) case TOKEN_STRING: parserState = PARSER_LINEEND; - if (type == SECTTYPE_INVALID) + if (placement.type == SECTTYPE_INVALID) errx("%s(%" PRIu32 "): Didn't specify a location before the section", linkerScriptName, lineNo); - section.section = + placement.section = sect_GetSection(token->attr.string); - if (!section.section) + if (!placement.section) errx("%s(%" PRIu32 "): Unknown section \"%s\"", linkerScriptName, lineNo, token->attr.string); - section.org = curaddr[type][bankID]; - section.bank = bank; + placement.org = curaddr[placement.type][bankID]; + placement.bank = bank; - curaddr[type][bankID] += section.section->size; - return §ion; + curaddr[placement.type][bankID] += placement.section->size; + return &placement; case TOKEN_COMMAND: case TOKEN_BANK: @@ -466,35 +465,35 @@ struct SectionPlacement *script_NextSection(void) arg = hasArg ? token->attr.number : 0; if (tokType == TOKEN_COMMAND) { - if (type == SECTTYPE_INVALID) + if (placement.type == SECTTYPE_INVALID) errx("%s(%" PRIu32 "): Didn't specify a location before the command", linkerScriptName, lineNo); if (!hasArg) errx("%s(%" PRIu32 "): Command specified without an argument", linkerScriptName, lineNo); - processCommand(attr.command, arg, &curaddr[type][bankID]); + processCommand(attr.command, arg, &curaddr[placement.type][bankID]); } else { /* TOKEN_BANK */ - type = attr.secttype; + placement.type = attr.secttype; /* * If there's only one bank, * specifying the number is optional. */ - if (!hasArg && nbbanks(type) != 1) + if (!hasArg && nbbanks(placement.type) != 1) errx("%s(%" PRIu32 "): Didn't specify a bank number", linkerScriptName, lineNo); else if (!hasArg) - arg = bankranges[type][0]; - else if (arg < bankranges[type][0]) + arg = bankranges[placement.type][0]; + else if (arg < bankranges[placement.type][0]) errx("%s(%" PRIu32 "): specified bank number is too low (%" PRIu32 " < %" PRIu32 ")", linkerScriptName, lineNo, - arg, bankranges[type][0]); - else if (arg > bankranges[type][1]) + arg, bankranges[placement.type][0]); + else if (arg > bankranges[placement.type][1]) errx("%s(%" PRIu32 "): specified bank number is too high (%" PRIu32 " > %" PRIu32 ")", linkerScriptName, lineNo, - arg, bankranges[type][1]); + arg, bankranges[placement.type][1]); bank = arg; - bankID = arg - bankranges[type][0]; + bankID = arg - bankranges[placement.type][0]; } /* If we read a token we shouldn't have... */ diff --git a/src/link/sdas_obj.c b/src/link/sdas_obj.c new file mode 100644 index 00000000..1934e940 --- /dev/null +++ b/src/link/sdas_obj.c @@ -0,0 +1,759 @@ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "linkdefs.h" +#include "platform.h" + +#include "link/assign.h" +#include "link/main.h" +#include "link/sdas_obj.h" +#include "link/section.h" +#include "link/symbol.h" + +enum NumberType { + HEX = 16, // X + DEC = 10, // D + OCT = 8, // Q +}; + +static void consumeLF(struct FileStackNode const *where, uint32_t lineNo, FILE *file) { + if (getc(file) != '\n') + fatal(where, lineNo, "Bad line ending (CR without LF)"); +} + +static char const *delim = " \f\n\r\t\v"; // Whitespace according to the C and POSIX locales + +static int nextLine(char **restrict lineBuf, size_t *restrict bufLen, uint32_t *restrict lineNo, struct FileStackNode const *where, FILE *file) { +retry: + ++*lineNo; + int firstChar = getc(file); + + switch (firstChar) { + case EOF: + return EOF; + case ';': + // Discard comment line + // TODO: if `;!FILE [...]` on the first line (`lineNo`), return it + do { + firstChar = getc(file); + } while (firstChar != EOF && firstChar != '\r' && firstChar != '\n'); + // fallthrough + case '\r': + if (firstChar == '\r' && getc(file) != '\n') + consumeLF(where, *lineNo, file); + // fallthrough + case '\n': + goto retry; + } + + size_t i = 0; + + for (;;) { + if (i >= *bufLen) { + assert(*bufLen != 0); + *bufLen *= 2; + *lineBuf = realloc(*lineBuf, *bufLen); + if (!*lineBuf) + fatal(where, *lineNo, "Failed to realloc: %s", strerror(errno)); + } + + int c = getc(file); + + switch (c) { + case '\r': + consumeLF(where, *lineNo, file); + // fallthrough + case '\n': + case EOF: + (*lineBuf)[i] = '\0'; // Terminate the string (space was ensured above) + return firstChar; + } + (*lineBuf)[i] = c; + ++i; + } +} + +static uint32_t readNumber(char const *restrict str, char const **endptr, enum NumberType base) { + uint32_t res = 0; + + for (;;) { + static char const *digits = "0123456789ABCDEF"; + char const *ptr = strchr(digits, toupper(*str)); + + if (!ptr || ptr - digits >= base) { + *endptr = str; + return res; + } + ++str; + res = res * base + (ptr - digits); + } +} + +static uint32_t parseNumber(struct FileStackNode const *where, uint32_t lineNo, char const *restrict str, enum NumberType base) { + if (str[0] == '\0') + fatal(where, lineNo, "Expected number, got empty string"); + + char const *endptr; + uint32_t res = readNumber(str, &endptr, base); + + if (*endptr != '\0') + fatal(where, lineNo, "Expected number, got \"%s\"", str); + return res; +} + +static uint8_t parseByte(struct FileStackNode const *where, uint32_t lineNo, char const *restrict str, enum NumberType base) { + uint32_t num = parseNumber(where, lineNo, str, base); + + if (num > UINT8_MAX) + fatal(where, lineNo, "\"%s\" is not a byte", str); + return num; +} + +enum AreaFlags { + AREA_TYPE = 2, // 0: Concatenate, 1: overlay + AREA_ISABS, // 0: Relative (???) address, 1: absolute address + AREA_PAGING, // Unsupported + + AREA_ALL_FLAGS = 1 << AREA_TYPE | 1 << AREA_ISABS | 1 << AREA_PAGING, +}; + +enum RelocFlags { + RELOC_SIZE, // 0: 16-bit, 1: 8-bit + RELOC_ISSYM, // 0: Area, 1: Symbol + RELOC_ISPCREL, // 0: Normal, 1: PC-relative + RELOC_EXPR16, // Only for 8-bit size; 0: 8-bit expr, 1: 16-bit expr + RELOC_SIGNED, // 0: signed, 1: unsigned + RELOC_ZPAGE, // Unsupported + RELOC_NPAGE, // Unsupported + RELOC_WHICHBYTE, // 8-bit size with 16-bit expr only; 0: LOW(), 1: HIGH() + RELOC_EXPR24, // Only for 8-bit size; 0: follow RELOC_EXPR16, 1: 24-bit expr + RELOC_BANKBYTE, // 8-bit size with 24-bit expr only; 0: follow RELOC_WHICHBYTE, 1: BANK() + + RELOC_ALL_FLAGS = 1 << RELOC_SIZE | 1 << RELOC_ISSYM | 1 << RELOC_ISPCREL | 1 << RELOC_EXPR16 + | 1 << RELOC_SIGNED | 1 << RELOC_ZPAGE | 1 << RELOC_NPAGE | 1 << RELOC_WHICHBYTE + | 1 << RELOC_EXPR24 | 1 << RELOC_BANKBYTE, +}; + +void sdobj_ReadFile(struct FileStackNode const *where, FILE *file) { + size_t bufLen = 256; + char *line = malloc(bufLen); + char const *token; + +#define getToken(ptr, ...) do { \ + token = strtok((ptr), delim); \ + if (!token) \ + fatal(where, lineNo, __VA_ARGS__); \ +} while (0) +#define expectEol(...) do { \ + token = strtok(NULL, delim); \ + if (token) \ + fatal(where, lineNo, __VA_ARGS__); \ +} while (0) +#define expectToken(expected, lineType) do { \ + getToken(NULL, "'%c' line is too short", (lineType)); \ + if (strcasecmp(token, (expected)) != 0) \ + fatal(where, lineNo, "Malformed '%c' line: expected \"%s\", got \"%s\"", (lineType), (expected), token); \ +} while (0) + + if (!line) + fatal(where, 0, "Failed to alloc a line buffer: %s", strerror(errno)); + uint32_t lineNo = 0; + int lineType = nextLine(&line, &bufLen, &lineNo, where, file); + enum NumberType numberType; + + // The first letter (thus, the line type) identifies the integer type + switch (lineType) { + case EOF: + fatal(where, lineNo, "SDCC object only contains comments and empty lines"); + case 'X': + numberType = HEX; + break; + case 'D': + numberType = DEC; + break; + case 'Q': + numberType = OCT; + break; + default: + fatal(where, lineNo, "This does not look like a SDCC object file (unknown integer format '%c')", lineType); + } + + switch (line[0]) { + case 'L': + break; + case 'H': + fatal(where, lineNo, "Big-endian SDCC object files are not supported"); + default: + fatal(where, lineNo, "Unknown endianness type '%c'", line[0]); + } + +#define ADDR_SIZE 3 + if (line[1] != '0' + ADDR_SIZE) + fatal(where, lineNo, "Unknown or unsupported address size '%c'", line[1]); + + if (line[2] != '\0') + warning(where, lineNo, "Ignoring unknown characters (\"%s\") in first line", &line[2]); + + // Header line + + lineType = nextLine(&line, &bufLen, &lineNo, where, file); + if (lineType != 'H') + fatal(where, lineNo, "Expected header line, got '%c' line", lineType); + // Expected format: "A areas S global symbols" + + getToken(line, "Empty 'H' line"); + uint32_t expectedNbAreas = parseNumber(where, lineNo, token, numberType); + + expectToken("areas", 'H'); + + getToken(NULL, "'H' line is too short"); + uint32_t expectedNbSymbols = parseNumber(where, lineNo, token, numberType); + + expectToken("global", 'H'); + + expectToken("symbols", 'H'); + + expectEol("'H' line is too long"); + + // Now, let's parse the rest of the lines as they come! + + struct { + struct Section *section; + uint16_t writeIndex; + } *fileSections = NULL; + struct Symbol **fileSymbols = malloc(sizeof(*fileSymbols) * expectedNbSymbols); + size_t nbSections = 0, nbSymbols = 0; + + if (!fileSymbols) + fatal(where, lineNo, "Failed to alloc file symbols table: %s", strerror(errno)); + size_t nbBytes = 0; // How many bytes are in `data`, including the ADDR_SIZE "header" bytes + size_t dataCapacity = 16 + ADDR_SIZE; // SDCC object files usually contain 16 bytes per T line + uint8_t *data = malloc(sizeof(*data) * dataCapacity); + + if (!data) + fatal(where, lineNo, "Failed to alloc data buffer: %s", strerror(errno)); + for (;;) { + lineType = nextLine(&line, &bufLen, &lineNo, where, file); + if (lineType == EOF) + break; + switch (lineType) { + uint32_t tmp; + + case 'M': // Module name + case 'O': // Assembler flags + // Ignored + break; + + case 'A': + if (nbSections == expectedNbAreas) + warning(where, lineNo, "Got more 'A' lines than the expected %" PRIu32, expectedNbAreas); + fileSections = realloc(fileSections, sizeof(*fileSections) * (nbSections + 1)); + if (!fileSections) + fatal(where, lineNo, "Failed to realloc file areas: %s", strerror(errno)); + fileSections[nbSections].writeIndex = 0; +#define curSection (fileSections[nbSections].section) + curSection = malloc(sizeof(*curSection)); + if (!curSection) + fatal(where, lineNo, "Failed to alloc new area: %s", strerror(errno)); + + getToken(line, "'A' line is too short"); + assert(strlen(token) != 0); // This should be impossible, tokens are non-empty + curSection->name = strdup(token); // We need a pointer that will live longer + if (!curSection->name) + fatal(where, lineNo, "Failed to alloc new area's name: %s", strerror(errno)); + // The following is required for fragment offsets to be reliably predicted + for (size_t i = 0; i < nbSections; ++i) { + if (!strcmp(token, fileSections[i].section->name)) + fatal(where, lineNo, "Area \"%s\" already defined earlier", token); + } + + expectToken("size", 'A'); + + getToken(NULL, "'A' line is too short"); + tmp = parseNumber(where, lineNo, token, numberType); + if (tmp > UINT16_MAX) + fatal(where, lineNo, "Area \"%s\" is larger than the GB address space!?", curSection->name); + curSection->size = tmp; + + expectToken("flags", 'A'); + + getToken(NULL, "'A' line is too short"); + tmp = parseNumber(where, lineNo, token, numberType); + if (tmp & (1 << AREA_PAGING)) + fatal(where, lineNo, "Internal error: paging is not supported"); + curSection->isAddressFixed = tmp & (1 << AREA_ISABS); + curSection->isBankFixed = curSection->isAddressFixed; + curSection->modifier = curSection->isAddressFixed || (tmp & (1 << AREA_TYPE)) + ? SECTION_NORMAL : SECTION_FRAGMENT; + + expectToken("addr", 'A'); + + getToken(NULL, "'A' line is too short"); + tmp = parseNumber(where, lineNo, token, numberType); + curSection->org = tmp; // Truncation keeps the address portion only + curSection->bank = tmp >> 16; + + expectEol("'A' line is too long"); + + // Init the rest of the members + curSection->offset = 0; + if (curSection->isAddressFixed) { + uint8_t high = curSection->org >> 8; + + if (high < 0x40) { + curSection->type = SECTTYPE_ROM0; + } else if (high < 0x80) { + curSection->type = SECTTYPE_ROMX; + } else if (high < 0xA0) { + curSection->type = SECTTYPE_VRAM; + } else if (high < 0xC0) { + curSection->type = SECTTYPE_SRAM; + } else if (high < 0xD0) { + curSection->type = SECTTYPE_WRAM0; + } else if (high < 0xE0) { + curSection->type = SECTTYPE_WRAMX; + } else if (high < 0xFE) { + fatal(where, lineNo, "Areas in echo RAM are not supported"); + } else if (high < 0xFF) { + curSection->type = SECTTYPE_OAM; + } else { + curSection->type = SECTTYPE_HRAM; + } + } else { + curSection->type = SECTTYPE_INVALID; // This means "indeterminate" + } + curSection->isAlignFixed = false; // No such concept! + // The array will be allocated if the section does contain data + curSection->data = NULL; + curSection->nbPatches = 0; + curSection->patches = NULL; // Same as `data` + curSection->fileSymbols = fileSymbols; // IDs are instead per-section + curSection->nbSymbols = 0; + curSection->symbols = NULL; // Will be allocated on demand as well + curSection->nextu = NULL; +#undef curSection + ++nbSections; + break; + + case 'S': + if (nbSymbols == expectedNbSymbols) + warning(where, lineNo, "Got more 'S' lines than the expected %" PRIu32, expectedNbSymbols); + // `realloc` is dangerous, as sections contain a pointer to `fileSymbols`. + // We can try to be nice, but if the pointer moves, it's game over! + if (nbSymbols >= expectedNbSymbols) { + struct Symbol **newFileSymbols = realloc(fileSymbols, sizeof(*fileSymbols) * (nbSymbols + 1)); + + if (!newFileSymbols) + fatal(where, lineNo, "Failed to alloc extra symbols: %s", strerror(errno)); + if (newFileSymbols != fileSymbols) + fatal(where, lineNo, "Couldn't handle extra 'S' lines (pointer moved)"); + // No need to assign, obviously + } +#define symbol (fileSymbols[nbSymbols]) + symbol = malloc(sizeof(*symbol)); + if (!symbol) + fatal(where, lineNo, "Failed to alloc symbol: %s", strerror(errno)); + + // Init other members + symbol->objFileName = where->name; + symbol->src = where; + symbol->lineNo = lineNo; + + // No need to set the `sectionID`, since we can directly set the pointer + symbol->section = fileSections ? fileSections[nbSections - 1].section : NULL; + + getToken(line, "'S' line is too short"); + symbol->name = strdup(token); + if (!symbol->name) + fatal(where, lineNo, "Failed to alloc symbol name: %s", strerror(errno)); + + getToken(NULL, "'S' line is too short"); + // It might be an `offset`, but both types are the same so type punning is fine + symbol->value = parseNumber(where, lineNo, &token[3], numberType); + if (symbol->section && symbol->section->isAddressFixed) { + assert(symbol->offset >= symbol->section->org); + symbol->offset -= symbol->section->org; + assert(symbol->offset <= symbol->section->size); + } + + // Expected format: /[DR]ef[0-9A-F]+/i + if (token[0] == 'R' || token[0] == 'r') { + symbol->type = SYMTYPE_IMPORT; + // TODO: hard error if the rest is not zero + } else if (token[0] != 'D' && token[0] != 'd') { + fatal(where, lineNo, "'S' line is neither \"Def\" nor \"Ref\""); + } else { + // All symbols are exported + symbol->type = SYMTYPE_EXPORT; + struct Symbol const *other = sym_GetSymbol(symbol->name); + + if (other) { + // The same symbol can only be defined twice if neither + // definition is in a floating section + if ((other->section && !other->section->isAddressFixed) + || (symbol->section && !symbol->section->isAddressFixed)) { + sym_AddSymbol(symbol); // This will error out + } else if (other->value != symbol->value) { + error(where, lineNo, + "Definition of \"%s\" conflicts with definition in %s (%" PRId32 " != %" PRId32 ")", + symbol->name, other->objFileName, symbol->value, other->value); + } + } else { + // Add a new definition + sym_AddSymbol(symbol); + } + // It's fine to keep modifying the symbol after `AddSymbol`, only + // the name must not be modified + } + if (strncasecmp(&token[1], "ef", 2) != 0) + fatal(where, lineNo, "'S' line is neither \"Def\" nor \"Ref\""); + + if (nbSections != 0) { + struct Section *section = fileSections[nbSections - 1].section; + + ++section->nbSymbols; + section->symbols = realloc(section->symbols, sizeof(section->symbols[0]) * section->nbSymbols); + if (!section->symbols) + fatal(where, lineNo, "Failed to realloc \"%s\"'s symbol list: %s", section->name, strerror(errno)); + section->symbols[section->nbSymbols - 1] = symbol; + } +#undef symbol + + expectEol("'S' line is too long"); + + ++nbSymbols; + break; + + case 'T': + // Now, time to parse the data! + if (nbBytes != 0) + warning(where, lineNo, "Previous 'T' line had no 'R' line (ignored)"); + + nbBytes = 0; + for (token = strtok(line, delim); token; token = strtok(NULL, delim)) { + if (dataCapacity == nbBytes) { + dataCapacity *= 2; + data = realloc(data, sizeof(*data) * dataCapacity); + if (!data) + fatal(where, lineNo, "Failed to realloc data buffer: %s", strerror(errno)); + } + data[nbBytes] = parseByte(where, lineNo, token, numberType); + ++nbBytes; + } + + if (nbBytes < ADDR_SIZE) + fatal(where, lineNo, "'T' line is too short"); + // Importantly, now we know that `nbBytes != 0`, which means "pending data" + break; + + case 'R': // Supposed to directly follow `T` + if (nbBytes == 0) { + warning(where, lineNo, "'R' line with no 'T' line, ignoring"); + break; + } + + // First two bytes are ignored + getToken(line, "'R' line is too short"); + getToken(NULL, "'R' line is too short"); + uint16_t areaIdx; + + getToken(NULL, "'R' line is too short"); + areaIdx = parseByte(where, lineNo, token, numberType); + getToken(NULL, "'R' line is too short"); + areaIdx |= (uint16_t)parseByte(where, lineNo, token, numberType) << 8; + if (areaIdx >= nbSections) + fatal(where, lineNo, "'R' line references area #%" PRIu16 ", but there are only %zu (so far)", areaIdx, nbSections); + assert(fileSections); // There should be at least one, from the above check + struct Section *section = fileSections[areaIdx].section; + uint16_t *writeIndex = &fileSections[areaIdx].writeIndex; + uint8_t writtenOfs = ADDR_SIZE; // Bytes before this have been written to ->data + uint16_t addr = data[0] | data[1] << 8; + + if (section->isAddressFixed) { + if (addr < section->org) + fatal(where, lineNo, "'T' line reports address $%04" PRIx16 " in \"%s\", which starts at $%04" PRIx16, addr, section->name, section->org); + addr -= section->org; + } + // Lines are emitted that violate this check but contain no "payload"; + // ignore those. "Empty" lines shouldn't trigger allocation, either. + if (nbBytes != ADDR_SIZE) { + if (addr != *writeIndex) + fatal(where, lineNo, "'T' lines which don't append to their section are not supported (%" PRIu16 " != %" PRIu16 ")", addr, *writeIndex); + if (!section->data) { + assert(section->size != 0); + section->data = malloc(section->size); + if (!section->data) + fatal(where, lineNo, "Failed to alloc data for \"%s\": %s", section->name, strerror(errno)); + } + } + + // Processing relocations is made difficult by SDLD's honestly quite bonkers + // handling of the thing. + // The way they work is that 16-bit relocs are, simply enough, writing a + // 16-bit value over a 16-bit "gap". Nothing weird here. + // 8-bit relocs, however, do not write an 8-bit value over an 8-bit gap! + // They write an 8-bit value over a 16-bit gap... and either of the two + // bytes is *discarded*. The "24-bit" flag extends this behavior to three + // bytes instead of two, but the idea's the same. + // Additionally, the "offset" is relative to *before* bytes from previous + // relocs are removed, so this needs to be accounted for as well. + // This all can be "translated" to RGBDS parlance by generating the + // appropriate RPN expression (depending on flags), plus an addition for the + // bytes being patched over. + while ((token = strtok(NULL, delim)) != NULL) { + uint16_t flags = parseByte(where, lineNo, token, numberType); + + if ((flags & 0xF0) == 0xF0) { + getToken(NULL, "Incomplete relocation"); + flags = (flags & 0x0F) | (uint16_t)parseByte(where, lineNo, token, numberType) << 4; + } + + getToken(NULL, "Incomplete relocation"); + uint8_t offset = parseByte(where, lineNo, token, numberType); + + if (offset < ADDR_SIZE) + fatal(where, lineNo, "Relocation index cannot point to header (%" PRIu16 " < %u)", offset, ADDR_SIZE); + if (offset >= nbBytes) + fatal(where, lineNo, "Relocation index is out of bounds (%" PRIu16 " >= %zu)", offset, nbBytes); + + getToken(NULL, "Incomplete relocation"); + uint16_t idx = parseByte(where, lineNo, token, numberType); + + getToken(NULL, "Incomplete relocation"); + idx |= (uint16_t)parseByte(where, lineNo, token, numberType); + + // Loudly fail on unknown flags + if (flags & (1 << RELOC_ZPAGE | 1 << RELOC_NPAGE)) + fatal(where, lineNo, "Paging flags are not supported"); + if (flags & ~RELOC_ALL_FLAGS) + warning(where, lineNo, "Unknown reloc flags 0x%x", flags & ~RELOC_ALL_FLAGS); + + // Turn this into a Patch + section->patches = realloc(section->patches, sizeof(section->patches[0]) * (section->nbPatches + 1)); + if (!section->patches) + fatal(where, lineNo, "Failed to alloc extra patch for \"%s\"", section->name); + struct Patch *patch = §ion->patches[section->nbPatches]; + + patch->lineNo = lineNo; + patch->src = where; + patch->offset = offset - writtenOfs + *writeIndex; + if (section->nbPatches != 0 && section->patches[section->nbPatches - 1].offset >= patch->offset) + fatal(where, lineNo, "Relocs not sorted by offset are not supported (%" PRIu32 " >= %" PRIu32 ")", section->patches[section->nbPatches - 1].offset, patch->offset); + patch->pcSection = section; // No need to fill `pcSectionID`, then + patch->pcOffset = patch->offset - 1; // For `jr`s + + patch->type = flags & 1 << RELOC_SIZE ? PATCHTYPE_BYTE : PATCHTYPE_WORD; + uint8_t nbBaseBytes = patch->type == PATCHTYPE_BYTE ? ADDR_SIZE : 2; + uint32_t baseValue = 0; + + assert(offset < nbBytes); + if (nbBytes - offset < nbBaseBytes) + fatal(where, lineNo, "Reloc would patch out of bounds (%" PRIu8 " > %zu)", nbBaseBytes, nbBytes - offset); + for (uint8_t i = 0; i < nbBaseBytes; ++i) + baseValue = baseValue | data[offset + i] << (8 * i); + +// Extra size that must be reserved for additional operators +#define RPN_EXTRA_SIZE (5 + 1 + 5 + 1 + 5 + 1) // >> 8 & $FF, then + +#define allocPatch(size) do { \ + patch->rpnSize = (size); \ + patch->rpnExpression = malloc(patch->rpnSize + RPN_EXTRA_SIZE); \ + if (!patch->rpnExpression) \ + fatal(where, lineNo, "Failed to alloc RPN expression: %s", strerror(errno)); \ +} while (0) + // Bit 4 specifies signedness, but I don't think that matters? + // Generate a RPN expression from the info and flags + if (flags & 1 << RELOC_ISSYM) { + if (idx >= nbSymbols) + fatal(where, lineNo, "Reloc refers to symbol #%" PRIu16 " out of %zu", idx, nbSymbols); + struct Symbol const *sym = fileSymbols[idx]; + + // SDCC has a bunch of "magic symbols" that start with a + // letter and an underscore. These are not compatibility + // hacks, this is how SDLD actually works. + if (sym->name[0] == 'b' && sym->name[1] == '_') { + // Look for the symbol being referenced, and use its index instead + for (idx = 0; idx < nbSymbols; ++idx) { + if (strcmp(&sym->name[1], fileSymbols[idx]->name) == 0) + break; + } + if (idx == nbSymbols) + fatal(where, lineNo, "\"%s\" is missing a reference to \"%s\"", sym->name, &sym->name[1]); + allocPatch(5); + patch->rpnExpression[0] = RPN_BANK_SYM; + patch->rpnExpression[1] = idx; + patch->rpnExpression[2] = idx >> 8; + patch->rpnExpression[3] = idx >> 16; + patch->rpnExpression[4] = idx >> 24; + } else if (sym->name[0] == 'l' && sym->name[1] == '_') { + allocPatch(1 + strlen(&sym->name[2]) + 1); + patch->rpnExpression[0] = RPN_SIZEOF_SECT; + strcpy((char *)&patch->rpnExpression[1], &sym->name[2]); + } else if (sym->name[0] == 's' && sym->name[1] == '_') { + allocPatch(1 + strlen(&sym->name[2]) + 1); + patch->rpnExpression[0] = RPN_STARTOF_SECT; + strcpy((char *)&patch->rpnExpression[1], &sym->name[2]); + } else { + allocPatch(5); + patch->rpnExpression[0] = RPN_SYM; + patch->rpnExpression[1] = idx; + patch->rpnExpression[2] = idx >> 8; + patch->rpnExpression[3] = idx >> 16; + patch->rpnExpression[4] = idx >> 24; + } + } else { + if (idx >= nbSections) + fatal(where, lineNo, "Reloc refers to area #%" PRIu16 " out of %zu", idx, nbSections); + // It gets funky. If the area is absolute, *actually*, we + // must not add its base address, as the assembler will + // already have added it in `baseValue`. + // We counteract this by subtracting the section's base + // address from `baseValue`, undoing what the assembler did; + // this allows the relocation to still be correct, even if + // the section gets moved for any reason. + if (fileSections[idx].section->isAddressFixed) + baseValue -= fileSections[idx].section->org; + char const *name = fileSections[idx].section->name; + struct Section const *other = sect_GetSection(name); + + // Unlike with `s_`, referencing an area in this way + // wants the beginning of this fragment, so we must add the + // fragment's (putative) offset to account for this. + // The fragment offset prediction is guaranteed since each + // section can only have one fragment per SDLD object file, + // so this fragment will be appended to the existing section + // *if any*, and thus its offset will be the section's + // current size. + if (other) + baseValue += other->size; + allocPatch(1 + strlen(name) + 1); + patch->rpnSize = 1 + strlen(name) + 1; + patch->rpnExpression = malloc(patch->rpnSize + RPN_EXTRA_SIZE); + if (!patch->rpnExpression) + fatal(where, lineNo, "Failed to alloc RPN expression: %s", strerror(errno)); + patch->rpnExpression[0] = RPN_STARTOF_SECT; + // The cast is fine, it's just different signedness + strcpy((char *)&patch->rpnExpression[1], name); + } +#undef allocPatch + + patch->rpnExpression[patch->rpnSize] = RPN_CONST; + patch->rpnExpression[patch->rpnSize + 1] = baseValue; + patch->rpnExpression[patch->rpnSize + 2] = baseValue >> 8; + patch->rpnExpression[patch->rpnSize + 3] = baseValue >> 16; + patch->rpnExpression[patch->rpnSize + 4] = baseValue >> 24; + patch->rpnExpression[patch->rpnSize + 5] = RPN_ADD; + patch->rpnSize += 5 + 1; + + if (patch->type == PATCHTYPE_BYTE) { + // Despite the flag's name, as soon as it is set, 3 bytes + // are present, so we must skip two of them + if (flags & 1 << RELOC_EXPR16) { + if (*writeIndex + (offset - writtenOfs) > section->size) + fatal(where, lineNo, "'T' line writes past \"%s\"'s end (%u > %" PRIu16 ")", section->name, *writeIndex + (offset - writtenOfs), section->size); + // Copy all bytes up to those (plus the byte that we'll overwrite) + memcpy(§ion->data[*writeIndex], &data[writtenOfs], offset - writtenOfs + 1); + *writeIndex += offset - writtenOfs + 1; + writtenOfs = offset + 3; // Skip all three `baseValue` bytes, though + } + + // Append the necessary operations... + if (flags & 1 << RELOC_ISPCREL) { + // The result must *not* be truncated for those! + patch->type = PATCHTYPE_JR; + // TODO: check the other flags? + } else if (flags & 1 << RELOC_EXPR24 && flags & 1 << RELOC_BANKBYTE) { + patch->rpnExpression[patch->rpnSize] = RPN_CONST; + patch->rpnExpression[patch->rpnSize + 1] = 16; + patch->rpnExpression[patch->rpnSize + 2] = 16 >> 8; + patch->rpnExpression[patch->rpnSize + 3] = 16 >> 16; + patch->rpnExpression[patch->rpnSize + 4] = 16 >> 24; + patch->rpnExpression[patch->rpnSize + 5] = flags & 1 << RELOC_SIGNED ? RPN_SHR : RPN_USHR; + patch->rpnSize += 5 + 1; + } else { + if (flags & 1 << RELOC_EXPR16 && flags & 1 << RELOC_WHICHBYTE) { + patch->rpnExpression[patch->rpnSize] = RPN_CONST; + patch->rpnExpression[patch->rpnSize + 1] = 8; + patch->rpnExpression[patch->rpnSize + 2] = 8 >> 8; + patch->rpnExpression[patch->rpnSize + 3] = 8 >> 16; + patch->rpnExpression[patch->rpnSize + 4] = 8 >> 24; + patch->rpnExpression[patch->rpnSize + 5] = flags & 1 << RELOC_SIGNED ? RPN_SHR : RPN_USHR; + patch->rpnSize += 5 + 1; + } + patch->rpnExpression[patch->rpnSize] = RPN_CONST; + patch->rpnExpression[patch->rpnSize + 1] = 0xFF; + patch->rpnExpression[patch->rpnSize + 2] = 0xFF >> 8; + patch->rpnExpression[patch->rpnSize + 3] = 0xFF >> 16; + patch->rpnExpression[patch->rpnSize + 4] = 0xFF >> 24; + patch->rpnExpression[patch->rpnSize + 5] = RPN_AND; + patch->rpnSize += 5 + 1; + } + } else if (flags & 1 << RELOC_ISPCREL) { + assert(patch->type == PATCHTYPE_WORD); + fatal(where, lineNo, "16-bit PC-relative relocations are not supported"); + } else if (flags & (1 << RELOC_EXPR16 | 1 << RELOC_EXPR24)) { + fatal(where, lineNo, "Flags 0x%x are not supported for 16-bit relocs", flags & (1 << RELOC_EXPR16 | 1 << RELOC_EXPR24)); + } + + ++section->nbPatches; + } + + // If there is some data left to append, do so + if (writtenOfs != nbBytes) { + assert(nbBytes > writtenOfs); + if (*writeIndex + (nbBytes - writtenOfs) > section->size) + fatal(where, lineNo, "'T' line writes past \"%s\"'s end (%zu > %" PRIu16 ")", section->name, *writeIndex + (nbBytes - writtenOfs), section->size); + memcpy(§ion->data[*writeIndex], &data[writtenOfs], nbBytes - writtenOfs); + *writeIndex += nbBytes - writtenOfs; + } + + nbBytes = 0; // Do not allow two R lines to refer to the same T line + break; + + case 'P': + default: + warning(where, lineNo, "Unknown/unsupported line type '%c', ignoring", lineType); + break; + } + } + + if (nbBytes != 0) + warning(where, lineNo, "Last 'T' line had no 'R' line (ignored)"); + if (nbSections < expectedNbAreas) + warning(where, lineNo, "Expected %" PRIu32 " 'A' lines, got only %zu", expectedNbAreas, nbSections); + if (nbSymbols < expectedNbSymbols) + warning(where, lineNo, "Expected %" PRIu32 " 'S' lines, got only %zu", expectedNbSymbols, nbSymbols); + + nbSectionsToAssign += nbSections; + + for (size_t i = 0; i < nbSections; ++i) { + struct Section *section = fileSections[i].section; + + // RAM sections can have a size, but don't get any data (they shouldn't have any) + if (fileSections[i].writeIndex != section->size && fileSections[i].writeIndex != 0) + fatal(where, lineNo, "\"%s\" was not fully written (%" PRIu16 " < %" PRIu16 ")", section->name, fileSections[i].writeIndex, section->size); + + // This must be done last, so that `->data` is not NULL anymore + sect_AddSection(section); + + if (section->modifier == SECTION_FRAGMENT) { + // Add the fragment's offset to all of its symbols + for (uint32_t j = 0; j < section->nbSymbols; ++j) + section->symbols[j]->offset += section->offset; + } + } + +#undef expectEol +#undef expectToken +#undef getToken + + free(fileSections); + free(data); + fclose(file); +} diff --git a/src/link/section.c b/src/link/section.c index 86598030..f87b2889 100644 --- a/src/link/section.c +++ b/src/link/section.c @@ -6,6 +6,7 @@ * SPDX-License-Identifier: MIT */ +#include #include #include #include @@ -153,18 +154,33 @@ static void mergeSections(struct Section *target, struct Section *other, enum Se case SECTION_FRAGMENT: checkFragmentCompat(target, other); + // Append `other` to `target` + // Note that the order in which fragments are stored in the `nextu` list does not + // really matter, only that offsets are properly computed + other->offset = target->size; target->size += other->size; - other->offset = target->size - other->size; - if (sect_HasData(target->type)) { - /* Ensure we're not allocating 0 bytes */ - target->data = realloc(target->data, - sizeof(*target->data) * target->size + 1); - if (!target->data) - errx("Failed to concatenate \"%s\"'s fragments", target->name); - memcpy(target->data + target->size - other->size, other->data, other->size); + // Normally we'd check that `sect_HasData`, but SDCC areas may be `_INVALID` here + // Note that if either fragment has data (= a non-NULL `data` pointer), then it's + // assumed that both fragments "have data", and thus should either have a non-NULL + // `data` pointer, or a size of 0. + if (other->data) { + if (target->data) { + /* Ensure we're not allocating 0 bytes */ + target->data = realloc(target->data, + sizeof(*target->data) * target->size + 1); + if (!target->data) + errx("Failed to concatenate \"%s\"'s fragments", target->name); + memcpy(&target->data[other->offset], other->data, other->size); + } else { + assert(target->size == other->size); // It has been increased just above + target->data = other->data; + other->data = NULL; // Prevent a double free() + } /* Adjust patches' PC offsets */ for (uint32_t patchID = 0; patchID < other->nbPatches; patchID++) other->patches[patchID].pcOffset += other->offset; + } else if (target->data) { + assert(other->size == 0); } break; @@ -208,39 +224,33 @@ void sect_CleanupSections(void) hash_EmptyMap(sections); } -static bool sanityChecksFailed; - static void doSanityChecks(struct Section *section, void *ptr) { (void)ptr; -#define fail(...) do { \ - warnx(__VA_ARGS__); \ - sanityChecksFailed = true; \ -} while (0) /* Sanity check the section's type */ if (section->type < 0 || section->type >= SECTTYPE_INVALID) { - fail("Section \"%s\" has an invalid type.", section->name); + error(NULL, 0, "Section \"%s\" has an invalid type", section->name); return; } if (is32kMode && section->type == SECTTYPE_ROMX) { if (section->isBankFixed && section->bank != 1) - fail("%s: ROMX sections must be in bank 1 (if any) with option -t", + error(NULL, 0, "%s: ROMX sections must be in bank 1 (if any) with option -t", section->name); else section->type = SECTTYPE_ROM0; } if (isWRA0Mode && section->type == SECTTYPE_WRAMX) { if (section->isBankFixed && section->bank != 1) - fail("%s: WRAMX sections must be in bank 1 with options -w or -d", + error(NULL, 0, "%s: WRAMX sections must be in bank 1 with options -w or -d", section->name); else section->type = SECTTYPE_WRAMX; } if (isDmgMode && section->type == SECTTYPE_VRAM && section->bank == 1) - fail("%s: VRAM bank 1 can't be used with option -d", + error(NULL, 0, "%s: VRAM bank 1 can't be used with option -d", section->name); /* @@ -252,20 +262,20 @@ static void doSanityChecks(struct Section *section, void *ptr) /* Too large an alignment may not be satisfiable */ if (section->isAlignFixed && (section->alignMask & startaddr[section->type])) - fail("%s: %s sections cannot be aligned to $%04x bytes", + error(NULL, 0, "%s: %s sections cannot be aligned to $%04x bytes", section->name, typeNames[section->type], section->alignMask + 1); uint32_t minbank = bankranges[section->type][0], maxbank = bankranges[section->type][1]; if (section->isBankFixed && section->bank < minbank && section->bank > maxbank) - fail(minbank == maxbank + error(NULL, 0, minbank == maxbank ? "Cannot place section \"%s\" in bank %" PRIu32 ", it must be %" PRIu32 : "Cannot place section \"%s\" in bank %" PRIu32 ", it must be between %" PRIu32 " and %" PRIu32, section->name, section->bank, minbank, maxbank); /* Check if section has a chance to be placed */ if (section->size > maxsize[section->type]) - fail("Section \"%s\" is bigger than the max size for that type: %#" PRIx16 " > %#" PRIx16, + error(NULL, 0, "Section \"%s\" is bigger than the max size for that type: %#" PRIx16 " > %#" PRIx16, section->name, section->size, maxsize[section->type]); /* Translate loose constraints to strong ones when they're equivalent */ @@ -279,7 +289,7 @@ static void doSanityChecks(struct Section *section, void *ptr) /* It doesn't make sense to have both org and alignment set */ if (section->isAlignFixed) { if ((section->org & section->alignMask) != section->alignOfs) - fail("Section \"%s\"'s fixed address doesn't match its alignment", + error(NULL, 0, "Section \"%s\"'s fixed address doesn't match its alignment", section->name); section->isAlignFixed = false; } @@ -287,12 +297,12 @@ static void doSanityChecks(struct Section *section, void *ptr) /* Ensure the target address is valid */ if (section->org < startaddr[section->type] || section->org > endaddr(section->type)) - fail("Section \"%s\"'s fixed address %#" PRIx16 " is outside of range [%#" + error(NULL, 0, "Section \"%s\"'s fixed address %#" PRIx16 " is outside of range [%#" PRIx16 "; %#" PRIx16 "]", section->name, section->org, startaddr[section->type], endaddr(section->type)); if (section->org + section->size > endaddr(section->type) + 1) - fail("Section \"%s\"'s end address %#x is greater than last address %#x", + error(NULL, 0, "Section \"%s\"'s end address %#x is greater than last address %#x", section->name, section->org + section->size, endaddr(section->type) + 1); } @@ -303,6 +313,4 @@ static void doSanityChecks(struct Section *section, void *ptr) void sect_DoSanityChecks(void) { sect_ForEach(doSanityChecks, NULL); - if (sanityChecksFailed) - errx("Sanity checks failed"); } diff --git a/test/link/rom0-tiny-no-t.out b/test/link/rom0-tiny-no-t.out index 5493b563..9143b45f 100644 --- a/test/link/rom0-tiny-no-t.out +++ b/test/link/rom0-tiny-no-t.out @@ -1,2 +1,2 @@ -warning: Section "rom" is bigger than the max size for that type: 0x8000 > 0x4000 -error: Sanity checks failed +error: Section "rom" is bigger than the max size for that type: 0x8000 > 0x4000 +Linking failed with 1 error diff --git a/test/link/vram-fixed-dmg-mode-d.out b/test/link/vram-fixed-dmg-mode-d.out index 16c50329..bbc42471 100644 --- a/test/link/vram-fixed-dmg-mode-d.out +++ b/test/link/vram-fixed-dmg-mode-d.out @@ -1,2 +1,2 @@ -warning: v1: VRAM bank 1 can't be used with option -d -error: Sanity checks failed +error: v1: VRAM bank 1 can't be used with option -d +Linking failed with 1 error