Skip to content

Commit

Permalink
more workarounds for buggy apps generated by Intel C v4.5. Their int1…
Browse files Browse the repository at this point in the history
…c handler trashes both the stack and code it doesn't own. Plus, (x)printf writes trash to output strings for doubles.
  • Loading branch information
davidly committed Sep 19, 2024
1 parent c6031ae commit 8fedbdc
Show file tree
Hide file tree
Showing 3 changed files with 60 additions and 29 deletions.
36 changes: 18 additions & 18 deletions djl8086d.hxx
Original file line number Diff line number Diff line change
Expand Up @@ -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() );
//
Expand Down Expand Up @@ -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

Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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

Expand Down
4 changes: 2 additions & 2 deletions i8086.cxx
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down Expand Up @@ -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

Expand Down
49 changes: 40 additions & 9 deletions ntvdm.cxx
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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] ) );
Expand Down Expand Up @@ -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 )
{
Expand Down Expand Up @@ -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 };
Expand Down Expand Up @@ -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();
Expand Down Expand Up @@ -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 );
Expand All @@ -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;
Expand Down Expand Up @@ -9566,9 +9596,9 @@ int main( int argc, char * argv[] )

unique_ptr<CSimpleThread> 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
{
Expand Down Expand Up @@ -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)

Expand Down

0 comments on commit 8fedbdc

Please sign in to comment.