1
0
mirror of https://github.com/fachat/xa65.git synced 2024-06-08 07:29:39 +00:00
xa65/xa/misc/reloc65.c
2024-03-19 09:32:45 +01:00

532 lines
14 KiB
C
Raw Blame History

/* reloc65 -- A part of xa65 - 65xx/65816 cross-assembler and utility suite
* o65 file relocator
*
* Copyright (C) 1989-1997 Andr<64> Fachat (a.fachat@physik.tu-chemnitz.de)
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*/
#include <stdio.h>
#include <stdlib.h>
#include <sys/stat.h>
#include <unistd.h>
#include <errno.h>
#include <string.h>
#include "version.h"
#define BUF (9*2+8) /* 16 bit header */
#define programname "reloc65"
#define progversion "v0.2.1"
#define author "Written by Andre Fachat"
#define copyright "Copyright (C) 1997-2002 Andre Fachat."
/* o65 file format mode bits */
#define FM_OBJ 0x1000
#define FM_SIZE 0x2000
#define FM_RELOC 0x4000
#define FM_CPU 0x8000
#define FM_CPU2 0x00f0
#define FM_CPU2_6502 0x0000
#define FM_CPU2_65C02 0x0010
#define FM_CPU2_65SC02 0x0020
#define FM_CPU2_65CE02 0x0030
#define FM_CPU2_NMOS 0x0040
#define FM_CPU2_65816E 0x0050
typedef struct {
char *fname;
size_t fsize;
unsigned char *buf;
int tbase, tlen, dbase, dlen, bbase, blen, zbase, zlen;
int tdiff, ddiff, bdiff, zdiff;
unsigned char *segt;
unsigned char *segd;
unsigned char *utab;
unsigned char *rttab;
unsigned char *rdtab;
unsigned char *extab;
} file65;
int read_options(unsigned char *f);
int read_undef(unsigned char *f);
unsigned char *reloc_seg(unsigned char *f, int len, unsigned char *rtab, file65 *fp, int undefwarn);
unsigned char *reloc_globals(unsigned char *, file65 *fp);
file65 file;
unsigned char cmp[] = { 1, 0, 'o', '6', '5' };
void usage(FILE *fp)
{
fprintf(fp,
"Usage: %s [OPTION]... [FILE]...\n"
"Relocator for o65 object files\n"
"\n"
" -b? addr relocates segment '?' (i.e. 't' for text segment,\n"
" 'd' for data, 'b' for bss and 'z' for zeropage) to the new\n"
" address `addr'\n"
" -o file uses `file' as output file. Default is `a.o65'\n"
" -x? extracts text `?' = `t' or data `?' = `d' segment from file\n",
programname);
fprintf(fp,
" instead of writing back the whole file\n"
" -X extracts the file such that text and data\n"
" segments are chained, i.e. possibly relocating\n"
" the data segment to the end of the text segment\n"
" -C <CPU> Set the o65 CPU flags in the output for the following CPUs:\n"
" 6502, 65SC02, 65C02, 65CE02, 65816, NMOS6502\n"
" (for details see the man page)\n"
" -v verbose output\n"
" --version output version information and exit\n"
" --help display this help and exit\n");
}
const char *cpunames[16] = {
"6502",
"65C02",
"65SC02",
"65CE02",
"NMOS6502",
"65816",
NULL, NULL,
"6809", NULL, // 1000 -
"Z80", NULL, NULL, // 1010 -
"8086", // 1101 -
"80286", // 1110 -
NULL
};
int main(int argc, char *argv[]) {
int i = 1, mode, hlen;
int j;
size_t n;
FILE *fp;
int tflag = 0, dflag = 0, bflag = 0, zflag = 0;
int tbase = 0, dbase = 0, bbase = 0, zbase = 0;
int *base;
char *outfile = "a.o65";
int extract = 0;
int verbose = 0;
int trgcpu = -1; // output file target CPU flag (-1 = do not change)
char *arg; // temporary argument pointer
if (argc <= 1) {
usage(stderr);
exit(1);
}
if (strstr(argv[1], "--help") || strstr(argv[1], "-?")
|| strstr(argv[1], "-h")) {
usage(stdout);
exit(0);
}
if (strstr(argv[1], "--version")) {
version(programname, progversion, author, copyright);
exit(0);
}
while (i < argc) {
arg = NULL;
if (argv[i][0] == '-') {
/* process options */
switch (argv[i][1]) {
case 'v':
verbose = 1;
break;
case 'o':
if (argv[i][2])
outfile = argv[i] + 2;
else if (i + 1 < argc)
outfile = argv[++i];
else
fprintf(stderr, "%s: missing output file\n", programname);
break;
case 'X':
extract = 3;
break;
case 'b':
base = NULL;
switch (argv[i][2]) {
case 't':
tflag = 1;
base = &tbase;
break;
case 'd':
dflag = 1;
base = &dbase;
break;
case 'b':
bflag = 1;
base = &bbase;
break;
case 'z':
zflag = 1;
base = &zbase;
break;
default:
printf("Unknown segment type '%c' - ignored!\n",
argv[i][2]);
break;
}
if (base != NULL) {
if (argv[i][3])
*base = atoi(argv[i] + 3);
else if (i + 1 < argc)
*base = atoi(argv[++i]);
else
fprintf(stderr, "%s: missing address\n", programname);
}
break;
case 'x': /* extract segment */
switch (argv[i][2]) {
case 't':
extract = 1;
break;
case 'd':
extract = 2;
break;
case 'z':
case 'b':
printf("Cannot extract segment type '%c' - ignored!\n",
argv[i][2]);
break;
default:
printf("Unknown segment type '%c' - ignored!\n",
argv[i][2]);
break;
}
break;
case 'C':
if (argv[i][2])
arg = argv[i] + 2;
else if (i + 1 < argc)
arg = argv[++i];
if (arg == NULL) {
printf("Missing CPU parameter to -C - ignored\n");
break;
}
for (j = 0; j < 16; j++) {
if (cpunames[j] != NULL && !strcmp(arg, cpunames[j])) {
break;
}
}
if (j == 16) {
printf("Unknown CPU identifier '%s' for -C - ignored\n",
arg);
} else {
trgcpu = j;
}
break;
default:
fprintf(stderr, "%s: %s unknown option, use '-h' for help\n",
programname, argv[i]);
break;
}
} else {
struct stat fs;
file.fname = argv[i];
stat(argv[i], &fs);
file.fsize = fs.st_size;
file.buf = malloc(file.fsize);
if (!file.buf) {
fprintf(stderr, "Oops, no more memory! (%d)\n", file.fsize);
exit(1);
}
printf("reloc65: read file %s -> %s\n", argv[i], outfile);
fp = fopen(argv[i], "rb");
if (fp) {
n = fread(file.buf, 1, file.fsize, fp);
fclose(fp);
if ((n >= file.fsize) && (!memcmp(file.buf, cmp, 5))) {
mode = file.buf[7] * 256 + file.buf[6];
if (mode & FM_SIZE) {
fprintf(stderr,
"reloc65: %s: 32 bit size not supported\n",
argv[i]);
} else if (mode & FM_RELOC) {
fprintf(stderr,
"reloc65: %s: pagewise relocation not supported\n",
argv[i]);
} else {
if (trgcpu >= 0) {
// change CPU flags
mode &= ~FM_CPU;
mode &= ~FM_CPU2;
mode |= (trgcpu << 4);
if (trgcpu == 5) {
// this trgcpu is actually 65816 in emulation mode
// unsure if we should do an own cmdline option
mode |= FM_CPU; // 65816 native
}
}
file.buf[6] = mode & 0xff;
file.buf[7] = (mode >> 8) & 0xff;
hlen = BUF + read_options(file.buf + BUF);
file.tbase = file.buf[9] * 256 + file.buf[8];
file.tlen = file.buf[11] * 256 + file.buf[10];
file.tdiff = tflag ? tbase - file.tbase : 0;
file.dbase = file.buf[13] * 256 + file.buf[12];
file.dlen = file.buf[15] * 256 + file.buf[14];
file.ddiff = dflag ? dbase - file.dbase : 0;
if (extract == 3) {
if (dflag) {
fprintf(stderr,
"reloc65: %s: Warning: data segment address overrides -X option\n",
argv[i]);
} else {
dbase = file.tbase + file.tdiff + file.tlen;
file.ddiff = dbase - file.dbase;
}
}
file.bbase = file.buf[17] * 256 + file.buf[16];
file.blen = file.buf[19] * 256 + file.buf[18];
file.bdiff = bflag ? bbase - file.bbase : 0;
if (extract == 3) {
if (bflag) {
fprintf(stderr,
"reloc65: %s: Warning: bss segment address overrides -X option\n",
argv[i]);
} else {
bbase = file.dbase + file.ddiff + file.dlen;
file.bdiff = bbase - file.bbase;
}
}
file.zbase = file.buf[21] * 256 + file.buf[20];
file.zlen = file.buf[23] * 256 + file.buf[22];
file.zdiff = zflag ? zbase - file.zbase : 0;
if (verbose) {
printf("Relocating segments to:\n");
printf(
"text segment @ $%04x - $%04x, %5d ($%04x) bytes, diff is %5d ($%04x)\n",
file.tbase + file.tdiff,
file.tbase + file.tdiff + file.tlen,
file.tlen, file.tlen, file.tdiff,
file.tdiff & 0xffff);
printf(
"data segment @ $%04x - $%04x, %5d ($%04x) bytes, diff is %5d ($%04x)\n",
file.dbase + file.ddiff,
file.dbase + file.ddiff + file.dlen,
file.dlen, file.dlen, file.ddiff,
file.ddiff & 0xffff);
printf(
"bss segment @ $%04x - $%04x, %5d ($%04x) bytes, diff is %5d ($%04x)\n",
file.bbase + file.bdiff,
file.bbase + file.bdiff + file.blen,
file.blen, file.blen, file.bdiff,
file.bdiff & 0xffff);
printf(
"zero segment @ $%04x - $%04x, %5d ($%04x) bytes, diff is %5d ($%04x)\n",
file.zbase + file.zdiff,
file.zbase + file.zdiff + file.zlen,
file.zlen, file.zlen, file.zdiff,
file.zdiff & 0xffff);
}
/* pointer of position in file */
file.segt = file.buf + hlen;
file.segd = file.segt + file.tlen;
file.utab = file.segd + file.dlen;
file.rttab = file.utab + read_undef(file.utab);
file.rdtab = reloc_seg(file.segt, file.tlen, file.rttab,
&file, extract);
file.extab = reloc_seg(file.segd, file.dlen, file.rdtab,
&file, extract);
reloc_globals(file.extab, &file);
if (tflag) {
file.buf[9] = (tbase >> 8) & 255;
file.buf[8] = tbase & 255;
}
if (dflag) {
file.buf[13] = (dbase >> 8) & 255;
file.buf[12] = dbase & 255;
}
if (bflag) {
file.buf[17] = (bbase >> 8) & 255;
file.buf[16] = bbase & 255;
}
if (zflag) {
file.buf[21] = (zbase >> 8) & 255;
file.buf[20] = zbase & 255;
}
fp = fopen(outfile, "wb");
if (fp) {
switch (extract) {
case 0: /* whole file */
fwrite(file.buf, 1, file.fsize, fp);
break;
case 1: /* text segment */
fwrite(file.segt, 1, file.tlen, fp);
break;
case 2: /* data segment */
fwrite(file.segd, 1, file.dlen, fp);
break;
case 3: /* text+data */
fwrite(file.segt, 1, file.tlen, fp);
fwrite(file.segd, 1, file.dlen, fp);
break;
}
fclose(fp);
} else {
fprintf(stderr, "reloc65: write '%s': %s\n",
outfile, strerror(errno));
}
}
} else {
fprintf(stderr, "reloc65: %s: not an o65 file!\n", argv[i]);
if (file.buf[0] == 1 && file.buf[1] == 8
&& file.buf[3] == 8) {
printf(
"%s: C64 BASIC executable (start address $0801)?\n",
argv[i]);
} else if (file.buf[0] == 1 && file.buf[1] == 4
&& file.buf[3] == 4) {
printf(
"%s: CBM PET BASIC executable (start address $0401)?\n",
argv[i]);
}
}
} else {
fprintf(stderr, "reloc65: read '%s': %s\n", argv[i],
strerror(errno));
}
}
i++;
}
exit(0);
}
int read_options(unsigned char *buf) {
int c, l = 0;
c = buf[0];
while (c && c != EOF) {
c &= 255;
l += c;
c = buf[l];
}
return ++l;
}
int read_undef(unsigned char *buf) {
int n, l = 2;
n = buf[0] + 256 * buf[1];
while (n) {
n--;
while (buf[l] != 0) {
l++;
}
l++;
}
return l;
}
#define reldiff(s) (((s)==2)?fp->tdiff:(((s)==3)?fp->ddiff:(((s)==4)?fp->bdiff:(((s)==5)?fp->zdiff:0))))
unsigned char* reloc_seg(unsigned char *buf, int len, unsigned char *rtab,
file65 *fp, int undefwarn) {
int adr = -1;
int type, seg, old, new;
/*printf("tdiff=%04x, ddiff=%04x, bdiff=%04x, zdiff=%04x\n",
fp->tdiff, fp->ddiff, fp->bdiff, fp->zdiff);*/
while (*rtab) {
if ((*rtab & 255) == 255) {
adr += 254;
rtab++;
} else {
adr += *rtab & 255;
rtab++;
type = *rtab & 0xe0;
seg = *rtab & 0x07;
/*printf("reloc entry @ rtab=%p (offset=%d), adr=%04x, type=%02x, seg=%d\n",rtab-1, *(rtab-1), adr, type, seg);*/
rtab++;
switch (type) {
case 0x80: /* WORD - two byte address */
old = buf[adr] + 256 * buf[adr + 1];
new = old + reldiff(seg);
buf[adr] = new & 255;
buf[adr + 1] = (new >> 8) & 255;
break;
case 0x40: /* HIGH - high byte of an address */
old = buf[adr] * 256 + *rtab;
new = old + reldiff(seg);
buf[adr] = (new >> 8) & 255;
*rtab = new & 255;
rtab++;
break;
case 0x20: /* LOW - low byt of an address */
old = buf[adr];
new = old + reldiff(seg);
buf[adr] = new & 255;
break;
}
if (seg == 0) {
/* undefined segment entry */
if (undefwarn) {
fprintf(stderr,
"reloc65: %s: Warning: undefined relocation table entry not handled!\n",
fp->fname);
}
rtab += 2;
}
}
}
if (adr > len) {
fprintf(stderr,
"reloc65: %s: Warning: relocation table entries past segment end!\n",
fp->fname);
fprintf(stderr, "reloc65: adr=%x len=%x\n", adr, len);
}
return ++rtab;
}
unsigned char* reloc_globals(unsigned char *buf, file65 *fp) {
int n, old, new, seg;
n = buf[0] + 256 * buf[1];
buf += 2;
while (n) {
/*printf("relocating %s, ", buf);*/
while (*(buf++))
;
seg = *buf;
old = buf[1] + 256 * buf[2];
new = old + reldiff(seg);
/*printf("old=%04x, seg=%d, rel=%04x, new=%04x\n", old, seg, reldiff(seg), new);*/
buf[1] = new & 255;
buf[2] = (new >> 8) & 255;
buf += 3;
n--;
}
return buf;
}