mirror of
https://github.com/gbdev/rgbds.git
synced 2025-11-20 10:12:06 +00:00
Files can now be included with the following syntax:
INCLUDE "path.link"
The maximum include depth is 5.
Fixed linkerscript parser and lexer error messages so that they are more
informative (show file and line of the error).
Man page updated.
Signed-off-by: Antonio Niño Díaz <antonio_nd@outlook.com>
584 lines
14 KiB
C
584 lines
14 KiB
C
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <stdbool.h>
|
|
#include <string.h>
|
|
|
|
#include "extern/err.h"
|
|
#include "link/assign.h"
|
|
#include "link/mylink.h"
|
|
#include "link/main.h"
|
|
#include "link/script.h"
|
|
#include "link/symbol.h"
|
|
|
|
struct sFreeArea {
|
|
SLONG nOrg;
|
|
SLONG nSize;
|
|
struct sFreeArea *pPrev, *pNext;
|
|
};
|
|
|
|
struct sSectionAttributes {
|
|
const char *name;
|
|
SLONG bank;
|
|
SLONG offset; // bank + offset = bank originally stored in a section struct
|
|
SLONG minBank;
|
|
SLONG bankCount;
|
|
};
|
|
|
|
struct sFreeArea *BankFree[MAXBANKS];
|
|
SLONG MaxAvail[MAXBANKS];
|
|
SLONG MaxBankUsed;
|
|
SLONG MaxWBankUsed;
|
|
SLONG MaxSBankUsed;
|
|
SLONG MaxVBankUsed;
|
|
|
|
const enum eSectionType SECT_MIN = SECT_WRAM0;
|
|
const enum eSectionType SECT_MAX = SECT_OAM;
|
|
const struct sSectionAttributes SECT_ATTRIBUTES[] = {
|
|
{"WRAM0", BANK_WRAM0, 0, 0, BANK_COUNT_WRAM0},
|
|
{"VRAM", BANK_VRAM, 0, 0, BANK_COUNT_VRAM},
|
|
{"ROMX", BANK_ROMX, -1, 1, BANK_COUNT_ROMX},
|
|
{"ROM0", BANK_ROM0, 0, 0, BANK_COUNT_ROM0},
|
|
{"HRAM", BANK_HRAM, 0, 0, BANK_COUNT_HRAM},
|
|
{"WRAMX", BANK_WRAMX, 0, 0, BANK_COUNT_WRAMX},
|
|
{"SRAM", BANK_SRAM, 0, 0, BANK_COUNT_SRAM},
|
|
{"OAM", BANK_OAM, 0, 0, BANK_COUNT_OAM}
|
|
};
|
|
|
|
#define DOMAXBANK(x, y) {switch (x) { \
|
|
case SECT_ROMX: DOMAXRBANK(y); break; \
|
|
case SECT_WRAMX: DOMAXWBANK(y); break; \
|
|
case SECT_SRAM: DOMAXSBANK(y); break; \
|
|
case SECT_VRAM: DOMAXVBANK(y); break; \
|
|
default: break; }}
|
|
#define DOMAXRBANK(x) {if( (x)>MaxBankUsed ) MaxBankUsed=(x);}
|
|
#define DOMAXWBANK(x) {if( (x)>MaxWBankUsed ) MaxWBankUsed=(x);}
|
|
#define DOMAXSBANK(x) {if( (x)>MaxSBankUsed ) MaxSBankUsed=(x);}
|
|
#define DOMAXVBANK(x) {if( (x)>MaxVBankUsed ) MaxVBankUsed=(x);}
|
|
|
|
void
|
|
ensureSectionTypeIsValid(enum eSectionType type)
|
|
{
|
|
if (type < SECT_MIN || type > SECT_MAX) {
|
|
errx(1, "(INTERNAL) Invalid section type found.");
|
|
}
|
|
}
|
|
|
|
SLONG
|
|
area_Avail(SLONG bank)
|
|
{
|
|
SLONG r;
|
|
struct sFreeArea *pArea;
|
|
|
|
r = 0;
|
|
pArea = BankFree[bank];
|
|
|
|
while (pArea) {
|
|
r += pArea->nSize;
|
|
pArea = pArea->pNext;
|
|
}
|
|
|
|
return (r);
|
|
}
|
|
|
|
SLONG
|
|
area_doAlloc(struct sFreeArea *pArea, SLONG org, SLONG size)
|
|
{
|
|
if (org >= pArea->nOrg && (org + size) <= (pArea->nOrg + pArea->nSize)) {
|
|
if (org == pArea->nOrg) {
|
|
pArea->nOrg += size;
|
|
pArea->nSize -= size;
|
|
return org;
|
|
} else {
|
|
if ((org + size) == (pArea->nOrg + pArea->nSize)) {
|
|
pArea->nSize -= size;
|
|
return org;
|
|
} else {
|
|
struct sFreeArea *pNewArea;
|
|
|
|
if ((pNewArea = malloc(sizeof(struct sFreeArea))) != NULL) {
|
|
*pNewArea = *pArea;
|
|
pNewArea->pPrev = pArea;
|
|
pArea->pNext = pNewArea;
|
|
pArea->nSize = org - pArea->nOrg;
|
|
pNewArea->nOrg = org + size;
|
|
pNewArea->nSize -= size + pArea->nSize;
|
|
return org;
|
|
|
|
} else {
|
|
err(1, NULL);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return -1;
|
|
}
|
|
|
|
SLONG
|
|
area_AllocAbs(struct sFreeArea ** ppArea, SLONG org, SLONG size)
|
|
{
|
|
struct sFreeArea *pArea;
|
|
|
|
pArea = *ppArea;
|
|
while (pArea) {
|
|
SLONG result = area_doAlloc(pArea, org, size);
|
|
if (result != -1) {
|
|
return result;
|
|
}
|
|
|
|
ppArea = &(pArea->pNext);
|
|
pArea = *ppArea;
|
|
}
|
|
|
|
return -1;
|
|
}
|
|
|
|
SLONG
|
|
area_AllocAbsAnyBank(SLONG org, SLONG size, enum eSectionType type)
|
|
{
|
|
ensureSectionTypeIsValid(type);
|
|
|
|
SLONG startBank = SECT_ATTRIBUTES[type].bank;
|
|
SLONG bankCount = SECT_ATTRIBUTES[type].bankCount;
|
|
|
|
for (int i = 0; i < bankCount; i++) {
|
|
if (area_AllocAbs(&BankFree[startBank + i], org, size) != -1) {
|
|
return startBank + i;
|
|
}
|
|
}
|
|
|
|
return -1;
|
|
}
|
|
|
|
SLONG
|
|
area_Alloc(struct sFreeArea ** ppArea, SLONG size, SLONG alignment) {
|
|
struct sFreeArea *pArea;
|
|
if (alignment < 1) {
|
|
alignment = 1;
|
|
}
|
|
|
|
pArea = *ppArea;
|
|
while (pArea) {
|
|
SLONG org = pArea->nOrg;
|
|
if (org % alignment) {
|
|
org += alignment;
|
|
}
|
|
org -= org % alignment;
|
|
|
|
SLONG result = area_doAlloc(pArea, org, size);
|
|
if (result != -1) {
|
|
return result;
|
|
}
|
|
|
|
ppArea = &(pArea->pNext);
|
|
pArea = *ppArea;
|
|
}
|
|
|
|
return -1;
|
|
}
|
|
|
|
SLONG
|
|
area_AllocAnyBank(SLONG size, SLONG alignment, enum eSectionType type) {
|
|
ensureSectionTypeIsValid(type);
|
|
|
|
SLONG startBank = SECT_ATTRIBUTES[type].bank;
|
|
SLONG bankCount = SECT_ATTRIBUTES[type].bankCount;
|
|
|
|
for (int i = 0; i < bankCount; i++) {
|
|
SLONG org = area_Alloc(&BankFree[startBank + i], size, alignment);
|
|
if (org != -1) {
|
|
return ((startBank + i) << 16) | org;
|
|
}
|
|
}
|
|
|
|
return -1;
|
|
}
|
|
|
|
struct sSection *
|
|
FindLargestSection(enum eSectionType type, bool bankFixed)
|
|
{
|
|
struct sSection *pSection, *r = NULL;
|
|
SLONG nLargest = 0;
|
|
SLONG nLargestAlignment = 0;
|
|
|
|
pSection = pSections;
|
|
while (pSection) {
|
|
if (pSection->oAssigned == 0 && pSection->Type == type && (bankFixed ^ (pSection->nBank == -1))) {
|
|
if (pSection->nAlign > nLargestAlignment || (pSection->nAlign == nLargestAlignment && pSection->nByteSize > nLargest)) {
|
|
nLargest = pSection->nByteSize;
|
|
nLargestAlignment = pSection->nAlign;
|
|
r = pSection;
|
|
}
|
|
}
|
|
pSection = pSection->pNext;
|
|
}
|
|
|
|
return r;
|
|
}
|
|
|
|
int
|
|
IsSectionNameInUse(const char *name)
|
|
{
|
|
struct sSection *pSection;
|
|
|
|
pSection = pSections;
|
|
while (pSection) {
|
|
if (strcmp(pSection->pzName, name) == 0)
|
|
return 1;
|
|
|
|
pSection = pSection->pNext;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
int
|
|
IsSectionSameTypeBankAndFloating(const char *name, enum eSectionType type, int bank)
|
|
{
|
|
struct sSection *pSection;
|
|
|
|
pSection = pSections;
|
|
while (pSection) {
|
|
if (pSection->oAssigned == 0) {
|
|
if (strcmp(pSection->pzName, name) == 0) {
|
|
/* Section must be floating in source */
|
|
if (pSection->nOrg != -1 || pSection->nAlign != 1)
|
|
return 0;
|
|
/* It must have the same type in source and linkerscript */
|
|
if (pSection->Type != type)
|
|
return 0;
|
|
/* Bank number must be unassigned in source or equal */
|
|
if (pSection->nBank != -1 && pSection->nBank != bank)
|
|
return 0;
|
|
return 1;
|
|
}
|
|
}
|
|
pSection = pSection->pNext;
|
|
}
|
|
|
|
errx(1, "Section \"%s\" not found (or already used).\n", name);
|
|
}
|
|
|
|
unsigned int
|
|
AssignSectionAddressAndBankByName(const char *name, unsigned int address, int bank)
|
|
{
|
|
struct sSection *pSection;
|
|
|
|
pSection = pSections;
|
|
while (pSection) {
|
|
if (pSection->oAssigned == 0) {
|
|
if (strcmp(pSection->pzName, name) == 0) {
|
|
if (pSection->nOrg != -1 || pSection->nAlign != 1)
|
|
errx(1, "Section \"%s\" from linkerscript isn't floating.\n", name);
|
|
if (pSection->nBank != -1 && pSection->nBank != bank)
|
|
errx(1, "Section \"%s\" from linkerscript has different bank number than in the source.\n", name);
|
|
pSection->nOrg = address;
|
|
pSection->nBank = bank;
|
|
pSection->nAlign = -1;
|
|
return pSection->nByteSize;
|
|
}
|
|
}
|
|
pSection = pSection->pNext;
|
|
}
|
|
|
|
errx(1, "Section \"%s\" not found (or already used).\n", name);
|
|
}
|
|
|
|
bool
|
|
VerifyAndSetBank(struct sSection *pSection)
|
|
{
|
|
ensureSectionTypeIsValid(pSection->Type);
|
|
|
|
if (pSection->nBank >= SECT_ATTRIBUTES[pSection->Type].minBank
|
|
&& pSection->nBank < SECT_ATTRIBUTES[pSection->Type].minBank + SECT_ATTRIBUTES[pSection->Type].bankCount) {
|
|
pSection->nBank += SECT_ATTRIBUTES[pSection->Type].bank + SECT_ATTRIBUTES[pSection->Type].offset;
|
|
return true;
|
|
|
|
} else {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
void
|
|
AssignFixedBankSections(enum eSectionType type)
|
|
{
|
|
ensureSectionTypeIsValid(type);
|
|
|
|
struct sSection *pSection;
|
|
|
|
while ((pSection = FindLargestSection(type, true))) {
|
|
if (VerifyAndSetBank(pSection) &&
|
|
(pSection->nOrg = area_Alloc(&BankFree[pSection->nBank], pSection->nByteSize, pSection->nAlign)) != -1) {
|
|
pSection->oAssigned = 1;
|
|
DOMAXBANK(pSection->Type, pSection->nBank);
|
|
} else {
|
|
if (pSection->nAlign <= 1) {
|
|
errx(1, "Unable to place '%s' (%s section) in bank $%02lX",
|
|
pSection->pzName, SECT_ATTRIBUTES[pSection->Type].name, pSection->nBank);
|
|
} else {
|
|
errx(1, "Unable to place '%s' (%s section) in bank $%02lX (with $%lX-byte alignment)",
|
|
pSection->pzName, SECT_ATTRIBUTES[pSection->Type].name, pSection->nBank, pSection->nAlign);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void
|
|
AssignFloatingBankSections(enum eSectionType type)
|
|
{
|
|
ensureSectionTypeIsValid(type);
|
|
|
|
struct sSection *pSection;
|
|
|
|
while ((pSection = FindLargestSection(type, false))) {
|
|
SLONG org;
|
|
|
|
if ((org = area_AllocAnyBank(pSection->nByteSize, pSection->nAlign, type)) != -1) {
|
|
if (options & OPT_OVERLAY) {
|
|
errx(1, "All sections must be fixed when using overlay");
|
|
}
|
|
pSection->nOrg = org & 0xFFFF;
|
|
pSection->nBank = org >> 16;
|
|
pSection->oAssigned = 1;
|
|
DOMAXBANK(pSection->Type, pSection->nBank);
|
|
} else {
|
|
const char *locality = "anywhere";
|
|
if (SECT_ATTRIBUTES[pSection->Type].bankCount > 1) {
|
|
locality = "in any bank";
|
|
}
|
|
|
|
if (pSection->nAlign <= 1) {
|
|
errx(1, "Unable to place '%s' (%s section) %s",
|
|
pSection->pzName, SECT_ATTRIBUTES[type].name, locality);
|
|
} else {
|
|
errx(1, "Unable to place '%s' (%s section) %s (with $%lX-byte alignment)",
|
|
pSection->pzName, SECT_ATTRIBUTES[type].name, locality, pSection->nAlign);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
char *tzLinkerscriptName = NULL;
|
|
|
|
void
|
|
SetLinkerscriptName(char *tzLinkerscriptFile)
|
|
{
|
|
tzLinkerscriptName = tzLinkerscriptFile;
|
|
}
|
|
|
|
void
|
|
AssignSections(void)
|
|
{
|
|
SLONG i;
|
|
struct sSection *pSection;
|
|
|
|
MaxBankUsed = 0;
|
|
|
|
/*
|
|
* Initialize the memory areas
|
|
*
|
|
*/
|
|
|
|
for (i = 0; i < MAXBANKS; i += 1) {
|
|
BankFree[i] = malloc(sizeof *BankFree[i]);
|
|
|
|
if (!BankFree[i]) {
|
|
err(1, NULL);
|
|
}
|
|
|
|
if (i == BANK_ROM0) {
|
|
/* ROM0 bank */
|
|
BankFree[i]->nOrg = 0x0000;
|
|
if (options & OPT_TINY) {
|
|
BankFree[i]->nSize = 0x8000;
|
|
} else {
|
|
BankFree[i]->nSize = 0x4000;
|
|
}
|
|
} else if (i >= BANK_ROMX && i < BANK_ROMX + BANK_COUNT_ROMX) {
|
|
/* Swappable ROM bank */
|
|
BankFree[i]->nOrg = 0x4000;
|
|
BankFree[i]->nSize = 0x4000;
|
|
} else if (i == BANK_WRAM0) {
|
|
/* WRAM */
|
|
BankFree[i]->nOrg = 0xC000;
|
|
if (options & OPT_DMG_MODE) {
|
|
BankFree[i]->nSize = 0x2000;
|
|
} else {
|
|
BankFree[i]->nSize = 0x1000;
|
|
}
|
|
} else if (i >= BANK_SRAM && i < BANK_SRAM + BANK_COUNT_SRAM) {
|
|
/* Swappable SRAM bank */
|
|
BankFree[i]->nOrg = 0xA000;
|
|
BankFree[i]->nSize = 0x2000;
|
|
} else if (i >= BANK_WRAMX && i < BANK_WRAMX + BANK_COUNT_WRAMX) {
|
|
/* Swappable WRAM bank */
|
|
BankFree[i]->nOrg = 0xD000;
|
|
BankFree[i]->nSize = 0x1000;
|
|
} else if (i >= BANK_VRAM && i < BANK_VRAM + BANK_COUNT_VRAM) {
|
|
/* Swappable VRAM bank */
|
|
BankFree[i]->nOrg = 0x8000;
|
|
if (options & OPT_DMG_MODE && i != BANK_VRAM) {
|
|
BankFree[i]->nSize = 0;
|
|
} else {
|
|
BankFree[i]->nSize = 0x2000;
|
|
}
|
|
} else if (i == BANK_OAM) {
|
|
BankFree[i]->nOrg = 0xFE00;
|
|
BankFree[i]->nSize = 0x00A0;
|
|
} else if (i == BANK_HRAM) {
|
|
/* HRAM */
|
|
BankFree[i]->nOrg = 0xFF80;
|
|
BankFree[i]->nSize = 0x007F;
|
|
} else {
|
|
errx(1, "(INTERNAL) Unknown bank type!");
|
|
}
|
|
|
|
MaxAvail[i] = BankFree[i]->nSize;
|
|
BankFree[i]->pPrev = NULL;
|
|
BankFree[i]->pNext = NULL;
|
|
}
|
|
|
|
/*
|
|
* First, let's parse the linkerscript.
|
|
*
|
|
*/
|
|
|
|
if (tzLinkerscriptName) {
|
|
script_InitSections();
|
|
script_Parse(tzLinkerscriptName);
|
|
}
|
|
|
|
/*
|
|
* Second, let's assign all the fixed sections...
|
|
*
|
|
*/
|
|
|
|
pSection = pSections;
|
|
while (pSection) {
|
|
if ((pSection->nOrg != -1 || pSection->nBank != -1)
|
|
&& pSection->oAssigned == 0) {
|
|
/* User wants to have a say... */
|
|
|
|
switch (pSection->Type) {
|
|
case SECT_WRAM0:
|
|
case SECT_HRAM:
|
|
case SECT_ROM0:
|
|
case SECT_OAM:
|
|
pSection->nBank = SECT_ATTRIBUTES[pSection->Type].bank;
|
|
if (area_AllocAbs(&BankFree[pSection->nBank], pSection->nOrg,
|
|
pSection->nByteSize) == -1) {
|
|
errx(1, "Unable to place '%s' (%s section) at $%lX",
|
|
pSection->pzName, SECT_ATTRIBUTES[pSection->Type].name, pSection->nOrg);
|
|
}
|
|
pSection->oAssigned = 1;
|
|
break;
|
|
|
|
case SECT_SRAM:
|
|
case SECT_WRAMX:
|
|
case SECT_VRAM:
|
|
case SECT_ROMX:
|
|
if (pSection->nBank != -1 && pSection->nOrg != -1) {
|
|
if (VerifyAndSetBank(pSection) &&
|
|
area_AllocAbs(&BankFree[pSection->nBank], pSection->nOrg, pSection->nByteSize) != -1) {
|
|
DOMAXBANK(pSection->Type, pSection->nBank);
|
|
pSection->oAssigned = 1;
|
|
} else {
|
|
errx(1, "Unable to place '%s' (%s section) at $%lX in bank $%02lX",
|
|
pSection->pzName, SECT_ATTRIBUTES[pSection->Type].name, pSection->nOrg, pSection->nBank);
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
pSection = pSection->pNext;
|
|
}
|
|
|
|
/*
|
|
* Next, let's assign all the bankfixed ONLY sections...
|
|
*
|
|
*/
|
|
for (enum eSectionType i = SECT_MIN; i <= SECT_MAX; i++) {
|
|
AssignFixedBankSections(i);
|
|
}
|
|
|
|
/*
|
|
* Now, let's assign all the floating bank but fixed ROMX sections...
|
|
*
|
|
*/
|
|
|
|
pSection = pSections;
|
|
while (pSection) {
|
|
if (pSection->oAssigned == 0
|
|
&& pSection->nOrg != -1 && pSection->nBank == -1) {
|
|
if (options & OPT_OVERLAY) {
|
|
errx(1, "All sections must be fixed when using overlay");
|
|
}
|
|
switch (pSection->Type) {
|
|
case SECT_ROMX:
|
|
case SECT_VRAM:
|
|
case SECT_SRAM:
|
|
case SECT_WRAMX:
|
|
if ((pSection->nBank =
|
|
area_AllocAbsAnyBank(pSection->nOrg, pSection->nByteSize,
|
|
pSection->Type)) == -1) {
|
|
errx(1, "Unable to place '%s' (%s section) at $%lX in any bank",
|
|
pSection->pzName, SECT_ATTRIBUTES[pSection->Type].name, pSection->nOrg);
|
|
}
|
|
pSection->oAssigned = 1;
|
|
DOMAXBANK(pSection->Type, pSection->nBank);
|
|
break;
|
|
|
|
default: // Handle other sections later
|
|
break;
|
|
}
|
|
}
|
|
|
|
pSection = pSection->pNext;
|
|
}
|
|
|
|
/*
|
|
* OK, all that nasty stuff is done so let's assign all the other
|
|
* sections
|
|
*
|
|
*/
|
|
for (enum eSectionType i = SECT_MIN; i <= SECT_MAX; i++) {
|
|
AssignFloatingBankSections(i);
|
|
}
|
|
}
|
|
|
|
void
|
|
CreateSymbolTable(void)
|
|
{
|
|
struct sSection *pSect;
|
|
|
|
sym_Init();
|
|
|
|
pSect = pSections;
|
|
|
|
while (pSect) {
|
|
SLONG i;
|
|
|
|
i = pSect->nNumberOfSymbols;
|
|
|
|
while (i--) {
|
|
if ((pSect->tSymbols[i]->Type == SYM_EXPORT) &&
|
|
((pSect->tSymbols[i]->pSection == pSect) ||
|
|
(pSect->tSymbols[i]->pSection == NULL))) {
|
|
if (pSect->tSymbols[i]->pSection == NULL)
|
|
sym_CreateSymbol(pSect->tSymbols[i]->
|
|
pzName,
|
|
pSect->tSymbols[i]->
|
|
nOffset, -1);
|
|
else
|
|
sym_CreateSymbol(pSect->tSymbols[i]->
|
|
pzName,
|
|
pSect->nOrg +
|
|
pSect->tSymbols[i]->
|
|
nOffset, pSect->nBank);
|
|
}
|
|
}
|
|
pSect = pSect->pNext;
|
|
}
|
|
}
|