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
|
WARNFLAGS := -Wall
|
||||||
|
|
||||||
# Overridable CFLAGS
|
# Overridable CFLAGS
|
||||||
CFLAGS := -g
|
CFLAGS := -g -O0
|
||||||
# Non-overridable CFLAGS
|
# Non-overridable CFLAGS
|
||||||
REALCFLAGS := ${CFLAGS} ${WARNFLAGS} -std=c99 -D_POSIX_C_SOURCE=200809L \
|
REALCFLAGS := ${CFLAGS} ${WARNFLAGS} -std=c11 -D_POSIX_C_SOURCE=200809L \
|
||||||
-Iinclude -DBUILD_VERSION_STRING=\"${VERSION_STRING}\"
|
-D_DEFAULT_SOURCE -Iinclude \
|
||||||
|
-DBUILD_VERSION_STRING=\"${VERSION_STRING}\"
|
||||||
# Overridable LDFLAGS
|
# Overridable LDFLAGS
|
||||||
LDFLAGS :=
|
LDFLAGS :=
|
||||||
# Non-overridable 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 := \
|
rgblink_obj := \
|
||||||
src/link/assign.o \
|
src/link/assign.o \
|
||||||
src/link/lexer.o \
|
|
||||||
src/link/library.o \
|
|
||||||
src/link/main.o \
|
src/link/main.o \
|
||||||
src/link/mapfile.o \
|
|
||||||
src/link/object.o \
|
src/link/object.o \
|
||||||
src/link/output.o \
|
src/link/output.o \
|
||||||
src/link/patch.o \
|
src/link/patch.o \
|
||||||
src/link/parser.o \
|
|
||||||
src/link/script.o \
|
src/link/script.o \
|
||||||
|
src/link/section.o \
|
||||||
src/link/symbol.o \
|
src/link/symbol.o \
|
||||||
src/extern/err.o \
|
src/extern/err.o \
|
||||||
|
src/hashmap.o \
|
||||||
src/version.o
|
src/version.o
|
||||||
|
|
||||||
src/link/lexer.o: src/link/parser.h
|
|
||||||
|
|
||||||
rgbfix_obj := \
|
rgbfix_obj := \
|
||||||
src/fix/main.o \
|
src/fix/main.o \
|
||||||
src/extern/err.o \
|
src/extern/err.o \
|
||||||
|
|||||||
@@ -9,7 +9,8 @@
|
|||||||
#ifndef RGBDS_COMMON_H
|
#ifndef RGBDS_COMMON_H
|
||||||
#define 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 {
|
enum eBankCount {
|
||||||
BANK_COUNT_ROM0 = 1,
|
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 */
|
/* GCC or compatible */
|
||||||
#define noreturn_ __attribute__ ((noreturn))
|
#define noreturn_ __attribute__ ((noreturn))
|
||||||
#define unused_ __attribute__ ((unused))
|
#define unused_ __attribute__ ((unused))
|
||||||
|
#define trap_ __builtin_trap()
|
||||||
#else
|
#else
|
||||||
/* Unsupported, but no need to throw a fit */
|
/* Unsupported, but no need to throw a fit */
|
||||||
#define noreturn_
|
#define noreturn_
|
||||||
#define unused_
|
#define unused_
|
||||||
|
#define trap_
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#endif /* HELPERS_H */
|
#endif /* HELPERS_H */
|
||||||
|
|||||||
@@ -1,54 +1,29 @@
|
|||||||
/*
|
/*
|
||||||
* This file is part of RGBDS.
|
* 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
|
* SPDX-License-Identifier: MIT
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
/* Assigning all sections a place */
|
||||||
#ifndef RGBDS_LINK_ASSIGN_H
|
#ifndef RGBDS_LINK_ASSIGN_H
|
||||||
#define RGBDS_LINK_ASSIGN_H
|
#define RGBDS_LINK_ASSIGN_H
|
||||||
|
|
||||||
#include <stdint.h>
|
#include <stdint.h>
|
||||||
|
|
||||||
#include "common.h"
|
#include "common.h"
|
||||||
#include "mylink.h"
|
|
||||||
|
|
||||||
/* Bank numbers as seen by the linker */
|
extern uint64_t nbSectionsToAssign;
|
||||||
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 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);
|
* `free`s all assignment memory that was allocated.
|
||||||
void CreateSymbolTable(void);
|
*/
|
||||||
struct sSection *GetSectionByName(const char *name);
|
void assign_Cleanup(void);
|
||||||
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);
|
|
||||||
|
|
||||||
#endif /* RGBDS_LINK_ASSIGN_H */
|
#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.
|
* 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
|
* SPDX-License-Identifier: MIT
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
/* Declarations that all modules use, as well as `main` and related */
|
||||||
#ifndef RGBDS_LINK_MAIN_H
|
#ifndef RGBDS_LINK_MAIN_H
|
||||||
#define RGBDS_LINK_MAIN_H
|
#define RGBDS_LINK_MAIN_H
|
||||||
|
|
||||||
#include <stdint.h>
|
#include <stdint.h>
|
||||||
|
#include <stdbool.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
|
||||||
extern int32_t fillchar;
|
/* Variables related to CLI options */
|
||||||
extern char *smartlinkstartsymbol;
|
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 */
|
#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.
|
* 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
|
* SPDX-License-Identifier: MIT
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
/* Declarations related to processing of object (.o) files */
|
||||||
|
|
||||||
#ifndef RGBDS_LINK_OBJECT_H
|
#ifndef RGBDS_LINK_OBJECT_H
|
||||||
#define 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 */
|
#endif /* RGBDS_LINK_OBJECT_H */
|
||||||
|
|||||||
@@ -1,16 +1,28 @@
|
|||||||
/*
|
/*
|
||||||
* This file is part of RGBDS.
|
* 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
|
* SPDX-License-Identifier: MIT
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
/* Outputting the result of linking */
|
||||||
#ifndef RGBDS_LINK_OUTPUT_H
|
#ifndef RGBDS_LINK_OUTPUT_H
|
||||||
#define RGBDS_LINK_OUTPUT_H
|
#define RGBDS_LINK_OUTPUT_H
|
||||||
|
|
||||||
void out_Setname(char *tzOutputfile);
|
#include <stdint.h>
|
||||||
void out_SetOverlayname(char *tzOverlayfile);
|
|
||||||
void Output(void);
|
#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 */
|
#endif /* RGBDS_LINK_OUTPUT_H */
|
||||||
|
|||||||
@@ -1,17 +1,18 @@
|
|||||||
/*
|
/*
|
||||||
* This file is part of RGBDS.
|
* 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
|
* SPDX-License-Identifier: MIT
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
/* Applying patches to SECTIONs */
|
||||||
#ifndef RGBDS_LINK_PATCH_H
|
#ifndef RGBDS_LINK_PATCH_H
|
||||||
#define RGBDS_LINK_PATCH_H
|
#define RGBDS_LINK_PATCH_H
|
||||||
|
|
||||||
#include <stdint.h>
|
/**
|
||||||
|
* Applies all SECTIONs' patches to them
|
||||||
void Patch(void);
|
*/
|
||||||
extern int32_t nPC;
|
void patch_ApplyPatches(void);
|
||||||
|
|
||||||
#endif /* RGBDS_LINK_PATCH_H */
|
#endif /* RGBDS_LINK_PATCH_H */
|
||||||
|
|||||||
@@ -1,30 +1,36 @@
|
|||||||
/*
|
/*
|
||||||
* This file is part of RGBDS.
|
* 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
|
* SPDX-License-Identifier: MIT
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
/* Parsing a linker script */
|
||||||
#ifndef RGBDS_LINK_SCRIPT_H
|
#ifndef RGBDS_LINK_SCRIPT_H
|
||||||
#define RGBDS_LINK_SCRIPT_H
|
#define RGBDS_LINK_SCRIPT_H
|
||||||
|
|
||||||
#include <stdint.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);
|
* Parses the linker script to return the next section constraint
|
||||||
void script_IncludePop(void);
|
* @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);
|
* `free`s all assignment memory that was allocated.
|
||||||
void script_SetAddress(uint32_t addr);
|
*/
|
||||||
void script_SetAlignment(uint32_t alignment);
|
void script_Cleanup(void);
|
||||||
void script_OutputSection(const char *section_name);
|
|
||||||
|
|
||||||
#endif /* RGBDS_LINK_SCRIPT_H */
|
#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.
|
* 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
|
* SPDX-License-Identifier: MIT
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
/* Declarations manipulating symbols */
|
||||||
#ifndef RGBDS_LINK_SYMBOL_H
|
#ifndef RGBDS_LINK_SYMBOL_H
|
||||||
#define RGBDS_LINK_SYMBOL_H
|
#define RGBDS_LINK_SYMBOL_H
|
||||||
|
|
||||||
|
/* GUIDELINE: external code MUST NOT BE AWARE of the data structure used!! */
|
||||||
|
|
||||||
#include <stdint.h>
|
#include <stdint.h>
|
||||||
|
|
||||||
void sym_Init(void);
|
#include "linkdefs.h"
|
||||||
void sym_CreateSymbol(char *tzName, int32_t nValue, int32_t nBank,
|
|
||||||
char *tzObjFileName, char *tzFileName,
|
struct Symbol {
|
||||||
uint32_t nFileLine);
|
/* Info contained in the object files */
|
||||||
int32_t sym_GetValue(struct sPatch *pPatch, char *tzName);
|
char *name;
|
||||||
int32_t sym_GetBank(struct sPatch *pPatch, char *tzName);
|
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 */
|
#endif /* RGBDS_LINK_SYMBOL_H */
|
||||||
|
|||||||
@@ -9,7 +9,7 @@
|
|||||||
#ifndef RGBDS_LINKDEFS_H
|
#ifndef RGBDS_LINKDEFS_H
|
||||||
#define RGBDS_LINKDEFS_H
|
#define RGBDS_LINKDEFS_H
|
||||||
|
|
||||||
enum eRpnData {
|
enum RPNCommand {
|
||||||
RPN_ADD = 0x00,
|
RPN_ADD = 0x00,
|
||||||
RPN_SUB = 0x01,
|
RPN_SUB = 0x01,
|
||||||
RPN_MUL = 0x02,
|
RPN_MUL = 0x02,
|
||||||
@@ -46,28 +46,43 @@ enum eRpnData {
|
|||||||
RPN_SYM = 0x81
|
RPN_SYM = 0x81
|
||||||
};
|
};
|
||||||
|
|
||||||
enum eSectionType {
|
enum SectionType {
|
||||||
SECT_WRAM0 = 0x00,
|
SECTTYPE_WRAM0,
|
||||||
SECT_VRAM = 0x01,
|
SECTTYPE_VRAM,
|
||||||
SECT_ROMX = 0x02,
|
SECTTYPE_ROMX,
|
||||||
SECT_ROM0 = 0x03,
|
SECTTYPE_ROM0,
|
||||||
SECT_HRAM = 0x04,
|
SECTTYPE_HRAM,
|
||||||
SECT_WRAMX = 0x05,
|
SECTTYPE_WRAMX,
|
||||||
SECT_SRAM = 0x06,
|
SECTTYPE_SRAM,
|
||||||
SECT_OAM = 0x07
|
SECTTYPE_OAM,
|
||||||
|
|
||||||
|
SECTTYPE_INVALID
|
||||||
};
|
};
|
||||||
|
|
||||||
enum eSymbolType {
|
enum SymbolType {
|
||||||
SYM_LOCAL = 0x00,
|
SYMTYPE_LOCAL,
|
||||||
SYM_IMPORT = 0x01,
|
SYMTYPE_IMPORT,
|
||||||
SYM_EXPORT = 0x02
|
SYMTYPE_EXPORT
|
||||||
};
|
};
|
||||||
|
|
||||||
enum ePatchType {
|
enum PatchType {
|
||||||
PATCH_BYTE = 0x00,
|
PATCHTYPE_BYTE,
|
||||||
PATCH_WORD_L = 0x01,
|
PATCHTYPE_WORD,
|
||||||
PATCH_LONG_L = 0x02,
|
PATCHTYPE_LONG,
|
||||||
PATCH_BYTE_JR = 0x03
|
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 */
|
#endif /* RGBDS_LINKDEFS_H */
|
||||||
|
|||||||
@@ -45,22 +45,22 @@ static void bankrangecheck(char *name, uint32_t secttype, int32_t org,
|
|||||||
char *stype = NULL;
|
char *stype = NULL;
|
||||||
|
|
||||||
switch (secttype) {
|
switch (secttype) {
|
||||||
case SECT_ROMX:
|
case SECTTYPE_ROMX:
|
||||||
stype = "ROMX";
|
stype = "ROMX";
|
||||||
minbank = BANK_MIN_ROMX;
|
minbank = BANK_MIN_ROMX;
|
||||||
maxbank = BANK_MAX_ROMX;
|
maxbank = BANK_MAX_ROMX;
|
||||||
break;
|
break;
|
||||||
case SECT_SRAM:
|
case SECTTYPE_SRAM:
|
||||||
stype = "SRAM";
|
stype = "SRAM";
|
||||||
minbank = BANK_MIN_SRAM;
|
minbank = BANK_MIN_SRAM;
|
||||||
maxbank = BANK_MAX_SRAM;
|
maxbank = BANK_MAX_SRAM;
|
||||||
break;
|
break;
|
||||||
case SECT_WRAMX:
|
case SECTTYPE_WRAMX:
|
||||||
stype = "WRAMX";
|
stype = "WRAMX";
|
||||||
minbank = BANK_MIN_WRAMX;
|
minbank = BANK_MIN_WRAMX;
|
||||||
maxbank = BANK_MAX_WRAMX;
|
maxbank = BANK_MAX_WRAMX;
|
||||||
break;
|
break;
|
||||||
case SECT_VRAM:
|
case SECTTYPE_VRAM:
|
||||||
stype = "VRAM";
|
stype = "VRAM";
|
||||||
minbank = BANK_MIN_VRAM;
|
minbank = BANK_MIN_VRAM;
|
||||||
maxbank = BANK_MAX_VRAM;
|
maxbank = BANK_MAX_VRAM;
|
||||||
@@ -1523,33 +1523,33 @@ section : T_POP_SECTION string comma sectiontype
|
|||||||
}
|
}
|
||||||
;
|
;
|
||||||
|
|
||||||
sectiontype : T_SECT_WRAM0 { $$ = SECT_WRAM0; }
|
sectiontype : T_SECT_WRAM0 { $$ = SECTTYPE_WRAM0; }
|
||||||
| T_SECT_VRAM { $$ = SECT_VRAM; }
|
| T_SECT_VRAM { $$ = SECTTYPE_VRAM; }
|
||||||
| T_SECT_ROMX { $$ = SECT_ROMX; }
|
| T_SECT_ROMX { $$ = SECTTYPE_ROMX; }
|
||||||
| T_SECT_ROM0 { $$ = SECT_ROM0; }
|
| T_SECT_ROM0 { $$ = SECTTYPE_ROM0; }
|
||||||
| T_SECT_HRAM { $$ = SECT_HRAM; }
|
| T_SECT_HRAM { $$ = SECTTYPE_HRAM; }
|
||||||
| T_SECT_WRAMX { $$ = SECT_WRAMX; }
|
| T_SECT_WRAMX { $$ = SECTTYPE_WRAMX; }
|
||||||
| T_SECT_SRAM { $$ = SECT_SRAM; }
|
| T_SECT_SRAM { $$ = SECTTYPE_SRAM; }
|
||||||
| T_SECT_OAM { $$ = SECT_OAM; }
|
| T_SECT_OAM { $$ = SECTTYPE_OAM; }
|
||||||
| T_SECT_HOME
|
| T_SECT_HOME
|
||||||
{
|
{
|
||||||
warning("HOME section name is deprecated, use ROM0 instead.");
|
warning("HOME section name is deprecated, use ROM0 instead.");
|
||||||
$$ = SECT_ROM0;
|
$$ = SECTTYPE_ROM0;
|
||||||
}
|
}
|
||||||
| T_SECT_DATA
|
| T_SECT_DATA
|
||||||
{
|
{
|
||||||
warning("DATA section name is deprecated, use ROMX instead.");
|
warning("DATA section name is deprecated, use ROMX instead.");
|
||||||
$$ = SECT_ROMX;
|
$$ = SECTTYPE_ROMX;
|
||||||
}
|
}
|
||||||
| T_SECT_CODE
|
| T_SECT_CODE
|
||||||
{
|
{
|
||||||
warning("CODE section name is deprecated, use ROMX instead.");
|
warning("CODE section name is deprecated, use ROMX instead.");
|
||||||
$$ = SECT_ROMX;
|
$$ = SECTTYPE_ROMX;
|
||||||
}
|
}
|
||||||
| T_SECT_BSS
|
| T_SECT_BSS
|
||||||
{
|
{
|
||||||
warning("BSS section name is deprecated, use WRAM0 instead.");
|
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)
|
static uint32_t getmaxsectionsize(uint32_t secttype, char *sectname)
|
||||||
{
|
{
|
||||||
switch (secttype) {
|
switch (secttype) {
|
||||||
case SECT_ROM0:
|
case SECTTYPE_ROM0:
|
||||||
return 0x8000; /* If ROMX sections not used */
|
return 0x8000; /* If ROMX sections not used */
|
||||||
case SECT_ROMX:
|
case SECTTYPE_ROMX:
|
||||||
return 0x4000;
|
return 0x4000;
|
||||||
case SECT_VRAM:
|
case SECTTYPE_VRAM:
|
||||||
return 0x2000;
|
return 0x2000;
|
||||||
case SECT_SRAM:
|
case SECTTYPE_SRAM:
|
||||||
return 0x2000;
|
return 0x2000;
|
||||||
case SECT_WRAM0:
|
case SECTTYPE_WRAM0:
|
||||||
return 0x2000; /* If WRAMX sections not used */
|
return 0x2000; /* If WRAMX sections not used */
|
||||||
case SECT_WRAMX:
|
case SECTTYPE_WRAMX:
|
||||||
return 0x1000;
|
return 0x1000;
|
||||||
case SECT_OAM:
|
case SECTTYPE_OAM:
|
||||||
return 0xA0;
|
return 0xA0;
|
||||||
case SECT_HRAM:
|
case SECTTYPE_HRAM:
|
||||||
return 0x7F;
|
return 0x7F;
|
||||||
default:
|
default:
|
||||||
break;
|
break;
|
||||||
@@ -241,7 +241,7 @@ static void writesection(struct Section *pSect, FILE *f)
|
|||||||
fputlong(pSect->nBank, f);
|
fputlong(pSect->nBank, f);
|
||||||
fputlong(pSect->nAlign, f);
|
fputlong(pSect->nAlign, f);
|
||||||
|
|
||||||
if ((pSect->nType == SECT_ROM0) || (pSect->nType == SECT_ROMX)) {
|
if (sect_HasData(pSect->nType)) {
|
||||||
struct Patch *pPatch;
|
struct Patch *pPatch;
|
||||||
|
|
||||||
fwrite(pSect->tData, 1, pSect->nPC, f);
|
fwrite(pSect->tData, 1, pSect->nPC, f);
|
||||||
@@ -265,23 +265,23 @@ static void writesymbol(struct sSymbol *pSym, FILE *f)
|
|||||||
int32_t sectid;
|
int32_t sectid;
|
||||||
|
|
||||||
if (!(pSym->nType & SYMF_DEFINED)) {
|
if (!(pSym->nType & SYMF_DEFINED)) {
|
||||||
type = SYM_IMPORT;
|
type = SYMTYPE_IMPORT;
|
||||||
} else if (pSym->nType & SYMF_EXPORT) {
|
} else if (pSym->nType & SYMF_EXPORT) {
|
||||||
type = SYM_EXPORT;
|
type = SYMTYPE_EXPORT;
|
||||||
} else {
|
} else {
|
||||||
type = SYM_LOCAL;
|
type = SYMTYPE_LOCAL;
|
||||||
}
|
}
|
||||||
|
|
||||||
switch (type) {
|
switch (type) {
|
||||||
case SYM_LOCAL:
|
case SYMTYPE_LOCAL:
|
||||||
offset = pSym->nValue;
|
offset = pSym->nValue;
|
||||||
sectid = getsectid(pSym->pSection);
|
sectid = getsectid(pSym->pSection);
|
||||||
break;
|
break;
|
||||||
case SYM_IMPORT:
|
case SYMTYPE_IMPORT:
|
||||||
offset = 0;
|
offset = 0;
|
||||||
sectid = -1;
|
sectid = -1;
|
||||||
break;
|
break;
|
||||||
case SYM_EXPORT:
|
case SYMTYPE_EXPORT:
|
||||||
offset = pSym->nValue;
|
offset = pSym->nValue;
|
||||||
if (pSym->nType & SYMF_CONST)
|
if (pSym->nType & SYMF_CONST)
|
||||||
sectid = -1;
|
sectid = -1;
|
||||||
@@ -293,7 +293,7 @@ static void writesymbol(struct sSymbol *pSym, FILE *f)
|
|||||||
fputstring(pSym->tzName, f);
|
fputstring(pSym->tzName, f);
|
||||||
fputc(type, f);
|
fputc(type, f);
|
||||||
|
|
||||||
if (type != SYM_IMPORT) {
|
if (type != SYMTYPE_IMPORT) {
|
||||||
fputstring(pSym->tzFileName, f);
|
fputstring(pSym->tzFileName, f);
|
||||||
fputlong(pSym->nFileLine, f);
|
fputlong(pSym->nFileLine, f);
|
||||||
|
|
||||||
@@ -496,8 +496,7 @@ static void checksection(void)
|
|||||||
static void checkcodesection(void)
|
static void checkcodesection(void)
|
||||||
{
|
{
|
||||||
checksection();
|
checksection();
|
||||||
if (pCurrentSection->nType != SECT_ROM0 &&
|
if (!sect_HasData(pCurrentSection->nType)) {
|
||||||
pCurrentSection->nType != SECT_ROMX) {
|
|
||||||
fatalerror("Section '%s' cannot contain code or data (not ROM0 or ROMX)",
|
fatalerror("Section '%s' cannot contain code or data (not ROM0 or ROMX)",
|
||||||
pCurrentSection->pzName);
|
pCurrentSection->pzName);
|
||||||
} else if (nUnionDepth > 0) {
|
} else if (nUnionDepth > 0) {
|
||||||
@@ -570,8 +569,7 @@ void out_WriteObject(void)
|
|||||||
if (f == NULL)
|
if (f == NULL)
|
||||||
fatalerror("Couldn't write file '%s'\n", tzObjectname);
|
fatalerror("Couldn't write file '%s'\n", tzObjectname);
|
||||||
|
|
||||||
fwrite(RGBDS_OBJECT_VERSION_STRING, 1,
|
fprintf(f, RGBDS_OBJECT_VERSION_STRING, RGBDS_OBJECT_VERSION_NUMBER);
|
||||||
strlen(RGBDS_OBJECT_VERSION_STRING), f);
|
|
||||||
|
|
||||||
fputlong(countsymbols(), f);
|
fputlong(countsymbols(), f);
|
||||||
fputlong(countsections(), f);
|
fputlong(countsections(), f);
|
||||||
@@ -647,7 +645,7 @@ struct Section *out_FindSection(char *pzName, uint32_t secttype, int32_t org,
|
|||||||
pSect->charmap = NULL;
|
pSect->charmap = NULL;
|
||||||
|
|
||||||
/* It is only needed to allocate memory for ROM sections. */
|
/* It is only needed to allocate memory for ROM sections. */
|
||||||
if (secttype == SECT_ROM0 || secttype == SECT_ROMX) {
|
if (sect_HasData(secttype)) {
|
||||||
uint32_t sectsize;
|
uint32_t sectsize;
|
||||||
|
|
||||||
sectsize = getmaxsectionsize(secttype, pzName);
|
sectsize = getmaxsectionsize(secttype, pzName);
|
||||||
@@ -743,8 +741,7 @@ void out_Skip(int32_t skip)
|
|||||||
{
|
{
|
||||||
checksection();
|
checksection();
|
||||||
checksectionoverflow(skip);
|
checksectionoverflow(skip);
|
||||||
if (!((pCurrentSection->nType == SECT_ROM0)
|
if (!sect_HasData(pCurrentSection->nType)) {
|
||||||
|| (pCurrentSection->nType == SECT_ROMX))) {
|
|
||||||
pCurrentSection->nPC += skip;
|
pCurrentSection->nPC += skip;
|
||||||
nPC += skip;
|
nPC += skip;
|
||||||
pPCSymbol->nValue += skip;
|
pPCSymbol->nValue += skip;
|
||||||
@@ -779,7 +776,7 @@ void out_RelByte(struct Expression *expr)
|
|||||||
checksectionoverflow(1);
|
checksectionoverflow(1);
|
||||||
if (rpn_isReloc(expr)) {
|
if (rpn_isReloc(expr)) {
|
||||||
pCurrentSection->tData[nPC] = 0;
|
pCurrentSection->tData[nPC] = 0;
|
||||||
createpatch(PATCH_BYTE, expr);
|
createpatch(PATCHTYPE_BYTE, expr);
|
||||||
pCurrentSection->nPC += 1;
|
pCurrentSection->nPC += 1;
|
||||||
nPC += 1;
|
nPC += 1;
|
||||||
pPCSymbol->nValue += 1;
|
pPCSymbol->nValue += 1;
|
||||||
@@ -815,7 +812,7 @@ void out_RelWord(struct Expression *expr)
|
|||||||
if (rpn_isReloc(expr)) {
|
if (rpn_isReloc(expr)) {
|
||||||
pCurrentSection->tData[nPC] = 0;
|
pCurrentSection->tData[nPC] = 0;
|
||||||
pCurrentSection->tData[nPC + 1] = 0;
|
pCurrentSection->tData[nPC + 1] = 0;
|
||||||
createpatch(PATCH_WORD_L, expr);
|
createpatch(PATCHTYPE_WORD, expr);
|
||||||
pCurrentSection->nPC += 2;
|
pCurrentSection->nPC += 2;
|
||||||
nPC += 2;
|
nPC += 2;
|
||||||
pPCSymbol->nValue += 2;
|
pPCSymbol->nValue += 2;
|
||||||
@@ -854,7 +851,7 @@ void out_RelLong(struct Expression *expr)
|
|||||||
pCurrentSection->tData[nPC + 1] = 0;
|
pCurrentSection->tData[nPC + 1] = 0;
|
||||||
pCurrentSection->tData[nPC + 2] = 0;
|
pCurrentSection->tData[nPC + 2] = 0;
|
||||||
pCurrentSection->tData[nPC + 3] = 0;
|
pCurrentSection->tData[nPC + 3] = 0;
|
||||||
createpatch(PATCH_LONG_L, expr);
|
createpatch(PATCHTYPE_LONG, expr);
|
||||||
pCurrentSection->nPC += 4;
|
pCurrentSection->nPC += 4;
|
||||||
nPC += 4;
|
nPC += 4;
|
||||||
pPCSymbol->nValue += 4;
|
pPCSymbol->nValue += 4;
|
||||||
@@ -875,7 +872,7 @@ void out_PCRelByte(struct Expression *expr)
|
|||||||
|
|
||||||
/* Always let the linker calculate the offset. */
|
/* Always let the linker calculate the offset. */
|
||||||
pCurrentSection->tData[nPC] = 0;
|
pCurrentSection->tData[nPC] = 0;
|
||||||
createpatch(PATCH_BYTE_JR, expr);
|
createpatch(PATCHTYPE_JR, expr);
|
||||||
pCurrentSection->nPC += 1;
|
pCurrentSection->nPC += 1;
|
||||||
nPC += 1;
|
nPC += 1;
|
||||||
pPCSymbol->nValue += 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)
|
void rgbds_vwarn(const char *fmt, va_list ap)
|
||||||
{
|
{
|
||||||
fprintf(stderr, "warning");
|
fprintf(stderr, "warning: ");
|
||||||
if (fmt) {
|
if (fmt) {
|
||||||
fputs(": ", stderr);
|
|
||||||
vfprintf(stderr, fmt, ap);
|
vfprintf(stderr, fmt, ap);
|
||||||
|
fputs(": ", stderr);
|
||||||
}
|
}
|
||||||
putc('\n', stderr);
|
perror(NULL);
|
||||||
perror(0);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void rgbds_vwarnx(const char *fmt, va_list ap)
|
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)
|
noreturn_ void rgbds_verr(int status, const char *fmt, va_list ap)
|
||||||
{
|
{
|
||||||
fprintf(stderr, "error");
|
fprintf(stderr, "error: ");
|
||||||
if (fmt) {
|
if (fmt) {
|
||||||
fputs(": ", stderr);
|
|
||||||
vfprintf(stderr, fmt, ap);
|
vfprintf(stderr, fmt, ap);
|
||||||
|
fputs(": ", stderr);
|
||||||
}
|
}
|
||||||
putc('\n', stderr);
|
perror(NULL);
|
||||||
exit(status);
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
1000
src/link/assign.c
1000
src/link/assign.c
File diff suppressed because it is too large
Load Diff
194
src/link/lexer.l
194
src/link/lexer.l
@@ -1,194 +0,0 @@
|
|||||||
/*
|
|
||||||
* This file is part of RGBDS.
|
|
||||||
*
|
|
||||||
* Copyright (c) 2017-2018, Antonio Nino Diaz and RGBDS contributors.
|
|
||||||
*
|
|
||||||
* SPDX-License-Identifier: MIT
|
|
||||||
*/
|
|
||||||
|
|
||||||
%option noinput
|
|
||||||
%option yylineno
|
|
||||||
|
|
||||||
%{
|
|
||||||
#include <stdarg.h>
|
|
||||||
#include <stdint.h>
|
|
||||||
#include <unistd.h>
|
|
||||||
|
|
||||||
#include "extern/err.h"
|
|
||||||
|
|
||||||
#include "link/mylink.h"
|
|
||||||
#include "link/script.h"
|
|
||||||
|
|
||||||
#include "parser.h"
|
|
||||||
|
|
||||||
#include "types.h"
|
|
||||||
|
|
||||||
extern int yyparse(void);
|
|
||||||
|
|
||||||
/* File include stack. */
|
|
||||||
|
|
||||||
#define MAX_INCLUDE_DEPTH 8
|
|
||||||
|
|
||||||
static int32_t include_stack_ptr;
|
|
||||||
|
|
||||||
static YY_BUFFER_STATE include_stack[MAX_INCLUDE_DEPTH];
|
|
||||||
static char include_path[MAX_INCLUDE_DEPTH][_MAX_PATH + 1];
|
|
||||||
static int32_t include_line[MAX_INCLUDE_DEPTH];
|
|
||||||
|
|
||||||
static char linkerscript_path[_MAX_PATH + 1]; /* Base file */
|
|
||||||
%}
|
|
||||||
|
|
||||||
%%
|
|
||||||
|
|
||||||
\"([^\\\"]|\\.)*\" {
|
|
||||||
if (strlen(yytext) > sizeof(yylval.s) - 1) {
|
|
||||||
script_fatalerror("String is too long: %s\n.",
|
|
||||||
yytext);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (strlen(yytext) < 3) { /* 2 quotes + 1 character */
|
|
||||||
script_fatalerror("String %s is invalid\n.",
|
|
||||||
yytext);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Ignore first quote */
|
|
||||||
yytext++;
|
|
||||||
strcpy(yylval.s, yytext);
|
|
||||||
/* Remove end quote */
|
|
||||||
yylval.s[strlen(yylval.s)-1] = '\0';
|
|
||||||
|
|
||||||
return STRING;
|
|
||||||
}
|
|
||||||
|
|
||||||
\$[a-fA-F0-9]+ {
|
|
||||||
yytext++; /* Skip prefix */
|
|
||||||
yylval.i = strtol(yytext, NULL, 16);
|
|
||||||
return INTEGER;
|
|
||||||
}
|
|
||||||
[0-9]+ {
|
|
||||||
yylval.i = strtol(yytext, NULL, 10);
|
|
||||||
return INTEGER;
|
|
||||||
}
|
|
||||||
|
|
||||||
(?i:ROM0) { strcpy(yylval.s, "ROM0"); return SECTION_NONBANKED; }
|
|
||||||
(?i:ROMX) { strcpy(yylval.s, "ROMX"); return SECTION_BANKED; }
|
|
||||||
(?i:VRAM) { strcpy(yylval.s, "VRAM"); return SECTION_BANKED; }
|
|
||||||
(?i:WRAM0) { strcpy(yylval.s, "WRAM0"); return SECTION_NONBANKED; }
|
|
||||||
(?i:WRAMX) { strcpy(yylval.s, "WRAMX"); return SECTION_BANKED; }
|
|
||||||
(?i:SRAM) { strcpy(yylval.s, "SRAM"); return SECTION_BANKED; }
|
|
||||||
(?i:OAM) { strcpy(yylval.s, "OAM"); return SECTION_NONBANKED; }
|
|
||||||
(?i:HRAM) { strcpy(yylval.s, "HRAM"); return SECTION_NONBANKED; }
|
|
||||||
|
|
||||||
(?i:ALIGN) { return COMMAND_ALIGN; }
|
|
||||||
(?i:ORG) { return COMMAND_ORG; }
|
|
||||||
|
|
||||||
(?i:INCLUDE) { return COMMAND_INCLUDE; }
|
|
||||||
|
|
||||||
"\n" { return NEWLINE; }
|
|
||||||
|
|
||||||
;.* { /* Ignore comments. A dot doesn't match newline. */ }
|
|
||||||
|
|
||||||
[[:space:]] { /* Ignore whitespace. */ }
|
|
||||||
|
|
||||||
. { script_fatalerror("Invalid character [%s]\n.", yytext); }
|
|
||||||
|
|
||||||
%%
|
|
||||||
|
|
||||||
extern FILE *yyin;
|
|
||||||
|
|
||||||
void script_Parse(const char * path)
|
|
||||||
{
|
|
||||||
yyin = fopen(path, "r");
|
|
||||||
|
|
||||||
if (!yyin)
|
|
||||||
errx(1, "Error opening file! \"%s\"\n", path);
|
|
||||||
|
|
||||||
strncpy(linkerscript_path, path, sizeof(linkerscript_path));
|
|
||||||
linkerscript_path[sizeof(linkerscript_path) - 1] = '\0';
|
|
||||||
|
|
||||||
do {
|
|
||||||
yyparse();
|
|
||||||
} while (!feof(yyin));
|
|
||||||
|
|
||||||
fclose(yyin);
|
|
||||||
}
|
|
||||||
|
|
||||||
void script_IncludeFile(const char * path)
|
|
||||||
{
|
|
||||||
if (include_stack_ptr == (MAX_INCLUDE_DEPTH - 1))
|
|
||||||
script_fatalerror("Includes nested too deeply.");
|
|
||||||
|
|
||||||
include_line[include_stack_ptr] = yylineno;
|
|
||||||
include_stack[include_stack_ptr] = YY_CURRENT_BUFFER;
|
|
||||||
|
|
||||||
include_stack_ptr++;
|
|
||||||
|
|
||||||
yyin = fopen(path, "r");
|
|
||||||
|
|
||||||
if (!yyin)
|
|
||||||
script_fatalerror("Couldn't open file \"%s\"", path);
|
|
||||||
|
|
||||||
strncpy(include_path[include_stack_ptr], path, sizeof(include_path[0]));
|
|
||||||
include_path[include_stack_ptr][sizeof(include_path[0]) - 1] = '\0';
|
|
||||||
|
|
||||||
yy_switch_to_buffer(yy_create_buffer(yyin, YY_BUF_SIZE));
|
|
||||||
yylineno = 1;
|
|
||||||
|
|
||||||
/*
|
|
||||||
* The INCLUDE keyword is handled before reaching a newline token, it's
|
|
||||||
* handled right after parsing the string with the file name that has to
|
|
||||||
* be included. It isn't actually needed to include a newline after the
|
|
||||||
* path, the last line of the linkerscript doesn't need to have a
|
|
||||||
* newline character but it can have a command.
|
|
||||||
*
|
|
||||||
* This means that, when opening a new file, we must tell the parser
|
|
||||||
* that what it is going to start at a new line or it will think that
|
|
||||||
* the first line of the included script is the continuation of the
|
|
||||||
* INCLUDE line of the parent script. If this is not done, the first
|
|
||||||
* line of an included linkerscript can only be a comment (or empty).
|
|
||||||
*/
|
|
||||||
unput('\n');
|
|
||||||
}
|
|
||||||
|
|
||||||
int32_t script_IncludeDepthGet(void)
|
|
||||||
{
|
|
||||||
return include_stack_ptr;
|
|
||||||
}
|
|
||||||
|
|
||||||
void script_IncludePop(void)
|
|
||||||
{
|
|
||||||
fclose(yyin);
|
|
||||||
|
|
||||||
include_stack_ptr--;
|
|
||||||
|
|
||||||
yy_delete_buffer(YY_CURRENT_BUFFER);
|
|
||||||
yy_switch_to_buffer(include_stack[include_stack_ptr]);
|
|
||||||
yylineno = include_line[include_stack_ptr];
|
|
||||||
}
|
|
||||||
|
|
||||||
void script_PrintFileStack(void)
|
|
||||||
{
|
|
||||||
int32_t i = include_stack_ptr;
|
|
||||||
|
|
||||||
include_line[i] = yylineno;
|
|
||||||
|
|
||||||
while (i > 0) {
|
|
||||||
fprintf(stderr, "%s(%d) -> ", include_path[i], include_line[i]);
|
|
||||||
i--;
|
|
||||||
}
|
|
||||||
fprintf(stderr, "%s(%d)", linkerscript_path, include_line[i]);
|
|
||||||
}
|
|
||||||
|
|
||||||
noreturn_ void script_fatalerror(const char *fmt, ...)
|
|
||||||
{
|
|
||||||
va_list args;
|
|
||||||
va_start(args, fmt);
|
|
||||||
fprintf(stderr, "error: ");
|
|
||||||
script_PrintFileStack();
|
|
||||||
fprintf(stderr, ":\n ");
|
|
||||||
vfprintf(stderr, fmt, args);
|
|
||||||
fprintf(stderr, "\n");
|
|
||||||
va_end(args);
|
|
||||||
exit(1);
|
|
||||||
}
|
|
||||||
|
|
||||||
@@ -1,122 +0,0 @@
|
|||||||
/*
|
|
||||||
* This file is part of RGBDS.
|
|
||||||
*
|
|
||||||
* Copyright (c) 1997-2018, Carsten Sorensen and RGBDS contributors.
|
|
||||||
*
|
|
||||||
* SPDX-License-Identifier: MIT
|
|
||||||
*/
|
|
||||||
|
|
||||||
#include <stdint.h>
|
|
||||||
#include <stdio.h>
|
|
||||||
#include <stdlib.h>
|
|
||||||
#include <string.h>
|
|
||||||
|
|
||||||
#include "extern/err.h"
|
|
||||||
|
|
||||||
#include "link/mylink.h"
|
|
||||||
#include "link/main.h"
|
|
||||||
|
|
||||||
static uint8_t symboldefined(char *name)
|
|
||||||
{
|
|
||||||
const struct sSection *pSect;
|
|
||||||
|
|
||||||
pSect = pSections;
|
|
||||||
|
|
||||||
while (pSect) {
|
|
||||||
int32_t i;
|
|
||||||
|
|
||||||
for (i = 0; i < pSect->nNumberOfSymbols; i += 1) {
|
|
||||||
const struct sSymbol *tSymbol = pSect->tSymbols[i];
|
|
||||||
|
|
||||||
if ((tSymbol->Type == SYM_EXPORT)
|
|
||||||
|| ((tSymbol->Type == SYM_LOCAL)
|
|
||||||
&& (pSect == tSymbol->pSection))) {
|
|
||||||
|
|
||||||
if (strcmp(tSymbol->pzName, name) == 0)
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
pSect = pSect->pNext;
|
|
||||||
}
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
static uint8_t addmodulecontaining(char *name)
|
|
||||||
{
|
|
||||||
struct sSection **ppLSect = &pLibSections;
|
|
||||||
|
|
||||||
while (*ppLSect) {
|
|
||||||
int32_t i;
|
|
||||||
|
|
||||||
for (i = 0; i < (*ppLSect)->nNumberOfSymbols; i += 1) {
|
|
||||||
const struct sSymbol *tSymbol = (*ppLSect)->tSymbols[i];
|
|
||||||
|
|
||||||
if ((tSymbol->Type == SYM_EXPORT)
|
|
||||||
|| ((tSymbol->Type == SYM_LOCAL)
|
|
||||||
&& ((*ppLSect) == tSymbol->pSection))) {
|
|
||||||
|
|
||||||
if (strcmp(tSymbol->pzName, name) == 0) {
|
|
||||||
struct sSection **ppSect = &pSections;
|
|
||||||
|
|
||||||
while (*ppSect)
|
|
||||||
ppSect = &((*ppSect)->pNext);
|
|
||||||
|
|
||||||
*ppSect = *ppLSect;
|
|
||||||
*ppLSect = (*ppLSect)->pNext;
|
|
||||||
(*ppSect)->pNext = NULL;
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
ppLSect = &((*ppLSect)->pNext);
|
|
||||||
}
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
void AddNeededModules(void)
|
|
||||||
{
|
|
||||||
struct sSection *pSect;
|
|
||||||
|
|
||||||
if ((options & OPT_SMART_C_LINK) == 0) {
|
|
||||||
struct sSection **ppLSect;
|
|
||||||
|
|
||||||
ppLSect = &pLibSections;
|
|
||||||
|
|
||||||
while (*ppLSect) {
|
|
||||||
struct sSection **ppSect = &pSections;
|
|
||||||
|
|
||||||
while (*ppSect)
|
|
||||||
ppSect = &((*ppSect)->pNext);
|
|
||||||
|
|
||||||
*ppSect = *ppLSect;
|
|
||||||
*ppLSect = (*ppLSect)->pNext;
|
|
||||||
(*ppSect)->pNext = NULL;
|
|
||||||
|
|
||||||
/* ppLSect=&((*ppLSect)->pNext); */
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (options & OPT_SMART_C_LINK) {
|
|
||||||
if (!addmodulecontaining(smartlinkstartsymbol)) {
|
|
||||||
errx(1, "Can't find start symbol '%s'",
|
|
||||||
smartlinkstartsymbol);
|
|
||||||
} else {
|
|
||||||
printf("Smart linking with symbol '%s'\n",
|
|
||||||
smartlinkstartsymbol);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
pSect = pSections;
|
|
||||||
|
|
||||||
while (pSect) {
|
|
||||||
int32_t i;
|
|
||||||
|
|
||||||
for (i = 0; i < pSect->nNumberOfSymbols; i += 1) {
|
|
||||||
if ((pSect->tSymbols[i]->Type == SYM_IMPORT)
|
|
||||||
|| (pSect->tSymbols[i]->Type == SYM_LOCAL)) {
|
|
||||||
if (!symboldefined(pSect->tSymbols[i]->pzName))
|
|
||||||
addmodulecontaining(pSect->tSymbols[i]->pzName);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
pSect = pSect->pNext;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
203
src/link/main.c
203
src/link/main.c
@@ -1,141 +1,162 @@
|
|||||||
/*
|
/*
|
||||||
* This file is part of RGBDS.
|
* 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
|
* SPDX-License-Identifier: MIT
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#include <stdint.h>
|
#include <sys/types.h>
|
||||||
#include <stdio.h>
|
#include <sys/stat.h>
|
||||||
#include <stdlib.h>
|
|
||||||
#include <string.h>
|
|
||||||
#include <unistd.h>
|
#include <unistd.h>
|
||||||
|
#include <stdio.h>
|
||||||
#include "extern/err.h"
|
#include <stdbool.h>
|
||||||
|
#include <stdint.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
|
||||||
#include "link/object.h"
|
#include "link/object.h"
|
||||||
#include "link/output.h"
|
#include "link/symbol.h"
|
||||||
|
#include "link/section.h"
|
||||||
#include "link/assign.h"
|
#include "link/assign.h"
|
||||||
#include "link/patch.h"
|
#include "link/patch.h"
|
||||||
#include "link/mylink.h"
|
#include "link/output.h"
|
||||||
#include "link/mapfile.h"
|
|
||||||
#include "link/main.h"
|
|
||||||
#include "link/library.h"
|
|
||||||
|
|
||||||
|
#include "extern/err.h"
|
||||||
#include "version.h"
|
#include "version.h"
|
||||||
|
|
||||||
enum eBlockType {
|
bool isDmgMode; /* -d */
|
||||||
BLOCK_COMMENT,
|
char const *linkerScriptName; /* -l */
|
||||||
BLOCK_OBJECTS,
|
char const *mapFileName; /* -m */
|
||||||
BLOCK_LIBRARIES,
|
char const *symFileName; /* -n */
|
||||||
BLOCK_OUTPUT
|
char const *overlayFileName; /* -O */
|
||||||
};
|
char const *outputFileName; /* -o */
|
||||||
|
uint8_t padValue; /* -p */
|
||||||
|
bool is32kMode; /* -t */
|
||||||
|
bool beVerbose; /* -v */
|
||||||
|
bool isWRA0Mode; /* -w */
|
||||||
|
|
||||||
int32_t options;
|
FILE *openFile(char const *fileName, char const *mode)
|
||||||
int32_t fillchar;
|
|
||||||
char *smartlinkstartsymbol;
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Print the usagescreen
|
|
||||||
*/
|
|
||||||
|
|
||||||
static void print_usage(void)
|
|
||||||
{
|
{
|
||||||
printf(
|
if (!fileName)
|
||||||
"usage: rgblink [-dtVw] [-l linkerscript] [-m mapfile] [-n symfile] [-O overlay]\n"
|
return NULL;
|
||||||
" [-o outfile] [-p pad_value] [-s symbol] file [...]\n");
|
|
||||||
exit(1);
|
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 main(int argc, char *argv[])
|
||||||
{
|
{
|
||||||
int ch;
|
char optionChar;
|
||||||
char *ep;
|
char *endptr; /* For error checking with `strtol` */
|
||||||
|
unsigned long value; /* For storing `strtoul`'s return value */
|
||||||
|
|
||||||
if (argc == 1)
|
/* Parse options */
|
||||||
print_usage();
|
while ((optionChar = getopt(argc, argv, "dl:m:n:O:o:p:s:tVvw")) != -1) {
|
||||||
|
switch (optionChar) {
|
||||||
while ((ch = getopt(argc, argv, "dl:m:n:O:o:p:s:tVw")) != -1) {
|
case 'd':
|
||||||
switch (ch) {
|
isDmgMode = true;
|
||||||
|
isWRA0Mode = true;
|
||||||
|
break;
|
||||||
case 'l':
|
case 'l':
|
||||||
SetLinkerscriptName(optarg);
|
linkerScriptName = optarg;
|
||||||
break;
|
break;
|
||||||
case 'm':
|
case 'm':
|
||||||
SetMapfileName(optarg);
|
mapFileName = optarg;
|
||||||
break;
|
break;
|
||||||
case 'n':
|
case 'n':
|
||||||
SetSymfileName(optarg);
|
symFileName = optarg;
|
||||||
break;
|
|
||||||
case 'o':
|
|
||||||
out_Setname(optarg);
|
|
||||||
break;
|
break;
|
||||||
case 'O':
|
case 'O':
|
||||||
out_SetOverlayname(optarg);
|
overlayFileName = optarg;
|
||||||
options |= OPT_OVERLAY;
|
break;
|
||||||
|
case 'o':
|
||||||
|
outputFileName = optarg;
|
||||||
break;
|
break;
|
||||||
case 'p':
|
case 'p':
|
||||||
fillchar = strtoul(optarg, &ep, 0);
|
value = strtoul(optarg, &endptr, 0);
|
||||||
if (optarg[0] == '\0' || *ep != '\0')
|
if (optarg[0] == '\0' || *endptr != '\0')
|
||||||
errx(1, "Invalid argument for option 'p'");
|
errx(1, "Invalid argument for option 'p'");
|
||||||
if (fillchar < 0 || fillchar > 0xFF)
|
if (value > 0xFF)
|
||||||
errx(1, "Argument for option 'p' must be between 0 and 0xFF");
|
errx(1, "Argument for 'p' must be a byte (between 0 and 0xFF)");
|
||||||
|
padValue = value;
|
||||||
break;
|
break;
|
||||||
case 's':
|
case 's':
|
||||||
options |= OPT_SMART_C_LINK;
|
/* FIXME: nobody knows what this does, figure it out */
|
||||||
smartlinkstartsymbol = optarg;
|
(void)optarg;
|
||||||
|
warnx("Nobody has any idea what `-s` does");
|
||||||
break;
|
break;
|
||||||
case 't':
|
case 't':
|
||||||
options |= OPT_TINY;
|
is32kMode = true;
|
||||||
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;
|
|
||||||
break;
|
break;
|
||||||
case 'V':
|
case 'V':
|
||||||
printf("rgblink %s\n", get_package_version_string());
|
printf("rgblink %s\n", get_package_version_string());
|
||||||
exit(0);
|
exit(0);
|
||||||
|
case 'v':
|
||||||
|
beVerbose = true;
|
||||||
|
break;
|
||||||
|
case 'w':
|
||||||
|
isWRA0Mode = true;
|
||||||
|
break;
|
||||||
default:
|
default:
|
||||||
print_usage();
|
printUsage();
|
||||||
/* NOTREACHED */
|
exit(1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
argc -= optind;
|
|
||||||
argv += optind;
|
|
||||||
|
|
||||||
if (argc == 0)
|
int curArgIndex = optind;
|
||||||
print_usage();
|
|
||||||
|
|
||||||
for (int32_t i = 0; i < argc; ++i)
|
/* If no input files were specified, the user must have screwed up */
|
||||||
obj_Readfile(argv[i]);
|
if (curArgIndex == argc) {
|
||||||
|
fprintf(stderr, "No input files");
|
||||||
|
printUsage();
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
AddNeededModules();
|
/* Patch the size array depending on command-line options */
|
||||||
AssignSections();
|
if (is32kMode)
|
||||||
CreateSymbolTable();
|
maxsize[SECTTYPE_ROM0] = 0x8000;
|
||||||
Patch();
|
if (isWRA0Mode)
|
||||||
Output();
|
maxsize[SECTTYPE_WRAM0] = 0x2000;
|
||||||
CloseMapfile();
|
|
||||||
|
|
||||||
return 0;
|
/* Patch the bank ranges array depending on command-line options */
|
||||||
|
if (isDmgMode)
|
||||||
|
bankranges[SECTTYPE_VRAM][1] = BANK_MIN_VRAM;
|
||||||
|
|
||||||
|
/* Read all object files first, */
|
||||||
|
while (curArgIndex < argc)
|
||||||
|
obj_ReadFile(argv[curArgIndex++]);
|
||||||
|
|
||||||
|
/* then process them, */
|
||||||
|
obj_DoSanityChecks();
|
||||||
|
assign_AssignSections();
|
||||||
|
assign_Cleanup();
|
||||||
|
|
||||||
|
/* and finally output the result. */
|
||||||
|
patch_ApplyPatches();
|
||||||
|
out_WriteFiles();
|
||||||
|
|
||||||
|
/* Do cleanup before quitting, though. */
|
||||||
|
cleanup();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,151 +0,0 @@
|
|||||||
/*
|
|
||||||
* This file is part of RGBDS.
|
|
||||||
*
|
|
||||||
* Copyright (c) 1997-2018, Carsten Sorensen and RGBDS contributors.
|
|
||||||
*
|
|
||||||
* SPDX-License-Identifier: MIT
|
|
||||||
*/
|
|
||||||
|
|
||||||
#include <errno.h>
|
|
||||||
#include <stdint.h>
|
|
||||||
#include <stdio.h>
|
|
||||||
#include <stdlib.h>
|
|
||||||
#include <string.h>
|
|
||||||
|
|
||||||
#include "extern/err.h"
|
|
||||||
|
|
||||||
#include "link/assign.h"
|
|
||||||
#include "link/main.h"
|
|
||||||
#include "link/mylink.h"
|
|
||||||
|
|
||||||
static int32_t currentbank;
|
|
||||||
static int32_t sfbank;
|
|
||||||
static FILE *mf;
|
|
||||||
static FILE *sf;
|
|
||||||
|
|
||||||
void SetMapfileName(char *name)
|
|
||||||
{
|
|
||||||
mf = fopen(name, "w");
|
|
||||||
|
|
||||||
if (mf == NULL)
|
|
||||||
err(1, "Cannot open mapfile '%s'", name);
|
|
||||||
}
|
|
||||||
|
|
||||||
void SetSymfileName(char *name)
|
|
||||||
{
|
|
||||||
sf = fopen(name, "w");
|
|
||||||
|
|
||||||
if (sf == NULL)
|
|
||||||
err(1, "Cannot open symfile '%s'", name);
|
|
||||||
|
|
||||||
fprintf(sf, "; File generated by rgblink\n\n");
|
|
||||||
}
|
|
||||||
|
|
||||||
void CloseMapfile(void)
|
|
||||||
{
|
|
||||||
if (mf) {
|
|
||||||
fclose(mf);
|
|
||||||
mf = NULL;
|
|
||||||
}
|
|
||||||
if (sf) {
|
|
||||||
fclose(sf);
|
|
||||||
sf = NULL;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void MapfileInitBank(int32_t bank)
|
|
||||||
{
|
|
||||||
if ((bank < 0) || (bank >= BANK_INDEX_MAX))
|
|
||||||
errx(1, "%s: Unknown bank %d\n", __func__, bank);
|
|
||||||
|
|
||||||
if (mf) {
|
|
||||||
currentbank = bank;
|
|
||||||
if (BankIndexIsROM0(bank)) {
|
|
||||||
fprintf(mf, "ROM Bank #0 (HOME):\n");
|
|
||||||
} else if (BankIndexIsROMX(bank)) {
|
|
||||||
fprintf(mf, "ROM Bank #%d:\n",
|
|
||||||
bank - BANK_INDEX_ROMX + 1);
|
|
||||||
} else if (BankIndexIsWRAM0(bank)) {
|
|
||||||
fprintf(mf, "WRAM Bank #0:\n");
|
|
||||||
} else if (BankIndexIsWRAMX(bank)) {
|
|
||||||
fprintf(mf, "WRAM Bank #%d:\n",
|
|
||||||
bank - BANK_INDEX_WRAMX + 1);
|
|
||||||
} else if (BankIndexIsVRAM(bank)) {
|
|
||||||
fprintf(mf, "VRAM Bank #%d:\n", bank - BANK_INDEX_VRAM);
|
|
||||||
} else if (BankIndexIsOAM(bank)) {
|
|
||||||
fprintf(mf, "OAM:\n");
|
|
||||||
} else if (BankIndexIsHRAM(bank)) {
|
|
||||||
fprintf(mf, "HRAM:\n");
|
|
||||||
} else if (BankIndexIsSRAM(bank)) {
|
|
||||||
fprintf(mf, "SRAM Bank #%d:\n", bank - BANK_INDEX_SRAM);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (sf) {
|
|
||||||
if (BankIndexIsROM0(bank))
|
|
||||||
sfbank = 0;
|
|
||||||
else if (BankIndexIsROMX(bank))
|
|
||||||
sfbank = bank - BANK_INDEX_ROMX + 1;
|
|
||||||
else if (BankIndexIsWRAM0(bank))
|
|
||||||
sfbank = 0;
|
|
||||||
else if (BankIndexIsWRAMX(bank))
|
|
||||||
sfbank = bank - BANK_INDEX_WRAMX + 1;
|
|
||||||
else if (BankIndexIsVRAM(bank))
|
|
||||||
sfbank = bank - BANK_INDEX_VRAM;
|
|
||||||
else if (BankIndexIsOAM(bank))
|
|
||||||
sfbank = 0;
|
|
||||||
else if (BankIndexIsHRAM(bank))
|
|
||||||
sfbank = 0;
|
|
||||||
else if (BankIndexIsSRAM(bank))
|
|
||||||
sfbank = bank - BANK_INDEX_SRAM;
|
|
||||||
else
|
|
||||||
sfbank = 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void MapfileWriteSection(const struct sSection *pSect)
|
|
||||||
{
|
|
||||||
int32_t i;
|
|
||||||
|
|
||||||
if (mf) {
|
|
||||||
if (pSect->nByteSize > 0) {
|
|
||||||
fprintf(mf, " SECTION: $%04X-$%04X ($%04X bytes) [\"%s\"]\n",
|
|
||||||
pSect->nOrg, pSect->nOrg + pSect->nByteSize - 1,
|
|
||||||
pSect->nByteSize, pSect->pzName);
|
|
||||||
} else {
|
|
||||||
fprintf(mf, " SECTION: $%04X ($0 bytes) [\"%s\"]\n",
|
|
||||||
pSect->nOrg, pSect->pzName);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for (i = 0; i < pSect->nNumberOfSymbols; i += 1) {
|
|
||||||
const struct sSymbol *pSym = pSect->tSymbols[i];
|
|
||||||
|
|
||||||
/* Don't print '@' */
|
|
||||||
if (strcmp(pSym->pzName, "@") == 0)
|
|
||||||
continue;
|
|
||||||
|
|
||||||
if ((pSym->pSection == pSect) && (pSym->Type != SYM_IMPORT)) {
|
|
||||||
if (mf) {
|
|
||||||
fprintf(mf, " $%04X = %s\n",
|
|
||||||
pSym->nOffset + pSect->nOrg,
|
|
||||||
pSym->pzName);
|
|
||||||
}
|
|
||||||
if (sf) {
|
|
||||||
fprintf(sf, "%02X:%04X %s\n", sfbank,
|
|
||||||
pSym->nOffset + pSect->nOrg,
|
|
||||||
pSym->pzName);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void MapfileCloseBank(int32_t slack)
|
|
||||||
{
|
|
||||||
if (!mf)
|
|
||||||
return;
|
|
||||||
|
|
||||||
if (slack == MaxAvail[currentbank])
|
|
||||||
fprintf(mf, " EMPTY\n\n");
|
|
||||||
else
|
|
||||||
fprintf(mf, " SLACK: $%04X bytes\n\n", slack);
|
|
||||||
}
|
|
||||||
@@ -1,381 +1,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 <stdio.h>
|
||||||
#include <stdlib.h>
|
#include <stdint.h>
|
||||||
#include <string.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 "extern/err.h"
|
||||||
|
#include "linkdefs.h"
|
||||||
|
#include "common.h"
|
||||||
|
|
||||||
#include "link/assign.h"
|
static struct SymbolList {
|
||||||
#include "link/mylink.h"
|
size_t nbSymbols;
|
||||||
#include "link/main.h"
|
struct Symbol **symbolList;
|
||||||
|
struct SymbolList *next;
|
||||||
|
} *symbolLists;
|
||||||
|
|
||||||
struct sSymbol **tSymbols;
|
/***** Helper functions for reading object files *****/
|
||||||
struct sSection *pSections;
|
|
||||||
struct sSection *pLibSections;
|
|
||||||
uint8_t oReadLib;
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Read 32-bit values with the correct endianness
|
* Internal, DO NOT USE.
|
||||||
|
* For helper wrapper macros defined below, such as `tryReadlong`
|
||||||
*/
|
*/
|
||||||
static int32_t readlong(FILE *f)
|
#define tryRead(func, type, errval, var, file, ...) \
|
||||||
|
do { \
|
||||||
|
FILE *tmpFile = file; \
|
||||||
|
type tmpVal = func(tmpFile); \
|
||||||
|
if (tmpVal == (errval)) { \
|
||||||
|
errx(1, __VA_ARGS__, feof(tmpFile) \
|
||||||
|
? "Unexpected end of file" \
|
||||||
|
: strerror(errno)); \
|
||||||
|
} \
|
||||||
|
var = tmpVal; \
|
||||||
|
} while (0)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reads an unsigned long (32-bit) value from a file.
|
||||||
|
* @param file The file to read from. This will read 4 bytes from the file.
|
||||||
|
* @return The value read, cast to a int64_t, or -1 on failure.
|
||||||
|
*/
|
||||||
|
static int64_t readlong(FILE *file)
|
||||||
{
|
{
|
||||||
uint32_t r;
|
uint32_t value = 0;
|
||||||
|
|
||||||
r = ((uint32_t)(uint8_t)fgetc(f));
|
/* Read the little-endian value byte by byte */
|
||||||
r |= ((uint32_t)(uint8_t)fgetc(f)) << 8;
|
for (uint8_t shift = 0; shift < sizeof(value) * CHAR_BIT; shift += 8) {
|
||||||
r |= ((uint32_t)(uint8_t)fgetc(f)) << 16;
|
int byte = getc(file);
|
||||||
r |= ((uint32_t)(uint8_t)fgetc(f)) << 24;
|
|
||||||
|
|
||||||
return (int32_t)r;
|
if (byte == EOF)
|
||||||
|
return INT64_MAX;
|
||||||
|
value |= (uint8_t)byte << shift;
|
||||||
|
}
|
||||||
|
return value;
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/**
|
||||||
* Read a NULL terminated string from a file
|
* Helper macro for reading longs from a file, and errors out if it fails to.
|
||||||
|
* Not as a function to avoid overhead in the general case.
|
||||||
|
* TODO: maybe mark the condition as `unlikely`; how to do that portably?
|
||||||
|
* @param var The variable to stash the number into
|
||||||
|
* @param file The file to read from. Its position will be advanced
|
||||||
|
* @param ... A format string and related arguments; note that an extra string
|
||||||
|
* argument is provided, the reason for failure
|
||||||
*/
|
*/
|
||||||
int32_t readasciiz(char **dest, FILE *f)
|
#define tryReadlong(var, file, ...) \
|
||||||
{
|
tryRead(readlong, int64_t, INT64_MAX, var, file, __VA_ARGS__)
|
||||||
size_t r = 0;
|
|
||||||
|
|
||||||
size_t bufferLength = 16;
|
/* There is no `readbyte`, just use `fgetc` or `getc`. */
|
||||||
char *start = malloc(bufferLength);
|
|
||||||
char *s = start;
|
|
||||||
|
|
||||||
if (!s)
|
/**
|
||||||
err(1, "%s: Couldn't allocate memory", __func__);
|
* 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).
|
||||||
while (((*s++) = fgetc(f)) != 0) {
|
* Not as a function to avoid overhead in the general case.
|
||||||
r += 1;
|
* TODO: maybe mark the condition as `unlikely`; how to do that portably?
|
||||||
|
* @param var The variable to stash the number into
|
||||||
if (r >= bufferLength) {
|
* @param file The file to read from. Its position will be advanced
|
||||||
bufferLength *= 2;
|
* @param ... A format string and related arguments; note that an extra string
|
||||||
start = realloc(start, bufferLength);
|
* argument is provided, the reason for failure
|
||||||
if (!start) {
|
|
||||||
err(1, "%s: Couldn't allocate memory",
|
|
||||||
__func__);
|
|
||||||
}
|
|
||||||
s = start + r;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
*dest = start;
|
|
||||||
return (r + 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Allocate a new section and link it into the list
|
|
||||||
*/
|
*/
|
||||||
struct sSection *AllocSection(void)
|
#define tryFgetc(var, file, ...) \
|
||||||
{
|
tryRead(fgetc, int, EOF, var, file, __VA_ARGS__)
|
||||||
struct sSection **ppSections;
|
|
||||||
|
|
||||||
if (oReadLib == 1)
|
/**
|
||||||
ppSections = &pLibSections;
|
* Helper macro for reading bytes from a file, and errors out if it fails to.
|
||||||
else
|
* Differs from `tryGetc` in that the backing function is fgetc(1).
|
||||||
ppSections = &pSections;
|
* Not as a function to avoid overhead in the general case.
|
||||||
|
* TODO: maybe mark the condition as `unlikely`; how to do that portably?
|
||||||
while (*ppSections)
|
* @param var The variable to stash the number into
|
||||||
ppSections = &((*ppSections)->pNext);
|
* @param file The file to read from. Its position will be advanced
|
||||||
|
* @param ... A format string and related arguments; note that an extra string
|
||||||
*ppSections = malloc(sizeof **ppSections);
|
* argument is provided, the reason for failure
|
||||||
if (!*ppSections)
|
|
||||||
err(1, "%s: Couldn't allocate memory", __func__);
|
|
||||||
|
|
||||||
(*ppSections)->tSymbols = tSymbols;
|
|
||||||
(*ppSections)->pNext = NULL;
|
|
||||||
(*ppSections)->pPatches = NULL;
|
|
||||||
(*ppSections)->oAssigned = 0;
|
|
||||||
return *ppSections;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Read a symbol from a file
|
|
||||||
*/
|
*/
|
||||||
struct sSymbol *obj_ReadSymbol(FILE *f, char *tzObjectfile)
|
#define tryGetc(var, file, ...) \
|
||||||
{
|
tryRead(getc, int, EOF, var, file, __VA_ARGS__)
|
||||||
struct sSymbol *pSym;
|
|
||||||
|
|
||||||
pSym = malloc(sizeof(*pSym));
|
/**
|
||||||
if (!pSym)
|
* Reads a '\0'-terminated string from a file.
|
||||||
err(1, "%s: Couldn't allocate memory", __func__);
|
* @param file The file to read from. The file position will be advanced.
|
||||||
|
* @return The string read, or NULL on failure.
|
||||||
readasciiz(&pSym->pzName, f);
|
* If a non-NULL pointer is returned, make sure to `free` it when done!
|
||||||
pSym->Type = (enum eSymbolType)fgetc(f);
|
|
||||||
|
|
||||||
pSym->pzObjFileName = tzObjectfile;
|
|
||||||
|
|
||||||
if (pSym->Type != SYM_IMPORT) {
|
|
||||||
readasciiz(&pSym->pzFileName, f);
|
|
||||||
pSym->nFileLine = readlong(f);
|
|
||||||
|
|
||||||
pSym->nSectionID = readlong(f);
|
|
||||||
pSym->nOffset = readlong(f);
|
|
||||||
}
|
|
||||||
|
|
||||||
return pSym;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
* RGB object reader routines
|
|
||||||
*/
|
*/
|
||||||
struct sSection *obj_ReadRGBSection(FILE *f)
|
static char *readstr(FILE *file)
|
||||||
{
|
{
|
||||||
struct sSection *pSection;
|
/* Default buffer size, have it close to the average string length */
|
||||||
char *pzName;
|
size_t capacity = 32 / 2;
|
||||||
|
size_t index = -1;
|
||||||
|
/* Force the first iteration to allocate */
|
||||||
|
char *str = NULL;
|
||||||
|
|
||||||
readasciiz(&pzName, f);
|
do {
|
||||||
if (IsSectionNameInUse(pzName))
|
/* Prepare going to next char */
|
||||||
errx(1, "Section name \"%s\" is already in use.", pzName);
|
index++;
|
||||||
|
|
||||||
pSection = AllocSection();
|
/* If the buffer isn't suitable to write the next char... */
|
||||||
pSection->pzName = pzName;
|
if (index >= capacity || !str) {
|
||||||
|
capacity *= 2;
|
||||||
pSection->nByteSize = readlong(f);
|
str = realloc(str, capacity);
|
||||||
pSection->Type = (enum eSectionType)fgetc(f);
|
/* End now in case of error */
|
||||||
pSection->nOrg = readlong(f);
|
if (!str)
|
||||||
pSection->nBank = readlong(f);
|
return NULL;
|
||||||
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__);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
pPatch->pRPN = NULL;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pPatch->pNext = NULL;
|
/* Read char */
|
||||||
ppPatch = &(pPatch->pNext);
|
int byte = getc(file);
|
||||||
}
|
|
||||||
|
|
||||||
return pSection;
|
if (byte == EOF)
|
||||||
|
return NULL;
|
||||||
|
str[index] = byte;
|
||||||
|
} while (str[index]);
|
||||||
|
return str;
|
||||||
}
|
}
|
||||||
|
|
||||||
void obj_ReadRGB(FILE *pObjfile, char *tzObjectfile)
|
/**
|
||||||
|
* 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)
|
||||||
{
|
{
|
||||||
struct sSection *pFirstSection;
|
tryReadstr(symbol->name, file, "%s: Cannot read symbol name: %s",
|
||||||
int32_t nNumberOfSymbols, nNumberOfSections, i;
|
fileName);
|
||||||
|
tryGetc(symbol->type, file, "%s: Cannot read \"%s\"'s type: %s",
|
||||||
nNumberOfSymbols = readlong(pObjfile);
|
fileName, symbol->name);
|
||||||
nNumberOfSections = readlong(pObjfile);
|
/* If the symbol is defined in this file, read its definition */
|
||||||
|
if (symbol->type != SYMTYPE_IMPORT) {
|
||||||
/* First comes the symbols */
|
symbol->objFileName = fileName;
|
||||||
|
tryReadstr(symbol->fileName, file,
|
||||||
if (nNumberOfSymbols) {
|
"%s: Cannot read \"%s\"'s file name: %s",
|
||||||
tSymbols = malloc(nNumberOfSymbols * sizeof(*tSymbols));
|
fileName, symbol->name);
|
||||||
if (!tSymbols)
|
tryReadlong(symbol->lineNo, file,
|
||||||
err(1, "%s: Couldn't allocate memory", __func__);
|
"%s: Cannot read \"%s\"'s line number: %s",
|
||||||
|
fileName, symbol->name);
|
||||||
for (i = 0; i < nNumberOfSymbols; i += 1)
|
tryReadlong(symbol->sectionID, file,
|
||||||
tSymbols[i] = obj_ReadSymbol(pObjfile, tzObjectfile);
|
"%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 {
|
} else {
|
||||||
tSymbols = NULL;
|
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
|
||||||
|
a = c + 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Next we have the sections */
|
struct Symbol const *tmp = symbol;
|
||||||
|
|
||||||
pFirstSection = NULL;
|
for (uint32_t i = a; i <= section->nbSymbols; i++) {
|
||||||
while (nNumberOfSections--) {
|
symbol = tmp;
|
||||||
struct sSection *pNewSection;
|
tmp = section->symbols[i];
|
||||||
|
section->symbols[i] = symbol;
|
||||||
pNewSection = obj_ReadRGBSection(pObjfile);
|
|
||||||
pNewSection->nNumberOfSymbols = nNumberOfSymbols;
|
|
||||||
if (pFirstSection == NULL)
|
|
||||||
pFirstSection = pNewSection;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
section->nbSymbols++;
|
||||||
* Fill in the pSection entry in the symbolstructure.
|
}
|
||||||
* This REALLY needs some cleaning up... but, hey, it works
|
|
||||||
*/
|
|
||||||
|
|
||||||
for (i = 0; i < nNumberOfSymbols; i += 1) {
|
/**
|
||||||
struct sSection *pConvSect = pFirstSection;
|
* Reads a RGB6 object file.
|
||||||
|
* @param file The file to read from
|
||||||
|
* @param fileName The filename to report in errors
|
||||||
|
*/
|
||||||
|
static void readRGB6File(FILE *file, char const *fileName)
|
||||||
|
{
|
||||||
|
uint32_t nbSymbols;
|
||||||
|
uint32_t nbSections;
|
||||||
|
|
||||||
if ((tSymbols[i]->Type != SYM_IMPORT) &&
|
tryReadlong(nbSymbols, file, "%s: Cannot read number of symbols: %s",
|
||||||
(tSymbols[i]->nSectionID != -1)) {
|
fileName);
|
||||||
int32_t j = 0;
|
tryReadlong(nbSections, file, "%s: Cannot read number of sections: %s",
|
||||||
|
fileName);
|
||||||
|
|
||||||
while (j != tSymbols[i]->nSectionID) {
|
nbSectionsToAssign += nbSections;
|
||||||
j += 1;
|
|
||||||
pConvSect = pConvSect->pNext;
|
/* This file's symbols, kept to link sections to them */
|
||||||
}
|
struct Symbol **fileSymbols =
|
||||||
tSymbols[i]->pSection = pConvSect;
|
malloc(sizeof(*fileSymbols) * nbSymbols + 1);
|
||||||
|
|
||||||
|
if (!fileSymbols)
|
||||||
|
err(1, "Failed to get memory for %s's symbols", fileName);
|
||||||
|
|
||||||
|
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]++;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* This file's sections, stored in a table to link symbols to them */
|
||||||
|
struct Section *fileSections[nbSections ? nbSections : 1];
|
||||||
|
|
||||||
|
verbosePrint("Reading %u sections...\n", nbSections);
|
||||||
|
for (uint32_t i = 0; i < nbSections; i++) {
|
||||||
|
/* Read section */
|
||||||
|
struct Section *section = malloc(sizeof(*section));
|
||||||
|
|
||||||
|
if (!section)
|
||||||
|
err(1, "%s: Couldn't create new section", fileName);
|
||||||
|
readSection(file, section, fileName);
|
||||||
|
section->fileSymbols = fileSymbols;
|
||||||
|
|
||||||
|
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 {
|
} else {
|
||||||
tSymbols[i]->pSection = NULL;
|
section->symbols = NULL;
|
||||||
|
}
|
||||||
|
section->nbSymbols = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Give symbols pointers to their sections */
|
||||||
|
for (uint32_t i = 0; i < nbSymbols; i++) {
|
||||||
|
int32_t sectionID = fileSymbols[i]->sectionID;
|
||||||
|
|
||||||
|
if (sectionID == -1) {
|
||||||
|
fileSymbols[i]->section = NULL;
|
||||||
|
} else {
|
||||||
|
fileSymbols[i]->section = fileSections[sectionID];
|
||||||
|
/* Give the section a pointer to the symbol as well */
|
||||||
|
linkSymToSect(fileSymbols[i], fileSections[sectionID]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/**
|
||||||
* The main objectfileloadroutine (phew)
|
* Reads an object file of any supported format
|
||||||
|
* @param fileName The filename to report for errors
|
||||||
*/
|
*/
|
||||||
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),
|
if (!file) {
|
||||||
pObjfile) != strlen(RGBDS_OBJECT_VERSION_STRING)) {
|
err(1, "Could not open file %s", fileName);
|
||||||
errx(1, "%s: Read error", tzObjectfile);
|
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,
|
if (matchedElems != 1)
|
||||||
strlen(RGBDS_OBJECT_VERSION_STRING)) == 0) {
|
errx(1, "\"%s\" is not a RGBDS object file", fileName);
|
||||||
obj_ReadRGB(pObjfile, tzObjectfile);
|
/* TODO: support other versions? */
|
||||||
} else {
|
if (versionNumber != 6)
|
||||||
int32_t i;
|
errx(1, "\"%s\" is an incompatible version %hhu object file",
|
||||||
|
fileName, versionNumber);
|
||||||
|
|
||||||
for (i = 0; i < strlen(RGBDS_OBJECT_VERSION_STRING); i++)
|
verbosePrint("Reading object file %s, version %hhu\n",
|
||||||
if (!isprint(tzHeader[i]))
|
fileName, versionNumber);
|
||||||
tzHeader[i] = '?';
|
|
||||||
|
|
||||||
errx(1, "%s: Invalid file or object file version [%s]",
|
readRGB6File(file, fileName);
|
||||||
tzObjectfile, tzHeader);
|
|
||||||
|
fclose(file);
|
||||||
|
}
|
||||||
|
|
||||||
|
void obj_DoSanityChecks(void)
|
||||||
|
{
|
||||||
|
sect_DoSanityChecks();
|
||||||
|
}
|
||||||
|
|
||||||
|
static void freeSection(struct Section *section, void *arg)
|
||||||
|
{
|
||||||
|
(void)arg;
|
||||||
|
|
||||||
|
free(section->name);
|
||||||
|
if (sect_HasData(section->type)) {
|
||||||
|
free(section->data);
|
||||||
|
for (int32_t i = 0; i < section->nbPatches; i++) {
|
||||||
|
struct Patch *patch = §ion->patches[i];
|
||||||
|
|
||||||
|
free(patch->fileName);
|
||||||
|
free(patch->rpnExpression);
|
||||||
|
}
|
||||||
|
free(section->patches);
|
||||||
|
}
|
||||||
|
free(section->symbols);
|
||||||
|
free(section);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void freeSymbol(struct Symbol *symbol)
|
||||||
|
{
|
||||||
|
free(symbol->name);
|
||||||
|
if (symbol->type != SYMTYPE_IMPORT)
|
||||||
|
free(symbol->fileName);
|
||||||
|
free(symbol);
|
||||||
|
}
|
||||||
|
|
||||||
|
void obj_Cleanup(void)
|
||||||
|
{
|
||||||
|
sym_CleanupSymbols();
|
||||||
|
|
||||||
|
sect_ForEach(freeSection, NULL);
|
||||||
|
sect_CleanupSections();
|
||||||
|
|
||||||
|
struct SymbolList *list = symbolLists;
|
||||||
|
|
||||||
|
while (list) {
|
||||||
|
for (size_t i = 0; i < list->nbSymbols; i++)
|
||||||
|
freeSymbol(list->symbolList[i]);
|
||||||
|
free(list->symbolList);
|
||||||
|
|
||||||
|
struct SymbolList *next = list->next;
|
||||||
|
|
||||||
|
free(list);
|
||||||
|
list = next;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void obj_Readfile(char *tzObjectfile)
|
|
||||||
{
|
|
||||||
FILE *pObjfile;
|
|
||||||
|
|
||||||
if (options & OPT_SMART_C_LINK)
|
|
||||||
oReadLib = 1;
|
|
||||||
else
|
|
||||||
oReadLib = 0;
|
|
||||||
|
|
||||||
pObjfile = fopen(tzObjectfile, "rb");
|
|
||||||
if (pObjfile == NULL)
|
|
||||||
err(1, "Unable to open object '%s'", tzObjectfile);
|
|
||||||
|
|
||||||
obj_ReadOpenFile(pObjfile, tzObjectfile);
|
|
||||||
fclose(pObjfile);
|
|
||||||
|
|
||||||
oReadLib = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
int32_t file_Length(FILE *f)
|
|
||||||
{
|
|
||||||
uint32_t r, p;
|
|
||||||
|
|
||||||
p = ftell(f);
|
|
||||||
fseek(f, 0, SEEK_END);
|
|
||||||
r = ftell(f);
|
|
||||||
fseek(f, p, SEEK_SET);
|
|
||||||
|
|
||||||
return r;
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -1,174 +1,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 <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 "extern/err.h"
|
||||||
|
|
||||||
#include "link/mylink.h"
|
FILE *outputFile;
|
||||||
#include "link/mapfile.h"
|
FILE *overlayFile;
|
||||||
#include "link/main.h"
|
FILE *symFile;
|
||||||
#include "link/assign.h"
|
FILE *mapFile;
|
||||||
|
|
||||||
char *tzOutname;
|
struct SortedSection {
|
||||||
char *tzOverlayname;
|
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;
|
static uint32_t maxNbBanks[] = {
|
||||||
uint8_t *mem;
|
[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]);
|
uint32_t targetBank = section->bank - bankranges[section->type][0];
|
||||||
if (!mem)
|
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;
|
return;
|
||||||
|
|
||||||
if (f_overlay != NULL) {
|
if (fseek(overlayFile, 0, SEEK_END) != 0) {
|
||||||
fseek(f_overlay, 0L, SEEK_SET);
|
warnx("Overlay file is not seekable, cannot check if properly formed");
|
||||||
if (fread(mem, 1, MaxAvail[BANK_INDEX_ROM0], f_overlay) !=
|
return;
|
||||||
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;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
MapfileCloseBank(area_Avail(0));
|
long overlaySize = ftell(overlayFile);
|
||||||
|
|
||||||
fwrite(mem, 1, MaxAvail[BANK_INDEX_ROM0], f);
|
if (overlaySize % 0x4000)
|
||||||
free(mem);
|
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;
|
uint16_t offset = 0;
|
||||||
uint8_t *mem;
|
|
||||||
|
|
||||||
mem = malloc(MaxAvail[bank]);
|
while (bankSections) {
|
||||||
if (!mem)
|
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;
|
return;
|
||||||
|
|
||||||
if (f_overlay != NULL && bank <= MaxOverlayBank) {
|
struct {
|
||||||
fseek(f_overlay, bank * 0x4000, SEEK_SET);
|
struct SortedSection const *sections;
|
||||||
if (fread(mem, 1, MaxAvail[bank], f_overlay) != MaxAvail[bank])
|
#define sect sections->section /* Fake member as a shortcut */
|
||||||
warnx("Failed to read data from overlay file.");
|
uint32_t i;
|
||||||
} else {
|
struct Symbol const *sym;
|
||||||
memset(mem, fillchar, MaxAvail[bank]);
|
uint16_t addr;
|
||||||
}
|
} sectList = { .sections = bankSections->sections, .i = 0 },
|
||||||
MapfileInitBank(bank);
|
zlSectList = { .sections = bankSections->zeroLenSections, .i = 0 },
|
||||||
|
*minSectList;
|
||||||
|
|
||||||
pSect = pSections;
|
for (;;) {
|
||||||
while (pSect) {
|
while (sectList.sections
|
||||||
if (pSect->Type == SECT_ROMX && pSect->nBank == bank) {
|
&& sectList.i == sectList.sect->nbSymbols) {
|
||||||
memcpy(mem + pSect->nOrg - 0x4000, pSect->pData,
|
sectList.sections = sectList.sections->next;
|
||||||
pSect->nByteSize);
|
sectList.i = 0;
|
||||||
MapfileWriteSection(pSect);
|
|
||||||
}
|
}
|
||||||
pSect = pSect->pNext;
|
while (zlSectList.sections
|
||||||
}
|
&& zlSectList.i == zlSectList.sect->nbSymbols) {
|
||||||
|
zlSectList.sections = zlSectList.sections->next;
|
||||||
MapfileCloseBank(area_Avail(bank));
|
zlSectList.i = 0;
|
||||||
|
|
||||||
fwrite(mem, 1, MaxAvail[bank], f);
|
|
||||||
free(mem);
|
|
||||||
}
|
|
||||||
|
|
||||||
void out_Setname(char *tzOutputfile)
|
|
||||||
{
|
|
||||||
tzOutname = tzOutputfile;
|
|
||||||
}
|
|
||||||
|
|
||||||
void out_SetOverlayname(char *tzOverlayfile)
|
|
||||||
{
|
|
||||||
tzOverlayname = tzOverlayfile;
|
|
||||||
}
|
|
||||||
|
|
||||||
void Output(void)
|
|
||||||
{
|
|
||||||
int32_t i;
|
|
||||||
FILE *f;
|
|
||||||
FILE *f_overlay = NULL;
|
|
||||||
|
|
||||||
/*
|
|
||||||
* 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 (!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;
|
||||||
|
|
||||||
if (ftell(f_overlay) % 0x4000 != 0)
|
minSectList = sectList.addr < zlSectList.addr
|
||||||
errx(1, "Overlay file must be aligned to 0x4000 bytes.");
|
? §List
|
||||||
|
: &zlSectList;
|
||||||
|
} else if (sectList.sections) {
|
||||||
|
sectList.sym = sectList.sect->symbols[sectList.i];
|
||||||
|
sectList.addr =
|
||||||
|
sectList.sym->offset + sectList.sect->org;
|
||||||
|
|
||||||
MaxOverlayBank = (ftell(f_overlay) / 0x4000) - 1;
|
minSectList = §List;
|
||||||
|
} else {
|
||||||
|
zlSectList.sym = zlSectList.sect->symbols[zlSectList.i];
|
||||||
|
zlSectList.addr =
|
||||||
|
zlSectList.sym->offset + zlSectList.sect->org;
|
||||||
|
|
||||||
if (MaxOverlayBank < 1)
|
minSectList = &zlSectList;
|
||||||
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));
|
fprintf(symFile, "%02x:%04x %s\n",
|
||||||
|
minSectList->sect->bank, minSectList->addr,
|
||||||
|
minSectList->sym->name);
|
||||||
|
minSectList->i++;
|
||||||
|
}
|
||||||
|
#undef sect
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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)
|
||||||
|
{
|
||||||
|
if (!mapFile)
|
||||||
|
return;
|
||||||
|
|
||||||
|
struct SortedSection const *section = sectList->sections;
|
||||||
|
struct SortedSection const *zeroLenSection = sectList->zeroLenSections;
|
||||||
|
|
||||||
|
fprintf(mapFile, "%s bank #%u:\n", typeNames[type],
|
||||||
|
bank + bankranges[type][0]);
|
||||||
|
|
||||||
|
uint16_t slack = maxsize[type];
|
||||||
|
|
||||||
|
while (section || zeroLenSection) {
|
||||||
|
struct SortedSection const **pickedSection =
|
||||||
|
nextSection(§ion, &zeroLenSection);
|
||||||
|
struct Section const *sect = (*pickedSection)->section;
|
||||||
|
|
||||||
|
slack -= sect->size;
|
||||||
|
|
||||||
|
fprintf(mapFile, " SECTION: $%04x-$%04x ($%04x byte%s) [\"%s\"]\n",
|
||||||
|
sect->org, sect->org + sect->size - 1, sect->size,
|
||||||
|
sect->size == 1 ? "" : "s", sect->name);
|
||||||
|
|
||||||
|
for (size_t i = 0; i < sect->nbSymbols; i++)
|
||||||
|
fprintf(mapFile, " $%04x = %s\n",
|
||||||
|
sect->symbols[i]->offset + sect->org,
|
||||||
|
sect->symbols[i]->name);
|
||||||
|
|
||||||
|
*pickedSection = (*pickedSection)->next;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (slack == maxsize[type])
|
||||||
|
fputs(" EMPTY\n\n", mapFile);
|
||||||
|
else
|
||||||
|
fprintf(mapFile, " SLACK: $%04x byte%s\n\n", slack,
|
||||||
|
slack == 1 ? "" : "s");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Writes the sym and/or map files, if applicable.
|
||||||
|
*/
|
||||||
|
static void writeSymAndMap(void)
|
||||||
|
{
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
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)
|
||||||
|
{
|
||||||
|
while (section) {
|
||||||
|
struct SortedSection *next = section->next;
|
||||||
|
|
||||||
|
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);
|
|
||||||
}
|
|
||||||
|
|
||||||
501
src/link/patch.c
501
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 <stdlib.h>
|
||||||
|
#include <limits.h>
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
|
|
||||||
|
#include "link/patch.h"
|
||||||
|
#include "link/section.h"
|
||||||
|
#include "link/symbol.h"
|
||||||
|
|
||||||
|
#include "linkdefs.h"
|
||||||
|
|
||||||
#include "extern/err.h"
|
#include "extern/err.h"
|
||||||
|
|
||||||
#include "link/assign.h"
|
/* This is an "empty"-type stack */
|
||||||
#include "link/main.h"
|
struct RPNStack {
|
||||||
#include "link/mylink.h"
|
int32_t *buf;
|
||||||
#include "link/symbol.h"
|
size_t size;
|
||||||
|
size_t capacity;
|
||||||
|
} stack;
|
||||||
|
|
||||||
#define RPN_STACK_SIZE 256
|
static inline void initRPNStack(void)
|
||||||
|
|
||||||
static struct sSection *pCurrentSection;
|
|
||||||
static int32_t rpnstack[RPN_STACK_SIZE];
|
|
||||||
static int32_t rpnp;
|
|
||||||
int32_t nPC;
|
|
||||||
|
|
||||||
static void rpnpush(int32_t i)
|
|
||||||
{
|
{
|
||||||
if (rpnp >= RPN_STACK_SIZE)
|
stack.capacity = 64;
|
||||||
errx(1, "RPN stack overflow");
|
stack.buf = malloc(sizeof(*stack.buf) * stack.capacity);
|
||||||
|
if (!stack.buf)
|
||||||
rpnstack[rpnp] = i;
|
err(1, "Failed to init RPN stack");
|
||||||
rpnp++;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static int32_t rpnpop(void)
|
static inline void clearRPNStack(void)
|
||||||
{
|
{
|
||||||
rpnp--;
|
stack.size = 0;
|
||||||
return rpnstack[rpnp];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static int32_t getsymvalue(struct sPatch *pPatch, int32_t symid)
|
static void pushRPN(int32_t value)
|
||||||
{
|
{
|
||||||
const struct sSymbol *tSymbol = pCurrentSection->tSymbols[symid];
|
if (stack.size >= stack.capacity) {
|
||||||
|
stack.capacity *= 2;
|
||||||
switch (tSymbol->Type) {
|
stack.buf =
|
||||||
case SYM_IMPORT:
|
realloc(stack.buf, sizeof(*stack.buf) * stack.capacity);
|
||||||
return sym_GetValue(pPatch, tSymbol->pzName);
|
if (!stack.buf)
|
||||||
|
err(1, "Failed to resize RPN stack");
|
||||||
case SYM_EXPORT:
|
|
||||||
case SYM_LOCAL:
|
|
||||||
if (strcmp(tSymbol->pzName, "@") == 0)
|
|
||||||
return nPC;
|
|
||||||
|
|
||||||
return tSymbol->nOffset + tSymbol->pSection->nOrg;
|
|
||||||
|
|
||||||
default:
|
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
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) ||
|
if (stack.size == 0)
|
||||||
BankIndexIsOAM(n) || BankIndexIsHRAM(n)) {
|
errx(1, "Internal error, RPN stack empty");
|
||||||
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;
|
|
||||||
}
|
|
||||||
|
|
||||||
return n;
|
stack.size--;
|
||||||
|
return stack.buf[stack.size];
|
||||||
}
|
}
|
||||||
|
|
||||||
static int32_t getsymbank(struct sPatch *pPatch, int32_t symid)
|
static inline void freeRPNStack(void)
|
||||||
{
|
{
|
||||||
int32_t nBank;
|
free(stack.buf);
|
||||||
const struct sSymbol *tSymbol = pCurrentSection->tSymbols[symid];
|
|
||||||
|
|
||||||
switch (tSymbol->Type) {
|
|
||||||
case SYM_IMPORT:
|
|
||||||
nBank = sym_GetBank(pPatch, tSymbol->pzName);
|
|
||||||
break;
|
|
||||||
case SYM_EXPORT:
|
|
||||||
case SYM_LOCAL:
|
|
||||||
nBank = tSymbol->pSection->nBank;
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
errx(1, "%s: Unknown symbol type", __func__);
|
|
||||||
}
|
|
||||||
|
|
||||||
return getrealbankfrominternalbank(nBank);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
int32_t calcrpn(struct sPatch *pPatch)
|
/* RPN operators */
|
||||||
|
|
||||||
|
static uint8_t getRPNByte(uint8_t const **expression, int32_t *size,
|
||||||
|
char const *fileName, int32_t lineNo)
|
||||||
{
|
{
|
||||||
int32_t t, size;
|
if (!(*size)--)
|
||||||
uint8_t *rpn;
|
errx(1, "%s(%d): RPN expression overread", fileName, lineNo);
|
||||||
uint8_t rpn_cmd;
|
return *(*expression)++;
|
||||||
int32_t nBank;
|
}
|
||||||
|
|
||||||
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;
|
clearRPNStack();
|
||||||
rpn = pPatch->pRPN;
|
|
||||||
pPatch->oRelocPatch = 0;
|
|
||||||
|
|
||||||
while (size > 0) {
|
while (size > 0) {
|
||||||
size -= 1;
|
enum RPNCommand command = getRPNByte(&expression, &size,
|
||||||
rpn_cmd = *rpn++;
|
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:
|
case RPN_ADD:
|
||||||
rpnpush(rpnpop() + rpnpop());
|
value = popRPN() + popRPN();
|
||||||
break;
|
break;
|
||||||
case RPN_SUB:
|
case RPN_SUB:
|
||||||
t = rpnpop();
|
value = popRPN();
|
||||||
rpnpush(rpnpop() - t);
|
value = popRPN() - value;
|
||||||
break;
|
break;
|
||||||
case RPN_MUL:
|
case RPN_MUL:
|
||||||
rpnpush(rpnpop() * rpnpop());
|
value = popRPN() * popRPN();
|
||||||
break;
|
break;
|
||||||
case RPN_DIV:
|
case RPN_DIV:
|
||||||
t = rpnpop();
|
value = popRPN();
|
||||||
rpnpush(rpnpop() / t);
|
value = popRPN() / value;
|
||||||
break;
|
break;
|
||||||
case RPN_MOD:
|
case RPN_MOD:
|
||||||
t = rpnpop();
|
value = popRPN();
|
||||||
rpnpush(rpnpop() % t);
|
value = popRPN() % value;
|
||||||
break;
|
break;
|
||||||
case RPN_UNSUB:
|
case RPN_UNSUB:
|
||||||
rpnpush(-rpnpop());
|
value = -popRPN();
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case RPN_OR:
|
case RPN_OR:
|
||||||
rpnpush(rpnpop() | rpnpop());
|
value = popRPN() | popRPN();
|
||||||
break;
|
break;
|
||||||
case RPN_AND:
|
case RPN_AND:
|
||||||
rpnpush(rpnpop() & rpnpop());
|
value = popRPN() & popRPN();
|
||||||
break;
|
break;
|
||||||
case RPN_XOR:
|
case RPN_XOR:
|
||||||
rpnpush(rpnpop() ^ rpnpop());
|
value = popRPN() ^ popRPN();
|
||||||
break;
|
break;
|
||||||
case RPN_UNNOT:
|
case RPN_UNNOT:
|
||||||
rpnpush(~rpnpop());
|
value = ~popRPN();
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case RPN_LOGAND:
|
case RPN_LOGAND:
|
||||||
rpnpush(rpnpop() && rpnpop());
|
value = popRPN();
|
||||||
|
value = popRPN() && value;
|
||||||
break;
|
break;
|
||||||
case RPN_LOGOR:
|
case RPN_LOGOR:
|
||||||
rpnpush(rpnpop() || rpnpop());
|
value = popRPN();
|
||||||
|
value = popRPN() || value;
|
||||||
break;
|
break;
|
||||||
case RPN_LOGUNNOT:
|
case RPN_LOGUNNOT:
|
||||||
rpnpush(!rpnpop());
|
value = !popRPN();
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case RPN_LOGEQ:
|
case RPN_LOGEQ:
|
||||||
rpnpush(rpnpop() == rpnpop());
|
value = popRPN() == popRPN();
|
||||||
break;
|
break;
|
||||||
case RPN_LOGNE:
|
case RPN_LOGNE:
|
||||||
rpnpush(rpnpop() != rpnpop());
|
value = popRPN() != popRPN();
|
||||||
break;
|
break;
|
||||||
case RPN_LOGGT:
|
case RPN_LOGGT:
|
||||||
t = rpnpop();
|
value = popRPN();
|
||||||
rpnpush(rpnpop() > t);
|
value = popRPN() > value;
|
||||||
break;
|
break;
|
||||||
case RPN_LOGLT:
|
case RPN_LOGLT:
|
||||||
t = rpnpop();
|
value = popRPN();
|
||||||
rpnpush(rpnpop() < t);
|
value = popRPN() < value;
|
||||||
break;
|
break;
|
||||||
case RPN_LOGGE:
|
case RPN_LOGGE:
|
||||||
t = rpnpop();
|
value = popRPN();
|
||||||
rpnpush(rpnpop() >= t);
|
value = popRPN() >= value;
|
||||||
break;
|
break;
|
||||||
case RPN_LOGLE:
|
case RPN_LOGLE:
|
||||||
t = rpnpop();
|
value = popRPN();
|
||||||
rpnpush(rpnpop() <= t);
|
value = popRPN() <= value;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
/* FIXME: sanitize shifts */
|
||||||
case RPN_SHL:
|
case RPN_SHL:
|
||||||
t = rpnpop();
|
value = popRPN();
|
||||||
rpnpush(rpnpop() << t);
|
value = popRPN() << value;
|
||||||
break;
|
break;
|
||||||
case RPN_SHR:
|
case RPN_SHR:
|
||||||
t = rpnpop();
|
value = popRPN();
|
||||||
rpnpush(rpnpop() >> t);
|
value = popRPN() >> value;
|
||||||
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;
|
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case RPN_BANK_SYM:
|
case RPN_BANK_SYM:
|
||||||
/* symbol */
|
value = 0;
|
||||||
t = (*rpn++);
|
for (uint8_t shift = 0; shift < 32; shift += 8)
|
||||||
t |= (*rpn++) << 8;
|
value |= getRPNByte(&expression, &size,
|
||||||
t |= (*rpn++) << 16;
|
patch->fileName,
|
||||||
t |= (*rpn++) << 24;
|
patch->lineNo) << shift;
|
||||||
rpnpush(getsymbank(pPatch, t));
|
|
||||||
size -= 4;
|
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;
|
break;
|
||||||
|
|
||||||
case RPN_BANK_SECT:
|
case RPN_BANK_SECT:
|
||||||
{
|
name = (char const *)expression;
|
||||||
char *name = (char *)rpn;
|
while (getRPNByte(&expression, &size, patch->fileName,
|
||||||
|
patch->lineNo))
|
||||||
|
;
|
||||||
|
|
||||||
struct sSection *pSection = GetSectionByName(name);
|
sect = sect_GetSection(name);
|
||||||
|
|
||||||
if (pSection == NULL) {
|
if (!sect)
|
||||||
errx(1,
|
errx(1, "%s(%d): Requested BANK() of section \"%s\", which was not found",
|
||||||
"%s(%ld) : Requested BANK() of section \"%s\", which was not found.\n",
|
patch->fileName, patch->lineNo, name);
|
||||||
pPatch->pzFilename, pPatch->nLineNo,
|
|
||||||
name);
|
|
||||||
}
|
|
||||||
|
|
||||||
nBank = pSection->nBank;
|
value = sect->bank;
|
||||||
rpnpush(getrealbankfrominternalbank(nBank));
|
|
||||||
|
|
||||||
int len = strlen(name);
|
|
||||||
|
|
||||||
size -= len + 1;
|
|
||||||
rpn += len + 1;
|
|
||||||
break;
|
break;
|
||||||
}
|
|
||||||
case RPN_BANK_SELF:
|
case RPN_BANK_SELF:
|
||||||
nBank = pCurrentSection->nBank;
|
value = section->bank;
|
||||||
rpnpush(getrealbankfrominternalbank(nBank));
|
|
||||||
break;
|
break;
|
||||||
default:
|
|
||||||
errx(1, "%s: Invalid command %d\n", __func__,
|
case RPN_HRAM:
|
||||||
rpn_cmd);
|
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;
|
break;
|
||||||
}
|
|
||||||
}
|
|
||||||
return rpnpop();
|
|
||||||
}
|
|
||||||
|
|
||||||
void Patch(void)
|
case RPN_CONST:
|
||||||
{
|
value = 0;
|
||||||
struct sSection *pSect;
|
for (uint8_t shift = 0; shift < 32; shift += 8)
|
||||||
|
value |= getRPNByte(&expression, &size,
|
||||||
|
patch->fileName,
|
||||||
|
patch->lineNo) << shift;
|
||||||
|
break;
|
||||||
|
|
||||||
pSect = pSections;
|
case RPN_SYM:
|
||||||
while (pSect) {
|
value = 0;
|
||||||
struct sPatch *pPatch;
|
for (uint8_t shift = 0; shift < 32; shift += 8)
|
||||||
|
value |= getRPNByte(&expression, &size,
|
||||||
|
patch->fileName,
|
||||||
|
patch->lineNo) << shift;
|
||||||
|
|
||||||
pCurrentSection = pSect;
|
symbol = section->fileSymbols[value];
|
||||||
pPatch = pSect->pPatches;
|
|
||||||
while (pPatch) {
|
|
||||||
int32_t t;
|
|
||||||
int32_t nPatchOrg;
|
|
||||||
|
|
||||||
nPC = pSect->nOrg + pPatch->nOffset;
|
/* If the symbol is defined elsewhere... */
|
||||||
t = calcrpn(pPatch);
|
if (symbol->type == SYMTYPE_IMPORT) {
|
||||||
switch (pPatch->Type) {
|
struct Symbol const *symbolDefinition =
|
||||||
case PATCH_BYTE:
|
sym_GetSymbol(symbol->name);
|
||||||
if (t >= -128 && t <= 255) {
|
if (!symbolDefinition)
|
||||||
t &= 0xFF;
|
errx(1, "%s(%d): Unknown symbol \"%s\"",
|
||||||
pSect->pData[pPatch->nOffset] =
|
patch->fileName, patch->lineNo,
|
||||||
(uint8_t)t;
|
symbol->name);
|
||||||
} else {
|
symbol = symbolDefinition;
|
||||||
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;
|
|
||||||
|
|
||||||
/* 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);
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
errx(1, "%s: Internal error.", __func__);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pPatch = pPatch->pNext;
|
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;
|
||||||
}
|
}
|
||||||
|
|
||||||
pSect = pSect->pNext;
|
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)
|
||||||
|
{
|
||||||
|
(void)arg;
|
||||||
|
|
||||||
|
if (!sect_HasData(section->type))
|
||||||
|
return;
|
||||||
|
|
||||||
|
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);
|
||||||
|
|
||||||
|
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 {
|
||||||
|
/* 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}
|
||||||
|
};
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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 <stdint.h>
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
|
#include <ctype.h>
|
||||||
|
|
||||||
|
#include "link/main.h"
|
||||||
|
#include "link/script.h"
|
||||||
|
#include "link/section.h"
|
||||||
|
|
||||||
#include "extern/err.h"
|
#include "extern/err.h"
|
||||||
|
|
||||||
#include "link/assign.h"
|
FILE *linkerScript;
|
||||||
#include "link/mylink.h"
|
|
||||||
|
static uint32_t lineNo;
|
||||||
|
|
||||||
static struct {
|
static struct {
|
||||||
uint32_t address; /* current address to write sections to */
|
FILE *file;
|
||||||
uint32_t top_address; /* not inclusive */
|
uint32_t lineNo;
|
||||||
enum eSectionType type;
|
char const *name;
|
||||||
} bank[BANK_INDEX_MAX];
|
} *fileStack;
|
||||||
|
|
||||||
static int32_t current_bank = -1; /* Bank as seen by the bank array */
|
static uint32_t fileStackSize;
|
||||||
static int32_t current_real_bank = -1; /* bank as seen by the GB */
|
static uint32_t fileStackIndex;
|
||||||
|
|
||||||
/* Current section attributes */
|
static void pushFile(char const *newFileName)
|
||||||
static int32_t fix_org = -1;
|
|
||||||
static int32_t fix_align = 1;
|
|
||||||
|
|
||||||
void script_InitSections(void)
|
|
||||||
{
|
{
|
||||||
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 (fileStackIndex == fileStackSize) {
|
||||||
if (BankIndexIsROM0(i)) {
|
if (!fileStackSize) /* Init file stack */
|
||||||
/* ROM0 bank */
|
fileStackSize = 4;
|
||||||
bank[i].address = 0x0000;
|
fileStackSize *= 2;
|
||||||
if (options & OPT_TINY)
|
fileStack = realloc(fileStack,
|
||||||
bank[i].top_address = 0x8000;
|
sizeof(*fileStack) * fileStackSize);
|
||||||
else
|
if (!fileStack)
|
||||||
bank[i].top_address = 0x4000;
|
err(1, "%s(%u): Internal INCLUDE error",
|
||||||
bank[i].type = SECT_ROM0;
|
linkerScriptName, lineNo);
|
||||||
} else if (BankIndexIsROMX(i)) {
|
}
|
||||||
/* Swappable ROM bank */
|
|
||||||
bank[i].address = 0x4000;
|
fileStack[fileStackIndex].file = linkerScript;
|
||||||
bank[i].top_address = 0x8000;
|
fileStack[fileStackIndex].lineNo = lineNo;
|
||||||
bank[i].type = SECT_ROMX;
|
fileStack[fileStackIndex].name = linkerScriptName;
|
||||||
} else if (BankIndexIsWRAM0(i)) {
|
fileStackIndex++;
|
||||||
/* WRAM */
|
|
||||||
bank[i].address = 0xC000;
|
linkerScript = fopen(newFileName, "r");
|
||||||
if (options & OPT_CONTWRAM)
|
if (!linkerScript)
|
||||||
bank[i].top_address = 0xE000;
|
err(1, "%s(%u): Could not open \"%s\"",
|
||||||
else
|
linkerScriptName, lineNo, newFileName);
|
||||||
bank[i].top_address = 0xD000;
|
lineNo = 1;
|
||||||
bank[i].type = SECT_WRAM0;
|
linkerScriptName = newFileName;
|
||||||
} else if (BankIndexIsSRAM(i)) {
|
}
|
||||||
/* Swappable SRAM bank */
|
|
||||||
bank[i].address = 0xA000;
|
static bool popFile(void)
|
||||||
bank[i].top_address = 0xC000;
|
{
|
||||||
bank[i].type = SECT_SRAM;
|
if (!fileStackIndex)
|
||||||
} else if (BankIndexIsWRAMX(i)) {
|
return false;
|
||||||
/* Swappable WRAM bank */
|
|
||||||
bank[i].address = 0xD000;
|
fileStackIndex--;
|
||||||
bank[i].top_address = 0xE000;
|
linkerScript = fileStack[fileStackIndex].file;
|
||||||
bank[i].type = SECT_WRAMX;
|
lineNo = fileStack[fileStackIndex].lineNo;
|
||||||
} else if (BankIndexIsVRAM(i)) {
|
linkerScriptName = fileStack[fileStackIndex].name;
|
||||||
/* Swappable VRAM bank */
|
|
||||||
bank[i].address = 0x8000;
|
return true;
|
||||||
bank[i].type = SECT_VRAM;
|
}
|
||||||
if (options & OPT_DMG_MODE && i != BANK_INDEX_VRAM) {
|
|
||||||
/* In DMG the only available bank is bank 0. */
|
static inline bool isWhiteSpace(int c)
|
||||||
bank[i].top_address = 0x8000;
|
{
|
||||||
} else {
|
return c == ' ' || c == '\t';
|
||||||
bank[i].top_address = 0xA000;
|
}
|
||||||
|
|
||||||
|
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__);
|
||||||
|
}
|
||||||
|
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__);
|
||||||
|
}
|
||||||
|
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';
|
||||||
}
|
}
|
||||||
} 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);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
}
|
}
|
||||||
|
|
||||||
void script_SetCurrentSectionType(const char *type, uint32_t bank_num)
|
static void processCommand(enum LinkerScriptCommand command, uint16_t arg,
|
||||||
|
uint16_t *pc)
|
||||||
{
|
{
|
||||||
if (strcmp(type, "ROM0") == 0) {
|
switch (command) {
|
||||||
if (bank_num != 0)
|
case COMMAND_INVALID:
|
||||||
errx(1, "Trying to assign a bank number to ROM0.\n");
|
trap_;
|
||||||
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");
|
|
||||||
|
|
||||||
current_bank = BANK_INDEX_WRAM0;
|
case COMMAND_ORG:
|
||||||
current_real_bank = 0;
|
*pc = arg;
|
||||||
return;
|
break;
|
||||||
} else if (strcmp(type, "WRAMX") == 0) {
|
|
||||||
if (bank_num == 0)
|
case COMMAND_ALIGN:
|
||||||
errx(1, "WRAMX index can't be 0.\n");
|
if (arg >= 16)
|
||||||
if (bank_num > BANK_COUNT_WRAMX) {
|
arg = 0;
|
||||||
errx(1, "WRAMX index too big (%d > %d).\n", bank_num,
|
else
|
||||||
BANK_COUNT_WRAMX);
|
arg = (*pc + (1 << arg) - 1) & ~((1 << arg) - 1);
|
||||||
}
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
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",
|
|
||||||
__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",
|
|
||||||
__func__);
|
|
||||||
}
|
|
||||||
current_bank = BANK_INDEX_HRAM;
|
|
||||||
current_real_bank = 0;
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
errx(1, "%s: Unknown section type \"%s\".\n", __func__, type);
|
if (arg < *pc)
|
||||||
|
errx(1, "%s(%u): `%s` cannot be used to go backwards",
|
||||||
|
linkerScriptName, lineNo, commands[command]);
|
||||||
|
*pc = arg;
|
||||||
}
|
}
|
||||||
|
|
||||||
void script_SetAddress(uint32_t addr)
|
enum LinkerScriptParserState {
|
||||||
|
PARSER_FIRSTTIME,
|
||||||
|
PARSER_LINESTART,
|
||||||
|
PARSER_INCLUDE, /* After an INCLUDE token */
|
||||||
|
PARSER_LINEEND
|
||||||
|
};
|
||||||
|
|
||||||
|
/* Part of internal state, but has data that needs to be freed */
|
||||||
|
static uint16_t *curaddr[SECTTYPE_INVALID];
|
||||||
|
|
||||||
|
/* Put as global to ensure it's initialized only once */
|
||||||
|
static enum LinkerScriptParserState parserState = PARSER_FIRSTTIME;
|
||||||
|
|
||||||
|
struct SectionPlacement *script_NextSection(void)
|
||||||
{
|
{
|
||||||
if (current_bank == -1)
|
static struct SectionPlacement section;
|
||||||
errx(1, "Trying to set an address without assigned bank\n");
|
static enum SectionType type;
|
||||||
|
static uint32_t bank;
|
||||||
|
static uint32_t bankID;
|
||||||
|
|
||||||
/* Make sure that we don't go back. */
|
if (parserState == PARSER_FIRSTTIME) {
|
||||||
if (bank[current_bank].address > addr) {
|
lineNo = 1;
|
||||||
errx(1, "Trying to go to a previous address (0x%04X to 0x%04X)\n",
|
|
||||||
bank[current_bank].address, addr);
|
/* 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];
|
||||||
|
}
|
||||||
|
|
||||||
|
type = SECTTYPE_INVALID;
|
||||||
|
|
||||||
|
parserState = PARSER_LINESTART;
|
||||||
}
|
}
|
||||||
|
|
||||||
bank[current_bank].address = addr;
|
for (;;) {
|
||||||
|
struct LinkerScriptToken const *token = nextToken();
|
||||||
|
enum LinkerScriptTokenType tokType;
|
||||||
|
union LinkerScriptTokenAttr attr;
|
||||||
|
bool hasArg;
|
||||||
|
uint32_t arg;
|
||||||
|
|
||||||
/* Make sure we don't overflow */
|
if (type != SECTTYPE_INVALID) {
|
||||||
if (bank[current_bank].address >= bank[current_bank].top_address) {
|
if (curaddr[type][bankID] > endaddr(type) + 1)
|
||||||
errx(1, "Bank overflowed (0x%04X >= 0x%04X)\n",
|
errx(1, "%s(%u): Sections would extend past the end of %s ($%04hx > $%04hx)",
|
||||||
bank[current_bank].address,
|
linkerScriptName, lineNo, typeNames[type],
|
||||||
bank[current_bank].top_address);
|
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]);
|
||||||
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fix_org = addr;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void script_SetAlignment(uint32_t alignment)
|
void script_Cleanup(void)
|
||||||
{
|
{
|
||||||
if (current_bank == -1)
|
for (enum SectionType type = 0; type < SECTTYPE_INVALID; type++)
|
||||||
errx(1, "Trying to set an alignment without assigned bank\n");
|
free(curaddr[type]);
|
||||||
|
|
||||||
if (alignment > 15)
|
|
||||||
errx(1, "Trying to set an alignment too big: %d\n", alignment);
|
|
||||||
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Make sure we don't overflow */
|
|
||||||
if (bank[current_bank].address >= bank[current_bank].top_address) {
|
|
||||||
errx(1, "Bank overflowed (0x%04X >= 0x%04X)\n",
|
|
||||||
bank[current_bank].address,
|
|
||||||
bank[current_bank].top_address);
|
|
||||||
}
|
|
||||||
|
|
||||||
fix_align = size;
|
|
||||||
}
|
|
||||||
|
|
||||||
void script_OutputSection(const char *section_name)
|
|
||||||
{
|
|
||||||
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;
|
|
||||||
}
|
}
|
||||||
|
|||||||
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 <stdbool.h>
|
||||||
#include <stdio.h>
|
|
||||||
#include <stdlib.h>
|
|
||||||
#include <string.h>
|
|
||||||
|
|
||||||
#include "extern/err.h"
|
|
||||||
|
|
||||||
|
#include "link/symbol.h"
|
||||||
#include "link/main.h"
|
#include "link/main.h"
|
||||||
#include "link/patch.h"
|
#include "extern/err.h"
|
||||||
#include "link/mylink.h"
|
#include "hashmap.h"
|
||||||
|
|
||||||
#include "types.h"
|
HashMap symbols;
|
||||||
|
|
||||||
#define HASHSIZE 73
|
struct ForEachArg {
|
||||||
|
void (*callback)(struct Symbol *symbol, void *arg);
|
||||||
struct ISymbol {
|
void *arg;
|
||||||
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 ISymbol *tHash[HASHSIZE];
|
static void forEach(void *symbol, void *arg)
|
||||||
|
|
||||||
int32_t calchash(char *s)
|
|
||||||
{
|
{
|
||||||
int32_t r = 0;
|
struct ForEachArg *callbackArg = (struct ForEachArg *)arg;
|
||||||
|
|
||||||
while (*s)
|
callbackArg->callback((struct Symbol *)symbol, callbackArg->arg);
|
||||||
r += *s++;
|
|
||||||
|
|
||||||
return r % HASHSIZE;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
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)
|
hash_ForEach(symbols, forEach, &callbackArg);
|
||||||
tHash[i] = NULL;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
int32_t sym_GetValue(struct sPatch *pPatch, char *tzName)
|
void sym_AddSymbol(struct Symbol *symbol)
|
||||||
{
|
{
|
||||||
if (strcmp(tzName, "@") == 0)
|
/* Check if the symbol already exists */
|
||||||
return nPC;
|
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)]);
|
/* If not, add it */
|
||||||
while (*ppSym) {
|
bool collided = hash_AddElement(symbols, symbol->name, symbol);
|
||||||
if (strcmp(tzName, (*ppSym)->pzName))
|
|
||||||
ppSym = &((*ppSym)->pNext);
|
|
||||||
else
|
|
||||||
return ((*ppSym)->nValue);
|
|
||||||
}
|
|
||||||
|
|
||||||
errx(1,
|
if (beVerbose && collided)
|
||||||
"%s(%ld) : Unknown symbol '%s'",
|
warnx("Symbol hashmap collision occurred!");
|
||||||
pPatch->pzFilename, pPatch->nLineNo,
|
|
||||||
tzName);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
int32_t sym_GetBank(struct sPatch *pPatch, char *tzName)
|
struct Symbol *sym_GetSymbol(char const *name)
|
||||||
{
|
{
|
||||||
struct ISymbol **ppSym;
|
return (struct Symbol *)hash_GetElement(symbols, name);
|
||||||
|
|
||||||
ppSym = &(tHash[calchash(tzName)]);
|
|
||||||
while (*ppSym) {
|
|
||||||
if (strcmp(tzName, (*ppSym)->pzName))
|
|
||||||
ppSym = &((*ppSym)->pNext);
|
|
||||||
else
|
|
||||||
return ((*ppSym)->nBank);
|
|
||||||
}
|
|
||||||
|
|
||||||
errx(1,
|
|
||||||
"%s(%ld) : Unknown symbol '%s'",
|
|
||||||
pPatch->pzFilename, pPatch->nLineNo,
|
|
||||||
tzName);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void sym_CreateSymbol(char *tzName, int32_t nValue, int32_t nBank,
|
void sym_CleanupSymbols(void)
|
||||||
char *tzObjFileName, char *tzFileName, uint32_t nFileLine)
|
|
||||||
{
|
{
|
||||||
if (strcmp(tzName, "@") == 0)
|
hash_EmptyMap(symbols);
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -91,8 +91,8 @@ REPT NumberOfSections
|
|||||||
; decide (floating bank). This field is only valid for ROMX,
|
; decide (floating bank). This field is only valid for ROMX,
|
||||||
; VRAM, WRAMX and SRAM sections.
|
; VRAM, WRAMX and SRAM sections.
|
||||||
|
|
||||||
LONG Align ; Alignment of this section (expressed as number of low bits
|
LONG Align ; Alignment of this section, expressed as 1 << align. 1 if
|
||||||
; to leave as 0). -1 if not defined.
|
; not specified.
|
||||||
|
|
||||||
IF (Type == ROMX) || (Type == ROM0) ; Sections that can contain data.
|
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
|
ROM0
|
||||||
org $10
|
org $18
|
||||||
"sec"
|
"sec"
|
||||||
org $20
|
org $20
|
||||||
"secfix"
|
"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