/* SPDX-License-Identifier: MIT */ #include "link/object.hpp" #include #include #include #include #include #include #include #include #include #include #include "error.hpp" #include "helpers.hpp" #include "linkdefs.hpp" #include "platform.hpp" #include "version.hpp" #include "link/assign.hpp" #include "link/main.hpp" #include "link/sdas_obj.hpp" #include "link/section.hpp" #include "link/symbol.hpp" static std::deque> symbolLists; static std::vector> nodes; // Helper functions for reading object files // Internal, DO NOT USE. // For helper wrapper macros defined below, such as `tryReadlong` #define tryRead(func, type, errval, vartype, var, file, ...) \ do { \ FILE *tmpFile = file; \ type tmpVal = func(tmpFile); \ /* TODO: maybe mark the condition as `unlikely`; how to do that portably? */ \ if (tmpVal == (errval)) { \ errx(__VA_ARGS__, feof(tmpFile) ? "Unexpected end of file" : strerror(errno)); \ } \ var = (vartype)tmpVal; \ } while (0) /* * Reads an unsigned long (32-bit) value from a file. * @param file The file to read from. This will read 4 bytes from the file. * @return The value read, cast to a int64_t, or -1 on failure. */ static int64_t readlong(FILE *file) { uint32_t value = 0; // Read the little-endian value byte by byte for (uint8_t shift = 0; shift < sizeof(value) * CHAR_BIT; shift += 8) { int byte = getc(file); if (byte == EOF) return INT64_MAX; // This must be casted to `unsigned`, not `uint8_t`. Rationale: // the type of the shift is the type of `byte` after undergoing // integer promotion, which would be `int` if this was casted to // `uint8_t`, because int is large enough to hold a byte. This // however causes values larger than 127 to be too large when // shifted, potentially triggering undefined behavior. value |= (unsigned int)byte << shift; } return value; } /* * Helper macro for reading longs from a file, and errors out if it fails to. * Not as a function to avoid overhead in the general case. * @param var The variable to stash the number into * @param file The file to read from. Its position will be advanced * @param ... A format string and related arguments; note that an extra string * argument is provided, the reason for failure */ #define tryReadlong(var, file, ...) \ tryRead(readlong, int64_t, INT64_MAX, long, var, file, __VA_ARGS__) // There is no `readbyte`, just use `fgetc` or `getc`. /* * Helper macro for reading bytes from a file, and errors out if it fails to. * Not as a function to avoid overhead in the general case. * @param var The variable to stash the number into * @param file The file to read from. Its position will be advanced * @param ... A format string and related arguments; note that an extra string * argument is provided, the reason for failure */ #define tryGetc(type, var, file, ...) tryRead(getc, int, EOF, type, var, file, __VA_ARGS__) /* * Helper macro for readings '\0'-terminated strings from a file, and errors out if it fails to. * Not as a function to avoid overhead in the general case. * @param var The variable to stash the string into * @param file The file to read from. Its position will be advanced * @param ... A format string and related arguments; note that an extra string * argument is provided, the reason for failure */ #define tryReadstring(var, file, ...) \ do { \ FILE *tmpFile = file; \ std::string &tmpVal = var; \ for (int tmpByte = getc(tmpFile); tmpByte != '\0'; tmpByte = getc(tmpFile)) { \ if (tmpByte == EOF) { \ errx(__VA_ARGS__, feof(tmpFile) ? "Unexpected end of file" : strerror(errno)); \ } else { \ tmpVal.push_back(tmpByte); \ } \ }; \ } while (0) // Functions to parse object files /* * Reads a file stack node form a file. * @param file The file to read from * @param nodes The file's array of nodes * @param i The ID of the node in the array * @param fileName The filename to report in errors */ static void readFileStackNode( FILE *file, std::vector &fileNodes, uint32_t i, char const *fileName ) { FileStackNode &node = fileNodes[i]; uint32_t parentID; tryReadlong(parentID, file, "%s: Cannot read node #%" PRIu32 "'s parent ID: %s", fileName, i); node.parent = parentID != (uint32_t)-1 ? &fileNodes[parentID] : nullptr; tryReadlong( node.lineNo, file, "%s: Cannot read node #%" PRIu32 "'s line number: %s", fileName, i ); tryGetc( FileStackNodeType, node.type, file, "%s: Cannot read node #%" PRIu32 "'s type: %s", fileName, i ); switch (node.type) { case NODE_FILE: case NODE_MACRO: node.data = ""; tryReadstring( node.name(), file, "%s: Cannot read node #%" PRIu32 "'s file name: %s", fileName, i ); break; uint32_t depth; case NODE_REPT: tryReadlong(depth, file, "%s: Cannot read node #%" PRIu32 "'s rept depth: %s", fileName, i); node.data = std::vector(depth); for (uint32_t k = 0; k < depth; k++) tryReadlong( node.iters()[k], file, "%s: Cannot read node #%" PRIu32 "'s iter #%" PRIu32 ": %s", fileName, i, k ); if (!node.parent) fatal( nullptr, 0, "%s is not a valid object file: root node (#%" PRIu32 ") may not be REPT", fileName, i ); } } /* * Reads a symbol from a file. * @param file The file to read from * @param symbol The symbol to fill * @param fileName The filename to report in errors */ static void readSymbol( FILE *file, Symbol &symbol, char const *fileName, std::vector const &fileNodes ) { tryReadstring(symbol.name, file, "%s: Cannot read symbol name: %s", fileName); tryGetc( ExportLevel, symbol.type, file, "%s: Cannot read \"%s\"'s type: %s", fileName, symbol.name.c_str() ); // If the symbol is defined in this file, read its definition if (symbol.type != SYMTYPE_IMPORT) { symbol.objFileName = fileName; uint32_t nodeID; tryReadlong( nodeID, file, "%s: Cannot read \"%s\"'s node ID: %s", fileName, symbol.name.c_str() ); symbol.src = &fileNodes[nodeID]; tryReadlong( symbol.lineNo, file, "%s: Cannot read \"%s\"'s line number: %s", fileName, symbol.name.c_str() ); int32_t sectionID, value; tryReadlong( sectionID, file, "%s: Cannot read \"%s\"'s section ID: %s", fileName, symbol.name.c_str() ); tryReadlong( value, file, "%s: Cannot read \"%s\"'s value: %s", fileName, symbol.name.c_str() ); if (sectionID == -1) { symbol.data = value; } else { symbol.data = Label{ .sectionID = sectionID, .offset = value, // Set the `.section` later based on the `.sectionID` .section = nullptr, }; } } else { symbol.data = -1; } } /* * Reads a patch from a file. * @param file The file to read from * @param patch The patch to fill * @param fileName The filename to report in errors * @param i The number of the patch to report in errors */ static void readPatch( FILE *file, Patch &patch, char const *fileName, std::string const §Name, uint32_t i, std::vector const &fileNodes ) { uint32_t nodeID, rpnSize; PatchType type; tryReadlong( nodeID, file, "%s: Unable to read \"%s\"'s patch #%" PRIu32 "'s node ID: %s", fileName, sectName.c_str(), i ); patch.src = &fileNodes[nodeID]; tryReadlong( patch.lineNo, file, "%s: Unable to read \"%s\"'s patch #%" PRIu32 "'s line number: %s", fileName, sectName.c_str(), i ); tryReadlong( patch.offset, file, "%s: Unable to read \"%s\"'s patch #%" PRIu32 "'s offset: %s", fileName, sectName.c_str(), i ); tryReadlong( patch.pcSectionID, file, "%s: Unable to read \"%s\"'s patch #%" PRIu32 "'s PC offset: %s", fileName, sectName.c_str(), i ); tryReadlong( patch.pcOffset, file, "%s: Unable to read \"%s\"'s patch #%" PRIu32 "'s PC offset: %s", fileName, sectName.c_str(), i ); tryGetc( PatchType, type, file, "%s: Unable to read \"%s\"'s patch #%" PRIu32 "'s type: %s", fileName, sectName.c_str(), i ); patch.type = type; tryReadlong( rpnSize, file, "%s: Unable to read \"%s\"'s patch #%" PRIu32 "'s RPN size: %s", fileName, sectName.c_str(), i ); patch.rpnExpression.resize(rpnSize); size_t nbElementsRead = fread(patch.rpnExpression.data(), 1, rpnSize, file); if (nbElementsRead != rpnSize) errx( "%s: Cannot read \"%s\"'s patch #%" PRIu32 "'s RPN expression: %s", fileName, sectName.c_str(), i, feof(file) ? "Unexpected end of file" : strerror(errno) ); } /* * Sets a patch's pcSection from its pcSectionID. * @param patch The patch to fix */ static void linkPatchToPCSect(Patch &patch, std::vector> const &fileSections) { patch.pcSection = patch.pcSectionID != (uint32_t)-1 ? fileSections[patch.pcSectionID].get() : nullptr; } /* * Reads a section from a file. * @param file The file to read from * @param section The section to fill * @param fileName The filename to report in errors */ static void readSection( FILE *file, Section §ion, char const *fileName, std::vector const &fileNodes ) { int32_t tmp; uint8_t byte; tryReadstring(section.name, file, "%s: Cannot read section name: %s", fileName); tryReadlong(tmp, file, "%s: Cannot read \"%s\"'s' size: %s", fileName, section.name.c_str()); if (tmp < 0 || tmp > UINT16_MAX) errx("\"%s\"'s section size (%" PRId32 ") is invalid", section.name.c_str(), tmp); section.size = tmp; section.offset = 0; tryGetc( uint8_t, byte, file, "%s: Cannot read \"%s\"'s type: %s", fileName, section.name.c_str() ); section.type = (SectionType)(byte & 0x3F); if (byte >> 7) section.modifier = SECTION_UNION; else if (byte >> 6) section.modifier = SECTION_FRAGMENT; else section.modifier = SECTION_NORMAL; tryReadlong(tmp, file, "%s: Cannot read \"%s\"'s org: %s", fileName, section.name.c_str()); section.isAddressFixed = tmp >= 0; if (tmp > UINT16_MAX) { error(nullptr, 0, "\"%s\"'s org is too large (%" PRId32 ")", section.name.c_str(), tmp); tmp = UINT16_MAX; } section.org = tmp; tryReadlong(tmp, file, "%s: Cannot read \"%s\"'s bank: %s", fileName, section.name.c_str()); section.isBankFixed = tmp >= 0; section.bank = tmp; tryGetc( uint8_t, byte, file, "%s: Cannot read \"%s\"'s alignment: %s", fileName, section.name.c_str() ); if (byte > 16) byte = 16; section.isAlignFixed = byte != 0; section.alignMask = (1 << byte) - 1; tryReadlong( tmp, file, "%s: Cannot read \"%s\"'s alignment offset: %s", fileName, section.name.c_str() ); if (tmp > UINT16_MAX) { error( nullptr, 0, "\"%s\"'s alignment offset is too large (%" PRId32 ")", section.name.c_str(), tmp ); tmp = UINT16_MAX; } section.alignOfs = tmp; if (sect_HasData(section.type)) { if (section.size) { section.data.resize(section.size); if (size_t nbRead = fread(section.data.data(), 1, section.size, file); nbRead != section.size) errx( "%s: Cannot read \"%s\"'s data: %s", fileName, section.name.c_str(), feof(file) ? "Unexpected end of file" : strerror(errno) ); } uint32_t nbPatches; tryReadlong( nbPatches, file, "%s: Cannot read \"%s\"'s number of patches: %s", fileName, section.name.c_str() ); section.patches.resize(nbPatches); for (uint32_t i = 0; i < nbPatches; i++) readPatch(file, section.patches[i], fileName, section.name, i, fileNodes); } } /* * Links a symbol to a section, keeping the section's symbol list sorted. * @param symbol The symbol to link * @param section The section to link */ static void linkSymToSect(Symbol &symbol, Section §ion) { uint32_t a = 0, b = section.symbols.size(); int32_t symbolOffset = symbol.label().offset; while (a != b) { uint32_t c = (a + b) / 2; int32_t otherOffset = section.symbols[c]->label().offset; if (otherOffset > symbolOffset) b = c; else a = c + 1; } section.symbols.insert(section.symbols.begin() + a, &symbol); } /* * Reads an assertion from a file * @param file The file to read from * @param assert The assertion to fill * @param fileName The filename to report in errors */ static void readAssertion( FILE *file, Assertion &assert, char const *fileName, uint32_t i, std::vector const &fileNodes ) { std::string assertName("Assertion #"); assertName += std::to_string(i); readPatch(file, assert.patch, fileName, assertName, 0, fileNodes); tryReadstring(assert.message, file, "%s: Cannot read assertion's message: %s", fileName); } void obj_ReadFile(char const *fileName, unsigned int fileID) { FILE *file; if (strcmp(fileName, "-")) { file = fopen(fileName, "rb"); } else { fileName = ""; file = fdopen(STDIN_FILENO, "rb"); // `stdin` is in text mode by default } if (!file) err("Failed to open file \"%s\"", fileName); Defer closeFile{[&] { fclose(file); }}; // 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(nullptr, 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].push_back({ .type = NODE_FILE, .data = fileName, .parent = nullptr, .lineNo = 0, }); std::vector &fileSymbols = symbolLists.emplace_front(); sdobj_ReadFile(nodes[fileID].back(), file, fileSymbols); return; } // Begin by reading the magic bytes int matchedElems; if (fscanf(file, RGBDS_OBJECT_VERSION_STRING "%n", &matchedElems) == 1 && matchedElems != strlen(RGBDS_OBJECT_VERSION_STRING)) errx("%s: Not a RGBDS object file", fileName); verbosePrint("Reading object file %s\n", fileName); uint32_t revNum; tryReadlong(revNum, file, "%s: Cannot read revision number: %s", fileName); if (revNum != RGBDS_OBJECT_REV) errx( "%s: Unsupported object file for rgblink %s; try rebuilding \"%s\"%s" " (expected revision %d, got %d)", fileName, get_package_version_string(), fileName, revNum > RGBDS_OBJECT_REV ? " or updating rgblink" : "", RGBDS_OBJECT_REV, revNum ); uint32_t nbNodes; uint32_t nbSymbols; uint32_t nbSections; tryReadlong(nbSymbols, file, "%s: Cannot read number of symbols: %s", fileName); tryReadlong(nbSections, file, "%s: Cannot read number of sections: %s", fileName); nbSectionsToAssign += nbSections; tryReadlong(nbNodes, file, "%s: Cannot read number of nodes: %s", fileName); nodes[fileID].resize(nbNodes); verbosePrint("Reading %u nodes...\n", nbNodes); for (uint32_t i = nbNodes; i--;) readFileStackNode(file, nodes[fileID], i, fileName); // This file's symbols, kept to link sections to them std::vector &fileSymbols = symbolLists.emplace_front(nbSymbols); std::vector nbSymPerSect(nbSections, 0); verbosePrint("Reading %" PRIu32 " symbols...\n", nbSymbols); for (uint32_t i = 0; i < nbSymbols; i++) { // Read symbol Symbol &symbol = fileSymbols[i]; readSymbol(file, symbol, fileName, nodes[fileID]); if (symbol.type == SYMTYPE_EXPORT) sym_AddSymbol(symbol); if (auto *label = std::get_if