// SPDX-License-Identifier: MIT #include "link/sdas_obj.hpp" #include #include #include #include #include #include #include #include #include #include "helpers.hpp" // assume, literal_strlen #include "linkdefs.hpp" #include "platform.hpp" #include "util.hpp" // parseWholeNumber #include "link/fstack.hpp" #include "link/section.hpp" #include "link/symbol.hpp" #include "link/warning.hpp" struct Location { FileStackNode const *src; uint32_t lineNo; }; static void consumeLF(Location const &where, FILE *file) { if (getc(file) != '\n') { fatalAt(where, "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(std::vector &lineBuf, Location &where, FILE *file) { int firstChar; for (;;) { ++where.lineNo; firstChar = getc(file); lineBuf.clear(); switch (firstChar) { case EOF: return EOF; case ';': // Discard comment line // TODO: if `;!FILE [...]` on the first line (`where.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, file); } [[fallthrough]]; case '\n': continue; } break; } for (;;) { int c = getc(file); switch (c) { case '\r': consumeLF(where, file); [[fallthrough]]; case '\n': case EOF: lineBuf.push_back('\0'); // Terminate the string (space was ensured above) return firstChar; } lineBuf.push_back(c); } } static uint64_t readNumber(Location const &where, char const *str, NumberBase base) { std::optional res = parseWholeNumber(str, base); if (!res) { fatalAt(where, "Expected number, got \"%s\"", str); } return *res; } static uint32_t readInt(Location const &where, char const *str, NumberBase base) { uint64_t num = readNumber(where, str, base); if (num > UINT32_MAX) { fatalAt(where, "\"%s\" is not an int", str); } return num; } static uint8_t readByte(Location const &where, char const *str, NumberBase base) { uint64_t num = readNumber(where, str, base); if (num > UINT8_MAX) { fatalAt(where, "\"%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(FileStackNode const &src, FILE *file, std::vector &fileSymbols) { Location where{.src = &src, .lineNo = 0}; std::vector line; line.reserve(256); char const *token; #define getToken(ptr, ...) \ do { \ token = strtok((ptr), delim); \ if (!token) { \ fatalAt(where, __VA_ARGS__); \ } \ } while (0) #define expectEol(...) \ do { \ token = strtok(nullptr, delim); \ if (token) { \ fatalAt(where, __VA_ARGS__); \ } \ } while (0) #define expectToken(expected, lineType) \ do { \ getToken(nullptr, "'%c' line is too short", (lineType)); \ if (strcasecmp(token, (expected)) != 0) { \ fatalAt( \ where, \ "Malformed '%c' line: expected \"%s\", got \"%s\"", \ (lineType), \ (expected), \ token \ ); \ } \ } while (0) int lineType = nextLine(line, where, file); // The first letter (thus, the line type) identifies the integer type NumberBase numberBase; switch (lineType) { case EOF: fatalAt(where, "SDCC object only contains comments and empty lines"); case 'X': numberBase = BASE_16; break; case 'D': numberBase = BASE_10; break; case 'Q': numberBase = BASE_8; break; default: fatalAt( where, "This does not look like a SDCC object file (unknown integer format '%c')", lineType ); } switch (line[0]) { case 'L': break; case 'H': fatalAt(where, "Big-endian SDCC object files are not supported"); default: fatalAt(where, "Unknown endianness type '%c'", line[0]); } uint8_t addrSize; switch (line[1]) { case '3': addrSize = 3; break; case '4': addrSize = 4; break; default: fatalAt(where, "Unknown or unsupported address size '%c'", line[1]); } if (line[2] != '\0') { warningAt(where, "Ignoring unknown characters (\"%s\") in first line", &line[2]); } // Header line lineType = nextLine(line, where, file); if (lineType != 'H') { fatalAt(where, "Expected header line, got '%c' line", lineType); } // Expected format: "A areas S global symbols" getToken(line.data(), "Empty 'H' line"); uint32_t expectedNbAreas = readInt(where, token, numberBase); expectToken("areas", 'H'); getToken(nullptr, "'H' line is too short"); uint32_t expectedNbSymbols = readInt(where, token, numberBase); fileSymbols.reserve(expectedNbSymbols); 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 FileSection { std::unique_ptr
section; uint16_t writeIndex; }; std::vector fileSections; std::vector data; for (;;) { lineType = nextLine(line, where, file); if (lineType == EOF) { break; } switch (lineType) { case 'M': // Module name case 'O': // Assembler flags // TODO: check the calling convention metadata as of SDCC 4.5.0 break; case 'A': { if (fileSections.size() == expectedNbAreas) { warningAt(where, "Got more 'A' lines than the expected %" PRIu32, expectedNbAreas); } std::unique_ptr
curSection = std::make_unique
(); curSection->src = where.src; curSection->lineNo = where.lineNo; getToken(line.data(), "'A' line is too short"); assume(strlen(token) != 0); // This should be impossible, tokens are non-empty // The following is required for fragment offsets to be reliably predicted for (FileSection &entry : fileSections) { if (!strcmp(token, entry.section->name.c_str())) { fatalAt(where, "Area \"%s\" already defined", token); } } char const *sectName = token; // We'll deal with the section's name depending on type expectToken("size", 'A'); getToken(nullptr, "'A' line is too short"); uint32_t tmp = readInt(where, token, numberBase); if (tmp > UINT16_MAX) { fatalAt( where, "Area \"%s\" is larger than the GB address space!?", curSection->name.c_str() ); } curSection->size = tmp; expectToken("flags", 'A'); getToken(nullptr, "'A' line is too short"); tmp = readInt(where, token, numberBase); if (tmp & (1 << AREA_PAGING)) { fatalAt(where, "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; // If the section is absolute, its name might not be unique; thus, mangle the name if (curSection->modifier == SECTION_NORMAL) { curSection->name.append(where.src->name()); curSection->name.append(" "); } curSection->name.append(sectName); expectToken("addr", 'A'); getToken(nullptr, "'A' line is too short"); tmp = readInt(where, token, numberBase); 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) { fatalAt(where, "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! curSection->fileSymbols = &fileSymbols; // IDs are instead per-section curSection->nextPiece = nullptr; fileSections.push_back({.section = std::move(curSection), .writeIndex = 0}); break; } case 'S': { if (fileSymbols.size() == expectedNbSymbols) { errorAt(where, "Got more 'S' lines than the expected %" PRIu32, expectedNbSymbols); break; // Refuse processing the line further. } Symbol &symbol = fileSymbols.emplace_back(); // Init other members symbol.src = where.src; symbol.lineNo = where.lineNo; getToken(line.data(), "'S' line is too short"); symbol.name = token; getToken(nullptr, "'S' line is too short"); if (int32_t value = readInt(where, &token[3], numberBase); !fileSections.empty()) { // Symbols in sections are labels; their value is an offset Section *section = fileSections.back().section.get(); if (section->isAddressFixed) { assume(value >= section->org && value <= section->org + section->size); value -= section->org; } // No need to set the `sectionID`, since we set the pointer symbol.data = Label{.sectionID = 0, .offset = value, .section = section}; } else { // Symbols without sections are just constants symbol.data = value; } // Expected format: /[DR]ef[0-9A-F]+/i if (token[0] == 'R' || token[0] == 'r') { symbol.type = SYMTYPE_IMPORT; sym_AddSymbol(symbol); // TODO: hard error if the rest is not zero } else if (token[0] != 'D' && token[0] != 'd') { fatalAt(where, "'S' line is neither \"Def\" nor \"Ref\""); } else { // All symbols are exported symbol.type = SYMTYPE_EXPORT; 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 auto checkSymbol = [](Symbol const &sym) -> std::pair
{ if (std::holds_alternative