1 /*
2 * This file is part of RGBDS.
3 *
4 * Copyright (c) 1997-2019, Carsten Sorensen and RGBDS contributors.
5 *
6 * SPDX-License-Identifier: MIT
7 */
8
9 #include <assert.h>
10 #include <inttypes.h>
11 #include <limits.h>
12 #include <stdbool.h>
13 #include <stdarg.h>
14 #include <stdio.h>
15 #include <stdint.h>
16 #include <stdlib.h>
17 #include <string.h>
18 #include <sys/stat.h>
19 #include <sys/types.h>
20
21 #include "link/object.h"
22 #include "link/symbol.h"
23 #include "link/section.h"
24 #include "link/assign.h"
25 #include "link/patch.h"
26 #include "link/output.h"
27
28 #include "extern/getopt.h"
29
30 #include "error.h"
31 #include "platform.h"
32 #include "version.h"
33
34 bool isDmgMode; /* -d */
35 char *linkerScriptName; /* -l */
36 char const *mapFileName; /* -m */
37 char const *symFileName; /* -n */
38 char const *overlayFileName; /* -O */
39 char const *outputFileName; /* -o */
40 uint8_t padValue; /* -p */
41 // Setting these three to 0 disables the functionality
42 uint16_t scrambleROMX = 0; /* -S */
43 uint8_t scrambleWRAMX = 0;
44 uint8_t scrambleSRAM = 0;
45 bool is32kMode; /* -t */
46 bool beVerbose; /* -v */
47 bool isWRA0Mode; /* -w */
48 bool disablePadding; /* -x */
49
50 static uint32_t nbErrors = 0;
51
52 /***** Helper function to dump a file stack to stderr *****/
53
dumpFileStack(struct FileStackNode const * node)54 char const *dumpFileStack(struct FileStackNode const *node)
55 {
56 char const *lastName;
57
58 if (node->parent) {
59 lastName = dumpFileStack(node->parent);
60 /* REPT nodes use their parent's name */
61 if (node->type != NODE_REPT)
62 lastName = node->name;
63 fprintf(stderr, "(%" PRIu32 ") -> %s", node->lineNo, lastName);
64 if (node->type == NODE_REPT) {
65 for (uint32_t i = 0; i < node->reptDepth; i++)
66 fprintf(stderr, "::REPT~%" PRIu32, node->iters[i]);
67 }
68 } else {
69 assert(node->type != NODE_REPT);
70 lastName = node->name;
71 fputs(lastName, stderr);
72 }
73
74 return lastName;
75 }
76
warning(struct FileStackNode const * where,uint32_t lineNo,char const * fmt,...)77 void warning(struct FileStackNode const *where, uint32_t lineNo, char const *fmt, ...)
78 {
79 va_list ap;
80
81 fputs("warning: ", stderr);
82 if (where) {
83 dumpFileStack(where);
84 fprintf(stderr, "(%" PRIu32 "): ", lineNo);
85 }
86 va_start(ap, fmt);
87 vfprintf(stderr, fmt, ap);
88 va_end(ap);
89 putc('\n', stderr);
90 }
91
error(struct FileStackNode const * where,uint32_t lineNo,char const * fmt,...)92 void error(struct FileStackNode const *where, uint32_t lineNo, char const *fmt, ...)
93 {
94 va_list ap;
95
96 fputs("error: ", stderr);
97 if (where) {
98 dumpFileStack(where);
99 fprintf(stderr, "(%" PRIu32 "): ", lineNo);
100 }
101 va_start(ap, fmt);
102 vfprintf(stderr, fmt, ap);
103 va_end(ap);
104 putc('\n', stderr);
105
106 if (nbErrors != UINT32_MAX)
107 nbErrors++;
108 }
109
argErr(char flag,char const * fmt,...)110 void argErr(char flag, char const *fmt, ...)
111 {
112 va_list ap;
113
114 fprintf(stderr, "error: Invalid argument for option '%c': ", flag);
115 va_start(ap, fmt);
116 vfprintf(stderr, fmt, ap);
117 va_end(ap);
118 putc('\n', stderr);
119
120 if (nbErrors != UINT32_MAX)
121 nbErrors++;
122 }
123
fatal(struct FileStackNode const * where,uint32_t lineNo,char const * fmt,...)124 _Noreturn void fatal(struct FileStackNode const *where, uint32_t lineNo, char const *fmt, ...)
125 {
126 va_list ap;
127
128 fputs("FATAL: ", stderr);
129 if (where) {
130 dumpFileStack(where);
131 fprintf(stderr, "(%" PRIu32 "): ", lineNo);
132 }
133 va_start(ap, fmt);
134 vfprintf(stderr, fmt, ap);
135 va_end(ap);
136 putc('\n', stderr);
137
138 if (nbErrors != UINT32_MAX)
139 nbErrors++;
140
141 fprintf(stderr, "Linking aborted after %" PRIu32 " error%s\n", nbErrors,
142 nbErrors == 1 ? "" : "s");
143 exit(1);
144 }
145
openFile(char const * fileName,char const * mode)146 FILE *openFile(char const *fileName, char const *mode)
147 {
148 if (!fileName)
149 return NULL;
150
151 FILE *file;
152 if (strcmp(fileName, "-") != 0)
153 file = fopen(fileName, mode);
154 else if (mode[0] == 'r')
155 file = fdopen(0, mode);
156 else
157 file = fdopen(1, mode);
158
159 if (!file)
160 err("Could not open file \"%s\"", fileName);
161
162 return file;
163 }
164
165 /* Short options */
166 static const char *optstring = "dl:m:n:O:o:p:S:s:tVvWwx";
167
168 /*
169 * Equivalent long options
170 * Please keep in the same order as short opts
171 *
172 * Also, make sure long opts don't create ambiguity:
173 * A long opt's name should start with the same letter as its short opt,
174 * except if it doesn't create any ambiguity (`verbose` versus `version`).
175 * This is because long opt matching, even to a single char, is prioritized
176 * over short opt matching
177 */
178 static struct option const longopts[] = {
179 { "dmg", no_argument, NULL, 'd' },
180 { "linkerscript", required_argument, NULL, 'l' },
181 { "map", required_argument, NULL, 'm' },
182 { "sym", required_argument, NULL, 'n' },
183 { "overlay", required_argument, NULL, 'O' },
184 { "output", required_argument, NULL, 'o' },
185 { "pad", required_argument, NULL, 'p' },
186 { "scramble", required_argument, NULL, 'S' },
187 { "smart", required_argument, NULL, 's' },
188 { "tiny", no_argument, NULL, 't' },
189 { "version", no_argument, NULL, 'V' },
190 { "verbose", no_argument, NULL, 'v' },
191 { "wramx", no_argument, NULL, 'w' },
192 { "nopad", no_argument, NULL, 'x' },
193 { NULL, no_argument, NULL, 0 }
194 };
195
196 /**
197 * Prints the program's usage to stdout.
198 */
printUsage(void)199 static void printUsage(void)
200 {
201 fputs(
202 "Usage: rgblink [-dtVvwx] [-l script] [-m map_file] [-n sym_file]\n"
203 " [-O overlay_file] [-o out_file] [-p pad_value]\n"
204 " [-S spec] [-s symbol] <file> ...\n"
205 "Useful options:\n"
206 " -l, --linkerscript <path> set the input linker script\n"
207 " -m, --map <path> set the output map file\n"
208 " -n, --sym <path> set the output symbol list file\n"
209 " -o, --output <path> set the output file\n"
210 " -p, --pad <value> set the value to pad between sections with\n"
211 " -x, --nopad disable padding of output binary\n"
212 " -V, --version print RGBLINK version and exits\n"
213 "\n"
214 "For help, use `man rgblink' or go to https://rgbds.gbdev.io/docs/\n",
215 stderr);
216 }
217
218 /**
219 * Cleans up what has been done
220 * Mostly here to please tools such as `valgrind` so actual errors can be seen
221 */
cleanup(void)222 static void cleanup(void)
223 {
224 obj_Cleanup();
225 }
226
227 enum ScrambledRegion {
228 SCRAMBLE_ROMX,
229 SCRAMBLE_SRAM,
230 SCRAMBLE_WRAMX,
231
232 SCRAMBLE_UNK, // Used for errors
233 };
234
235 struct {
236 char const *name;
237 uint16_t max;
238 } scrambleSpecs[SCRAMBLE_UNK] = {
239 [SCRAMBLE_ROMX] = { "romx", 65535 },
240 [SCRAMBLE_SRAM] = { "sram", 255 },
241 [SCRAMBLE_WRAMX] = { "wramx", 7},
242 };
243
parseScrambleSpec(char const * spec)244 static void parseScrambleSpec(char const *spec)
245 {
246 // Skip any leading whitespace
247 spec += strspn(spec, " \t");
248
249 // The argument to `-S` should be a comma-separated list of sections followed by an '='
250 // indicating their scramble limit.
251 while (spec) {
252 // Invariant: we should not be pointing at whitespace at this point
253 assert(*spec != ' ' && *spec != '\t');
254
255 // Remember where the region's name begins and ends
256 char const *regionName = spec;
257 size_t regionNameLen = strcspn(spec, "=, \t");
258 // Length of region name string slice for printing, truncated if too long
259 int regionNamePrintLen = regionNameLen > INT_MAX ? INT_MAX : (int)regionNameLen;
260
261 // If this trips, `spec` must be pointing at a ',' or '=' (or NUL) due to the assert
262 if (regionNameLen == 0) {
263 argErr('S', "Missing region name");
264
265 if (*spec == '\0')
266 break;
267 if (*spec == '=') // Skip the limit, too
268 spec = strchr(&spec[1], ','); // Skip to next comma, if any
269 goto next;
270 }
271
272 // Find the next non-blank char after the region name's end
273 spec += regionNameLen + strspn(&spec[regionNameLen], " \t");
274 if (*spec != '\0' && *spec != ',' && *spec != '=') {
275 argErr('S', "Unexpected '%c' after region name \"%.*s\"",
276 regionNamePrintLen, regionName);
277 // Skip to next ',' or '=' (or NUL) and keep parsing
278 spec += 1 + strcspn(&spec[1], ",=");
279 }
280
281 // Now, determine which region type this is
282 enum ScrambledRegion region = 0;
283
284 while (region < SCRAMBLE_UNK) {
285 // If the strings match (case-insensitively), we got it!
286 // It's OK not to use `strncasecmp` because `regionName` is still
287 // NUL-terminated, since the encompassing spec is.
288 if (!strcasecmp(scrambleSpecs[region].name, regionName))
289 break;
290 region++;
291 }
292
293 if (region == SCRAMBLE_UNK)
294 argErr('S', "Unknown region \"%.*s\"", regionNamePrintLen, regionName);
295
296 if (*spec == '=') {
297 spec++; // `strtoul` will skip the whitespace on its own
298 unsigned long limit;
299 char *endptr;
300
301 if (*spec == '\0' || *spec == ',') {
302 argErr('S', "Empty limit for region \"%.*s\"",
303 regionNamePrintLen, regionName);
304 goto next;
305 }
306 limit = strtoul(spec, &endptr, 10);
307 endptr += strspn(endptr, " \t");
308 if (*endptr != '\0' && *endptr != ',') {
309 argErr('S', "Invalid non-numeric limit for region \"%.*s\"",
310 regionNamePrintLen, regionName);
311 endptr = strchr(endptr, ',');
312 }
313 spec = endptr;
314
315 if (region != SCRAMBLE_UNK && limit >= scrambleSpecs[region].max) {
316 argErr('S', "Limit for region \"%.*s\" may not exceed %" PRIu16,
317 regionNamePrintLen, regionName, scrambleSpecs[region].max);
318 limit = scrambleSpecs[region].max;
319 }
320
321 switch (region) {
322 case SCRAMBLE_ROMX:
323 scrambleROMX = limit;
324 break;
325 case SCRAMBLE_SRAM:
326 scrambleSRAM = limit;
327 break;
328 case SCRAMBLE_WRAMX:
329 scrambleWRAMX = limit;
330 break;
331 case SCRAMBLE_UNK: // The error has already been reported, do nothing
332 break;
333 }
334 } else if (region == SCRAMBLE_WRAMX) {
335 // Only WRAMX can be implied, since ROMX and SRAM size may vary
336 scrambleWRAMX = 7;
337 } else {
338 argErr('S', "Cannot imply limit for region \"%.*s\"",
339 regionNamePrintLen, regionName);
340 }
341
342 next:
343 if (spec) {
344 assert(*spec == ',' || *spec == '\0');
345 if (*spec == ',')
346 spec += 1 + strspn(&spec[1], " \t");
347 if (*spec == '\0')
348 break;
349 }
350 }
351 }
352
main(int argc,char * argv[])353 int main(int argc, char *argv[])
354 {
355 int optionChar;
356 char *endptr; /* For error checking with `strtoul` */
357 unsigned long value; /* For storing `strtoul`'s return value */
358
359 /* Parse options */
360 while ((optionChar = musl_getopt_long_only(argc, argv, optstring,
361 longopts, NULL)) != -1) {
362 switch (optionChar) {
363 case 'd':
364 isDmgMode = true;
365 isWRA0Mode = true;
366 break;
367 case 'l':
368 linkerScriptName = musl_optarg;
369 break;
370 case 'm':
371 mapFileName = musl_optarg;
372 break;
373 case 'n':
374 symFileName = musl_optarg;
375 break;
376 case 'O':
377 overlayFileName = musl_optarg;
378 break;
379 case 'o':
380 outputFileName = musl_optarg;
381 break;
382 case 'p':
383 value = strtoul(musl_optarg, &endptr, 0);
384 if (musl_optarg[0] == '\0' || *endptr != '\0') {
385 argErr('p', "");
386 value = 0xFF;
387 }
388 if (value > 0xFF) {
389 argErr('p', "Argument for 'p' must be a byte (between 0 and 0xFF)");
390 value = 0xFF;
391 }
392 padValue = value;
393 break;
394 case 'S':
395 parseScrambleSpec(musl_optarg);
396 break;
397 case 's':
398 /* FIXME: nobody knows what this does, figure it out */
399 (void)musl_optarg;
400 warning(NULL, 0, "Nobody has any idea what `-s` does");
401 break;
402 case 't':
403 is32kMode = true;
404 break;
405 case 'V':
406 printf("rgblink %s\n", get_package_version_string());
407 exit(0);
408 case 'v':
409 beVerbose = true;
410 break;
411 case 'w':
412 isWRA0Mode = true;
413 break;
414 case 'x':
415 disablePadding = true;
416 /* implies tiny mode */
417 is32kMode = true;
418 break;
419 default:
420 printUsage();
421 exit(1);
422 }
423 }
424
425 int curArgIndex = musl_optind;
426
427 /* If no input files were specified, the user must have screwed up */
428 if (curArgIndex == argc) {
429 fputs("FATAL: no input files\n", stderr);
430 printUsage();
431 exit(1);
432 }
433
434 /* Patch the size array depending on command-line options */
435 if (!is32kMode)
436 maxsize[SECTTYPE_ROM0] = 0x4000;
437 if (!isWRA0Mode)
438 maxsize[SECTTYPE_WRAM0] = 0x1000;
439
440 /* Patch the bank ranges array depending on command-line options */
441 if (isDmgMode)
442 bankranges[SECTTYPE_VRAM][1] = BANK_MIN_VRAM;
443
444 /* Read all object files first, */
445 for (obj_Setup(argc - curArgIndex); curArgIndex < argc; curArgIndex++)
446 obj_ReadFile(argv[curArgIndex], argc - curArgIndex - 1);
447
448 /* then process them, */
449 obj_DoSanityChecks();
450 assign_AssignSections();
451 obj_CheckAssertions();
452 assign_Cleanup();
453
454 /* and finally output the result. */
455 patch_ApplyPatches();
456 if (nbErrors) {
457 fprintf(stderr, "Linking failed with %" PRIu32 " error%s\n",
458 nbErrors, nbErrors == 1 ? "" : "s");
459 exit(1);
460 }
461 out_WriteFiles();
462
463 /* Do cleanup before quitting, though. */
464 cleanup();
465 }
466