mirror of
https://github.com/gbdev/rgbds.git
synced 2025-11-20 10:12:06 +00:00
Rewrite RGBLINK entirely
The goal was to improve readability, but along the way a few things were gained. - Sorted sym and map files - Infrastructure for supporting multiple .o versions - Valgrind-proof, as far as my testing goes anyways - Improved verbosity messages - Added error checking - Performance improvements, see end of commit message The readability improvement was spurred while trying to make sense of the old code while trying to implement features such as sorted sym and map files. I also did my best to remove hardcoded logic, such that modifications should be doable; for example, "RAM loading" sections, which are linked against a different location than the one they're stored at. Some work remains to be done, see the "TODO:" and "FIXME:" comments. Further, while regression tests pass, this new linker should be tested on different codebases (ideally while instrumented with `make develop` and under valgrind). The few errors spotted in the man pages (alignment) need to be corrected. Finally, documentation comments need to be written, I have written a lot of them but not all. This also provides a significant performance boost (benchmarked with a 51994-symbol project): Current master RGBLINK: 2.02user 0.03system 0:02.06elapsed 99%CPU (0avgtext+0avgdata 84336maxresident)k 0inputs+11584outputs (0major+20729minor)pagefaults 0swaps Rewritten RGBLINK: 0.19user 0.06system 0:00.63elapsed 40%CPU (0avgtext+0avgdata 32460maxresident)k 23784inputs+11576outputs (0major+7672minor)pagefaults 0swaps
This commit is contained in:
15
Makefile
15
Makefile
@@ -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 \
|
||||
|
||||
@@ -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
68
include/hashmap.h
Normal 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 */
|
||||
@@ -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 */
|
||||
|
||||
@@ -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 */
|
||||
|
||||
@@ -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 */
|
||||
@@ -1,17 +1,35 @@
|
||||
/*
|
||||
* 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 FILE *linkerScript;
|
||||
extern FILE *mapFile;
|
||||
extern FILE *symFile;
|
||||
extern FILE *overlayFile;
|
||||
extern FILE *outputFile;
|
||||
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)
|
||||
|
||||
#endif /* RGBDS_LINK_MAIN_H */
|
||||
|
||||
@@ -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 */
|
||||
@@ -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 */
|
||||
@@ -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 */
|
||||
|
||||
@@ -1,16 +1,21 @@
|
||||
/*
|
||||
* 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"
|
||||
|
||||
void out_AddSection(struct Section const *section);
|
||||
|
||||
void out_WriteFiles(void);
|
||||
|
||||
#endif /* RGBDS_LINK_OUTPUT_H */
|
||||
|
||||
@@ -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 */
|
||||
|
||||
@@ -1,30 +1,34 @@
|
||||
/*
|
||||
* 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"
|
||||
struct SectionPlacement {
|
||||
struct Section *section;
|
||||
uint16_t org;
|
||||
uint32_t bank;
|
||||
};
|
||||
|
||||
noreturn_ void script_fatalerror(const char *fmt, ...);
|
||||
extern uint64_t script_lineNo;
|
||||
|
||||
void script_Parse(const char *path);
|
||||
/**
|
||||
* 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_IncludeFile(const char *path);
|
||||
int32_t script_IncludeDepthGet(void);
|
||||
void script_IncludePop(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
112
include/link/section.h
Normal 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 */
|
||||
@@ -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 */
|
||||
|
||||
@@ -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 */
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
;
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
118
src/hashmap.c
Normal file
118
src/hashmap.c
Normal 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;
|
||||
}
|
||||
}
|
||||
1000
src/link/assign.c
1000
src/link/assign.c
File diff suppressed because it is too large
Load Diff
194
src/link/lexer.l
194
src/link/lexer.l
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
216
src/link/main.c
216
src/link/main.c
@@ -1,141 +1,177 @@
|
||||
/*
|
||||
* 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 <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 */
|
||||
FILE *linkerScript; /* -l */
|
||||
FILE *mapFile; /* -m */
|
||||
FILE *symFile; /* -n */
|
||||
FILE *overlayFile; /* -O */
|
||||
FILE *outputFile; /* -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
|
||||
/**
|
||||
* Prints the program's usage to stdout.
|
||||
*/
|
||||
|
||||
static void print_usage(void)
|
||||
static void printUsage(void)
|
||||
{
|
||||
printf(
|
||||
"usage: rgblink [-dtVw] [-l linkerscript] [-m mapfile] [-n symfile] [-O overlay]\n"
|
||||
" [-o outfile] [-p pad_value] [-s symbol] file [...]\n");
|
||||
exit(1);
|
||||
puts("usage: rgblink [-dtVvw] [-l linkerscript] [-m mapfile] [-n symfile] [-O overlay]\n"
|
||||
" [-o outfile] [-p pad_value] [-s symbol] file [...]");
|
||||
}
|
||||
|
||||
/*
|
||||
* The main routine
|
||||
/**
|
||||
* Helper function for `main`'s argument parsing.
|
||||
* For use with options which take a file name to operate on
|
||||
* If the file fails to be opened, an error message will be printed,
|
||||
* and the function `exit`s.
|
||||
* @param mode The mode to open the file in
|
||||
* @param failureMessage A format string that will be printed on failure.
|
||||
* A single (string) argument is given, the file name.
|
||||
* @return What `fopen` returned; this cannot be NULL.
|
||||
*/
|
||||
static FILE *openArgFile(char const *mode, char const *failureMessage)
|
||||
{
|
||||
FILE *file = fopen(optarg, mode);
|
||||
|
||||
if (!file)
|
||||
err(1, failureMessage, optarg);
|
||||
return 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)
|
||||
{
|
||||
if (linkerScript)
|
||||
fclose(linkerScript);
|
||||
if (mapFile)
|
||||
fclose(mapFile);
|
||||
if (symFile)
|
||||
fclose(symFile);
|
||||
if (overlayFile)
|
||||
fclose(overlayFile);
|
||||
if (outputFile)
|
||||
fclose(outputFile);
|
||||
|
||||
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);
|
||||
linkerScript = openArgFile("r", "Could not open linker script file \"%s\"");
|
||||
break;
|
||||
case 'm':
|
||||
SetMapfileName(optarg);
|
||||
mapFile = openArgFile("w", "Could not open map file \"%s\"");
|
||||
break;
|
||||
case 'n':
|
||||
SetSymfileName(optarg);
|
||||
break;
|
||||
case 'o':
|
||||
out_Setname(optarg);
|
||||
symFile = openArgFile("w", "Could not open sym file \"%s\"");
|
||||
break;
|
||||
case 'O':
|
||||
out_SetOverlayname(optarg);
|
||||
options |= OPT_OVERLAY;
|
||||
overlayFile = openArgFile("r+b", "Could not open overlay file \"%s\"");
|
||||
break;
|
||||
case 'o':
|
||||
outputFile = openArgFile("wb", "Could not open output file \"%s\"");
|
||||
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]);
|
||||
/* If no input files were specified, the user must have screwed up */
|
||||
if (curArgIndex == argc) {
|
||||
fprintf(stderr, "No input files");
|
||||
printUsage();
|
||||
exit(1);
|
||||
}
|
||||
|
||||
AddNeededModules();
|
||||
AssignSections();
|
||||
CreateSymbolTable();
|
||||
Patch();
|
||||
Output();
|
||||
CloseMapfile();
|
||||
/* Patch the size array depending on command-line options */
|
||||
if (is32kMode)
|
||||
maxsize[SECTTYPE_ROM0] = 0x8000;
|
||||
if (isWRA0Mode)
|
||||
maxsize[SECTTYPE_WRAM0] = 0x2000;
|
||||
|
||||
return 0;
|
||||
/* 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();
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
@@ -1,381 +1,461 @@
|
||||
/*
|
||||
* 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)
|
||||
#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)
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
static int64_t readlong(FILE *file)
|
||||
{
|
||||
uint32_t r;
|
||||
uint32_t value = 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;
|
||||
/* Read the little-endian value byte by byte */
|
||||
for (uint8_t shift = 0; shift < sizeof(value) * CHAR_BIT; shift += 8) {
|
||||
int byte = getc(file);
|
||||
|
||||
return (int32_t)r;
|
||||
if (byte == EOF)
|
||||
return INT64_MAX;
|
||||
value |= (uint8_t)byte << shift;
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
/*
|
||||
* Read a NULL terminated string from a file
|
||||
/**
|
||||
* 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
|
||||
*/
|
||||
int32_t readasciiz(char **dest, FILE *f)
|
||||
#define tryReadlong(var, file, ...) \
|
||||
tryRead(readlong, int64_t, INT64_MAX, var, file, __VA_ARGS__)
|
||||
|
||||
/* 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)
|
||||
{
|
||||
size_t r = 0;
|
||||
/* 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;
|
||||
|
||||
size_t bufferLength = 16;
|
||||
char *start = malloc(bufferLength);
|
||||
char *s = start;
|
||||
|
||||
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__);
|
||||
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;
|
||||
}
|
||||
s = start + r;
|
||||
}
|
||||
}
|
||||
|
||||
*dest = start;
|
||||
return (r + 1);
|
||||
/* Read char */
|
||||
str[index] = getc(file);
|
||||
} while (str[index]);
|
||||
return str;
|
||||
}
|
||||
|
||||
/*
|
||||
* Allocate a new section and link it into the list
|
||||
/**
|
||||
* 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
|
||||
*/
|
||||
struct sSection *AllocSection(void)
|
||||
{
|
||||
struct sSection **ppSections;
|
||||
#define tryReadstr(var, file, ...) \
|
||||
tryRead(readstr, char*, NULL, var, file, __VA_ARGS__)
|
||||
|
||||
if (oReadLib == 1)
|
||||
ppSections = &pLibSections;
|
||||
/***** Functions to parse object files *****/
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
a = c + 1;
|
||||
}
|
||||
|
||||
while (*ppSections)
|
||||
ppSections = &((*ppSections)->pNext);
|
||||
struct Symbol const *tmp = symbol;
|
||||
|
||||
*ppSections = malloc(sizeof **ppSections);
|
||||
if (!*ppSections)
|
||||
err(1, "%s: Couldn't allocate memory", __func__);
|
||||
for (uint32_t i = a; i <= section->nbSymbols; i++) {
|
||||
symbol = tmp;
|
||||
tmp = section->symbols[i];
|
||||
section->symbols[i] = symbol;
|
||||
}
|
||||
|
||||
(*ppSections)->tSymbols = tSymbols;
|
||||
(*ppSections)->pNext = NULL;
|
||||
(*ppSections)->pPatches = NULL;
|
||||
(*ppSections)->oAssigned = 0;
|
||||
return *ppSections;
|
||||
section->nbSymbols++;
|
||||
}
|
||||
|
||||
/*
|
||||
* Read a symbol from a file
|
||||
*/
|
||||
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;
|
||||
}
|
||||
|
||||
/* 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
|
||||
*/
|
||||
|
||||
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;
|
||||
fileSymbols[i]->section = fileSections[sectionID];
|
||||
/* Give the section a pointer to the symbol as well */
|
||||
linkSymToSect(fileSymbols[i], fileSections[sectionID]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* 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_DoSanityChecks(void)
|
||||
{
|
||||
sect_DoSanityChecks();
|
||||
}
|
||||
|
||||
static void freeSection(struct Section *section, void *arg)
|
||||
{
|
||||
(void)arg;
|
||||
|
||||
free(section->name);
|
||||
if (sect_HasData(section->type)) {
|
||||
free(section->data);
|
||||
for (int32_t i = 0; i < section->nbPatches; i++) {
|
||||
struct Patch *patch = §ion->patches[i];
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
void obj_Readfile(char *tzObjectfile)
|
||||
{
|
||||
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;
|
||||
}
|
||||
|
||||
int32_t file_Length(FILE *f)
|
||||
{
|
||||
uint32_t r, p;
|
||||
|
||||
p = ftell(f);
|
||||
fseek(f, 0, SEEK_END);
|
||||
r = ftell(f);
|
||||
fseek(f, p, SEEK_SET);
|
||||
|
||||
return r;
|
||||
}
|
||||
|
||||
@@ -1,174 +1,344 @@
|
||||
/*
|
||||
* 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"
|
||||
struct SortedSection {
|
||||
struct Section const *section;
|
||||
struct SortedSection *next;
|
||||
};
|
||||
|
||||
char *tzOutname;
|
||||
char *tzOverlayname;
|
||||
static struct {
|
||||
uint32_t nbBanks;
|
||||
struct SortedSections {
|
||||
struct SortedSection *sections;
|
||||
struct SortedSection *zeroLenSections;
|
||||
} *banks;
|
||||
} sections[SECTTYPE_INVALID];
|
||||
|
||||
int32_t MaxOverlayBank;
|
||||
|
||||
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
|
||||
? §ions[section->type].banks[targetBank].sections
|
||||
: §ions[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;
|
||||
}
|
||||
|
||||
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");
|
||||
|
||||
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)
|
||||
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++;
|
||||
}
|
||||
}
|
||||
|
||||
static void writeROM(void)
|
||||
{
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
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
|
||||
? §List
|
||||
: &zlSectList;
|
||||
} else if (sectList.sect) {
|
||||
sectList.sym = sectList.sect->symbols[sectList.i];
|
||||
sectList.addr =
|
||||
sectList.sym->offset + sectList.sect->org;
|
||||
|
||||
minSectList = §List;
|
||||
} 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++;
|
||||
}
|
||||
|
||||
MapfileCloseBank(area_Avail(bank));
|
||||
|
||||
fwrite(mem, 1, MaxAvail[bank], f);
|
||||
free(mem);
|
||||
#undef sect
|
||||
}
|
||||
|
||||
void out_Setname(char *tzOutputfile)
|
||||
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(§ion, &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;
|
||||
}
|
||||
|
||||
if (slack == maxsize[type])
|
||||
fputs(" EMPTY\n\n", mapFile);
|
||||
else
|
||||
fprintf(mapFile, " SLACK: $%04x byte%s\n\n", slack,
|
||||
slack == 1 ? "" : "s");
|
||||
}
|
||||
|
||||
void out_SetOverlayname(char *tzOverlayfile)
|
||||
static void writeSymAndMap(void)
|
||||
{
|
||||
tzOverlayname = tzOverlayfile;
|
||||
if (!symFile && !mapFile)
|
||||
return;
|
||||
|
||||
enum SectionType typeMap[SECTTYPE_INVALID] = {
|
||||
SECTTYPE_ROM0,
|
||||
SECTTYPE_ROMX,
|
||||
SECTTYPE_VRAM,
|
||||
SECTTYPE_SRAM,
|
||||
SECTTYPE_WRAM0,
|
||||
SECTTYPE_WRAMX,
|
||||
SECTTYPE_OAM,
|
||||
SECTTYPE_HRAM
|
||||
};
|
||||
|
||||
if (symFile)
|
||||
fputs("; File generated by rgblink\n", symFile);
|
||||
|
||||
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(§ions[type].banks[bank]);
|
||||
writeMapBank(§ions[type].banks[bank],
|
||||
type, bank);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Output(void)
|
||||
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 =
|
||||
§ions[type].banks[i];
|
||||
|
||||
cleanupSections(bank->sections);
|
||||
cleanupSections(bank->zeroLenSections);
|
||||
}
|
||||
free(sections[type].banks);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void out_WriteFiles(void)
|
||||
{
|
||||
writeROM();
|
||||
writeSymAndMap();
|
||||
|
||||
cleanup();
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
478
src/link/patch.c
478
src/link/patch.c
@@ -1,344 +1,328 @@
|
||||
/*
|
||||
* 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");
|
||||
|
||||
return n;
|
||||
stack.size--;
|
||||
return stack.buf[stack.size];
|
||||
}
|
||||
|
||||
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__);
|
||||
}
|
||||
|
||||
return getrealbankfrominternalbank(nBank);
|
||||
free(stack.buf);
|
||||
}
|
||||
|
||||
int32_t calcrpn(struct sPatch *pPatch)
|
||||
/* RPN operators */
|
||||
|
||||
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;
|
||||
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) {
|
||||
symbol = sym_GetSymbol(symbol->name);
|
||||
if (!symbol)
|
||||
errx(1, "%s(%d): Unknown symbol \"%s\"",
|
||||
patch->fileName, patch->lineNo,
|
||||
symbol->name);
|
||||
}
|
||||
|
||||
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;
|
||||
|
||||
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) {
|
||||
symbol = sym_GetSymbol(symbol->name);
|
||||
if (!symbol)
|
||||
errx(1, "%s(%d): Unknown symbol \"%s\"",
|
||||
patch->fileName, patch->lineNo,
|
||||
symbol->name);
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
return rpnpop();
|
||||
|
||||
if (stack.size > 1)
|
||||
warnx("%s(%d): RPN stack has %lu entries on exit, not 1",
|
||||
patch->fileName, patch->lineNo, stack.size);
|
||||
|
||||
return popRPN();
|
||||
}
|
||||
|
||||
void Patch(void)
|
||||
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 = §ion->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();
|
||||
}
|
||||
|
||||
@@ -1,237 +1,410 @@
|
||||
/*
|
||||
* 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"
|
||||
|
||||
static struct {
|
||||
uint32_t address; /* current address to write sections to */
|
||||
uint32_t top_address; /* not inclusive */
|
||||
enum eSectionType type;
|
||||
} bank[BANK_INDEX_MAX];
|
||||
|
||||
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 */
|
||||
|
||||
/* Current section attributes */
|
||||
static int32_t fix_org = -1;
|
||||
static int32_t fix_align = 1;
|
||||
|
||||
void script_InitSections(void)
|
||||
static inline bool isWhiteSpace(int c)
|
||||
{
|
||||
int32_t i;
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
return c == ' ' || c == '\t';
|
||||
}
|
||||
|
||||
void script_SetCurrentSectionType(const char *type, uint32_t bank_num)
|
||||
static inline bool isNewline(int c)
|
||||
{
|
||||
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");
|
||||
return c == '\r' || c == '\n';
|
||||
}
|
||||
|
||||
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);
|
||||
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;
|
||||
}
|
||||
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);
|
||||
|
||||
/* 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++;
|
||||
}
|
||||
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",
|
||||
if (digit == base)
|
||||
return false;
|
||||
*number = *number * base + digit;
|
||||
} while (*str);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
enum LinkerScriptTokenType {
|
||||
TOKEN_NEWLINE,
|
||||
TOKEN_COMMAND,
|
||||
TOKEN_BANK,
|
||||
TOKEN_NUMBER,
|
||||
TOKEN_SECTION,
|
||||
TOKEN_EOF,
|
||||
|
||||
TOKEN_INVALID
|
||||
};
|
||||
|
||||
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 uint32_t lineNo;
|
||||
|
||||
static int readChar(FILE *file)
|
||||
{
|
||||
int curchar = getc_unlocked(file);
|
||||
|
||||
if (curchar == EOF && ferror(file))
|
||||
err(1, "%s: Unexpected error reading linker script", __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_SECTION)
|
||||
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 section name */
|
||||
token.type = TOKEN_SECTION;
|
||||
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, "Line %u: Unterminated string", 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 section name",
|
||||
__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;
|
||||
for (enum LinkerScriptCommand i = 0; i < COMMAND_INVALID; i++) {
|
||||
if (!strcmp(commands[i], str)) {
|
||||
token.type = TOKEN_COMMAND;
|
||||
token.attr.command = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (token.type == TOKEN_INVALID) {
|
||||
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) {
|
||||
/* None of the strings matched, do we have a number? */
|
||||
if (tryParseNumber(str, &token.attr.number))
|
||||
token.type = TOKEN_NUMBER;
|
||||
else
|
||||
errx(1, "Unknown token \"%s\" on linker script line %u",
|
||||
str, lineNo);
|
||||
}
|
||||
|
||||
free(str);
|
||||
}
|
||||
|
||||
return &token;
|
||||
}
|
||||
|
||||
void script_SetAddress(uint32_t addr)
|
||||
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);
|
||||
}
|
||||
|
||||
fix_org = addr;
|
||||
if (arg < *pc)
|
||||
errx(1, "Linkerscript line %u: `%s` cannot be used to go backwards",
|
||||
lineNo, commands[command]);
|
||||
*pc = arg;
|
||||
}
|
||||
|
||||
void script_SetAlignment(uint32_t alignment)
|
||||
enum LinkerScriptParserState {
|
||||
PARSER_FIRSTTIME,
|
||||
PARSER_LINESTART,
|
||||
PARSER_LINEEND
|
||||
};
|
||||
|
||||
/* 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, "Linkerscript line %u: PC overflowed (%u > %u)",
|
||||
lineNo, curaddr[type][bankID],
|
||||
endaddr(type));
|
||||
if (curaddr[type][bankID] < startaddr[type])
|
||||
errx(1, "Linkerscript line %u: PC underflowed (%u < %u)",
|
||||
lineNo, curaddr[type][bankID],
|
||||
startaddr[type]);
|
||||
}
|
||||
|
||||
switch (parserState) {
|
||||
case PARSER_FIRSTTIME:
|
||||
trap_;
|
||||
|
||||
case PARSER_LINESTART:
|
||||
switch (token->type) {
|
||||
case TOKEN_INVALID:
|
||||
trap_;
|
||||
|
||||
case TOKEN_EOF:
|
||||
return NULL;
|
||||
|
||||
case TOKEN_NUMBER:
|
||||
errx(1, "Linkerscript line %u: stray number",
|
||||
lineNo);
|
||||
|
||||
case TOKEN_NEWLINE:
|
||||
lineNo++;
|
||||
break;
|
||||
|
||||
case TOKEN_SECTION:
|
||||
parserState = PARSER_LINEEND;
|
||||
|
||||
if (type == SECTTYPE_INVALID)
|
||||
errx(1, "Linkerscript line %u: Didn't specify a location before the section",
|
||||
lineNo);
|
||||
|
||||
section.section =
|
||||
sect_GetSection(token->attr.string);
|
||||
section.org = curaddr[type][bankID];
|
||||
section.bank = bank;
|
||||
|
||||
curaddr[type][bankID] += section.section->size;
|
||||
return §ion;
|
||||
|
||||
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, "Linkerscript line %u: Didn't specify a location before the command",
|
||||
lineNo);
|
||||
if (!hasArg)
|
||||
errx(1, "Linkerscript line %u: Command specified without an argument",
|
||||
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, "Linkerscript line %u: Didn't specify a bank number",
|
||||
lineNo);
|
||||
else if (!hasArg)
|
||||
arg = bankranges[type][0];
|
||||
else if (arg < bankranges[type][0])
|
||||
errx(1, "Linkerscript line %u: specified bank number is too low (%u < %u)",
|
||||
lineNo, arg,
|
||||
bankranges[type][0]);
|
||||
else if (arg > bankranges[type][1])
|
||||
errx(1, "Linkerscript line %u: specified bank number is too high (%u > %u)",
|
||||
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;
|
||||
}
|
||||
break;
|
||||
|
||||
case PARSER_LINEEND:
|
||||
lineend:
|
||||
if (token->type == TOKEN_EOF)
|
||||
return NULL;
|
||||
else if (token->type != TOKEN_NEWLINE)
|
||||
errx(1, "Linkerscript line %u: Unexpected token at the end",
|
||||
lineNo);
|
||||
lineNo++;
|
||||
parserState = PARSER_LINESTART;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void script_OutputSection(const char *section_name)
|
||||
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
168
src/link/section.c
Normal 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);
|
||||
}
|
||||
@@ -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);
|
||||
|
||||
errx(1,
|
||||
"%s(%ld) : Unknown symbol '%s'",
|
||||
pPatch->pzFilename, pPatch->nLineNo,
|
||||
tzName);
|
||||
if (beVerbose && collided)
|
||||
warnx("Symbol hashmap collision occurred!");
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
errx(1,
|
||||
"%s(%ld) : Unknown symbol '%s'",
|
||||
pPatch->pzFilename, pPatch->nLineNo,
|
||||
tzName);
|
||||
return (struct Symbol *)hash_GetElement(symbols, name);
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
@@ -1 +1 @@
|
||||
error: Unable to place 'r0b' (ROM0 section) anywhere
|
||||
error: Unable to place "r0a" (ROM0 section) anywhere
|
||||
|
||||
@@ -1 +1 @@
|
||||
error: ROMX sections can't be used with option -t.
|
||||
error: rx: ROMX sections cannot be used with option -t.
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
ROM0
|
||||
org $10
|
||||
org $18
|
||||
"sec"
|
||||
org $20
|
||||
"secfix"
|
||||
|
||||
@@ -1,2 +1 @@
|
||||
error: Different attributes for "sec" in source and linkerscript
|
||||
|
||||
error: Linker script contradicts "sec"'s alignment
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -1 +1 @@
|
||||
error: Unable to place 'v1' (VRAM section) in any bank
|
||||
error: Unable to place "v1" (VRAM section) anywhere
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -1 +1 @@
|
||||
error: Unable to place 'w0b' (WRAM0 section) anywhere
|
||||
error: Unable to place "w0b" (WRAM0 section) anywhere
|
||||
|
||||
Reference in New Issue
Block a user