/* mipsdasm.c - a MIPS opcode disassembler */ /* Version 0.1 26-DEC-00 */ /* Copyright © 2000 Sean Riddle */ /* based on 6809dasm.c */ /* Freely distributable on any medium given all copyrights are retained */ /* by the author and no charge greater than $7.00 is made for obtaining */ /* this software */ /* Please send all bug reports, update ideas and data files to: */ /* seanriddle@home.com */ /* the data files must be kept compatible across systems! */ /* usage: mipsdasm [] */ /* output to stdout, so use redirection */ /* The optional data file contains 7 types of lines: */ /* o Remarks - these are lines beginning with a semi-colon (;) */ /* they are completely ignored. */ /* o 1 ORG line - gives the origin of the code; this is the starting */ /* address to be used for the disassembly. */ /* o COMMENT lines - used to add comments to the end of lines of the */ /* disassembly. */ /* o COMMENTLINE lines - provide full-line comments to be included before */ /* a given address in the disassembly. */ /* o DATA lines - mark sections as data. These sections will not be */ /* disassembled, but dumped as hex data instead. */ /* o ASCII lines - mark sections as text. These sections will not be */ /* disassembled, but printed as text instead. */ /* o ROT13 lines - subtracts 13 from the text */ /* See sample data files (*.dat) for examples. */ /* current limitations: */ /* o number of LABEL, DATA/ASCII, COMMENT and COMMENTLINE lines determined */ /* at compile-time - see MAXLABEL, MAXDATA, MAXCOMMENT and MAXCOMMLINE */ /* o all DATA/ASCII lines in data file must be sorted in ascending */ /* address order */ /* o ditto for COMMENT and COMMENTLINE lines */ /* o if a DATA/ASCII area is preceded by what mipsdasm thinks is code */ /* that continues into the DATA/ASCII area, the data will not be marked */ /* as such, and an error will be printed. If this is the case, mark the */ /* line before the data as data also. */ /* to do: */ /* o sort comment, ascii, data lines */ /* o look at JMP and JSR addresses- set to code unless overridden */ /* in data file */ /* o perhaps a 'scan' that creates a first-guess .dat file? */ /* generate dummy labels, mark code, find ASCII, etc. */ /* Visual c++ 6 */ #include #include #include #include /* a few of my datatypes (from Amiga) */ #define TRUE (1==1) #define FALSE (!TRUE) typedef short BOOL; /* boolean quantity */ typedef unsigned char UBYTE; /* unsigned 8-bit quantity */ /* array sizes for labels, DATA/ASCII definitions and COMMENT/COMMENTLINE */ #define MAXLABEL 999 #define MAXDATA 999 /* both DATA and ASCII lines */ #define MAXCOMMENT 999 #define MAXCOMMLINE 999 /* output listing spacing */ #define TABOPNAME 22 #define TABOPERAND 28 #define TABCOMM 55 #define OPNAMETAB (TABOPNAME-1) #define OPERANDTAB (TABOPERAND-1) #define COMMTAB (TABCOMM-1) typedef struct { /* opcode structure */ UBYTE opcode; char name[10]; BOOL src; /* display flags */ BOOL trg; BOOL dst; char mode; /* I=imm, B=branch, S=shift, P=page select, C=cop bra, J=jump, Z=cop # */ } opcodeinfo; #define NUMPAGES 6 /* number of opcodes in each page */ #define NUMMAINOPS 41 #define NUMPG0OPS 28 #define NUMPG1OPS 4 #define NUMCOPOPS 9 #define NUMCOP0OPS 7 #define NUMFPOPS 26 int numops[NUMPAGES]={ NUMMAINOPS,NUMPG0OPS,NUMPG1OPS,NUMCOPOPS,NUMCOP0OPS,NUMFPOPS, }; /* these tables gleaned from a few net sources- probably some errors */ /* src trg dst mode */ opcodeinfo mainopcodes[NUMMAINOPS]={ 0,"PG0",FALSE,FALSE,FALSE,'P', 1,"PG1",FALSE,FALSE,FALSE,'P', 2,"J",FALSE,FALSE,FALSE,'J', 3,"JAL",FALSE,FALSE,FALSE,'J', 4,"BEQ",TRUE,TRUE,FALSE,'B', 5,"BNE",TRUE,TRUE,FALSE,'B', 6,"BLEZ",TRUE,FALSE,FALSE,'B', 7,"BGTZ",TRUE,FALSE,FALSE,'B', 8,"ADDI",TRUE,TRUE,FALSE,'I', 9,"ADDIU",TRUE,TRUE,FALSE,'I', 10,"SLTI",TRUE,TRUE,FALSE,'I', 11,"SLTIU",TRUE,TRUE,FALSE,'I', 12,"ANDI",TRUE,TRUE,FALSE,'I', 13,"ORI",TRUE,TRUE,FALSE,'I', 14,"XORI",TRUE,TRUE,FALSE,'I', 15,"LUI",FALSE,TRUE,FALSE,'I', 16,"COP",FALSE,FALSE,FALSE,'P', 17,"COP",FALSE,FALSE,FALSE,'P', 18,"COP",FALSE,FALSE,FALSE,'P', 19,"COP",FALSE,FALSE,FALSE,'P', 32,"LB",TRUE,TRUE,FALSE,'O', /* signed? */ 33,"LH",TRUE,TRUE,FALSE,'O', /* signed? */ 34,"LWL",TRUE,TRUE,FALSE,'O', /* signed? */ 35,"LW",TRUE,TRUE,FALSE,'O', /* signed? */ 36,"LBU",TRUE,TRUE,FALSE,'O', /* signed? */ 37,"LHU",TRUE,TRUE,FALSE,'O', /* signed? */ 38,"LWR",TRUE,TRUE,FALSE,'O', /* signed? */ 39,"LWU",TRUE,TRUE,FALSE,'O', /* signed? */ 40,"SB",TRUE,TRUE,FALSE,'O', /* signed? */ 41,"SH",TRUE,TRUE,FALSE,'O', /* signed? */ 42,"SWL",TRUE,TRUE,FALSE,'O', /* signed? */ 43,"SW",TRUE,TRUE,FALSE,'O', /* signed? */ 46,"SWR",TRUE,TRUE,FALSE,'O', /* signed? */ 48,"LWC0",TRUE,TRUE,FALSE,'O', /* signed? */ 49,"LWC1",TRUE,TRUE,FALSE,'O', /* signed? */ 50,"LWC2",TRUE,TRUE,FALSE,'O', /* signed? */ 51,"LWC3",TRUE,TRUE,FALSE,'O', /* signed? */ 56,"SWC0",TRUE,TRUE,FALSE,'O', /* signed? */ 57,"SWC1",TRUE,TRUE,FALSE,'O', /* signed? */ 58,"SWC2",TRUE,TRUE,FALSE,'O', /* signed? */ 59,"SWC3",TRUE,TRUE,FALSE,'O', /* signed? */ }; /* src trg dst mode */ opcodeinfo pg0opcodes[NUMPG0OPS]={ 0,"SLL",FALSE,TRUE,TRUE,'S', 2,"SRL",FALSE,TRUE,TRUE,'S', 3,"SRA",FALSE,TRUE,TRUE,'S', 4,"SLLV",TRUE,TRUE,TRUE,'S', 6,"SRLV",TRUE,TRUE,TRUE,'S', 7,"SRAV",TRUE,TRUE,TRUE,'S', 8,"JR",TRUE,FALSE,FALSE,'J', 9,"JALR",TRUE,FALSE,TRUE,'J', 12,"SYSCALL",FALSE,FALSE,FALSE,' ', 13,"BREAK",FALSE,FALSE,FALSE,' ', 16,"MFHI",FALSE,FALSE,TRUE,' ', 17,"MTHI",TRUE,FALSE,FALSE,' ', 18,"MFLO",FALSE,FALSE,TRUE,' ', 19,"MTLO",TRUE,FALSE,FALSE,' ', 24,"MULT",TRUE,TRUE,FALSE,' ', 25,"MULTU",TRUE,TRUE,FALSE,' ', 26,"DIV",TRUE,TRUE,FALSE,' ', 27,"DIVU",TRUE,TRUE,FALSE,' ', 32,"ADD",TRUE,TRUE,TRUE,' ', 33,"ADDU",TRUE,TRUE,TRUE,' ', 34,"SUB",TRUE,TRUE,TRUE,' ', 35,"SUBU",TRUE,TRUE,TRUE,' ', 36,"AND",TRUE,TRUE,TRUE,' ', 37,"OR",TRUE,TRUE,TRUE,' ', 38,"XOR",TRUE,TRUE,TRUE,' ', 39,"NOR",TRUE,TRUE,TRUE,' ', 42,"SLT",TRUE,TRUE,TRUE,' ', 43,"SLTU",TRUE,TRUE,TRUE,' ', }; /* src trg dst mode */ opcodeinfo pg1opcodes[NUMPG1OPS]={ 0,"BLTZ",TRUE,FALSE,FALSE,'B', 1,"BGEZ",TRUE,FALSE,FALSE,'B', 16,"BLTZAL",TRUE,FALSE,FALSE,'B', 17,"BGEZAL",TRUE,FALSE,FALSE,'B', }; /* src trg dst mode */ opcodeinfo copopcodes[NUMCOPOPS]={ 0,"MFC",FALSE,TRUE,TRUE,'Z', 1,"DMFC",FALSE,TRUE,TRUE,'Z', 2,"CFC",FALSE,FALSE,FALSE,'Z', 4,"MTC",FALSE,TRUE,TRUE,'Z', 5,"DMTC",FALSE,TRUE,TRUE,'Z', 6,"CTC",FALSE,FALSE,FALSE,'Z', 8,"BC",FALSE,FALSE,FALSE,'C', /* funky bczf/bczt */ 16,"COP0",FALSE,FALSE,FALSE,' ', 17,"FP",FALSE,FALSE,FALSE,' ', }; /* src trg dst mode */ opcodeinfo cop0opcodes[NUMCOP0OPS]={ 0,"COP0",FALSE,FALSE,FALSE,' ', 1,"TLBR",FALSE,FALSE,FALSE,' ', 2,"TLBWI",FALSE,FALSE,FALSE,' ', 6,"TLBWR",FALSE,FALSE,FALSE,' ', 8,"TLBP",FALSE,FALSE,FALSE,' ', 16,"RFE",FALSE,FALSE,FALSE,' ', 24,"ERET",FALSE,FALSE,FALSE,' ', }; /* src trg dst mode */ opcodeinfo fpopcodes[NUMFPOPS]={ 0,"ADD",TRUE,TRUE,TRUE,'F', 1,"SUB",TRUE,TRUE,TRUE,'F', 2,"MUL",TRUE,TRUE,TRUE,'F', 3,"DIV",TRUE,TRUE,TRUE,'F', 5,"ABS",TRUE,FALSE,TRUE,'F', 6,"MOV",TRUE,FALSE,TRUE,'F', 7,"NEG",TRUE,FALSE,TRUE,'F', 32,"CVT.S",TRUE,FALSE,TRUE,'F', 33,"CVT.D",TRUE,FALSE,TRUE,'F', 36,"CVT.W",TRUE,FALSE,TRUE,'F', 48,"C.F",TRUE,TRUE,FALSE,'F', 49,"C.UN",TRUE,TRUE,FALSE,'F', 50,"C.EQ",TRUE,TRUE,FALSE,'F', 51,"C.UEQ",TRUE,TRUE,FALSE,'F', 52,"C.OLT",TRUE,TRUE,FALSE,'F', 53,"C.ULT",TRUE,TRUE,FALSE,'F', 54,"C.OLE",TRUE,TRUE,FALSE,'F', 55,"C.ULE",TRUE,TRUE,FALSE,'F', 56,"C.ST",TRUE,TRUE,FALSE,'F', 57,"C.NGLE",TRUE,TRUE,FALSE,'F', 58,"C.SEQ",TRUE,TRUE,FALSE,'F', 59,"C.NGL",TRUE,TRUE,FALSE,'F', 60,"C.LT",TRUE,TRUE,FALSE,'F', 61,"C.NGE",TRUE,TRUE,FALSE,'F', 62,"C.LE",TRUE,TRUE,FALSE,'F', 63,"C.NGT",TRUE,TRUE,FALSE,'F', }; opcodeinfo *pgpointers[NUMPAGES]={ mainopcodes,pg0opcodes,pg1opcodes,copopcodes,cop0opcodes,fpopcodes }; unsigned int count; /* current program counter for disasm */ /* getword() - get 32 bits from a file, and increment the byte counter */ unsigned int getword(FILE *fp) { unsigned int w; int i; count+=4; w=0; for(i=0;(i<4)&&!feof(fp);i++) { w<<=8; w=w|getc(fp); } return(w); } #ifdef NOCONST #define const #endif const char *regs[32]={"zero","at","v0","v1","a0","a1","a2","a3", "t0","t1","t2","t3","t4","t5","t6","t7","s0","s1","s2","s3","s4","s5","s6","s7", "t8","t9","k0","k1","gp","sp","fp","ra"}; #define LABELSIZE 40 /* label structure */ struct lastruct { unsigned int lab; /* label address */ char label[LABELSIZE]; /* label text */ } *labarray=NULL; int numlab=0; /* number of labels defined */ #ifndef AMIGA /* hmmm, these aren't ANSI */ /* stricmp() - compare two strings, case insensitive */ int stricmp(const char *s1, const char *s2) { for(;toupper(*s1)==toupper(*s2);++s1,++s2) if(*s1=='\0') return(0); return((toupper(*(unsigned char *)s1)>26)&0x3f; for(i=0;(i>16)&0x1f; src=(opcode>>21)&0x1f; dst=(opcode>>11)&0x1f; if(!strcmp(mainopcodes[i].name,"PG0")) { page=1; opn=opcode&0x3f; } else if(!strcmp(mainopcodes[i].name,"PG1")) { page=2; opn=(opcode>>16)&0x1f; } else if(!strcmp(mainopcodes[i].name,"COP")) { page=3; opn=(opcode>>21)&0x1f; for(i=0;(i>21)&1==0) ddd[1]='S'; else ddd[1]='D'; src=(opcode>>11)&0x1f; dst=(opcode>>6)&0x1f; } } } else page=0; for(i=0;(i>6)&0x1f); strcat(str,out2); } else if(pgpointers[page][op1].mode=='O') { /* offset = T,D,#(S) */ for(i=strlen(out2);i<10;i++) strcat(str," "); if(pgpointers[page][op1].trg) { sprintf(out2,"%s $%s",comma,regs[trg]); strcat(str,out2); comma[0]=','; } if(pgpointers[page][op1].dst) { sprintf(out2,"%s $%s",comma,regs[dst]); strcat(str,out2); comma[0]=','; } sprintf(out2,"%s #%04X",comma,opcode&0xffff); strcat(str,out2); if(pgpointers[page][op1].src) { sprintf(out2," ($%s)",regs[src]); strcat(str,out2); comma[0]=','; } } else if(pgpointers[page][op1].mode=='B') { /* branch = D,S,T,# */ for(i=strlen(out2);i<10;i++) strcat(str," "); if(pgpointers[page][op1].dst) { sprintf(out2,"%s $%s",comma,regs[dst]); strcat(str,out2); comma[0]=','; } if(pgpointers[page][op1].src) { sprintf(out2,"%s $%s",comma,regs[src]); strcat(str,out2); comma[0]=','; } if(pgpointers[page][op1].trg) { sprintf(out2,"%s $%s",comma,regs[trg]); strcat(str,out2); comma[0]=','; } strcat(str,comma); strcat(str," "); rel=opcode&0xffff; sprintf(out2,checklabs(count+4*((rel<32768) ? rel : rel-65536),FALSE)); strcat(str,out2); if((opn==4)&&(src==trg)) xl=1; } else if(pgpointers[page][op1].mode=='F') { /* float = D,S,T,# */ strcat(str,ddd); for(i=strlen(out2);i<10;i++) strcat(str," "); if(pgpointers[page][op1].dst) { sprintf(out2,"%s $%s",comma,regs[dst]); strcat(str,out2); comma[0]=','; } if(pgpointers[page][op1].src) { sprintf(out2," ($%s)",regs[src]); strcat(str,out2); comma[0]=','; } if(pgpointers[page][op1].trg) { sprintf(out2,"%s $%s",comma,regs[trg]); strcat(str,out2); comma[0]=','; } } else if((pgpointers[page][op1].mode=='C')|| (pgpointers[page][op1].mode=='Z')) { /* cop : use Z */ zzz[0]=((opcode>>26)&0x3)|'0'; strcat(str,zzz); if(pgpointers[page][op1].mode=='C') { if(((opcode>>16)&0x1)==0) strcat(str,"F"); else strcat(str,"T"); for(i=strlen(out2)+2;i<10;i++) strcat(str," "); rel=opcode&0xffff; sprintf(out2,checklabs(count+4*((rel<32768) ? rel : rel-65536),FALSE)); strcat(str,out2); } else { for(i=strlen(out2)+1;i<10;i++) strcat(str," "); } } else if(pgpointers[page][op1].mode=='J') { /* jump = S D target */ for(i=strlen(out2);i<10;i++) strcat(str," "); if(pgpointers[page][op1].src) { sprintf(out2,"%s $%s",comma,regs[src]); strcat(str,out2); comma[0]=','; } if(pgpointers[page][op1].dst) { sprintf(out2,"%s $%s",comma,regs[dst]); strcat(str,out2); comma[0]=','; } if(!pgpointers[page][op1].src) { sprintf(out2,"%s %s",comma,checklabs(((opcode&0x3ffffff)<<2)|(count&0xf0000000),FALSE)); strcat(str,out2); } if((opn==2)||(opn==8)) xl=1; } else if(pgpointers[page][op1].mode=='I') { /* imm = D,T,S,Imm */ for(i=strlen(out2);i<10;i++) strcat(str," "); if(pgpointers[page][op1].dst) { sprintf(out2,"%s $%s",comma,regs[dst]); strcat(str,out2); comma[0]=','; } if(pgpointers[page][op1].trg) { sprintf(out2,"%s $%s",comma,regs[trg]); strcat(str,out2); comma[0]=','; } if(pgpointers[page][op1].src) { sprintf(out2,"%s $%s",comma,regs[src]); strcat(str,out2); comma[0]=','; } sprintf(out2,"%s #%04X",comma,opcode&0xffff); strcat(str,out2); } else { /* default is D,S,T */ for(i=strlen(out2);i<10;i++) strcat(str," "); if(pgpointers[page][op1].dst) { sprintf(out2,"%s $%s",comma,regs[dst]); strcat(str,out2); comma[0]=','; } if(pgpointers[page][op1].src) { sprintf(out2,"%s $%s",comma,regs[src]); strcat(str,out2); comma[0]=','; } if(pgpointers[page][op1].trg) { sprintf(out2,"%s $%s",comma,regs[trg]); strcat(str,out2); comma[0]=','; } } } } } else strcat(str,"???"); /* unknown (to me) opcode */ } #define DATA 0 /* type of data to display */ #define ASCII 1 #define ROT13 2 /* DATA/ASCII structure definition */ struct dastruct { unsigned int start; /* beginning address of DATA/ASCII */ unsigned int end; /* ending address */ unsigned short per_line; /* values to print on a line */ short type; /* DATA or ASCII ? */ } *dataarray=NULL; #define COMMENTSIZE 80 /* COMMENT/COMMENTLINE structure definition */ struct castruct { unsigned int comline; /* comment line address */ char comment[COMMENTSIZE]; /* comment text */ } *commarray=NULL,*commlinearray=NULL; char out[512],out2[80]; /* temp string buffers */ void readdatafile(char *filename, unsigned int *org,int *numlab,int *numdata,int *numcomm,int *numcommline) { char line2[256]; char *line; FILE *fp; int dataline,data,data2,per_line,i,k; unsigned int s; if(fp=fopen(filename,"r")) { /* read data file */ while(fgets(line2,255,fp)) { while(strlen(line2)&&!isprint(line2[strlen(line2)-1])) /* strip cr */ line2[strlen(line2)-1]='\0'; for(s=0;s=*numlab) (*numlab)++; } else printf("Too many labels\n"); } else if(!strnicmp(line,"DATA ",5)) { if(*numdatacommarray[*curcomm].comline+numoperands*4+4)&& (*curcomm=commarray[*curcomm].comline)&& (*curcomm0) { if(*curcommlinecommlinearray[*curcommline].comline+4)&& (*curcommline=commlinearray[*curcommline].comline)&&(line<=commlinearray[*curcommline].comline+4)&& (*curcommline0) data-=13; else data='.'; return(data); } char ascii(UBYTE data) { if((data<31)||(data>127)) data='_'; return(data); } BOOL newl=FALSE; BOOL diddata=FALSE; int dumpdata(unsigned int opcode,int *curdata,int numdata,FILE *fp, int *curcomm,int numcomm,int *curcommline,int numcommline) { int pnum,tnum; int numoperands,numbytes; int k; int numchars=4; unsigned int mdata; numbytes=(2+dataarray[*curdata].end-count); numoperands=(2+dataarray[*curdata].end-count)/4; pnum=dataarray[*curdata].per_line; /* print up to pnum bytes data */ if(dataarray[*curdata].type==DATA) { sprintf(out2,"%08X ",opcode); strcat(out,out2); if((pnum<1)||(pnum>24)) pnum=15; /* no more than 24 bytes hex data */ } else { if(dataarray[*curdata].type==ASCII) { out2[0]=ascii((opcode>>24)&0xff); out2[1]=ascii((opcode>>16)&0xff); out2[2]=ascii((opcode>>8)&0xff); out2[3]=ascii(opcode&0xff); } else { out2[0]=rot13((opcode>>24)&0xff); out2[1]=rot13((opcode>>16)&0xff); out2[2]=rot13((opcode>>8)&0xff); out2[3]=rot13(opcode&0xff); } out2[4]=0; strcat(out,out2); if((pnum<1)||(pnum>70)) pnum=40; /* no more than 70 bytes ASCII data */ } if(pnum>numbytes) pnum=numbytes; newl=FALSE; tnum=(pnum>numoperands)?numoperands-2:pnum-1; for(k=0;k=pnum)||newl) { docomment(count,curcomm,numcomm,numbytes); docommline(count-tnum,curcommline,numcommline); printf("%s\n",out); sprintf(out,"%08X: ",count); numchars=0; newl=FALSE; } numchars+=4; mdata=getword(fp); if(dataarray[*curdata].type==DATA) sprintf(out2,"%08X ",mdata); /* hex */ else { if(dataarray[*curdata].type==ASCII) { out2[0]=ascii((mdata>>24)&0xff); out2[1]=ascii((mdata>>16)&0xff); out2[2]=ascii((mdata>>8)&0xff); out2[3]=ascii(mdata&0xff); } else { out2[0]=rot13((mdata>>24)&0xff); out2[1]=rot13((mdata>>16)&0xff); out2[2]=rot13((mdata>>8)&0xff); out2[3]=rot13(mdata&0xff); } out2[4]='\0'; } strcat(out,out2); } if(*curdata1?numbytes-2:0); } void freearrays(void) { if(dataarray) free(dataarray); if(commarray) free(commarray); if(commlinearray) free(commlinearray); if(labarray) free(labarray); } BOOL mallocarrays(void) { BOOL gotit=FALSE; if(labarray=(struct lastruct *)malloc(sizeof(struct lastruct)*MAXLABEL)) if(dataarray=(struct dastruct *) malloc(sizeof(struct dastruct)*MAXDATA)) if(commarray=(struct castruct *) malloc(sizeof(struct castruct)*MAXCOMMENT)) if(commlinearray=(struct castruct *) malloc(sizeof(struct castruct)*MAXCOMMLINE)) gotit=TRUE; if(!gotit) freearrays(); return(gotit); } void main(int argc,char *argv[]) { int i,j,k; unsigned int opcode; FILE *fp; unsigned int org=0xffffffff; opcodeinfo *op; int numoperands; int numcomm=0,numdata=0,numcommline=0; int curcomm=0,curdata=0,curcommline=0; if(argc>1) { if(!stricmp(argv[1],"list")) { /* show all instructions */ for(j=0;j2) readdatafile(argv[2],&org,&numlab,&numdata,&numcomm,&numcommline); if(org!=0xffffffff) /* int PC to ORG val or 0 */ count=org; else count=0; for(k=0;kdataarray[curdata].end)&& (curdata=dataarray[curdata].start)&& /* data? */ ((count-4)<=dataarray[curdata].end)) { numoperands=dumpdata(opcode,&curdata,numdata,fp, &curcomm,numcomm,&curcommline,numcommline); i=numops[0]+1; /* skip decoding as an opcode */ } else { /* not data - search for opcode */ sprintf(out2,"%08X ",opcode); strcat(out,out2); printoperands(opcode,fp,out); numoperands=0; } docomment(count-4-numoperands,&curcomm,numcomm,numoperands+1); if(xl==2) { strcat(out,"\n"); xl=0; } if(xl==1) xl=2; if(diddata) { if(strlen(out)&&(out[strlen(out)-1]!='\n')) strcat(out,"\n"); } printf("%s\n",out); diddata=FALSE; opcode=getword(fp); } fclose(fp); } else fprintf(stderr,"Can't open file `%s'\n",argv[1]); } } else printf("Usage: `%s []'\n",argv[0]); freearrays(); }