Files
oot/tools/mkspecrules.c
Tharo 2c481eaeeb Partial linking of spec segments (#2661)
* Partial linking of overlay segments, relax linker script alignment

* Partial linking of all spec segments

* Fix update, remove _RomPos from linker script

* iQue version working pending COM-plugin update

* Add plf map file resolution to sym_info.py, local symbol merging is broken

* git subrepo pull tools/com-plugin

subrepo:
  subdir:   "tools/com-plugin"
  merged:   "c4f3ba845"
upstream:
  origin:   "git@github.com:Thar0/com-plugin.git"
  branch:   "main"
  commit:   "c4f3ba845"
git-subrepo:
  version:  "0.4.6"
  origin:   "https://github.com/ingydotnet/git-subrepo"
  commit:   "110b9eb"

* Make tools compatible with partial linking

Co-authored-by: Anghelo Carvajal <angheloalf95@gmail.com>

* Remove unused files

* Fix some makefile bits

* mkspecrules cleanup

* Comment on the makerom linker layout in mkldscript

* Revert linker padding strategy back to pad_text spec directives

* Comment on objcopy elf -> rom step

* Adjust tool descriptions

* Fix compressed builds

* rm reloc_prereq, no longer used

* Makefile merge fix

Co-authored-by: Dragorn421 <Dragorn421@users.noreply.github.com>

---------

Co-authored-by: Anghelo Carvajal <angheloalf95@gmail.com>
Co-authored-by: Dragorn421 <Dragorn421@users.noreply.github.com>
2026-05-06 23:33:03 +02:00

184 lines
5.7 KiB
C

#include <stddef.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "spec.h"
#include "util.h"
struct Segment* g_segments;
int g_segmentsCount;
static void write_progbits_section(FILE *fout, struct Segment *seg, const char *section_name, bool with_padding)
{
fprintf(fout, " .%s :\n {\n", section_name);
for (int i = 0; i < seg->includesCount; i++) {
fprintf(fout, " %s(.%s*)\n", seg->includes[i].fpath, section_name);
if (with_padding && seg->includes[i].linkerPadding != 0)
fprintf(fout, " . += 0x%X;\n", seg->includes[i].linkerPadding);
}
fprintf(fout, " }\n");
}
static void write_noload_section(FILE *fout, struct Segment *seg, const char *section_name)
{
fprintf(fout, " .%s (NOLOAD) :\n {\n", section_name);
for (int i = 0; i < seg->includesCount; i++)
fprintf(fout, " %s(.%s*)\n", seg->includes[i].fpath, section_name);
fprintf(fout, " }\n");
}
/**
* Write a linker script for partial linking of a single segment.
*
* The original plan was to use a single linker script and pass the files over the command line to ld,
* however in the iQue version we have bss files that are created partway through linking that need
* special handling in the linker script.
*/
static void write_linker_script(FILE *fout, struct Segment *seg)
{
fprintf(fout,
"OUTPUT_ARCH (mips)\n"
"SECTIONS {\n"
);
write_progbits_section(fout, seg, "text", true);
write_progbits_section(fout, seg, "data", false);
write_progbits_section(fout, seg, "rodata", false);
write_noload_section(fout, seg, "bss");
/* 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.
*/
fprintf(fout,
" /DISCARD/ :\n"
" {\n"
" *(.gptab.*)\n"
" }\n"
"}\n"
);
}
static void write_overlay_rules(FILE *fout, const char *ovls_dir)
{
for (int i = 0; i < g_segmentsCount; i++) {
Segment *seg = &g_segments[i];
/* Write rule for partial linkage of this segment */
fprintf(fout, "%s/%s.plf:", ovls_dir, seg->name);
for (int j = 0; j < seg->includesCount; j++) {
if (seg->includes[j].fpath[0] != '*') /* Skip iQue bss files */
fprintf(fout, " \\\n\t\t%s", seg->includes[j].fpath);
}
fprintf(fout, " \\\n\t\t$(SEGMENTS_DIR)/Makefile");
fprintf(fout, "\n"
"\t@echo Linking \"%s\"\n"
"\t$(SEG_VERBOSE)$(LD) $(SEG_LDFLAGS) -o $@\n"
"\n", seg->name);
}
/* List every expected plf in a variable */
fprintf(fout, "SEGMENT_FILES :=");
for (int i = 0; i < g_segmentsCount; i++)
fprintf(fout, " \\\n\t\t%s/%s.plf", ovls_dir, g_segments[i].name);
/* List overlay plfs in a variable */
fprintf(fout, "\n\nOVL_SEGMENT_FILES :=");
for (int 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, "spec segment makefile 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;
}
const char *spec_path = argv[1];
const char *seg_dir = argv[2];
const char *makefile_path = argv[3];
if (seg_dir[0] == '\0') {
usage(argv[0]);
return 2;
}
spec = util_read_whole_file(spec_path, &size);
parse_rom_spec(spec, &g_segments, &g_segmentsCount);
makefile = fopen(makefile_path, "w");
if (makefile == NULL)
util_fatal_error("failed to open file '%s' for writing", makefile_path);
write_overlay_rules(makefile, seg_dir);
fclose(makefile);
// Find the longest segment name
size_t max_name = 1;
for (int i = 0; i < g_segmentsCount; i++) {
size_t namelen = strlen(g_segments[i].name);
if (namelen > max_name)
max_name = namelen;
}
size_t seg_dir_len = strlen(seg_dir);
// Check if segments dir path needs a path separator appended
bool needs_sep = seg_dir[seg_dir_len - 1] != '/';
// Allocate a buffer large enough for the longest path
char *ld_outpath = malloc(seg_dir_len + needs_sep + max_name + strlen(".ld") + 1);
// Write segments dir path now since it's always the same
strcpy(ld_outpath, seg_dir);
if (needs_sep) {
ld_outpath[seg_dir_len] = '/';
seg_dir_len++;
}
for (int i = 0; i < g_segmentsCount; i++) {
Segment *seg = &g_segments[i];
// Form the full path for this segment's linker script
strcpy(ld_outpath + seg_dir_len, seg->name);
ld_outpath[seg_dir_len + strlen(seg->name) + 0] = '.';
ld_outpath[seg_dir_len + strlen(seg->name) + 1] = 'l';
ld_outpath[seg_dir_len + strlen(seg->name) + 2] = 'd';
ld_outpath[seg_dir_len + strlen(seg->name) + 3] = '\0';
FILE *ldf = fopen(ld_outpath, "w");
write_linker_script(ldf, seg);
fclose(ldf);
}
free(ld_outpath);
free_rom_spec(g_segments, g_segmentsCount);
free(spec);
return 0;
}