Merge pull request #434 from ISSOtm/rgblink_rewrite

Rewrite RGBLINK entirely
This commit is contained in:
Eldred Habert
2019-11-03 16:15:37 +01:00
committed by GitHub
41 changed files with 2753 additions and 2623 deletions

View File

@@ -32,10 +32,11 @@ VERSION_STRING := `git describe --tags --dirty --always 2>/dev/null`
WARNFLAGS := -Wall
# Overridable CFLAGS
CFLAGS := -g
CFLAGS := -g -O0
# Non-overridable CFLAGS
REALCFLAGS := ${CFLAGS} ${WARNFLAGS} -std=c99 -D_POSIX_C_SOURCE=200809L \
-Iinclude -DBUILD_VERSION_STRING=\"${VERSION_STRING}\"
REALCFLAGS := ${CFLAGS} ${WARNFLAGS} -std=c11 -D_POSIX_C_SOURCE=200809L \
-D_DEFAULT_SOURCE -Iinclude \
-DBUILD_VERSION_STRING=\"${VERSION_STRING}\"
# Overridable LDFLAGS
LDFLAGS :=
# Non-overridable LDFLAGS
@@ -73,21 +74,17 @@ src/asm/globlex.o src/asm/lexer.o src/asm/constexpr.o: src/asm/asmy.h
rgblink_obj := \
src/link/assign.o \
src/link/lexer.o \
src/link/library.o \
src/link/main.o \
src/link/mapfile.o \
src/link/object.o \
src/link/output.o \
src/link/patch.o \
src/link/parser.o \
src/link/script.o \
src/link/section.o \
src/link/symbol.o \
src/extern/err.o \
src/hashmap.o \
src/version.o
src/link/lexer.o: src/link/parser.h
rgbfix_obj := \
src/fix/main.o \
src/extern/err.o \

View File

@@ -9,7 +9,8 @@
#ifndef RGBDS_COMMON_H
#define RGBDS_COMMON_H
#define RGBDS_OBJECT_VERSION_STRING "RGB6"
#define RGBDS_OBJECT_VERSION_STRING "RGB%1hhu"
#define RGBDS_OBJECT_VERSION_NUMBER (uint8_t)6
enum eBankCount {
BANK_COUNT_ROM0 = 1,

68
include/hashmap.h Normal file
View File

@@ -0,0 +1,68 @@
/*
* This file is part of RGBDS.
*
* Copyright (c) 1997-2019, Carsten Sorensen and RGBDS contributors.
*
* SPDX-License-Identifier: MIT
*/
/* Generic hashmap implementation (C++ templates are calling...) */
#ifndef RGBDS_LINK_HASHMAP_H
#define RGBDS_LINK_HASHMAP_H
#include <assert.h>
#define HASH_NB_BITS 32
#define HALF_HASH_NB_BITS 16
_Static_assert(HALF_HASH_NB_BITS * 2 == HASH_NB_BITS, "");
#define HASHMAP_NB_BUCKETS (1 << HALF_HASH_NB_BITS)
/* HashMapEntry is internal, please do not attempt to use it */
typedef struct HashMapEntry *HashMap[HASHMAP_NB_BUCKETS];
/**
* Adds an element to a hashmap.
* @warning Adding a new element with an already-present key will not cause an
* error, this must be handled externally.
* @warning Inserting a NULL will make `hash_GetElement`'s return ambiguous!
* @param map The HashMap to add the element to
* @param key The key with which the element will be stored and retrieved
* @param element The element to add
* @return True if a collision occurred (for statistics)
*/
bool hash_AddElement(HashMap map, char const *key, void *element);
/**
* Removes an element from a hashmap.
* @param map The HashMap to remove the element from
* @param key The key to search the element with
* @return True if the element was found and removed
*/
bool hash_RemoveElement(HashMap map, char const *key);
/**
* Finds an element in a hashmap.
* @param map The map to consider the elements of
* @param key The key to search an element for
* @return A pointer to the element, or NULL if not found. (NULL can be returned
* if such an element was added, but that sounds pretty silly.)
*/
void *hash_GetElement(HashMap const map, char const *key);
/**
* Executes a function on each element in a hashmap.
* @param map The map to consider the elements of
* @param func The function to run. The first argument will be the element,
* the second will be `arg`.
* @param arg An argument to be passed to all function calls
*/
void hash_ForEach(HashMap const map, void (*func)(void *, void *), void *arg);
/**
* Cleanly empties a hashmap from its contents.
* This does not `free` the data structure itself!
* @param map The map to empty
*/
void hash_EmptyMap(HashMap map);
#endif /* RGBDS_LINK_HASHMAP_H */

View File

@@ -13,10 +13,12 @@
/* GCC or compatible */
#define noreturn_ __attribute__ ((noreturn))
#define unused_ __attribute__ ((unused))
#define trap_ __builtin_trap()
#else
/* Unsupported, but no need to throw a fit */
#define noreturn_
#define unused_
#define trap_
#endif
#endif /* HELPERS_H */

View File

@@ -1,54 +1,29 @@
/*
* This file is part of RGBDS.
*
* Copyright (c) 1997-2018, Carsten Sorensen and RGBDS contributors.
* Copyright (c) 1997-2019, Carsten Sorensen and RGBDS contributors.
*
* SPDX-License-Identifier: MIT
*/
/* Assigning all sections a place */
#ifndef RGBDS_LINK_ASSIGN_H
#define RGBDS_LINK_ASSIGN_H
#include <stdint.h>
#include "common.h"
#include "mylink.h"
/* Bank numbers as seen by the linker */
enum eBankDefine {
BANK_INDEX_ROM0 = 0,
BANK_INDEX_ROMX = BANK_INDEX_ROM0 + BANK_COUNT_ROM0,
BANK_INDEX_WRAM0 = BANK_INDEX_ROMX + BANK_COUNT_ROMX,
BANK_INDEX_WRAMX = BANK_INDEX_WRAM0 + BANK_COUNT_WRAM0,
BANK_INDEX_VRAM = BANK_INDEX_WRAMX + BANK_COUNT_WRAMX,
BANK_INDEX_OAM = BANK_INDEX_VRAM + BANK_COUNT_VRAM,
BANK_INDEX_HRAM = BANK_INDEX_OAM + BANK_COUNT_OAM,
BANK_INDEX_SRAM = BANK_INDEX_HRAM + BANK_COUNT_HRAM,
BANK_INDEX_MAX = BANK_INDEX_SRAM + BANK_COUNT_SRAM
};
extern uint64_t nbSectionsToAssign;
extern int32_t MaxBankUsed;
extern int32_t MaxAvail[BANK_INDEX_MAX];
/**
* Assigns all sections a slice of the address space
*/
void assign_AssignSections(void);
int32_t area_Avail(int32_t bank);
void AssignSections(void);
void CreateSymbolTable(void);
struct sSection *GetSectionByName(const char *name);
int32_t IsSectionNameInUse(const char *name);
void SetLinkerscriptName(char *tzLinkerscriptFile);
int32_t IsSectionSameTypeBankAndAttrs(const char *name,
enum eSectionType type, int32_t bank,
int32_t org, int32_t align);
uint32_t AssignSectionAddressAndBankByName(const char *name, uint32_t address,
int32_t bank);
int BankIndexIsROM0(int32_t bank);
int BankIndexIsROMX(int32_t bank);
int BankIndexIsWRAM0(int32_t bank);
int BankIndexIsWRAMX(int32_t bank);
int BankIndexIsVRAM(int32_t bank);
int BankIndexIsOAM(int32_t bank);
int BankIndexIsHRAM(int32_t bank);
int BankIndexIsSRAM(int32_t bank);
/**
* `free`s all assignment memory that was allocated.
*/
void assign_Cleanup(void);
#endif /* RGBDS_LINK_ASSIGN_H */

View File

@@ -1,14 +0,0 @@
/*
* This file is part of RGBDS.
*
* Copyright (c) 1997-2018, Carsten Sorensen and RGBDS contributors.
*
* SPDX-License-Identifier: MIT
*/
#ifndef RGBDS_LINK_LIBRARY_H
#define RGBDS_LINK_LIBRARY_H
void AddNeededModules(void);
#endif /* RGBDS_LINK_LIBRARY_H */

View File

@@ -1,17 +1,49 @@
/*
* This file is part of RGBDS.
*
* Copyright (c) 1997-2018, Carsten Sorensen and RGBDS contributors.
* Copyright (c) 1997-2019, Carsten Sorensen and RGBDS contributors.
*
* SPDX-License-Identifier: MIT
*/
/* Declarations that all modules use, as well as `main` and related */
#ifndef RGBDS_LINK_MAIN_H
#define RGBDS_LINK_MAIN_H
#include <stdint.h>
#include <stdbool.h>
#include <stdio.h>
extern int32_t fillchar;
extern char *smartlinkstartsymbol;
/* Variables related to CLI options */
extern bool isDmgMode;
extern char const *linkerScriptName;
extern char const *mapFileName;
extern char const *symFileName;
extern char const *overlayFileName;
extern char const *outputFileName;
extern uint8_t padValue;
extern bool is32kMode;
extern bool beVerbose;
extern bool isWRA0Mode;
/* Helper macro for printing verbose-mode messages */
#define verbosePrint(...) do { \
if (beVerbose) \
fprintf(stderr, __VA_ARGS__); \
} while (0)
/**
* Opens a file if specified, and aborts on error.
* @param fileName The name of the file to open; if NULL, no file will be opened
* @param mode The mode to open the file with
* @return A pointer to a valid FILE structure, or NULL if fileName was NULL
*/
FILE *openFile(char const *fileName, char const *mode);
#define closeFile(file) do { \
FILE *tmp = file; \
if (tmp) \
fclose(tmp); \
} while (0)
#endif /* RGBDS_LINK_MAIN_H */

View File

@@ -1,21 +0,0 @@
/*
* This file is part of RGBDS.
*
* Copyright (c) 1997-2018, Carsten Sorensen and RGBDS contributors.
*
* SPDX-License-Identifier: MIT
*/
#ifndef RGBDS_LINK_MAPFILE_H
#define RGBDS_LINK_MAPFILE_H
#include <stdint.h>
void SetMapfileName(char *name);
void SetSymfileName(char *name);
void CloseMapfile(void);
void MapfileWriteSection(const struct sSection *pSect);
void MapfileInitBank(int32_t bank);
void MapfileCloseBank(int32_t slack);
#endif /* RGBDS_LINK_MAPFILE_H */

View File

@@ -1,68 +0,0 @@
/*
* This file is part of RGBDS.
*
* Copyright (c) 1997-2018, Carsten Sorensen and RGBDS contributors.
*
* SPDX-License-Identifier: MIT
*/
#ifndef RGBDS_LINK_LINK_H
#define RGBDS_LINK_LINK_H
#include <stdint.h>
#include "linkdefs.h"
extern int32_t options;
#define OPT_TINY 0x01
#define OPT_SMART_C_LINK 0x02
#define OPT_OVERLAY 0x04
#define OPT_CONTWRAM 0x08
#define OPT_DMG_MODE 0x10
struct sSection {
int32_t nBank;
int32_t nOrg;
int32_t nAlign;
uint8_t oAssigned;
char *pzName;
int32_t nByteSize;
enum eSectionType Type;
uint8_t *pData;
int32_t nNumberOfSymbols;
struct sSymbol **tSymbols;
struct sPatch *pPatches;
struct sSection *pNext;
};
struct sSymbol {
char *pzName;
enum eSymbolType Type;
/* The following 3 items only valid when Type!=SYM_IMPORT */
int32_t nSectionID; /* Internal to object.c */
struct sSection *pSection;
int32_t nOffset;
char *pzObjFileName; /* Object file where the symbol is located. */
char *pzFileName; /* Source file where the symbol was defined. */
uint32_t nFileLine; /* Line where the symbol was defined. */
};
struct sPatch {
char *pzFilename;
int32_t nLineNo;
int32_t nOffset;
enum ePatchType Type;
int32_t nRPNSize;
uint8_t *pRPN;
struct sPatch *pNext;
uint8_t oRelocPatch;
};
extern struct sSection *pSections;
extern struct sSection *pLibSections;
#endif /* RGBDS_LINK_LINK_H */

View File

@@ -1,14 +1,30 @@
/*
* This file is part of RGBDS.
*
* Copyright (c) 1997-2018, Carsten Sorensen and RGBDS contributors.
* Copyright (c) 1997-2019, Carsten Sorensen and RGBDS contributors.
*
* SPDX-License-Identifier: MIT
*/
/* Declarations related to processing of object (.o) files */
#ifndef RGBDS_LINK_OBJECT_H
#define RGBDS_LINK_OBJECT_H
void obj_Readfile(char *tzObjectfile);
/**
* Read an object (.o) file, and add its info to the data structures.
* @param fileName A path to the object file to be read
*/
void obj_ReadFile(char const *fileName);
/**
* Perform validation on the object files' contents
*/
void obj_DoSanityChecks(void);
/**
* `free`s all object memory that was allocated.
*/
void obj_Cleanup(void);
#endif /* RGBDS_LINK_OBJECT_H */

View File

@@ -1,16 +1,28 @@
/*
* This file is part of RGBDS.
*
* Copyright (c) 1997-2018, Carsten Sorensen and RGBDS contributors.
* Copyright (c) 1997-2019, Carsten Sorensen and RGBDS contributors.
*
* SPDX-License-Identifier: MIT
*/
/* Outputting the result of linking */
#ifndef RGBDS_LINK_OUTPUT_H
#define RGBDS_LINK_OUTPUT_H
void out_Setname(char *tzOutputfile);
void out_SetOverlayname(char *tzOverlayfile);
void Output(void);
#include <stdint.h>
#include "link/section.h"
/**
* Registers a section for output.
* @param section The section to add
*/
void out_AddSection(struct Section const *section);
/**
* Writes all output (bin, sym, map) files.
*/
void out_WriteFiles(void);
#endif /* RGBDS_LINK_OUTPUT_H */

View File

@@ -1,17 +1,18 @@
/*
* This file is part of RGBDS.
*
* Copyright (c) 1997-2018, Carsten Sorensen and RGBDS contributors.
* Copyright (c) 1997-2019, Carsten Sorensen and RGBDS contributors.
*
* SPDX-License-Identifier: MIT
*/
/* Applying patches to SECTIONs */
#ifndef RGBDS_LINK_PATCH_H
#define RGBDS_LINK_PATCH_H
#include <stdint.h>
void Patch(void);
extern int32_t nPC;
/**
* Applies all SECTIONs' patches to them
*/
void patch_ApplyPatches(void);
#endif /* RGBDS_LINK_PATCH_H */

View File

@@ -1,30 +1,36 @@
/*
* This file is part of RGBDS.
*
* Copyright (c) 2017-2018, Antonio Nino Diaz and RGBDS contributors.
* Copyright (c) 1997-2019, Carsten Sorensen and RGBDS contributors.
*
* SPDX-License-Identifier: MIT
*/
/* Parsing a linker script */
#ifndef RGBDS_LINK_SCRIPT_H
#define RGBDS_LINK_SCRIPT_H
#include <stdint.h>
#include "helpers.h"
extern FILE *linkerScript;
noreturn_ void script_fatalerror(const char *fmt, ...);
struct SectionPlacement {
struct Section *section;
uint16_t org;
uint32_t bank;
};
void script_Parse(const char *path);
extern uint64_t script_lineNo;
void script_IncludeFile(const char *path);
int32_t script_IncludeDepthGet(void);
void script_IncludePop(void);
/**
* Parses the linker script to return the next section constraint
* @return A pointer to a struct, or NULL on EOF. The pointer shouldn't be freed
*/
struct SectionPlacement *script_NextSection(void);
void script_InitSections(void);
void script_SetCurrentSectionType(const char *type, uint32_t bank);
void script_SetAddress(uint32_t addr);
void script_SetAlignment(uint32_t alignment);
void script_OutputSection(const char *section_name);
/**
* `free`s all assignment memory that was allocated.
*/
void script_Cleanup(void);
#endif /* RGBDS_LINK_SCRIPT_H */

112
include/link/section.h Normal file
View File

@@ -0,0 +1,112 @@
/*
* This file is part of RGBDS.
*
* Copyright (c) 1997-2019, Carsten Sorensen and RGBDS contributors.
*
* SPDX-License-Identifier: MIT
*/
/* Declarations manipulating symbols */
#ifndef RGBDS_LINK_SECTION_H
#define RGBDS_LINK_SECTION_H
/* GUIDELINE: external code MUST NOT BE AWARE of the data structure used!! */
#include <stdint.h>
#include <stdbool.h>
#include "link/main.h"
#include "linkdefs.h"
struct AttachedSymbol {
struct Symbol *symbol;
struct AttachedSymbol *next;
};
struct Patch {
char *fileName;
int32_t lineNo;
int32_t offset;
enum PatchType type;
int32_t rpnSize;
uint8_t *rpnExpression;
};
struct Section {
/* Info contained in the object files */
char *name;
uint16_t size;
enum SectionType type;
bool isAddressFixed;
uint16_t org;
bool isBankFixed;
uint32_t bank;
bool isAlignFixed;
uint16_t alignMask;
uint8_t *data; /* Array of size `size`*/
uint32_t nbPatches;
struct Patch *patches;
/* Extra info computed during linking */
struct Symbol **fileSymbols;
uint32_t nbSymbols;
struct Symbol const **symbols;
};
extern uint16_t startaddr[];
extern uint16_t maxsize[];
extern uint32_t bankranges[][2];
extern char const * const typeNames[SECTTYPE_INVALID];
/**
* Computes a memory region's end address (last byte), eg. 0x7FFF
* @return The address of the last byte in that memory region
*/
static inline uint16_t endaddr(enum SectionType type)
{
return startaddr[type] + maxsize[type] - 1;
}
/**
* Computes a memory region's number of banks
* @return The number of banks, 1 for regions without banking
*/
static inline uint32_t nbbanks(enum SectionType type)
{
return bankranges[type][1] - bankranges[type][0] + 1;
}
/*
* Execute a callback for each section currently registered.
* This is to avoid exposing the data structure in which sections are stored.
* @param callback The function to call for each structure;
* the first argument will be a pointer to the structure,
* the second argument will be the pointer `arg`.
* @param arg A pointer which will be passed to all calls to `callback`.
*/
void sect_ForEach(void (*callback)(struct Section *, void *), void *arg);
/**
* Registers a section to be processed.
* @param section The section to register.
*/
void sect_AddSection(struct Section *section);
/**
* Finds a section by its name.
* @param name The name of the section to look for
* @return A pointer to the section, or NULL if it wasn't found
*/
struct Section *sect_GetSection(char const *name);
/**
* `free`s all section memory that was allocated.
*/
void sect_CleanupSections(void);
/**
* Checks if all sections meet reasonable criteria, such as max size
*/
void sect_DoSanityChecks(void);
#endif /* RGBDS_LINK_SECTION_H */

View File

@@ -1,21 +1,59 @@
/*
* This file is part of RGBDS.
*
* Copyright (c) 1997-2018, Carsten Sorensen and RGBDS contributors.
* Copyright (c) 1997-2019, Carsten Sorensen and RGBDS contributors.
*
* SPDX-License-Identifier: MIT
*/
/* Declarations manipulating symbols */
#ifndef RGBDS_LINK_SYMBOL_H
#define RGBDS_LINK_SYMBOL_H
/* GUIDELINE: external code MUST NOT BE AWARE of the data structure used!! */
#include <stdint.h>
void sym_Init(void);
void sym_CreateSymbol(char *tzName, int32_t nValue, int32_t nBank,
char *tzObjFileName, char *tzFileName,
uint32_t nFileLine);
int32_t sym_GetValue(struct sPatch *pPatch, char *tzName);
int32_t sym_GetBank(struct sPatch *pPatch, char *tzName);
#include "linkdefs.h"
struct Symbol {
/* Info contained in the object files */
char *name;
enum SymbolType type;
char const *objFileName;
char *fileName;
int32_t lineNo;
int32_t sectionID;
union {
int32_t offset;
int32_t value;
};
/* Extra info computed during linking */
struct Section *section;
};
/*
* Execute a callback for each symbol currently registered.
* This is done to avoid exposing the data structure in which symbol are stored.
* @param callback The function to call for each symbol;
* the first argument will be a pointer to the symbol,
* the second argument will be the pointer `arg`.
* @param arg A pointer which will be passed to all calls to `callback`.
*/
void sym_ForEach(void (*callback)(struct Symbol *, void *), void *arg);
void sym_AddSymbol(struct Symbol *symbol);
/**
* Finds a symbol in all the defined symbols.
* @param name The name of the symbol to look for
* @return A pointer to the symbol, or NULL if not found.
*/
struct Symbol *sym_GetSymbol(char const *name);
/**
* `free`s all symbol memory that was allocated.
*/
void sym_CleanupSymbols(void);
#endif /* RGBDS_LINK_SYMBOL_H */

View File

@@ -9,7 +9,7 @@
#ifndef RGBDS_LINKDEFS_H
#define RGBDS_LINKDEFS_H
enum eRpnData {
enum RPNCommand {
RPN_ADD = 0x00,
RPN_SUB = 0x01,
RPN_MUL = 0x02,
@@ -46,28 +46,43 @@ enum eRpnData {
RPN_SYM = 0x81
};
enum eSectionType {
SECT_WRAM0 = 0x00,
SECT_VRAM = 0x01,
SECT_ROMX = 0x02,
SECT_ROM0 = 0x03,
SECT_HRAM = 0x04,
SECT_WRAMX = 0x05,
SECT_SRAM = 0x06,
SECT_OAM = 0x07
enum SectionType {
SECTTYPE_WRAM0,
SECTTYPE_VRAM,
SECTTYPE_ROMX,
SECTTYPE_ROM0,
SECTTYPE_HRAM,
SECTTYPE_WRAMX,
SECTTYPE_SRAM,
SECTTYPE_OAM,
SECTTYPE_INVALID
};
enum eSymbolType {
SYM_LOCAL = 0x00,
SYM_IMPORT = 0x01,
SYM_EXPORT = 0x02
enum SymbolType {
SYMTYPE_LOCAL,
SYMTYPE_IMPORT,
SYMTYPE_EXPORT
};
enum ePatchType {
PATCH_BYTE = 0x00,
PATCH_WORD_L = 0x01,
PATCH_LONG_L = 0x02,
PATCH_BYTE_JR = 0x03
enum PatchType {
PATCHTYPE_BYTE,
PATCHTYPE_WORD,
PATCHTYPE_LONG,
PATCHTYPE_JR,
PATCHTYPE_INVALID
};
/**
* Tells whether a section has data in its object file definition,
* depending on type.
* @param type The section's type
* @return `true` if the section's definition includes data
*/
static inline bool sect_HasData(enum SectionType type)
{
return type == SECTTYPE_ROM0 || type == SECTTYPE_ROMX;
}
#endif /* RGBDS_LINKDEFS_H */

View File

@@ -45,22 +45,22 @@ static void bankrangecheck(char *name, uint32_t secttype, int32_t org,
char *stype = NULL;
switch (secttype) {
case SECT_ROMX:
case SECTTYPE_ROMX:
stype = "ROMX";
minbank = BANK_MIN_ROMX;
maxbank = BANK_MAX_ROMX;
break;
case SECT_SRAM:
case SECTTYPE_SRAM:
stype = "SRAM";
minbank = BANK_MIN_SRAM;
maxbank = BANK_MAX_SRAM;
break;
case SECT_WRAMX:
case SECTTYPE_WRAMX:
stype = "WRAMX";
minbank = BANK_MIN_WRAMX;
maxbank = BANK_MAX_WRAMX;
break;
case SECT_VRAM:
case SECTTYPE_VRAM:
stype = "VRAM";
minbank = BANK_MIN_VRAM;
maxbank = BANK_MAX_VRAM;
@@ -1523,33 +1523,33 @@ section : T_POP_SECTION string comma sectiontype
}
;
sectiontype : T_SECT_WRAM0 { $$ = SECT_WRAM0; }
| T_SECT_VRAM { $$ = SECT_VRAM; }
| T_SECT_ROMX { $$ = SECT_ROMX; }
| T_SECT_ROM0 { $$ = SECT_ROM0; }
| T_SECT_HRAM { $$ = SECT_HRAM; }
| T_SECT_WRAMX { $$ = SECT_WRAMX; }
| T_SECT_SRAM { $$ = SECT_SRAM; }
| T_SECT_OAM { $$ = SECT_OAM; }
sectiontype : T_SECT_WRAM0 { $$ = SECTTYPE_WRAM0; }
| T_SECT_VRAM { $$ = SECTTYPE_VRAM; }
| T_SECT_ROMX { $$ = SECTTYPE_ROMX; }
| T_SECT_ROM0 { $$ = SECTTYPE_ROM0; }
| T_SECT_HRAM { $$ = SECTTYPE_HRAM; }
| T_SECT_WRAMX { $$ = SECTTYPE_WRAMX; }
| T_SECT_SRAM { $$ = SECTTYPE_SRAM; }
| T_SECT_OAM { $$ = SECTTYPE_OAM; }
| T_SECT_HOME
{
warning("HOME section name is deprecated, use ROM0 instead.");
$$ = SECT_ROM0;
$$ = SECTTYPE_ROM0;
}
| T_SECT_DATA
{
warning("DATA section name is deprecated, use ROMX instead.");
$$ = SECT_ROMX;
$$ = SECTTYPE_ROMX;
}
| T_SECT_CODE
{
warning("CODE section name is deprecated, use ROMX instead.");
$$ = SECT_ROMX;
$$ = SECTTYPE_ROMX;
}
| T_SECT_BSS
{
warning("BSS section name is deprecated, use WRAM0 instead.");
$$ = SECT_WRAM0;
$$ = SECTTYPE_WRAM0;
}
;

View File

@@ -96,21 +96,21 @@ void out_PopSection(void)
static uint32_t getmaxsectionsize(uint32_t secttype, char *sectname)
{
switch (secttype) {
case SECT_ROM0:
case SECTTYPE_ROM0:
return 0x8000; /* If ROMX sections not used */
case SECT_ROMX:
case SECTTYPE_ROMX:
return 0x4000;
case SECT_VRAM:
case SECTTYPE_VRAM:
return 0x2000;
case SECT_SRAM:
case SECTTYPE_SRAM:
return 0x2000;
case SECT_WRAM0:
case SECTTYPE_WRAM0:
return 0x2000; /* If WRAMX sections not used */
case SECT_WRAMX:
case SECTTYPE_WRAMX:
return 0x1000;
case SECT_OAM:
case SECTTYPE_OAM:
return 0xA0;
case SECT_HRAM:
case SECTTYPE_HRAM:
return 0x7F;
default:
break;
@@ -241,7 +241,7 @@ static void writesection(struct Section *pSect, FILE *f)
fputlong(pSect->nBank, f);
fputlong(pSect->nAlign, f);
if ((pSect->nType == SECT_ROM0) || (pSect->nType == SECT_ROMX)) {
if (sect_HasData(pSect->nType)) {
struct Patch *pPatch;
fwrite(pSect->tData, 1, pSect->nPC, f);
@@ -265,23 +265,23 @@ static void writesymbol(struct sSymbol *pSym, FILE *f)
int32_t sectid;
if (!(pSym->nType & SYMF_DEFINED)) {
type = SYM_IMPORT;
type = SYMTYPE_IMPORT;
} else if (pSym->nType & SYMF_EXPORT) {
type = SYM_EXPORT;
type = SYMTYPE_EXPORT;
} else {
type = SYM_LOCAL;
type = SYMTYPE_LOCAL;
}
switch (type) {
case SYM_LOCAL:
case SYMTYPE_LOCAL:
offset = pSym->nValue;
sectid = getsectid(pSym->pSection);
break;
case SYM_IMPORT:
case SYMTYPE_IMPORT:
offset = 0;
sectid = -1;
break;
case SYM_EXPORT:
case SYMTYPE_EXPORT:
offset = pSym->nValue;
if (pSym->nType & SYMF_CONST)
sectid = -1;
@@ -293,7 +293,7 @@ static void writesymbol(struct sSymbol *pSym, FILE *f)
fputstring(pSym->tzName, f);
fputc(type, f);
if (type != SYM_IMPORT) {
if (type != SYMTYPE_IMPORT) {
fputstring(pSym->tzFileName, f);
fputlong(pSym->nFileLine, f);
@@ -496,8 +496,7 @@ static void checksection(void)
static void checkcodesection(void)
{
checksection();
if (pCurrentSection->nType != SECT_ROM0 &&
pCurrentSection->nType != SECT_ROMX) {
if (!sect_HasData(pCurrentSection->nType)) {
fatalerror("Section '%s' cannot contain code or data (not ROM0 or ROMX)",
pCurrentSection->pzName);
} else if (nUnionDepth > 0) {
@@ -570,8 +569,7 @@ void out_WriteObject(void)
if (f == NULL)
fatalerror("Couldn't write file '%s'\n", tzObjectname);
fwrite(RGBDS_OBJECT_VERSION_STRING, 1,
strlen(RGBDS_OBJECT_VERSION_STRING), f);
fprintf(f, RGBDS_OBJECT_VERSION_STRING, RGBDS_OBJECT_VERSION_NUMBER);
fputlong(countsymbols(), f);
fputlong(countsections(), f);
@@ -647,7 +645,7 @@ struct Section *out_FindSection(char *pzName, uint32_t secttype, int32_t org,
pSect->charmap = NULL;
/* It is only needed to allocate memory for ROM sections. */
if (secttype == SECT_ROM0 || secttype == SECT_ROMX) {
if (sect_HasData(secttype)) {
uint32_t sectsize;
sectsize = getmaxsectionsize(secttype, pzName);
@@ -743,8 +741,7 @@ void out_Skip(int32_t skip)
{
checksection();
checksectionoverflow(skip);
if (!((pCurrentSection->nType == SECT_ROM0)
|| (pCurrentSection->nType == SECT_ROMX))) {
if (!sect_HasData(pCurrentSection->nType)) {
pCurrentSection->nPC += skip;
nPC += skip;
pPCSymbol->nValue += skip;
@@ -779,7 +776,7 @@ void out_RelByte(struct Expression *expr)
checksectionoverflow(1);
if (rpn_isReloc(expr)) {
pCurrentSection->tData[nPC] = 0;
createpatch(PATCH_BYTE, expr);
createpatch(PATCHTYPE_BYTE, expr);
pCurrentSection->nPC += 1;
nPC += 1;
pPCSymbol->nValue += 1;
@@ -815,7 +812,7 @@ void out_RelWord(struct Expression *expr)
if (rpn_isReloc(expr)) {
pCurrentSection->tData[nPC] = 0;
pCurrentSection->tData[nPC + 1] = 0;
createpatch(PATCH_WORD_L, expr);
createpatch(PATCHTYPE_WORD, expr);
pCurrentSection->nPC += 2;
nPC += 2;
pPCSymbol->nValue += 2;
@@ -854,7 +851,7 @@ void out_RelLong(struct Expression *expr)
pCurrentSection->tData[nPC + 1] = 0;
pCurrentSection->tData[nPC + 2] = 0;
pCurrentSection->tData[nPC + 3] = 0;
createpatch(PATCH_LONG_L, expr);
createpatch(PATCHTYPE_LONG, expr);
pCurrentSection->nPC += 4;
nPC += 4;
pPCSymbol->nValue += 4;
@@ -875,7 +872,7 @@ void out_PCRelByte(struct Expression *expr)
/* Always let the linker calculate the offset. */
pCurrentSection->tData[nPC] = 0;
createpatch(PATCH_BYTE_JR, expr);
createpatch(PATCHTYPE_JR, expr);
pCurrentSection->nPC += 1;
nPC += 1;
pPCSymbol->nValue += 1;

13
src/extern/err.c vendored
View File

@@ -14,13 +14,12 @@
void rgbds_vwarn(const char *fmt, va_list ap)
{
fprintf(stderr, "warning");
fprintf(stderr, "warning: ");
if (fmt) {
fputs(": ", stderr);
vfprintf(stderr, fmt, ap);
fputs(": ", stderr);
}
putc('\n', stderr);
perror(0);
perror(NULL);
}
void rgbds_vwarnx(const char *fmt, va_list ap)
@@ -35,12 +34,12 @@ void rgbds_vwarnx(const char *fmt, va_list ap)
noreturn_ void rgbds_verr(int status, const char *fmt, va_list ap)
{
fprintf(stderr, "error");
fprintf(stderr, "error: ");
if (fmt) {
fputs(": ", stderr);
vfprintf(stderr, fmt, ap);
fputs(": ", stderr);
}
putc('\n', stderr);
perror(NULL);
exit(status);
}

118
src/hashmap.c Normal file
View File

@@ -0,0 +1,118 @@
#include <stdint.h>
#include <stdbool.h>
#include <stdlib.h>
#include <string.h>
#include <assert.h>
#include "hashmap.h"
#include "extern/err.h"
/*
* The lower half of the hash is used to index the "master" table,
* the upper half is used to help resolve collisions more quickly
*/
#define UINT_BITS_(NB_BITS) uint##NB_BITS##_t
#define UINT_BITS(NB_BITS) UINT_BITS_(NB_BITS)
typedef UINT_BITS(HASH_NB_BITS) HashType;
typedef UINT_BITS(HALF_HASH_NB_BITS) HalfHashType;
struct HashMapEntry {
HalfHashType hash;
char const *key;
void *content;
struct HashMapEntry *next;
};
#define FNV_OFFSET_BASIS 0x811c9dc5
#define FNV_PRIME 16777619
/* FNV-1a hash */
static HashType hash(char const *str)
{
HashType hash = FNV_OFFSET_BASIS;
while (*str) {
hash ^= (uint8_t)*str++;
hash *= FNV_PRIME;
}
return hash;
}
bool hash_AddElement(HashMap map, char const *key, void *element)
{
HashType hashedKey = hash(key);
HalfHashType index = hashedKey;
struct HashMapEntry *newEntry = malloc(sizeof(*newEntry));
if (!newEntry)
err(1, "%s: Failed to allocate new entry", __func__);
newEntry->hash = hashedKey >> HALF_HASH_NB_BITS;
newEntry->key = key;
newEntry->content = element;
newEntry->next = map[index];
map[index] = newEntry;
return newEntry->next != NULL;
}
bool hash_DeleteElement(HashMap map, char const *key)
{
HashType hashedKey = hash(key);
struct HashMapEntry **ptr = &map[(HalfHashType)hashedKey];
while (*ptr) {
if (hashedKey >> HALF_HASH_NB_BITS == (*ptr)->hash
&& !strcmp((*ptr)->key, key)) {
struct HashMapEntry *next = (*ptr)->next;
free(*ptr);
*ptr = next;
return true;
}
}
return false;
}
void *hash_GetElement(HashMap const map, char const *key)
{
HashType hashedKey = hash(key);
struct HashMapEntry *ptr = map[(HalfHashType)hashedKey];
while (ptr) {
if (hashedKey >> HALF_HASH_NB_BITS == ptr->hash
&& !strcmp(ptr->key, key)) {
return ptr->content;
}
ptr = ptr->next;
}
return NULL;
}
void hash_ForEach(HashMap const map, void (*func)(void *, void *), void *arg)
{
for (size_t i = 0; i < HASHMAP_NB_BUCKETS; i++) {
struct HashMapEntry *ptr = map[i];
while (ptr) {
func(ptr->content, arg);
ptr = ptr->next;
}
}
}
void hash_EmptyMap(HashMap map)
{
for (size_t i = 0; i < HASHMAP_NB_BUCKETS; i++) {
struct HashMapEntry *ptr = map[i];
while (ptr) {
struct HashMapEntry *next = ptr->next;
free(ptr);
ptr = next;
}
map[i] = NULL;
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -1,194 +0,0 @@
/*
* This file is part of RGBDS.
*
* Copyright (c) 2017-2018, Antonio Nino Diaz and RGBDS contributors.
*
* SPDX-License-Identifier: MIT
*/
%option noinput
%option yylineno
%{
#include <stdarg.h>
#include <stdint.h>
#include <unistd.h>
#include "extern/err.h"
#include "link/mylink.h"
#include "link/script.h"
#include "parser.h"
#include "types.h"
extern int yyparse(void);
/* File include stack. */
#define MAX_INCLUDE_DEPTH 8
static int32_t include_stack_ptr;
static YY_BUFFER_STATE include_stack[MAX_INCLUDE_DEPTH];
static char include_path[MAX_INCLUDE_DEPTH][_MAX_PATH + 1];
static int32_t include_line[MAX_INCLUDE_DEPTH];
static char linkerscript_path[_MAX_PATH + 1]; /* Base file */
%}
%%
\"([^\\\"]|\\.)*\" {
if (strlen(yytext) > sizeof(yylval.s) - 1) {
script_fatalerror("String is too long: %s\n.",
yytext);
}
if (strlen(yytext) < 3) { /* 2 quotes + 1 character */
script_fatalerror("String %s is invalid\n.",
yytext);
}
/* Ignore first quote */
yytext++;
strcpy(yylval.s, yytext);
/* Remove end quote */
yylval.s[strlen(yylval.s)-1] = '\0';
return STRING;
}
\$[a-fA-F0-9]+ {
yytext++; /* Skip prefix */
yylval.i = strtol(yytext, NULL, 16);
return INTEGER;
}
[0-9]+ {
yylval.i = strtol(yytext, NULL, 10);
return INTEGER;
}
(?i:ROM0) { strcpy(yylval.s, "ROM0"); return SECTION_NONBANKED; }
(?i:ROMX) { strcpy(yylval.s, "ROMX"); return SECTION_BANKED; }
(?i:VRAM) { strcpy(yylval.s, "VRAM"); return SECTION_BANKED; }
(?i:WRAM0) { strcpy(yylval.s, "WRAM0"); return SECTION_NONBANKED; }
(?i:WRAMX) { strcpy(yylval.s, "WRAMX"); return SECTION_BANKED; }
(?i:SRAM) { strcpy(yylval.s, "SRAM"); return SECTION_BANKED; }
(?i:OAM) { strcpy(yylval.s, "OAM"); return SECTION_NONBANKED; }
(?i:HRAM) { strcpy(yylval.s, "HRAM"); return SECTION_NONBANKED; }
(?i:ALIGN) { return COMMAND_ALIGN; }
(?i:ORG) { return COMMAND_ORG; }
(?i:INCLUDE) { return COMMAND_INCLUDE; }
"\n" { return NEWLINE; }
;.* { /* Ignore comments. A dot doesn't match newline. */ }
[[:space:]] { /* Ignore whitespace. */ }
. { script_fatalerror("Invalid character [%s]\n.", yytext); }
%%
extern FILE *yyin;
void script_Parse(const char * path)
{
yyin = fopen(path, "r");
if (!yyin)
errx(1, "Error opening file! \"%s\"\n", path);
strncpy(linkerscript_path, path, sizeof(linkerscript_path));
linkerscript_path[sizeof(linkerscript_path) - 1] = '\0';
do {
yyparse();
} while (!feof(yyin));
fclose(yyin);
}
void script_IncludeFile(const char * path)
{
if (include_stack_ptr == (MAX_INCLUDE_DEPTH - 1))
script_fatalerror("Includes nested too deeply.");
include_line[include_stack_ptr] = yylineno;
include_stack[include_stack_ptr] = YY_CURRENT_BUFFER;
include_stack_ptr++;
yyin = fopen(path, "r");
if (!yyin)
script_fatalerror("Couldn't open file \"%s\"", path);
strncpy(include_path[include_stack_ptr], path, sizeof(include_path[0]));
include_path[include_stack_ptr][sizeof(include_path[0]) - 1] = '\0';
yy_switch_to_buffer(yy_create_buffer(yyin, YY_BUF_SIZE));
yylineno = 1;
/*
* The INCLUDE keyword is handled before reaching a newline token, it's
* handled right after parsing the string with the file name that has to
* be included. It isn't actually needed to include a newline after the
* path, the last line of the linkerscript doesn't need to have a
* newline character but it can have a command.
*
* This means that, when opening a new file, we must tell the parser
* that what it is going to start at a new line or it will think that
* the first line of the included script is the continuation of the
* INCLUDE line of the parent script. If this is not done, the first
* line of an included linkerscript can only be a comment (or empty).
*/
unput('\n');
}
int32_t script_IncludeDepthGet(void)
{
return include_stack_ptr;
}
void script_IncludePop(void)
{
fclose(yyin);
include_stack_ptr--;
yy_delete_buffer(YY_CURRENT_BUFFER);
yy_switch_to_buffer(include_stack[include_stack_ptr]);
yylineno = include_line[include_stack_ptr];
}
void script_PrintFileStack(void)
{
int32_t i = include_stack_ptr;
include_line[i] = yylineno;
while (i > 0) {
fprintf(stderr, "%s(%d) -> ", include_path[i], include_line[i]);
i--;
}
fprintf(stderr, "%s(%d)", linkerscript_path, include_line[i]);
}
noreturn_ void script_fatalerror(const char *fmt, ...)
{
va_list args;
va_start(args, fmt);
fprintf(stderr, "error: ");
script_PrintFileStack();
fprintf(stderr, ":\n ");
vfprintf(stderr, fmt, args);
fprintf(stderr, "\n");
va_end(args);
exit(1);
}

View File

@@ -1,122 +0,0 @@
/*
* This file is part of RGBDS.
*
* Copyright (c) 1997-2018, Carsten Sorensen and RGBDS contributors.
*
* SPDX-License-Identifier: MIT
*/
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "extern/err.h"
#include "link/mylink.h"
#include "link/main.h"
static uint8_t symboldefined(char *name)
{
const struct sSection *pSect;
pSect = pSections;
while (pSect) {
int32_t i;
for (i = 0; i < pSect->nNumberOfSymbols; i += 1) {
const struct sSymbol *tSymbol = pSect->tSymbols[i];
if ((tSymbol->Type == SYM_EXPORT)
|| ((tSymbol->Type == SYM_LOCAL)
&& (pSect == tSymbol->pSection))) {
if (strcmp(tSymbol->pzName, name) == 0)
return 1;
}
}
pSect = pSect->pNext;
}
return 0;
}
static uint8_t addmodulecontaining(char *name)
{
struct sSection **ppLSect = &pLibSections;
while (*ppLSect) {
int32_t i;
for (i = 0; i < (*ppLSect)->nNumberOfSymbols; i += 1) {
const struct sSymbol *tSymbol = (*ppLSect)->tSymbols[i];
if ((tSymbol->Type == SYM_EXPORT)
|| ((tSymbol->Type == SYM_LOCAL)
&& ((*ppLSect) == tSymbol->pSection))) {
if (strcmp(tSymbol->pzName, name) == 0) {
struct sSection **ppSect = &pSections;
while (*ppSect)
ppSect = &((*ppSect)->pNext);
*ppSect = *ppLSect;
*ppLSect = (*ppLSect)->pNext;
(*ppSect)->pNext = NULL;
return 1;
}
}
}
ppLSect = &((*ppLSect)->pNext);
}
return 0;
}
void AddNeededModules(void)
{
struct sSection *pSect;
if ((options & OPT_SMART_C_LINK) == 0) {
struct sSection **ppLSect;
ppLSect = &pLibSections;
while (*ppLSect) {
struct sSection **ppSect = &pSections;
while (*ppSect)
ppSect = &((*ppSect)->pNext);
*ppSect = *ppLSect;
*ppLSect = (*ppLSect)->pNext;
(*ppSect)->pNext = NULL;
/* ppLSect=&((*ppLSect)->pNext); */
}
return;
}
if (options & OPT_SMART_C_LINK) {
if (!addmodulecontaining(smartlinkstartsymbol)) {
errx(1, "Can't find start symbol '%s'",
smartlinkstartsymbol);
} else {
printf("Smart linking with symbol '%s'\n",
smartlinkstartsymbol);
}
}
pSect = pSections;
while (pSect) {
int32_t i;
for (i = 0; i < pSect->nNumberOfSymbols; i += 1) {
if ((pSect->tSymbols[i]->Type == SYM_IMPORT)
|| (pSect->tSymbols[i]->Type == SYM_LOCAL)) {
if (!symboldefined(pSect->tSymbols[i]->pzName))
addmodulecontaining(pSect->tSymbols[i]->pzName);
}
}
pSect = pSect->pNext;
}
}

View File

@@ -1,141 +1,162 @@
/*
* This file is part of RGBDS.
*
* Copyright (c) 1997-2018, Carsten Sorensen and RGBDS contributors.
* Copyright (c) 1997-2019, Carsten Sorensen and RGBDS contributors.
*
* SPDX-License-Identifier: MIT
*/
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include "extern/err.h"
#include <stdio.h>
#include <stdbool.h>
#include <stdint.h>
#include <stdlib.h>
#include "link/object.h"
#include "link/output.h"
#include "link/symbol.h"
#include "link/section.h"
#include "link/assign.h"
#include "link/patch.h"
#include "link/mylink.h"
#include "link/mapfile.h"
#include "link/main.h"
#include "link/library.h"
#include "link/output.h"
#include "extern/err.h"
#include "version.h"
enum eBlockType {
BLOCK_COMMENT,
BLOCK_OBJECTS,
BLOCK_LIBRARIES,
BLOCK_OUTPUT
};
bool isDmgMode; /* -d */
char const *linkerScriptName; /* -l */
char const *mapFileName; /* -m */
char const *symFileName; /* -n */
char const *overlayFileName; /* -O */
char const *outputFileName; /* -o */
uint8_t padValue; /* -p */
bool is32kMode; /* -t */
bool beVerbose; /* -v */
bool isWRA0Mode; /* -w */
int32_t options;
int32_t fillchar;
char *smartlinkstartsymbol;
/*
* Print the usagescreen
*/
static void print_usage(void)
FILE *openFile(char const *fileName, char const *mode)
{
printf(
"usage: rgblink [-dtVw] [-l linkerscript] [-m mapfile] [-n symfile] [-O overlay]\n"
" [-o outfile] [-p pad_value] [-s symbol] file [...]\n");
exit(1);
if (!fileName)
return NULL;
FILE *file = fopen(fileName, mode);
if (!file)
err(1, "Could not open file \"%s\"", fileName);
return file;
}
/*
* The main routine
/**
* Prints the program's usage to stdout.
*/
static void printUsage(void)
{
puts("usage: rgblink [-dtVvw] [-l linkerscript] [-m mapfile] [-n symfile] [-O overlay]\n"
" [-o outfile] [-p pad_value] [-s symbol] file [...]");
}
/**
* Cleans up what has been done
* Mostly here to please tools such as `valgrind` so actual errors can be seen
*/
static void cleanup(void)
{
obj_Cleanup();
}
int main(int argc, char *argv[])
{
int ch;
char *ep;
char optionChar;
char *endptr; /* For error checking with `strtol` */
unsigned long value; /* For storing `strtoul`'s return value */
if (argc == 1)
print_usage();
while ((ch = getopt(argc, argv, "dl:m:n:O:o:p:s:tVw")) != -1) {
switch (ch) {
/* Parse options */
while ((optionChar = getopt(argc, argv, "dl:m:n:O:o:p:s:tVvw")) != -1) {
switch (optionChar) {
case 'd':
isDmgMode = true;
isWRA0Mode = true;
break;
case 'l':
SetLinkerscriptName(optarg);
linkerScriptName = optarg;
break;
case 'm':
SetMapfileName(optarg);
mapFileName = optarg;
break;
case 'n':
SetSymfileName(optarg);
break;
case 'o':
out_Setname(optarg);
symFileName = optarg;
break;
case 'O':
out_SetOverlayname(optarg);
options |= OPT_OVERLAY;
overlayFileName = optarg;
break;
case 'o':
outputFileName = optarg;
break;
case 'p':
fillchar = strtoul(optarg, &ep, 0);
if (optarg[0] == '\0' || *ep != '\0')
value = strtoul(optarg, &endptr, 0);
if (optarg[0] == '\0' || *endptr != '\0')
errx(1, "Invalid argument for option 'p'");
if (fillchar < 0 || fillchar > 0xFF)
errx(1, "Argument for option 'p' must be between 0 and 0xFF");
if (value > 0xFF)
errx(1, "Argument for 'p' must be a byte (between 0 and 0xFF)");
padValue = value;
break;
case 's':
options |= OPT_SMART_C_LINK;
smartlinkstartsymbol = optarg;
/* FIXME: nobody knows what this does, figure it out */
(void)optarg;
warnx("Nobody has any idea what `-s` does");
break;
case 't':
options |= OPT_TINY;
break;
case 'd':
/*
* Set to set WRAM as a single continuous block as on
* DMG. All WRAM sections must be WRAM0 as bankable WRAM
* sections do not exist in this mode. A WRAMX section
* will raise an error. VRAM bank 1 can't be used if
* this option is enabled either.
*
* This option implies OPT_CONTWRAM.
*/
options |= OPT_DMG_MODE;
/* FALLTHROUGH */
case 'w':
/*
* Set to set WRAM as a single continuous block as on
* DMG. All WRAM sections must be WRAM0 as bankable WRAM
* sections do not exist in this mode. A WRAMX section
* will raise an error.
*/
options |= OPT_CONTWRAM;
is32kMode = true;
break;
case 'V':
printf("rgblink %s\n", get_package_version_string());
exit(0);
case 'v':
beVerbose = true;
break;
case 'w':
isWRA0Mode = true;
break;
default:
print_usage();
/* NOTREACHED */
printUsage();
exit(1);
}
}
argc -= optind;
argv += optind;
if (argc == 0)
print_usage();
int curArgIndex = optind;
for (int32_t i = 0; i < argc; ++i)
obj_Readfile(argv[i]);
AddNeededModules();
AssignSections();
CreateSymbolTable();
Patch();
Output();
CloseMapfile();
return 0;
/* If no input files were specified, the user must have screwed up */
if (curArgIndex == argc) {
fprintf(stderr, "No input files");
printUsage();
exit(1);
}
/* Patch the size array depending on command-line options */
if (is32kMode)
maxsize[SECTTYPE_ROM0] = 0x8000;
if (isWRA0Mode)
maxsize[SECTTYPE_WRAM0] = 0x2000;
/* Patch the bank ranges array depending on command-line options */
if (isDmgMode)
bankranges[SECTTYPE_VRAM][1] = BANK_MIN_VRAM;
/* Read all object files first, */
while (curArgIndex < argc)
obj_ReadFile(argv[curArgIndex++]);
/* then process them, */
obj_DoSanityChecks();
assign_AssignSections();
assign_Cleanup();
/* and finally output the result. */
patch_ApplyPatches();
out_WriteFiles();
/* Do cleanup before quitting, though. */
cleanup();
}

View File

@@ -1,151 +0,0 @@
/*
* This file is part of RGBDS.
*
* Copyright (c) 1997-2018, Carsten Sorensen and RGBDS contributors.
*
* SPDX-License-Identifier: MIT
*/
#include <errno.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "extern/err.h"
#include "link/assign.h"
#include "link/main.h"
#include "link/mylink.h"
static int32_t currentbank;
static int32_t sfbank;
static FILE *mf;
static FILE *sf;
void SetMapfileName(char *name)
{
mf = fopen(name, "w");
if (mf == NULL)
err(1, "Cannot open mapfile '%s'", name);
}
void SetSymfileName(char *name)
{
sf = fopen(name, "w");
if (sf == NULL)
err(1, "Cannot open symfile '%s'", name);
fprintf(sf, "; File generated by rgblink\n\n");
}
void CloseMapfile(void)
{
if (mf) {
fclose(mf);
mf = NULL;
}
if (sf) {
fclose(sf);
sf = NULL;
}
}
void MapfileInitBank(int32_t bank)
{
if ((bank < 0) || (bank >= BANK_INDEX_MAX))
errx(1, "%s: Unknown bank %d\n", __func__, bank);
if (mf) {
currentbank = bank;
if (BankIndexIsROM0(bank)) {
fprintf(mf, "ROM Bank #0 (HOME):\n");
} else if (BankIndexIsROMX(bank)) {
fprintf(mf, "ROM Bank #%d:\n",
bank - BANK_INDEX_ROMX + 1);
} else if (BankIndexIsWRAM0(bank)) {
fprintf(mf, "WRAM Bank #0:\n");
} else if (BankIndexIsWRAMX(bank)) {
fprintf(mf, "WRAM Bank #%d:\n",
bank - BANK_INDEX_WRAMX + 1);
} else if (BankIndexIsVRAM(bank)) {
fprintf(mf, "VRAM Bank #%d:\n", bank - BANK_INDEX_VRAM);
} else if (BankIndexIsOAM(bank)) {
fprintf(mf, "OAM:\n");
} else if (BankIndexIsHRAM(bank)) {
fprintf(mf, "HRAM:\n");
} else if (BankIndexIsSRAM(bank)) {
fprintf(mf, "SRAM Bank #%d:\n", bank - BANK_INDEX_SRAM);
}
}
if (sf) {
if (BankIndexIsROM0(bank))
sfbank = 0;
else if (BankIndexIsROMX(bank))
sfbank = bank - BANK_INDEX_ROMX + 1;
else if (BankIndexIsWRAM0(bank))
sfbank = 0;
else if (BankIndexIsWRAMX(bank))
sfbank = bank - BANK_INDEX_WRAMX + 1;
else if (BankIndexIsVRAM(bank))
sfbank = bank - BANK_INDEX_VRAM;
else if (BankIndexIsOAM(bank))
sfbank = 0;
else if (BankIndexIsHRAM(bank))
sfbank = 0;
else if (BankIndexIsSRAM(bank))
sfbank = bank - BANK_INDEX_SRAM;
else
sfbank = 0;
}
}
void MapfileWriteSection(const struct sSection *pSect)
{
int32_t i;
if (mf) {
if (pSect->nByteSize > 0) {
fprintf(mf, " SECTION: $%04X-$%04X ($%04X bytes) [\"%s\"]\n",
pSect->nOrg, pSect->nOrg + pSect->nByteSize - 1,
pSect->nByteSize, pSect->pzName);
} else {
fprintf(mf, " SECTION: $%04X ($0 bytes) [\"%s\"]\n",
pSect->nOrg, pSect->pzName);
}
}
for (i = 0; i < pSect->nNumberOfSymbols; i += 1) {
const struct sSymbol *pSym = pSect->tSymbols[i];
/* Don't print '@' */
if (strcmp(pSym->pzName, "@") == 0)
continue;
if ((pSym->pSection == pSect) && (pSym->Type != SYM_IMPORT)) {
if (mf) {
fprintf(mf, " $%04X = %s\n",
pSym->nOffset + pSect->nOrg,
pSym->pzName);
}
if (sf) {
fprintf(sf, "%02X:%04X %s\n", sfbank,
pSym->nOffset + pSect->nOrg,
pSym->pzName);
}
}
}
}
void MapfileCloseBank(int32_t slack)
{
if (!mf)
return;
if (slack == MaxAvail[currentbank])
fprintf(mf, " EMPTY\n\n");
else
fprintf(mf, " SLACK: $%04X bytes\n\n", slack);
}

View File

@@ -1,381 +1,500 @@
/*
* This file is part of RGBDS.
*
* Copyright (c) 1997-2018, Carsten Sorensen and RGBDS contributors.
*
* SPDX-License-Identifier: MIT
*/
/*
* Here we have the routines that read an objectfile
*/
#include <ctype.h>
#include <errno.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <string.h>
#include <stdlib.h>
#include <errno.h>
#include <limits.h>
#include "common.h"
#include "link/object.h"
#include "link/main.h"
#include "link/symbol.h"
#include "link/section.h"
#include "link/assign.h"
#include "extern/err.h"
#include "linkdefs.h"
#include "common.h"
#include "link/assign.h"
#include "link/mylink.h"
#include "link/main.h"
static struct SymbolList {
size_t nbSymbols;
struct Symbol **symbolList;
struct SymbolList *next;
} *symbolLists;
struct sSymbol **tSymbols;
struct sSection *pSections;
struct sSection *pLibSections;
uint8_t oReadLib;
/***** Helper functions for reading object files *****/
/*
* Read 32-bit values with the correct endianness
* Internal, DO NOT USE.
* For helper wrapper macros defined below, such as `tryReadlong`
*/
static int32_t readlong(FILE *f)
{
uint32_t r;
#define tryRead(func, type, errval, var, file, ...) \
do { \
FILE *tmpFile = file; \
type tmpVal = func(tmpFile); \
if (tmpVal == (errval)) { \
errx(1, __VA_ARGS__, feof(tmpFile) \
? "Unexpected end of file" \
: strerror(errno)); \
} \
var = tmpVal; \
} while (0)
r = ((uint32_t)(uint8_t)fgetc(f));
r |= ((uint32_t)(uint8_t)fgetc(f)) << 8;
r |= ((uint32_t)(uint8_t)fgetc(f)) << 16;
r |= ((uint32_t)(uint8_t)fgetc(f)) << 24;
return (int32_t)r;
}
/*
* Read a NULL terminated string from a file
/**
* Reads an unsigned long (32-bit) value from a file.
* @param file The file to read from. This will read 4 bytes from the file.
* @return The value read, cast to a int64_t, or -1 on failure.
*/
int32_t readasciiz(char **dest, FILE *f)
static int64_t readlong(FILE *file)
{
size_t r = 0;
uint32_t value = 0;
size_t bufferLength = 16;
char *start = malloc(bufferLength);
char *s = start;
/* Read the little-endian value byte by byte */
for (uint8_t shift = 0; shift < sizeof(value) * CHAR_BIT; shift += 8) {
int byte = getc(file);
if (!s)
err(1, "%s: Couldn't allocate memory", __func__);
while (((*s++) = fgetc(f)) != 0) {
r += 1;
if (r >= bufferLength) {
bufferLength *= 2;
start = realloc(start, bufferLength);
if (!start) {
err(1, "%s: Couldn't allocate memory",
__func__);
}
s = start + r;
if (byte == EOF)
return INT64_MAX;
value |= (uint8_t)byte << shift;
}
return value;
}
*dest = start;
return (r + 1);
}
/*
* Allocate a new section and link it into the list
/**
* Helper macro for reading longs from a file, and errors out if it fails to.
* Not as a function to avoid overhead in the general case.
* TODO: maybe mark the condition as `unlikely`; how to do that portably?
* @param var The variable to stash the number into
* @param file The file to read from. Its position will be advanced
* @param ... A format string and related arguments; note that an extra string
* argument is provided, the reason for failure
*/
struct sSection *AllocSection(void)
{
struct sSection **ppSections;
#define tryReadlong(var, file, ...) \
tryRead(readlong, int64_t, INT64_MAX, var, file, __VA_ARGS__)
if (oReadLib == 1)
ppSections = &pLibSections;
/* There is no `readbyte`, just use `fgetc` or `getc`. */
/**
* Helper macro for reading bytes from a file, and errors out if it fails to.
* Differs from `tryGetc` in that the backing function is fgetc(1).
* Not as a function to avoid overhead in the general case.
* TODO: maybe mark the condition as `unlikely`; how to do that portably?
* @param var The variable to stash the number into
* @param file The file to read from. Its position will be advanced
* @param ... A format string and related arguments; note that an extra string
* argument is provided, the reason for failure
*/
#define tryFgetc(var, file, ...) \
tryRead(fgetc, int, EOF, var, file, __VA_ARGS__)
/**
* Helper macro for reading bytes from a file, and errors out if it fails to.
* Differs from `tryGetc` in that the backing function is fgetc(1).
* Not as a function to avoid overhead in the general case.
* TODO: maybe mark the condition as `unlikely`; how to do that portably?
* @param var The variable to stash the number into
* @param file The file to read from. Its position will be advanced
* @param ... A format string and related arguments; note that an extra string
* argument is provided, the reason for failure
*/
#define tryGetc(var, file, ...) \
tryRead(getc, int, EOF, var, file, __VA_ARGS__)
/**
* Reads a '\0'-terminated string from a file.
* @param file The file to read from. The file position will be advanced.
* @return The string read, or NULL on failure.
* If a non-NULL pointer is returned, make sure to `free` it when done!
*/
static char *readstr(FILE *file)
{
/* Default buffer size, have it close to the average string length */
size_t capacity = 32 / 2;
size_t index = -1;
/* Force the first iteration to allocate */
char *str = NULL;
do {
/* Prepare going to next char */
index++;
/* If the buffer isn't suitable to write the next char... */
if (index >= capacity || !str) {
capacity *= 2;
str = realloc(str, capacity);
/* End now in case of error */
if (!str)
return NULL;
}
/* Read char */
int byte = getc(file);
if (byte == EOF)
return NULL;
str[index] = byte;
} while (str[index]);
return str;
}
/**
* Helper macro for reading bytes from a file, and errors out if it fails to.
* Not as a function to avoid overhead in the general case.
* TODO: maybe mark the condition as `unlikely`; how to do that portably?
* @param var The variable to stash the string into
* @param file The file to read from. Its position will be advanced
* @param ... A format string and related arguments; note that an extra string
* argument is provided, the reason for failure
*/
#define tryReadstr(var, file, ...) \
tryRead(readstr, char*, NULL, var, file, __VA_ARGS__)
/***** Functions to parse object files *****/
/**
* Reads a RGB6 symbol from a file.
* @param file The file to read from
* @param symbol The struct to fill
* @param fileName The filename to report in errors
*/
static void readSymbol(FILE *file, struct Symbol *symbol, char const *fileName)
{
tryReadstr(symbol->name, file, "%s: Cannot read symbol name: %s",
fileName);
tryGetc(symbol->type, file, "%s: Cannot read \"%s\"'s type: %s",
fileName, symbol->name);
/* If the symbol is defined in this file, read its definition */
if (symbol->type != SYMTYPE_IMPORT) {
symbol->objFileName = fileName;
tryReadstr(symbol->fileName, file,
"%s: Cannot read \"%s\"'s file name: %s",
fileName, symbol->name);
tryReadlong(symbol->lineNo, file,
"%s: Cannot read \"%s\"'s line number: %s",
fileName, symbol->name);
tryReadlong(symbol->sectionID, file,
"%s: Cannot read \"%s\"'s section ID: %s",
fileName, symbol->name);
tryReadlong(symbol->offset, file,
"%s: Cannot read \"%s\"'s value: %s",
fileName, symbol->name);
} else {
symbol->sectionID = -1;
}
}
/**
* Reads a RGB6 patch from a file.
* @param file The file to read from
* @param patch The struct to fill
* @param fileName The filename to report in errors
* @param i The number of the patch to report in errors
*/
static void readPatch(FILE *file, struct Patch *patch,
char const *fileName, char const *sectName, uint32_t i)
{
tryReadstr(patch->fileName, file,
"%s: Unable to read \"%s\"'s patch #%u's name: %s",
fileName, sectName, i);
tryReadlong(patch->lineNo, file,
"%s: Unable to read \"%s\"'s patch #%u's line number: %s",
fileName, sectName, i);
tryReadlong(patch->offset, file,
"%s: Unable to read \"%s\"'s patch #%u's offset: %s",
fileName, sectName, i);
tryGetc(patch->type, file,
"%s: Unable to read \"%s\"'s patch #%u's type: %s",
fileName, sectName, i);
tryReadlong(patch->rpnSize, file,
"%s: Unable to read \"%s\"'s patch #%u's RPN size: %s",
fileName, sectName, i);
uint8_t *rpnExpression =
malloc(sizeof(*rpnExpression) * patch->rpnSize);
size_t nbElementsRead = fread(rpnExpression, sizeof(*rpnExpression),
patch->rpnSize, file);
if (nbElementsRead != patch->rpnSize)
errx(1, "%s: Cannot read \"%s\"'s patch #%u's RPN expression: %s",
fileName, sectName, i);
patch->rpnExpression = rpnExpression;
}
/**
* Reads a RGB6 section from a file.
* @param file The file to read from
* @param section The struct to fill
* @param fileName The filename to report in errors
*/
static void readSection(FILE *file, struct Section *section,
char const *fileName)
{
int32_t tmp;
tryReadstr(section->name, file, "%s: Cannot read section name: %s",
fileName);
tryReadlong(tmp, file, "%s: Cannot read \"%s\"'s' size: %s",
fileName, section->name);
if (tmp < 0 || tmp > UINT16_MAX)
errx(1, "\"%s\"'s section size (%d) is invalid", section->name,
tmp);
section->size = tmp;
tryGetc(section->type, file, "%s: Cannot read \"%s\"'s type: %s",
fileName, section->name);
tryReadlong(tmp, file, "%s: Cannot read \"%s\"'s org: %s",
fileName, section->name);
section->isAddressFixed = tmp >= 0;
if (tmp > UINT16_MAX)
errx(1, "\"%s\" is too large (%d)", tmp);
section->org = tmp;
tryReadlong(tmp, file, "%s: Cannot read \"%s\"'s bank: %s",
fileName, section->name);
section->isBankFixed = tmp >= 0;
section->bank = tmp;
tryReadlong(tmp, file, "%s: Cannot read \"%s\"'s alignment: %s",
fileName, section->name);
section->isAlignFixed = tmp != 1;
section->alignMask = tmp - 1;
if (sect_HasData(section->type)) {
/* Ensure we never allocate 0 bytes */
uint8_t *data = malloc(sizeof(*data) * section->size + 1);
if (!data)
err(1, "%s: Unable to read \"%s\"'s data", fileName,
section->name);
if (section->size) {
size_t nbElementsRead = fread(data, sizeof(*data),
section->size, file);
if (nbElementsRead != section->size)
errx(1, "%s: Cannot read \"%s\"'s data: %s",
fileName, section->name,
feof(file) ? "Unexpected end of file"
: strerror(errno));
}
section->data = data;
tryReadlong(section->nbPatches, file,
"%s: Cannot read \"%s\"'s number of patches: %s",
fileName, section->name);
struct Patch *patches =
malloc(sizeof(*patches) * section->nbPatches + 1);
if (!patches)
err(1, "%s: Unable to read \"%s\"'s patches", fileName,
section->name);
for (uint32_t i = 0; i < section->nbPatches; i++)
readPatch(file, &patches[i], fileName, section->name,
i);
section->patches = patches;
}
}
/**
* Links a symbol to a section, keeping the section's symbol list sorted.
* @param symbol The symbol to link
* @param section The section to link
*/
static void linkSymToSect(struct Symbol const *symbol, struct Section *section)
{
uint32_t a = 0, b = section->nbSymbols;
while (a != b) {
uint32_t c = (a + b) / 2;
if (section->symbols[c]->offset > symbol->offset)
b = c;
else
ppSections = &pSections;
while (*ppSections)
ppSections = &((*ppSections)->pNext);
*ppSections = malloc(sizeof **ppSections);
if (!*ppSections)
err(1, "%s: Couldn't allocate memory", __func__);
(*ppSections)->tSymbols = tSymbols;
(*ppSections)->pNext = NULL;
(*ppSections)->pPatches = NULL;
(*ppSections)->oAssigned = 0;
return *ppSections;
a = c + 1;
}
/*
* Read a symbol from a file
struct Symbol const *tmp = symbol;
for (uint32_t i = a; i <= section->nbSymbols; i++) {
symbol = tmp;
tmp = section->symbols[i];
section->symbols[i] = symbol;
}
section->nbSymbols++;
}
/**
* Reads a RGB6 object file.
* @param file The file to read from
* @param fileName The filename to report in errors
*/
struct sSymbol *obj_ReadSymbol(FILE *f, char *tzObjectfile)
static void readRGB6File(FILE *file, char const *fileName)
{
struct sSymbol *pSym;
uint32_t nbSymbols;
uint32_t nbSections;
pSym = malloc(sizeof(*pSym));
if (!pSym)
err(1, "%s: Couldn't allocate memory", __func__);
tryReadlong(nbSymbols, file, "%s: Cannot read number of symbols: %s",
fileName);
tryReadlong(nbSections, file, "%s: Cannot read number of sections: %s",
fileName);
readasciiz(&pSym->pzName, f);
pSym->Type = (enum eSymbolType)fgetc(f);
nbSectionsToAssign += nbSections;
pSym->pzObjFileName = tzObjectfile;
/* This file's symbols, kept to link sections to them */
struct Symbol **fileSymbols =
malloc(sizeof(*fileSymbols) * nbSymbols + 1);
if (pSym->Type != SYM_IMPORT) {
readasciiz(&pSym->pzFileName, f);
pSym->nFileLine = readlong(f);
if (!fileSymbols)
err(1, "Failed to get memory for %s's symbols", fileName);
pSym->nSectionID = readlong(f);
pSym->nOffset = readlong(f);
struct SymbolList *symbolList = malloc(sizeof(*symbolList));
if (!symbolList)
err(1, "Failed to register %s's symbol list", fileName);
symbolList->symbolList = fileSymbols;
symbolList->nbSymbols = nbSymbols;
symbolList->next = symbolLists;
symbolLists = symbolList;
uint32_t nbSymPerSect[nbSections];
memset(nbSymPerSect, 0, sizeof(nbSymPerSect));
verbosePrint("Reading %u symbols...\n", nbSymbols);
for (uint32_t i = 0; i < nbSymbols; i++) {
/* Read symbol */
struct Symbol *symbol = malloc(sizeof(*symbol));
if (!symbol)
err(1, "%s: Couldn't create new symbol", fileName);
readSymbol(file, symbol, fileName);
fileSymbols[i] = symbol;
if (symbol->type == SYMTYPE_EXPORT)
sym_AddSymbol(symbol);
if (symbol->sectionID != -1)
nbSymPerSect[symbol->sectionID]++;
}
return pSym;
}
/* This file's sections, stored in a table to link symbols to them */
struct Section *fileSections[nbSections ? nbSections : 1];
/*
* RGB object reader routines
*/
struct sSection *obj_ReadRGBSection(FILE *f)
{
struct sSection *pSection;
char *pzName;
verbosePrint("Reading %u sections...\n", nbSections);
for (uint32_t i = 0; i < nbSections; i++) {
/* Read section */
struct Section *section = malloc(sizeof(*section));
readasciiz(&pzName, f);
if (IsSectionNameInUse(pzName))
errx(1, "Section name \"%s\" is already in use.", pzName);
if (!section)
err(1, "%s: Couldn't create new section", fileName);
readSection(file, section, fileName);
section->fileSymbols = fileSymbols;
pSection = AllocSection();
pSection->pzName = pzName;
pSection->nByteSize = readlong(f);
pSection->Type = (enum eSectionType)fgetc(f);
pSection->nOrg = readlong(f);
pSection->nBank = readlong(f);
pSection->nAlign = readlong(f);
if ((options & OPT_TINY) && (pSection->Type == SECT_ROMX))
errx(1, "ROMX sections can't be used with option -t.");
if ((options & OPT_CONTWRAM) && (pSection->Type == SECT_WRAMX))
errx(1, "WRAMX sections can't be used with options -w or -d.");
if (options & OPT_DMG_MODE) {
/* WRAMX sections are checked for OPT_CONTWRAM */
if (pSection->Type == SECT_VRAM && pSection->nBank == 1)
errx(1, "VRAM bank 1 can't be used with option -d.");
}
uint32_t maxsize = 0;
/* Verify that the section isn't too big */
switch (pSection->Type) {
case SECT_ROM0:
maxsize = (options & OPT_TINY) ? 0x8000 : 0x4000;
break;
case SECT_ROMX:
maxsize = 0x4000;
break;
case SECT_VRAM:
case SECT_SRAM:
maxsize = 0x2000;
break;
case SECT_WRAM0:
maxsize = (options & OPT_CONTWRAM) ? 0x2000 : 0x1000;
break;
case SECT_WRAMX:
maxsize = 0x1000;
break;
case SECT_OAM:
maxsize = 0xA0;
break;
case SECT_HRAM:
maxsize = 0x7F;
break;
default:
errx(1, "Section \"%s\" has an invalid section type.", pzName);
break;
}
if (pSection->nByteSize > maxsize) {
errx(1, "Section \"%s\" is bigger than the max size for that type: 0x%X > 0x%X",
pzName, pSection->nByteSize, maxsize);
}
/*
* If the section doesn't contain data, it is ready
*/
if ((pSection->Type != SECT_ROMX) && (pSection->Type != SECT_ROM0))
return pSection;
/* If there is no data to read, exit */
if (pSection->nByteSize == 0) {
/* Skip number of patches */
readlong(f);
pSection->pData = NULL;
return pSection;
}
pSection->pData = malloc(pSection->nByteSize);
if (!pSection->pData)
err(1, "%s: Couldn't allocate memory", __func__);
int32_t nNumberOfPatches;
struct sPatch **ppPatch, *pPatch;
if (fread(pSection->pData, sizeof(uint8_t), pSection->nByteSize, f)
!= pSection->nByteSize) {
err(1, "%s: Read error", __func__);
}
nNumberOfPatches = readlong(f);
ppPatch = &pSection->pPatches;
/*
* And patches...
*/
while (nNumberOfPatches--) {
pPatch = malloc(sizeof(*pPatch));
if (!pPatch)
err(1, "%s: Couldn't allocate memory", __func__);
*ppPatch = pPatch;
readasciiz(&pPatch->pzFilename, f);
pPatch->nLineNo = readlong(f);
pPatch->nOffset = readlong(f);
pPatch->Type = (enum ePatchType)fgetc(f);
pPatch->nRPNSize = readlong(f);
if (pPatch->nRPNSize > 0) {
pPatch->pRPN = malloc(pPatch->nRPNSize);
if (!pPatch->pRPN) {
err(1, "%s: Couldn't allocate memory",
__func__);
}
if (fread(pPatch->pRPN, sizeof(uint8_t),
pPatch->nRPNSize, f) != pPatch->nRPNSize) {
errx(1, "%s: Read error", __func__);
}
sect_AddSection(section);
fileSections[i] = section;
if (nbSymPerSect[i]) {
section->symbols = malloc(sizeof(*section->symbols)
* nbSymPerSect[i]);
if (!section->symbols)
err(1, "%s: Couldn't link to symbols");
} else {
pPatch->pRPN = NULL;
section->symbols = NULL;
}
section->nbSymbols = 0;
}
pPatch->pNext = NULL;
ppPatch = &(pPatch->pNext);
}
/* Give symbols pointers to their sections */
for (uint32_t i = 0; i < nbSymbols; i++) {
int32_t sectionID = fileSymbols[i]->sectionID;
return pSection;
}
void obj_ReadRGB(FILE *pObjfile, char *tzObjectfile)
{
struct sSection *pFirstSection;
int32_t nNumberOfSymbols, nNumberOfSections, i;
nNumberOfSymbols = readlong(pObjfile);
nNumberOfSections = readlong(pObjfile);
/* First comes the symbols */
if (nNumberOfSymbols) {
tSymbols = malloc(nNumberOfSymbols * sizeof(*tSymbols));
if (!tSymbols)
err(1, "%s: Couldn't allocate memory", __func__);
for (i = 0; i < nNumberOfSymbols; i += 1)
tSymbols[i] = obj_ReadSymbol(pObjfile, tzObjectfile);
if (sectionID == -1) {
fileSymbols[i]->section = NULL;
} else {
tSymbols = NULL;
fileSymbols[i]->section = fileSections[sectionID];
/* Give the section a pointer to the symbol as well */
linkSymToSect(fileSymbols[i], fileSections[sectionID]);
}
}
}
/* Next we have the sections */
pFirstSection = NULL;
while (nNumberOfSections--) {
struct sSection *pNewSection;
pNewSection = obj_ReadRGBSection(pObjfile);
pNewSection->nNumberOfSymbols = nNumberOfSymbols;
if (pFirstSection == NULL)
pFirstSection = pNewSection;
}
/*
* Fill in the pSection entry in the symbolstructure.
* This REALLY needs some cleaning up... but, hey, it works
/**
* Reads an object file of any supported format
* @param fileName The filename to report for errors
*/
for (i = 0; i < nNumberOfSymbols; i += 1) {
struct sSection *pConvSect = pFirstSection;
if ((tSymbols[i]->Type != SYM_IMPORT) &&
(tSymbols[i]->nSectionID != -1)) {
int32_t j = 0;
while (j != tSymbols[i]->nSectionID) {
j += 1;
pConvSect = pConvSect->pNext;
}
tSymbols[i]->pSection = pConvSect;
} else {
tSymbols[i]->pSection = NULL;
}
}
}
/*
* The main objectfileloadroutine (phew)
*/
void obj_ReadOpenFile(FILE *pObjfile, char *tzObjectfile)
void obj_ReadFile(char const *fileName)
{
char tzHeader[strlen(RGBDS_OBJECT_VERSION_STRING) + 1];
FILE *file = strcmp("-", fileName) ? fopen(fileName, "rb") : stdin;
if (fread(tzHeader, sizeof(char), strlen(RGBDS_OBJECT_VERSION_STRING),
pObjfile) != strlen(RGBDS_OBJECT_VERSION_STRING)) {
errx(1, "%s: Read error", tzObjectfile);
if (!file) {
err(1, "Could not open file %s", fileName);
return;
}
tzHeader[strlen(RGBDS_OBJECT_VERSION_STRING)] = 0;
/* Begin by reading the magic bytes and version number */
uint8_t versionNumber;
int matchedElems = fscanf(file, RGBDS_OBJECT_VERSION_STRING,
&versionNumber);
if (strncmp(tzHeader, RGBDS_OBJECT_VERSION_STRING,
strlen(RGBDS_OBJECT_VERSION_STRING)) == 0) {
obj_ReadRGB(pObjfile, tzObjectfile);
} else {
int32_t i;
if (matchedElems != 1)
errx(1, "\"%s\" is not a RGBDS object file", fileName);
/* TODO: support other versions? */
if (versionNumber != 6)
errx(1, "\"%s\" is an incompatible version %hhu object file",
fileName, versionNumber);
for (i = 0; i < strlen(RGBDS_OBJECT_VERSION_STRING); i++)
if (!isprint(tzHeader[i]))
tzHeader[i] = '?';
verbosePrint("Reading object file %s, version %hhu\n",
fileName, versionNumber);
errx(1, "%s: Invalid file or object file version [%s]",
tzObjectfile, tzHeader);
}
readRGB6File(file, fileName);
fclose(file);
}
void obj_Readfile(char *tzObjectfile)
void obj_DoSanityChecks(void)
{
FILE *pObjfile;
if (options & OPT_SMART_C_LINK)
oReadLib = 1;
else
oReadLib = 0;
pObjfile = fopen(tzObjectfile, "rb");
if (pObjfile == NULL)
err(1, "Unable to open object '%s'", tzObjectfile);
obj_ReadOpenFile(pObjfile, tzObjectfile);
fclose(pObjfile);
oReadLib = 0;
sect_DoSanityChecks();
}
int32_t file_Length(FILE *f)
static void freeSection(struct Section *section, void *arg)
{
uint32_t r, p;
(void)arg;
p = ftell(f);
fseek(f, 0, SEEK_END);
r = ftell(f);
fseek(f, p, SEEK_SET);
free(section->name);
if (sect_HasData(section->type)) {
free(section->data);
for (int32_t i = 0; i < section->nbPatches; i++) {
struct Patch *patch = &section->patches[i];
return r;
free(patch->fileName);
free(patch->rpnExpression);
}
free(section->patches);
}
free(section->symbols);
free(section);
}
static void freeSymbol(struct Symbol *symbol)
{
free(symbol->name);
if (symbol->type != SYMTYPE_IMPORT)
free(symbol->fileName);
free(symbol);
}
void obj_Cleanup(void)
{
sym_CleanupSymbols();
sect_ForEach(freeSection, NULL);
sect_CleanupSections();
struct SymbolList *list = symbolLists;
while (list) {
for (size_t i = 0; i < list->nbSymbols; i++)
freeSymbol(list->symbolList[i]);
free(list->symbolList);
struct SymbolList *next = list->next;
free(list);
list = next;
}
}

View File

@@ -1,174 +1,394 @@
/*
* This file is part of RGBDS.
*
* Copyright (c) 1997-2018, Carsten Sorensen and RGBDS contributors.
*
* SPDX-License-Identifier: MIT
*/
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdint.h>
#include "link/output.h"
#include "link/main.h"
#include "link/section.h"
#include "link/symbol.h"
#include "extern/err.h"
#include "link/mylink.h"
#include "link/mapfile.h"
#include "link/main.h"
#include "link/assign.h"
FILE *outputFile;
FILE *overlayFile;
FILE *symFile;
FILE *mapFile;
char *tzOutname;
char *tzOverlayname;
struct SortedSection {
struct Section const *section;
struct SortedSection *next;
};
int32_t MaxOverlayBank;
static struct {
uint32_t nbBanks;
struct SortedSections {
struct SortedSection *sections;
struct SortedSection *zeroLenSections;
} *banks;
} sections[SECTTYPE_INVALID];
void writehome(FILE *f, FILE *f_overlay)
void out_AddSection(struct Section const *section)
{
const struct sSection *pSect;
uint8_t *mem;
static uint32_t maxNbBanks[] = {
[SECTTYPE_ROM0] = 1,
[SECTTYPE_ROMX] = UINT32_MAX,
[SECTTYPE_VRAM] = 2,
[SECTTYPE_SRAM] = UINT32_MAX,
[SECTTYPE_WRAM0] = 1,
[SECTTYPE_WRAMX] = 7,
[SECTTYPE_OAM] = 1,
[SECTTYPE_HRAM] = 1
};
mem = malloc(MaxAvail[BANK_INDEX_ROM0]);
if (!mem)
uint32_t targetBank = section->bank - bankranges[section->type][0];
uint32_t minNbBanks = targetBank + 1;
if (minNbBanks > maxNbBanks[section->type])
errx(1, "Section \"%s\" has invalid bank range (%u > %u)",
section->name, section->bank,
maxNbBanks[section->type] - 1);
if (minNbBanks > sections[section->type].nbBanks) {
sections[section->type].banks =
realloc(sections[section->type].banks,
sizeof(*sections[0].banks) * minNbBanks);
for (uint32_t i = sections[section->type].nbBanks;
i < minNbBanks; i++) {
sections[section->type].banks[i].sections = NULL;
sections[section->type].banks[i].zeroLenSections = NULL;
}
sections[section->type].nbBanks = minNbBanks;
}
if (!sections[section->type].banks)
err(1, "Failed to realloc banks");
struct SortedSection *newSection = malloc(sizeof(*newSection));
struct SortedSection **ptr = section->size
? &sections[section->type].banks[targetBank].sections
: &sections[section->type].banks[targetBank].zeroLenSections;
if (!newSection)
err(1, "Failed to add new section \"%s\"", section->name);
newSection->section = section;
while (*ptr && (*ptr)->section->org < section->org)
ptr = &(*ptr)->next;
newSection->next = *ptr;
*ptr = newSection;
}
/**
* Performs sanity checks on the overlay file.
*/
static void checkOverlay(void)
{
if (!overlayFile)
return;
if (f_overlay != NULL) {
fseek(f_overlay, 0L, SEEK_SET);
if (fread(mem, 1, MaxAvail[BANK_INDEX_ROM0], f_overlay) !=
MaxAvail[BANK_INDEX_ROM0]) {
warnx("Failed to read data from overlay file.");
}
} else {
memset(mem, fillchar, MaxAvail[BANK_INDEX_ROM0]);
}
MapfileInitBank(0);
pSect = pSections;
while (pSect) {
if (pSect->Type == SECT_ROM0) {
memcpy(mem + pSect->nOrg, pSect->pData,
pSect->nByteSize);
MapfileWriteSection(pSect);
}
pSect = pSect->pNext;
if (fseek(overlayFile, 0, SEEK_END) != 0) {
warnx("Overlay file is not seekable, cannot check if properly formed");
return;
}
MapfileCloseBank(area_Avail(0));
long overlaySize = ftell(overlayFile);
fwrite(mem, 1, MaxAvail[BANK_INDEX_ROM0], f);
free(mem);
if (overlaySize % 0x4000)
errx(1, "Overlay file must have a size multiple of 0x4000");
/* Reset back to beginning */
fseek(overlayFile, 0, SEEK_SET);
uint32_t nbOverlayBanks = overlaySize / 0x4000 - 1;
if (nbOverlayBanks < 1)
errx(1, "Overlay must be at least 0x8000 bytes large");
if (nbOverlayBanks > sections[SECTTYPE_ROMX].nbBanks) {
sections[SECTTYPE_ROMX].banks =
realloc(sections[SECTTYPE_ROMX].banks,
sizeof(*sections[SECTTYPE_ROMX].banks) *
nbOverlayBanks);
if (!sections[SECTTYPE_ROMX].banks)
err(1, "Failed to realloc banks for overlay");
sections[SECTTYPE_ROMX].nbBanks = nbOverlayBanks;
}
}
void writebank(FILE *f, FILE *f_overlay, int32_t bank)
/**
* Write a ROM bank's sections to the output file.
* @param bankSections The bank's sections, ordered by increasing address
* @param baseOffset The address of the bank's first byte in GB address space
* @param size The size of the bank
*/
static void writeBank(struct SortedSection *bankSections, uint16_t baseOffset,
uint16_t size)
{
const struct sSection *pSect;
uint8_t *mem;
uint16_t offset = 0;
mem = malloc(MaxAvail[bank]);
if (!mem)
while (bankSections) {
struct Section const *section = bankSections->section;
/* Output padding up to the next SECTION */
while (offset + baseOffset < section->org) {
putc_unlocked(overlayFile ? getc_unlocked(overlayFile)
: padValue,
outputFile);
offset++;
}
/* Output the section itself */
fwrite(section->data, sizeof(*section->data), section->size,
outputFile);
if (overlayFile) {
/* Skip bytes even with pipes */
for (uint16_t i = 0; i < section->size; i++)
getc_unlocked(overlayFile);
}
offset += section->size;
bankSections = bankSections->next;
}
while (offset < size) {
putc_unlocked(overlayFile ? getc_unlocked(overlayFile)
: padValue,
outputFile);
offset++;
}
}
/**
* Writes a ROM file to the output.
*/
static void writeROM(void)
{
outputFile = openFile(outputFileName, "wb");
overlayFile = openFile(overlayFileName, "rb");
checkOverlay();
if (outputFile) {
flockfile(outputFile);
if (overlayFile)
flockfile(overlayFile);
if (sections[SECTTYPE_ROM0].nbBanks > 0)
writeBank(sections[SECTTYPE_ROM0].banks[0].sections,
0x0000, 0x4000);
for (uint32_t i = 0 ; i < sections[SECTTYPE_ROMX].nbBanks; i++)
writeBank(sections[SECTTYPE_ROMX].banks[i].sections,
0x4000, 0x4000);
if (overlayFile)
funlockfile(overlayFile);
funlockfile(outputFile);
}
closeFile(outputFile);
closeFile(overlayFile);
}
/**
* Get the lowest section by address out of the two
* @param s1 One choice
* @param s2 The other
* @return The lowest section of the two, or the non-NULL one if applicable
*/
static struct SortedSection const **nextSection(struct SortedSection const **s1,
struct SortedSection const **s2)
{
if (!*s1)
return s2;
if (!*s2)
return s1;
return (*s1)->section->org < (*s2)->section->org ? s1 : s2;
}
/**
* Write a bank's contents to the sym file
* @param bankSections The bank's sections
*/
static void writeSymBank(struct SortedSections const *bankSections)
{
if (!symFile)
return;
if (f_overlay != NULL && bank <= MaxOverlayBank) {
fseek(f_overlay, bank * 0x4000, SEEK_SET);
if (fread(mem, 1, MaxAvail[bank], f_overlay) != MaxAvail[bank])
warnx("Failed to read data from overlay file.");
struct {
struct SortedSection const *sections;
#define sect sections->section /* Fake member as a shortcut */
uint32_t i;
struct Symbol const *sym;
uint16_t addr;
} sectList = { .sections = bankSections->sections, .i = 0 },
zlSectList = { .sections = bankSections->zeroLenSections, .i = 0 },
*minSectList;
for (;;) {
while (sectList.sections
&& sectList.i == sectList.sect->nbSymbols) {
sectList.sections = sectList.sections->next;
sectList.i = 0;
}
while (zlSectList.sections
&& zlSectList.i == zlSectList.sect->nbSymbols) {
zlSectList.sections = zlSectList.sections->next;
zlSectList.i = 0;
}
if (!sectList.sections && !zlSectList.sections) {
break;
} else if (sectList.sections && zlSectList.sections) {
sectList.sym = sectList.sect->symbols[sectList.i];
zlSectList.sym = zlSectList.sect->symbols[zlSectList.i];
sectList.addr =
sectList.sym->offset + sectList.sect->org;
zlSectList.addr =
zlSectList.sym->offset + zlSectList.sect->org;
minSectList = sectList.addr < zlSectList.addr
? &sectList
: &zlSectList;
} else if (sectList.sections) {
sectList.sym = sectList.sect->symbols[sectList.i];
sectList.addr =
sectList.sym->offset + sectList.sect->org;
minSectList = &sectList;
} else {
memset(mem, fillchar, MaxAvail[bank]);
}
MapfileInitBank(bank);
zlSectList.sym = zlSectList.sect->symbols[zlSectList.i];
zlSectList.addr =
zlSectList.sym->offset + zlSectList.sect->org;
pSect = pSections;
while (pSect) {
if (pSect->Type == SECT_ROMX && pSect->nBank == bank) {
memcpy(mem + pSect->nOrg - 0x4000, pSect->pData,
pSect->nByteSize);
MapfileWriteSection(pSect);
minSectList = &zlSectList;
}
pSect = pSect->pNext;
fprintf(symFile, "%02x:%04x %s\n",
minSectList->sect->bank, minSectList->addr,
minSectList->sym->name);
minSectList->i++;
}
#undef sect
}
MapfileCloseBank(area_Avail(bank));
fwrite(mem, 1, MaxAvail[bank], f);
free(mem);
}
void out_Setname(char *tzOutputfile)
/**
* Write a bank's contents to the map file
* @param bankSections The bank's sections
*/
static void writeMapBank(struct SortedSections const *sectList,
enum SectionType type, uint32_t bank)
{
tzOutname = tzOutputfile;
if (!mapFile)
return;
struct SortedSection const *section = sectList->sections;
struct SortedSection const *zeroLenSection = sectList->zeroLenSections;
fprintf(mapFile, "%s bank #%u:\n", typeNames[type],
bank + bankranges[type][0]);
uint16_t slack = maxsize[type];
while (section || zeroLenSection) {
struct SortedSection const **pickedSection =
nextSection(&section, &zeroLenSection);
struct Section const *sect = (*pickedSection)->section;
slack -= sect->size;
fprintf(mapFile, " SECTION: $%04x-$%04x ($%04x byte%s) [\"%s\"]\n",
sect->org, sect->org + sect->size - 1, sect->size,
sect->size == 1 ? "" : "s", sect->name);
for (size_t i = 0; i < sect->nbSymbols; i++)
fprintf(mapFile, " $%04x = %s\n",
sect->symbols[i]->offset + sect->org,
sect->symbols[i]->name);
*pickedSection = (*pickedSection)->next;
}
void out_SetOverlayname(char *tzOverlayfile)
if (slack == maxsize[type])
fputs(" EMPTY\n\n", mapFile);
else
fprintf(mapFile, " SLACK: $%04x byte%s\n\n", slack,
slack == 1 ? "" : "s");
}
/**
* Writes the sym and/or map files, if applicable.
*/
static void writeSymAndMap(void)
{
tzOverlayname = tzOverlayfile;
if (!symFileName && !mapFileName)
return;
enum SectionType typeMap[SECTTYPE_INVALID] = {
SECTTYPE_ROM0,
SECTTYPE_ROMX,
SECTTYPE_VRAM,
SECTTYPE_SRAM,
SECTTYPE_WRAM0,
SECTTYPE_WRAMX,
SECTTYPE_OAM,
SECTTYPE_HRAM
};
symFile = openFile(symFileName, "w");
mapFile = openFile(mapFileName, "w");
if (symFileName) {
fputs("; File generated by rgblink\n", symFile);
}
void Output(void)
for (uint8_t i = 0; i < SECTTYPE_INVALID; i++) {
enum SectionType type = typeMap[i];
if (sections[type].nbBanks > 0) {
for (uint32_t bank = 0; bank < sections[type].nbBanks;
bank++) {
writeSymBank(&sections[type].banks[bank]);
writeMapBank(&sections[type].banks[bank],
type, bank);
}
}
}
closeFile(symFile);
closeFile(mapFile);
}
static void cleanupSections(struct SortedSection *section)
{
int32_t i;
FILE *f;
FILE *f_overlay = NULL;
while (section) {
struct SortedSection *next = section->next;
/*
* Load overlay
*/
if (tzOverlayname) {
f_overlay = fopen(tzOverlayname, "rb");
if (!f_overlay) {
errx(1, "Failed to open overlay file %s\n",
tzOverlayname);
}
fseek(f_overlay, 0, SEEK_END);
if (ftell(f_overlay) % 0x4000 != 0)
errx(1, "Overlay file must be aligned to 0x4000 bytes.");
MaxOverlayBank = (ftell(f_overlay) / 0x4000) - 1;
if (MaxOverlayBank < 1)
errx(1, "Overlay file must be at least 0x8000 bytes.");
if (MaxOverlayBank > MaxBankUsed)
MaxBankUsed = MaxOverlayBank;
}
/*
* Write ROM.
*/
f = fopen(tzOutname, "wb");
if (f != NULL) {
writehome(f, f_overlay);
for (i = 1; i <= MaxBankUsed; i += 1)
writebank(f, f_overlay, i);
fclose(f);
}
/*
* Close overlay
*/
if (tzOverlayname)
fclose(f_overlay);
/*
* Add regular sections to map and sym files.
*/
for (i = BANK_INDEX_WRAM0; i < BANK_INDEX_MAX; i++) {
const struct sSection *pSect;
MapfileInitBank(i);
pSect = pSections;
while (pSect) {
if (pSect->nBank == i)
MapfileWriteSection(pSect);
pSect = pSect->pNext;
}
MapfileCloseBank(area_Avail(i));
free(section);
section = next;
}
}
static void cleanup(void)
{
for (enum SectionType type = 0; type < SECTTYPE_INVALID; type++) {
if (sections[type].nbBanks > 0) {
for (uint32_t i = 0; i < sections[type].nbBanks; i++) {
struct SortedSections *bank =
&sections[type].banks[i];
cleanupSections(bank->sections);
cleanupSections(bank->zeroLenSections);
}
free(sections[type].banks);
}
}
}
void out_WriteFiles(void)
{
writeROM();
writeSymAndMap();
cleanup();
}

View File

@@ -1,127 +0,0 @@
/*
* This file is part of RGBDS.
*
* Copyright (c) 2017-2018, Antonio Nino Diaz and RGBDS contributors.
*
* SPDX-License-Identifier: MIT
*/
%{
#include <stdint.h>
#include <stdio.h>
#include "extern/err.h"
#include "link/script.h"
int yylex(void);
void yyerror(char *);
extern int yylineno;
%}
%union {
int32_t i;
char s[512];
}
%token<i> INTEGER
%token<s> STRING
%token<s> SECTION_NONBANKED
%token<s> SECTION_BANKED
%token COMMAND_ALIGN
%token COMMAND_ORG
%token COMMAND_INCLUDE
%token NEWLINE
%start lines
%%
lines:
/* empty */
| lines line NEWLINE
;
line:
/* empty */
| statement
;
statement:
/* Statements to set the current section */
SECTION_NONBANKED
{
script_SetCurrentSectionType($1, 0);
}
| SECTION_NONBANKED INTEGER
{
script_fatalerror("Trying to assign a bank to a non-banked section.\n");
}
| SECTION_BANKED
{
script_fatalerror("Banked section without assigned bank.\n");
}
| SECTION_BANKED INTEGER
{
script_SetCurrentSectionType($1, $2);
}
/* Commands to adjust the address inside the current section */
| COMMAND_ALIGN INTEGER
{
script_SetAlignment($2);
}
| COMMAND_ALIGN
{
script_fatalerror("ALIGN keyword needs an argument.\n");
}
| COMMAND_ORG INTEGER
{
script_SetAddress($2);
}
| COMMAND_ORG
{
script_fatalerror("ORG keyword needs an argument.\n");
}
/* Section name */
| STRING
{
script_OutputSection($1);
}
/* Include file */
| COMMAND_INCLUDE STRING
{
script_IncludeFile($2);
}
/* End */
;
%%
extern int yylex(void);
extern int yyparse(void);
int yywrap(void)
{
if (script_IncludeDepthGet() == 0)
return 1;
script_IncludePop();
return 0;
}
void yyerror(char *s)
{
script_fatalerror("Linkerscript parse error: \"%s\"\n", s);
}

View File

@@ -1,344 +1,343 @@
/*
* This file is part of RGBDS.
*
* Copyright (c) 1997-2018, Carsten Sorensen and RGBDS contributors.
*
* SPDX-License-Identifier: MIT
*/
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <limits.h>
#include <string.h>
#include "link/patch.h"
#include "link/section.h"
#include "link/symbol.h"
#include "linkdefs.h"
#include "extern/err.h"
#include "link/assign.h"
#include "link/main.h"
#include "link/mylink.h"
#include "link/symbol.h"
/* This is an "empty"-type stack */
struct RPNStack {
int32_t *buf;
size_t size;
size_t capacity;
} stack;
#define RPN_STACK_SIZE 256
static struct sSection *pCurrentSection;
static int32_t rpnstack[RPN_STACK_SIZE];
static int32_t rpnp;
int32_t nPC;
static void rpnpush(int32_t i)
static inline void initRPNStack(void)
{
if (rpnp >= RPN_STACK_SIZE)
errx(1, "RPN stack overflow");
rpnstack[rpnp] = i;
rpnp++;
stack.capacity = 64;
stack.buf = malloc(sizeof(*stack.buf) * stack.capacity);
if (!stack.buf)
err(1, "Failed to init RPN stack");
}
static int32_t rpnpop(void)
static inline void clearRPNStack(void)
{
rpnp--;
return rpnstack[rpnp];
stack.size = 0;
}
static int32_t getsymvalue(struct sPatch *pPatch, int32_t symid)
static void pushRPN(int32_t value)
{
const struct sSymbol *tSymbol = pCurrentSection->tSymbols[symid];
switch (tSymbol->Type) {
case SYM_IMPORT:
return sym_GetValue(pPatch, tSymbol->pzName);
case SYM_EXPORT:
case SYM_LOCAL:
if (strcmp(tSymbol->pzName, "@") == 0)
return nPC;
return tSymbol->nOffset + tSymbol->pSection->nOrg;
default:
break;
if (stack.size >= stack.capacity) {
stack.capacity *= 2;
stack.buf =
realloc(stack.buf, sizeof(*stack.buf) * stack.capacity);
if (!stack.buf)
err(1, "Failed to resize RPN stack");
}
errx(1, "%s: Unknown symbol type", __func__);
stack.buf[stack.size] = value;
stack.size++;
}
static int32_t getrealbankfrominternalbank(int32_t n)
static int32_t popRPN(void)
{
if (BankIndexIsWRAM0(n) || BankIndexIsROM0(n) ||
BankIndexIsOAM(n) || BankIndexIsHRAM(n)) {
return 0;
} else if (BankIndexIsROMX(n)) {
return n - BANK_INDEX_ROMX + 1;
} else if (BankIndexIsWRAMX(n)) {
return n - BANK_INDEX_WRAMX + 1;
} else if (BankIndexIsVRAM(n)) {
return n - BANK_INDEX_VRAM;
} else if (BankIndexIsSRAM(n)) {
return n - BANK_INDEX_SRAM;
if (stack.size == 0)
errx(1, "Internal error, RPN stack empty");
stack.size--;
return stack.buf[stack.size];
}
return n;
}
static int32_t getsymbank(struct sPatch *pPatch, int32_t symid)
static inline void freeRPNStack(void)
{
int32_t nBank;
const struct sSymbol *tSymbol = pCurrentSection->tSymbols[symid];
switch (tSymbol->Type) {
case SYM_IMPORT:
nBank = sym_GetBank(pPatch, tSymbol->pzName);
break;
case SYM_EXPORT:
case SYM_LOCAL:
nBank = tSymbol->pSection->nBank;
break;
default:
errx(1, "%s: Unknown symbol type", __func__);
free(stack.buf);
}
return getrealbankfrominternalbank(nBank);
}
/* RPN operators */
int32_t calcrpn(struct sPatch *pPatch)
static uint8_t getRPNByte(uint8_t const **expression, int32_t *size,
char const *fileName, int32_t lineNo)
{
int32_t t, size;
uint8_t *rpn;
uint8_t rpn_cmd;
int32_t nBank;
if (!(*size)--)
errx(1, "%s(%d): RPN expression overread", fileName, lineNo);
return *(*expression)++;
}
rpnp = 0;
/**
* Compute a patch's value from its RPN string.
* @param patch The patch to compute the value of
* @param section The section the patch is contained in
* @return The patch's value
*/
static int32_t computeRPNExpr(struct Patch const *patch,
struct Section const *section)
{
uint8_t const *expression = patch->rpnExpression;
int32_t size = patch->rpnSize;
size = pPatch->nRPNSize;
rpn = pPatch->pRPN;
pPatch->oRelocPatch = 0;
clearRPNStack();
while (size > 0) {
size -= 1;
rpn_cmd = *rpn++;
enum RPNCommand command = getRPNByte(&expression, &size,
patch->fileName,
patch->lineNo);
int32_t value;
/*
* Friendly reminder:
* Be VERY careful with two `popRPN` in the same expression.
* C does not guarantee order of evaluation of operands!!
* So, if there are two `popRPN` in the same expression, make
* sure the operation is commutative.
*/
switch (command) {
struct Symbol const *symbol;
char const *name;
struct Section const *sect;
switch (rpn_cmd) {
case RPN_ADD:
rpnpush(rpnpop() + rpnpop());
value = popRPN() + popRPN();
break;
case RPN_SUB:
t = rpnpop();
rpnpush(rpnpop() - t);
value = popRPN();
value = popRPN() - value;
break;
case RPN_MUL:
rpnpush(rpnpop() * rpnpop());
value = popRPN() * popRPN();
break;
case RPN_DIV:
t = rpnpop();
rpnpush(rpnpop() / t);
value = popRPN();
value = popRPN() / value;
break;
case RPN_MOD:
t = rpnpop();
rpnpush(rpnpop() % t);
value = popRPN();
value = popRPN() % value;
break;
case RPN_UNSUB:
rpnpush(-rpnpop());
value = -popRPN();
break;
case RPN_OR:
rpnpush(rpnpop() | rpnpop());
value = popRPN() | popRPN();
break;
case RPN_AND:
rpnpush(rpnpop() & rpnpop());
value = popRPN() & popRPN();
break;
case RPN_XOR:
rpnpush(rpnpop() ^ rpnpop());
value = popRPN() ^ popRPN();
break;
case RPN_UNNOT:
rpnpush(~rpnpop());
value = ~popRPN();
break;
case RPN_LOGAND:
rpnpush(rpnpop() && rpnpop());
value = popRPN();
value = popRPN() && value;
break;
case RPN_LOGOR:
rpnpush(rpnpop() || rpnpop());
value = popRPN();
value = popRPN() || value;
break;
case RPN_LOGUNNOT:
rpnpush(!rpnpop());
value = !popRPN();
break;
case RPN_LOGEQ:
rpnpush(rpnpop() == rpnpop());
value = popRPN() == popRPN();
break;
case RPN_LOGNE:
rpnpush(rpnpop() != rpnpop());
value = popRPN() != popRPN();
break;
case RPN_LOGGT:
t = rpnpop();
rpnpush(rpnpop() > t);
value = popRPN();
value = popRPN() > value;
break;
case RPN_LOGLT:
t = rpnpop();
rpnpush(rpnpop() < t);
value = popRPN();
value = popRPN() < value;
break;
case RPN_LOGGE:
t = rpnpop();
rpnpush(rpnpop() >= t);
value = popRPN();
value = popRPN() >= value;
break;
case RPN_LOGLE:
t = rpnpop();
rpnpush(rpnpop() <= t);
value = popRPN();
value = popRPN() <= value;
break;
/* FIXME: sanitize shifts */
case RPN_SHL:
t = rpnpop();
rpnpush(rpnpop() << t);
value = popRPN();
value = popRPN() << value;
break;
case RPN_SHR:
t = rpnpop();
rpnpush(rpnpop() >> t);
break;
case RPN_HRAM:
t = rpnpop();
rpnpush(t & 0xFF);
if (t < 0 || (t > 0xFF && t < 0xFF00) || t > 0xFFFF) {
errx(1,
"%s(%ld) : Value must be in the HRAM area",
pPatch->pzFilename, pPatch->nLineNo);
}
break;
case RPN_CONST:
/* constant */
t = (*rpn++);
t |= (*rpn++) << 8;
t |= (*rpn++) << 16;
t |= (*rpn++) << 24;
rpnpush(t);
size -= 4;
break;
case RPN_SYM:
/* symbol */
t = (*rpn++);
t |= (*rpn++) << 8;
t |= (*rpn++) << 16;
t |= (*rpn++) << 24;
rpnpush(getsymvalue(pPatch, t));
pPatch->oRelocPatch |= (getsymbank(pPatch, t) != -1);
size -= 4;
value = popRPN();
value = popRPN() >> value;
break;
case RPN_BANK_SYM:
/* symbol */
t = (*rpn++);
t |= (*rpn++) << 8;
t |= (*rpn++) << 16;
t |= (*rpn++) << 24;
rpnpush(getsymbank(pPatch, t));
size -= 4;
value = 0;
for (uint8_t shift = 0; shift < 32; shift += 8)
value |= getRPNByte(&expression, &size,
patch->fileName,
patch->lineNo) << shift;
symbol = section->fileSymbols[value];
/* If the symbol is defined elsewhere... */
if (symbol->type == SYMTYPE_IMPORT) {
struct Symbol const *symbolDefinition =
sym_GetSymbol(symbol->name);
if (!symbolDefinition)
errx(1, "%s(%d): Unknown symbol \"%s\"",
patch->fileName, patch->lineNo,
symbol->name);
symbol = symbolDefinition;
}
value = symbol->section->bank;
break;
case RPN_BANK_SECT:
{
char *name = (char *)rpn;
name = (char const *)expression;
while (getRPNByte(&expression, &size, patch->fileName,
patch->lineNo))
;
struct sSection *pSection = GetSectionByName(name);
sect = sect_GetSection(name);
if (pSection == NULL) {
errx(1,
"%s(%ld) : Requested BANK() of section \"%s\", which was not found.\n",
pPatch->pzFilename, pPatch->nLineNo,
name);
}
if (!sect)
errx(1, "%s(%d): Requested BANK() of section \"%s\", which was not found",
patch->fileName, patch->lineNo, name);
nBank = pSection->nBank;
rpnpush(getrealbankfrominternalbank(nBank));
int len = strlen(name);
size -= len + 1;
rpn += len + 1;
value = sect->bank;
break;
}
case RPN_BANK_SELF:
nBank = pCurrentSection->nBank;
rpnpush(getrealbankfrominternalbank(nBank));
value = section->bank;
break;
default:
errx(1, "%s: Invalid command %d\n", __func__,
rpn_cmd);
case RPN_HRAM:
value = popRPN();
if (value < 0
|| (value > 0xFF && value < 0xFF00)
|| value > 0xFFFF)
errx(1, "%s(%d): Value %d is not in HRAM range",
patch->fileName, patch->lineNo, value);
value &= 0xFF;
break;
}
}
return rpnpop();
case RPN_CONST:
value = 0;
for (uint8_t shift = 0; shift < 32; shift += 8)
value |= getRPNByte(&expression, &size,
patch->fileName,
patch->lineNo) << shift;
break;
case RPN_SYM:
value = 0;
for (uint8_t shift = 0; shift < 32; shift += 8)
value |= getRPNByte(&expression, &size,
patch->fileName,
patch->lineNo) << shift;
symbol = section->fileSymbols[value];
/* If the symbol is defined elsewhere... */
if (symbol->type == SYMTYPE_IMPORT) {
struct Symbol const *symbolDefinition =
sym_GetSymbol(symbol->name);
if (!symbolDefinition)
errx(1, "%s(%d): Unknown symbol \"%s\"",
patch->fileName, patch->lineNo,
symbol->name);
symbol = symbolDefinition;
}
void Patch(void)
if (!strcmp(symbol->name, "@")) {
value = section->org + patch->offset;
} else {
value = symbol->value;
/* Symbols attached to sections have offsets */
if (symbol->section)
value += symbol->section->org;
}
break;
}
pushRPN(value);
}
if (stack.size > 1)
warnx("%s(%d): RPN stack has %lu entries on exit, not 1",
patch->fileName, patch->lineNo, stack.size);
return popRPN();
}
/**
* Applies all of a section's patches
* @param section The section to patch
* @param arg Ignored callback arg
*/
static void applyPatches(struct Section *section, void *arg)
{
struct sSection *pSect;
(void)arg;
pSect = pSections;
while (pSect) {
struct sPatch *pPatch;
if (!sect_HasData(section->type))
return;
pCurrentSection = pSect;
pPatch = pSect->pPatches;
while (pPatch) {
int32_t t;
int32_t nPatchOrg;
verbosePrint("Patching section \"%s\"...\n", section->name);
for (uint32_t patchID = 0; patchID < section->nbPatches; patchID++) {
struct Patch *patch = &section->patches[patchID];
int32_t value = computeRPNExpr(patch, section);
nPC = pSect->nOrg + pPatch->nOffset;
t = calcrpn(pPatch);
switch (pPatch->Type) {
case PATCH_BYTE:
if (t >= -128 && t <= 255) {
t &= 0xFF;
pSect->pData[pPatch->nOffset] =
(uint8_t)t;
if (patch->type == PATCHTYPE_JR) {
/* `jr` is quite unlike the others... */
uint16_t address = section->org + patch->offset;
/* Target is relative to the byte *after* the operand */
int32_t offset = value - (address + 1);
if (offset < -128 || offset > 127)
errx(1, "%s(%d): jr target out of reach (%d)",
patch->fileName, patch->lineNo, offset);
section->data[patch->offset] = offset & 0xFF;
} else {
errx(1,
"%s(%ld) : Value must be 8-bit",
pPatch->pzFilename,
pPatch->nLineNo);
}
break;
case PATCH_WORD_L:
if (t >= -32768 && t <= 65535) {
t &= 0xFFFF;
pSect->pData[pPatch->nOffset] =
t & 0xFF;
pSect->pData[pPatch->nOffset + 1] =
(t >> 8) & 0xFF;
} else {
errx(1,
"%s(%ld) : Value must be 16-bit",
pPatch->pzFilename,
pPatch->nLineNo);
}
break;
case PATCH_LONG_L:
pSect->pData[pPatch->nOffset + 0] = t & 0xFF;
pSect->pData[pPatch->nOffset + 1] =
(t >> 8) & 0xFF;
pSect->pData[pPatch->nOffset + 2] =
(t >> 16) & 0xFF;
pSect->pData[pPatch->nOffset + 3] =
(t >> 24) & 0xFF;
break;
case PATCH_BYTE_JR:
/* Calculate absolute address of the patch */
nPatchOrg = pSect->nOrg + pPatch->nOffset;
/* Patch a certain number of bytes */
struct {
uint8_t size;
int32_t min;
int32_t max;
} const types[] = {
[PATCHTYPE_BYTE] = {1, -128, 255},
[PATCHTYPE_WORD] = {2, -32768, 65536},
[PATCHTYPE_LONG] = {4, INT32_MIN, INT32_MAX}
};
/* t contains the destination of the jump */
t = (int16_t)((t & 0xFFFF) - (nPatchOrg + 1));
if (t >= -128 && t <= 127) {
t &= 0xFF;
pSect->pData[pPatch->nOffset] =
(uint8_t)t;
} else {
errx(1,
"%s(%ld) : Value must be 8-bit",
pPatch->pzFilename,
pPatch->nLineNo);
if (value < types[patch->type].min
|| value > types[patch->type].max)
errx(1, "%s(%d): Value %#x%s is not %u-bit",
patch->fileName, patch->lineNo, value,
value < 0 ? " (maybe negative?)" : "",
types[patch->type].size * 8);
for (uint8_t i = 0; i < types[patch->type].size; i++) {
section->data[patch->offset + i] = value & 0xFF;
value >>= 8;
}
}
}
break;
default:
errx(1, "%s: Internal error.", __func__);
}
pPatch = pPatch->pNext;
}
pSect = pSect->pNext;
}
void patch_ApplyPatches(void)
{
initRPNStack();
sect_ForEach(applyPatches, NULL);
freeRPNStack();
}

View File

@@ -1,237 +1,516 @@
/*
* This file is part of RGBDS.
*
* Copyright (c) 2017-2018, Antonio Nino Diaz and RGBDS contributors.
*
* SPDX-License-Identifier: MIT
*/
#include <stdlib.h>
#include <stdbool.h>
#include <stdint.h>
#include <string.h>
#include <ctype.h>
#include "link/main.h"
#include "link/script.h"
#include "link/section.h"
#include "extern/err.h"
#include "link/assign.h"
#include "link/mylink.h"
FILE *linkerScript;
static uint32_t lineNo;
static struct {
uint32_t address; /* current address to write sections to */
uint32_t top_address; /* not inclusive */
enum eSectionType type;
} bank[BANK_INDEX_MAX];
FILE *file;
uint32_t lineNo;
char const *name;
} *fileStack;
static int32_t current_bank = -1; /* Bank as seen by the bank array */
static int32_t current_real_bank = -1; /* bank as seen by the GB */
static uint32_t fileStackSize;
static uint32_t fileStackIndex;
/* Current section attributes */
static int32_t fix_org = -1;
static int32_t fix_align = 1;
void script_InitSections(void)
static void pushFile(char const *newFileName)
{
int32_t i;
if (fileStackIndex == UINT32_MAX)
errx(1, "%s(%u): INCLUDE recursion limit reached",
linkerScriptName, lineNo);
for (i = 0; i < BANK_INDEX_MAX; i++) {
if (BankIndexIsROM0(i)) {
/* ROM0 bank */
bank[i].address = 0x0000;
if (options & OPT_TINY)
bank[i].top_address = 0x8000;
else
bank[i].top_address = 0x4000;
bank[i].type = SECT_ROM0;
} else if (BankIndexIsROMX(i)) {
/* Swappable ROM bank */
bank[i].address = 0x4000;
bank[i].top_address = 0x8000;
bank[i].type = SECT_ROMX;
} else if (BankIndexIsWRAM0(i)) {
/* WRAM */
bank[i].address = 0xC000;
if (options & OPT_CONTWRAM)
bank[i].top_address = 0xE000;
else
bank[i].top_address = 0xD000;
bank[i].type = SECT_WRAM0;
} else if (BankIndexIsSRAM(i)) {
/* Swappable SRAM bank */
bank[i].address = 0xA000;
bank[i].top_address = 0xC000;
bank[i].type = SECT_SRAM;
} else if (BankIndexIsWRAMX(i)) {
/* Swappable WRAM bank */
bank[i].address = 0xD000;
bank[i].top_address = 0xE000;
bank[i].type = SECT_WRAMX;
} else if (BankIndexIsVRAM(i)) {
/* Swappable VRAM bank */
bank[i].address = 0x8000;
bank[i].type = SECT_VRAM;
if (options & OPT_DMG_MODE && i != BANK_INDEX_VRAM) {
/* In DMG the only available bank is bank 0. */
bank[i].top_address = 0x8000;
} else {
bank[i].top_address = 0xA000;
}
} else if (BankIndexIsOAM(i)) {
/* OAM */
bank[i].address = 0xFE00;
bank[i].top_address = 0xFEA0;
bank[i].type = SECT_OAM;
} else if (BankIndexIsHRAM(i)) {
/* HRAM */
bank[i].address = 0xFF80;
bank[i].top_address = 0xFFFF;
bank[i].type = SECT_HRAM;
} else {
errx(1, "%s: Unknown bank type %d", __func__, i);
}
}
if (fileStackIndex == fileStackSize) {
if (!fileStackSize) /* Init file stack */
fileStackSize = 4;
fileStackSize *= 2;
fileStack = realloc(fileStack,
sizeof(*fileStack) * fileStackSize);
if (!fileStack)
err(1, "%s(%u): Internal INCLUDE error",
linkerScriptName, lineNo);
}
void script_SetCurrentSectionType(const char *type, uint32_t bank_num)
fileStack[fileStackIndex].file = linkerScript;
fileStack[fileStackIndex].lineNo = lineNo;
fileStack[fileStackIndex].name = linkerScriptName;
fileStackIndex++;
linkerScript = fopen(newFileName, "r");
if (!linkerScript)
err(1, "%s(%u): Could not open \"%s\"",
linkerScriptName, lineNo, newFileName);
lineNo = 1;
linkerScriptName = newFileName;
}
static bool popFile(void)
{
if (strcmp(type, "ROM0") == 0) {
if (bank_num != 0)
errx(1, "Trying to assign a bank number to ROM0.\n");
current_bank = BANK_INDEX_ROM0;
current_real_bank = 0;
return;
} else if (strcmp(type, "ROMX") == 0) {
if (bank_num == 0)
errx(1, "ROMX index can't be 0.\n");
if (bank_num > BANK_COUNT_ROMX) {
errx(1, "ROMX index too big (%d > %d).\n", bank_num,
BANK_COUNT_ROMX);
}
current_bank = BANK_INDEX_ROMX + bank_num - 1;
current_real_bank = bank_num;
return;
} else if (strcmp(type, "VRAM") == 0) {
if (bank_num >= BANK_COUNT_VRAM) {
errx(1, "VRAM index too big (%d >= %d).\n", bank_num,
BANK_COUNT_VRAM);
}
current_bank = BANK_INDEX_VRAM + bank_num;
current_real_bank = bank_num;
return;
} else if (strcmp(type, "WRAM0") == 0) {
if (bank_num != 0)
errx(1, "Trying to assign a bank number to WRAM0.\n");
if (!fileStackIndex)
return false;
current_bank = BANK_INDEX_WRAM0;
current_real_bank = 0;
return;
} else if (strcmp(type, "WRAMX") == 0) {
if (bank_num == 0)
errx(1, "WRAMX index can't be 0.\n");
if (bank_num > BANK_COUNT_WRAMX) {
errx(1, "WRAMX index too big (%d > %d).\n", bank_num,
BANK_COUNT_WRAMX);
fileStackIndex--;
linkerScript = fileStack[fileStackIndex].file;
lineNo = fileStack[fileStackIndex].lineNo;
linkerScriptName = fileStack[fileStackIndex].name;
return true;
}
current_bank = BANK_INDEX_WRAMX + bank_num - 1;
current_real_bank = bank_num;
return;
} else if (strcmp(type, "SRAM") == 0) {
if (bank_num >= BANK_COUNT_SRAM) {
errx(1, "SRAM index too big (%d >= %d).\n", bank_num,
BANK_COUNT_SRAM);
static inline bool isWhiteSpace(int c)
{
return c == ' ' || c == '\t';
}
current_bank = BANK_INDEX_SRAM + bank_num;
current_real_bank = bank_num;
return;
} else if (strcmp(type, "OAM") == 0) {
if (bank_num != 0) {
errx(1, "%s: Trying to assign a bank number to OAM.\n",
static inline bool isNewline(int c)
{
return c == '\r' || c == '\n';
}
/**
* Try parsing a number, in base 16 if it begins with a dollar,
* in base 10 otherwise
* @param str The number to parse
* @param number A pointer where the number will be written to
* @return True if parsing was successful, false otherwise
*/
static bool tryParseNumber(char const *str, uint32_t *number)
{
static char const digits[] = {
'0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
'A', 'B', 'C', 'D', 'E', 'F'
};
uint8_t base = 10;
if (*str == '$') {
str++;
base = 16;
}
/* An empty string is not a number */
if (!*str)
return false;
*number = 0;
do {
char chr = toupper(*str++);
uint8_t digit = 0;
while (digit < base) {
if (chr == digits[digit])
break;
digit++;
}
if (digit == base)
return false;
*number = *number * base + digit;
} while (*str);
return true;
}
enum LinkerScriptTokenType {
TOKEN_NEWLINE,
TOKEN_COMMAND,
TOKEN_BANK,
TOKEN_INCLUDE,
TOKEN_NUMBER,
TOKEN_STRING,
TOKEN_EOF,
TOKEN_INVALID
};
char const *tokenTypes[] = {
[TOKEN_NEWLINE] = "newline",
[TOKEN_COMMAND] = "command",
[TOKEN_BANK] = "bank command",
[TOKEN_NUMBER] = "number",
[TOKEN_STRING] = "string",
[TOKEN_EOF] = "end of file"
};
enum LinkerScriptCommand {
COMMAND_ORG,
COMMAND_ALIGN,
COMMAND_INVALID
};
struct LinkerScriptToken {
enum LinkerScriptTokenType type;
union LinkerScriptTokenAttr {
enum LinkerScriptCommand command;
enum SectionType secttype;
uint32_t number;
char *string;
} attr;
};
static char const * const commands[] = {
[COMMAND_ORG] = "ORG",
[COMMAND_ALIGN] = "ALIGN"
};
static int readChar(FILE *file)
{
int curchar = getc_unlocked(file);
if (curchar == EOF && ferror(file))
err(1, "%s(%u): Unexpected error in %s", linkerScriptName,
lineNo, __func__);
return curchar;
}
static struct LinkerScriptToken const *nextToken(void)
{
static struct LinkerScriptToken token;
int curchar;
/* If the token has a string, make sure to avoid leaking it */
if (token.type == TOKEN_STRING)
free(token.attr.string);
/* Skip initial whitespace... */
do
curchar = readChar(linkerScript);
while (isWhiteSpace(curchar));
/* If this is a comment, skip to the end of the line */
if (curchar == ';') {
do
curchar = readChar(linkerScript);
while (!isNewline(curchar) && curchar != EOF);
}
if (curchar == EOF) {
token.type = TOKEN_EOF;
} else if (isNewline(curchar)) {
/* If we have a newline char, this is a newline token */
token.type = TOKEN_NEWLINE;
/* FIXME: This works with CRLF newlines, but not CR-only */
if (curchar == '\r')
readChar(linkerScript); /* Read and discard LF */
} else if (curchar == '"') {
/* If we have a string start, this is a string */
token.type = TOKEN_STRING;
token.attr.string = NULL; /* Force initial alloc */
size_t size = 0;
size_t capacity = 16; /* Half of the default capacity */
do {
curchar = readChar(linkerScript);
if (curchar == EOF || isNewline(curchar))
errx(1, "%s(%u): Unterminated string",
linkerScriptName, lineNo);
else if (curchar == '"')
/* Quotes force a string termination */
curchar = '\0';
if (size >= capacity || token.attr.string == NULL) {
capacity *= 2;
token.attr.string = realloc(token.attr.string,
capacity);
if (!token.attr.string)
err(1, "%s: Failed to allocate memory for string",
__func__);
}
current_bank = BANK_INDEX_OAM;
current_real_bank = 0;
return;
} else if (strcmp(type, "HRAM") == 0) {
if (bank_num != 0) {
errx(1, "%s: Trying to assign a bank number to HRAM.\n",
token.attr.string[size++] = curchar;
} while (curchar);
} else {
/* This is either a number, command or bank, that is: a word */
char *str = NULL;
size_t size = 0;
size_t capacity = 8; /* Half of the default capacity */
for (;;) {
if (size >= capacity || str == NULL) {
capacity *= 2;
str = realloc(str, capacity);
if (!str)
err(1, "%s: Failed to allocate memory for token",
__func__);
}
current_bank = BANK_INDEX_HRAM;
current_real_bank = 0;
return;
str[size] = toupper(curchar);
size++;
if (!curchar)
break;
curchar = readChar(linkerScript);
/* Whitespace, a newline or a comment end the token */
if (isWhiteSpace(curchar) || isNewline(curchar)
|| curchar == ';') {
ungetc(curchar, linkerScript);
curchar = '\0';
}
}
errx(1, "%s: Unknown section type \"%s\".\n", __func__, type);
token.type = TOKEN_INVALID;
/* Try to match a command */
for (enum LinkerScriptCommand i = 0; i < COMMAND_INVALID; i++) {
if (!strcmp(commands[i], str)) {
token.type = TOKEN_COMMAND;
token.attr.command = i;
break;
}
}
void script_SetAddress(uint32_t addr)
if (token.type == TOKEN_INVALID) {
/* Try to match a bank specifier */
for (enum SectionType type = 0; type < SECTTYPE_INVALID;
type++) {
if (!strcmp(typeNames[type], str)) {
token.type = TOKEN_BANK;
token.attr.secttype = type;
break;
}
}
}
if (token.type == TOKEN_INVALID) {
/* Try to match an include token */
if (!strcmp("INCLUDE", str))
token.type = TOKEN_INCLUDE;
}
if (token.type == TOKEN_INVALID) {
/* None of the strings matched, do we have a number? */
if (tryParseNumber(str, &token.attr.number))
token.type = TOKEN_NUMBER;
else
errx(1, "%s(%u): Unknown token \"%s\"",
linkerScriptName, lineNo, str);
}
free(str);
}
return &token;
}
static void processCommand(enum LinkerScriptCommand command, uint16_t arg,
uint16_t *pc)
{
if (current_bank == -1)
errx(1, "Trying to set an address without assigned bank\n");
switch (command) {
case COMMAND_INVALID:
trap_;
/* Make sure that we don't go back. */
if (bank[current_bank].address > addr) {
errx(1, "Trying to go to a previous address (0x%04X to 0x%04X)\n",
bank[current_bank].address, addr);
case COMMAND_ORG:
*pc = arg;
break;
case COMMAND_ALIGN:
if (arg >= 16)
arg = 0;
else
arg = (*pc + (1 << arg) - 1) & ~((1 << arg) - 1);
}
bank[current_bank].address = addr;
/* Make sure we don't overflow */
if (bank[current_bank].address >= bank[current_bank].top_address) {
errx(1, "Bank overflowed (0x%04X >= 0x%04X)\n",
bank[current_bank].address,
bank[current_bank].top_address);
if (arg < *pc)
errx(1, "%s(%u): `%s` cannot be used to go backwards",
linkerScriptName, lineNo, commands[command]);
*pc = arg;
}
fix_org = addr;
}
enum LinkerScriptParserState {
PARSER_FIRSTTIME,
PARSER_LINESTART,
PARSER_INCLUDE, /* After an INCLUDE token */
PARSER_LINEEND
};
void script_SetAlignment(uint32_t alignment)
/* Part of internal state, but has data that needs to be freed */
static uint16_t *curaddr[SECTTYPE_INVALID];
/* Put as global to ensure it's initialized only once */
static enum LinkerScriptParserState parserState = PARSER_FIRSTTIME;
struct SectionPlacement *script_NextSection(void)
{
if (current_bank == -1)
errx(1, "Trying to set an alignment without assigned bank\n");
static struct SectionPlacement section;
static enum SectionType type;
static uint32_t bank;
static uint32_t bankID;
if (alignment > 15)
errx(1, "Trying to set an alignment too big: %d\n", alignment);
if (parserState == PARSER_FIRSTTIME) {
lineNo = 1;
uint32_t size = 1 << alignment;
uint32_t mask = size - 1;
if (bank[current_bank].address & mask) {
bank[current_bank].address &= ~mask;
bank[current_bank].address += size;
/* Init PC for all banks */
for (enum SectionType i = 0; i < SECTTYPE_INVALID; i++) {
curaddr[i] = malloc(sizeof(*curaddr[i]) * nbbanks(i));
for (uint32_t b = 0; b < nbbanks(i); b++)
curaddr[i][b] = startaddr[i];
}
/* Make sure we don't overflow */
if (bank[current_bank].address >= bank[current_bank].top_address) {
errx(1, "Bank overflowed (0x%04X >= 0x%04X)\n",
bank[current_bank].address,
bank[current_bank].top_address);
type = SECTTYPE_INVALID;
parserState = PARSER_LINESTART;
}
fix_align = size;
for (;;) {
struct LinkerScriptToken const *token = nextToken();
enum LinkerScriptTokenType tokType;
union LinkerScriptTokenAttr attr;
bool hasArg;
uint32_t arg;
if (type != SECTTYPE_INVALID) {
if (curaddr[type][bankID] > endaddr(type) + 1)
errx(1, "%s(%u): Sections would extend past the end of %s ($%04hx > $%04hx)",
linkerScriptName, lineNo, typeNames[type],
curaddr[type][bankID], endaddr(type));
if (curaddr[type][bankID] < startaddr[type])
errx(1, "%s(%u): PC underflowed ($%04hx < $%04hx)",
linkerScriptName, lineNo,
curaddr[type][bankID], startaddr[type]);
}
void script_OutputSection(const char *section_name)
switch (parserState) {
case PARSER_FIRSTTIME:
trap_;
case PARSER_LINESTART:
switch (token->type) {
case TOKEN_INVALID:
trap_;
case TOKEN_EOF:
if (!popFile())
return NULL;
parserState = PARSER_LINEEND;
break;
case TOKEN_NUMBER:
errx(1, "%s(%u): stray number \"%u\"",
linkerScriptName, lineNo,
token->attr.number);
case TOKEN_NEWLINE:
lineNo++;
break;
/* A stray string is a section name */
case TOKEN_STRING:
parserState = PARSER_LINEEND;
if (type == SECTTYPE_INVALID)
errx(1, "%s(%u): Didn't specify a location before the section",
linkerScriptName, lineNo);
section.section =
sect_GetSection(token->attr.string);
section.org = curaddr[type][bankID];
section.bank = bank;
curaddr[type][bankID] += section.section->size;
return &section;
case TOKEN_COMMAND:
case TOKEN_BANK:
tokType = token->type;
attr = token->attr;
token = nextToken();
hasArg = token->type == TOKEN_NUMBER;
/*
* Leaving `arg` uninitialized when `!hasArg`
* causes GCC to warn about its use as an
* argument to `processCommand`. This cannot
* happen because `hasArg` has to be true, but
* silence the warning anyways.
* I dislike doing this because it could swallow
* actual errors, but I don't have a choice.
*/
arg = hasArg ? token->attr.number : 0;
if (tokType == TOKEN_COMMAND) {
if (type == SECTTYPE_INVALID)
errx(1, "%s(%u): Didn't specify a location before the command",
linkerScriptName, lineNo);
if (!hasArg)
errx(1, "%s(%u): Command specified without an argument",
linkerScriptName, lineNo);
processCommand(attr.command, arg,
&curaddr[type][bankID]);
} else { /* TOKEN_BANK */
type = attr.secttype;
/*
* If there's only one bank,
* specifying the number is optional.
*/
if (!hasArg && nbbanks(type) != 1)
errx(1, "%s(%u): Didn't specify a bank number",
linkerScriptName, lineNo);
else if (!hasArg)
arg = bankranges[type][0];
else if (arg < bankranges[type][0])
errx(1, "%s(%u): specified bank number is too low (%u < %u)",
linkerScriptName, lineNo,
arg, bankranges[type][0]);
else if (arg > bankranges[type][1])
errx(1, "%s(%u): specified bank number is too high (%u > %u)",
linkerScriptName, lineNo,
arg, bankranges[type][1]);
bank = arg;
bankID = arg - bankranges[type][0];
}
/* If we read a token we shouldn't have... */
if (token->type != TOKEN_NUMBER)
goto lineend;
break;
case TOKEN_INCLUDE:
parserState = PARSER_INCLUDE;
break;
}
break;
case PARSER_INCLUDE:
if (token->type != TOKEN_STRING)
errx(1, "%s(%u): Expected a file name after INCLUDE",
linkerScriptName, lineNo);
/* Switch to that file */
pushFile(token->attr.string);
parserState = PARSER_LINESTART;
break;
case PARSER_LINEEND:
lineend:
lineNo++;
parserState = PARSER_LINESTART;
if (token->type == TOKEN_EOF) {
if (!popFile())
return NULL;
parserState = PARSER_LINEEND;
} else if (token->type != TOKEN_NEWLINE)
errx(1, "%s(%u): Unexpected %s at the end of the line",
linkerScriptName, lineNo,
tokenTypes[token->type]);
break;
}
}
}
void script_Cleanup(void)
{
if (current_bank == -1) {
errx(1, "Trying to place section \"%s\" without assigned bank\n",
section_name);
}
if (!IsSectionSameTypeBankAndAttrs(section_name,
bank[current_bank].type,
current_real_bank,
fix_org,
fix_align)) {
errx(1, "Different attributes for \"%s\" in source and linkerscript\n",
section_name);
}
/* Move section to its place. */
bank[current_bank].address +=
AssignSectionAddressAndBankByName(section_name,
bank[current_bank].address,
current_real_bank);
fix_org = -1;
fix_align = 1;
for (enum SectionType type = 0; type < SECTTYPE_INVALID; type++)
free(curaddr[type]);
}

168
src/link/section.c Normal file
View File

@@ -0,0 +1,168 @@
#include <stdbool.h>
#include "link/main.h"
#include "link/section.h"
#include "extern/err.h"
#include "hashmap.h"
#include "common.h"
uint16_t startaddr[] = {
[SECTTYPE_ROM0] = 0x0000,
[SECTTYPE_ROMX] = 0x4000,
[SECTTYPE_VRAM] = 0x8000,
[SECTTYPE_SRAM] = 0xA000,
[SECTTYPE_WRAM0] = 0xC000,
[SECTTYPE_WRAMX] = 0xD000,
[SECTTYPE_OAM] = 0xFE00,
[SECTTYPE_HRAM] = 0xFF80
};
uint16_t maxsize[] = {
[SECTTYPE_ROM0] = 0x4000,
[SECTTYPE_ROMX] = 0x4000,
[SECTTYPE_VRAM] = 0x2000,
[SECTTYPE_SRAM] = 0x2000,
[SECTTYPE_WRAM0] = 0x1000,
[SECTTYPE_WRAMX] = 0x1000,
[SECTTYPE_OAM] = 0x00A0,
[SECTTYPE_HRAM] = 0x007F
};
uint32_t bankranges[][2] = {
[SECTTYPE_ROM0] = {BANK_MIN_ROM0, BANK_MAX_ROM0},
[SECTTYPE_ROMX] = {BANK_MIN_ROMX, BANK_MAX_ROMX},
[SECTTYPE_VRAM] = {BANK_MIN_VRAM, BANK_MAX_VRAM},
[SECTTYPE_SRAM] = {BANK_MIN_SRAM, BANK_MAX_SRAM},
[SECTTYPE_WRAM0] = {BANK_MIN_WRAM0, BANK_MAX_WRAM0},
[SECTTYPE_WRAMX] = {BANK_MIN_WRAMX, BANK_MAX_WRAMX},
[SECTTYPE_OAM] = {BANK_MIN_OAM, BANK_MAX_OAM},
[SECTTYPE_HRAM] = {BANK_MIN_HRAM, BANK_MAX_HRAM}
};
char const * const typeNames[] = {
[SECTTYPE_ROM0] = "ROM0",
[SECTTYPE_ROMX] = "ROMX",
[SECTTYPE_VRAM] = "VRAM",
[SECTTYPE_SRAM] = "SRAM",
[SECTTYPE_WRAM0] = "WRAM0",
[SECTTYPE_WRAMX] = "WRAMX",
[SECTTYPE_OAM] = "OAM",
[SECTTYPE_HRAM] = "HRAM"
};
HashMap sections;
struct ForEachArg {
void (*callback)(struct Section *section, void *arg);
void *arg;
};
static void forEach(void *section, void *arg)
{
struct ForEachArg *callbackArg = (struct ForEachArg *)arg;
callbackArg->callback((struct Section *)section, callbackArg->arg);
}
void sect_ForEach(void (*callback)(struct Section *, void *), void *arg)
{
struct ForEachArg callbackArg = { .callback = callback, .arg = arg};
hash_ForEach(sections, forEach, &callbackArg);
}
void sect_AddSection(struct Section *section)
{
/* Check if the section already exists */
if (hash_GetElement(sections, section->name))
errx(1, "Section name \"%s\" is already in use", section->name);
/* If not, add it */
bool collided = hash_AddElement(sections, section->name, section);
if (beVerbose && collided)
warnx("Section hashmap collision occurred!");
}
struct Section *sect_GetSection(char const *name)
{
return (struct Section *)hash_GetElement(sections, name);
}
void sect_CleanupSections(void)
{
hash_EmptyMap(sections);
}
static void doSanityChecks(struct Section *section, void *ptr)
{
(void)ptr;
/* Sanity check the section's type */
if (section->type < 0 || section->type >= SECTTYPE_INVALID)
errx(1, "Section \"%s\" has an invalid type.", section->name);
if (is32kMode && section->type == SECTTYPE_ROMX)
errx(1, "%s: ROMX sections cannot be used with option -t.",
section->name);
if (isWRA0Mode && section->type == SECTTYPE_WRAMX)
errx(1, "%s: WRAMX sections cannot be used with options -w or -d.",
section->name);
if (isDmgMode && section->type == SECTTYPE_VRAM && section->bank == 1)
errx(1, "%s: VRAM bank 1 can't be used with option -d.",
section->name);
/*
* Check if alignment is reasonable, this is important to avoid UB
* An alignment of zero is equivalent to no alignment, basically
*/
if (section->isAlignFixed && section->alignMask == 1)
section->isAlignFixed = false;
uint32_t minbank = bankranges[section->type][0],
maxbank = bankranges[section->type][1];
if (section->isBankFixed && section->bank < minbank
&& section->bank > maxbank)
errx(1, minbank == maxbank
? "Cannot place section \"%s\" in bank %d, it must be %d"
: "Cannot place section \"%s\" in bank %d, it must be between %d and %d",
section->name, section->bank, minbank, maxbank);
/* Check if section has a chance to be placed */
if (section->size > maxsize[section->type])
errx(1, "Section \"%s\" is bigger than the max size for that type: %#X > %#X",
section->size, maxsize[section->type]);
/* Translate loose constraints to strong ones when they're equivalent */
if (minbank == maxbank) {
section->bank = minbank;
section->isBankFixed = true;
}
if (section->isAlignFixed) {
enum SectionType type = section->type;
/* It doesn't make sense to have both org and alignment set */
if (section->isAddressFixed) {
if (section->org & section->alignMask)
errx(1, "Section \"%s\"'s fixed address doesn't match its alignment",
section->name);
section->isAlignFixed = false;
} else if ((endaddr(type) & section->alignMask)
== startaddr[type]) {
section->org = startaddr[type];
section->isAlignFixed = false;
section->isAddressFixed = true;
}
}
}
void sect_DoSanityChecks(void)
{
sect_ForEach(doSanityChecks, NULL);
}

View File

@@ -1,137 +1,56 @@
/*
* This file is part of RGBDS.
*
* Copyright (c) 1997-2018, Carsten Sorensen and RGBDS contributors.
*
* SPDX-License-Identifier: MIT
*/
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "extern/err.h"
#include <stdbool.h>
#include "link/symbol.h"
#include "link/main.h"
#include "link/patch.h"
#include "link/mylink.h"
#include "extern/err.h"
#include "hashmap.h"
#include "types.h"
HashMap symbols;
#define HASHSIZE 73
struct ISymbol {
char *pzName;
int32_t nValue;
int32_t nBank; /* -1 = constant */
/* Object file where the symbol was defined. */
char tzObjFileName[_MAX_PATH + 1];
/* Source file where the symbol was defined. */
char tzFileName[_MAX_PATH + 1];
/* Line where the symbol was defined. */
uint32_t nFileLine;
struct ISymbol *pNext;
struct ForEachArg {
void (*callback)(struct Symbol *symbol, void *arg);
void *arg;
};
struct ISymbol *tHash[HASHSIZE];
int32_t calchash(char *s)
static void forEach(void *symbol, void *arg)
{
int32_t r = 0;
struct ForEachArg *callbackArg = (struct ForEachArg *)arg;
while (*s)
r += *s++;
return r % HASHSIZE;
callbackArg->callback((struct Symbol *)symbol, callbackArg->arg);
}
void sym_Init(void)
void sym_ForEach(void (*callback)(struct Symbol *, void *), void *arg)
{
int32_t i;
struct ForEachArg callbackArg = { .callback = callback, .arg = arg};
for (i = 0; i < HASHSIZE; i += 1)
tHash[i] = NULL;
hash_ForEach(symbols, forEach, &callbackArg);
}
int32_t sym_GetValue(struct sPatch *pPatch, char *tzName)
void sym_AddSymbol(struct Symbol *symbol)
{
if (strcmp(tzName, "@") == 0)
return nPC;
/* Check if the symbol already exists */
struct Symbol *other = hash_GetElement(symbols, symbol->name);
struct ISymbol **ppSym;
if (other)
errx(1, "\"%s\" both in %s from %s(%d) and in %s from %s(%d)",
symbol->name,
symbol->objFileName, symbol->fileName, symbol->lineNo,
other->objFileName, other->fileName, other->lineNo);
ppSym = &(tHash[calchash(tzName)]);
while (*ppSym) {
if (strcmp(tzName, (*ppSym)->pzName))
ppSym = &((*ppSym)->pNext);
else
return ((*ppSym)->nValue);
/* If not, add it */
bool collided = hash_AddElement(symbols, symbol->name, symbol);
if (beVerbose && collided)
warnx("Symbol hashmap collision occurred!");
}
errx(1,
"%s(%ld) : Unknown symbol '%s'",
pPatch->pzFilename, pPatch->nLineNo,
tzName);
}
int32_t sym_GetBank(struct sPatch *pPatch, char *tzName)
struct Symbol *sym_GetSymbol(char const *name)
{
struct ISymbol **ppSym;
ppSym = &(tHash[calchash(tzName)]);
while (*ppSym) {
if (strcmp(tzName, (*ppSym)->pzName))
ppSym = &((*ppSym)->pNext);
else
return ((*ppSym)->nBank);
return (struct Symbol *)hash_GetElement(symbols, name);
}
errx(1,
"%s(%ld) : Unknown symbol '%s'",
pPatch->pzFilename, pPatch->nLineNo,
tzName);
}
void sym_CreateSymbol(char *tzName, int32_t nValue, int32_t nBank,
char *tzObjFileName, char *tzFileName, uint32_t nFileLine)
void sym_CleanupSymbols(void)
{
if (strcmp(tzName, "@") == 0)
return;
struct ISymbol **ppSym;
ppSym = &(tHash[calchash(tzName)]);
while (*ppSym) {
if (strcmp(tzName, (*ppSym)->pzName)) {
ppSym = &((*ppSym)->pNext);
} else {
if (nBank == -1)
return;
errx(1, "'%s' in both %s : %s(%d) and %s : %s(%d)",
tzName, tzObjFileName, tzFileName, nFileLine,
(*ppSym)->tzObjFileName,
(*ppSym)->tzFileName, (*ppSym)->nFileLine);
}
}
*ppSym = malloc(sizeof **ppSym);
if (*ppSym != NULL) {
(*ppSym)->pzName = malloc(strlen(tzName) + 1);
if ((*ppSym)->pzName != NULL) {
strcpy((*ppSym)->pzName, tzName);
(*ppSym)->nValue = nValue;
(*ppSym)->nBank = nBank;
(*ppSym)->pNext = NULL;
strncpy((*ppSym)->tzObjFileName, tzObjFileName,
sizeof((*ppSym)->tzObjFileName));
strncpy((*ppSym)->tzFileName, tzFileName,
sizeof((*ppSym)->tzFileName));
(*ppSym)->nFileLine = nFileLine;
}
}
hash_EmptyMap(symbols);
}

View File

@@ -91,8 +91,8 @@ REPT NumberOfSections
; decide (floating bank). This field is only valid for ROMX,
; VRAM, WRAMX and SRAM sections.
LONG Align ; Alignment of this section (expressed as number of low bits
; to leave as 0). -1 if not defined.
LONG Align ; Alignment of this section, expressed as 1 << align. 1 if
; not specified.
IF (Type == ROMX) || (Type == ROM0) ; Sections that can contain data.

View File

@@ -1 +1 @@
error: Unable to place 'r0b' (ROM0 section) anywhere
error: Unable to place "r0a" (ROM0 section) anywhere

View File

@@ -1 +1 @@
error: ROMX sections can't be used with option -t.
error: rx: ROMX sections cannot be used with option -t.

View File

@@ -1,5 +1,5 @@
ROM0
org $10
org $18
"sec"
org $20
"secfix"

View File

@@ -1,2 +1 @@
error: Different attributes for "sec" in source and linkerscript
error: Linker script contradicts "sec"'s alignment

View File

@@ -1 +1 @@
error: VRAM bank 1 can't be used with option -d.
error: v1: VRAM bank 1 can't be used with option -d.

View File

@@ -1 +1 @@
error: Unable to place 'v1' (VRAM section) in any bank
error: Unable to place "v1" (VRAM section) anywhere

View File

@@ -1 +1 @@
error: WRAMX sections can't be used with options -w or -d.
error: wx: WRAMX sections cannot be used with options -w or -d.

View File

@@ -1 +1 @@
error: Unable to place 'w0b' (WRAM0 section) anywhere
error: Unable to place "w0b" (WRAM0 section) anywhere