Partial linking of overlay segments, relax linker script alignment

This commit is contained in:
Thar0 2024-11-07 16:04:35 +00:00 committed by Tharo
parent b204d6c089
commit cf2b2716a7
13 changed files with 1298 additions and 1404 deletions

View File

@ -316,8 +316,8 @@ CHECK_WARNINGS += -Werror=implicit-int -Werror=implicit-function-declaration -We
# `-traditional-cpp` was passed) so we use `gcc -E` instead.
CPP := gcc -E
MKLDSCRIPT := tools/mkldscript
MKOVLRULES := tools/mkovlrules
MKDMADATA := tools/mkdmadata
ELF2ROM := tools/elf2rom
BIN2C := tools/bin2c
N64TEXCONV := tools/assets/n64texconv/n64texconv
FADO := tools/fado/fado.elf
@ -494,14 +494,6 @@ ASSET_FILES_OUT := $(foreach f,$(ASSET_FILES_BIN_EXTRACTED:.bin=.bin.inc.c),$(f:
# Find all .o files included in the spec
SPEC_O_FILES := $(shell $(CPP) $(CPPFLAGS) -I. $(SPEC) | $(BUILD_DIR_REPLACE) | sed -n -E 's/^[ \t]*include[ \t]*"([a-zA-Z0-9/_.-]+\.o)"/\1/p')
# Split out reloc files
O_FILES := $(filter-out %_reloc.o,$(SPEC_O_FILES))
OVL_RELOC_FILES := $(filter %_reloc.o,$(SPEC_O_FILES))
# Automatic dependency files
# (Only asm_processor dependencies and reloc dependencies are handled for now)
DEP_FILES := $(O_FILES:.o=.d) $(O_FILES:.o=.asmproc.d) $(OVL_RELOC_FILES:.o=.d) $(BUILD_DIR)/spec.d
TEXTURE_FILES_PNG_EXTRACTED := $(foreach dir,$(ASSET_BIN_DIRS_EXTRACTED),$(wildcard $(dir)/*.png))
TEXTURE_FILES_PNG_COMMITTED := $(foreach dir,$(ASSET_BIN_DIRS_COMMITTED),$(wildcard $(dir)/*.png))
TEXTURE_FILES_JPG_EXTRACTED := $(foreach dir,$(ASSET_BIN_DIRS_EXTRACTED),$(wildcard $(dir)/*.jpg))
@ -511,10 +503,13 @@ TEXTURE_FILES_OUT := $(foreach f,$(TEXTURE_FILES_PNG_EXTRACTED:.png=.inc.c),$(f:
$(foreach f,$(TEXTURE_FILES_JPG_EXTRACTED:.jpg=.jpg.inc.c),$(f:$(EXTRACTED_DIR)/%=$(BUILD_DIR)/%)) \
$(foreach f,$(TEXTURE_FILES_JPG_COMMITTED:.jpg=.jpg.inc.c),$(BUILD_DIR)/$f)
OVL_SEGMENTS_DIR := $(BUILD_DIR)/segments
# create build directories
$(shell mkdir -p $(BUILD_DIR)/baserom \
$(BUILD_DIR)/assets/text \
$(BUILD_DIR)/linker_scripts)
$(BUILD_DIR)/linker_scripts \
$(OVL_SEGMENTS_DIR))
$(shell mkdir -p $(foreach dir, \
$(SRC_DIRS) \
$(UNDECOMPILED_DATA_DIRS) \
@ -534,6 +529,24 @@ $(shell mkdir -p $(foreach dir, \
$(dir:$(EXTRACTED_DIR)/%=$(BUILD_DIR)/%)))
endif
# Generate and include segment makefile rules for combining overlay .o files into single .plf files, from which
# overlay relocations will be generated.
# If this makefile doesn't exist or if the spec has been modified since make was last ran it will use the rule
# later on in the file to regenerate this file before including it. The test against MAKECMDGOALS ensures this
# doesn't happen if we're not running a task that needs these partially linked files; this is especially important
# for setup since the rule to generate the segment makefile rules requires setup to have ran first.
OVLDFLAGS = -r -T linker_scripts/segment.ld -Map $(@:.plf=.map)
ifeq ($(MAKECMDGOALS),$(filter-out clean assetclean distclean setup,$(MAKECMDGOALS)))
include $(OVL_SEGMENTS_DIR)/Makefile
else
OVL_SEGMENT_FILES :=
endif
OVL_RELOC_FILES := $(OVL_SEGMENT_FILES:.plf=.reloc.o)
# Automatic dependency files
# (Only asm_processor dependencies and reloc dependencies are handled for now)
DEP_FILES := $(O_FILES:.o=.asmproc.d) $(OVL_RELOC_FILES:.o=.d)
$(BUILD_DIR)/src/boot/build.o: CPP_DEFINES += -DBUILD_CREATOR="\"$(BUILD_CREATOR)\"" -DBUILD_DATE="\"$(BUILD_DATE)\"" -DBUILD_TIME="\"$(BUILD_TIME)\""
$(BUILD_DIR)/src/audio/internal/seqplayer.o: CPP_DEFINES += -DMML_VERSION=MML_VERSION_OOT
@ -742,7 +755,7 @@ else
$(BUILD_DIR)/assets/%.o: CFLAGS += -fno-zero-initialized-in-bss -fno-toplevel-reorder
$(BUILD_DIR)/src/%.o: CFLAGS += -fexec-charset=euc-jp
$(BUILD_DIR)/src/libultra/libc/ll.o: OPTFLAGS := -Ofast
$(BUILD_DIR)/src/overlays/%.o: CFLAGS += -fno-merge-constants -mno-explicit-relocs -mno-split-addresses
$(BUILD_DIR)/src/overlays/%.o: CFLAGS += -mno-explicit-relocs -mno-split-addresses
endif
#### Main Targets ###
@ -821,7 +834,8 @@ else
endif
$(ROM): $(ELF)
$(ELF2ROM) -cic $(CIC) $< $@
$(OBJCOPY) --pad-to 0x$$($(OBJDUMP) -t $< | grep _RomSize | cut -d ' ' -f 1) -O binary $< $@
$(PYTHON) -m ipl3checksum sum --cic 6105 --update $@
$(ROMC): $(ROM) $(ELF) $(BUILD_DIR)/compress_ranges.txt
$(PYTHON) tools/compress.py --in $(ROM) --out $@ --dmadata-start `./tools/dmadata_start.sh $(NM) $(ELF)` --compress `cat $(BUILD_DIR)/compress_ranges.txt` --threads $(N_THREADS) $(COMPRESS_ARGS)
@ -837,7 +851,8 @@ ifeq ($(PLATFORM),IQUE)
endif
endif
$(ELF): $(TEXTURE_FILES_OUT) $(ASSET_FILES_OUT) $(O_FILES) $(OVL_RELOC_FILES) $(LDSCRIPT) $(BUILD_DIR)/linker_scripts/makerom.ld $(BUILD_DIR)/undefined_syms.txt \
$(ELF): $(TEXTURE_FILES_OUT) $(ASSET_FILES_OUT) $(O_FILES) $(OVL_SEGMENT_FILES) $(OVL_RELOC_FILES) $(LDSCRIPT) \
$(BUILD_DIR)/linker_scripts/makerom.ld $(BUILD_DIR)/undefined_syms.txt \
$(SAMPLEBANK_O_FILES) $(SOUNDFONT_O_FILES) $(SEQUENCE_O_FILES) \
$(BUILD_DIR)/assets/audio/sequence_font_table.o $(BUILD_DIR)/assets/audio/audiobank_padding.o
$(LD) $(LDFLAGS) -o $@
@ -846,22 +861,31 @@ $(BUILD_DIR)/linker_scripts/makerom.ld: linker_scripts/makerom.ld
$(CPP) -I include $(CPPFLAGS) $< > $@
## Order-only prerequisites
# These ensure e.g. the O_FILES are built before the OVL_RELOC_FILES.
# These ensure e.g. texture files are built before object files that include them.
# The intermediate phony targets avoid quadratically-many dependencies between the targets and prerequisites.
o_files: $(O_FILES)
$(OVL_RELOC_FILES): | o_files
asset_files: $(TEXTURE_FILES_OUT) $(ASSET_FILES_OUT)
$(O_FILES): | asset_files
.PHONY: o_files asset_files
.PHONY: asset_files
$(BUILD_DIR)/spec: $(SPEC) $(SPEC_INCLUDES)
$(CPP) $(CPPFLAGS) -MD -MP -MF $@.d -MT $@ -I. $< | $(BUILD_DIR_REPLACE) > $@
$(LDSCRIPT): $(BUILD_DIR)/spec
$(MKLDSCRIPT) $< $@
$(LDSCRIPT): $(BUILD_DIR)/$(SPEC)
$(MKLDSCRIPT) $< $@ $(OVL_SEGMENTS_DIR)
# Generates a makefile containing rules for building .plf files
# from overlay .o files for every overlay defined in the spec.
$(OVL_SEGMENTS_DIR)/Makefile: $(BUILD_DIR)/$(SPEC)
$(MKOVLRULES) $< $(OVL_SEGMENTS_DIR) $@
# Generates relocations for each overlay after partial linking so that the final
# link step cannot later insert padding between individual overlay files after
# relocations have already been calculated.
$(OVL_SEGMENTS_DIR)/%.reloc.o: $(OVL_SEGMENTS_DIR)/%.plf
$(FADO) $< -n $(notdir $*) -o $(@:.o=.s)
$(AS) $(ASFLAGS) $(@:.o=.s) -o $@
$(BUILD_DIR)/undefined_syms.txt: undefined_syms.txt
$(CPP) $(CPPFLAGS) $< > $@
@ -974,12 +998,9 @@ $(BUILD_DIR)/src/overlays/misc/ovl_kaleido_scope/ovl_kaleido_scope_reloc.o: POST
endif
endif
$(BUILD_DIR)/src/overlays/%_reloc.o: $(BUILD_DIR)/spec
$(FADO) $$(tools/reloc_prereq $< $(notdir $*)) -n $(notdir $*) -o $(@:.o=.s) -M $(@:.o=.d)
$(POSTPROCESS_OBJ) $(@:.o=.s)
$(AS) $(ASFLAGS) $(@:.o=.s) -o $@
# Assets from assets/
$(BUILD_DIR)/assets/%.inc.c: assets/%.png
$(ZAPD) btex -eh -tt $(subst .,,$(suffix $*)) -i $< -o $@
$(BUILD_DIR)/assets/%.inc.c: assets/%.png
tools/assets/build_from_png/build_from_png $< $(dir $@) assets/$(dir $*) $(wildcard $(EXTRACTED_DIR)/assets/$(dir $*))

22
linker_scripts/segment.ld Normal file
View File

@ -0,0 +1,22 @@
OUTPUT_ARCH (mips)
/* Pass through all sections for partial linking of overlays. Also performs constant merging for GCC. */
SECTIONS {
.rodata :
{
*(*.rodata.cst4)
*(*.rodata.cst8)
*(*.rodata.*)
}
/DISCARD/ :
{
/* GNU ld assumes that the linker script always combines .gptab.data and
* .gptab.sdata into .gptab.sdata, and likewise for .gptab.bss and .gptab.sbss.
* To avoid dealing with this, we just discard all .gptab sections.
*/
*(.gptab.*)
}
}

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -911,38 +911,39 @@ endseg
beginseg
name "ovl_title"
compress
flags OVERLAY
address 0x80800000
include "$(BUILD_DIR)/src/overlays/gamestates/ovl_title/z_title.o"
include "$(BUILD_DIR)/src/overlays/gamestates/ovl_title/ovl_title_reloc.o"
endseg
beginseg
name "ovl_select"
compress
flags OVERLAY
include "$(BUILD_DIR)/src/overlays/gamestates/ovl_select/z_select.o"
include "$(BUILD_DIR)/src/overlays/gamestates/ovl_select/ovl_select_reloc.o"
endseg
beginseg
name "ovl_opening"
compress
flags OVERLAY
include "$(BUILD_DIR)/src/overlays/gamestates/ovl_opening/z_opening.o"
include "$(BUILD_DIR)/src/overlays/gamestates/ovl_opening/ovl_opening_reloc.o"
endseg
beginseg
name "ovl_file_choose"
compress
flags OVERLAY
include "$(BUILD_DIR)/src/overlays/gamestates/ovl_file_choose/z_file_nameset_data.o"
include "$(BUILD_DIR)/src/overlays/gamestates/ovl_file_choose/z_file_copy_erase.o"
include "$(BUILD_DIR)/src/overlays/gamestates/ovl_file_choose/z_file_nameset.o"
include "$(BUILD_DIR)/src/overlays/gamestates/ovl_file_choose/z_file_choose.o"
include "$(BUILD_DIR)/src/overlays/gamestates/ovl_file_choose/ovl_file_choose_reloc.o"
endseg
beginseg
name "ovl_kaleido_scope"
compress
flags OVERLAY
include "$(BUILD_DIR)/src/overlays/misc/ovl_kaleido_scope/z_kaleido_collect.o"
include "$(BUILD_DIR)/src/overlays/misc/ovl_kaleido_scope/z_kaleido_debug.o"
include "$(BUILD_DIR)/src/overlays/misc/ovl_kaleido_scope/z_kaleido_equipment.o"
@ -956,32 +957,31 @@ beginseg
#else
include "$(BUILD_DIR)/src/overlays/misc/ovl_kaleido_scope/z_lmap_mark_data_mq.o"
#endif
include "$(BUILD_DIR)/src/overlays/misc/ovl_kaleido_scope/ovl_kaleido_scope_reloc.o"
endseg
beginseg
name "ovl_player_actor"
compress
flags OVERLAY
include "$(BUILD_DIR)/src/overlays/actors/ovl_player_actor/z_player.o"
include "$(BUILD_DIR)/src/overlays/actors/ovl_player_actor/ovl_player_actor_reloc.o"
endseg
beginseg
name "ovl_map_mark_data"
compress
flags OVERLAY
#if !OOT_MQ
include "$(BUILD_DIR)/src/overlays/misc/ovl_map_mark_data/z_map_mark_data.o"
#else
include "$(BUILD_DIR)/src/overlays/misc/ovl_map_mark_data/z_map_mark_data_mq.o"
#endif
include "$(BUILD_DIR)/src/overlays/misc/ovl_map_mark_data/ovl_map_mark_data_reloc.o"
endseg
beginseg
name "ovl_En_Test"
compress
flags OVERLAY
include "$(BUILD_DIR)/src/overlays/actors/ovl_En_Test/z_en_test.o"
include "$(BUILD_DIR)/src/overlays/actors/ovl_En_Test/ovl_En_Test_reloc.o"
endseg
// Overlays for most actors and effects are reordered between versions. On N64 and iQue,

2
tools/.gitignore vendored
View File

@ -1,10 +1,10 @@
# Output files
*.exe
bin2c
elf2rom
makeromfs
mkdmadata
mkldscript
mkovlrules
preprocess_pragmas
reloc_prereq
vtxdis

View File

@ -1,5 +1,5 @@
CFLAGS := -Wall -Wextra -pedantic -std=gnu99 -g -O2
PROGRAMS := bin2c elf2rom makeromfs mkdmadata mkldscript preprocess_pragmas reloc_prereq vtxdis
PROGRAMS := bin2c makeromfs mkdmadata mkldscript mkovlrules preprocess_pragmas reloc_prereq vtxdis
UNAME_S := $(shell uname -s)
ifeq ($(UNAME_S),Linux)
@ -54,11 +54,11 @@ distclean: clean
.PHONY: all clean distclean
elf2rom_SOURCES := elf2rom.c elf32.c n64chksum.c util.c
bin2c_SOURCES := bin2c.c
makeromfs_SOURCES := makeromfs.c n64chksum.c util.c
mkdmadata_SOURCES := mkdmadata.c spec.c util.c
mkldscript_SOURCES := mkldscript.c spec.c util.c
mkovlrules_SOURCES := mkovlrules.c spec.c util.c
preprocess_pragmas_SOURCES := preprocess_pragmas.c
reloc_prereq_SOURCES := reloc_prereq.c spec.c util.c
vtxdis_SOURCES := vtxdis.c

View File

@ -1,257 +0,0 @@
#include <stdarg.h>
#include <stdbool.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "elf32.h"
#include "n64chksum.h"
#include "util.h"
#define ROM_SEG_START_SUFFIX ".rom_start"
#define ROM_SEG_END_SUFFIX ".rom_end"
struct RomSegment
{
const char *name;
const void *data;
int size;
int romStart;
int romEnd;
};
static struct RomSegment *g_romSegments = NULL;
static int g_romSegmentsCount = 0;
static int g_romSize;
static bool parse_number(const char *str, int *num)
{
char *endptr;
long int n = strtol(str, &endptr, 0);
*num = n;
return endptr > str;
}
static char *sprintf_alloc(const char *fmt, ...)
{
va_list args;
int size;
char *buffer;
va_start(args, fmt);
size = vsnprintf(NULL, 0, fmt, args) + 1;
va_end(args);
buffer = malloc(size);
va_start(args, fmt);
vsprintf(buffer, fmt, args);
va_end(args);
return buffer;
}
static struct RomSegment *add_rom_segment(const char *name)
{
int index = g_romSegmentsCount;
g_romSegmentsCount++;
g_romSegments = realloc(g_romSegments, g_romSegmentsCount * sizeof(*g_romSegments));
g_romSegments[index].name = name;
return &g_romSegments[index];
}
static int find_symbol_value(struct Elf32_Symbol *syms, int numsymbols, const char *name)
{
struct Elf32_Symbol *sym;
int lo, hi, mid, cmp;
// Binary search for the symbol. We maintain the invariant that [lo, hi) is
// the interval that remains to search.
lo = 0;
hi = numsymbols;
while (lo < hi)
{
mid = lo + (hi - lo) / 2;
sym = &syms[mid];
cmp = strcmp(sym->name, name);
if (cmp == 0)
return (int) sym->value;
else if (cmp < 0)
lo = mid + 1;
else
hi = mid;
}
util_fatal_error("Symbol %s is not defined\n", name);
}
static int find_rom_address(struct Elf32_Symbol *syms, int numsymbols, const char *name, const char *suffix)
{
char *symName = sprintf_alloc("_%sSegmentRom%s", name, suffix);
int ret = find_symbol_value(syms, numsymbols, symName);
free(symName);
return ret;
}
static int cmp_symbol_by_name(const void *a, const void *b)
{
return strcmp(
((struct Elf32_Symbol *)a)->name,
((struct Elf32_Symbol *)b)->name);
}
static void parse_input_file(const char *filename)
{
struct Elf32 elf;
struct Elf32_Symbol *syms;
const void *data;
size_t size;
int numRomSymbols;
int i;
data = util_read_whole_file(filename, &size);
if (!elf32_init(&elf, data, size) || elf.machine != ELF_MACHINE_MIPS)
util_fatal_error("%s is not a valid 32-bit MIPS ELF file", filename);
// sort all symbols that contain the substring "Rom" for fast access
// (sorting all symbols costs 0.1s, might as well avoid that)
syms = malloc(elf.numsymbols * sizeof(struct Elf32_Symbol));
numRomSymbols = 0;
for (i = 0; i < elf.numsymbols; i++)
{
if (!elf32_get_symbol(&elf, &syms[numRomSymbols], i))
util_fatal_error("invalid or corrupt ELF file");
if (strstr(syms[numRomSymbols].name, "Rom"))
numRomSymbols++;
}
qsort(syms, numRomSymbols, sizeof(struct Elf32_Symbol), cmp_symbol_by_name);
// get ROM segments
// sections of type SHT_PROGBITS and whose name is ..secname are considered ROM segments
for (i = 0; i < elf.shnum; i++)
{
struct Elf32_Section sec;
struct RomSegment *segment;
if (!elf32_get_section(&elf, &sec, i))
util_fatal_error("invalid or corrupt ELF file");
if (sec.type == SHT_PROGBITS && sec.name[0] == '.' && sec.name[1] == '.'
// HACK! ld sometimes marks NOLOAD sections as SHT_PROGBITS for no apparent reason,
// so we must ignore the ..secname.bss sections explicitly
&& strchr(sec.name + 2, '.') == NULL)
{
segment = add_rom_segment(sec.name + 2);
segment->data = elf.data + sec.offset;
segment->romStart = find_rom_address(syms, numRomSymbols, segment->name, "Start");
segment->romEnd = find_rom_address(syms, numRomSymbols, segment->name, "End");
}
}
g_romSize = find_symbol_value(syms, numRomSymbols, "_RomSize");
free(syms);
}
// Writes the N64 ROM
static void write_rom_file(const char *filename, int cicType)
{
uint8_t *buffer = calloc(g_romSize, 1);
int i;
uint32_t chksum[2];
// write segments
for (i = 0; i < g_romSegmentsCount; i++)
{
int size = g_romSegments[i].romEnd - g_romSegments[i].romStart;
memcpy(buffer + g_romSegments[i].romStart, g_romSegments[i].data, size);
}
// write checksum
if (!n64chksum_calculate(buffer, cicType, chksum))
util_fatal_error("invalid cic type %i", cicType);
util_write_uint32_be(buffer + 0x10, chksum[0]);
util_write_uint32_be(buffer + 0x14, chksum[1]);
util_write_whole_file(filename, buffer, g_romSize);
free(buffer);
}
static void usage(const char *execname)
{
printf("usage: %s -cic <number> input.elf output.z64\n", execname);
}
int main(int argc, char **argv)
{
int i;
const char *inputFileName = NULL;
const char *outputFileName = NULL;
int cicType = -1;
for (i = 1; i < argc; i++)
{
if (argv[i][0] == '-')
{
if (strcmp(argv[i], "-cic") == 0)
{
i++;
if (i >= argc || !parse_number(argv[i], &cicType))
{
fputs("error: expected number after -cic\n", stderr);
goto bad_args;
}
}
else if (strcmp(argv[i], "-help") == 0)
{
usage(argv[0]);
return 0;
}
else
{
fprintf(stderr, "unknown option %s\n", argv[i]);
goto bad_args;
}
}
else
{
if (inputFileName == NULL)
inputFileName = argv[i];
else if (outputFileName == NULL)
outputFileName = argv[i];
else
{
fputs("error: too many parameters specified\n", stderr);
goto bad_args;
}
}
}
if (inputFileName == NULL)
{
fputs("error: no input file specified\n", stderr);
goto bad_args;
}
if (outputFileName == NULL)
{
fputs("error: no output file specified\n", stderr);
goto bad_args;
}
if (cicType == -1)
{
fputs("error: no CIC type specified\n", stderr);
goto bad_args;
}
parse_input_file(inputFileName);
write_rom_file(outputFileName, cicType);
return 0;
bad_args:
usage(argv[0]);
return 1;
}

View File

@ -241,7 +241,7 @@ def compare_pointers(version: str) -> dict[Path, BssSection]:
) as p:
for mapfile_segment in source_code_segments:
for file in mapfile_segment:
if not str(file.filepath).endswith(".o"):
if not str(file.filepath).endswith((".o", ".plf")):
continue
if file.sectionType == ".bss":
continue
@ -282,39 +282,67 @@ def compare_pointers(version: str) -> dict[Path, BssSection]:
if not file.sectionType == ".bss":
continue
pointers_in_section = [
p
for p in pointers
if file.vram <= p.build_value < file.vram + file.size
]
object_file = file.filepath.relative_to(f"build/{version}")
# Hack to handle the combined z_message_z_game_over.o file.
# Fortunately z_game_over has no BSS so we can just analyze z_message instead.
if str(object_file) == "src/code/z_message_z_game_over.o":
object_file = Path("src/code/z_message.o")
c_file = object_file.with_suffix(".c")
# c_file = object_file.with_suffix(".c")
# For the baserom, assume that the lowest address is the start of the BSS section. This might
# not be true if the first BSS variable is not referenced so account for that specifically.
base_start_address = (
min(p.base_value for p in pointers_in_section)
if pointers_in_section
else 0
)
# Account for the fact that z_rumble starts with unreferenced bss
if str(c_file) == "src/code/z_rumble.c":
base_start_address -= 0x10
elif str(c_file) == "src/boot/z_locale.c":
base_start_address -= 0x18
if object_file.suffix == ".plf":
# For partially linked overlays, read the map file for the plf to get the
# object file corresponding to a single source file
plf_map = mapfile_parser.mapfile.MapFile()
plf_map.readMapFile(file.filepath.with_suffix(".map"))
for plf_seg in plf_map:
for plf_file in plf_seg:
if not plf_file.sectionType == ".bss":
continue
c_file = plf_file.filepath.relative_to(f"build/{version}").with_suffix(".c")
build_start_address = file.vram
pointers_in_section = [
p
for p in pointers
if file.vram + plf_file.vram <= p.build_value < file.vram + plf_file.vram + plf_file.size
]
bss_sections[c_file] = BssSection(
base_start_address, build_start_address, pointers_in_section
)
base_start_address = (
min(p.base_value for p in pointers_in_section)
if pointers_in_section
else 0
)
# Account for the fact that z_rumble starts with unreferenced bss
if str(c_file) == "src/code/z_rumble.c":
base_start_address -= 0x10
elif str(c_file) == "src/boot/z_locale.c":
base_start_address -= 0x18
bss_sections[c_file] = BssSection(base_start_address, file.vram + plf_file.vram, pointers_in_section)
else:
c_file = object_file.with_suffix(".c")
pointers_in_section = [
p
for p in pointers
if file.vram <= p.build_value < file.vram + file.size
]
base_start_address = (
min(p.base_value for p in pointers_in_section)
if pointers_in_section
else 0
)
# Account for the fact that z_rumble starts with unreferenced bss
if str(c_file) == "src/code/z_rumble.c":
base_start_address -= 0x10
elif str(c_file) == "src/boot/z_locale.c":
base_start_address -= 0x18
bss_sections[c_file] = BssSection(base_start_address, file.vram, pointers_in_section)
return bss_sections

View File

@ -9,229 +9,235 @@
#include "spec.h"
#include "util.h"
// Note: *SECTION ALIGNMENT* Object files built with a compiler such as GCC can, by default, use narrower
// alignment for sections size, compared to IDO padding sections to a 0x10-aligned size.
// To properly generate relocations relative to section starts, sections currently need to be aligned
// explicitly (to 0x10 currently, a narrower alignment might work), otherwise the linker does implicit alignment
// and inserts padding between the address indicated by section start symbols (such as *SegmentRoDataStart) and
// the actual aligned start of the section.
// With IDO, the padding of sections to an aligned size makes the section start at aligned addresses out of the box,
// so the explicit alignment has no further effect.
struct Segment *g_segments;
int g_segmentsCount;
static void write_ld_script(FILE *fout)
static void write_includes(const struct Segment *seg, FILE *fout, const char *segments_dir, const char *section,
bool linker_pad)
{
// Note sections contain a suffix wildcard as compilers other than IDO such as GCC may emit sections titled
// e.g. .rodata.cstN, .rodata.strN.M, .text.FUNCNAME depending on their settings.
if (seg->flags & FLAG_OVL) {
// For overlays they are already partially linked.
fprintf(fout, " %s/%s.plf (%s*)\n", segments_dir, seg->name, section);
} else {
// For non-overlays, list each include separately.
int i;
for (i = 0; i < seg->includesCount; i++) {
fprintf(fout, " %s (%s*)\n", seg->includes[i].fpath, section);
if (linker_pad && seg->includes[i].linkerPadding != 0)
fprintf(fout, " . += 0x%X;\n", seg->includes[i].linkerPadding);
}
}
}
static void write_ld_script(FILE *fout, const char *segments_dir)
{
int i;
int j;
fputs("OUTPUT_ARCH (mips)\n\n"
"SECTIONS {\n"
" _RomSize = 0;\n"
" _RomStart = _RomSize;\n\n",
fputs("OUTPUT_ARCH (mips)\n"
"\n"
"SECTIONS\n"
"{\n"
" _RomPos = 0;\n"
"\n",
fout);
for (i = 0; i < g_segmentsCount; i++)
{
for (i = 0; i < g_segmentsCount; i++) {
const struct Segment *seg = &g_segments[i];
fprintf(fout, " /* %s */\n\n", seg->name);
// align start of ROM segment
if (seg->fields & (1 << STMT_romalign))
fprintf(fout, " _RomSize = (_RomSize + %i) & ~ %i;\n", seg->romalign - 1, seg->romalign - 1);
fprintf(fout, " _RomPos = ALIGN(_RomPos, %i);\n", seg->romalign);
// initialized data (.text, .data, .rodata, .sdata)
fprintf(fout, " _%sSegmentRomStartTemp = _RomSize;\n"
" _%sSegmentRomStart = _%sSegmentRomStartTemp;\n"
" ..%s ", seg->name, seg->name, seg->name, seg->name);
// Begin initialized data (.text, .data, .rodata)
fprintf(fout, " _%sSegmentRomStartTemp = _RomPos;\n"
" _%sSegmentRomStart = _%sSegmentRomStartTemp;\n"
" ..%s ", seg->name, seg->name, seg->name, seg->name);
if (seg->fields & (1 << STMT_after))
fprintf(fout, "(_%sSegmentEnd + %i) & ~ %i ", seg->after, seg->align - 1, seg->align - 1);
// Continue after the requested segment, aligning to the required alignment for the new segment.
fprintf(fout, "ALIGN(_%sSegmentEnd, %i)", seg->after, seg->align);
else if (seg->fields & (1 << STMT_number))
fprintf(fout, "0x%02X000000 ", seg->number);
// Start at a new segmented address.
fprintf(fout, "0x%02X000000", seg->number);
else if (seg->fields & (1 << STMT_address))
fprintf(fout, "0x%08X ", seg->address);
// Start at a new absolute address.
fprintf(fout, "0x%08X", seg->address);
else
fprintf(fout, "ALIGN(0x%X) ", seg->align);
// Continue after previous segment, aligning to the required alignment for the new segment.
fprintf(fout, "ALIGN(0x%X)", seg->align);
// (AT(_RomSize) isn't necessary, but adds useful "load address" lines to the map file)
fprintf(fout, ": AT(_RomSize)\n {\n"
" _%sSegmentStart = .;\n"
" . = ALIGN(0x10);\n"
" _%sSegmentTextStart = .;\n",
seg->name, seg->name);
// AT(_RomPos) isn't necessary, but adds useful "load address" lines to the map file.
// Also force an alignment of at least 0x10 at the start of any segment. This is especially important for
// overlays as the final link step must not introduce alignment padding between the SegmentTextStart symbol
// and the section contents as this would cause all generated relocations done prior to be wrong.
fprintf(fout, " : AT(_RomPos)\n"
" {\n"
" . = ALIGN(0x10);\n"
" _%sSegmentStart = .;\n"
"\n",
seg->name);
for (j = 0; j < seg->includesCount; j++)
{
fprintf(fout, " %s (.text)\n", seg->includes[j].fpath);
if (seg->includes[j].linkerPadding != 0)
fprintf(fout, " . += 0x%X;\n", seg->includes[j].linkerPadding);
fprintf(fout, " . = ALIGN(0x10);\n");
}
fprintf(fout, " _%sSegmentTextEnd = .;\n", seg->name);
fprintf(fout, " _%sSegmentTextSize = ABSOLUTE( _%sSegmentTextEnd - _%sSegmentTextStart );\n", seg->name, seg->name, seg->name);
// Write .text
fprintf(fout, " _%sSegmentTextStart = .;\n", seg->name);
write_includes(seg, fout, segments_dir, ".text", true);
fprintf(fout, " . = ALIGN(0x10);\n"
" _%sSegmentTextEnd = .;\n"
" _%sSegmentTextSize = ABSOLUTE( _%sSegmentTextEnd - _%sSegmentTextStart );\n"
"\n", seg->name, seg->name, seg->name, seg->name);
// Write .data
fprintf(fout, " _%sSegmentDataStart = .;\n", seg->name);
write_includes(seg, fout, segments_dir, ".data", false);
fprintf(fout, " . = ALIGN(0x10);\n"
" _%sSegmentDataEnd = .;\n"
" _%sSegmentDataSize = ABSOLUTE( _%sSegmentDataEnd - _%sSegmentDataStart );\n"
"\n", seg->name, seg->name, seg->name, seg->name);
for (j = 0; j < seg->includesCount; j++)
{
fprintf(fout, " %s (.data)\n"
" . = ALIGN(0x10);\n", seg->includes[j].fpath);
}
fprintf(fout, " _%sSegmentDataEnd = .;\n", seg->name);
fprintf(fout, " _%sSegmentDataSize = ABSOLUTE( _%sSegmentDataEnd - _%sSegmentDataStart );\n", seg->name, seg->name, seg->name);
// Write .rodata
fprintf(fout, " _%sSegmentRoDataStart = .;\n", seg->name);
write_includes(seg, fout, segments_dir, ".rodata", false);
fprintf(fout, " . = ALIGN(0x10);\n"
" _%sSegmentRoDataEnd = .;\n"
" _%sSegmentRoDataSize = ABSOLUTE( _%sSegmentRoDataEnd - _%sSegmentRoDataStart );\n"
"\n", seg->name, seg->name, seg->name, seg->name);
for (j = 0; j < seg->includesCount; j++)
{
// Compilers other than IDO, such as GCC, produce different sections such as
// the ones named directly below. These sections do not contain values that
// need relocating, but we need to ensure that the base .rodata section
// always comes first. The reason this is important is due to relocs assuming
// the base of .rodata being the offset for the relocs and thus needs to remain
// the beginning of the entire rodata area in order to remain consistent.
// Inconsistencies will lead to various .rodata reloc crashes as a result of
// either missing relocs or wrong relocs.
fprintf(fout, " %s (.rodata)\n"
" %s (.rodata.str*)\n"
" %s (.rodata.cst*)\n"
" . = ALIGN(0x10);\n",
seg->includes[j].fpath, seg->includes[j].fpath, seg->includes[j].fpath);
}
fprintf(fout, " _%sSegmentRoDataEnd = .;\n", seg->name);
fprintf(fout, " _%sSegmentRoDataSize = ABSOLUTE( _%sSegmentRoDataEnd - _%sSegmentRoDataStart );\n", seg->name, seg->name, seg->name);
fprintf(fout, " _%sSegmentSDataStart = .;\n", seg->name);
for (j = 0; j < seg->includesCount; j++)
fprintf(fout, " %s (.sdata)\n"
" . = ALIGN(0x10);\n", seg->includes[j].fpath);
fprintf(fout, " _%sSegmentSDataEnd = .;\n", seg->name);
fprintf(fout, " _%sSegmentOvlStart = .;\n", seg->name);
for (j = 0; j < seg->includesCount; j++)
fprintf(fout, " %s (.ovl)\n", seg->includes[j].fpath);
fprintf(fout, " _%sSegmentOvlEnd = .;\n", seg->name);
// Write an address increment if requested
if (seg->fields & (1 << STMT_increment))
fprintf(fout, " . += 0x%08X;\n", seg->increment);
fputs(" }\n", fout);
if (seg->flags & FLAG_OVL) {
// Write .ovl if the segment is an overlay.
fprintf(fout, " _%sSegmentOvlStart = .;\n"
" %s/%s.reloc.o (.ovl)\n"
" _%sSegmentOvlEnd = .;\n"
" _%sSegmentOvlSize = ABSOLUTE( _%sSegmentOvlEnd - _%sSegmentOvlStart );\n"
"\n", seg->name, segments_dir, seg->name, seg->name, seg->name, seg->name, seg->name);
}
fprintf(fout, " _RomSize += ( _%sSegmentOvlEnd - _%sSegmentTextStart );\n", seg->name, seg->name);
const char *last_loadable = (seg->flags & FLAG_OVL) ? "Ovl" : "RoData";
fprintf(fout, " _%sSegmentRomEndTemp = _RomSize;\n"
"_%sSegmentRomEnd = _%sSegmentRomEndTemp;\n\n",
seg->name, seg->name, seg->name);
// End initialized data.
fprintf(fout, " }\n"
" _RomPos += ( _%sSegment%sEnd - _%sSegmentTextStart );\n"
" _%sSegmentRomEndTemp = _RomPos;\n"
" _%sSegmentRomEnd = _%sSegmentRomEndTemp;\n"
" _%sSegmentRomSize = ABSOLUTE( _%sSegmentRomEnd - _%sSegmentRomStart );\n"
"\n",
seg->name, last_loadable, seg->name, seg->name, seg->name, seg->name,
seg->name, seg->name, seg->name);
// align end of ROM segment
// Align end of ROM segment
if (seg->fields & (1 << STMT_romalign))
fprintf(fout, " _RomSize = (_RomSize + %i) & ~ %i;\n", seg->romalign - 1, seg->romalign - 1);
fprintf(fout, " _RomPos = ALIGN(_RomPos, %i);\n", seg->romalign);
// uninitialized data (.sbss, .scommon, .bss, COMMON)
fprintf(fout, " ..%s.bss ADDR(..%s) + SIZEOF(..%s) (NOLOAD) :\n"
/*" ..%s.bss :\n"*/
// Begin uninitialized data (.bss, COMMON)
// Note we must enforce a minimum alignment of at least 8 for
// bss sections due to how bss is cleared in steps of 8 in
// entry.s, and more widely it's more efficient.
fprintf(fout, " ..%s.bss (NOLOAD) :\n"
" {\n"
" . = ALIGN(0x10);\n"
" . = ALIGN(8);\n"
" _%sSegmentBssStart = .;\n",
seg->name, seg->name, seg->name, seg->name);
seg->name, seg->name);
for (j = 0; j < seg->includesCount; j++)
fprintf(fout, " %s (.sbss)\n"
" . = ALIGN(0x10);\n", seg->includes[j].fpath);
// Write .bss and COMMON
write_includes(seg, fout, segments_dir, ".bss", false);
write_includes(seg, fout, segments_dir, "COMMON", false);
for (j = 0; j < seg->includesCount; j++)
fprintf(fout, " %s (.scommon)\n"
" . = ALIGN(0x10);\n", seg->includes[j].fpath);
for (j = 0; j < seg->includesCount; j++)
fprintf(fout, " %s (.bss)\n"
" . = ALIGN(0x10);\n", seg->includes[j].fpath);
for (j = 0; j < seg->includesCount; j++)
fprintf(fout, " %s (COMMON)\n"
" . = ALIGN(0x10);\n", seg->includes[j].fpath);
fprintf(fout, " . = ALIGN(0x10);\n"
// End uninitialized data
fprintf(fout, " . = ALIGN(8);\n"
" _%sSegmentBssEnd = .;\n"
" _%sSegmentBssSize = ABSOLUTE( _%sSegmentBssEnd - _%sSegmentBssStart );\n"
"\n"
" _%sSegmentEnd = .;\n"
" }\n"
" _%sSegmentBssSize = ABSOLUTE( _%sSegmentBssEnd - _%sSegmentBssStart );\n\n",
"\n",
seg->name, seg->name, seg->name, seg->name, seg->name);
}
fputs(" _RomEnd = _RomSize;\n\n", fout);
fputs(" _RomSize = _RomPos;\n\n", fout);
// Debugging sections
fputs(
// mdebug sections
" .pdr : { *(.pdr) }" "\n"
" .mdebug : { *(.mdebug) }" "\n"
" .mdebug.abi32 : { *(.mdebug.abi32) }" "\n"
" .pdr : { *(.pdr) }" "\n"
" .mdebug : { *(.mdebug) }" "\n"
// Stabs debugging sections
" .stab 0 : { *(.stab) }" "\n"
" .stabstr 0 : { *(.stabstr) }" "\n"
" .stab.excl 0 : { *(.stab.excl) }" "\n"
" .stab.exclstr 0 : { *(.stab.exclstr) }" "\n"
" .stab.index 0 : { *(.stab.index) }" "\n"
" .stab.indexstr 0 : { *(.stab.indexstr) }" "\n"
" .comment 0 : { *(.comment) }" "\n"
" .gnu.build.attributes : { *(.gnu.build.attributes .gnu.build.attributes.*) }" "\n"
// DWARF debug sections
// Symbols in the DWARF debugging sections are relative to the beginning of the section so we begin them at 0.
// DWARF 1
" .debug 0 : { *(.debug) }" "\n"
" .line 0 : { *(.line) }" "\n"
" .debug 0 : { *(.debug) }" "\n"
" .line 0 : { *(.line) }" "\n"
// GNU DWARF 1 extensions
" .debug_srcinfo 0 : { *(.debug_srcinfo) }" "\n"
" .debug_sfnames 0 : { *(.debug_sfnames) }" "\n"
" .debug_srcinfo 0 : { *(.debug_srcinfo) }" "\n"
" .debug_sfnames 0 : { *(.debug_sfnames) }" "\n"
// DWARF 1.1 and DWARF 2
" .debug_aranges 0 : { *(.debug_aranges) }" "\n"
" .debug_pubnames 0 : { *(.debug_pubnames) }" "\n"
" .debug_aranges 0 : { *(.debug_aranges) }" "\n"
" .debug_pubnames 0 : { *(.debug_pubnames) }" "\n"
// DWARF 2
" .debug_info 0 : { *(.debug_info .gnu.linkonce.wi.*) }" "\n"
" .debug_abbrev 0 : { *(.debug_abbrev) }" "\n"
" .debug_line 0 : { *(.debug_line .debug_line.* .debug_line_end ) }" "\n"
" .debug_frame 0 : { *(.debug_frame) }" "\n"
" .debug_str 0 : { *(.debug_str) }" "\n"
" .debug_loc 0 : { *(.debug_loc) }" "\n"
" .debug_macinfo 0 : { *(.debug_macinfo) }" "\n"
" .debug_info 0 : { *(.debug_info .gnu.linkonce.wi.*) }" "\n"
" .debug_abbrev 0 : { *(.debug_abbrev) }" "\n"
" .debug_line 0 : { *(.debug_line .debug_line.* .debug_line_end ) }" "\n"
" .debug_frame 0 : { *(.debug_frame) }" "\n"
" .debug_str 0 : { *(.debug_str) }" "\n"
" .debug_loc 0 : { *(.debug_loc) }" "\n"
" .debug_macinfo 0 : { *(.debug_macinfo) }" "\n"
// SGI/MIPS DWARF 2 extensions
" .debug_weaknames 0 : { *(.debug_weaknames) }" "\n"
" .debug_funcnames 0 : { *(.debug_funcnames) }" "\n"
" .debug_typenames 0 : { *(.debug_typenames) }" "\n"
" .debug_varnames 0 : { *(.debug_varnames) }" "\n"
" .debug_weaknames 0 : { *(.debug_weaknames) }" "\n"
" .debug_funcnames 0 : { *(.debug_funcnames) }" "\n"
" .debug_typenames 0 : { *(.debug_typenames) }" "\n"
" .debug_varnames 0 : { *(.debug_varnames) }" "\n"
// DWARF 3
" .debug_pubtypes 0 : { *(.debug_pubtypes) }" "\n"
" .debug_ranges 0 : { *(.debug_ranges) }" "\n"
" .debug_pubtypes 0 : { *(.debug_pubtypes) }" "\n"
" .debug_ranges 0 : { *(.debug_ranges) }" "\n"
// DWARF 5
" .debug_addr 0 : { *(.debug_addr) }" "\n"
" .debug_line_str 0 : { *(.debug_line_str) }" "\n"
" .debug_loclists 0 : { *(.debug_loclists) }" "\n"
" .debug_macro 0 : { *(.debug_macro) }" "\n"
" .debug_names 0 : { *(.debug_names) }" "\n"
" .debug_rnglists 0 : { *(.debug_rnglists) }" "\n"
" .debug_str_offsets 0 : { *(.debug_str_offsets) }" "\n"
" .debug_sup 0 : { *(.debug_sup) }\n"
" .debug_addr 0 : { *(.debug_addr) }" "\n"
" .debug_line_str 0 : { *(.debug_line_str) }" "\n"
" .debug_loclists 0 : { *(.debug_loclists) }" "\n"
" .debug_macro 0 : { *(.debug_macro) }" "\n"
" .debug_names 0 : { *(.debug_names) }" "\n"
" .debug_rnglists 0 : { *(.debug_rnglists) }" "\n"
" .debug_str_offsets 0 : { *(.debug_str_offsets) }" "\n"
" .debug_sup 0 : { *(.debug_sup) }" "\n"
// gnu attributes
" .gnu.attributes 0 : { KEEP (*(.gnu.attributes)) }" "\n", fout);
" .gnu.attributes 0 : { KEEP (*(.gnu.attributes)) }" "\n"
// Sections generated by GCC to inform GDB about the ABI
" .mdebug.abi32 0 : { KEEP (*(.mdebug.abi32)) }" "\n"
" .mdebug.abiN32 0 : { KEEP (*(.mdebug.abiN32)) }" "\n"
" .mdebug.abi64 0 : { KEEP (*(.mdebug.abi64)) }" "\n"
" .mdebug.abiO64 0 : { KEEP (*(.mdebug.abiO64)) }" "\n"
" .mdebug.eabi32 0 : { KEEP (*(.mdebug.eabi32)) }" "\n"
" .mdebug.eabi64 0 : { KEEP (*(.mdebug.eabi64)) }" "\n"
" .gcc_compiled_long32 0 : { KEEP (*(.gcc_compiled_long32)) }" "\n"
" .gcc_compiled_long64 0 : { KEEP (*(.gcc_compiled_long64)) }" "\n\n", fout);
// Discard all other sections not mentioned above
fputs(" /DISCARD/ :" "\n"
" {" "\n"
" *(*);" "\n"
" }" "\n", fout);
fputs("}\n", fout);
fputs(" /DISCARD/ :" "\n"
" {" "\n"
" *(*);" "\n"
" }" "\n"
"}\n", fout);
}
static void usage(const char *execname)
{
fprintf(stderr, "Nintendo 64 linker script generation tool v0.03\n"
"usage: %s SPEC_FILE LD_SCRIPT\n"
"SPEC_FILE file describing the organization of object files into segments\n"
"LD_SCRIPT filename of output linker script\n",
fprintf(stderr, "Nintendo 64 linker script generation tool v0.04\n"
"usage: %s SPEC_FILE LD_SCRIPT SEGMENTS_DIR\n"
"SPEC_FILE file describing the organization of object files into segments\n"
"LD_SCRIPT filename of output linker script\n"
"SEGMENTS_DIR dir name containing partially linked overlay segments\n",
execname);
}
@ -241,10 +247,9 @@ int main(int argc, char **argv)
void *spec;
size_t size;
if (argc != 3)
{
if (argc != 4) {
usage(argv[0]);
return 1;
return EXIT_FAILURE;
}
spec = util_read_whole_file(argv[1], &size);
@ -253,11 +258,10 @@ int main(int argc, char **argv)
ldout = fopen(argv[2], "w");
if (ldout == NULL)
util_fatal_error("failed to open file '%s' for writing", argv[2]);
write_ld_script(ldout);
write_ld_script(ldout, argv[3]);
fclose(ldout);
free_rom_spec(g_segments, g_segmentsCount);
free(spec);
return 0;
return EXIT_SUCCESS;
}

73
tools/mkovlrules.c Normal file
View File

@ -0,0 +1,73 @@
#include <stddef.h>
#include <stdio.h>
#include <stdlib.h>
#include "spec.h"
#include "util.h"
struct Segment* g_segments;
int g_segmentsCount;
static void write_overlay_rules(FILE *fout, const char *ovls_dir)
{
int i, j;
for (i = 0; i < g_segmentsCount; i++) {
if (!(g_segments[i].flags & FLAG_OVL))
continue;
/* Write rule for partial linkage of this segment */
fprintf(fout, "%s/%s.plf:", ovls_dir, g_segments[i].name);
for (j = 0; j < g_segments[i].includesCount; j++)
fprintf(fout, " \\\n\t\t%s", g_segments[i].includes[j].fpath);
fprintf(fout, "\n"
"\t$(LD) $(OVLDFLAGS) $^ -o $@\n"
"\n");
}
/* List every expected plf in a variable */
fprintf(fout, "OVL_SEGMENT_FILES :=");
for (i = 0; i < g_segmentsCount; i++) {
if (!(g_segments[i].flags & FLAG_OVL))
continue;
fprintf(fout, " \\\n\t\t%s/%s.plf", ovls_dir, g_segments[i].name);
}
fprintf(fout, "\n");
}
static void usage(const char *execname)
{
fprintf(stderr, "zelda64 overlay rules generator v0.01\n"
"usage: %s SPEC_FILE OBJ_DIRECTORY MAKEFILE_OUT\n"
"SPEC_FILE file describing the organization of object files into segments\n"
"OBJ_DIRECTORY directory where object files will be stored\n"
"MAKEFILE_OUT filename of output makefile to write linking rules\n",
execname);
}
int main(int argc, char **argv)
{
FILE *makefile;
void *spec;
size_t size;
if (argc != 4) {
usage(argv[0]);
return 1;
}
spec = util_read_whole_file(argv[1], &size);
parse_rom_spec(spec, &g_segments, &g_segmentsCount);
makefile = fopen(argv[3], "w");
if (makefile == NULL)
util_fatal_error("failed to open file '%s' for writing", argv[2]);
write_overlay_rules(makefile, argv[2]);
fclose(makefile);
free_rom_spec(g_segments, g_segmentsCount);
free(spec);
return 0;
}

View File

@ -87,6 +87,8 @@ static bool parse_flags(char *str, unsigned int *flags)
f |= FLAG_NOLOAD;
else if (strcmp(str, "SYMS") == 0)
f |= FLAG_SYMS;
else if (strcmp(str, "OVERLAY") == 0)
f |= FLAG_OVL;
else
return false;

View File

@ -27,7 +27,8 @@ enum {
FLAG_OBJECT = (1 << 1),
FLAG_RAW = (1 << 2),
FLAG_NOLOAD = (1 << 3),
FLAG_SYMS = (1 << 4)
FLAG_SYMS = (1 << 4),
FLAG_OVL = (1 << 5)
};
struct Include {