/* xa_link.c
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 3, 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, see . */
/* WORK IN PROGRESS: do not watch this if you don't have the legal
age in your country to watch this.
*/
/* This is a cheap hack. The xa51 has a couple of ways to scramble
relocation info into it's opcode that the standard linker can't
handle, not to mention word allignment.
No hash or qsort yet.
The relocatable format looks like the known one, BUT ISN'T.
The only things that are handled now are:
"SDCCXA rel, version %f" must be the first line, sort of MAGIC word
"H %d areas %d global symbols" defines the # of areas and globals
"S [Ref0000 | DefXXXX | AbsXXXX]" Def's are supposed to be
defined in their own area/segment
"A size %d flags %d" switch to another segment. this can happen
multiple times and should be equal. flags is ignored for now
"T xxxx 0"
"R xxxx " the relocation info. xxxx is the address
within relative code space. How is something like REL_FF, REL_FFFF,
ABS_70FF. Symbol is the referenced symbol and pc+ is the program
counter that will be used to calculate the relative address (that is
the address of the following instruction).
So, this is not a standalone linker. It will only link files generated
by xa_rasm, which will only process files generated by the xa51 sdcc
port.
*/
#include
#include
#include
#include
#include "xa_version.h"
enum {
// these are all concatenated into the code image
GSINIT=1,
CSEG,
XINIT,
// here goes the final output and should be used by the assembler
GSFINAL,
// these are only for storage
BSEG,
DSEG,
XSEG,
XISEG,
// that's all
MAX_SEGMENTS
};
enum {
REL_FF=1,
REL_FFFF,
BIT_03FF,
DIR_07FF,
DIR_70FF,
DIR_0700FF,
ABS_0F,
ABS_FF,
ABS_FFFF,
ABS_PC,
MAX_REFS
};
char *refModes[]={
"???",
"REL_FF",
"REL_FFFF",
"BIT_03FF",
"DIR_07FF",
"DIR_70FF",
"DIR_0700FF",
"ABS_0F",
"ABS_FF",
"ABS_FFFF",
"ABS_PC"
};
#define CODESIZE 0x10000
int fatalErrors=0;
unsigned char gsinitImage[CODESIZE];
unsigned char csegImage[CODESIZE];
unsigned char xinitImage[CODESIZE];
unsigned char gsfinalImage[CODESIZE];
struct SEGMENT {
short id;
char *name;
int hasSymbols;
int _size;
int start;
int current;
unsigned char *image;
} segments[MAX_SEGMENTS]={
{0, "???", 0, 0, 0, 0, NULL},
{GSINIT, "GSINIT", 0, 0, 0, 0, gsinitImage},
{CSEG, "CSEG", 0, 0, 0, 0, csegImage},
{XINIT, "XINIT", 0, 0, 0, 0, xinitImage},
{GSFINAL, "GSFINAL", 0, 0, 0, 0, gsfinalImage},
{BSEG, "BSEG", 0, 0, 0, 0, NULL},
{DSEG, "DSEG", 0, 0, 0, 0, NULL},
{XSEG, "XSEG", 0, 0, 0, 0, NULL},
{XISEG, "XISEG", 0, 0, 0, 0, NULL},
};
struct MODULE {
char *name;
int offset[MAX_SEGMENTS];
int size[MAX_SEGMENTS];
int isLib;
struct MODULE *next;
struct MODULE *last;
} *modules=NULL;
struct SYMBOL {
char *name;
struct MODULE *module;
int lineno;
struct SEGMENT *segment;
char absolute;
int address;
struct SYMBOL *next;
struct SYMBOL *last;
} *symbols=NULL;
struct REFERENCE {
char *name;
struct MODULE *module;
struct SEGMENT *segment;
int lineno;
unsigned address, pc;
short how;
short resolved;
struct REFERENCE *next;
struct REFERENCE *last;
} *references=NULL;
char *libraryPaths[128];
int nlibPaths=0;
char *libraryFiles[128];
int nlibFiles=0;
static char outFileName[PATH_MAX]={'\0'};
static char mapFileName[PATH_MAX]={'\0'};
FILE *mapOut;
struct SEGMENT *currentSegment;
struct MODULE *currentModule;
int currentLine;
int howToReference(char *how) {
int r;
for (r=1; rnext) {
if (strcmp(symbol->name, symName)==0) {
return symbol;
}
}
return 0;
}
struct MODULE *findModuleByName(char *modName) {
struct MODULE *module;
for (module=modules; module; module=module->next) {
if (strcmp(module->name, modName)==0) {
return module;
}
}
return NULL;
}
void addToModules (char *name, int isLib) {
struct MODULE *module;
int s;
module=calloc(1, sizeof(struct MODULE));
module->name=strdup(name);
for (s=0; soffset[s]=(segments[s]._size+1)&0xfffffe;
}
module->isLib=isLib;
if (!modules) {
modules=module;
} else {
modules->last->next=module;
}
currentModule=modules->last=module;
}
void addToRefs(char *ref, int address, char *how, int pc) {
struct REFERENCE *reference;
reference=calloc(1, sizeof(struct REFERENCE));
reference->name=strdup(ref);
reference->module=currentModule;
reference->segment=currentSegment;
reference->lineno=currentLine;
reference->address=address;
reference->how=howToReference(how);
if (reference->how==ABS_PC) {
reference->resolved=1;
}
reference->pc=pc;
if (!references) {
references=reference;
} else {
references->last->next=reference;
}
references->last=reference;
}
void resolve() {
struct REFERENCE *reference;
for (reference=references; reference; reference=reference->next) {
if ((reference->how==ABS_PC) || findSymbolByName(reference->name)) {
reference->resolved=1;
}
}
}
int isUnresolved(char *ref, int resolved) {
struct REFERENCE *reference;
for (reference=references; reference; reference=reference->next) {
if (strcmp(reference->name, ref)==0) {
// found
if (reference->resolved) {
// already resolved
return 0;
}
if (resolved) {
reference->resolved=1;
return 1;
}
}
}
return 0;
}
void addToDefs(char *def, int address, char absolute) {
struct SYMBOL *symbol;
// no duplicates allowed
if ((symbol=findSymbolByName(def))) {
fprintf (stderr, "*** %s:%d duplicate symbol %s first defined in "
"module %s:%d\n",
currentModule->name, currentLine, def,
symbol->module->name, symbol->lineno);
fatalErrors++;
}
symbol=calloc(1, sizeof(struct SYMBOL));
symbol->name=strdup(def);
symbol->module=currentModule;
symbol->lineno=currentLine;
symbol->segment=currentSegment;
symbol->absolute=absolute;
symbol->address=currentModule->offset[currentSegment->id]+address;
if (!symbols) {
symbols=symbol;
} else {
symbols->last->next=symbol;
}
symbols->last=symbol;
currentSegment->hasSymbols++;
}
void syntaxError (char *err) {
fprintf (stderr, "*** %s:%d error while parsing '%s'\n",
currentModule->name, currentLine, err);
fatalErrors++;
}
void readModule(char *module, int isLib) {
double hisVersion;
char line[132];
FILE *relModule;
char moduleName[PATH_MAX];
int segments, globals;
currentLine=1;
if ((relModule=fopen(module, "r"))==NULL) {
perror (module);
exit (1);
}
// first we need to check if this is a valid file
if (sscanf(fgets(line, 132, relModule),
"SDCCXA rel, version %lf", &hisVersion)!=1) {
fprintf (stderr, "*** %s is not a valid input file\n", module);
exit (1);
}
if (hisVersion!=version) {
fprintf (stderr, "*** WARNING: version conflict; "
"we(%1.1f) != %s(%1.1f)\n",
version, module, hisVersion);
}
currentLine++;
// H 7 areas 168 global symbols
if (sscanf(fgets(line, 132, relModule),
"H %d areas %d global symbols",
&segments, &globals)!=2) {
syntaxError(line);
}
currentLine++;
// M module
if (sscanf(fgets(line, 132, relModule),
"M %s", moduleName)!=1) {
syntaxError(line);
}
// add this to the known modules with current offsets
addToModules(module, isLib);
currentLine++;
// now for the ASTR tags
while (fgets(line, 132, relModule)) {
switch (line[0])
{
case 'A': {
char segment[32];
int size, flags;
if (sscanf(line, "A %[^ ] size %d flags %d",
segment, &size, &flags)!=3) {
syntaxError(line);
}
// do we know this segment?
if (!(currentSegment=findSegmentByName(segment))) {
fprintf (stderr, "*** %s:%d unknown area: %s\n", module,
currentLine, segment);
exit (1);
}
// double check repeated 'A' records
if (currentModule->size[currentSegment->id]) {
// pleased to meet you again, I hope ...
if (currentModule->size[currentSegment->id] != size) {
fprintf (stderr, "*** %s:%d error %s size %d != %d\n",
module, currentLine,
currentSegment->name,
currentModule->size[currentSegment->id],
size);
fatalErrors++;
}
} else {
currentSegment->_size += size;
currentModule->size[currentSegment->id] = size;
}
// never mind about the flags for now
break;
}
case 'S': {
char symbol[132];
char refdef[132];
unsigned int address;
if (sscanf(line, "S %[^ ] %s", symbol, refdef)!=2) {
fprintf (stderr, "*** %s:%d syntax error near \"%s\"\n",
module, currentLine, line);
exit (1);
}
if (strncmp(refdef, "Ref", 3)==0) {
// we don't need them
} else if (strncmp(refdef, "Def", 3)==0) {
sscanf (refdef, "Def%04x", &address);
addToDefs(symbol, address, 0);
} else if (strncmp(refdef, "Abs", 3)==0) {
sscanf (refdef, "Abs%04x", &address);
addToDefs(symbol, address, 1);
} else {
fprintf (stderr, "%s:%d found invalid symbol definition \"%s\"\n",
module, currentLine, line);
exit (1);
}
break;
}
case 'T': {
unsigned int address;
unsigned int byte;
char *tline=NULL;
if (currentSegment->id!=CSEG &&
currentSegment->id!=GSINIT &&
currentSegment->id!=XINIT) {
fprintf (stderr, "%s:%d cannot emit bytes in %s\n",
module, currentLine, currentSegment->name);
exit (1);
}
if (sscanf(strtok(&line[2], " "), "%04x", &address)!=1) {
fprintf (stderr, "%s:%d error in T record\n", module, currentLine);
fatalErrors++;
}
address+=currentModule->offset[currentSegment->id];
//address+=currentSegment->current;
for ( ;
(tline=strtok(NULL, " \t\n")) &&
(sscanf(tline, "%02x", &byte)==1);
) {
currentSegment->image[address++]=byte;
currentSegment->current++;
}
break;
}
case 'R': {
unsigned address, pc;
char symbol[132];
char how[32];
sscanf (line, "R %x %[^ ] %[^ ] %x", &address, how, symbol, &pc);
addToRefs (symbol, address, how, pc);
break;
}
default:
fprintf (stderr, "%s:%d unknown record \"%s\"\n",
module, currentLine, line);
fatalErrors++;
break;
}
currentLine++;
}
fclose (relModule);
}
void writeModule(char *outFileName) {
FILE *fOut;
unsigned int address=segments[GSFINAL].start;
unsigned int size=segments[GSFINAL]._size;
unsigned int len;
unsigned int checksum;
if ((fOut=fopen(outFileName, "w"))==NULL) {
perror (outFileName);
}
while (size) {
len = size>16 ? 16 : size;
size-=len;
fprintf (fOut, ":%02X%04X%02X", len, address, 0);
checksum = len + (address>>8) + (address&0xff);
while (len--) {
checksum += gsfinalImage[address];
fprintf (fOut, "%02X", gsfinalImage[address++]);
}
checksum &= 0xff;
if (checksum) {
checksum = 0x100 - checksum;
}
fprintf (fOut, "%02X\n", checksum);
}
fprintf (fOut, ":00000001FF\n");
fclose (fOut);
}
int relocate() {
struct SYMBOL *symbol;
struct REFERENCE *reference;
char *from, *to;
int length=segments[GSINIT]._size +
segments[CSEG]._size +
segments[XINIT]._size;
int unresolved=0;
// first check if it will fit
if (length > 0xffff) {
fprintf (stderr, "error: code segment exceeds 0xffff\n");
fatalErrors++;
}
// resolve reverences
for (reference=references; reference; reference=reference->next) {
if (!reference->resolved && !findSymbolByName(reference->name)) {
unresolved++;
}
}
if (unresolved) {
// first scan the libraries
return unresolved;
}
// GSFINAL starts at --code-loc ( -b CSEG = 0x1234 )
if (segments[CSEG].start & 1) {
fprintf (stderr, "*** error: code doesn't start at "
"an even address: %04x\n", segments[CSEG].start);
exit (1);
}
segments[GSFINAL].start=segments[CSEG].start;
memset(gsfinalImage, 0xff, CODESIZE);
// copy gsinit to gsfinal
from = gsinitImage;
to = gsfinalImage + segments[GSFINAL].start + segments[GSFINAL]._size;
memcpy(to, from, segments[GSINIT]._size);
segments[GSINIT].start=segments[GSFINAL].start;
segments[GSFINAL]._size += segments[GSINIT]._size;
if (segments[GSFINAL]._size & 1) {
segments[GSFINAL]._size++;
}
// append cseg to gsfinal
from=csegImage;
to = gsfinalImage + segments[GSFINAL].start + segments[GSFINAL]._size;
memcpy(to, from, segments[CSEG]._size);
segments[CSEG].start=segments[GSFINAL].start+segments[GSFINAL]._size;
segments[GSFINAL]._size += segments[CSEG]._size;
if (segments[GSFINAL]._size & 1) {
segments[GSFINAL]._size++;
}
// append xinit to gsfinal
from=xinitImage;
to = gsfinalImage + segments[GSFINAL].start + segments[GSFINAL]._size;
memcpy(to, from, segments[XINIT]._size);
segments[XINIT].start=segments[GSFINAL].start+segments[GSFINAL]._size;
segments[GSFINAL]._size += segments[XINIT]._size;
if (segments[GSFINAL]._size & 1) {
segments[GSFINAL]._size++;
}
// XISEG is located after XSEG
if (segments[XSEG].start & 1) {
fprintf (stderr, "*** warning: xdata doesn't start at "
"an even address: %04x\n", segments[XSEG].start);
}
if (segments[XSEG]._size & 1) {
segments[XSEG]._size++;
}
segments[XISEG].start=segments[XSEG].start +
segments[XSEG]._size;
// now relocate the defined symbols
for (symbol=symbols; symbol; symbol=symbol->next) {
if (!symbol->absolute) {
symbol->address += symbol->segment->start;
}
}
// and the references
for (reference=references; reference; reference=reference->next) {
symbol=findSymbolByName(reference->name);
if (!reference->resolved && !symbol && reference->how!=ABS_PC) {
// this reference isn't resolved after all
fprintf (stderr, "*** %s:%d undefined symbol %s\n",
reference->module->name, reference->lineno,
reference->name);
fatalErrors++;
} else {
reference->address +=
reference->module->offset[reference->segment->id]+
reference->segment->start;
reference->pc +=
reference->module->offset[reference->segment->id]+
reference->segment->start;
switch (reference->how)
{
case REL_FF: {
int rel8 = symbol->address-(reference->pc & ~1);
if (rel8<-256 || rel8>256) {
fprintf (stderr,
"rel8 target for %s is out of range in module %s:%d\n",
reference->name, reference->module->name,
reference->lineno);
fatalErrors++;
}
gsfinalImage[reference->address]=rel8/2;
break;
}
case REL_FFFF: {
int rel16 = symbol->address-(reference->pc & ~1);
if (rel16<-65536 || rel16>65534) {
fprintf (stderr,
"rel16 target for %s is out of range in module %s:%d\n",
reference->name, reference->module->name,
reference->lineno);
fatalErrors++;
}
gsfinalImage[reference->address]=(rel16/2)>>8;
gsfinalImage[reference->address+1]=rel16/2;
break;
}
case DIR_70FF:
gsfinalImage[reference->address] =
(gsfinalImage[reference->address]&~0x70) +
((symbol->address>>4)&0x70);
gsfinalImage[reference->address+1] = symbol->address;
break;
case ABS_FFFF:
gsfinalImage[reference->address] = symbol->address>>8;
gsfinalImage[reference->address+1] = symbol->address;
break;
case ABS_FF:
gsfinalImage[reference->address] = symbol->address;
break;
case ABS_PC:
{
unsigned int address=
(gsfinalImage[reference->address]<<8) +
gsfinalImage[reference->address+1];
address += reference->module->offset[reference->segment->id];
address += segments[reference->segment->id].start;
gsfinalImage[reference->address] = address>>8;
gsfinalImage[reference->address+1] = address;
};
break;
default:
fprintf (stderr, "unsupported reference mode %d.\n",
reference->how);
fatalErrors++;
}
}
}
return 0;
}
void usage (char * progName, int errNo) {
fprintf (stderr, "usage: %s lnkCmdFile\n", progName);
if (errNo) {
exit (errNo);
}
}
int scanLibraries(int unresolved) {
int resolved=0;
int nlp, nlf;
char libFiles[PATH_MAX];
char libFile[PATH_MAX];
char line[132];
char symName[132];
FILE *lf, *lfs;
for (nlp=0; nlpnext) {
if (!reference->resolved) {
fprintf (stderr, "*** unresolved symbol %s in %s:%d\n",
reference->name, reference->module->name,
reference->lineno);
fatalErrors++;
}
}
break;
}
}
if (unresolved==0) {
writeModule(outFileName);
}
// the modules
fprintf (mapOut, "Modules:\n");
for (module=modules; module; module=module->next) {
fprintf (mapOut, "\t%s\n", module->name);
for (s=0; ssize[s]) {
fprintf (mapOut, "\t\t%s:0x%04x-0x%04x\n", segments[s].name,
module->offset[s]+segments[s].start,
module->offset[s]+segments[s].start+module->size[s]);
}
}
}
// the segments
fprintf (mapOut, "\nSegments:\n");
for (s=1; snext) {
fprintf (mapOut, "%s\t%s %s0x%04x %s\n", symbol->name,
symbol->segment->name,
symbol->absolute ? "= " : "",
symbol->address, symbol->module->name);
}
fclose(mapOut);
return fatalErrors? 1 : 0;
}