/**************************************************************************** * * This code is Public Domain. * * ======================================================================== * * Description: assemble a module. * ****************************************************************************/ #include #include #include "globals.h" #include "memalloc.h" #include "input.h" #include "parser.h" #include "reswords.h" #include "tokenize.h" #include "condasm.h" #include "segment.h" #include "assume.h" #include "proc.h" #include "expreval.h" #include "hll.h" #include "context.h" #include "types.h" #include "label.h" #include "macro.h" #include "extern.h" #include "fixup.h" #include "omf.h" #include "fastpass.h" #include "listing.h" #include "msgtext.h" #include "myassert.h" #include "linnum.h" #include "cpumodel.h" #include "lqueue.h" #if DLLIMPORT #include "mangle.h" #endif #if COFF_SUPPORT #include "coff.h" #endif #if ELF_SUPPORT #include "elf.h" #endif #if BIN_SUPPORT #include "bin.h" #endif #if 1 //def __SW_BD #include jmp_buf jmpenv; #endif #ifdef __SW_BD #define EXPQUAL __stdcall #else #define EXPQUAL #endif #define USELSLINE 1 /* must match switch in listing.c! */ //#define ASM_EXT "asm" #ifdef __UNIX__ #define OBJ_EXT "o" #else #define OBJ_EXT "obj" #endif #define LST_EXT "lst" #define ERR_EXT "err" #define BIN_EXT "BIN" #define EXE_EXT "EXE" extern int_32 LastCodeBufSize; extern char *DefaultDir[NUM_FILE_TYPES]; extern const char *ModelToken[]; #if FASTMEM==0 extern void FreeLibQueue(); #endif /* parameters for output formats. order must match enum oformat */ static const struct format_options formatoptions[] = { #if BIN_SUPPORT { bin_init, BIN_DISALLOWED, "BIN" }, #endif { omf_init, OMF_DISALLOWED, "OMF" }, #if COFF_SUPPORT { coff_init, COFF32_DISALLOWED, "COFF" }, #endif #if ELF_SUPPORT { elf_init, ELF32_DISALLOWED, "ELF" }, #endif }; struct module_info ModuleInfo; unsigned int Parse_Pass; /* assembly pass */ //unsigned int GeneratedCode; /* v2.10: moved to ModuleInfo */ struct qdesc LinnumQueue; /* queue of line_num_info items */ bool write_to_file; /* write object module */ #if 0 /* for OW, it would be good to remove the CharUpperA() emulation * implemented in apiemu.c. Unfortunately, OW isn't happy with * a local, simple version of _strupr() - it still wants to * import CharUpperA. */ char * _strupr( char *src ) { char *dst; for ( dst = src; *dst; dst++ ) if ( *dst >= 'a' && *dst <= 'z' ) *dst &= ~0x20; return( src ); } #endif #if COFF_SUPPORT || PE_SUPPORT /* struct to help convert section names in COFF, ELF, PE */ struct conv_section { uint_8 len; uint_8 flags; /* see below */ const char *src; const char *dst; }; enum cvs_flags { CSF_GRPCHK = 1 }; enum conv_section_index { CSI_TEXT = 0, CSI_DATA, CSI_CONST, CSI_BSS }; /* order must match enum conv_section_index above */ static const struct conv_section cst[] = { { 5, CSF_GRPCHK, "_TEXT", ".text" }, { 5, CSF_GRPCHK, "_DATA", ".data" }, { 5, CSF_GRPCHK, "CONST", ".rdata" }, { 4, 0, "_BSS", ".bss" } }; /* order must match enum conv_section_index above */ static const enum seg_type stt[] = { SEGTYPE_CODE, SEGTYPE_DATA, SEGTYPE_DATA, SEGTYPE_BSS }; /* * translate section names (COFF+PE): * _TEXT -> .text * _DATA -> .data * CONST -> .rdata * _BSS -> .bss */ char *ConvertSectionName( const struct asym *sym, enum seg_type *pst, char *buffer ) /**********************************************************************************/ { int i; for ( i = 0; i < sizeof( cst ) / sizeof( cst[0] ); i++ ) { if ( memcmp( sym->name, cst[i].src, cst[i].len ) == 0 ) { if ( sym->name[cst[i].len] == NULLC || ( sym->name[cst[i].len] == '$' && ( cst[i].flags & CSF_GRPCHK ) ) ) { if ( pst ) { if ( i == CSI_BSS && ( (struct dsym *)sym)->e.seginfo->bytes_written != 0 ) ; /* don't set segment type to BSS if the segment contains initialized data */ else *pst = stt[i]; } if ( sym->name[cst[i].len] == NULLC ) { #if DJGPP_SUPPORT /* DJGPP won't be happy with .rdata segment name */ if( ModuleInfo.sub_format == SFORMAT_DJGPP && i == CSI_CONST ) return( ".const" ); #endif return( (char *)cst[i].dst ); } strcpy( buffer, cst[i].dst ); strcat( buffer, sym->name+cst[i].len ); return( buffer ); } } } return( sym->name ); } #endif /* Write a byte to the segment buffer. * in OMF, the segment buffer is flushed when the max. record size is reached. */ void OutputByte( unsigned char byte ) /***********************************/ { if( write_to_file == TRUE ) { uint_32 idx = CurrSeg->e.seginfo->current_loc - CurrSeg->e.seginfo->start_loc; #ifdef DEBUG_OUT if ( CurrSeg->e.seginfo->current_loc < CurrSeg->e.seginfo->start_loc ) { //_asm int 3; } #endif /**/myassert( CurrSeg->e.seginfo->current_loc >= CurrSeg->e.seginfo->start_loc ); if( Options.output_format == OFORMAT_OMF && idx >= MAX_LEDATA_THRESHOLD ) { omf_FlushCurrSeg(); idx = CurrSeg->e.seginfo->current_loc - CurrSeg->e.seginfo->start_loc; } //DebugMsg(("OutputByte: buff=%p, idx=%" I32_SPEC "X, byte=%X, codebuff[0]=%X\n", CurrSeg->e.seginfo->CodeBuffer, idx, byte, *CurrSeg->e.seginfo->CodeBuffer )); CurrSeg->e.seginfo->CodeBuffer[idx] = byte; } #if 1 /* check this in pass 1 only */ else if( CurrSeg->e.seginfo->current_loc < CurrSeg->e.seginfo->start_loc ) { DebugMsg(("OutputByte: segment start loc changed from %" I32_SPEC "Xh to %" I32_SPEC "Xh\n", CurrSeg->e.seginfo->start_loc, CurrSeg->e.seginfo->current_loc)); CurrSeg->e.seginfo->start_loc = CurrSeg->e.seginfo->current_loc; } #endif CurrSeg->e.seginfo->current_loc++; CurrSeg->e.seginfo->bytes_written++; CurrSeg->e.seginfo->written = TRUE; if( CurrSeg->e.seginfo->current_loc > CurrSeg->sym.max_offset ) CurrSeg->sym.max_offset = CurrSeg->e.seginfo->current_loc; } #if 0 /* v2.03: OutputCodeByte is obsolete */ void OutputCodeByte( unsigned char byte ) /***************************************/ { // if ( ModuleInfo.CommentDataInCode ) // omf_OutSelect( FALSE ); OutputByte( byte ); } #endif void FillDataBytes( unsigned char byte, int len ) /***********************************************/ { if ( ModuleInfo.CommentDataInCode ) omf_OutSelect( TRUE ); for( ; len; len-- ) OutputByte( byte ); } /* * this function is to output (small, <= 8) amounts of bytes which must * not be separated ( for omf, because of fixups ) */ void OutputBytes( const unsigned char *pbytes, int len, struct fixup *fixup ) /***************************************************************************/ { if( write_to_file == TRUE ) { uint_32 idx = CurrSeg->e.seginfo->current_loc - CurrSeg->e.seginfo->start_loc; #if 0 /* def DEBUG_OUT */ if ( CurrSeg->e.seginfo->current_loc < CurrSeg->e.seginfo->start_loc ) _asm int 3; #endif /**/myassert( CurrSeg->e.seginfo->current_loc >= CurrSeg->e.seginfo->start_loc ); if( Options.output_format == OFORMAT_OMF && ((idx + len) > MAX_LEDATA_THRESHOLD ) ) { omf_FlushCurrSeg(); idx = CurrSeg->e.seginfo->current_loc - CurrSeg->e.seginfo->start_loc; } if ( fixup ) store_fixup( fixup, CurrSeg, (int_32 *)pbytes ); //DebugMsg(("OutputBytes: buff=%p, idx=%" I32_SPEC "X, byte=%X\n", CurrSeg->e.seginfo->CodeBuffer, idx, *pbytes )); memcpy( &CurrSeg->e.seginfo->CodeBuffer[idx], pbytes, len ); } #if 1 /* check this in pass 1 only */ else if( CurrSeg->e.seginfo->current_loc < CurrSeg->e.seginfo->start_loc ) { DebugMsg(("OutputBytes: segment start loc changed from %" I32_SPEC "Xh to %" I32_SPEC "Xh\n", CurrSeg->e.seginfo->start_loc, CurrSeg->e.seginfo->current_loc)); CurrSeg->e.seginfo->start_loc = CurrSeg->e.seginfo->current_loc; } #endif CurrSeg->e.seginfo->current_loc += len; CurrSeg->e.seginfo->bytes_written += len; CurrSeg->e.seginfo->written = TRUE; if( CurrSeg->e.seginfo->current_loc > CurrSeg->sym.max_offset ) CurrSeg->sym.max_offset = CurrSeg->e.seginfo->current_loc; } /* set current offset in a segment (usually CurrSeg) without to write anything */ ret_code SetCurrOffset( struct dsym *seg, uint_32 value, bool relative, bool select_data ) /****************************************************************************************/ { if( relative ) value += seg->e.seginfo->current_loc; if ( Options.output_format == OFORMAT_OMF ) { if ( seg == CurrSeg ) { if ( write_to_file == TRUE ) omf_FlushCurrSeg(); /* for debugging, tell if data is located in code sections*/ if( select_data ) if ( ModuleInfo.CommentDataInCode ) omf_OutSelect( TRUE ); LastCodeBufSize = value; } seg->e.seginfo->start_loc = value; /* for -bin, if there's an ORG (relative==false) and no initialized data * has been set yet, set start_loc! * v1.96: this is now also done for COFF and ELF */ /* else if ( Options.output_format == OFORMAT_BIN && relative == FALSE ) { */ } else { if ( write_to_file == FALSE ) { if ( relative ) { #if 0 /* don't include "preceding" uninitialized data */ if( seg->e.seginfo->current_loc < seg->e.seginfo->start_loc ) seg->e.seginfo->start_loc = seg->e.seginfo->current_loc; #endif } else { if ( seg->e.seginfo->bytes_written == 0 ) seg->e.seginfo->start_loc = value; } } } seg->e.seginfo->current_loc = value; seg->e.seginfo->written = FALSE; if( seg->e.seginfo->current_loc > seg->sym.max_offset ) seg->sym.max_offset = seg->e.seginfo->current_loc; return( NOT_ERROR ); } /* write object module */ static ret_code WriteModule( struct module_info *modinfo ) /********************************************************/ { struct dsym *curr; DebugMsg(("WriteModule enter\n")); /* final checks */ /* check limit of segments */ for( curr = SymTables[TAB_SEG].head; curr; curr = curr->next ) { if ( curr->e.seginfo->Ofssize == USE16 && curr->sym.max_offset > 0x10000 ) { if ( Options.output_format == OFORMAT_OMF ) EmitErr( SEGMENT_EXCEEDS_64K_LIMIT, curr->sym.name ); else EmitWarn( 2, SEGMENT_EXCEEDS_64K_LIMIT, curr->sym.name ); } } modinfo->g.WriteModule( modinfo ); #if DLLIMPORT /* is the -Fd option given with a file name? */ if ( Options.names[OPTN_LNKDEF_FN] ) { FILE *ld; ld = fopen( Options.names[OPTN_LNKDEF_FN], "w" ); if ( ld == NULL ) { return( EmitErr( CANNOT_OPEN_FILE, Options.names[OPTN_LNKDEF_FN], ErrnoStr() ) ); } for ( curr = SymTables[TAB_EXT].head; curr != NULL ; curr = curr->next ) { DebugMsg(("WriteModule: ext=%s, isproc=%u, weak=%u\n", curr->sym.name, curr->sym.isproc, curr->sym.weak )); if ( curr->sym.isproc && ( curr->sym.weak == FALSE || curr->sym.iat_used ) && curr->sym.dll && *(curr->sym.dll->name) != NULLC ) { int size; Mangle( &curr->sym, StringBufferEnd ); size = sprintf( CurrSource, "import '%s' %s.%s\n", StringBufferEnd, curr->sym.dll->name, curr->sym.name ); if ( fwrite( CurrSource, 1, size, ld ) != size ) WriteError(); } } fclose( ld ); } #endif DebugMsg(("WriteModule exit\n")); return( NOT_ERROR ); } #define is_valid_first_char( ch ) ( isalpha(ch) || ch=='_' || ch=='@' || ch=='$' || ch=='?' || ch=='.' ) /* check name of text macros defined via -D option */ static int is_valid_identifier( char *id ) /****************************************/ { /* special handling of first char of an id: it can't be a digit, but can be a dot (don't care about ModuleInfo.dotname!). */ if( is_valid_first_char( *id ) == 0 ) return( ERROR ); id++; for( ; *id != NULLC; id++ ) { if ( is_valid_id_char( *id ) == FALSE ) return( ERROR ); } /* don't allow a single dot! */ if ( *(id-1) == '.' ) return( ERROR ); return( NOT_ERROR ); } /* add text macros defined with the -D cmdline switch */ static void add_cmdline_tmacros( void ) /****************************************/ { struct qitem *p; char *name; char *value; int len; struct asym *sym; DebugMsg(("add_cmdline_tmacros enter\n")); for ( p = Options.queues[OPTQ_MACRO]; p; p = p->next ) { DebugMsg(("add_cmdline_tmacros: found >%s<\n", p->value)); name = p->value; value = strchr( name, '=' ); if( value == NULL ) { /* v2.06: ensure that 'value' doesn't point to r/o space */ //value = ""; value = name + strlen( name ); /* use the terminating NULL */ } else { len = value - name; name = (char *)myalloca( len + 1 ); memcpy( name, p->value, len ); *(name + len) = NULLC; value++; } /* there's no check whether the name is a reserved word! */ if( is_valid_identifier( name ) == ERROR ) { DebugMsg(("add_cmdline_tmacros: name >%s< invalid\n", name )); EmitErr( SYNTAX_ERROR_EX, name ); } else { sym = SymSearch( name ); if ( sym == NULL ) { sym = SymCreate( name ); sym->state = SYM_TMACRO; } if ( sym->state == SYM_TMACRO ) { sym->isdefined = TRUE; sym->predefined = TRUE; sym->string_ptr = value; } else EmitErr( SYMBOL_ALREADY_DEFINED, name ); } } return; } /* add the include paths set by -I option */ static void add_incpaths( void ) /******************************/ { struct qitem *p; DebugMsg(("add_incpaths: enter\n")); for ( p = Options.queues[OPTQ_INCPATH]; p; p = p->next ) { AddStringToIncludePath( p->value ); } } /* this is called for every pass. * symbol table and ModuleInfo are initialized. */ static void CmdlParamsInit( int pass ) /************************************/ { DebugMsg(("CmdlParamsInit(%u) enter\n", pass)); #if BUILD_TARGET if ( pass == PASS_1 ) { struct asym *sym; char *tmp; char *p; _strupr( Options.build_target ); tmp = myalloca( strlen( Options.build_target ) + 5 ); /* null + 4 uscores */ strcpy( tmp, uscores ); strcat( tmp, Options.build_target ); strcat( tmp, uscores ); /* define target */ sym = CreateVariable( tmp, 0 ); sym->predefined = TRUE; p = NULL; if( _stricmp( Options.build_target, "DOS" ) == 0 ) { p = "__MSDOS__"; } else if( _stricmp( Options.build_target, "NETWARE" ) == 0 ) { if( ( ModuleInfo.curr_cpu & P_CPU_MASK ) >= P_386 ) { p = "__NETWARE_386__"; } else { /* do nothing ... __NETWARE__ already defined */ } } else if( _stricmp( Options.build_target, "WINDOWS" ) == 0 ) { if( ( ModuleInfo.curr_cpu & P_CPU_MASK ) >= P_386 ) { p = "__WINDOWS_386__"; } else { /* do nothing ... __WINDOWS__ already defined */ } } else if( _stricmp( Options.build_target, "QNX" ) == 0 ) { p = "__UNIX__"; } else if( _stricmp( Options.build_target, "LINUX" ) == 0 ) { p = "__UNIX__"; } if ( p ) { sym = CreateVariable( p, 0 ); sym->predefined = TRUE; } } #endif if ( pass == PASS_1 ) { char *env; /* v2.06: this is done in ModulePassInit now */ //SetCPU( Options.cpu ); add_cmdline_tmacros(); add_incpaths(); if ( Options.ignore_include == FALSE ) if ( env = getenv( "INCLUDE" ) ) AddStringToIncludePath( env ); } DebugMsg(("CmdlParamsInit exit\n")); return; } void WritePreprocessedLine( const char *string ) /**********************************************/ /* print out preprocessed source lines */ { static bool PrintEmptyLine = TRUE; const char *p; #if 0 /* v2.08: removed, obsolete */ /* filter some macro specific directives */ if ( tokenarray[0].token == T_DIRECTIVE && ( tokenarray[0].tokval == T_ENDM || tokenarray[0].tokval == T_EXITM)) return; /* don't print generated code - with one exception: if the code was generated as a result of structure initialization, then do! */ if ( GeneratedCode ) return; #endif if ( Token_Count > 0 ) { /* v2.08: don't print a leading % (this char is no longer filtered) */ for ( p = string; isspace( *p ); p++ ); printf("%s\n", *p == '%' ? p+1 : string ); PrintEmptyLine = TRUE; } else if ( PrintEmptyLine ) { PrintEmptyLine = FALSE; printf("\n"); } } /* set Masm v5.1 compatibility options */ void SetMasm510( bool value ) /***************************/ { ModuleInfo.m510 = value; ModuleInfo.oldstructs = value; /* ModuleInfo.oldmacros = value; not implemented yet */ ModuleInfo.dotname = value; ModuleInfo.setif2 = value; if ( value ) { if ( ModuleInfo.model == MODEL_NONE ) { /* if no model is specified, set OFFSET:SEGMENT */ ModuleInfo.offsettype = OT_SEGMENT; if ( ModuleInfo.langtype == LANG_NONE ) { ModuleInfo.scoped = FALSE; ModuleInfo.procs_private = TRUE; } } } return; } /* called for each pass */ static void ModulePassInit( void ) /********************************/ { enum cpu_info cpu = Options.cpu; enum model_type model = Options.model; #if DLLIMPORT struct dsym *curr; #endif DebugMsg(( "ModulePassInit() enter\n" )); /* set default values not affected by the masm 5.1 compat switch */ ModuleInfo.procs_private = FALSE; ModuleInfo.procs_export = FALSE; ModuleInfo.offsettype = OT_GROUP; ModuleInfo.scoped = TRUE; #if FASTPASS /* v2.03: don't generate the code if fastpass is active */ /* v2.08: query UseSavedState instead of StoreState */ //if ( StoreState == FALSE ) { if ( UseSavedState == FALSE ) { #endif ModuleInfo.langtype = Options.langtype; ModuleInfo.fctype = Options.fctype; #if AMD64_SUPPORT if ( ModuleInfo.sub_format == SFORMAT_64BIT ) { /* v2.06: force cpu to be at least P_64, without side effect to Options.cpu */ if ( ( cpu & P_CPU_MASK ) < P_64 ) /* enforce cpu to be 64-bit */ cpu = P_64; /* ignore -m switch for 64-bit formats. * there's no other model than FLAT possible. */ model = MODEL_FLAT; if ( ModuleInfo.langtype == LANG_NONE && Options.output_format == OFORMAT_COFF ) ModuleInfo.langtype = LANG_FASTCALL; } else #endif /* if model FLAT is to be set, ensure that cpu is compat. */ if ( model == MODEL_FLAT && ( cpu & P_CPU_MASK ) < P_386 ) /* cpu < 386? */ cpu = P_386; SetCPU( cpu ); /* table ModelToken starts with MODEL_TINY, which is index 1" */ if ( model != MODEL_NONE ) AddLineQueueX( "%r %s", T_DOT_MODEL, ModelToken[model - 1] ); #if FASTPASS } #endif SetMasm510( Options.masm51_compat ); ModuleInfo.defOfssize = USE16; ModuleInfo.ljmp = TRUE; ModuleInfo.list = Options.write_listing; ModuleInfo.cref = TRUE; ModuleInfo.listif = Options.listif; ModuleInfo.list_generated_code = Options.list_generated_code; ModuleInfo.list_macro = Options.list_macro; ModuleInfo.case_sensitive = Options.case_sensitive; ModuleInfo.convert_uppercase = Options.convert_uppercase; SymSetCmpFunc(); ModuleInfo.segorder = SEGORDER_SEQ; ModuleInfo.radix = 10; ModuleInfo.fieldalign = Options.fieldalign; #if PROCALIGN ModuleInfo.procalign = 0; #endif #if DLLIMPORT /* if OPTION DLLIMPORT was used, reset all iat_used flags */ if ( ModuleInfo.g.DllQueue ) for ( curr = SymTables[TAB_EXT].head; curr; curr = curr->next ) curr->sym.iat_used = FALSE; #endif } #if 0 /* v2.07: removed */ /* scan - and clear - global queue (EXTERNDEFs). * items which have been defined within the module * will become public. * PROTOs aren't included in the global queue. * They will become public when - and if - the PROC directive * for the symbol is met. */ static void scan_globals( void ) /******************************/ { struct qnode *curr; struct qnode *next; struct asym *sym; /* turn EXTERNDEFs into PUBLICs if defined in the module. * PROCs are handled differently - so ignore these entries here! */ /* obsolete since v2.07. * it's simpler and better to make the symbol public if it turns * from SYM_EXTERNAL to SYM_INTERNAL. * the other case, that is, the EXTERNDEF comes AFTER the definition, * is handled in ExterndefDirective() */ DebugMsg(("scan_globals: GlobalQueue=%X\n", ModuleInfo.g.GlobalQueue)); for ( curr = ModuleInfo.g.GlobalQueue.head; curr; curr = next ) { next = curr->next; sym = (struct asym *)curr->elmt; DebugMsg(("scan_globals: %s state=%u used=%u public=%u\n", sym->name, sym->state, sym->used, sym->public )); if( sym->state == SYM_INTERNAL && sym->public == FALSE && sym->isproc == FALSE ) { /* add it to the public queue */ sym->public = TRUE; QEnqueue( &ModuleInfo.g.PubQueue, curr ); DebugMsg(("scan_globals: %s added to public queue\n", sym->name )); continue; /* don't free this item! */ } LclFree( curr ); } /* the queue is empty now */ ModuleInfo.g.GlobalQueue.head = NULL; } #endif /* checks after pass one has been finished without errors */ static void PassOneChecks( void ) /*******************************/ { struct dsym *curr; struct dsym *next; struct qnode *q; struct qnode *qn; #ifdef DEBUG_OUT int cntUnusedExt = 0; #endif /* check for open structures and segments has been done inside the * END directive handling already * v2.10: now done for PROCs as well, since procedures * must be closed BEFORE segments are to be closed. */ //ProcCheckOpen(); HllCheckOpen(); CondCheckOpen(); if( ModuleInfo.EndDirFound == FALSE ) EmitError( END_DIRECTIVE_REQUIRED ); #ifdef DEBUG_OUT for ( curr = SymTables[TAB_UNDEF].head; curr; curr = curr->next ) { DebugMsg(("PassOneChecks: undefined symbol %s\n", curr->sym.name )); } #endif /* v2.04: check the publics queue. * - only internal symbols can be public. * - weak external symbols are filtered ( since v2.11 ) * - anything else is an error * v2.11: moved here ( from inside the "#if FASTPASS"-block ) * because the loop will now filter weak externals [ this * was previously done in GetPublicSymbols() ] */ for( q = ModuleInfo.g.PubQueue.head, qn = (struct qnode *)&ModuleInfo.g.PubQueue ; q; q = q->next ) { if ( q->sym->state == SYM_INTERNAL ) qn = q; else if ( q->sym->state == SYM_EXTERNAL && q->sym->weak == TRUE ) { DebugMsg(("PassOneChecks: public for weak external skipped: %s\n", q->sym->name )); qn->next = q->next; LclFree( q ); q = qn; } else { DebugMsg(("PassOneChecks: invalid public attribute for %s [state=%u weak=%u]\n", q->sym->name, q->sym->state, q->sym->weak )); #if FASTPASS SkipSavedState(); #endif break; } } #if FASTPASS if ( SymTables[TAB_UNDEF].head ) { /* to force a full second pass in case of missing symbols, * activate the next line. It was implemented to have proper * error displays if a forward reference wasn't found. * However, v1.95 final won't need this anymore, because both * filename + lineno for every line is known now in pass 2. */ /* SkipSavedState(); */ } /* check if there's an undefined segment reference. * This segment was an argument to a group definition then. * Just do a full second pass, the GROUP directive will report * the error. */ for( curr = SymTables[TAB_SEG].head; curr; curr = curr->next ) { if( curr->sym.segment == NULL ) { DebugMsg(("PassOneChecks: undefined segment %s\n", curr->sym.name )); SkipSavedState(); break; } } #if COFF_SUPPORT /* if there's an item in the safeseh list which is not an * internal proc, make a full second pass to emit a proper * error msg at the .SAFESEH directive */ for ( q = ModuleInfo.g.SafeSEHQueue.head; q; q = q->next ) { if ( q->sym->state != SYM_INTERNAL || q->sym->isproc == FALSE ) { SkipSavedState(); break; } } #endif /* scan ALIASes for COFF/ELF */ #if COFF_SUPPORT || ELF_SUPPORT if ( Options.output_format == OFORMAT_COFF #if ELF_SUPPORT || Options.output_format == OFORMAT_ELF #endif ) { for( curr = SymTables[TAB_ALIAS].head ; curr != NULL ;curr = curr->next ) { struct asym *sym; sym = curr->sym.substitute; /* check if symbol is external or public */ if ( sym == NULL || ( sym->state != SYM_EXTERNAL && ( sym->state != SYM_INTERNAL || sym->ispublic == FALSE ))) { SkipSavedState(); break; } /* make sure it becomes a strong external */ if ( sym->state == SYM_EXTERNAL ) sym->used = TRUE; } } #endif #endif /* FASTPASS */ /* scan the EXTERN/EXTERNDEF items */ for( curr = SymTables[TAB_EXT].head ; curr; curr = next ) { next = curr->next; /* v2.01: externdefs which have been "used" become "strong" */ if ( curr->sym.used ) curr->sym.weak = FALSE; /* remove unused EXTERNDEF/PROTO items from queue. */ if ( curr->sym.weak == TRUE #if DLLIMPORT && curr->sym.iat_used == FALSE #endif ) { sym_remove_table( &SymTables[TAB_EXT], curr ); #ifdef DEBUG_OUT cntUnusedExt++; #endif continue; } #if FASTMEM==0 /* v2.05: clear fixup list (used for backpatching in pass one) */ if ( curr->sym.bp_fixup ) { struct fixup *c; struct fixup *n; for( c = curr->sym.bp_fixup ; c; ) { n = c->nextbp; LclFree( c ); c = n; } curr->sym.bp_fixup = NULL; } #endif if ( curr->sym.iscomm == TRUE ) continue; /* optional alternate symbol must be INTERNAL or EXTERNAL. * COFF ( and ELF? ) also wants internal symbols to be public * ( which is reasonable, since the linker won't know private * symbols and hence will search for a symbol of that name * "elsewhere" ). */ #if FASTPASS if ( curr->sym.altname ) { if ( curr->sym.altname->state == SYM_INTERNAL ) { #if COFF_SUPPORT || ELF_SUPPORT /* for COFF/ELF, the altname must be public or external */ if ( curr->sym.altname->ispublic == FALSE && ( Options.output_format == OFORMAT_COFF #if ELF_SUPPORT || Options.output_format == OFORMAT_ELF #endif ) ) { SkipSavedState(); } #endif } else if ( curr->sym.altname->state != SYM_EXTERNAL ) { /* do not use saved state, scan full source in second pass */ SkipSavedState(); } } #endif } #ifdef DEBUG_OUT DebugMsg(("PassOneChecks: removed unused externals: %u\n", cntUnusedExt )); DebugMsg(("PassOneChecks: forward references:\n")); for( curr = SymTables[TAB_SEG].head; curr; curr = curr->next ) { int i; int j; struct asym * sym; struct fixup * fix; for ( i = 0, j = 0, sym = curr->e.seginfo->label_list; sym; sym = (struct asym *)((struct dsym *)sym)->next ) { i++; for ( fix = sym->bp_fixup; fix ; fix = fix->nextbp, j++ ); } DebugMsg(("PassOneChecks: segm=%s, labels=%u forward refs=%u\n", curr->sym.name, i, j)); } #endif if ( ModuleInfo.g.error_count == 0 ) { /* make all symbols of type SYM_INTERNAL, which aren't a constant, public. */ if ( Options.all_symbols_public ) SymMakeAllSymbolsPublic(); if ( Options.syntax_check_only == FALSE ) write_to_file = TRUE; if ( ModuleInfo.g.Pass1Checks ) ModuleInfo.g.Pass1Checks( &ModuleInfo ); } return; } /* do ONE assembly pass * the FASTPASS variant (which is default now) doesn't scan the full source * for each pass. For this to work, the following things are implemented: * 1. in pass one, save state if the first byte is to be emitted. * is the segment stack, moduleinfo state, ... * 2. once the state is saved, all preprocessed lines must be stored. * this can be done here, in OnePass, the line is in . * Preprocessed macro lines are stored in RunMacro(). * 3. for subsequent passes do * - restore the state * - read preprocessed lines and feed ParseLine() with it */ static int OnePass( void ) /************************/ { InputPassInit(); ModulePassInit(); SymPassInit( Parse_Pass ); LabelInit(); SegmentInit( Parse_Pass ); ContextInit( Parse_Pass ); ProcInit(); TypesInit(); HllInit( Parse_Pass ); MacroInit( Parse_Pass ); /* insert predefined macros */ AssumeInit( Parse_Pass ); CmdlParamsInit( Parse_Pass ); ModuleInfo.EndDirFound = FALSE; ModuleInfo.PhaseError = FALSE; //Modend = FALSE; /* LineNumber = 0; */ LinnumInit(); #ifdef DEBUG_OUT if ( Parse_Pass > PASS_1 ) { DebugMsg(("OnePass(%u) segments (current=%s):\n", Parse_Pass + 1, CurrSeg ? CurrSeg->sym.name : "NULL" )); { struct dsym *dir; for( dir = SymTables[TAB_SEG].head; dir; dir = dir->next ) { DebugMsg(("OnePass(%u): segm=%-8s typ=%X start=%8X max_ofs=%8X\n", Parse_Pass + 1, dir->sym.name, dir->e.seginfo->segtype, dir->e.seginfo->start_loc, dir->sym.max_offset )); } } } #endif /* the functions above might have written something to the line queue */ if ( is_linequeue_populated() ) RunLineQueue(); #if FASTPASS StoreState = FALSE; if ( Parse_Pass > PASS_1 && UseSavedState == TRUE ) { LineStoreCurr = RestoreState(); while ( LineStoreCurr && ModuleInfo.EndDirFound == FALSE ) { /* the source line is modified in Tokenize() if it contains a comment! */ #if USELSLINE==0 strcpy( CurrSource, LineStoreCurr->line ); #endif set_curr_srcfile( LineStoreCurr->srcfile, LineStoreCurr->lineno ); /* v2.06: list flags now initialized on the top level */ ModuleInfo.line_flags = 0; MacroLevel = ( LineStoreCurr->srcfile == 0xFFF ? 1 : 0 ); DebugMsg1(("OnePass(%u) cur/nxt=%X/%X src=%X.%u mlvl=%u: >%s<\n", Parse_Pass+1, LineStoreCurr, LineStoreCurr->next, LineStoreCurr->srcfile, LineStoreCurr->lineno, MacroLevel, LineStoreCurr->line )); ModuleInfo.CurrComment = NULL; /* v2.08: added (var is never reset because GetTextLine() isn't called) */ #if USELSLINE if ( Token_Count = Tokenize( LineStoreCurr->line, 0, ModuleInfo.tokenarray, TOK_DEFAULT ) ) #else if ( Token_Count = Tokenize( CurrSource, 0, ModuleInfo.tokenarray, TOK_DEFAULT ) ) #endif ParseLine( ModuleInfo.tokenarray ); LineStoreCurr = LineStoreCurr->next; } } else #endif { struct qitem *pq; /* v2.11: handle -Fi files here ( previously in CmdlParamsInit ) */ for ( pq = Options.queues[OPTQ_FINCLUDE]; pq; pq = pq->next ) { DebugMsg(("OnePass: force include of file: %s\n", pq->value )); if ( SearchFile( pq->value, TRUE ) ) ProcessFile( ModuleInfo.tokenarray ); } ProcessFile( ModuleInfo.tokenarray ); /* process the main source file */ } LinnumFini(); if ( Parse_Pass == PASS_1 ) PassOneChecks(); ClearSrcStack(); return( 1 ); } #if BUILD_TARGET /* * from WASM : get os-specific xxx_INCLUDE environment variable. * if set, add string to include path. */ static void get_os_include( void ) /********************************/ { char *env; char *tmp; /* add OS_include to the include path */ tmp = myalloca( strlen( Options.build_target ) + 10 ); strcpy( tmp, Options.build_target ); strcat( tmp, "_INCLUDE" ); env = getenv( tmp ); if( env != NULL ) { AddStringToIncludePath( env ); } } #endif static void get_module_name( void ) /*********************************/ { //char dummy[_MAX_EXT]; char *p; /* v2.08: prefer name given by -nm option */ if ( Options.names[OPTN_MODULE_NAME] ) { strncpy( ModuleInfo.name, Options.names[OPTN_MODULE_NAME], sizeof( ModuleInfo.name ) ); ModuleInfo.name[ sizeof( ModuleInfo.name ) - 1] = NULLC; } else { /* v2.12: _splitpath()/_makepath() removed */ const char *fn = GetFNamePart( CurrFName[ASM] ); char *ext = GetExtPart( fn ); memcpy( ModuleInfo.name, fn, ext - fn ); ModuleInfo.name[ ext - fn ] = NULLC; //_splitpath( CurrFName[ASM], NULL, NULL, ModuleInfo.name, dummy ); } _strupr( ModuleInfo.name ); /* the module name must be a valid identifier, because it's used * as part of a segment name in certain memory models. */ for( p = ModuleInfo.name; *p; ++p ) { if( !( isalnum( *p ) || ( *p == '_' ) || ( *p == '$' ) || ( *p == '@' ) || ( *p == '?') ) ) { /* it's not a legal character for a symbol name */ *p = '_'; } } /* first character can't be a digit either */ if( isdigit( ModuleInfo.name[0] ) ) { ModuleInfo.name[0] = '_'; } } /* called by AssembleInit(), once per source module. * symbol table has been initialized here. */ static void ModuleInit( void ) /****************************/ { ModuleInfo.sub_format = Options.sub_format; ModuleInfo.fmtopt = &formatoptions[Options.output_format]; ModuleInfo.CommentDataInCode = (Options.output_format == OFORMAT_OMF && Options.no_comment_data_in_code_records == FALSE); ModuleInfo.g.error_count = 0; ModuleInfo.g.warning_count = 0; ModuleInfo.model = MODEL_NONE; /* ModuleInfo.distance = STACK_NONE; */ ModuleInfo.ostype = OPSYS_DOS; ModuleInfo.emulator = (Options.floating_point == FPO_EMULATION); //ModuleInfo.flatgrp_idx = 0; get_module_name(); /* set ModuleInfo.name */ /* v2.06: ST_PROC has been removed */ //SimpleType[ST_PROC].mem_type = MT_NEAR; memset( SymTables, 0, sizeof( SymTables[0] ) * TAB_LAST ); ModuleInfo.fmtopt->init( &ModuleInfo ); return; } static void ReswTableInit( void ) /*******************************/ { ResWordsInit(); if ( Options.output_format == OFORMAT_OMF ) { /* DebugMsg(("InitAsm: disable IMAGEREL+SECTIONREL\n")); */ /* for OMF, IMAGEREL and SECTIONREL are disabled */ #if IMAGERELSUPP DisableKeyword( T_IMAGEREL ); #endif #if SECTIONRELSUPP DisableKeyword( T_SECTIONREL ); #endif } if ( Options.strict_masm_compat == TRUE ) { DebugMsg(("ReswTableInit: disable INCBIN + FASTCALL keywords\n")); DisableKeyword( T_INCBIN ); DisableKeyword( T_FASTCALL ); } return; } static void open_files( void ) /****************************/ { /* open ASM file */ DebugMsg(("open_files() enter\n" )); //memset( CurrFile, 0, sizeof( CurrFile ) ); /* CurrFile[ASM] = fopen( CurrFName[ASM], "r" ); */ CurrFile[ASM] = fopen( CurrFName[ASM], "rb" ); if( CurrFile[ASM] == NULL ) { DebugMsg(("open_files(): cannot open source file, fopen(\"%s\") failed\n", CurrFName[ASM] )); Fatal( CANNOT_OPEN_FILE, CurrFName[ASM], ErrnoStr() ); } /* open OBJ file */ if ( Options.syntax_check_only == FALSE ) { CurrFile[OBJ] = fopen( CurrFName[OBJ], "wb" ); if( CurrFile[OBJ] == NULL ) { DebugMsg(("open_files(): cannot open object file, fopen(\"%s\") failed\n", CurrFName[OBJ] )); Fatal( CANNOT_OPEN_FILE, CurrFName[OBJ], ErrnoStr() ); } DebugMsg(("open_files(): output, fopen(\"%s\") ok\n", CurrFName[OBJ] )); } if( Options.write_listing ) { CurrFile[LST] = fopen( CurrFName[LST], "wb" ); if ( CurrFile[LST] == NULL ) Fatal( CANNOT_OPEN_FILE, CurrFName[LST], ErrnoStr() ); } return; } void close_files( void ) /**********************/ { /* v2.11: no fatal errors anymore if fclose() fails. * That's because Fatal() may cause close_files() to be * reentered and thus cause an endless loop. */ /* close ASM file */ if( CurrFile[ASM] != NULL ) { if( fclose( CurrFile[ASM] ) != 0 ) EmitErr( CANNOT_CLOSE_FILE, CurrFName[ASM], errno ); CurrFile[ASM] = NULL; } /* close OBJ file */ if ( CurrFile[OBJ] != NULL ) { if ( fclose( CurrFile[OBJ] ) != 0 ) EmitErr( CANNOT_CLOSE_FILE, CurrFName[OBJ], errno ); CurrFile[OBJ] = NULL; } /* delete the object module if errors occured */ if ( Options.syntax_check_only == FALSE && ModuleInfo.g.error_count > 0 ) { remove( CurrFName[OBJ] ); } if( CurrFile[LST] != NULL ) { fclose( CurrFile[LST] ); CurrFile[LST] = NULL; } /* close ERR file */ if ( CurrFile[ERR] != NULL ) { fclose( CurrFile[ERR] ); CurrFile[ERR] = NULL; } else if ( CurrFName[ERR] ) /* nothing written, delete any existing ERR file */ remove( CurrFName[ERR] ); return; } /* get default file extension for error, object and listing files */ static char *GetExt( int type ) /*****************************/ { switch ( type ) { case OBJ: #if BIN_SUPPORT if ( Options.output_format == OFORMAT_BIN ) #if MZ_SUPPORT || PE_SUPPORT if ( Options.sub_format == SFORMAT_MZ #if PE_SUPPORT || Options.sub_format == SFORMAT_PE #endif ) return( EXE_EXT ); else #endif return( BIN_EXT ); #endif return( OBJ_EXT ); case LST: return( LST_EXT ); case ERR: return( ERR_EXT ); } return( NULL ); } /* set filenames for .obj, .lst and .err * in: * name: assembly source name * DefaultDir[]: default directory part for .obj, .lst and .err * in: * CurrFName[] for .obj, .lst and .err ( may be NULL ) * v2.12: _splitpath()/_makepath() removed. */ static void SetFilenames( const char *name ) /******************************************/ { int i; const char *fn; char *ext; char path[ FILENAME_MAX ]; DebugMsg(("SetFilenames(\"%s\") enter\n", name )); /* set CurrFName[ASM] */ CurrFName[ASM] = LclAlloc( strlen( name ) + 1 ); strcpy( CurrFName[ASM], name ); /* set [OBJ], [ERR], [LST] */ fn = GetFNamePart( name ); for ( i = ASM+1; i < NUM_FILE_TYPES; i++ ) { if( Options.names[i] == NULL ) { path[0] = NULLC; if ( DefaultDir[i]) strcpy( path, DefaultDir[i] ); strcat( path, fn ); ext = GetExtPart( path ); *ext++ = '.'; strcpy( ext, GetExt( i ) ); } else { /* filename has been set by cmdline option -Fo, -Fl or -Fr */ const char *fn2; strcpy( path, Options.names[i] ); fn2 = GetFNamePart( path ); if( *fn2 == NULLC ) strcpy( (char *)fn2, fn ); ext = GetExtPart( fn2 ); if( *ext == NULLC ) { *ext++ = '.'; strcpy( ext, GetExt( i ) ); } } DebugMsg(("SetFilenames: i=%u >%s<\n", i, path )); CurrFName[i] = LclAlloc( strlen( path ) + 1 ); strcpy( CurrFName[i], path ); } return; } /* init assembler. called once per module */ static void AssembleInit( const char *source ) /********************************************/ { DebugMsg(("AssembleInit(\"%s\") enter\n", source )); MemInit(); //start_label = NULL; //start_displ = 0; write_to_file = FALSE; //GeneratedCode = 0; LinnumQueue.head = NULL; SetFilenames( source ); #if FASTPASS FastpassInit(); #endif open_files(); #if BUILD_TARGET get_os_include(); #endif ReswTableInit(); SymInit(); InputInit(); ModuleInit(); CondInit(); ExprEvalInit(); LstInit(); DebugMsg(("AssembleInit() exit\n")); return; } #ifdef DEBUG_OUT void DumpInstrStats( void ); #endif /* called once per module. AssembleModule() cleanup */ static void AssembleFini( void ) /******************************/ { int i; SegmentFini(); SymFini(); ResWordsFini(); #ifdef DEBUG_OUT DumpInstrStats(); MacroFini(); #endif FreePubQueue(); #if FASTMEM==0 FreeLibQueue(); ContextFini(); HllFini(); #endif InputFini(); close_files(); #if FASTPASS #if FASTMEM==0 FreeLineStore(); #endif #endif for ( i = 0; i < NUM_FILE_TYPES; i++ ) { LclFree( CurrFName[i] ); /* v2.05: make sure the pointer for ERR is cleared */ CurrFName[i] = NULL; } MemFini(); return; } /* AssembleModule() assembles one source file */ int EXPQUAL AssembleModule( const char *source ) /**********************************************/ { uint_32 prev_written = -1; uint_32 curr_written; int starttime; int endtime; struct dsym *seg; DebugMsg(("AssembleModule(\"%s\") enter\n", source )); memset( &ModuleInfo, 0, sizeof(ModuleInfo) ); DebugCmd( ModuleInfo.cref = TRUE ); /* enable debug displays */ #if 1 //def __SW_BD /* fatal errors during assembly won't terminate the program, * just the assembly step.! */ if ( setjmp( jmpenv ) ) { if ( ModuleInfo.g.src_stack ) ClearSrcStack(); /* avoid memory leaks! */ goto done; } #endif AssembleInit( source ); starttime = clock(); #if 0 /* 1=trigger a protection fault */ seg = NULL; seg->sym.state = SYM_UNDEFINED; #endif for( Parse_Pass = PASS_1; ; Parse_Pass++ ) { DebugMsg(( "*************\npass %u\n*************\n", Parse_Pass + 1 )); OnePass(); if( ModuleInfo.g.error_count > 0 ) { DebugMsg(("AssembleModule(%u): errorcnt=%u\n", Parse_Pass + 1, ModuleInfo.g.error_count )); break; } /* calculate total size of segments */ for ( curr_written = 0, seg = SymTables[TAB_SEG].head; seg ; seg = seg->next ) { /* v2.04: use instead of * (the latter is not always reliable due to backpatching). */ //curr_written += seg->e.seginfo->bytes_written; curr_written += seg->sym.max_offset; DebugMsg(("AssembleModule(%u): segm=%-8s start=%8" I32_SPEC "X max_ofs=%8" I32_SPEC "X written=%" I32_SPEC "X\n", Parse_Pass + 1, seg->sym.name, seg->e.seginfo->start_loc, seg->sym.max_offset, seg->e.seginfo->bytes_written )); } /* if there's no phase error and size of segments didn't change, we're done */ DebugMsg(("AssembleModule(%u): PhaseError=%u, prev_written=%" I32_SPEC "X, curr_written=%" I32_SPEC "X\n", Parse_Pass + 1, ModuleInfo.PhaseError, prev_written, curr_written)); if( !ModuleInfo.PhaseError && prev_written == curr_written ) break; #ifdef DEBUG_OUT if ( curr_written < prev_written && prev_written != -1 ) { printf( "size shrank from %" I32_SPEC "X to %" I32_SPEC "X in pass %u\n", prev_written, curr_written, Parse_Pass + 1 ); } #endif DebugMsg(("AssembleModule(%u): prepare for next pass\n", Parse_Pass + 1)); prev_written = curr_written; if ( Parse_Pass % 200 == 199 ) EmitWarn( 2, ASSEMBLY_PASSES, Parse_Pass+1 ); #ifdef DEBUG_OUT if ( Options.max_passes && Parse_Pass == (Options.max_passes - 1) ) break; #endif if ( Options.line_numbers ) { #if COFF_SUPPORT if ( Options.output_format == OFORMAT_COFF ) { for( seg = SymTables[TAB_SEG].head; seg; seg = seg->next ) { if ( seg->e.seginfo->LinnumQueue ) QueueDeleteLinnum( seg->e.seginfo->LinnumQueue ); seg->e.seginfo->LinnumQueue = NULL; } } else { #endif QueueDeleteLinnum( &LinnumQueue ); LinnumQueue.head = NULL; #if COFF_SUPPORT } #endif } /* set file position of ASM and LST files for next pass */ rewind( CurrFile[ASM] ); if ( write_to_file && Options.output_format == OFORMAT_OMF ) omf_set_filepos(); #if FASTPASS if ( UseSavedState == FALSE && CurrFile[LST] ) { #else if ( CurrFile[LST] ) { #endif rewind( CurrFile[LST] ); LstInit(); } } /* end for() */ if ( ( Parse_Pass > PASS_1 ) && write_to_file ) WriteModule( &ModuleInfo ); if ( ModuleInfo.pCodeBuff ) { LclFree( ModuleInfo.pCodeBuff ); } DebugMsg(("AssembleModule: finished, cleanup\n")); /* Write a symbol listing file (if requested) */ LstWriteCRef(); endtime = clock(); /* is in ms already */ sprintf( CurrSource, MsgGetEx( MSG_ASSEMBLY_RESULTS ), GetFName( ModuleInfo.srcfile )->fname, GetLineNumber(), Parse_Pass + 1, endtime - starttime, ModuleInfo.g.warning_count, ModuleInfo.g.error_count); if ( Options.quiet == FALSE ) printf( "%s\n", CurrSource ); if ( CurrFile[LST] ) { LstPrintf( CurrSource ); LstNL(); } #if 1 //def __SW_BD done: #endif AssembleFini(); DebugMsg(("AssembleModule exit\n")); return( ModuleInfo.g.error_count == 0 ); }