mirror of
https://github.com/gbdev/rgbds.git
synced 2025-11-20 10:12:06 +00:00
Make RGBLINK able to link SDCC object files
This requires a LOT of tricky code, mostly due to the format itself being, er, not the most straightforward. Everything is converted to existing RGBLINK concepts (sections, patches, etc.), so the core code is essentially unchanged. (A couple of genuine RGBLINK bugs were uncovered along the way, so some of the core code *is* changed, notably regarding `SECTION FRAGMENT`s.) All of this code was clean-roomed, so SDCC's GPLv2 license does not apply.
This commit is contained in:
1
Makefile
1
Makefile
@@ -91,6 +91,7 @@ rgblink_obj := \
|
|||||||
src/link/output.o \
|
src/link/output.o \
|
||||||
src/link/patch.o \
|
src/link/patch.o \
|
||||||
src/link/script.o \
|
src/link/script.o \
|
||||||
|
src/link/sdas_obj.o \
|
||||||
src/link/section.o \
|
src/link/section.o \
|
||||||
src/link/symbol.o \
|
src/link/symbol.o \
|
||||||
src/extern/getopt.o \
|
src/extern/getopt.o \
|
||||||
|
|||||||
@@ -11,11 +11,13 @@
|
|||||||
#define RGBDS_LINK_SCRIPT_H
|
#define RGBDS_LINK_SCRIPT_H
|
||||||
|
|
||||||
#include <stdint.h>
|
#include <stdint.h>
|
||||||
|
#include "linkdefs.h"
|
||||||
|
|
||||||
extern FILE * linkerScript;
|
extern FILE * linkerScript;
|
||||||
|
|
||||||
struct SectionPlacement {
|
struct SectionPlacement {
|
||||||
struct Section *section;
|
struct Section *section;
|
||||||
|
enum SectionType type;
|
||||||
uint16_t org;
|
uint16_t org;
|
||||||
uint32_t bank;
|
uint32_t bank;
|
||||||
};
|
};
|
||||||
|
|||||||
19
include/link/sdas_obj.h
Normal file
19
include/link/sdas_obj.h
Normal file
@@ -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 <stdio.h>
|
||||||
|
|
||||||
|
struct FileStackNode;
|
||||||
|
|
||||||
|
void sdobj_ReadFile(struct FileStackNode const *fileName, FILE *file);
|
||||||
|
|
||||||
|
#endif /* RGBDS_LINK_SDAS_OBJ_H */
|
||||||
@@ -48,6 +48,8 @@ struct Section {
|
|||||||
enum SectionType type;
|
enum SectionType type;
|
||||||
enum SectionModifier modifier;
|
enum SectionModifier modifier;
|
||||||
bool isAddressFixed;
|
bool isAddressFixed;
|
||||||
|
// This `struct`'s address in ROM.
|
||||||
|
// Importantly for fragments, this does not include `offset`!
|
||||||
uint16_t org;
|
uint16_t org;
|
||||||
bool isBankFixed;
|
bool isBankFixed;
|
||||||
uint32_t bank;
|
uint32_t bank;
|
||||||
@@ -60,7 +62,7 @@ struct Section {
|
|||||||
/* Extra info computed during linking */
|
/* Extra info computed during linking */
|
||||||
struct Symbol **fileSymbols;
|
struct Symbol **fileSymbols;
|
||||||
uint32_t nbSymbols;
|
uint32_t nbSymbols;
|
||||||
struct Symbol const **symbols;
|
struct Symbol **symbols;
|
||||||
struct Section *nextu; /* The next "component" of this unionized sect */
|
struct Section *nextu; /* The next "component" of this unionized sect */
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -27,6 +27,7 @@ struct Symbol {
|
|||||||
int32_t lineNo;
|
int32_t lineNo;
|
||||||
int32_t sectionID;
|
int32_t sectionID;
|
||||||
union {
|
union {
|
||||||
|
// Both types must be identical
|
||||||
int32_t offset;
|
int32_t offset;
|
||||||
int32_t value;
|
int32_t value;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -9,6 +9,7 @@
|
|||||||
#ifndef RGBDS_LINKDEFS_H
|
#ifndef RGBDS_LINKDEFS_H
|
||||||
#define RGBDS_LINKDEFS_H
|
#define RGBDS_LINKDEFS_H
|
||||||
|
|
||||||
|
#include <assert.h>
|
||||||
#include <stdbool.h>
|
#include <stdbool.h>
|
||||||
#include <stdint.h>
|
#include <stdint.h>
|
||||||
|
|
||||||
@@ -74,6 +75,8 @@ enum SectionType {
|
|||||||
SECTTYPE_SRAM,
|
SECTTYPE_SRAM,
|
||||||
SECTTYPE_OAM,
|
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
|
SECTTYPE_INVALID
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -93,6 +96,7 @@ extern char const * const sectionModNames[];
|
|||||||
*/
|
*/
|
||||||
static inline bool sect_HasData(enum SectionType type)
|
static inline bool sect_HasData(enum SectionType type)
|
||||||
{
|
{
|
||||||
|
assert(type != SECTTYPE_INVALID);
|
||||||
return type == SECTTYPE_ROM0 || type == SECTTYPE_ROMX;
|
return type == SECTTYPE_ROM0 || type == SECTTYPE_ROMX;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -81,6 +81,7 @@ set(rgblink_src
|
|||||||
"link/output.c"
|
"link/output.c"
|
||||||
"link/patch.c"
|
"link/patch.c"
|
||||||
"link/script.c"
|
"link/script.c"
|
||||||
|
"link/sdas_obj.c"
|
||||||
"link/section.c"
|
"link/section.c"
|
||||||
"link/symbol.c"
|
"link/symbol.c"
|
||||||
"hashmap.c"
|
"hashmap.c"
|
||||||
|
|||||||
@@ -29,6 +29,7 @@
|
|||||||
#include "extern/getopt.h"
|
#include "extern/getopt.h"
|
||||||
|
|
||||||
#include "error.h"
|
#include "error.h"
|
||||||
|
#include "linkdefs.h"
|
||||||
#include "platform.h"
|
#include "platform.h"
|
||||||
#include "version.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 main(int argc, char *argv[])
|
||||||
{
|
{
|
||||||
int optionChar;
|
int optionChar;
|
||||||
@@ -457,7 +464,15 @@ int main(int argc, char *argv[])
|
|||||||
while ((placement = script_NextSection())) {
|
while ((placement = script_NextSection())) {
|
||||||
struct Section *section = placement->section;
|
struct Section *section = placement->section;
|
||||||
|
|
||||||
|
assert(section->offset == 0);
|
||||||
/* Check if this doesn't conflict with what the code says */
|
/* 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)
|
if (section->isBankFixed && placement->bank != section->bank)
|
||||||
error(NULL, 0, "Linker script contradicts \"%s\"'s bank placement",
|
error(NULL, 0, "Linker script contradicts \"%s\"'s bank placement",
|
||||||
section->name);
|
section->name);
|
||||||
@@ -479,22 +494,25 @@ int main(int argc, char *argv[])
|
|||||||
fclose(linkerScript);
|
fclose(linkerScript);
|
||||||
|
|
||||||
script_Cleanup();
|
script_Cleanup();
|
||||||
|
|
||||||
|
// If the linker script produced any errors, some sections may be in an invalid state
|
||||||
|
if (nbErrors != 0)
|
||||||
|
reportErrors();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/* then process them, */
|
/* then process them, */
|
||||||
obj_DoSanityChecks();
|
obj_DoSanityChecks();
|
||||||
|
if (nbErrors != 0)
|
||||||
|
reportErrors();
|
||||||
assign_AssignSections();
|
assign_AssignSections();
|
||||||
obj_CheckAssertions();
|
obj_CheckAssertions();
|
||||||
assign_Cleanup();
|
assign_Cleanup();
|
||||||
|
|
||||||
/* and finally output the result. */
|
/* and finally output the result. */
|
||||||
patch_ApplyPatches();
|
patch_ApplyPatches();
|
||||||
if (nbErrors) {
|
if (nbErrors != 0)
|
||||||
fprintf(stderr, "Linking failed with %" PRIu32 " error%s\n",
|
reportErrors();
|
||||||
nbErrors, nbErrors == 1 ? "" : "s");
|
|
||||||
exit(1);
|
|
||||||
}
|
|
||||||
out_WriteFiles();
|
out_WriteFiles();
|
||||||
|
|
||||||
/* Do cleanup before quitting, though. */
|
/* Do cleanup before quitting, though. */
|
||||||
|
|||||||
@@ -18,6 +18,7 @@
|
|||||||
#include "link/main.h"
|
#include "link/main.h"
|
||||||
#include "link/object.h"
|
#include "link/object.h"
|
||||||
#include "link/patch.h"
|
#include "link/patch.h"
|
||||||
|
#include "link/sdas_obj.h"
|
||||||
#include "link/section.h"
|
#include "link/section.h"
|
||||||
#include "link/symbol.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 symbol The symbol to link
|
||||||
* @param section The section 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;
|
uint32_t a = 0, b = section->nbSymbols;
|
||||||
|
|
||||||
@@ -421,7 +422,7 @@ static void linkSymToSect(struct Symbol const *symbol, struct Section *section)
|
|||||||
a = c + 1;
|
a = c + 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
struct Symbol const *tmp = symbol;
|
struct Symbol *tmp = symbol;
|
||||||
|
|
||||||
for (uint32_t i = a; i <= section->nbSymbols; i++) {
|
for (uint32_t i = a; i <= section->nbSymbols; i++) {
|
||||||
symbol = tmp;
|
symbol = tmp;
|
||||||
@@ -466,6 +467,39 @@ void obj_ReadFile(char const *fileName, unsigned int fileID)
|
|||||||
if (!file)
|
if (!file)
|
||||||
err("Could not open file %s", fileName);
|
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 */
|
/* Begin by reading the magic bytes and version number */
|
||||||
unsigned versionNumber;
|
unsigned versionNumber;
|
||||||
int matchedElems = fscanf(file, RGBDS_OBJECT_VERSION_STRING,
|
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);
|
readFileStackNode(file, nodes[fileID].nodes, i, fileName);
|
||||||
|
|
||||||
/* This file's symbols, kept to link sections to them */
|
/* This file's symbols, kept to link sections to them */
|
||||||
struct Symbol **fileSymbols =
|
struct Symbol **fileSymbols = malloc(sizeof(*fileSymbols) * nbSymbols + 1);
|
||||||
malloc(sizeof(*fileSymbols) * nbSymbols + 1);
|
|
||||||
|
|
||||||
if (!fileSymbols)
|
if (!fileSymbols)
|
||||||
err("Failed to get memory for %s's symbols", fileName);
|
err("Failed to get memory for %s's symbols", fileName);
|
||||||
|
|||||||
@@ -9,6 +9,7 @@
|
|||||||
#include <assert.h>
|
#include <assert.h>
|
||||||
#include <inttypes.h>
|
#include <inttypes.h>
|
||||||
#include <stdint.h>
|
#include <stdint.h>
|
||||||
|
#include <stdio.h>
|
||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
|
|
||||||
#include "link/output.h"
|
#include "link/output.h"
|
||||||
@@ -199,6 +200,7 @@ static void writeBank(struct SortedSection *bankSections, uint16_t baseOffset,
|
|||||||
while (bankSections) {
|
while (bankSections) {
|
||||||
struct Section const *section = bankSections->section;
|
struct Section const *section = bankSections->section;
|
||||||
|
|
||||||
|
assert(section->offset == 0);
|
||||||
/* Output padding up to the next SECTION */
|
/* Output padding up to the next SECTION */
|
||||||
while (offset + baseOffset < section->org) {
|
while (offset + baseOffset < section->org) {
|
||||||
putc(overlayFile ? getc(overlayFile) : padValue,
|
putc(overlayFile ? getc(overlayFile) : padValue,
|
||||||
@@ -381,6 +383,7 @@ static uint16_t writeMapBank(struct SortedSections const *sectList,
|
|||||||
|
|
||||||
used += sect->size;
|
used += sect->size;
|
||||||
|
|
||||||
|
assert(sect->offset == 0);
|
||||||
if (sect->size != 0)
|
if (sect->size != 0)
|
||||||
fprintf(mapFile, " SECTION: $%04" PRIx16 "-$%04x ($%04" PRIx16
|
fprintf(mapFile, " SECTION: $%04" PRIx16 "-$%04x ($%04" PRIx16
|
||||||
" byte%s) [\"%s\"]\n",
|
" byte%s) [\"%s\"]\n",
|
||||||
@@ -394,6 +397,7 @@ static uint16_t writeMapBank(struct SortedSections const *sectList,
|
|||||||
uint16_t org = sect->org;
|
uint16_t org = sect->org;
|
||||||
|
|
||||||
while (sect) {
|
while (sect) {
|
||||||
|
fprintf(mapFile, " ; New %s\n", sect->modifier == SECTION_FRAGMENT ? "fragment": "union");
|
||||||
for (size_t i = 0; i < sect->nbSymbols; i++)
|
for (size_t i = 0; i < sect->nbSymbols; i++)
|
||||||
fprintf(mapFile, " $%04" PRIx32 " = %s\n",
|
fprintf(mapFile, " $%04" PRIx32 " = %s\n",
|
||||||
sect->symbols[i]->offset + org,
|
sect->symbols[i]->offset + org,
|
||||||
|
|||||||
@@ -354,6 +354,7 @@ static int32_t computeRPNExpr(struct Patch const *patch,
|
|||||||
;
|
;
|
||||||
|
|
||||||
sect = sect_GetSection(name);
|
sect = sect_GetSection(name);
|
||||||
|
assert(sect->offset == 0);
|
||||||
|
|
||||||
if (!sect) {
|
if (!sect) {
|
||||||
error(patch->src, patch->lineNo,
|
error(patch->src, patch->lineNo,
|
||||||
@@ -497,9 +498,6 @@ void patch_CheckAssertions(struct Assertion *assert)
|
|||||||
*/
|
*/
|
||||||
static void applyFilePatches(struct Section *section, struct Section *dataSection)
|
static void applyFilePatches(struct Section *section, struct Section *dataSection)
|
||||||
{
|
{
|
||||||
if (!sect_HasData(section->type))
|
|
||||||
return;
|
|
||||||
|
|
||||||
verbosePrint("Patching section \"%s\"...\n", section->name);
|
verbosePrint("Patching section \"%s\"...\n", section->name);
|
||||||
for (uint32_t patchID = 0; patchID < section->nbPatches; patchID++) {
|
for (uint32_t patchID = 0; patchID < section->nbPatches; patchID++) {
|
||||||
struct Patch *patch = §ion->patches[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) {
|
if (patch->type == PATCHTYPE_JR) {
|
||||||
// Offset is relative to the byte *after* the operand
|
// Offset is relative to the byte *after* the operand
|
||||||
// PC as operand to `jr` is lower than reference PC by 2
|
// PC as operand to `jr` is lower than reference PC by 2
|
||||||
uint16_t address = patch->pcSection->org
|
uint16_t address = patch->pcSection->org + patch->pcOffset + 2;
|
||||||
+ patch->pcOffset + 2;
|
|
||||||
int16_t jumpOffset = value - address;
|
int16_t jumpOffset = value - address;
|
||||||
|
|
||||||
if (!isError && (jumpOffset < -128 || jumpOffset > 127))
|
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)
|
static void applyPatches(struct Section *section, void *arg)
|
||||||
{
|
{
|
||||||
|
if (!sect_HasData(section->type))
|
||||||
|
return;
|
||||||
|
|
||||||
(void)arg;
|
(void)arg;
|
||||||
struct Section *dataSection = section;
|
struct Section *dataSection = section;
|
||||||
|
|
||||||
|
|||||||
@@ -365,8 +365,7 @@ static enum LinkerScriptParserState parserState = PARSER_FIRSTTIME;
|
|||||||
|
|
||||||
struct SectionPlacement *script_NextSection(void)
|
struct SectionPlacement *script_NextSection(void)
|
||||||
{
|
{
|
||||||
static struct SectionPlacement section;
|
static struct SectionPlacement placement;
|
||||||
static enum SectionType type;
|
|
||||||
static uint32_t bank;
|
static uint32_t bank;
|
||||||
static uint32_t bankID;
|
static uint32_t bankID;
|
||||||
|
|
||||||
@@ -380,7 +379,7 @@ struct SectionPlacement *script_NextSection(void)
|
|||||||
curaddr[i][b] = startaddr[i];
|
curaddr[i][b] = startaddr[i];
|
||||||
}
|
}
|
||||||
|
|
||||||
type = SECTTYPE_INVALID;
|
placement.type = SECTTYPE_INVALID;
|
||||||
|
|
||||||
parserState = PARSER_LINESTART;
|
parserState = PARSER_LINESTART;
|
||||||
}
|
}
|
||||||
@@ -392,15 +391,15 @@ struct SectionPlacement *script_NextSection(void)
|
|||||||
bool hasArg;
|
bool hasArg;
|
||||||
uint32_t arg;
|
uint32_t arg;
|
||||||
|
|
||||||
if (type != SECTTYPE_INVALID) {
|
if (placement.type != SECTTYPE_INVALID) {
|
||||||
if (curaddr[type][bankID] > endaddr(type) + 1)
|
if (curaddr[placement.type][bankID] > endaddr(placement.type) + 1)
|
||||||
errx("%s(%" PRIu32 "): Sections would extend past the end of %s ($%04" PRIx16 " > $%04" PRIx16 ")",
|
errx("%s(%" PRIu32 "): Sections would extend past the end of %s ($%04" PRIx16 " > $%04" PRIx16 ")",
|
||||||
linkerScriptName, lineNo, typeNames[type],
|
linkerScriptName, lineNo, typeNames[placement.type],
|
||||||
curaddr[type][bankID], endaddr(type));
|
curaddr[placement.type][bankID], endaddr(placement.type));
|
||||||
if (curaddr[type][bankID] < startaddr[type])
|
if (curaddr[placement.type][bankID] < startaddr[placement.type])
|
||||||
errx("%s(%" PRIu32 "): PC underflowed ($%04" PRIx16 " < $%04" PRIx16 ")",
|
errx("%s(%" PRIu32 "): PC underflowed ($%04" PRIx16 " < $%04" PRIx16 ")",
|
||||||
linkerScriptName, lineNo,
|
linkerScriptName, lineNo,
|
||||||
curaddr[type][bankID], startaddr[type]);
|
curaddr[placement.type][bankID], startaddr[placement.type]);
|
||||||
}
|
}
|
||||||
|
|
||||||
switch (parserState) {
|
switch (parserState) {
|
||||||
@@ -431,21 +430,21 @@ struct SectionPlacement *script_NextSection(void)
|
|||||||
case TOKEN_STRING:
|
case TOKEN_STRING:
|
||||||
parserState = PARSER_LINEEND;
|
parserState = PARSER_LINEEND;
|
||||||
|
|
||||||
if (type == SECTTYPE_INVALID)
|
if (placement.type == SECTTYPE_INVALID)
|
||||||
errx("%s(%" PRIu32 "): Didn't specify a location before the section",
|
errx("%s(%" PRIu32 "): Didn't specify a location before the section",
|
||||||
linkerScriptName, lineNo);
|
linkerScriptName, lineNo);
|
||||||
|
|
||||||
section.section =
|
placement.section =
|
||||||
sect_GetSection(token->attr.string);
|
sect_GetSection(token->attr.string);
|
||||||
if (!section.section)
|
if (!placement.section)
|
||||||
errx("%s(%" PRIu32 "): Unknown section \"%s\"",
|
errx("%s(%" PRIu32 "): Unknown section \"%s\"",
|
||||||
linkerScriptName, lineNo,
|
linkerScriptName, lineNo,
|
||||||
token->attr.string);
|
token->attr.string);
|
||||||
section.org = curaddr[type][bankID];
|
placement.org = curaddr[placement.type][bankID];
|
||||||
section.bank = bank;
|
placement.bank = bank;
|
||||||
|
|
||||||
curaddr[type][bankID] += section.section->size;
|
curaddr[placement.type][bankID] += placement.section->size;
|
||||||
return §ion;
|
return &placement;
|
||||||
|
|
||||||
case TOKEN_COMMAND:
|
case TOKEN_COMMAND:
|
||||||
case TOKEN_BANK:
|
case TOKEN_BANK:
|
||||||
@@ -466,35 +465,35 @@ struct SectionPlacement *script_NextSection(void)
|
|||||||
arg = hasArg ? token->attr.number : 0;
|
arg = hasArg ? token->attr.number : 0;
|
||||||
|
|
||||||
if (tokType == TOKEN_COMMAND) {
|
if (tokType == TOKEN_COMMAND) {
|
||||||
if (type == SECTTYPE_INVALID)
|
if (placement.type == SECTTYPE_INVALID)
|
||||||
errx("%s(%" PRIu32 "): Didn't specify a location before the command",
|
errx("%s(%" PRIu32 "): Didn't specify a location before the command",
|
||||||
linkerScriptName, lineNo);
|
linkerScriptName, lineNo);
|
||||||
if (!hasArg)
|
if (!hasArg)
|
||||||
errx("%s(%" PRIu32 "): Command specified without an argument",
|
errx("%s(%" PRIu32 "): Command specified without an argument",
|
||||||
linkerScriptName, lineNo);
|
linkerScriptName, lineNo);
|
||||||
|
|
||||||
processCommand(attr.command, arg, &curaddr[type][bankID]);
|
processCommand(attr.command, arg, &curaddr[placement.type][bankID]);
|
||||||
} else { /* TOKEN_BANK */
|
} else { /* TOKEN_BANK */
|
||||||
type = attr.secttype;
|
placement.type = attr.secttype;
|
||||||
/*
|
/*
|
||||||
* If there's only one bank,
|
* If there's only one bank,
|
||||||
* specifying the number is optional.
|
* 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",
|
errx("%s(%" PRIu32 "): Didn't specify a bank number",
|
||||||
linkerScriptName, lineNo);
|
linkerScriptName, lineNo);
|
||||||
else if (!hasArg)
|
else if (!hasArg)
|
||||||
arg = bankranges[type][0];
|
arg = bankranges[placement.type][0];
|
||||||
else if (arg < bankranges[type][0])
|
else if (arg < bankranges[placement.type][0])
|
||||||
errx("%s(%" PRIu32 "): specified bank number is too low (%" PRIu32 " < %" PRIu32 ")",
|
errx("%s(%" PRIu32 "): specified bank number is too low (%" PRIu32 " < %" PRIu32 ")",
|
||||||
linkerScriptName, lineNo,
|
linkerScriptName, lineNo,
|
||||||
arg, bankranges[type][0]);
|
arg, bankranges[placement.type][0]);
|
||||||
else if (arg > bankranges[type][1])
|
else if (arg > bankranges[placement.type][1])
|
||||||
errx("%s(%" PRIu32 "): specified bank number is too high (%" PRIu32 " > %" PRIu32 ")",
|
errx("%s(%" PRIu32 "): specified bank number is too high (%" PRIu32 " > %" PRIu32 ")",
|
||||||
linkerScriptName, lineNo,
|
linkerScriptName, lineNo,
|
||||||
arg, bankranges[type][1]);
|
arg, bankranges[placement.type][1]);
|
||||||
bank = arg;
|
bank = arg;
|
||||||
bankID = arg - bankranges[type][0];
|
bankID = arg - bankranges[placement.type][0];
|
||||||
}
|
}
|
||||||
|
|
||||||
/* If we read a token we shouldn't have... */
|
/* If we read a token we shouldn't have... */
|
||||||
|
|||||||
759
src/link/sdas_obj.c
Normal file
759
src/link/sdas_obj.c
Normal file
@@ -0,0 +1,759 @@
|
|||||||
|
|
||||||
|
#include <assert.h>
|
||||||
|
#include <ctype.h>
|
||||||
|
#include <errno.h>
|
||||||
|
#include <inttypes.h>
|
||||||
|
#include <stdint.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
|
#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 + <baseValue>
|
||||||
|
#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_<AREA>`, 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);
|
||||||
|
}
|
||||||
@@ -6,6 +6,7 @@
|
|||||||
* SPDX-License-Identifier: MIT
|
* SPDX-License-Identifier: MIT
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
#include <assert.h>
|
||||||
#include <inttypes.h>
|
#include <inttypes.h>
|
||||||
#include <stdbool.h>
|
#include <stdbool.h>
|
||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
@@ -153,18 +154,33 @@ static void mergeSections(struct Section *target, struct Section *other, enum Se
|
|||||||
|
|
||||||
case SECTION_FRAGMENT:
|
case SECTION_FRAGMENT:
|
||||||
checkFragmentCompat(target, other);
|
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;
|
target->size += other->size;
|
||||||
other->offset = target->size - other->size;
|
// Normally we'd check that `sect_HasData`, but SDCC areas may be `_INVALID` here
|
||||||
if (sect_HasData(target->type)) {
|
// Note that if either fragment has data (= a non-NULL `data` pointer), then it's
|
||||||
/* Ensure we're not allocating 0 bytes */
|
// assumed that both fragments "have data", and thus should either have a non-NULL
|
||||||
target->data = realloc(target->data,
|
// `data` pointer, or a size of 0.
|
||||||
sizeof(*target->data) * target->size + 1);
|
if (other->data) {
|
||||||
if (!target->data)
|
if (target->data) {
|
||||||
errx("Failed to concatenate \"%s\"'s fragments", target->name);
|
/* Ensure we're not allocating 0 bytes */
|
||||||
memcpy(target->data + target->size - other->size, other->data, other->size);
|
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 */
|
/* Adjust patches' PC offsets */
|
||||||
for (uint32_t patchID = 0; patchID < other->nbPatches; patchID++)
|
for (uint32_t patchID = 0; patchID < other->nbPatches; patchID++)
|
||||||
other->patches[patchID].pcOffset += other->offset;
|
other->patches[patchID].pcOffset += other->offset;
|
||||||
|
} else if (target->data) {
|
||||||
|
assert(other->size == 0);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
@@ -208,39 +224,33 @@ void sect_CleanupSections(void)
|
|||||||
hash_EmptyMap(sections);
|
hash_EmptyMap(sections);
|
||||||
}
|
}
|
||||||
|
|
||||||
static bool sanityChecksFailed;
|
|
||||||
|
|
||||||
static void doSanityChecks(struct Section *section, void *ptr)
|
static void doSanityChecks(struct Section *section, void *ptr)
|
||||||
{
|
{
|
||||||
(void)ptr;
|
(void)ptr;
|
||||||
#define fail(...) do { \
|
|
||||||
warnx(__VA_ARGS__); \
|
|
||||||
sanityChecksFailed = true; \
|
|
||||||
} while (0)
|
|
||||||
|
|
||||||
/* Sanity check the section's type */
|
/* Sanity check the section's type */
|
||||||
|
|
||||||
if (section->type < 0 || section->type >= SECTTYPE_INVALID) {
|
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;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (is32kMode && section->type == SECTTYPE_ROMX) {
|
if (is32kMode && section->type == SECTTYPE_ROMX) {
|
||||||
if (section->isBankFixed && section->bank != 1)
|
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);
|
section->name);
|
||||||
else
|
else
|
||||||
section->type = SECTTYPE_ROM0;
|
section->type = SECTTYPE_ROM0;
|
||||||
}
|
}
|
||||||
if (isWRA0Mode && section->type == SECTTYPE_WRAMX) {
|
if (isWRA0Mode && section->type == SECTTYPE_WRAMX) {
|
||||||
if (section->isBankFixed && section->bank != 1)
|
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);
|
section->name);
|
||||||
else
|
else
|
||||||
section->type = SECTTYPE_WRAMX;
|
section->type = SECTTYPE_WRAMX;
|
||||||
}
|
}
|
||||||
if (isDmgMode && section->type == SECTTYPE_VRAM && section->bank == 1)
|
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);
|
section->name);
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@@ -252,20 +262,20 @@ static void doSanityChecks(struct Section *section, void *ptr)
|
|||||||
|
|
||||||
/* Too large an alignment may not be satisfiable */
|
/* Too large an alignment may not be satisfiable */
|
||||||
if (section->isAlignFixed && (section->alignMask & startaddr[section->type]))
|
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);
|
section->name, typeNames[section->type], section->alignMask + 1);
|
||||||
|
|
||||||
uint32_t minbank = bankranges[section->type][0], maxbank = bankranges[section->type][1];
|
uint32_t minbank = bankranges[section->type][0], maxbank = bankranges[section->type][1];
|
||||||
|
|
||||||
if (section->isBankFixed && section->bank < minbank && section->bank > maxbank)
|
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 %" PRIu32
|
||||||
: "Cannot place section \"%s\" in bank %" PRIu32 ", it must be between %" PRIu32 " and %" PRIu32,
|
: "Cannot place section \"%s\" in bank %" PRIu32 ", it must be between %" PRIu32 " and %" PRIu32,
|
||||||
section->name, section->bank, minbank, maxbank);
|
section->name, section->bank, minbank, maxbank);
|
||||||
|
|
||||||
/* Check if section has a chance to be placed */
|
/* Check if section has a chance to be placed */
|
||||||
if (section->size > maxsize[section->type])
|
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]);
|
section->name, section->size, maxsize[section->type]);
|
||||||
|
|
||||||
/* Translate loose constraints to strong ones when they're equivalent */
|
/* 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 */
|
/* It doesn't make sense to have both org and alignment set */
|
||||||
if (section->isAlignFixed) {
|
if (section->isAlignFixed) {
|
||||||
if ((section->org & section->alignMask) != section->alignOfs)
|
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->name);
|
||||||
section->isAlignFixed = false;
|
section->isAlignFixed = false;
|
||||||
}
|
}
|
||||||
@@ -287,12 +297,12 @@ static void doSanityChecks(struct Section *section, void *ptr)
|
|||||||
/* Ensure the target address is valid */
|
/* Ensure the target address is valid */
|
||||||
if (section->org < startaddr[section->type]
|
if (section->org < startaddr[section->type]
|
||||||
|| section->org > endaddr(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,
|
PRIx16 "; %#" PRIx16 "]", section->name, section->org,
|
||||||
startaddr[section->type], endaddr(section->type));
|
startaddr[section->type], endaddr(section->type));
|
||||||
|
|
||||||
if (section->org + section->size > endaddr(section->type) + 1)
|
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,
|
section->name, section->org + section->size,
|
||||||
endaddr(section->type) + 1);
|
endaddr(section->type) + 1);
|
||||||
}
|
}
|
||||||
@@ -303,6 +313,4 @@ static void doSanityChecks(struct Section *section, void *ptr)
|
|||||||
void sect_DoSanityChecks(void)
|
void sect_DoSanityChecks(void)
|
||||||
{
|
{
|
||||||
sect_ForEach(doSanityChecks, NULL);
|
sect_ForEach(doSanityChecks, NULL);
|
||||||
if (sanityChecksFailed)
|
|
||||||
errx("Sanity checks failed");
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,2 +1,2 @@
|
|||||||
warning: Section "rom" is bigger than the max size for that type: 0x8000 > 0x4000
|
error: Section "rom" is bigger than the max size for that type: 0x8000 > 0x4000
|
||||||
error: Sanity checks failed
|
Linking failed with 1 error
|
||||||
|
|||||||
@@ -1,2 +1,2 @@
|
|||||||
warning: v1: VRAM bank 1 can't be used with option -d
|
error: v1: VRAM bank 1 can't be used with option -d
|
||||||
error: Sanity checks failed
|
Linking failed with 1 error
|
||||||
|
|||||||
Reference in New Issue
Block a user