// Copyright (C) 1999-2003 Core Technologies. // // This file is part of tpasm. // // tpasm is free software; you can redistribute it and/or modify // it under the terms of the tpasm LICENSE AGREEMENT. // // tpasm 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 // tpasm LICENSE AGREEMENT for more details. // // You should have received a copy of the tpasm LICENSE AGREEMENT // along with tpasm; see the file "LICENSE.TXT". // Cross Assembler #include "include.h" typedef struct tokenlist { char *token; int tokenNum; } TOKENLIST; enum { T_OUTPUT, T_DIRECTORY, T_DEFINE, T_PROCESSOR, T_PASSES, T_LISTNAME, T_WARNINGS, T_DEBUG, T_SHOWPROCESSORS, T_SHOWOUTPUTTYPES, }; static const TOKENLIST cmdTokens[]= { {"-o",T_OUTPUT}, {"-I",T_DIRECTORY}, {"-d",T_DEFINE}, {"-P",T_PROCESSOR}, {"-n",T_PASSES}, {"-l",T_LISTNAME}, {"-w",T_WARNINGS}, {"-p",T_DEBUG}, {"-show_procs",T_SHOWPROCESSORS}, {"-show_types",T_SHOWOUTPUTTYPES}, {"",0} }; static bool MatchToken(char *theString,const TOKENLIST *theList,int *theToken) // given a string, see if it matches any of the tokens in the token list, if so, return // its token number in theToken, otherwise, return false { int i; bool found,reachedEnd; found=reachedEnd=false; i=0; while(!found&&!reachedEnd) { if(theList[i].token[0]!='\0') { if(strcmp(theString,theList[i].token)==0) { found=true; *theToken=theList[i].tokenNum; } i++; } else { reachedEnd=true; } } return(found); } bool ProcessLineLocationLabel(bool haveLabel,bool isLocal,char *lineLabel) // When the label on a line is meant to be interpreted as labelling the current // location, call this to handle it { bool fail; fail=false; if(haveLabel) { if(currentSegment) { fail=!AssignLabel(lineLabel,isLocal,currentSegment->currentPC+currentSegment->codeGenOffset); // assign this label now, since no assembler pseudo-op was located which may have changed its meaning } else { AssemblyComplaint(NULL,true,"Label '%s' defined outside of segment\n",lineLabel); } } return(!fail); } static bool ParseLine(char *theLine,LISTING_RECORD *listingRecord) // Handle parsing and assembling // if active is false, this does nothing more than update the current context { bool fail; bool isLocal, haveLabel, haveOpcode; int lineIndex; char lineLabel[MAXSTRING]; char opcode[MAXSTRING]; PSEUDO_OPCODE *processorPseudoMatch, *pseudoMatch; void *opcodeMatch; MACRO_RECORD *macroMatch; int i; lineIndex=0; haveLabel=false; fail=false; if(ParseWhiteSpace(theLine,&lineIndex)||(haveLabel=ParseLabelDefinition(theLine,&lineIndex,lineLabel,&isLocal))||ParseComment(theLine,&lineIndex)) // does the line begin correctly? { haveOpcode=false; if(ParseOpcode(theLine,&lineIndex,opcode)) { i=0; while(opcode[i]) { opcode[i]=tolower(opcode[i]); // convert all opcodes to lower case i++; } haveOpcode=true; } processorPseudoMatch=NULL; pseudoMatch=NULL; opcodeMatch=NULL; macroMatch=NULL; if(haveOpcode) // find out what the opcode matches, in precedence order { if(!(processorPseudoMatch=MatchProcessorPseudoOpcode(opcode))) { if(!(opcodeMatch=MatchProcessorOpcode(opcode))) { if(!(pseudoMatch=MatchGlobalPseudoOpcode(opcode))) { macroMatch=MatchMacro(opcode); } } } } if(haveOpcode&&processorPseudoMatch) { if(contextStack->active) // processor pseudo-ops cannot mess with the context currently, and are only active when generating code { fail=!HandleProcessorPseudoOpcode(processorPseudoMatch,theLine,&lineIndex,haveLabel,isLocal,lineLabel,listingRecord); } } else if(haveOpcode&&pseudoMatch) { fail=!HandleGlobalPseudoOpcode(pseudoMatch,theLine,&lineIndex,haveLabel,isLocal,lineLabel,listingRecord); } else { if(contextStack->active) // deal with standard assembly { if(ProcessLineLocationLabel(haveLabel,isLocal,lineLabel)) // label is for the current location { if(haveOpcode) { if(opcodeMatch) // see if opcode for the current processor { if(HandleAliasMatches(theLine,&lineIndex,listingRecord)) // substitute operand aliases { fail=!HandleProcessorOpcode(opcodeMatch,theLine,&lineIndex,listingRecord); } else { fail=true; } } else { if(macroMatch) // see if the opcode is a macro { fail=!HandleMacroMatch(macroMatch,theLine,&lineIndex,listingRecord); } else { AssemblyComplaint(NULL,true,"Unrecognized opcode '%s'\n",opcode); } } } } else { fail=true; } } } } else { if(contextStack->active) { AssemblyComplaint(NULL,true,"Garbage at start of line\n"); } } return(!fail); } static bool AssembleLine(char *theLine,char sourceType,bool wantList) // Assemble the current source line // If there is a problem, (hard error), complain and return false // If there is a syntax error, or some sort of assembly problem // Do not complain about it until the last pass (assembly errors // are not considered failure for this routine). { bool fail; LISTING_RECORD listingRecord; TEXT_BLOCK *wasCollecting; fail=false; listingRecord.lineNumber=currentVirtualFileLine; listingRecord.listPC=0; if(currentSegment) { listingRecord.listPC=currentSegment->currentPC; } listingRecord.listObjectString[0]='\0'; listingRecord.wantList=wantList; listingRecord.sourceType=sourceType; // ### this is a little ugly. // We want to be able to collect the text BETWEEN the start and end pseudo-ops // this solves the problem wasCollecting=collectingBlock; if(ParseLine(theLine,&listingRecord)) { // generate list output OutputListFileLine(&listingRecord,theLine); // dump the generated bytes to the list file if(wasCollecting&&collectingBlock) // if nothing done to change block collection status, then collect the text line { if(!AddLineToTextBlock(collectingBlock,theLine)) { ReportComplaint(true,"Failed to create text line during text block collection\n"); fail=true; } } } else { fail=true; } return(!fail); } static bool GetLine(FILE *theFile,char *line,int lineLength,bool *atEOF,bool *overflow) // read a line from the given file into the array line // if there is a read error, return false // lineLength is considered to be the maximum number of characters // to read into the line (including the 0 terminator) // if the line fills before a CR is hit, overflow will be set true, and the rest of the // line will be read, but not stored // if the EOF is hit, atEOF is set true { int i; int result; unsigned char c; bool hadError; bool stopReading; i=0; // index into output line stopReading=hadError=(*atEOF)=(*overflow)=false; while(!stopReading) { result=fgetc(theFile); if(result!=EOF) // if something was read, check it, and add to output line { c=(unsigned char)result; if(c=='\n'||c=='\0') // found termination? { stopReading=true; // yes, output line is complete } else { if(c!='\r') // see if the character should be added to the line { if(ifirstLine; stopParsing=false; while(!fail&&!stopParsing&&tempLine) { currentVirtualFile=tempLine->whereFrom.theFile; currentVirtualFileLine=tempLine->whereFrom.fileLineNumber; if(substitutionList&&substitutionText) // make sure substitutions are desired { if(ProcessTextSubsitutions(inBuffer,&tempLine->theLine[0],substitutionList,substitutionText,&overflow)) { if(overflow) { AssemblyComplaint(NULL,false,"Line too long, truncation occurred\n"); } } else { fail=true; } } else { strcpy(inBuffer,&tempLine->theLine[0]); } if(!fail) { fail=!AssembleLine(inBuffer,sourceType,outputListingExpansions); tempLine=tempLine->next; // do next line } } DisposePtr(inBuffer); } else { ReportComplaint(true,"Could not allocate memory for line input buffer\nOS Reports: %s\n",strerror(errno)); fail=true; } scopeValue=lastScopeValue; // back out to the previous scope stopParsing=false; blockDepth--; // step back } else { AssemblyComplaint(NULL,true,"Attempt to nest text substitutions too deeply (arbitrary limit is %d)\n",MAXBLOCKDEPTH); } return(!fail); } bool ProcessSourceFile(char *theFileName,bool huntForIt) // process the passed source file, if there are any problems, return false // if intermediatePass is true, do not flag expressions which contain unresolved labels as errors, // and do not generate output. // if intermediatePass is false, then generate output, complaining about anything // that could not be resolved. // numUnresolvedLabels will be incremented for each unresolved label encountered. // numModifiedLabels will be incremented each time a label's value is changed. { char *inBuffer; // character buffers for line parsing FILE *sourceFile; int oldLineNum; // line number at entry SYMTABLENODE *newSourceFile, *oldSourceFile; bool fail; bool overflow; fail=false; if(includeDepthcontextType=CT_ROOT; // make a default context contextStack->active=true; // declare it active fail=!ProcessSourceFile(sourceFileName,false);// go handle this file (and any includes it may have) FlushContextRecords(); // unwind any contexts that happen to be left around DestroyAliases(); // get rid of alias definitions DestroyMacros(); // dispose of macro definitions currentFile=NULL; currentVirtualFile=NULL; } else { ReportComplaint(true,"Failed to create root context\n"); fail=true; } if(!intermediatePass) { OutputListFileSegments(); OutputListFileLabels(); if(errorCount==0) { DumpOutputFiles(); // dump out all requested segment or symbol files } } DestroySegments(); // remove segments created by assembly } else { ReportComplaint(true,"Failed to create default code segment\n"); fail=true; } SelectProcessor("",&found); // deselect processor at end of pass } else { ReportComplaint(true,"Default processor '%s' unrecognized\n",defaultProcessorName); fail=true; } } else { fail=true; // some hard failure in select (it was reported there) } return(!fail); } static bool HandleAssembly() // Assemble the source file(s), create output and list files as needed // If there is a problem, report it, and return false { bool fail; int lastNumUnresolvedLabels; time_t timeIn,timeOut; int totalTime; bool keepGoing; fail=false; listFile=NULL; if(!listFileName||(listFile=OpenTextOutputFile(listFileName))) { time(&timeIn); // get time at entry OutputListFileHeader(timeIn); passCount=0; intermediatePass=true; fail=!ProcessAssembly(); // make first pass on source passCount++; lastNumUnresolvedLabels=numUnresolvedLabels; // remember how many were unresolved this time keepGoing=(numUnresolvedLabels!=0); // if on first pass, nothing was unresolved, then we're done while(!fail&&keepGoing) // loop until we fail, or stop making progress with label resolution { if(ProcessAssembly()) // process the passed source file (failure here means HARD failure, not assembly errors) { passCount++; ReportDiagnostic("Unresolved labels: %d, Modified labels: %d\n",numUnresolvedLabels,numModifiedLabels); keepGoing=(((numUnresolvedLabels>0)&&(numUnresolvedLabels=maxPasses&&keepGoing) // hmmm, source is not resolving { ReportComplaint(true,"Assembly aborting due to too many passes (%d)\n",passCount); keepGoing=false; } } else { fail=true; } } if(!fail) // make final pass, creating output/listing files, complaining about any unresolved labels that still remain { intermediatePass=false; // this is the final pass fail=!ProcessAssembly(); // make final pass, generating output time(&timeOut); // get time now that assembly is finished totalTime=(int)difftime(timeOut,timeIn); if(!fail) { OutputListFileStats(totalTime); } } if(listFileName) // if list file was desired, then close it { CloseTextOutputFile(listFile); } } else { fail=true; } return(!fail); } static void InitGlobals() // initialize program globals { numAllocatedPointers=0; infoOnly=false; // assume we are actually assembling displayWarnings=true; displayDiagnostics=false; errorCount=0; warningCount=0; currentFile=NULL; // pointer to the current file being assembled currentFileLine=0; currentVirtualFile=NULL; // clear pointer to virtual file currentVirtualFileLine=0; maxPasses=DEFAULT_MAX_PASSES; // set default maximum number of passes sourceFileName=NULL; listFileName=NULL; defaultProcessorName=""; // by default, select no processor } static void Usage() // print some messages to stderr that tell how to use this program { fprintf(stderr,"\n"); fprintf(stderr,"%s version %s\n",programName,VERSION); fprintf(stderr,"Copyright (C) 1999-2003 Core Technologies.\n"); fprintf(stderr,"\n"); fprintf(stderr,"Cross Assembler\n"); fprintf(stderr,"\n"); fprintf(stderr,"Usage: %s [opts] sourceFile [opts]\n",programName); fprintf(stderr,"Assembly Options:\n"); fprintf(stderr,"\n"); fprintf(stderr," -o type fileName Output 'type' data to fileName\n"); fprintf(stderr," Multiple -o options are allowed, all will be processed\n"); fprintf(stderr," No output is generated by default\n"); fprintf(stderr," -I dir Append dir to the list of directories searched by include\n"); fprintf(stderr," -d label value Define a label to the given value\n"); fprintf(stderr," -P processor Choose initial processor to assemble for\n"); fprintf(stderr," -n passes Set maximum number of passes (default = %d)\n",DEFAULT_MAX_PASSES); fprintf(stderr," -l listName Create listing to listName\n"); fprintf(stderr," -w Do not report warnings\n"); fprintf(stderr," -p Print diagnostic messages to stderr\n"); fprintf(stderr,"\n"); fprintf(stderr,"\n"); fprintf(stderr,"Information Options:\n"); fprintf(stderr,"\n"); fprintf(stderr," -show_procs Dump the supported processor list\n"); fprintf(stderr," -show_types Dump the output file types list\n"); fprintf(stderr,"\n"); } static void NotEnoughArgs(char *commandName) // complain of inadequate number of arguments for a command { ReportComplaint(true,"Not enough arguments for '%s'\n",commandName); } static bool DoOutput(int *currentArg,int argc,char *argv[]) // register a type and name of output file { if((*currentArg)+3<=argc) { (*currentArg)++; if(SelectOutputFileType(argv[*currentArg],argv[(*currentArg)+1])) { (*currentArg)+=2; return(true); } } else { NotEnoughArgs(argv[*currentArg]); } return(false); } static bool DoDirectory(int *currentArg,int argc,char *argv[]) // add directory to be searched for include files { if((*currentArg)+2<=argc) { (*currentArg)++; if(AddIncludePath(argv[(*currentArg)++])) // remember this include path { return(true); } else { ReportComplaint(true,"Failed to add include path\n"); } } else { NotEnoughArgs(argv[*currentArg]); } return(false); } static bool DoDefine(int *currentArg,int argc,char *argv[]) // define a label { int theValue; if((*currentArg)+3<=argc) { (*currentArg)++; theValue=strtol(argv[(*currentArg)+1],NULL,0); if(CreateLabel(argv[*currentArg],theValue,LF_EXT_CONST,0,true)) { (*currentArg)+=2; return(true); } } else { NotEnoughArgs(argv[*currentArg]); } return(false); } static bool DoProcessor(int *currentArg,int argc,char *argv[]) // set initial processor { if((*currentArg)+2<=argc) { (*currentArg)++; defaultProcessorName=argv[(*currentArg)++]; return(true); } else { NotEnoughArgs(argv[*currentArg]); } return(false); } static bool DoPasses(int *currentArg,int argc,char *argv[]) // set initial processor { if((*currentArg)+2<=argc) { (*currentArg)++; maxPasses=strtol(argv[(*currentArg)++],NULL,0); return(true); } else { NotEnoughArgs(argv[*currentArg]); } return(false); } static bool DoListName(int *currentArg,int argc,char *argv[]) // set the name of the listing output file { if((*currentArg)+2<=argc) { (*currentArg)++; listFileName=argv[(*currentArg)++]; return(true); } else { NotEnoughArgs(argv[*currentArg]); } return(false); } static bool DoWarnings(int *currentArg,int argc,char *argv[]) // set no warning display { (*currentArg)++; displayWarnings=false; return(true); } static bool DoDebug(int *currentArg,int argc,char *argv[]) // set debug display { (*currentArg)++; displayDiagnostics=true; return(true); } static bool DoShowProcessors(int *currentArg,int argc,char *argv[]) // dump the list of supported processors { (*currentArg)++; fprintf(stderr,"\n"); fprintf(stderr,"Supported processors:\n"); fprintf(stderr,"\n"); DumpProcessorInformation(stderr); fprintf(stderr,"\n"); infoOnly=true; // tell parser that user just wanted info -- no assembly return(true); } static bool DoShowOutputTypes(int *currentArg,int argc,char *argv[]) // dump the list of supported output file types { (*currentArg)++; fprintf(stderr,"\n"); fprintf(stderr,"Supported output types:\n"); fprintf(stderr,"\n"); DumpOutputFileTypeInformation(stderr); fprintf(stderr,"\n"); infoOnly=true; // tell parser that user just wanted info -- no assembly return(true); } static bool ParseCommandLine(int argc,char *argv[]) // Parse and interpret the command line parameters // If there is a problem, complain and return false { int currentArg; int theToken; bool fail; fail=false; currentArg=1; // skip over the program name while(currentArg