/* SPDX-License-Identifier: MIT */ #include "link/sdas_obj.hpp" #include #include #include #include #include #include #include #include #include #include #include "helpers.hpp" #include "linkdefs.hpp" #include "platform.hpp" #include "link/assign.hpp" #include "link/main.hpp" #include "link/section.hpp" #include "link/symbol.hpp" enum NumberType { HEX = 16, // X DEC = 10, // D OCT = 8, // Q }; static void consumeLF(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(std::vector &lineBuf, uint32_t &lineNo, 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; } for (;;) { int c = getc(file); switch (c) { case '\r': consumeLF(where, lineNo, file); // fallthrough case '\n': case EOF: lineBuf.push_back('\0'); // Terminate the string (space was ensured above) return firstChar; } lineBuf.push_back(c); } } static uint32_t readNumber(char const *str, char const *&endptr, 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(FileStackNode const &where, uint32_t lineNo, char const *str, 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(FileStackNode const &where, uint32_t lineNo, char const *str, 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(FileStackNode const &where, FILE *file, std::vector &fileSymbols) { std::vector line(256); 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(nullptr, delim); \ if (token) \ fatal(&where, lineNo, __VA_ARGS__); \ } while (0) #define expectToken(expected, lineType) \ do { \ getToken(nullptr, "'%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) uint32_t lineNo = 0; int lineType = nextLine(line, lineNo, where, file); 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, 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.data(), "Empty 'H' line"); uint32_t expectedNbAreas = parseNumber(where, lineNo, token, numberType); expectToken("areas", 'H'); getToken(nullptr, "'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 FileSection { std::unique_ptr
section; uint16_t writeIndex; }; std::vector fileSections; std::vector data; for (;;) { lineType = nextLine(line, lineNo, where, file); if (lineType == EOF) break; switch (lineType) { case 'M': // Module name case 'O': // Assembler flags // Ignored break; case 'A': { if (fileSections.size() == expectedNbAreas) warning( &where, lineNo, "Got more 'A' lines than the expected %" PRIu32, expectedNbAreas ); std::unique_ptr
curSection = std::make_unique
(); getToken(line.data(), "'A' line is too short"); assert(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())) fatal(&where, lineNo, "Area \"%s\" already defined earlier", 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 = parseNumber(where, lineNo, token, numberType); if (tmp > UINT16_MAX) fatal( &where, lineNo, "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 = 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; // If the section is absolute, its name might not be unique; thus, mangle the name if (curSection->modifier == SECTION_NORMAL) { curSection->name.append(where.name()); curSection->name.append(" "); } curSection->name.append(sectName); expectToken("addr", 'A'); getToken(nullptr, "'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! curSection->fileSymbols = &fileSymbols; // IDs are instead per-section curSection->nextu = nullptr; fileSections.push_back({.section = std::move(curSection), .writeIndex = 0}); break; } case 'S': { if (fileSymbols.size() == expectedNbSymbols) warning( &where, lineNo, "Got more 'S' lines than the expected %" PRIu32, expectedNbSymbols ); Symbol &symbol = fileSymbols.emplace_back(); // Init other members symbol.objFileName = where.name().c_str(); symbol.src = &where; symbol.lineNo = lineNo; getToken(line.data(), "'S' line is too short"); symbol.name = token; getToken(nullptr, "'S' line is too short"); if (int32_t value = parseNumber(where, lineNo, &token[3], numberType); !fileSections.empty()) { // Symbols in sections are labels; their value is an offset Section *section = fileSections.back().section.get(); if (section->isAddressFixed) { assert(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; // 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; 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::tuple
{ if (auto *label = std::get_if