mirror of
https://github.com/gbdev/rgbds.git
synced 2025-11-21 10:42:07 +00:00
Merge pull request #434 from ISSOtm/rgblink_rewrite
Rewrite RGBLINK entirely
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,49 @@
|
||||
/*
|
||||
* This file is part of RGBDS.
|
||||
*
|
||||
* Copyright (c) 1997-2018, Carsten Sorensen and RGBDS contributors.
|
||||
* Copyright (c) 1997-2019, Carsten Sorensen and RGBDS contributors.
|
||||
*
|
||||
* SPDX-License-Identifier: MIT
|
||||
*/
|
||||
|
||||
/* Declarations that all modules use, as well as `main` and related */
|
||||
#ifndef RGBDS_LINK_MAIN_H
|
||||
#define RGBDS_LINK_MAIN_H
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stdbool.h>
|
||||
#include <stdio.h>
|
||||
|
||||
extern int32_t fillchar;
|
||||
extern char *smartlinkstartsymbol;
|
||||
/* Variables related to CLI options */
|
||||
extern bool isDmgMode;
|
||||
extern char const *linkerScriptName;
|
||||
extern char const *mapFileName;
|
||||
extern char const *symFileName;
|
||||
extern char const *overlayFileName;
|
||||
extern char const *outputFileName;
|
||||
extern uint8_t padValue;
|
||||
extern bool is32kMode;
|
||||
extern bool beVerbose;
|
||||
extern bool isWRA0Mode;
|
||||
|
||||
/* Helper macro for printing verbose-mode messages */
|
||||
#define verbosePrint(...) do { \
|
||||
if (beVerbose) \
|
||||
fprintf(stderr, __VA_ARGS__); \
|
||||
} while (0)
|
||||
|
||||
/**
|
||||
* Opens a file if specified, and aborts on error.
|
||||
* @param fileName The name of the file to open; if NULL, no file will be opened
|
||||
* @param mode The mode to open the file with
|
||||
* @return A pointer to a valid FILE structure, or NULL if fileName was NULL
|
||||
*/
|
||||
FILE *openFile(char const *fileName, char const *mode);
|
||||
|
||||
#define closeFile(file) do { \
|
||||
FILE *tmp = file; \
|
||||
if (tmp) \
|
||||
fclose(tmp); \
|
||||
} while (0)
|
||||
|
||||
#endif /* RGBDS_LINK_MAIN_H */
|
||||
|
||||
@@ -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,28 @@
|
||||
/*
|
||||
* This file is part of RGBDS.
|
||||
*
|
||||
* Copyright (c) 1997-2018, Carsten Sorensen and RGBDS contributors.
|
||||
* Copyright (c) 1997-2019, Carsten Sorensen and RGBDS contributors.
|
||||
*
|
||||
* SPDX-License-Identifier: MIT
|
||||
*/
|
||||
|
||||
/* Outputting the result of linking */
|
||||
#ifndef RGBDS_LINK_OUTPUT_H
|
||||
#define RGBDS_LINK_OUTPUT_H
|
||||
|
||||
void out_Setname(char *tzOutputfile);
|
||||
void out_SetOverlayname(char *tzOverlayfile);
|
||||
void Output(void);
|
||||
#include <stdint.h>
|
||||
|
||||
#include "link/section.h"
|
||||
|
||||
/**
|
||||
* Registers a section for output.
|
||||
* @param section The section to add
|
||||
*/
|
||||
void out_AddSection(struct Section const *section);
|
||||
|
||||
/**
|
||||
* Writes all output (bin, sym, map) files.
|
||||
*/
|
||||
void out_WriteFiles(void);
|
||||
|
||||
#endif /* RGBDS_LINK_OUTPUT_H */
|
||||
|
||||
@@ -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,36 @@
|
||||
/*
|
||||
* This file is part of RGBDS.
|
||||
*
|
||||
* Copyright (c) 2017-2018, Antonio Nino Diaz and RGBDS contributors.
|
||||
* Copyright (c) 1997-2019, Carsten Sorensen and RGBDS contributors.
|
||||
*
|
||||
* SPDX-License-Identifier: MIT
|
||||
*/
|
||||
|
||||
/* Parsing a linker script */
|
||||
#ifndef RGBDS_LINK_SCRIPT_H
|
||||
#define RGBDS_LINK_SCRIPT_H
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
#include "helpers.h"
|
||||
extern FILE *linkerScript;
|
||||
|
||||
noreturn_ void script_fatalerror(const char *fmt, ...);
|
||||
struct SectionPlacement {
|
||||
struct Section *section;
|
||||
uint16_t org;
|
||||
uint32_t bank;
|
||||
};
|
||||
|
||||
void script_Parse(const char *path);
|
||||
extern uint64_t script_lineNo;
|
||||
|
||||
void script_IncludeFile(const char *path);
|
||||
int32_t script_IncludeDepthGet(void);
|
||||
void script_IncludePop(void);
|
||||
/**
|
||||
* Parses the linker script to return the next section constraint
|
||||
* @return A pointer to a struct, or NULL on EOF. The pointer shouldn't be freed
|
||||
*/
|
||||
struct SectionPlacement *script_NextSection(void);
|
||||
|
||||
void script_InitSections(void);
|
||||
void script_SetCurrentSectionType(const char *type, uint32_t bank);
|
||||
void script_SetAddress(uint32_t addr);
|
||||
void script_SetAlignment(uint32_t alignment);
|
||||
void script_OutputSection(const char *section_name);
|
||||
/**
|
||||
* `free`s all assignment memory that was allocated.
|
||||
*/
|
||||
void script_Cleanup(void);
|
||||
|
||||
#endif /* RGBDS_LINK_SCRIPT_H */
|
||||
|
||||
112
include/link/section.h
Normal file
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;
|
||||
|
||||
13
src/extern/err.c
vendored
13
src/extern/err.c
vendored
@@ -14,13 +14,12 @@
|
||||
|
||||
void rgbds_vwarn(const char *fmt, va_list ap)
|
||||
{
|
||||
fprintf(stderr, "warning");
|
||||
fprintf(stderr, "warning: ");
|
||||
if (fmt) {
|
||||
fputs(": ", stderr);
|
||||
vfprintf(stderr, fmt, ap);
|
||||
fputs(": ", stderr);
|
||||
}
|
||||
putc('\n', stderr);
|
||||
perror(0);
|
||||
perror(NULL);
|
||||
}
|
||||
|
||||
void rgbds_vwarnx(const char *fmt, va_list ap)
|
||||
@@ -35,12 +34,12 @@ void rgbds_vwarnx(const char *fmt, va_list ap)
|
||||
|
||||
noreturn_ void rgbds_verr(int status, const char *fmt, va_list ap)
|
||||
{
|
||||
fprintf(stderr, "error");
|
||||
fprintf(stderr, "error: ");
|
||||
if (fmt) {
|
||||
fputs(": ", stderr);
|
||||
vfprintf(stderr, fmt, ap);
|
||||
fputs(": ", stderr);
|
||||
}
|
||||
putc('\n', stderr);
|
||||
perror(NULL);
|
||||
exit(status);
|
||||
}
|
||||
|
||||
|
||||
118
src/hashmap.c
Normal file
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;
|
||||
}
|
||||
}
|
||||
1056
src/link/assign.c
1056
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;
|
||||
}
|
||||
}
|
||||
207
src/link/main.c
207
src/link/main.c
@@ -1,141 +1,162 @@
|
||||
/*
|
||||
* This file is part of RGBDS.
|
||||
*
|
||||
* Copyright (c) 1997-2018, Carsten Sorensen and RGBDS contributors.
|
||||
* Copyright (c) 1997-2019, Carsten Sorensen and RGBDS contributors.
|
||||
*
|
||||
* SPDX-License-Identifier: MIT
|
||||
*/
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <sys/types.h>
|
||||
#include <sys/stat.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include "extern/err.h"
|
||||
#include <stdio.h>
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
#include "link/object.h"
|
||||
#include "link/output.h"
|
||||
#include "link/symbol.h"
|
||||
#include "link/section.h"
|
||||
#include "link/assign.h"
|
||||
#include "link/patch.h"
|
||||
#include "link/mylink.h"
|
||||
#include "link/mapfile.h"
|
||||
#include "link/main.h"
|
||||
#include "link/library.h"
|
||||
#include "link/output.h"
|
||||
|
||||
#include "extern/err.h"
|
||||
#include "version.h"
|
||||
|
||||
enum eBlockType {
|
||||
BLOCK_COMMENT,
|
||||
BLOCK_OBJECTS,
|
||||
BLOCK_LIBRARIES,
|
||||
BLOCK_OUTPUT
|
||||
};
|
||||
bool isDmgMode; /* -d */
|
||||
char const *linkerScriptName; /* -l */
|
||||
char const *mapFileName; /* -m */
|
||||
char const *symFileName; /* -n */
|
||||
char const *overlayFileName; /* -O */
|
||||
char const *outputFileName; /* -o */
|
||||
uint8_t padValue; /* -p */
|
||||
bool is32kMode; /* -t */
|
||||
bool beVerbose; /* -v */
|
||||
bool isWRA0Mode; /* -w */
|
||||
|
||||
int32_t options;
|
||||
int32_t fillchar;
|
||||
char *smartlinkstartsymbol;
|
||||
|
||||
/*
|
||||
* Print the usagescreen
|
||||
*/
|
||||
|
||||
static void print_usage(void)
|
||||
FILE *openFile(char const *fileName, char const *mode)
|
||||
{
|
||||
printf(
|
||||
"usage: rgblink [-dtVw] [-l linkerscript] [-m mapfile] [-n symfile] [-O overlay]\n"
|
||||
" [-o outfile] [-p pad_value] [-s symbol] file [...]\n");
|
||||
exit(1);
|
||||
if (!fileName)
|
||||
return NULL;
|
||||
|
||||
FILE *file = fopen(fileName, mode);
|
||||
|
||||
if (!file)
|
||||
err(1, "Could not open file \"%s\"", fileName);
|
||||
|
||||
return file;
|
||||
}
|
||||
|
||||
/*
|
||||
* The main routine
|
||||
/**
|
||||
* Prints the program's usage to stdout.
|
||||
*/
|
||||
static void printUsage(void)
|
||||
{
|
||||
puts("usage: rgblink [-dtVvw] [-l linkerscript] [-m mapfile] [-n symfile] [-O overlay]\n"
|
||||
" [-o outfile] [-p pad_value] [-s symbol] file [...]");
|
||||
}
|
||||
|
||||
/**
|
||||
* Cleans up what has been done
|
||||
* Mostly here to please tools such as `valgrind` so actual errors can be seen
|
||||
*/
|
||||
static void cleanup(void)
|
||||
{
|
||||
obj_Cleanup();
|
||||
}
|
||||
|
||||
int main(int argc, char *argv[])
|
||||
{
|
||||
int ch;
|
||||
char *ep;
|
||||
char optionChar;
|
||||
char *endptr; /* For error checking with `strtol` */
|
||||
unsigned long value; /* For storing `strtoul`'s return value */
|
||||
|
||||
if (argc == 1)
|
||||
print_usage();
|
||||
|
||||
while ((ch = getopt(argc, argv, "dl:m:n:O:o:p:s:tVw")) != -1) {
|
||||
switch (ch) {
|
||||
/* Parse options */
|
||||
while ((optionChar = getopt(argc, argv, "dl:m:n:O:o:p:s:tVvw")) != -1) {
|
||||
switch (optionChar) {
|
||||
case 'd':
|
||||
isDmgMode = true;
|
||||
isWRA0Mode = true;
|
||||
break;
|
||||
case 'l':
|
||||
SetLinkerscriptName(optarg);
|
||||
linkerScriptName = optarg;
|
||||
break;
|
||||
case 'm':
|
||||
SetMapfileName(optarg);
|
||||
mapFileName = optarg;
|
||||
break;
|
||||
case 'n':
|
||||
SetSymfileName(optarg);
|
||||
break;
|
||||
case 'o':
|
||||
out_Setname(optarg);
|
||||
symFileName = optarg;
|
||||
break;
|
||||
case 'O':
|
||||
out_SetOverlayname(optarg);
|
||||
options |= OPT_OVERLAY;
|
||||
overlayFileName = optarg;
|
||||
break;
|
||||
case 'o':
|
||||
outputFileName = optarg;
|
||||
break;
|
||||
case 'p':
|
||||
fillchar = strtoul(optarg, &ep, 0);
|
||||
if (optarg[0] == '\0' || *ep != '\0')
|
||||
value = strtoul(optarg, &endptr, 0);
|
||||
if (optarg[0] == '\0' || *endptr != '\0')
|
||||
errx(1, "Invalid argument for option 'p'");
|
||||
if (fillchar < 0 || fillchar > 0xFF)
|
||||
errx(1, "Argument for option 'p' must be between 0 and 0xFF");
|
||||
if (value > 0xFF)
|
||||
errx(1, "Argument for 'p' must be a byte (between 0 and 0xFF)");
|
||||
padValue = value;
|
||||
break;
|
||||
case 's':
|
||||
options |= OPT_SMART_C_LINK;
|
||||
smartlinkstartsymbol = optarg;
|
||||
/* FIXME: nobody knows what this does, figure it out */
|
||||
(void)optarg;
|
||||
warnx("Nobody has any idea what `-s` does");
|
||||
break;
|
||||
case 't':
|
||||
options |= OPT_TINY;
|
||||
break;
|
||||
case 'd':
|
||||
/*
|
||||
* Set to set WRAM as a single continuous block as on
|
||||
* DMG. All WRAM sections must be WRAM0 as bankable WRAM
|
||||
* sections do not exist in this mode. A WRAMX section
|
||||
* will raise an error. VRAM bank 1 can't be used if
|
||||
* this option is enabled either.
|
||||
*
|
||||
* This option implies OPT_CONTWRAM.
|
||||
*/
|
||||
options |= OPT_DMG_MODE;
|
||||
/* FALLTHROUGH */
|
||||
case 'w':
|
||||
/*
|
||||
* Set to set WRAM as a single continuous block as on
|
||||
* DMG. All WRAM sections must be WRAM0 as bankable WRAM
|
||||
* sections do not exist in this mode. A WRAMX section
|
||||
* will raise an error.
|
||||
*/
|
||||
options |= OPT_CONTWRAM;
|
||||
is32kMode = true;
|
||||
break;
|
||||
case 'V':
|
||||
printf("rgblink %s\n", get_package_version_string());
|
||||
exit(0);
|
||||
case 'v':
|
||||
beVerbose = true;
|
||||
break;
|
||||
case 'w':
|
||||
isWRA0Mode = true;
|
||||
break;
|
||||
default:
|
||||
print_usage();
|
||||
/* NOTREACHED */
|
||||
printUsage();
|
||||
exit(1);
|
||||
}
|
||||
}
|
||||
argc -= optind;
|
||||
argv += optind;
|
||||
|
||||
if (argc == 0)
|
||||
print_usage();
|
||||
int curArgIndex = optind;
|
||||
|
||||
for (int32_t i = 0; i < argc; ++i)
|
||||
obj_Readfile(argv[i]);
|
||||
|
||||
AddNeededModules();
|
||||
AssignSections();
|
||||
CreateSymbolTable();
|
||||
Patch();
|
||||
Output();
|
||||
CloseMapfile();
|
||||
|
||||
return 0;
|
||||
/* If no input files were specified, the user must have screwed up */
|
||||
if (curArgIndex == argc) {
|
||||
fprintf(stderr, "No input files");
|
||||
printUsage();
|
||||
exit(1);
|
||||
}
|
||||
|
||||
/* Patch the size array depending on command-line options */
|
||||
if (is32kMode)
|
||||
maxsize[SECTTYPE_ROM0] = 0x8000;
|
||||
if (isWRA0Mode)
|
||||
maxsize[SECTTYPE_WRAM0] = 0x2000;
|
||||
|
||||
/* Patch the bank ranges array depending on command-line options */
|
||||
if (isDmgMode)
|
||||
bankranges[SECTTYPE_VRAM][1] = BANK_MIN_VRAM;
|
||||
|
||||
/* Read all object files first, */
|
||||
while (curArgIndex < argc)
|
||||
obj_ReadFile(argv[curArgIndex++]);
|
||||
|
||||
/* then process them, */
|
||||
obj_DoSanityChecks();
|
||||
assign_AssignSections();
|
||||
assign_Cleanup();
|
||||
|
||||
/* and finally output the result. */
|
||||
patch_ApplyPatches();
|
||||
out_WriteFiles();
|
||||
|
||||
/* Do cleanup before quitting, though. */
|
||||
cleanup();
|
||||
}
|
||||
|
||||
@@ -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,500 @@
|
||||
/*
|
||||
* This file is part of RGBDS.
|
||||
*
|
||||
* Copyright (c) 1997-2018, Carsten Sorensen and RGBDS contributors.
|
||||
*
|
||||
* SPDX-License-Identifier: MIT
|
||||
*/
|
||||
|
||||
/*
|
||||
* Here we have the routines that read an objectfile
|
||||
*/
|
||||
|
||||
#include <ctype.h>
|
||||
#include <errno.h>
|
||||
#include <stdint.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <stdint.h>
|
||||
#include <string.h>
|
||||
#include <stdlib.h>
|
||||
#include <errno.h>
|
||||
#include <limits.h>
|
||||
|
||||
#include "common.h"
|
||||
#include "link/object.h"
|
||||
#include "link/main.h"
|
||||
#include "link/symbol.h"
|
||||
#include "link/section.h"
|
||||
#include "link/assign.h"
|
||||
|
||||
#include "extern/err.h"
|
||||
#include "linkdefs.h"
|
||||
#include "common.h"
|
||||
|
||||
#include "link/assign.h"
|
||||
#include "link/mylink.h"
|
||||
#include "link/main.h"
|
||||
static struct SymbolList {
|
||||
size_t nbSymbols;
|
||||
struct Symbol **symbolList;
|
||||
struct SymbolList *next;
|
||||
} *symbolLists;
|
||||
|
||||
struct sSymbol **tSymbols;
|
||||
struct sSection *pSections;
|
||||
struct sSection *pLibSections;
|
||||
uint8_t oReadLib;
|
||||
/***** Helper functions for reading object files *****/
|
||||
|
||||
/*
|
||||
* Read 32-bit values with the correct endianness
|
||||
* Internal, DO NOT USE.
|
||||
* For helper wrapper macros defined below, such as `tryReadlong`
|
||||
*/
|
||||
static int32_t readlong(FILE *f)
|
||||
{
|
||||
uint32_t r;
|
||||
#define tryRead(func, type, errval, var, file, ...) \
|
||||
do { \
|
||||
FILE *tmpFile = file; \
|
||||
type tmpVal = func(tmpFile); \
|
||||
if (tmpVal == (errval)) { \
|
||||
errx(1, __VA_ARGS__, feof(tmpFile) \
|
||||
? "Unexpected end of file" \
|
||||
: strerror(errno)); \
|
||||
} \
|
||||
var = tmpVal; \
|
||||
} while (0)
|
||||
|
||||
r = ((uint32_t)(uint8_t)fgetc(f));
|
||||
r |= ((uint32_t)(uint8_t)fgetc(f)) << 8;
|
||||
r |= ((uint32_t)(uint8_t)fgetc(f)) << 16;
|
||||
r |= ((uint32_t)(uint8_t)fgetc(f)) << 24;
|
||||
|
||||
return (int32_t)r;
|
||||
}
|
||||
|
||||
/*
|
||||
* Read a NULL terminated string from a file
|
||||
/**
|
||||
* Reads an unsigned long (32-bit) value from a file.
|
||||
* @param file The file to read from. This will read 4 bytes from the file.
|
||||
* @return The value read, cast to a int64_t, or -1 on failure.
|
||||
*/
|
||||
int32_t readasciiz(char **dest, FILE *f)
|
||||
static int64_t readlong(FILE *file)
|
||||
{
|
||||
size_t r = 0;
|
||||
uint32_t value = 0;
|
||||
|
||||
size_t bufferLength = 16;
|
||||
char *start = malloc(bufferLength);
|
||||
char *s = start;
|
||||
/* Read the little-endian value byte by byte */
|
||||
for (uint8_t shift = 0; shift < sizeof(value) * CHAR_BIT; shift += 8) {
|
||||
int byte = getc(file);
|
||||
|
||||
if (!s)
|
||||
err(1, "%s: Couldn't allocate memory", __func__);
|
||||
|
||||
while (((*s++) = fgetc(f)) != 0) {
|
||||
r += 1;
|
||||
|
||||
if (r >= bufferLength) {
|
||||
bufferLength *= 2;
|
||||
start = realloc(start, bufferLength);
|
||||
if (!start) {
|
||||
err(1, "%s: Couldn't allocate memory",
|
||||
__func__);
|
||||
}
|
||||
s = start + r;
|
||||
if (byte == EOF)
|
||||
return INT64_MAX;
|
||||
value |= (uint8_t)byte << shift;
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
*dest = start;
|
||||
return (r + 1);
|
||||
}
|
||||
|
||||
/*
|
||||
* Allocate a new section and link it into the list
|
||||
/**
|
||||
* Helper macro for reading longs from a file, and errors out if it fails to.
|
||||
* Not as a function to avoid overhead in the general case.
|
||||
* TODO: maybe mark the condition as `unlikely`; how to do that portably?
|
||||
* @param var The variable to stash the number into
|
||||
* @param file The file to read from. Its position will be advanced
|
||||
* @param ... A format string and related arguments; note that an extra string
|
||||
* argument is provided, the reason for failure
|
||||
*/
|
||||
struct sSection *AllocSection(void)
|
||||
{
|
||||
struct sSection **ppSections;
|
||||
#define tryReadlong(var, file, ...) \
|
||||
tryRead(readlong, int64_t, INT64_MAX, var, file, __VA_ARGS__)
|
||||
|
||||
if (oReadLib == 1)
|
||||
ppSections = &pLibSections;
|
||||
/* There is no `readbyte`, just use `fgetc` or `getc`. */
|
||||
|
||||
/**
|
||||
* Helper macro for reading bytes from a file, and errors out if it fails to.
|
||||
* Differs from `tryGetc` in that the backing function is fgetc(1).
|
||||
* Not as a function to avoid overhead in the general case.
|
||||
* TODO: maybe mark the condition as `unlikely`; how to do that portably?
|
||||
* @param var The variable to stash the number into
|
||||
* @param file The file to read from. Its position will be advanced
|
||||
* @param ... A format string and related arguments; note that an extra string
|
||||
* argument is provided, the reason for failure
|
||||
*/
|
||||
#define tryFgetc(var, file, ...) \
|
||||
tryRead(fgetc, int, EOF, var, file, __VA_ARGS__)
|
||||
|
||||
/**
|
||||
* Helper macro for reading bytes from a file, and errors out if it fails to.
|
||||
* Differs from `tryGetc` in that the backing function is fgetc(1).
|
||||
* Not as a function to avoid overhead in the general case.
|
||||
* TODO: maybe mark the condition as `unlikely`; how to do that portably?
|
||||
* @param var The variable to stash the number into
|
||||
* @param file The file to read from. Its position will be advanced
|
||||
* @param ... A format string and related arguments; note that an extra string
|
||||
* argument is provided, the reason for failure
|
||||
*/
|
||||
#define tryGetc(var, file, ...) \
|
||||
tryRead(getc, int, EOF, var, file, __VA_ARGS__)
|
||||
|
||||
/**
|
||||
* Reads a '\0'-terminated string from a file.
|
||||
* @param file The file to read from. The file position will be advanced.
|
||||
* @return The string read, or NULL on failure.
|
||||
* If a non-NULL pointer is returned, make sure to `free` it when done!
|
||||
*/
|
||||
static char *readstr(FILE *file)
|
||||
{
|
||||
/* Default buffer size, have it close to the average string length */
|
||||
size_t capacity = 32 / 2;
|
||||
size_t index = -1;
|
||||
/* Force the first iteration to allocate */
|
||||
char *str = NULL;
|
||||
|
||||
do {
|
||||
/* Prepare going to next char */
|
||||
index++;
|
||||
|
||||
/* If the buffer isn't suitable to write the next char... */
|
||||
if (index >= capacity || !str) {
|
||||
capacity *= 2;
|
||||
str = realloc(str, capacity);
|
||||
/* End now in case of error */
|
||||
if (!str)
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/* Read char */
|
||||
int byte = getc(file);
|
||||
|
||||
if (byte == EOF)
|
||||
return NULL;
|
||||
str[index] = byte;
|
||||
} while (str[index]);
|
||||
return str;
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper macro for reading bytes from a file, and errors out if it fails to.
|
||||
* Not as a function to avoid overhead in the general case.
|
||||
* TODO: maybe mark the condition as `unlikely`; how to do that portably?
|
||||
* @param var The variable to stash the string into
|
||||
* @param file The file to read from. Its position will be advanced
|
||||
* @param ... A format string and related arguments; note that an extra string
|
||||
* argument is provided, the reason for failure
|
||||
*/
|
||||
#define tryReadstr(var, file, ...) \
|
||||
tryRead(readstr, char*, NULL, var, file, __VA_ARGS__)
|
||||
|
||||
/***** Functions to parse object files *****/
|
||||
|
||||
/**
|
||||
* Reads a RGB6 symbol from a file.
|
||||
* @param file The file to read from
|
||||
* @param symbol The struct to fill
|
||||
* @param fileName The filename to report in errors
|
||||
*/
|
||||
static void readSymbol(FILE *file, struct Symbol *symbol, char const *fileName)
|
||||
{
|
||||
tryReadstr(symbol->name, file, "%s: Cannot read symbol name: %s",
|
||||
fileName);
|
||||
tryGetc(symbol->type, file, "%s: Cannot read \"%s\"'s type: %s",
|
||||
fileName, symbol->name);
|
||||
/* If the symbol is defined in this file, read its definition */
|
||||
if (symbol->type != SYMTYPE_IMPORT) {
|
||||
symbol->objFileName = fileName;
|
||||
tryReadstr(symbol->fileName, file,
|
||||
"%s: Cannot read \"%s\"'s file name: %s",
|
||||
fileName, symbol->name);
|
||||
tryReadlong(symbol->lineNo, file,
|
||||
"%s: Cannot read \"%s\"'s line number: %s",
|
||||
fileName, symbol->name);
|
||||
tryReadlong(symbol->sectionID, file,
|
||||
"%s: Cannot read \"%s\"'s section ID: %s",
|
||||
fileName, symbol->name);
|
||||
tryReadlong(symbol->offset, file,
|
||||
"%s: Cannot read \"%s\"'s value: %s",
|
||||
fileName, symbol->name);
|
||||
} else {
|
||||
symbol->sectionID = -1;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads a RGB6 patch from a file.
|
||||
* @param file The file to read from
|
||||
* @param patch The struct to fill
|
||||
* @param fileName The filename to report in errors
|
||||
* @param i The number of the patch to report in errors
|
||||
*/
|
||||
static void readPatch(FILE *file, struct Patch *patch,
|
||||
char const *fileName, char const *sectName, uint32_t i)
|
||||
{
|
||||
tryReadstr(patch->fileName, file,
|
||||
"%s: Unable to read \"%s\"'s patch #%u's name: %s",
|
||||
fileName, sectName, i);
|
||||
tryReadlong(patch->lineNo, file,
|
||||
"%s: Unable to read \"%s\"'s patch #%u's line number: %s",
|
||||
fileName, sectName, i);
|
||||
tryReadlong(patch->offset, file,
|
||||
"%s: Unable to read \"%s\"'s patch #%u's offset: %s",
|
||||
fileName, sectName, i);
|
||||
tryGetc(patch->type, file,
|
||||
"%s: Unable to read \"%s\"'s patch #%u's type: %s",
|
||||
fileName, sectName, i);
|
||||
tryReadlong(patch->rpnSize, file,
|
||||
"%s: Unable to read \"%s\"'s patch #%u's RPN size: %s",
|
||||
fileName, sectName, i);
|
||||
|
||||
uint8_t *rpnExpression =
|
||||
malloc(sizeof(*rpnExpression) * patch->rpnSize);
|
||||
size_t nbElementsRead = fread(rpnExpression, sizeof(*rpnExpression),
|
||||
patch->rpnSize, file);
|
||||
|
||||
if (nbElementsRead != patch->rpnSize)
|
||||
errx(1, "%s: Cannot read \"%s\"'s patch #%u's RPN expression: %s",
|
||||
fileName, sectName, i);
|
||||
patch->rpnExpression = rpnExpression;
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads a RGB6 section from a file.
|
||||
* @param file The file to read from
|
||||
* @param section The struct to fill
|
||||
* @param fileName The filename to report in errors
|
||||
*/
|
||||
static void readSection(FILE *file, struct Section *section,
|
||||
char const *fileName)
|
||||
{
|
||||
int32_t tmp;
|
||||
|
||||
tryReadstr(section->name, file, "%s: Cannot read section name: %s",
|
||||
fileName);
|
||||
tryReadlong(tmp, file, "%s: Cannot read \"%s\"'s' size: %s",
|
||||
fileName, section->name);
|
||||
if (tmp < 0 || tmp > UINT16_MAX)
|
||||
errx(1, "\"%s\"'s section size (%d) is invalid", section->name,
|
||||
tmp);
|
||||
section->size = tmp;
|
||||
tryGetc(section->type, file, "%s: Cannot read \"%s\"'s type: %s",
|
||||
fileName, section->name);
|
||||
tryReadlong(tmp, file, "%s: Cannot read \"%s\"'s org: %s",
|
||||
fileName, section->name);
|
||||
section->isAddressFixed = tmp >= 0;
|
||||
if (tmp > UINT16_MAX)
|
||||
errx(1, "\"%s\" is too large (%d)", tmp);
|
||||
section->org = tmp;
|
||||
tryReadlong(tmp, file, "%s: Cannot read \"%s\"'s bank: %s",
|
||||
fileName, section->name);
|
||||
section->isBankFixed = tmp >= 0;
|
||||
section->bank = tmp;
|
||||
tryReadlong(tmp, file, "%s: Cannot read \"%s\"'s alignment: %s",
|
||||
fileName, section->name);
|
||||
section->isAlignFixed = tmp != 1;
|
||||
section->alignMask = tmp - 1;
|
||||
|
||||
if (sect_HasData(section->type)) {
|
||||
/* Ensure we never allocate 0 bytes */
|
||||
uint8_t *data = malloc(sizeof(*data) * section->size + 1);
|
||||
|
||||
if (!data)
|
||||
err(1, "%s: Unable to read \"%s\"'s data", fileName,
|
||||
section->name);
|
||||
if (section->size) {
|
||||
size_t nbElementsRead = fread(data, sizeof(*data),
|
||||
section->size, file);
|
||||
if (nbElementsRead != section->size)
|
||||
errx(1, "%s: Cannot read \"%s\"'s data: %s",
|
||||
fileName, section->name,
|
||||
feof(file) ? "Unexpected end of file"
|
||||
: strerror(errno));
|
||||
}
|
||||
section->data = data;
|
||||
|
||||
tryReadlong(section->nbPatches, file,
|
||||
"%s: Cannot read \"%s\"'s number of patches: %s",
|
||||
fileName, section->name);
|
||||
|
||||
struct Patch *patches =
|
||||
malloc(sizeof(*patches) * section->nbPatches + 1);
|
||||
|
||||
if (!patches)
|
||||
err(1, "%s: Unable to read \"%s\"'s patches", fileName,
|
||||
section->name);
|
||||
for (uint32_t i = 0; i < section->nbPatches; i++)
|
||||
readPatch(file, &patches[i], fileName, section->name,
|
||||
i);
|
||||
section->patches = patches;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Links a symbol to a section, keeping the section's symbol list sorted.
|
||||
* @param symbol The symbol to link
|
||||
* @param section The section to link
|
||||
*/
|
||||
static void linkSymToSect(struct Symbol const *symbol, struct Section *section)
|
||||
{
|
||||
uint32_t a = 0, b = section->nbSymbols;
|
||||
|
||||
while (a != b) {
|
||||
uint32_t c = (a + b) / 2;
|
||||
|
||||
if (section->symbols[c]->offset > symbol->offset)
|
||||
b = c;
|
||||
else
|
||||
ppSections = &pSections;
|
||||
|
||||
while (*ppSections)
|
||||
ppSections = &((*ppSections)->pNext);
|
||||
|
||||
*ppSections = malloc(sizeof **ppSections);
|
||||
if (!*ppSections)
|
||||
err(1, "%s: Couldn't allocate memory", __func__);
|
||||
|
||||
(*ppSections)->tSymbols = tSymbols;
|
||||
(*ppSections)->pNext = NULL;
|
||||
(*ppSections)->pPatches = NULL;
|
||||
(*ppSections)->oAssigned = 0;
|
||||
return *ppSections;
|
||||
a = c + 1;
|
||||
}
|
||||
|
||||
/*
|
||||
* Read a symbol from a file
|
||||
struct Symbol const *tmp = symbol;
|
||||
|
||||
for (uint32_t i = a; i <= section->nbSymbols; i++) {
|
||||
symbol = tmp;
|
||||
tmp = section->symbols[i];
|
||||
section->symbols[i] = symbol;
|
||||
}
|
||||
|
||||
section->nbSymbols++;
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads a RGB6 object file.
|
||||
* @param file The file to read from
|
||||
* @param fileName The filename to report in errors
|
||||
*/
|
||||
struct sSymbol *obj_ReadSymbol(FILE *f, char *tzObjectfile)
|
||||
static void readRGB6File(FILE *file, char const *fileName)
|
||||
{
|
||||
struct sSymbol *pSym;
|
||||
uint32_t nbSymbols;
|
||||
uint32_t nbSections;
|
||||
|
||||
pSym = malloc(sizeof(*pSym));
|
||||
if (!pSym)
|
||||
err(1, "%s: Couldn't allocate memory", __func__);
|
||||
tryReadlong(nbSymbols, file, "%s: Cannot read number of symbols: %s",
|
||||
fileName);
|
||||
tryReadlong(nbSections, file, "%s: Cannot read number of sections: %s",
|
||||
fileName);
|
||||
|
||||
readasciiz(&pSym->pzName, f);
|
||||
pSym->Type = (enum eSymbolType)fgetc(f);
|
||||
nbSectionsToAssign += nbSections;
|
||||
|
||||
pSym->pzObjFileName = tzObjectfile;
|
||||
/* This file's symbols, kept to link sections to them */
|
||||
struct Symbol **fileSymbols =
|
||||
malloc(sizeof(*fileSymbols) * nbSymbols + 1);
|
||||
|
||||
if (pSym->Type != SYM_IMPORT) {
|
||||
readasciiz(&pSym->pzFileName, f);
|
||||
pSym->nFileLine = readlong(f);
|
||||
if (!fileSymbols)
|
||||
err(1, "Failed to get memory for %s's symbols", fileName);
|
||||
|
||||
pSym->nSectionID = readlong(f);
|
||||
pSym->nOffset = readlong(f);
|
||||
struct SymbolList *symbolList = malloc(sizeof(*symbolList));
|
||||
|
||||
if (!symbolList)
|
||||
err(1, "Failed to register %s's symbol list", fileName);
|
||||
symbolList->symbolList = fileSymbols;
|
||||
symbolList->nbSymbols = nbSymbols;
|
||||
symbolList->next = symbolLists;
|
||||
symbolLists = symbolList;
|
||||
|
||||
uint32_t nbSymPerSect[nbSections];
|
||||
|
||||
memset(nbSymPerSect, 0, sizeof(nbSymPerSect));
|
||||
|
||||
verbosePrint("Reading %u symbols...\n", nbSymbols);
|
||||
for (uint32_t i = 0; i < nbSymbols; i++) {
|
||||
/* Read symbol */
|
||||
struct Symbol *symbol = malloc(sizeof(*symbol));
|
||||
|
||||
if (!symbol)
|
||||
err(1, "%s: Couldn't create new symbol", fileName);
|
||||
readSymbol(file, symbol, fileName);
|
||||
|
||||
fileSymbols[i] = symbol;
|
||||
if (symbol->type == SYMTYPE_EXPORT)
|
||||
sym_AddSymbol(symbol);
|
||||
if (symbol->sectionID != -1)
|
||||
nbSymPerSect[symbol->sectionID]++;
|
||||
}
|
||||
|
||||
return pSym;
|
||||
}
|
||||
/* This file's sections, stored in a table to link symbols to them */
|
||||
struct Section *fileSections[nbSections ? nbSections : 1];
|
||||
|
||||
/*
|
||||
* RGB object reader routines
|
||||
*/
|
||||
struct sSection *obj_ReadRGBSection(FILE *f)
|
||||
{
|
||||
struct sSection *pSection;
|
||||
char *pzName;
|
||||
verbosePrint("Reading %u sections...\n", nbSections);
|
||||
for (uint32_t i = 0; i < nbSections; i++) {
|
||||
/* Read section */
|
||||
struct Section *section = malloc(sizeof(*section));
|
||||
|
||||
readasciiz(&pzName, f);
|
||||
if (IsSectionNameInUse(pzName))
|
||||
errx(1, "Section name \"%s\" is already in use.", pzName);
|
||||
if (!section)
|
||||
err(1, "%s: Couldn't create new section", fileName);
|
||||
readSection(file, section, fileName);
|
||||
section->fileSymbols = fileSymbols;
|
||||
|
||||
pSection = AllocSection();
|
||||
pSection->pzName = pzName;
|
||||
|
||||
pSection->nByteSize = readlong(f);
|
||||
pSection->Type = (enum eSectionType)fgetc(f);
|
||||
pSection->nOrg = readlong(f);
|
||||
pSection->nBank = readlong(f);
|
||||
pSection->nAlign = readlong(f);
|
||||
|
||||
if ((options & OPT_TINY) && (pSection->Type == SECT_ROMX))
|
||||
errx(1, "ROMX sections can't be used with option -t.");
|
||||
|
||||
if ((options & OPT_CONTWRAM) && (pSection->Type == SECT_WRAMX))
|
||||
errx(1, "WRAMX sections can't be used with options -w or -d.");
|
||||
|
||||
if (options & OPT_DMG_MODE) {
|
||||
/* WRAMX sections are checked for OPT_CONTWRAM */
|
||||
if (pSection->Type == SECT_VRAM && pSection->nBank == 1)
|
||||
errx(1, "VRAM bank 1 can't be used with option -d.");
|
||||
}
|
||||
|
||||
uint32_t maxsize = 0;
|
||||
|
||||
/* Verify that the section isn't too big */
|
||||
switch (pSection->Type) {
|
||||
case SECT_ROM0:
|
||||
maxsize = (options & OPT_TINY) ? 0x8000 : 0x4000;
|
||||
break;
|
||||
case SECT_ROMX:
|
||||
maxsize = 0x4000;
|
||||
break;
|
||||
case SECT_VRAM:
|
||||
case SECT_SRAM:
|
||||
maxsize = 0x2000;
|
||||
break;
|
||||
case SECT_WRAM0:
|
||||
maxsize = (options & OPT_CONTWRAM) ? 0x2000 : 0x1000;
|
||||
break;
|
||||
case SECT_WRAMX:
|
||||
maxsize = 0x1000;
|
||||
break;
|
||||
case SECT_OAM:
|
||||
maxsize = 0xA0;
|
||||
break;
|
||||
case SECT_HRAM:
|
||||
maxsize = 0x7F;
|
||||
break;
|
||||
default:
|
||||
errx(1, "Section \"%s\" has an invalid section type.", pzName);
|
||||
break;
|
||||
}
|
||||
if (pSection->nByteSize > maxsize) {
|
||||
errx(1, "Section \"%s\" is bigger than the max size for that type: 0x%X > 0x%X",
|
||||
pzName, pSection->nByteSize, maxsize);
|
||||
}
|
||||
|
||||
/*
|
||||
* If the section doesn't contain data, it is ready
|
||||
*/
|
||||
if ((pSection->Type != SECT_ROMX) && (pSection->Type != SECT_ROM0))
|
||||
return pSection;
|
||||
|
||||
/* If there is no data to read, exit */
|
||||
if (pSection->nByteSize == 0) {
|
||||
/* Skip number of patches */
|
||||
readlong(f);
|
||||
pSection->pData = NULL;
|
||||
return pSection;
|
||||
}
|
||||
|
||||
pSection->pData = malloc(pSection->nByteSize);
|
||||
if (!pSection->pData)
|
||||
err(1, "%s: Couldn't allocate memory", __func__);
|
||||
|
||||
int32_t nNumberOfPatches;
|
||||
struct sPatch **ppPatch, *pPatch;
|
||||
|
||||
if (fread(pSection->pData, sizeof(uint8_t), pSection->nByteSize, f)
|
||||
!= pSection->nByteSize) {
|
||||
err(1, "%s: Read error", __func__);
|
||||
}
|
||||
|
||||
nNumberOfPatches = readlong(f);
|
||||
ppPatch = &pSection->pPatches;
|
||||
|
||||
/*
|
||||
* And patches...
|
||||
*/
|
||||
while (nNumberOfPatches--) {
|
||||
pPatch = malloc(sizeof(*pPatch));
|
||||
if (!pPatch)
|
||||
err(1, "%s: Couldn't allocate memory", __func__);
|
||||
|
||||
*ppPatch = pPatch;
|
||||
readasciiz(&pPatch->pzFilename, f);
|
||||
pPatch->nLineNo = readlong(f);
|
||||
pPatch->nOffset = readlong(f);
|
||||
pPatch->Type = (enum ePatchType)fgetc(f);
|
||||
pPatch->nRPNSize = readlong(f);
|
||||
|
||||
if (pPatch->nRPNSize > 0) {
|
||||
pPatch->pRPN = malloc(pPatch->nRPNSize);
|
||||
if (!pPatch->pRPN) {
|
||||
err(1, "%s: Couldn't allocate memory",
|
||||
__func__);
|
||||
}
|
||||
|
||||
if (fread(pPatch->pRPN, sizeof(uint8_t),
|
||||
pPatch->nRPNSize, f) != pPatch->nRPNSize) {
|
||||
errx(1, "%s: Read error", __func__);
|
||||
}
|
||||
sect_AddSection(section);
|
||||
fileSections[i] = section;
|
||||
if (nbSymPerSect[i]) {
|
||||
section->symbols = malloc(sizeof(*section->symbols)
|
||||
* nbSymPerSect[i]);
|
||||
if (!section->symbols)
|
||||
err(1, "%s: Couldn't link to symbols");
|
||||
} else {
|
||||
pPatch->pRPN = NULL;
|
||||
section->symbols = NULL;
|
||||
}
|
||||
section->nbSymbols = 0;
|
||||
}
|
||||
|
||||
pPatch->pNext = NULL;
|
||||
ppPatch = &(pPatch->pNext);
|
||||
}
|
||||
/* Give symbols pointers to their sections */
|
||||
for (uint32_t i = 0; i < nbSymbols; i++) {
|
||||
int32_t sectionID = fileSymbols[i]->sectionID;
|
||||
|
||||
return pSection;
|
||||
}
|
||||
|
||||
void obj_ReadRGB(FILE *pObjfile, char *tzObjectfile)
|
||||
{
|
||||
struct sSection *pFirstSection;
|
||||
int32_t nNumberOfSymbols, nNumberOfSections, i;
|
||||
|
||||
nNumberOfSymbols = readlong(pObjfile);
|
||||
nNumberOfSections = readlong(pObjfile);
|
||||
|
||||
/* First comes the symbols */
|
||||
|
||||
if (nNumberOfSymbols) {
|
||||
tSymbols = malloc(nNumberOfSymbols * sizeof(*tSymbols));
|
||||
if (!tSymbols)
|
||||
err(1, "%s: Couldn't allocate memory", __func__);
|
||||
|
||||
for (i = 0; i < nNumberOfSymbols; i += 1)
|
||||
tSymbols[i] = obj_ReadSymbol(pObjfile, tzObjectfile);
|
||||
if (sectionID == -1) {
|
||||
fileSymbols[i]->section = NULL;
|
||||
} else {
|
||||
tSymbols = NULL;
|
||||
fileSymbols[i]->section = fileSections[sectionID];
|
||||
/* Give the section a pointer to the symbol as well */
|
||||
linkSymToSect(fileSymbols[i], fileSections[sectionID]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Next we have the sections */
|
||||
|
||||
pFirstSection = NULL;
|
||||
while (nNumberOfSections--) {
|
||||
struct sSection *pNewSection;
|
||||
|
||||
pNewSection = obj_ReadRGBSection(pObjfile);
|
||||
pNewSection->nNumberOfSymbols = nNumberOfSymbols;
|
||||
if (pFirstSection == NULL)
|
||||
pFirstSection = pNewSection;
|
||||
}
|
||||
|
||||
/*
|
||||
* Fill in the pSection entry in the symbolstructure.
|
||||
* This REALLY needs some cleaning up... but, hey, it works
|
||||
/**
|
||||
* Reads an object file of any supported format
|
||||
* @param fileName The filename to report for errors
|
||||
*/
|
||||
|
||||
for (i = 0; i < nNumberOfSymbols; i += 1) {
|
||||
struct sSection *pConvSect = pFirstSection;
|
||||
|
||||
if ((tSymbols[i]->Type != SYM_IMPORT) &&
|
||||
(tSymbols[i]->nSectionID != -1)) {
|
||||
int32_t j = 0;
|
||||
|
||||
while (j != tSymbols[i]->nSectionID) {
|
||||
j += 1;
|
||||
pConvSect = pConvSect->pNext;
|
||||
}
|
||||
tSymbols[i]->pSection = pConvSect;
|
||||
} else {
|
||||
tSymbols[i]->pSection = NULL;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* The main objectfileloadroutine (phew)
|
||||
*/
|
||||
void obj_ReadOpenFile(FILE *pObjfile, char *tzObjectfile)
|
||||
void obj_ReadFile(char const *fileName)
|
||||
{
|
||||
char tzHeader[strlen(RGBDS_OBJECT_VERSION_STRING) + 1];
|
||||
FILE *file = strcmp("-", fileName) ? fopen(fileName, "rb") : stdin;
|
||||
|
||||
if (fread(tzHeader, sizeof(char), strlen(RGBDS_OBJECT_VERSION_STRING),
|
||||
pObjfile) != strlen(RGBDS_OBJECT_VERSION_STRING)) {
|
||||
errx(1, "%s: Read error", tzObjectfile);
|
||||
if (!file) {
|
||||
err(1, "Could not open file %s", fileName);
|
||||
return;
|
||||
}
|
||||
|
||||
tzHeader[strlen(RGBDS_OBJECT_VERSION_STRING)] = 0;
|
||||
/* Begin by reading the magic bytes and version number */
|
||||
uint8_t versionNumber;
|
||||
int matchedElems = fscanf(file, RGBDS_OBJECT_VERSION_STRING,
|
||||
&versionNumber);
|
||||
|
||||
if (strncmp(tzHeader, RGBDS_OBJECT_VERSION_STRING,
|
||||
strlen(RGBDS_OBJECT_VERSION_STRING)) == 0) {
|
||||
obj_ReadRGB(pObjfile, tzObjectfile);
|
||||
} else {
|
||||
int32_t i;
|
||||
if (matchedElems != 1)
|
||||
errx(1, "\"%s\" is not a RGBDS object file", fileName);
|
||||
/* TODO: support other versions? */
|
||||
if (versionNumber != 6)
|
||||
errx(1, "\"%s\" is an incompatible version %hhu object file",
|
||||
fileName, versionNumber);
|
||||
|
||||
for (i = 0; i < strlen(RGBDS_OBJECT_VERSION_STRING); i++)
|
||||
if (!isprint(tzHeader[i]))
|
||||
tzHeader[i] = '?';
|
||||
verbosePrint("Reading object file %s, version %hhu\n",
|
||||
fileName, versionNumber);
|
||||
|
||||
errx(1, "%s: Invalid file or object file version [%s]",
|
||||
tzObjectfile, tzHeader);
|
||||
}
|
||||
readRGB6File(file, fileName);
|
||||
|
||||
fclose(file);
|
||||
}
|
||||
|
||||
void obj_Readfile(char *tzObjectfile)
|
||||
void obj_DoSanityChecks(void)
|
||||
{
|
||||
FILE *pObjfile;
|
||||
|
||||
if (options & OPT_SMART_C_LINK)
|
||||
oReadLib = 1;
|
||||
else
|
||||
oReadLib = 0;
|
||||
|
||||
pObjfile = fopen(tzObjectfile, "rb");
|
||||
if (pObjfile == NULL)
|
||||
err(1, "Unable to open object '%s'", tzObjectfile);
|
||||
|
||||
obj_ReadOpenFile(pObjfile, tzObjectfile);
|
||||
fclose(pObjfile);
|
||||
|
||||
oReadLib = 0;
|
||||
sect_DoSanityChecks();
|
||||
}
|
||||
|
||||
int32_t file_Length(FILE *f)
|
||||
static void freeSection(struct Section *section, void *arg)
|
||||
{
|
||||
uint32_t r, p;
|
||||
(void)arg;
|
||||
|
||||
p = ftell(f);
|
||||
fseek(f, 0, SEEK_END);
|
||||
r = ftell(f);
|
||||
fseek(f, p, SEEK_SET);
|
||||
free(section->name);
|
||||
if (sect_HasData(section->type)) {
|
||||
free(section->data);
|
||||
for (int32_t i = 0; i < section->nbPatches; i++) {
|
||||
struct Patch *patch = §ion->patches[i];
|
||||
|
||||
return r;
|
||||
free(patch->fileName);
|
||||
free(patch->rpnExpression);
|
||||
}
|
||||
free(section->patches);
|
||||
}
|
||||
free(section->symbols);
|
||||
free(section);
|
||||
}
|
||||
|
||||
static void freeSymbol(struct Symbol *symbol)
|
||||
{
|
||||
free(symbol->name);
|
||||
if (symbol->type != SYMTYPE_IMPORT)
|
||||
free(symbol->fileName);
|
||||
free(symbol);
|
||||
}
|
||||
|
||||
void obj_Cleanup(void)
|
||||
{
|
||||
sym_CleanupSymbols();
|
||||
|
||||
sect_ForEach(freeSection, NULL);
|
||||
sect_CleanupSections();
|
||||
|
||||
struct SymbolList *list = symbolLists;
|
||||
|
||||
while (list) {
|
||||
for (size_t i = 0; i < list->nbSymbols; i++)
|
||||
freeSymbol(list->symbolList[i]);
|
||||
free(list->symbolList);
|
||||
|
||||
struct SymbolList *next = list->next;
|
||||
|
||||
free(list);
|
||||
list = next;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,174 +1,394 @@
|
||||
/*
|
||||
* This file is part of RGBDS.
|
||||
*
|
||||
* Copyright (c) 1997-2018, Carsten Sorensen and RGBDS contributors.
|
||||
*
|
||||
* SPDX-License-Identifier: MIT
|
||||
*/
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <stdint.h>
|
||||
|
||||
#include "link/output.h"
|
||||
#include "link/main.h"
|
||||
#include "link/section.h"
|
||||
#include "link/symbol.h"
|
||||
|
||||
#include "extern/err.h"
|
||||
|
||||
#include "link/mylink.h"
|
||||
#include "link/mapfile.h"
|
||||
#include "link/main.h"
|
||||
#include "link/assign.h"
|
||||
FILE *outputFile;
|
||||
FILE *overlayFile;
|
||||
FILE *symFile;
|
||||
FILE *mapFile;
|
||||
|
||||
char *tzOutname;
|
||||
char *tzOverlayname;
|
||||
struct SortedSection {
|
||||
struct Section const *section;
|
||||
struct SortedSection *next;
|
||||
};
|
||||
|
||||
int32_t MaxOverlayBank;
|
||||
static struct {
|
||||
uint32_t nbBanks;
|
||||
struct SortedSections {
|
||||
struct SortedSection *sections;
|
||||
struct SortedSection *zeroLenSections;
|
||||
} *banks;
|
||||
} sections[SECTTYPE_INVALID];
|
||||
|
||||
void writehome(FILE *f, FILE *f_overlay)
|
||||
void out_AddSection(struct Section const *section)
|
||||
{
|
||||
const struct sSection *pSect;
|
||||
uint8_t *mem;
|
||||
static uint32_t maxNbBanks[] = {
|
||||
[SECTTYPE_ROM0] = 1,
|
||||
[SECTTYPE_ROMX] = UINT32_MAX,
|
||||
[SECTTYPE_VRAM] = 2,
|
||||
[SECTTYPE_SRAM] = UINT32_MAX,
|
||||
[SECTTYPE_WRAM0] = 1,
|
||||
[SECTTYPE_WRAMX] = 7,
|
||||
[SECTTYPE_OAM] = 1,
|
||||
[SECTTYPE_HRAM] = 1
|
||||
};
|
||||
|
||||
mem = malloc(MaxAvail[BANK_INDEX_ROM0]);
|
||||
if (!mem)
|
||||
uint32_t targetBank = section->bank - bankranges[section->type][0];
|
||||
uint32_t minNbBanks = targetBank + 1;
|
||||
|
||||
if (minNbBanks > maxNbBanks[section->type])
|
||||
errx(1, "Section \"%s\" has invalid bank range (%u > %u)",
|
||||
section->name, section->bank,
|
||||
maxNbBanks[section->type] - 1);
|
||||
|
||||
if (minNbBanks > sections[section->type].nbBanks) {
|
||||
sections[section->type].banks =
|
||||
realloc(sections[section->type].banks,
|
||||
sizeof(*sections[0].banks) * minNbBanks);
|
||||
for (uint32_t i = sections[section->type].nbBanks;
|
||||
i < minNbBanks; i++) {
|
||||
sections[section->type].banks[i].sections = NULL;
|
||||
sections[section->type].banks[i].zeroLenSections = NULL;
|
||||
}
|
||||
sections[section->type].nbBanks = minNbBanks;
|
||||
}
|
||||
if (!sections[section->type].banks)
|
||||
err(1, "Failed to realloc banks");
|
||||
|
||||
struct SortedSection *newSection = malloc(sizeof(*newSection));
|
||||
struct SortedSection **ptr = section->size
|
||||
? §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;
|
||||
}
|
||||
|
||||
/**
|
||||
* Performs sanity checks on the overlay file.
|
||||
*/
|
||||
static void checkOverlay(void)
|
||||
{
|
||||
if (!overlayFile)
|
||||
return;
|
||||
|
||||
if (f_overlay != NULL) {
|
||||
fseek(f_overlay, 0L, SEEK_SET);
|
||||
if (fread(mem, 1, MaxAvail[BANK_INDEX_ROM0], f_overlay) !=
|
||||
MaxAvail[BANK_INDEX_ROM0]) {
|
||||
warnx("Failed to read data from overlay file.");
|
||||
}
|
||||
} else {
|
||||
memset(mem, fillchar, MaxAvail[BANK_INDEX_ROM0]);
|
||||
}
|
||||
MapfileInitBank(0);
|
||||
|
||||
pSect = pSections;
|
||||
while (pSect) {
|
||||
if (pSect->Type == SECT_ROM0) {
|
||||
memcpy(mem + pSect->nOrg, pSect->pData,
|
||||
pSect->nByteSize);
|
||||
MapfileWriteSection(pSect);
|
||||
}
|
||||
pSect = pSect->pNext;
|
||||
if (fseek(overlayFile, 0, SEEK_END) != 0) {
|
||||
warnx("Overlay file is not seekable, cannot check if properly formed");
|
||||
return;
|
||||
}
|
||||
|
||||
MapfileCloseBank(area_Avail(0));
|
||||
long overlaySize = ftell(overlayFile);
|
||||
|
||||
fwrite(mem, 1, MaxAvail[BANK_INDEX_ROM0], f);
|
||||
free(mem);
|
||||
if (overlaySize % 0x4000)
|
||||
errx(1, "Overlay file must have a size multiple of 0x4000");
|
||||
|
||||
/* Reset back to beginning */
|
||||
fseek(overlayFile, 0, SEEK_SET);
|
||||
|
||||
uint32_t nbOverlayBanks = overlaySize / 0x4000 - 1;
|
||||
|
||||
if (nbOverlayBanks < 1)
|
||||
errx(1, "Overlay must be at least 0x8000 bytes large");
|
||||
|
||||
if (nbOverlayBanks > sections[SECTTYPE_ROMX].nbBanks) {
|
||||
sections[SECTTYPE_ROMX].banks =
|
||||
realloc(sections[SECTTYPE_ROMX].banks,
|
||||
sizeof(*sections[SECTTYPE_ROMX].banks) *
|
||||
nbOverlayBanks);
|
||||
if (!sections[SECTTYPE_ROMX].banks)
|
||||
err(1, "Failed to realloc banks for overlay");
|
||||
sections[SECTTYPE_ROMX].nbBanks = nbOverlayBanks;
|
||||
}
|
||||
}
|
||||
|
||||
void writebank(FILE *f, FILE *f_overlay, int32_t bank)
|
||||
/**
|
||||
* Write a ROM bank's sections to the output file.
|
||||
* @param bankSections The bank's sections, ordered by increasing address
|
||||
* @param baseOffset The address of the bank's first byte in GB address space
|
||||
* @param size The size of the bank
|
||||
*/
|
||||
static void writeBank(struct SortedSection *bankSections, uint16_t baseOffset,
|
||||
uint16_t size)
|
||||
{
|
||||
const struct sSection *pSect;
|
||||
uint8_t *mem;
|
||||
uint16_t offset = 0;
|
||||
|
||||
mem = malloc(MaxAvail[bank]);
|
||||
if (!mem)
|
||||
while (bankSections) {
|
||||
struct Section const *section = bankSections->section;
|
||||
|
||||
/* Output padding up to the next SECTION */
|
||||
while (offset + baseOffset < section->org) {
|
||||
putc_unlocked(overlayFile ? getc_unlocked(overlayFile)
|
||||
: padValue,
|
||||
outputFile);
|
||||
offset++;
|
||||
}
|
||||
|
||||
/* Output the section itself */
|
||||
fwrite(section->data, sizeof(*section->data), section->size,
|
||||
outputFile);
|
||||
if (overlayFile) {
|
||||
/* Skip bytes even with pipes */
|
||||
for (uint16_t i = 0; i < section->size; i++)
|
||||
getc_unlocked(overlayFile);
|
||||
}
|
||||
offset += section->size;
|
||||
|
||||
bankSections = bankSections->next;
|
||||
}
|
||||
|
||||
while (offset < size) {
|
||||
putc_unlocked(overlayFile ? getc_unlocked(overlayFile)
|
||||
: padValue,
|
||||
outputFile);
|
||||
offset++;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Writes a ROM file to the output.
|
||||
*/
|
||||
static void writeROM(void)
|
||||
{
|
||||
outputFile = openFile(outputFileName, "wb");
|
||||
overlayFile = openFile(overlayFileName, "rb");
|
||||
|
||||
checkOverlay();
|
||||
|
||||
if (outputFile) {
|
||||
flockfile(outputFile);
|
||||
if (overlayFile)
|
||||
flockfile(overlayFile);
|
||||
|
||||
if (sections[SECTTYPE_ROM0].nbBanks > 0)
|
||||
writeBank(sections[SECTTYPE_ROM0].banks[0].sections,
|
||||
0x0000, 0x4000);
|
||||
|
||||
for (uint32_t i = 0 ; i < sections[SECTTYPE_ROMX].nbBanks; i++)
|
||||
writeBank(sections[SECTTYPE_ROMX].banks[i].sections,
|
||||
0x4000, 0x4000);
|
||||
|
||||
if (overlayFile)
|
||||
funlockfile(overlayFile);
|
||||
funlockfile(outputFile);
|
||||
}
|
||||
|
||||
closeFile(outputFile);
|
||||
closeFile(overlayFile);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the lowest section by address out of the two
|
||||
* @param s1 One choice
|
||||
* @param s2 The other
|
||||
* @return The lowest section of the two, or the non-NULL one if applicable
|
||||
*/
|
||||
static struct SortedSection const **nextSection(struct SortedSection const **s1,
|
||||
struct SortedSection const **s2)
|
||||
{
|
||||
if (!*s1)
|
||||
return s2;
|
||||
if (!*s2)
|
||||
return s1;
|
||||
|
||||
return (*s1)->section->org < (*s2)->section->org ? s1 : s2;
|
||||
}
|
||||
|
||||
/**
|
||||
* Write a bank's contents to the sym file
|
||||
* @param bankSections The bank's sections
|
||||
*/
|
||||
static void writeSymBank(struct SortedSections const *bankSections)
|
||||
{
|
||||
if (!symFile)
|
||||
return;
|
||||
|
||||
if (f_overlay != NULL && bank <= MaxOverlayBank) {
|
||||
fseek(f_overlay, bank * 0x4000, SEEK_SET);
|
||||
if (fread(mem, 1, MaxAvail[bank], f_overlay) != MaxAvail[bank])
|
||||
warnx("Failed to read data from overlay file.");
|
||||
struct {
|
||||
struct SortedSection const *sections;
|
||||
#define sect sections->section /* Fake member as a shortcut */
|
||||
uint32_t i;
|
||||
struct Symbol const *sym;
|
||||
uint16_t addr;
|
||||
} sectList = { .sections = bankSections->sections, .i = 0 },
|
||||
zlSectList = { .sections = bankSections->zeroLenSections, .i = 0 },
|
||||
*minSectList;
|
||||
|
||||
for (;;) {
|
||||
while (sectList.sections
|
||||
&& sectList.i == sectList.sect->nbSymbols) {
|
||||
sectList.sections = sectList.sections->next;
|
||||
sectList.i = 0;
|
||||
}
|
||||
while (zlSectList.sections
|
||||
&& zlSectList.i == zlSectList.sect->nbSymbols) {
|
||||
zlSectList.sections = zlSectList.sections->next;
|
||||
zlSectList.i = 0;
|
||||
}
|
||||
|
||||
if (!sectList.sections && !zlSectList.sections) {
|
||||
break;
|
||||
} else if (sectList.sections && zlSectList.sections) {
|
||||
sectList.sym = sectList.sect->symbols[sectList.i];
|
||||
zlSectList.sym = zlSectList.sect->symbols[zlSectList.i];
|
||||
sectList.addr =
|
||||
sectList.sym->offset + sectList.sect->org;
|
||||
zlSectList.addr =
|
||||
zlSectList.sym->offset + zlSectList.sect->org;
|
||||
|
||||
minSectList = sectList.addr < zlSectList.addr
|
||||
? §List
|
||||
: &zlSectList;
|
||||
} else if (sectList.sections) {
|
||||
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++;
|
||||
}
|
||||
#undef sect
|
||||
}
|
||||
|
||||
MapfileCloseBank(area_Avail(bank));
|
||||
|
||||
fwrite(mem, 1, MaxAvail[bank], f);
|
||||
free(mem);
|
||||
}
|
||||
|
||||
void out_Setname(char *tzOutputfile)
|
||||
/**
|
||||
* Write a bank's contents to the map file
|
||||
* @param bankSections The bank's sections
|
||||
*/
|
||||
static void writeMapBank(struct SortedSections const *sectList,
|
||||
enum SectionType type, uint32_t bank)
|
||||
{
|
||||
tzOutname = tzOutputfile;
|
||||
if (!mapFile)
|
||||
return;
|
||||
|
||||
struct SortedSection const *section = sectList->sections;
|
||||
struct SortedSection const *zeroLenSection = sectList->zeroLenSections;
|
||||
|
||||
fprintf(mapFile, "%s bank #%u:\n", typeNames[type],
|
||||
bank + bankranges[type][0]);
|
||||
|
||||
uint16_t slack = maxsize[type];
|
||||
|
||||
while (section || zeroLenSection) {
|
||||
struct SortedSection const **pickedSection =
|
||||
nextSection(§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;
|
||||
}
|
||||
|
||||
void out_SetOverlayname(char *tzOverlayfile)
|
||||
if (slack == maxsize[type])
|
||||
fputs(" EMPTY\n\n", mapFile);
|
||||
else
|
||||
fprintf(mapFile, " SLACK: $%04x byte%s\n\n", slack,
|
||||
slack == 1 ? "" : "s");
|
||||
}
|
||||
|
||||
/**
|
||||
* Writes the sym and/or map files, if applicable.
|
||||
*/
|
||||
static void writeSymAndMap(void)
|
||||
{
|
||||
tzOverlayname = tzOverlayfile;
|
||||
if (!symFileName && !mapFileName)
|
||||
return;
|
||||
|
||||
enum SectionType typeMap[SECTTYPE_INVALID] = {
|
||||
SECTTYPE_ROM0,
|
||||
SECTTYPE_ROMX,
|
||||
SECTTYPE_VRAM,
|
||||
SECTTYPE_SRAM,
|
||||
SECTTYPE_WRAM0,
|
||||
SECTTYPE_WRAMX,
|
||||
SECTTYPE_OAM,
|
||||
SECTTYPE_HRAM
|
||||
};
|
||||
|
||||
symFile = openFile(symFileName, "w");
|
||||
mapFile = openFile(mapFileName, "w");
|
||||
|
||||
if (symFileName) {
|
||||
fputs("; File generated by rgblink\n", symFile);
|
||||
}
|
||||
|
||||
void Output(void)
|
||||
for (uint8_t i = 0; i < SECTTYPE_INVALID; i++) {
|
||||
enum SectionType type = typeMap[i];
|
||||
|
||||
if (sections[type].nbBanks > 0) {
|
||||
for (uint32_t bank = 0; bank < sections[type].nbBanks;
|
||||
bank++) {
|
||||
writeSymBank(§ions[type].banks[bank]);
|
||||
writeMapBank(§ions[type].banks[bank],
|
||||
type, bank);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
closeFile(symFile);
|
||||
closeFile(mapFile);
|
||||
}
|
||||
|
||||
static void cleanupSections(struct SortedSection *section)
|
||||
{
|
||||
int32_t i;
|
||||
FILE *f;
|
||||
FILE *f_overlay = NULL;
|
||||
while (section) {
|
||||
struct SortedSection *next = section->next;
|
||||
|
||||
/*
|
||||
* Load overlay
|
||||
*/
|
||||
|
||||
if (tzOverlayname) {
|
||||
f_overlay = fopen(tzOverlayname, "rb");
|
||||
|
||||
if (!f_overlay) {
|
||||
errx(1, "Failed to open overlay file %s\n",
|
||||
tzOverlayname);
|
||||
}
|
||||
|
||||
fseek(f_overlay, 0, SEEK_END);
|
||||
|
||||
if (ftell(f_overlay) % 0x4000 != 0)
|
||||
errx(1, "Overlay file must be aligned to 0x4000 bytes.");
|
||||
|
||||
MaxOverlayBank = (ftell(f_overlay) / 0x4000) - 1;
|
||||
|
||||
if (MaxOverlayBank < 1)
|
||||
errx(1, "Overlay file must be at least 0x8000 bytes.");
|
||||
|
||||
if (MaxOverlayBank > MaxBankUsed)
|
||||
MaxBankUsed = MaxOverlayBank;
|
||||
}
|
||||
|
||||
/*
|
||||
* Write ROM.
|
||||
*/
|
||||
|
||||
f = fopen(tzOutname, "wb");
|
||||
if (f != NULL) {
|
||||
writehome(f, f_overlay);
|
||||
for (i = 1; i <= MaxBankUsed; i += 1)
|
||||
writebank(f, f_overlay, i);
|
||||
|
||||
fclose(f);
|
||||
}
|
||||
|
||||
/*
|
||||
* Close overlay
|
||||
*/
|
||||
|
||||
if (tzOverlayname)
|
||||
fclose(f_overlay);
|
||||
|
||||
/*
|
||||
* Add regular sections to map and sym files.
|
||||
*/
|
||||
|
||||
for (i = BANK_INDEX_WRAM0; i < BANK_INDEX_MAX; i++) {
|
||||
const struct sSection *pSect;
|
||||
|
||||
MapfileInitBank(i);
|
||||
pSect = pSections;
|
||||
while (pSect) {
|
||||
if (pSect->nBank == i)
|
||||
MapfileWriteSection(pSect);
|
||||
pSect = pSect->pNext;
|
||||
}
|
||||
MapfileCloseBank(area_Avail(i));
|
||||
free(section);
|
||||
section = next;
|
||||
}
|
||||
}
|
||||
|
||||
static void cleanup(void)
|
||||
{
|
||||
for (enum SectionType type = 0; type < SECTTYPE_INVALID; type++) {
|
||||
if (sections[type].nbBanks > 0) {
|
||||
for (uint32_t i = 0; i < sections[type].nbBanks; i++) {
|
||||
struct SortedSections *bank =
|
||||
§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);
|
||||
}
|
||||
|
||||
497
src/link/patch.c
497
src/link/patch.c
@@ -1,344 +1,343 @@
|
||||
/*
|
||||
* This file is part of RGBDS.
|
||||
*
|
||||
* Copyright (c) 1997-2018, Carsten Sorensen and RGBDS contributors.
|
||||
*
|
||||
* SPDX-License-Identifier: MIT
|
||||
*/
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <limits.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "link/patch.h"
|
||||
#include "link/section.h"
|
||||
#include "link/symbol.h"
|
||||
|
||||
#include "linkdefs.h"
|
||||
|
||||
#include "extern/err.h"
|
||||
|
||||
#include "link/assign.h"
|
||||
#include "link/main.h"
|
||||
#include "link/mylink.h"
|
||||
#include "link/symbol.h"
|
||||
/* This is an "empty"-type stack */
|
||||
struct RPNStack {
|
||||
int32_t *buf;
|
||||
size_t size;
|
||||
size_t capacity;
|
||||
} stack;
|
||||
|
||||
#define RPN_STACK_SIZE 256
|
||||
|
||||
static struct sSection *pCurrentSection;
|
||||
static int32_t rpnstack[RPN_STACK_SIZE];
|
||||
static int32_t rpnp;
|
||||
int32_t nPC;
|
||||
|
||||
static void rpnpush(int32_t i)
|
||||
static inline void initRPNStack(void)
|
||||
{
|
||||
if (rpnp >= RPN_STACK_SIZE)
|
||||
errx(1, "RPN stack overflow");
|
||||
|
||||
rpnstack[rpnp] = i;
|
||||
rpnp++;
|
||||
stack.capacity = 64;
|
||||
stack.buf = malloc(sizeof(*stack.buf) * stack.capacity);
|
||||
if (!stack.buf)
|
||||
err(1, "Failed to init RPN stack");
|
||||
}
|
||||
|
||||
static int32_t rpnpop(void)
|
||||
static inline void clearRPNStack(void)
|
||||
{
|
||||
rpnp--;
|
||||
return rpnstack[rpnp];
|
||||
stack.size = 0;
|
||||
}
|
||||
|
||||
static int32_t getsymvalue(struct sPatch *pPatch, int32_t symid)
|
||||
static void pushRPN(int32_t value)
|
||||
{
|
||||
const struct sSymbol *tSymbol = pCurrentSection->tSymbols[symid];
|
||||
|
||||
switch (tSymbol->Type) {
|
||||
case SYM_IMPORT:
|
||||
return sym_GetValue(pPatch, tSymbol->pzName);
|
||||
|
||||
case SYM_EXPORT:
|
||||
case SYM_LOCAL:
|
||||
if (strcmp(tSymbol->pzName, "@") == 0)
|
||||
return nPC;
|
||||
|
||||
return tSymbol->nOffset + tSymbol->pSection->nOrg;
|
||||
|
||||
default:
|
||||
break;
|
||||
if (stack.size >= stack.capacity) {
|
||||
stack.capacity *= 2;
|
||||
stack.buf =
|
||||
realloc(stack.buf, sizeof(*stack.buf) * stack.capacity);
|
||||
if (!stack.buf)
|
||||
err(1, "Failed to resize RPN stack");
|
||||
}
|
||||
|
||||
errx(1, "%s: Unknown symbol type", __func__);
|
||||
stack.buf[stack.size] = value;
|
||||
stack.size++;
|
||||
}
|
||||
|
||||
static int32_t getrealbankfrominternalbank(int32_t n)
|
||||
static int32_t popRPN(void)
|
||||
{
|
||||
if (BankIndexIsWRAM0(n) || BankIndexIsROM0(n) ||
|
||||
BankIndexIsOAM(n) || BankIndexIsHRAM(n)) {
|
||||
return 0;
|
||||
} else if (BankIndexIsROMX(n)) {
|
||||
return n - BANK_INDEX_ROMX + 1;
|
||||
} else if (BankIndexIsWRAMX(n)) {
|
||||
return n - BANK_INDEX_WRAMX + 1;
|
||||
} else if (BankIndexIsVRAM(n)) {
|
||||
return n - BANK_INDEX_VRAM;
|
||||
} else if (BankIndexIsSRAM(n)) {
|
||||
return n - BANK_INDEX_SRAM;
|
||||
if (stack.size == 0)
|
||||
errx(1, "Internal error, RPN stack empty");
|
||||
|
||||
stack.size--;
|
||||
return stack.buf[stack.size];
|
||||
}
|
||||
|
||||
return n;
|
||||
}
|
||||
|
||||
static int32_t getsymbank(struct sPatch *pPatch, int32_t symid)
|
||||
static inline void freeRPNStack(void)
|
||||
{
|
||||
int32_t nBank;
|
||||
const struct sSymbol *tSymbol = pCurrentSection->tSymbols[symid];
|
||||
|
||||
switch (tSymbol->Type) {
|
||||
case SYM_IMPORT:
|
||||
nBank = sym_GetBank(pPatch, tSymbol->pzName);
|
||||
break;
|
||||
case SYM_EXPORT:
|
||||
case SYM_LOCAL:
|
||||
nBank = tSymbol->pSection->nBank;
|
||||
break;
|
||||
default:
|
||||
errx(1, "%s: Unknown symbol type", __func__);
|
||||
free(stack.buf);
|
||||
}
|
||||
|
||||
return getrealbankfrominternalbank(nBank);
|
||||
}
|
||||
/* RPN operators */
|
||||
|
||||
int32_t calcrpn(struct sPatch *pPatch)
|
||||
static uint8_t getRPNByte(uint8_t const **expression, int32_t *size,
|
||||
char const *fileName, int32_t lineNo)
|
||||
{
|
||||
int32_t t, size;
|
||||
uint8_t *rpn;
|
||||
uint8_t rpn_cmd;
|
||||
int32_t nBank;
|
||||
if (!(*size)--)
|
||||
errx(1, "%s(%d): RPN expression overread", fileName, lineNo);
|
||||
return *(*expression)++;
|
||||
}
|
||||
|
||||
rpnp = 0;
|
||||
/**
|
||||
* Compute a patch's value from its RPN string.
|
||||
* @param patch The patch to compute the value of
|
||||
* @param section The section the patch is contained in
|
||||
* @return The patch's value
|
||||
*/
|
||||
static int32_t computeRPNExpr(struct Patch const *patch,
|
||||
struct Section const *section)
|
||||
{
|
||||
uint8_t const *expression = patch->rpnExpression;
|
||||
int32_t size = patch->rpnSize;
|
||||
|
||||
size = pPatch->nRPNSize;
|
||||
rpn = pPatch->pRPN;
|
||||
pPatch->oRelocPatch = 0;
|
||||
clearRPNStack();
|
||||
|
||||
while (size > 0) {
|
||||
size -= 1;
|
||||
rpn_cmd = *rpn++;
|
||||
enum RPNCommand command = getRPNByte(&expression, &size,
|
||||
patch->fileName,
|
||||
patch->lineNo);
|
||||
int32_t value;
|
||||
|
||||
/*
|
||||
* Friendly reminder:
|
||||
* Be VERY careful with two `popRPN` in the same expression.
|
||||
* C does not guarantee order of evaluation of operands!!
|
||||
* So, if there are two `popRPN` in the same expression, make
|
||||
* sure the operation is commutative.
|
||||
*/
|
||||
switch (command) {
|
||||
struct Symbol const *symbol;
|
||||
char const *name;
|
||||
struct Section const *sect;
|
||||
|
||||
switch (rpn_cmd) {
|
||||
case RPN_ADD:
|
||||
rpnpush(rpnpop() + rpnpop());
|
||||
value = popRPN() + popRPN();
|
||||
break;
|
||||
case RPN_SUB:
|
||||
t = rpnpop();
|
||||
rpnpush(rpnpop() - t);
|
||||
value = popRPN();
|
||||
value = popRPN() - value;
|
||||
break;
|
||||
case RPN_MUL:
|
||||
rpnpush(rpnpop() * rpnpop());
|
||||
value = popRPN() * popRPN();
|
||||
break;
|
||||
case RPN_DIV:
|
||||
t = rpnpop();
|
||||
rpnpush(rpnpop() / t);
|
||||
value = popRPN();
|
||||
value = popRPN() / value;
|
||||
break;
|
||||
case RPN_MOD:
|
||||
t = rpnpop();
|
||||
rpnpush(rpnpop() % t);
|
||||
value = popRPN();
|
||||
value = popRPN() % value;
|
||||
break;
|
||||
case RPN_UNSUB:
|
||||
rpnpush(-rpnpop());
|
||||
value = -popRPN();
|
||||
break;
|
||||
|
||||
case RPN_OR:
|
||||
rpnpush(rpnpop() | rpnpop());
|
||||
value = popRPN() | popRPN();
|
||||
break;
|
||||
case RPN_AND:
|
||||
rpnpush(rpnpop() & rpnpop());
|
||||
value = popRPN() & popRPN();
|
||||
break;
|
||||
case RPN_XOR:
|
||||
rpnpush(rpnpop() ^ rpnpop());
|
||||
value = popRPN() ^ popRPN();
|
||||
break;
|
||||
case RPN_UNNOT:
|
||||
rpnpush(~rpnpop());
|
||||
value = ~popRPN();
|
||||
break;
|
||||
|
||||
case RPN_LOGAND:
|
||||
rpnpush(rpnpop() && rpnpop());
|
||||
value = popRPN();
|
||||
value = popRPN() && value;
|
||||
break;
|
||||
case RPN_LOGOR:
|
||||
rpnpush(rpnpop() || rpnpop());
|
||||
value = popRPN();
|
||||
value = popRPN() || value;
|
||||
break;
|
||||
case RPN_LOGUNNOT:
|
||||
rpnpush(!rpnpop());
|
||||
value = !popRPN();
|
||||
break;
|
||||
|
||||
case RPN_LOGEQ:
|
||||
rpnpush(rpnpop() == rpnpop());
|
||||
value = popRPN() == popRPN();
|
||||
break;
|
||||
case RPN_LOGNE:
|
||||
rpnpush(rpnpop() != rpnpop());
|
||||
value = popRPN() != popRPN();
|
||||
break;
|
||||
case RPN_LOGGT:
|
||||
t = rpnpop();
|
||||
rpnpush(rpnpop() > t);
|
||||
value = popRPN();
|
||||
value = popRPN() > value;
|
||||
break;
|
||||
case RPN_LOGLT:
|
||||
t = rpnpop();
|
||||
rpnpush(rpnpop() < t);
|
||||
value = popRPN();
|
||||
value = popRPN() < value;
|
||||
break;
|
||||
case RPN_LOGGE:
|
||||
t = rpnpop();
|
||||
rpnpush(rpnpop() >= t);
|
||||
value = popRPN();
|
||||
value = popRPN() >= value;
|
||||
break;
|
||||
case RPN_LOGLE:
|
||||
t = rpnpop();
|
||||
rpnpush(rpnpop() <= t);
|
||||
value = popRPN();
|
||||
value = popRPN() <= value;
|
||||
break;
|
||||
|
||||
/* FIXME: sanitize shifts */
|
||||
case RPN_SHL:
|
||||
t = rpnpop();
|
||||
rpnpush(rpnpop() << t);
|
||||
value = popRPN();
|
||||
value = popRPN() << value;
|
||||
break;
|
||||
case RPN_SHR:
|
||||
t = rpnpop();
|
||||
rpnpush(rpnpop() >> t);
|
||||
break;
|
||||
case RPN_HRAM:
|
||||
t = rpnpop();
|
||||
rpnpush(t & 0xFF);
|
||||
if (t < 0 || (t > 0xFF && t < 0xFF00) || t > 0xFFFF) {
|
||||
errx(1,
|
||||
"%s(%ld) : Value must be in the HRAM area",
|
||||
pPatch->pzFilename, pPatch->nLineNo);
|
||||
}
|
||||
break;
|
||||
case RPN_CONST:
|
||||
/* constant */
|
||||
t = (*rpn++);
|
||||
t |= (*rpn++) << 8;
|
||||
t |= (*rpn++) << 16;
|
||||
t |= (*rpn++) << 24;
|
||||
rpnpush(t);
|
||||
size -= 4;
|
||||
break;
|
||||
case RPN_SYM:
|
||||
/* symbol */
|
||||
t = (*rpn++);
|
||||
t |= (*rpn++) << 8;
|
||||
t |= (*rpn++) << 16;
|
||||
t |= (*rpn++) << 24;
|
||||
rpnpush(getsymvalue(pPatch, t));
|
||||
pPatch->oRelocPatch |= (getsymbank(pPatch, t) != -1);
|
||||
size -= 4;
|
||||
value = popRPN();
|
||||
value = popRPN() >> value;
|
||||
break;
|
||||
|
||||
case RPN_BANK_SYM:
|
||||
/* symbol */
|
||||
t = (*rpn++);
|
||||
t |= (*rpn++) << 8;
|
||||
t |= (*rpn++) << 16;
|
||||
t |= (*rpn++) << 24;
|
||||
rpnpush(getsymbank(pPatch, t));
|
||||
size -= 4;
|
||||
value = 0;
|
||||
for (uint8_t shift = 0; shift < 32; shift += 8)
|
||||
value |= getRPNByte(&expression, &size,
|
||||
patch->fileName,
|
||||
patch->lineNo) << shift;
|
||||
|
||||
symbol = section->fileSymbols[value];
|
||||
|
||||
/* If the symbol is defined elsewhere... */
|
||||
if (symbol->type == SYMTYPE_IMPORT) {
|
||||
struct Symbol const *symbolDefinition =
|
||||
sym_GetSymbol(symbol->name);
|
||||
if (!symbolDefinition)
|
||||
errx(1, "%s(%d): Unknown symbol \"%s\"",
|
||||
patch->fileName, patch->lineNo,
|
||||
symbol->name);
|
||||
symbol = symbolDefinition;
|
||||
}
|
||||
|
||||
value = symbol->section->bank;
|
||||
break;
|
||||
|
||||
case RPN_BANK_SECT:
|
||||
{
|
||||
char *name = (char *)rpn;
|
||||
name = (char const *)expression;
|
||||
while (getRPNByte(&expression, &size, patch->fileName,
|
||||
patch->lineNo))
|
||||
;
|
||||
|
||||
struct sSection *pSection = GetSectionByName(name);
|
||||
sect = sect_GetSection(name);
|
||||
|
||||
if (pSection == NULL) {
|
||||
errx(1,
|
||||
"%s(%ld) : Requested BANK() of section \"%s\", which was not found.\n",
|
||||
pPatch->pzFilename, pPatch->nLineNo,
|
||||
name);
|
||||
}
|
||||
if (!sect)
|
||||
errx(1, "%s(%d): Requested BANK() of section \"%s\", which was not found",
|
||||
patch->fileName, patch->lineNo, name);
|
||||
|
||||
nBank = pSection->nBank;
|
||||
rpnpush(getrealbankfrominternalbank(nBank));
|
||||
|
||||
int len = strlen(name);
|
||||
|
||||
size -= len + 1;
|
||||
rpn += len + 1;
|
||||
value = sect->bank;
|
||||
break;
|
||||
}
|
||||
|
||||
case RPN_BANK_SELF:
|
||||
nBank = pCurrentSection->nBank;
|
||||
rpnpush(getrealbankfrominternalbank(nBank));
|
||||
value = section->bank;
|
||||
break;
|
||||
default:
|
||||
errx(1, "%s: Invalid command %d\n", __func__,
|
||||
rpn_cmd);
|
||||
|
||||
case RPN_HRAM:
|
||||
value = popRPN();
|
||||
if (value < 0
|
||||
|| (value > 0xFF && value < 0xFF00)
|
||||
|| value > 0xFFFF)
|
||||
errx(1, "%s(%d): Value %d is not in HRAM range",
|
||||
patch->fileName, patch->lineNo, value);
|
||||
value &= 0xFF;
|
||||
break;
|
||||
}
|
||||
}
|
||||
return rpnpop();
|
||||
|
||||
case RPN_CONST:
|
||||
value = 0;
|
||||
for (uint8_t shift = 0; shift < 32; shift += 8)
|
||||
value |= getRPNByte(&expression, &size,
|
||||
patch->fileName,
|
||||
patch->lineNo) << shift;
|
||||
break;
|
||||
|
||||
case RPN_SYM:
|
||||
value = 0;
|
||||
for (uint8_t shift = 0; shift < 32; shift += 8)
|
||||
value |= getRPNByte(&expression, &size,
|
||||
patch->fileName,
|
||||
patch->lineNo) << shift;
|
||||
|
||||
symbol = section->fileSymbols[value];
|
||||
|
||||
/* If the symbol is defined elsewhere... */
|
||||
if (symbol->type == SYMTYPE_IMPORT) {
|
||||
struct Symbol const *symbolDefinition =
|
||||
sym_GetSymbol(symbol->name);
|
||||
if (!symbolDefinition)
|
||||
errx(1, "%s(%d): Unknown symbol \"%s\"",
|
||||
patch->fileName, patch->lineNo,
|
||||
symbol->name);
|
||||
symbol = symbolDefinition;
|
||||
}
|
||||
|
||||
void Patch(void)
|
||||
if (!strcmp(symbol->name, "@")) {
|
||||
value = section->org + patch->offset;
|
||||
} else {
|
||||
value = symbol->value;
|
||||
/* Symbols attached to sections have offsets */
|
||||
if (symbol->section)
|
||||
value += symbol->section->org;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
pushRPN(value);
|
||||
}
|
||||
|
||||
if (stack.size > 1)
|
||||
warnx("%s(%d): RPN stack has %lu entries on exit, not 1",
|
||||
patch->fileName, patch->lineNo, stack.size);
|
||||
|
||||
return popRPN();
|
||||
}
|
||||
|
||||
/**
|
||||
* Applies all of a section's patches
|
||||
* @param section The section to patch
|
||||
* @param arg Ignored callback arg
|
||||
*/
|
||||
static void applyPatches(struct Section *section, void *arg)
|
||||
{
|
||||
struct sSection *pSect;
|
||||
(void)arg;
|
||||
|
||||
pSect = pSections;
|
||||
while (pSect) {
|
||||
struct sPatch *pPatch;
|
||||
if (!sect_HasData(section->type))
|
||||
return;
|
||||
|
||||
pCurrentSection = pSect;
|
||||
pPatch = pSect->pPatches;
|
||||
while (pPatch) {
|
||||
int32_t t;
|
||||
int32_t nPatchOrg;
|
||||
verbosePrint("Patching section \"%s\"...\n", section->name);
|
||||
for (uint32_t patchID = 0; patchID < section->nbPatches; patchID++) {
|
||||
struct Patch *patch = §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,516 @@
|
||||
/*
|
||||
* This file is part of RGBDS.
|
||||
*
|
||||
* Copyright (c) 2017-2018, Antonio Nino Diaz and RGBDS contributors.
|
||||
*
|
||||
* SPDX-License-Identifier: MIT
|
||||
*/
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
#include <string.h>
|
||||
#include <ctype.h>
|
||||
|
||||
#include "link/main.h"
|
||||
#include "link/script.h"
|
||||
#include "link/section.h"
|
||||
|
||||
#include "extern/err.h"
|
||||
|
||||
#include "link/assign.h"
|
||||
#include "link/mylink.h"
|
||||
FILE *linkerScript;
|
||||
|
||||
static uint32_t lineNo;
|
||||
|
||||
static struct {
|
||||
uint32_t address; /* current address to write sections to */
|
||||
uint32_t top_address; /* not inclusive */
|
||||
enum eSectionType type;
|
||||
} bank[BANK_INDEX_MAX];
|
||||
FILE *file;
|
||||
uint32_t lineNo;
|
||||
char const *name;
|
||||
} *fileStack;
|
||||
|
||||
static int32_t current_bank = -1; /* Bank as seen by the bank array */
|
||||
static int32_t current_real_bank = -1; /* bank as seen by the GB */
|
||||
static uint32_t fileStackSize;
|
||||
static uint32_t fileStackIndex;
|
||||
|
||||
/* Current section attributes */
|
||||
static int32_t fix_org = -1;
|
||||
static int32_t fix_align = 1;
|
||||
|
||||
void script_InitSections(void)
|
||||
static void pushFile(char const *newFileName)
|
||||
{
|
||||
int32_t i;
|
||||
if (fileStackIndex == UINT32_MAX)
|
||||
errx(1, "%s(%u): INCLUDE recursion limit reached",
|
||||
linkerScriptName, lineNo);
|
||||
|
||||
for (i = 0; i < BANK_INDEX_MAX; i++) {
|
||||
if (BankIndexIsROM0(i)) {
|
||||
/* ROM0 bank */
|
||||
bank[i].address = 0x0000;
|
||||
if (options & OPT_TINY)
|
||||
bank[i].top_address = 0x8000;
|
||||
else
|
||||
bank[i].top_address = 0x4000;
|
||||
bank[i].type = SECT_ROM0;
|
||||
} else if (BankIndexIsROMX(i)) {
|
||||
/* Swappable ROM bank */
|
||||
bank[i].address = 0x4000;
|
||||
bank[i].top_address = 0x8000;
|
||||
bank[i].type = SECT_ROMX;
|
||||
} else if (BankIndexIsWRAM0(i)) {
|
||||
/* WRAM */
|
||||
bank[i].address = 0xC000;
|
||||
if (options & OPT_CONTWRAM)
|
||||
bank[i].top_address = 0xE000;
|
||||
else
|
||||
bank[i].top_address = 0xD000;
|
||||
bank[i].type = SECT_WRAM0;
|
||||
} else if (BankIndexIsSRAM(i)) {
|
||||
/* Swappable SRAM bank */
|
||||
bank[i].address = 0xA000;
|
||||
bank[i].top_address = 0xC000;
|
||||
bank[i].type = SECT_SRAM;
|
||||
} else if (BankIndexIsWRAMX(i)) {
|
||||
/* Swappable WRAM bank */
|
||||
bank[i].address = 0xD000;
|
||||
bank[i].top_address = 0xE000;
|
||||
bank[i].type = SECT_WRAMX;
|
||||
} else if (BankIndexIsVRAM(i)) {
|
||||
/* Swappable VRAM bank */
|
||||
bank[i].address = 0x8000;
|
||||
bank[i].type = SECT_VRAM;
|
||||
if (options & OPT_DMG_MODE && i != BANK_INDEX_VRAM) {
|
||||
/* In DMG the only available bank is bank 0. */
|
||||
bank[i].top_address = 0x8000;
|
||||
} else {
|
||||
bank[i].top_address = 0xA000;
|
||||
}
|
||||
} else if (BankIndexIsOAM(i)) {
|
||||
/* OAM */
|
||||
bank[i].address = 0xFE00;
|
||||
bank[i].top_address = 0xFEA0;
|
||||
bank[i].type = SECT_OAM;
|
||||
} else if (BankIndexIsHRAM(i)) {
|
||||
/* HRAM */
|
||||
bank[i].address = 0xFF80;
|
||||
bank[i].top_address = 0xFFFF;
|
||||
bank[i].type = SECT_HRAM;
|
||||
} else {
|
||||
errx(1, "%s: Unknown bank type %d", __func__, i);
|
||||
}
|
||||
}
|
||||
if (fileStackIndex == fileStackSize) {
|
||||
if (!fileStackSize) /* Init file stack */
|
||||
fileStackSize = 4;
|
||||
fileStackSize *= 2;
|
||||
fileStack = realloc(fileStack,
|
||||
sizeof(*fileStack) * fileStackSize);
|
||||
if (!fileStack)
|
||||
err(1, "%s(%u): Internal INCLUDE error",
|
||||
linkerScriptName, lineNo);
|
||||
}
|
||||
|
||||
void script_SetCurrentSectionType(const char *type, uint32_t bank_num)
|
||||
fileStack[fileStackIndex].file = linkerScript;
|
||||
fileStack[fileStackIndex].lineNo = lineNo;
|
||||
fileStack[fileStackIndex].name = linkerScriptName;
|
||||
fileStackIndex++;
|
||||
|
||||
linkerScript = fopen(newFileName, "r");
|
||||
if (!linkerScript)
|
||||
err(1, "%s(%u): Could not open \"%s\"",
|
||||
linkerScriptName, lineNo, newFileName);
|
||||
lineNo = 1;
|
||||
linkerScriptName = newFileName;
|
||||
}
|
||||
|
||||
static bool popFile(void)
|
||||
{
|
||||
if (strcmp(type, "ROM0") == 0) {
|
||||
if (bank_num != 0)
|
||||
errx(1, "Trying to assign a bank number to ROM0.\n");
|
||||
current_bank = BANK_INDEX_ROM0;
|
||||
current_real_bank = 0;
|
||||
return;
|
||||
} else if (strcmp(type, "ROMX") == 0) {
|
||||
if (bank_num == 0)
|
||||
errx(1, "ROMX index can't be 0.\n");
|
||||
if (bank_num > BANK_COUNT_ROMX) {
|
||||
errx(1, "ROMX index too big (%d > %d).\n", bank_num,
|
||||
BANK_COUNT_ROMX);
|
||||
}
|
||||
current_bank = BANK_INDEX_ROMX + bank_num - 1;
|
||||
current_real_bank = bank_num;
|
||||
return;
|
||||
} else if (strcmp(type, "VRAM") == 0) {
|
||||
if (bank_num >= BANK_COUNT_VRAM) {
|
||||
errx(1, "VRAM index too big (%d >= %d).\n", bank_num,
|
||||
BANK_COUNT_VRAM);
|
||||
}
|
||||
current_bank = BANK_INDEX_VRAM + bank_num;
|
||||
current_real_bank = bank_num;
|
||||
return;
|
||||
} else if (strcmp(type, "WRAM0") == 0) {
|
||||
if (bank_num != 0)
|
||||
errx(1, "Trying to assign a bank number to WRAM0.\n");
|
||||
if (!fileStackIndex)
|
||||
return false;
|
||||
|
||||
current_bank = BANK_INDEX_WRAM0;
|
||||
current_real_bank = 0;
|
||||
return;
|
||||
} else if (strcmp(type, "WRAMX") == 0) {
|
||||
if (bank_num == 0)
|
||||
errx(1, "WRAMX index can't be 0.\n");
|
||||
if (bank_num > BANK_COUNT_WRAMX) {
|
||||
errx(1, "WRAMX index too big (%d > %d).\n", bank_num,
|
||||
BANK_COUNT_WRAMX);
|
||||
fileStackIndex--;
|
||||
linkerScript = fileStack[fileStackIndex].file;
|
||||
lineNo = fileStack[fileStackIndex].lineNo;
|
||||
linkerScriptName = fileStack[fileStackIndex].name;
|
||||
|
||||
return true;
|
||||
}
|
||||
current_bank = BANK_INDEX_WRAMX + bank_num - 1;
|
||||
current_real_bank = bank_num;
|
||||
return;
|
||||
} else if (strcmp(type, "SRAM") == 0) {
|
||||
if (bank_num >= BANK_COUNT_SRAM) {
|
||||
errx(1, "SRAM index too big (%d >= %d).\n", bank_num,
|
||||
BANK_COUNT_SRAM);
|
||||
|
||||
static inline bool isWhiteSpace(int c)
|
||||
{
|
||||
return c == ' ' || c == '\t';
|
||||
}
|
||||
current_bank = BANK_INDEX_SRAM + bank_num;
|
||||
current_real_bank = bank_num;
|
||||
return;
|
||||
} else if (strcmp(type, "OAM") == 0) {
|
||||
if (bank_num != 0) {
|
||||
errx(1, "%s: Trying to assign a bank number to OAM.\n",
|
||||
|
||||
static inline bool isNewline(int c)
|
||||
{
|
||||
return c == '\r' || c == '\n';
|
||||
}
|
||||
|
||||
/**
|
||||
* Try parsing a number, in base 16 if it begins with a dollar,
|
||||
* in base 10 otherwise
|
||||
* @param str The number to parse
|
||||
* @param number A pointer where the number will be written to
|
||||
* @return True if parsing was successful, false otherwise
|
||||
*/
|
||||
static bool tryParseNumber(char const *str, uint32_t *number)
|
||||
{
|
||||
static char const digits[] = {
|
||||
'0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
|
||||
'A', 'B', 'C', 'D', 'E', 'F'
|
||||
};
|
||||
uint8_t base = 10;
|
||||
|
||||
if (*str == '$') {
|
||||
str++;
|
||||
base = 16;
|
||||
}
|
||||
|
||||
/* An empty string is not a number */
|
||||
if (!*str)
|
||||
return false;
|
||||
|
||||
*number = 0;
|
||||
do {
|
||||
char chr = toupper(*str++);
|
||||
uint8_t digit = 0;
|
||||
|
||||
while (digit < base) {
|
||||
if (chr == digits[digit])
|
||||
break;
|
||||
digit++;
|
||||
}
|
||||
if (digit == base)
|
||||
return false;
|
||||
*number = *number * base + digit;
|
||||
} while (*str);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
enum LinkerScriptTokenType {
|
||||
TOKEN_NEWLINE,
|
||||
TOKEN_COMMAND,
|
||||
TOKEN_BANK,
|
||||
TOKEN_INCLUDE,
|
||||
TOKEN_NUMBER,
|
||||
TOKEN_STRING,
|
||||
TOKEN_EOF,
|
||||
|
||||
TOKEN_INVALID
|
||||
};
|
||||
|
||||
char const *tokenTypes[] = {
|
||||
[TOKEN_NEWLINE] = "newline",
|
||||
[TOKEN_COMMAND] = "command",
|
||||
[TOKEN_BANK] = "bank command",
|
||||
[TOKEN_NUMBER] = "number",
|
||||
[TOKEN_STRING] = "string",
|
||||
[TOKEN_EOF] = "end of file"
|
||||
};
|
||||
|
||||
enum LinkerScriptCommand {
|
||||
COMMAND_ORG,
|
||||
COMMAND_ALIGN,
|
||||
|
||||
COMMAND_INVALID
|
||||
};
|
||||
|
||||
struct LinkerScriptToken {
|
||||
enum LinkerScriptTokenType type;
|
||||
union LinkerScriptTokenAttr {
|
||||
enum LinkerScriptCommand command;
|
||||
enum SectionType secttype;
|
||||
uint32_t number;
|
||||
char *string;
|
||||
} attr;
|
||||
};
|
||||
|
||||
static char const * const commands[] = {
|
||||
[COMMAND_ORG] = "ORG",
|
||||
[COMMAND_ALIGN] = "ALIGN"
|
||||
};
|
||||
|
||||
static int readChar(FILE *file)
|
||||
{
|
||||
int curchar = getc_unlocked(file);
|
||||
|
||||
if (curchar == EOF && ferror(file))
|
||||
err(1, "%s(%u): Unexpected error in %s", linkerScriptName,
|
||||
lineNo, __func__);
|
||||
return curchar;
|
||||
}
|
||||
|
||||
static struct LinkerScriptToken const *nextToken(void)
|
||||
{
|
||||
static struct LinkerScriptToken token;
|
||||
int curchar;
|
||||
|
||||
/* If the token has a string, make sure to avoid leaking it */
|
||||
if (token.type == TOKEN_STRING)
|
||||
free(token.attr.string);
|
||||
|
||||
/* Skip initial whitespace... */
|
||||
do
|
||||
curchar = readChar(linkerScript);
|
||||
while (isWhiteSpace(curchar));
|
||||
|
||||
/* If this is a comment, skip to the end of the line */
|
||||
if (curchar == ';') {
|
||||
do
|
||||
curchar = readChar(linkerScript);
|
||||
while (!isNewline(curchar) && curchar != EOF);
|
||||
}
|
||||
|
||||
if (curchar == EOF) {
|
||||
token.type = TOKEN_EOF;
|
||||
} else if (isNewline(curchar)) {
|
||||
/* If we have a newline char, this is a newline token */
|
||||
token.type = TOKEN_NEWLINE;
|
||||
|
||||
/* FIXME: This works with CRLF newlines, but not CR-only */
|
||||
if (curchar == '\r')
|
||||
readChar(linkerScript); /* Read and discard LF */
|
||||
} else if (curchar == '"') {
|
||||
/* If we have a string start, this is a string */
|
||||
token.type = TOKEN_STRING;
|
||||
token.attr.string = NULL; /* Force initial alloc */
|
||||
|
||||
size_t size = 0;
|
||||
size_t capacity = 16; /* Half of the default capacity */
|
||||
|
||||
do {
|
||||
curchar = readChar(linkerScript);
|
||||
if (curchar == EOF || isNewline(curchar))
|
||||
errx(1, "%s(%u): Unterminated string",
|
||||
linkerScriptName, lineNo);
|
||||
else if (curchar == '"')
|
||||
/* Quotes force a string termination */
|
||||
curchar = '\0';
|
||||
|
||||
if (size >= capacity || token.attr.string == NULL) {
|
||||
capacity *= 2;
|
||||
token.attr.string = realloc(token.attr.string,
|
||||
capacity);
|
||||
if (!token.attr.string)
|
||||
err(1, "%s: Failed to allocate memory for string",
|
||||
__func__);
|
||||
}
|
||||
current_bank = BANK_INDEX_OAM;
|
||||
current_real_bank = 0;
|
||||
return;
|
||||
} else if (strcmp(type, "HRAM") == 0) {
|
||||
if (bank_num != 0) {
|
||||
errx(1, "%s: Trying to assign a bank number to HRAM.\n",
|
||||
token.attr.string[size++] = curchar;
|
||||
} while (curchar);
|
||||
} else {
|
||||
/* This is either a number, command or bank, that is: a word */
|
||||
char *str = NULL;
|
||||
size_t size = 0;
|
||||
size_t capacity = 8; /* Half of the default capacity */
|
||||
|
||||
for (;;) {
|
||||
if (size >= capacity || str == NULL) {
|
||||
capacity *= 2;
|
||||
str = realloc(str, capacity);
|
||||
if (!str)
|
||||
err(1, "%s: Failed to allocate memory for token",
|
||||
__func__);
|
||||
}
|
||||
current_bank = BANK_INDEX_HRAM;
|
||||
current_real_bank = 0;
|
||||
return;
|
||||
str[size] = toupper(curchar);
|
||||
size++;
|
||||
|
||||
if (!curchar)
|
||||
break;
|
||||
|
||||
curchar = readChar(linkerScript);
|
||||
/* Whitespace, a newline or a comment end the token */
|
||||
if (isWhiteSpace(curchar) || isNewline(curchar)
|
||||
|| curchar == ';') {
|
||||
ungetc(curchar, linkerScript);
|
||||
curchar = '\0';
|
||||
}
|
||||
}
|
||||
|
||||
errx(1, "%s: Unknown section type \"%s\".\n", __func__, type);
|
||||
token.type = TOKEN_INVALID;
|
||||
|
||||
/* Try to match a command */
|
||||
for (enum LinkerScriptCommand i = 0; i < COMMAND_INVALID; i++) {
|
||||
if (!strcmp(commands[i], str)) {
|
||||
token.type = TOKEN_COMMAND;
|
||||
token.attr.command = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void script_SetAddress(uint32_t addr)
|
||||
if (token.type == TOKEN_INVALID) {
|
||||
/* Try to match a bank specifier */
|
||||
for (enum SectionType type = 0; type < SECTTYPE_INVALID;
|
||||
type++) {
|
||||
if (!strcmp(typeNames[type], str)) {
|
||||
token.type = TOKEN_BANK;
|
||||
token.attr.secttype = type;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (token.type == TOKEN_INVALID) {
|
||||
/* Try to match an include token */
|
||||
if (!strcmp("INCLUDE", str))
|
||||
token.type = TOKEN_INCLUDE;
|
||||
}
|
||||
|
||||
if (token.type == TOKEN_INVALID) {
|
||||
/* None of the strings matched, do we have a number? */
|
||||
if (tryParseNumber(str, &token.attr.number))
|
||||
token.type = TOKEN_NUMBER;
|
||||
else
|
||||
errx(1, "%s(%u): Unknown token \"%s\"",
|
||||
linkerScriptName, lineNo, str);
|
||||
}
|
||||
|
||||
free(str);
|
||||
}
|
||||
|
||||
return &token;
|
||||
}
|
||||
|
||||
static void processCommand(enum LinkerScriptCommand command, uint16_t arg,
|
||||
uint16_t *pc)
|
||||
{
|
||||
if (current_bank == -1)
|
||||
errx(1, "Trying to set an address without assigned bank\n");
|
||||
switch (command) {
|
||||
case COMMAND_INVALID:
|
||||
trap_;
|
||||
|
||||
/* Make sure that we don't go back. */
|
||||
if (bank[current_bank].address > addr) {
|
||||
errx(1, "Trying to go to a previous address (0x%04X to 0x%04X)\n",
|
||||
bank[current_bank].address, addr);
|
||||
case COMMAND_ORG:
|
||||
*pc = arg;
|
||||
break;
|
||||
|
||||
case COMMAND_ALIGN:
|
||||
if (arg >= 16)
|
||||
arg = 0;
|
||||
else
|
||||
arg = (*pc + (1 << arg) - 1) & ~((1 << arg) - 1);
|
||||
}
|
||||
|
||||
bank[current_bank].address = addr;
|
||||
|
||||
/* Make sure we don't overflow */
|
||||
if (bank[current_bank].address >= bank[current_bank].top_address) {
|
||||
errx(1, "Bank overflowed (0x%04X >= 0x%04X)\n",
|
||||
bank[current_bank].address,
|
||||
bank[current_bank].top_address);
|
||||
if (arg < *pc)
|
||||
errx(1, "%s(%u): `%s` cannot be used to go backwards",
|
||||
linkerScriptName, lineNo, commands[command]);
|
||||
*pc = arg;
|
||||
}
|
||||
|
||||
fix_org = addr;
|
||||
}
|
||||
enum LinkerScriptParserState {
|
||||
PARSER_FIRSTTIME,
|
||||
PARSER_LINESTART,
|
||||
PARSER_INCLUDE, /* After an INCLUDE token */
|
||||
PARSER_LINEEND
|
||||
};
|
||||
|
||||
void script_SetAlignment(uint32_t alignment)
|
||||
/* Part of internal state, but has data that needs to be freed */
|
||||
static uint16_t *curaddr[SECTTYPE_INVALID];
|
||||
|
||||
/* Put as global to ensure it's initialized only once */
|
||||
static enum LinkerScriptParserState parserState = PARSER_FIRSTTIME;
|
||||
|
||||
struct SectionPlacement *script_NextSection(void)
|
||||
{
|
||||
if (current_bank == -1)
|
||||
errx(1, "Trying to set an alignment without assigned bank\n");
|
||||
static struct SectionPlacement section;
|
||||
static enum SectionType type;
|
||||
static uint32_t bank;
|
||||
static uint32_t bankID;
|
||||
|
||||
if (alignment > 15)
|
||||
errx(1, "Trying to set an alignment too big: %d\n", alignment);
|
||||
if (parserState == PARSER_FIRSTTIME) {
|
||||
lineNo = 1;
|
||||
|
||||
uint32_t size = 1 << alignment;
|
||||
uint32_t mask = size - 1;
|
||||
|
||||
if (bank[current_bank].address & mask) {
|
||||
bank[current_bank].address &= ~mask;
|
||||
bank[current_bank].address += size;
|
||||
/* Init PC for all banks */
|
||||
for (enum SectionType i = 0; i < SECTTYPE_INVALID; i++) {
|
||||
curaddr[i] = malloc(sizeof(*curaddr[i]) * nbbanks(i));
|
||||
for (uint32_t b = 0; b < nbbanks(i); b++)
|
||||
curaddr[i][b] = startaddr[i];
|
||||
}
|
||||
|
||||
/* Make sure we don't overflow */
|
||||
if (bank[current_bank].address >= bank[current_bank].top_address) {
|
||||
errx(1, "Bank overflowed (0x%04X >= 0x%04X)\n",
|
||||
bank[current_bank].address,
|
||||
bank[current_bank].top_address);
|
||||
type = SECTTYPE_INVALID;
|
||||
|
||||
parserState = PARSER_LINESTART;
|
||||
}
|
||||
|
||||
fix_align = size;
|
||||
for (;;) {
|
||||
struct LinkerScriptToken const *token = nextToken();
|
||||
enum LinkerScriptTokenType tokType;
|
||||
union LinkerScriptTokenAttr attr;
|
||||
bool hasArg;
|
||||
uint32_t arg;
|
||||
|
||||
if (type != SECTTYPE_INVALID) {
|
||||
if (curaddr[type][bankID] > endaddr(type) + 1)
|
||||
errx(1, "%s(%u): Sections would extend past the end of %s ($%04hx > $%04hx)",
|
||||
linkerScriptName, lineNo, typeNames[type],
|
||||
curaddr[type][bankID], endaddr(type));
|
||||
if (curaddr[type][bankID] < startaddr[type])
|
||||
errx(1, "%s(%u): PC underflowed ($%04hx < $%04hx)",
|
||||
linkerScriptName, lineNo,
|
||||
curaddr[type][bankID], startaddr[type]);
|
||||
}
|
||||
|
||||
void script_OutputSection(const char *section_name)
|
||||
switch (parserState) {
|
||||
case PARSER_FIRSTTIME:
|
||||
trap_;
|
||||
|
||||
case PARSER_LINESTART:
|
||||
switch (token->type) {
|
||||
case TOKEN_INVALID:
|
||||
trap_;
|
||||
|
||||
case TOKEN_EOF:
|
||||
if (!popFile())
|
||||
return NULL;
|
||||
parserState = PARSER_LINEEND;
|
||||
break;
|
||||
|
||||
case TOKEN_NUMBER:
|
||||
errx(1, "%s(%u): stray number \"%u\"",
|
||||
linkerScriptName, lineNo,
|
||||
token->attr.number);
|
||||
|
||||
case TOKEN_NEWLINE:
|
||||
lineNo++;
|
||||
break;
|
||||
|
||||
/* A stray string is a section name */
|
||||
case TOKEN_STRING:
|
||||
parserState = PARSER_LINEEND;
|
||||
|
||||
if (type == SECTTYPE_INVALID)
|
||||
errx(1, "%s(%u): Didn't specify a location before the section",
|
||||
linkerScriptName, lineNo);
|
||||
|
||||
section.section =
|
||||
sect_GetSection(token->attr.string);
|
||||
section.org = curaddr[type][bankID];
|
||||
section.bank = bank;
|
||||
|
||||
curaddr[type][bankID] += section.section->size;
|
||||
return §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, "%s(%u): Didn't specify a location before the command",
|
||||
linkerScriptName, lineNo);
|
||||
if (!hasArg)
|
||||
errx(1, "%s(%u): Command specified without an argument",
|
||||
linkerScriptName, lineNo);
|
||||
|
||||
processCommand(attr.command, arg,
|
||||
&curaddr[type][bankID]);
|
||||
} else { /* TOKEN_BANK */
|
||||
type = attr.secttype;
|
||||
/*
|
||||
* If there's only one bank,
|
||||
* specifying the number is optional.
|
||||
*/
|
||||
if (!hasArg && nbbanks(type) != 1)
|
||||
errx(1, "%s(%u): Didn't specify a bank number",
|
||||
linkerScriptName, lineNo);
|
||||
else if (!hasArg)
|
||||
arg = bankranges[type][0];
|
||||
else if (arg < bankranges[type][0])
|
||||
errx(1, "%s(%u): specified bank number is too low (%u < %u)",
|
||||
linkerScriptName, lineNo,
|
||||
arg, bankranges[type][0]);
|
||||
else if (arg > bankranges[type][1])
|
||||
errx(1, "%s(%u): specified bank number is too high (%u > %u)",
|
||||
linkerScriptName, lineNo,
|
||||
arg, bankranges[type][1]);
|
||||
bank = arg;
|
||||
bankID = arg - bankranges[type][0];
|
||||
}
|
||||
|
||||
/* If we read a token we shouldn't have... */
|
||||
if (token->type != TOKEN_NUMBER)
|
||||
goto lineend;
|
||||
break;
|
||||
|
||||
case TOKEN_INCLUDE:
|
||||
parserState = PARSER_INCLUDE;
|
||||
break;
|
||||
}
|
||||
break;
|
||||
|
||||
case PARSER_INCLUDE:
|
||||
if (token->type != TOKEN_STRING)
|
||||
errx(1, "%s(%u): Expected a file name after INCLUDE",
|
||||
linkerScriptName, lineNo);
|
||||
|
||||
/* Switch to that file */
|
||||
pushFile(token->attr.string);
|
||||
|
||||
parserState = PARSER_LINESTART;
|
||||
break;
|
||||
|
||||
case PARSER_LINEEND:
|
||||
lineend:
|
||||
lineNo++;
|
||||
parserState = PARSER_LINESTART;
|
||||
if (token->type == TOKEN_EOF) {
|
||||
if (!popFile())
|
||||
return NULL;
|
||||
parserState = PARSER_LINEEND;
|
||||
} else if (token->type != TOKEN_NEWLINE)
|
||||
errx(1, "%s(%u): Unexpected %s at the end of the line",
|
||||
linkerScriptName, lineNo,
|
||||
tokenTypes[token->type]);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void script_Cleanup(void)
|
||||
{
|
||||
if (current_bank == -1) {
|
||||
errx(1, "Trying to place section \"%s\" without assigned bank\n",
|
||||
section_name);
|
||||
}
|
||||
|
||||
if (!IsSectionSameTypeBankAndAttrs(section_name,
|
||||
bank[current_bank].type,
|
||||
current_real_bank,
|
||||
fix_org,
|
||||
fix_align)) {
|
||||
errx(1, "Different attributes for \"%s\" in source and linkerscript\n",
|
||||
section_name);
|
||||
}
|
||||
|
||||
/* Move section to its place. */
|
||||
bank[current_bank].address +=
|
||||
AssignSectionAddressAndBankByName(section_name,
|
||||
bank[current_bank].address,
|
||||
current_real_bank);
|
||||
|
||||
fix_org = -1;
|
||||
fix_align = 1;
|
||||
for (enum SectionType type = 0; type < SECTTYPE_INVALID; type++)
|
||||
free(curaddr[type]);
|
||||
}
|
||||
|
||||
168
src/link/section.c
Normal file
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);
|
||||
|
||||
if (beVerbose && collided)
|
||||
warnx("Symbol hashmap collision occurred!");
|
||||
}
|
||||
|
||||
errx(1,
|
||||
"%s(%ld) : Unknown symbol '%s'",
|
||||
pPatch->pzFilename, pPatch->nLineNo,
|
||||
tzName);
|
||||
}
|
||||
|
||||
int32_t sym_GetBank(struct sPatch *pPatch, char *tzName)
|
||||
struct Symbol *sym_GetSymbol(char const *name)
|
||||
{
|
||||
struct ISymbol **ppSym;
|
||||
|
||||
ppSym = &(tHash[calchash(tzName)]);
|
||||
while (*ppSym) {
|
||||
if (strcmp(tzName, (*ppSym)->pzName))
|
||||
ppSym = &((*ppSym)->pNext);
|
||||
else
|
||||
return ((*ppSym)->nBank);
|
||||
return (struct Symbol *)hash_GetElement(symbols, name);
|
||||
}
|
||||
|
||||
errx(1,
|
||||
"%s(%ld) : Unknown symbol '%s'",
|
||||
pPatch->pzFilename, pPatch->nLineNo,
|
||||
tzName);
|
||||
}
|
||||
|
||||
void sym_CreateSymbol(char *tzName, int32_t nValue, int32_t nBank,
|
||||
char *tzObjFileName, char *tzFileName, uint32_t nFileLine)
|
||||
void sym_CleanupSymbols(void)
|
||||
{
|
||||
if (strcmp(tzName, "@") == 0)
|
||||
return;
|
||||
|
||||
struct ISymbol **ppSym;
|
||||
|
||||
ppSym = &(tHash[calchash(tzName)]);
|
||||
|
||||
while (*ppSym) {
|
||||
if (strcmp(tzName, (*ppSym)->pzName)) {
|
||||
ppSym = &((*ppSym)->pNext);
|
||||
} else {
|
||||
if (nBank == -1)
|
||||
return;
|
||||
|
||||
errx(1, "'%s' in both %s : %s(%d) and %s : %s(%d)",
|
||||
tzName, tzObjFileName, tzFileName, nFileLine,
|
||||
(*ppSym)->tzObjFileName,
|
||||
(*ppSym)->tzFileName, (*ppSym)->nFileLine);
|
||||
}
|
||||
}
|
||||
|
||||
*ppSym = malloc(sizeof **ppSym);
|
||||
|
||||
if (*ppSym != NULL) {
|
||||
(*ppSym)->pzName = malloc(strlen(tzName) + 1);
|
||||
|
||||
if ((*ppSym)->pzName != NULL) {
|
||||
strcpy((*ppSym)->pzName, tzName);
|
||||
(*ppSym)->nValue = nValue;
|
||||
(*ppSym)->nBank = nBank;
|
||||
(*ppSym)->pNext = NULL;
|
||||
strncpy((*ppSym)->tzObjFileName, tzObjFileName,
|
||||
sizeof((*ppSym)->tzObjFileName));
|
||||
strncpy((*ppSym)->tzFileName, tzFileName,
|
||||
sizeof((*ppSym)->tzFileName));
|
||||
(*ppSym)->nFileLine = nFileLine;
|
||||
}
|
||||
}
|
||||
hash_EmptyMap(symbols);
|
||||
}
|
||||
|
||||
@@ -91,8 +91,8 @@ REPT NumberOfSections
|
||||
; decide (floating bank). This field is only valid for ROMX,
|
||||
; VRAM, WRAMX and SRAM sections.
|
||||
|
||||
LONG Align ; Alignment of this section (expressed as number of low bits
|
||||
; to leave as 0). -1 if not defined.
|
||||
LONG Align ; Alignment of this section, expressed as 1 << align. 1 if
|
||||
; not specified.
|
||||
|
||||
IF (Type == ROMX) || (Type == ROM0) ; Sections that can contain data.
|
||||
|
||||
|
||||
@@ -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