@@ -303,6 +303,7 @@ static bool g_InRVOS = false; // true if running in the R
303
303
static uint64_t g_msAtStart = 0 ; // milliseconds since epoch at app start
304
304
static bool g_SendControlCInt = false ; // set to true when/if a ^C is detected and an interrupt should be sent
305
305
static uint16_t g_builtInHandles[ 5 ] = { 0 , 1 , 2 , 3 , 4 }; // stdin, stdout, stderr, stdaux, stdprn are all mapped to self initially
306
+ static bool g_IsIntelC45App = false ; // true for apps generated by the Intel C compiler
306
307
307
308
// Set to true to fill dos memory allocations with patterns to detect apps that use memory they previously freed.
308
309
// These include Microsoft Pascal v4, GWBASIC, BASIC compiler v7.10, Fortran v5, and Link v5.10.
@@ -6637,10 +6638,13 @@ void handle_int_21( uint8_t c )
6637
6638
}
6638
6639
else
6639
6640
{
6641
+ tracer.TraceBinaryData ( p, cpu.get_cx (), 4 );
6640
6642
tracer.Trace ( " writing text to display: '" );
6641
6643
for ( uint16_t x = 0 ; x < cpu.get_cx (); x++ )
6642
6644
{
6643
- if ( 0x0d != p[ x ] && 0x0b != p[ x ] )
6645
+ // intel c v4.5 generates apps where sprintf and printf insert a 0xf7 instead of the final digit of a floating point number
6646
+
6647
+ if ( 0x0d != p[ x ] && 0x0b != p[ x ] && 0xf7 != p[ x ] )
6644
6648
{
6645
6649
printf ( " %c" , p[ x ] );
6646
6650
tracer.Trace ( " %c" , printable ( p[x] ) );
@@ -8496,6 +8500,26 @@ uint16_t LoadAsBootSector( const char * acApp, const char * acAppArgs, uint8_t l
8496
8500
return BSSegment;
8497
8501
} // LoadAsBootSector
8498
8502
8503
+
8504
+ bool CheckForIntelC45App ( FILE * fp )
8505
+ {
8506
+ bool isIntel = false ;
8507
+ uint32_t file_size = (uint32_t ) portable_filelen ( fp );
8508
+
8509
+ if ( file_size > 1000 )
8510
+ {
8511
+ if ( -1 != fseek ( fp, file_size - 8 , SEEK_SET ) )
8512
+ {
8513
+ char ac8[ 16 ] = {0 };
8514
+ if ( fread ( ac8, 1 , 8 , fp ) )
8515
+ isIntel = ( !memcmp ( ac8, " instruction\n\r $" , 8 ) );
8516
+ fseek ( fp, 0 , SEEK_SET );
8517
+ }
8518
+ }
8519
+
8520
+ return isIntel;
8521
+ } // CheckForIntelC45App
8522
+
8499
8523
uint16_t LoadBinary ( const char * acApp, const char * acAppArgs, uint8_t lenAppArgs, uint16_t segEnvironment, bool setupRegs,
8500
8524
uint16_t * reg_ss, uint16_t * reg_sp, uint16_t * reg_cs, uint16_t * reg_ip, bool bootSectorLoad )
8501
8525
{
@@ -8578,6 +8602,9 @@ uint16_t LoadBinary( const char * acApp, const char * acAppArgs, uint8_t lenAppA
8578
8602
}
8579
8603
else // EXE
8580
8604
{
8605
+ g_IsIntelC45App = CheckForIntelC45App ( file.get () );
8606
+ tracer.Trace ( " is this probably an Intel C 4.5 app? %s\n " , g_IsIntelC45App ? " yes" : " no" );
8607
+
8581
8608
uint32_t file_size = (uint32_t ) portable_filelen ( file.get () );
8582
8609
8583
8610
ExeHeader head = { 0 };
@@ -9056,20 +9083,18 @@ void ValidateStateLooksOK()
9056
9083
cpu.trace_state ();
9057
9084
tracer.Trace ( " ntvdmCodeLo %04x, ntvdmCodeHi %04x, curCode %04x\n " , ntvdmCodeLo, ntvdmCodeHi, curCode );
9058
9085
9059
-
9060
9086
printf ( " ntvdmCodeLo %04x, ntvdmCodeHi %04x, curCode %04x\n " , ntvdmCodeLo, ntvdmCodeHi, curCode );
9061
9087
printf ( " cs %04x, ip %04x\n " , cpu.get_cs (), cpu.get_ip () );
9062
9088
9063
9089
for ( size_t i = 0 ; i < g_allocEntries.size (); i++ )
9064
9090
{
9065
9091
DosAllocation & da = g_allocEntries[i];
9066
- uint32_t lo = flat_address ( da.segment , 0 );
9067
- uint32_t hi = flat_address ( da.segment + da.para_length , 0 );
9068
9092
printf ( " da %zd: process %04x, seg %04x, length %04x\n " , i, da.seg_process , da.segment , da.para_length );
9069
9093
}
9070
9094
9071
9095
i8086_hard_exit ( " ERROR: instruction pointer isn't in the OS or the app's memory\n " );
9072
9096
}
9097
+
9073
9098
if ( !stackInRange )
9074
9099
{
9075
9100
trace_all_allocations ();
@@ -9455,6 +9480,10 @@ int main( int argc, char * argv[] )
9455
9480
// Tick tock interrupt 0x1c just does an iret for performance.
9456
9481
// DOS uses int21, which returns Z and C flags as status codes. So use far ret 2 (not iret) so as to not trash the flags.
9457
9482
// int16 is a BIOS interrupt but it it uses the Z flag to indicate whether a character is available so it must use far ret as well.
9483
+ // Note:
9484
+ // The Intel C v4.5 C runtime executed when running apps the compiler generates has code in the int1c handler that writes to the
9485
+ // code below, breaking it. Intel apparently expects BIOS and DOS runtime code to look a certain way. This results in
9486
+ // somewhat random crashes including stack trashing.
9458
9487
9459
9488
uint32_t * pVectors = (uint32_t *) cpu.flat_address ( 0 , 0 );
9460
9489
uint8_t * pRoutines = cpu.flat_address8 ( InterruptRoutineSegment, 0 );
@@ -9471,7 +9500,8 @@ int main( int argc, char * argv[] )
9471
9500
routine[ 2 ] = 0xcf ; // iret
9472
9501
9473
9502
// note: this is strictly a workaround for Intel C v4.5 apps, which have int 1c handlers that modify their
9474
- // stack to iret back HERE instead of one byte earlier.
9503
+ // stack to iret back HERE instead of one byte earlier. This workaround is only partial and only sometimes
9504
+ // works due to the Intel C runtime trashing interrupt code.
9475
9505
routine[ 3 ] = 0xcf ; // iret
9476
9506
9477
9507
codeOffset += 4 ;
@@ -9566,9 +9596,9 @@ int main( int argc, char * argv[] )
9566
9596
9567
9597
unique_ptr<CSimpleThread> peekKbdThread ( g_UseOneThread ? 0 : new CSimpleThread ( PeekKeyboardThreadProc ) );
9568
9598
9569
- uint64_t total_cycles = 0 ; // this will be inaccurate if I8086_TRACK_CYCLES isn't defined
9570
9599
CPUCycleDelay delay ( clockrate );
9571
9600
g_tAppStart = high_resolution_clock::now ();
9601
+ uint64_t total_cycles = 0 ; // this will be inaccurate if I8086_TRACK_CYCLES isn't defined
9572
9602
9573
9603
do
9574
9604
{
@@ -9619,10 +9649,11 @@ int main( int argc, char * argv[] )
9619
9649
continue ;
9620
9650
}
9621
9651
9622
- // if interrupt 8 (timer) or 0x1c (tick tock) are hooked by an app and 55 milliseconds have elapsed,
9652
+ // If interrupt 8 (timer) or 0x1c (tick tock) are hooked by an app and 55 milliseconds have elapsed,
9623
9653
// invoke int 8, which by default then invokes int 1c.
9624
-
9625
- if ( timer_changed && ( InterruptHookedByApp ( 0x1c ) || InterruptHookedByApp ( 8 ) ) )
9654
+ // Never send timer interrupts for Intel C v4.5-generated apps because their 0x1c handler trashes both code and the stack.
9655
+
9656
+ if ( timer_changed && ( InterruptHookedByApp ( 0x1c ) || InterruptHookedByApp ( 8 ) ) && !g_IsIntelC45App )
9626
9657
{
9627
9658
// on my machine this is invoked about every 72 million total_cycles if no throttle sleeping happened (tens of thousands if so)
9628
9659
0 commit comments