From 8fedbdc193fe3aa1ce7760ffd890e5219f1c9f4a Mon Sep 17 00:00:00 2001 From: davidly Date: Thu, 19 Sep 2024 09:13:16 -0700 Subject: [PATCH] more workarounds for buggy apps generated by Intel C v4.5. Their int1c handler trashes both the stack and code it doesn't own. Plus, (x)printf writes trash to output strings for doubles. --- djl8086d.hxx | 36 ++++++++++++++++++------------------ i8086.cxx | 4 ++-- ntvdm.cxx | 49 ++++++++++++++++++++++++++++++++++++++++--------- 3 files changed, 60 insertions(+), 29 deletions(-) diff --git a/djl8086d.hxx b/djl8086d.hxx index 79d8e4f..1346756 100644 --- a/djl8086d.hxx +++ b/djl8086d.hxx @@ -5,7 +5,7 @@ // it has been tested for a handful of apps, so most instructions have been validated. // usage: // CDisassemble8086 dis; -// const char * p = dis.Disassemble( uint8_t * pcode ); +// const char * p = dis.Disassemble( pcode ); // printf( "next instruction: %s\n", p ); // printf( " it was %d bytes long\n", dis.BytesConsumed() ); // @@ -37,21 +37,21 @@ class CDisassemble8086 { private: - uint8_t * _pcode; // pointer to stream of bytes to disassemble - uint8_t _bc; // # of bytes consumed by most recent instruction disassembeled - uint8_t _b0; // pcode[ 0 ] - uint8_t _b1; // pcode[ 1 ] - uint8_t _b2; // pcode[ 2 ]; - uint8_t _b3; // pcode[ 3 ]; - uint8_t _b4; // pcode[ 4 ]; - uint16_t _b12; // b1 and b2 as a little-endian word - uint16_t _b23; // b2 and b3 as a little-endian word - uint16_t _b34; // b3 and b4 as a little-endian word - uint8_t _reg; // bits 5:3 of _b1 - uint8_t _rm; // bits 2:0 of _b1 - uint8_t _mod; // bits 7:6 of _b1 - bool _isword; // true if bit 0 of _b0 is 1 - bool _toreg; // true if bit 1 of _b0 is 1 + const uint8_t * _pcode; // pointer to stream of bytes to disassemble + uint8_t _bc; // # of bytes consumed by most recent instruction disassembeled + uint8_t _b0; // pcode[ 0 ] + uint8_t _b1; // pcode[ 1 ] + uint8_t _b2; // pcode[ 2 ]; + uint8_t _b3; // pcode[ 3 ]; + uint8_t _b4; // pcode[ 4 ]; + uint16_t _b12; // b1 and b2 as a little-endian word + uint16_t _b23; // b2 and b3 as a little-endian word + uint16_t _b34; // b3 and b4 as a little-endian word + uint8_t _reg; // bits 5:3 of _b1 + uint8_t _rm; // bits 2:0 of _b1 + uint8_t _mod; // bits 7:6 of _b1 + bool _isword; // true if bit 0 of _b0 is 1 + bool _toreg; // true if bit 1 of _b0 is 1 // wish I could make these static without requiring an initialization elsewhere @@ -178,7 +178,7 @@ class CDisassemble8086 return i_opBits[ register_val | ( _isword ? 8 : 0 ) ]; } //opBits - void DecodeInstruction( uint8_t * pcode ) + void DecodeInstruction( const uint8_t * pcode ) { _bc = 1; _pcode = pcode; @@ -237,7 +237,7 @@ class CDisassemble8086 uint8_t BytesConsumed() { return _bc; } // can be called after Disassemble void ClearLastIP() { _pcode = 0; } // jumps and interrupts make instruction length assert invalid - const char * Disassemble( uint8_t * pcode ) + const char * Disassemble( const uint8_t * pcode ) { // 0x69 is a fake opcode used by ntvdm for syscall interrupts diff --git a/i8086.cxx b/i8086.cxx index 689ac8f..daea585 100644 --- a/i8086.cxx +++ b/i8086.cxx @@ -69,7 +69,7 @@ void i8086::reset_disassembler() void i8086::trace_state() { - uint8_t * pcode = flat_address8( cs, ip ); + const uint8_t * pcode = flat_address8( cs, ip ); const char * pdisassemble = g_Disassembler.Disassemble( pcode ); tracer.TraceQuiet( "ip %4x, opc %02x %02x %02x %02x %02x, ax %04x, bx %04x, cx %04x, dx %04x, di %04x, " "si %04x, ds %04x, es %04x, cs %04x, ss %04x, bp %04x, sp %04x, %s, %s ; %u\n", @@ -763,7 +763,7 @@ not_inlined void i8086::op_interrupt( uint8_t interrupt_num, uint8_t instruction if ( ( 0 == ip ) && ( 0 == cs ) ) { tracer.Trace( "probable app bug: invoking interrupt %02x, which has a vector of 0:0\n", interrupt_num ); - i8086_hard_exit( "interrupt vector points to 0:0\n" ); + i8086_hard_exit( "fatal error: interrupt vector points to 0:0\n" ); } } //op_interrupt diff --git a/ntvdm.cxx b/ntvdm.cxx index 4a4ccb0..0151adf 100644 --- a/ntvdm.cxx +++ b/ntvdm.cxx @@ -303,6 +303,7 @@ static bool g_InRVOS = false; // true if running in the R static uint64_t g_msAtStart = 0; // milliseconds since epoch at app start static bool g_SendControlCInt = false; // set to true when/if a ^C is detected and an interrupt should be sent static uint16_t g_builtInHandles[ 5 ] = { 0, 1, 2, 3, 4 }; // stdin, stdout, stderr, stdaux, stdprn are all mapped to self initially +static bool g_IsIntelC45App = false; // true for apps generated by the Intel C compiler // Set to true to fill dos memory allocations with patterns to detect apps that use memory they previously freed. // 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 ) } else { + tracer.TraceBinaryData( p, cpu.get_cx(), 4 ); tracer.Trace( " writing text to display: '" ); for ( uint16_t x = 0; x < cpu.get_cx(); x++ ) { - if ( 0x0d != p[ x ] && 0x0b != p[ x ] ) + // intel c v4.5 generates apps where sprintf and printf insert a 0xf7 instead of the final digit of a floating point number + + if ( 0x0d != p[ x ] && 0x0b != p[ x ] && 0xf7 != p[ x ] ) { printf( "%c", p[ x ] ); tracer.Trace( "%c", printable( p[x] ) ); @@ -8496,6 +8500,26 @@ uint16_t LoadAsBootSector( const char * acApp, const char * acAppArgs, uint8_t l return BSSegment; } //LoadAsBootSector + +bool CheckForIntelC45App( FILE * fp ) +{ + bool isIntel = false; + uint32_t file_size = (uint32_t) portable_filelen( fp ); + + if ( file_size > 1000 ) + { + if ( -1 != fseek( fp, file_size - 8, SEEK_SET ) ) + { + char ac8[ 16 ] = {0}; + if ( fread( ac8, 1, 8, fp ) ) + isIntel = ( !memcmp( ac8, " instruction\n\r$", 8 ) ); + fseek( fp, 0, SEEK_SET ); + } + } + + return isIntel; +} //CheckForIntelC45App + uint16_t LoadBinary( const char * acApp, const char * acAppArgs, uint8_t lenAppArgs, uint16_t segEnvironment, bool setupRegs, uint16_t * reg_ss, uint16_t * reg_sp, uint16_t * reg_cs, uint16_t * reg_ip, bool bootSectorLoad ) { @@ -8578,6 +8602,9 @@ uint16_t LoadBinary( const char * acApp, const char * acAppArgs, uint8_t lenAppA } else // EXE { + g_IsIntelC45App = CheckForIntelC45App( file.get() ); + tracer.Trace( " is this probably an Intel C 4.5 app? %s\n", g_IsIntelC45App ? "yes" : "no" ); + uint32_t file_size = (uint32_t) portable_filelen( file.get() ); ExeHeader head = { 0 }; @@ -9056,20 +9083,18 @@ void ValidateStateLooksOK() cpu.trace_state(); tracer.Trace( "ntvdmCodeLo %04x, ntvdmCodeHi %04x, curCode %04x\n", ntvdmCodeLo, ntvdmCodeHi, curCode ); - printf( "ntvdmCodeLo %04x, ntvdmCodeHi %04x, curCode %04x\n", ntvdmCodeLo, ntvdmCodeHi, curCode ); printf( " cs %04x, ip %04x\n", cpu.get_cs(), cpu.get_ip() ); for ( size_t i = 0; i < g_allocEntries.size(); i++ ) { DosAllocation & da = g_allocEntries[i]; - uint32_t lo = flat_address( da.segment, 0 ); - uint32_t hi = flat_address( da.segment + da.para_length, 0 ); printf( " da %zd: process %04x, seg %04x, length %04x\n", i, da.seg_process, da.segment, da.para_length ); } i8086_hard_exit( "ERROR: instruction pointer isn't in the OS or the app's memory\n" ); } + if ( !stackInRange ) { trace_all_allocations(); @@ -9455,6 +9480,10 @@ int main( int argc, char * argv[] ) // Tick tock interrupt 0x1c just does an iret for performance. // 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. // 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. + // Note: + // The Intel C v4.5 C runtime executed when running apps the compiler generates has code in the int1c handler that writes to the + // code below, breaking it. Intel apparently expects BIOS and DOS runtime code to look a certain way. This results in + // somewhat random crashes including stack trashing. uint32_t * pVectors = (uint32_t *) cpu.flat_address( 0, 0 ); uint8_t * pRoutines = cpu.flat_address8( InterruptRoutineSegment, 0 ); @@ -9471,7 +9500,8 @@ int main( int argc, char * argv[] ) routine[ 2 ] = 0xcf; // iret // note: this is strictly a workaround for Intel C v4.5 apps, which have int 1c handlers that modify their - // stack to iret back HERE instead of one byte earlier. + // stack to iret back HERE instead of one byte earlier. This workaround is only partial and only sometimes + // works due to the Intel C runtime trashing interrupt code. routine[ 3 ] = 0xcf; // iret codeOffset += 4; @@ -9566,9 +9596,9 @@ int main( int argc, char * argv[] ) unique_ptr peekKbdThread( g_UseOneThread ? 0 : new CSimpleThread( PeekKeyboardThreadProc ) ); - uint64_t total_cycles = 0; // this will be inaccurate if I8086_TRACK_CYCLES isn't defined CPUCycleDelay delay( clockrate ); g_tAppStart = high_resolution_clock::now(); + uint64_t total_cycles = 0; // this will be inaccurate if I8086_TRACK_CYCLES isn't defined do { @@ -9619,10 +9649,11 @@ int main( int argc, char * argv[] ) continue; } - // if interrupt 8 (timer) or 0x1c (tick tock) are hooked by an app and 55 milliseconds have elapsed, + // If interrupt 8 (timer) or 0x1c (tick tock) are hooked by an app and 55 milliseconds have elapsed, // invoke int 8, which by default then invokes int 1c. - - if ( timer_changed && ( InterruptHookedByApp( 0x1c ) || InterruptHookedByApp( 8 ) ) ) + // Never send timer interrupts for Intel C v4.5-generated apps because their 0x1c handler trashes both code and the stack. + + if ( timer_changed && ( InterruptHookedByApp( 0x1c ) || InterruptHookedByApp( 8 ) ) && !g_IsIntelC45App ) { // on my machine this is invoked about every 72 million total_cycles if no throttle sleeping happened (tens of thousands if so)