Skip to content

Commit 8fedbdc

Browse files
committed
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.
1 parent c6031ae commit 8fedbdc

File tree

3 files changed

+60
-29
lines changed

3 files changed

+60
-29
lines changed

djl8086d.hxx

Lines changed: 18 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
// it has been tested for a handful of apps, so most instructions have been validated.
66
// usage:
77
// CDisassemble8086 dis;
8-
// const char * p = dis.Disassemble( uint8_t * pcode );
8+
// const char * p = dis.Disassemble( pcode );
99
// printf( "next instruction: %s\n", p );
1010
// printf( " it was %d bytes long\n", dis.BytesConsumed() );
1111
//
@@ -37,21 +37,21 @@
3737
class CDisassemble8086
3838
{
3939
private:
40-
uint8_t * _pcode; // pointer to stream of bytes to disassemble
41-
uint8_t _bc; // # of bytes consumed by most recent instruction disassembeled
42-
uint8_t _b0; // pcode[ 0 ]
43-
uint8_t _b1; // pcode[ 1 ]
44-
uint8_t _b2; // pcode[ 2 ];
45-
uint8_t _b3; // pcode[ 3 ];
46-
uint8_t _b4; // pcode[ 4 ];
47-
uint16_t _b12; // b1 and b2 as a little-endian word
48-
uint16_t _b23; // b2 and b3 as a little-endian word
49-
uint16_t _b34; // b3 and b4 as a little-endian word
50-
uint8_t _reg; // bits 5:3 of _b1
51-
uint8_t _rm; // bits 2:0 of _b1
52-
uint8_t _mod; // bits 7:6 of _b1
53-
bool _isword; // true if bit 0 of _b0 is 1
54-
bool _toreg; // true if bit 1 of _b0 is 1
40+
const uint8_t * _pcode; // pointer to stream of bytes to disassemble
41+
uint8_t _bc; // # of bytes consumed by most recent instruction disassembeled
42+
uint8_t _b0; // pcode[ 0 ]
43+
uint8_t _b1; // pcode[ 1 ]
44+
uint8_t _b2; // pcode[ 2 ];
45+
uint8_t _b3; // pcode[ 3 ];
46+
uint8_t _b4; // pcode[ 4 ];
47+
uint16_t _b12; // b1 and b2 as a little-endian word
48+
uint16_t _b23; // b2 and b3 as a little-endian word
49+
uint16_t _b34; // b3 and b4 as a little-endian word
50+
uint8_t _reg; // bits 5:3 of _b1
51+
uint8_t _rm; // bits 2:0 of _b1
52+
uint8_t _mod; // bits 7:6 of _b1
53+
bool _isword; // true if bit 0 of _b0 is 1
54+
bool _toreg; // true if bit 1 of _b0 is 1
5555

5656
// wish I could make these static without requiring an initialization elsewhere
5757

@@ -178,7 +178,7 @@ class CDisassemble8086
178178
return i_opBits[ register_val | ( _isword ? 8 : 0 ) ];
179179
} //opBits
180180

181-
void DecodeInstruction( uint8_t * pcode )
181+
void DecodeInstruction( const uint8_t * pcode )
182182
{
183183
_bc = 1;
184184
_pcode = pcode;
@@ -237,7 +237,7 @@ class CDisassemble8086
237237
uint8_t BytesConsumed() { return _bc; } // can be called after Disassemble
238238
void ClearLastIP() { _pcode = 0; } // jumps and interrupts make instruction length assert invalid
239239

240-
const char * Disassemble( uint8_t * pcode )
240+
const char * Disassemble( const uint8_t * pcode )
241241
{
242242
// 0x69 is a fake opcode used by ntvdm for syscall interrupts
243243

i8086.cxx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,7 @@ void i8086::reset_disassembler()
6969

7070
void i8086::trace_state()
7171
{
72-
uint8_t * pcode = flat_address8( cs, ip );
72+
const uint8_t * pcode = flat_address8( cs, ip );
7373
const char * pdisassemble = g_Disassembler.Disassemble( pcode );
7474
tracer.TraceQuiet( "ip %4x, opc %02x %02x %02x %02x %02x, ax %04x, bx %04x, cx %04x, dx %04x, di %04x, "
7575
"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
763763
if ( ( 0 == ip ) && ( 0 == cs ) )
764764
{
765765
tracer.Trace( "probable app bug: invoking interrupt %02x, which has a vector of 0:0\n", interrupt_num );
766-
i8086_hard_exit( "interrupt vector points to 0:0\n" );
766+
i8086_hard_exit( "fatal error: interrupt vector points to 0:0\n" );
767767
}
768768
} //op_interrupt
769769

ntvdm.cxx

Lines changed: 40 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -303,6 +303,7 @@ static bool g_InRVOS = false; // true if running in the R
303303
static uint64_t g_msAtStart = 0; // milliseconds since epoch at app start
304304
static bool g_SendControlCInt = false; // set to true when/if a ^C is detected and an interrupt should be sent
305305
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
306307

307308
// Set to true to fill dos memory allocations with patterns to detect apps that use memory they previously freed.
308309
// 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 )
66376638
}
66386639
else
66396640
{
6641+
tracer.TraceBinaryData( p, cpu.get_cx(), 4 );
66406642
tracer.Trace( " writing text to display: '" );
66416643
for ( uint16_t x = 0; x < cpu.get_cx(); x++ )
66426644
{
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 ] )
66446648
{
66456649
printf( "%c", p[ x ] );
66466650
tracer.Trace( "%c", printable( p[x] ) );
@@ -8496,6 +8500,26 @@ uint16_t LoadAsBootSector( const char * acApp, const char * acAppArgs, uint8_t l
84968500
return BSSegment;
84978501
} //LoadAsBootSector
84988502

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+
84998523
uint16_t LoadBinary( const char * acApp, const char * acAppArgs, uint8_t lenAppArgs, uint16_t segEnvironment, bool setupRegs,
85008524
uint16_t * reg_ss, uint16_t * reg_sp, uint16_t * reg_cs, uint16_t * reg_ip, bool bootSectorLoad )
85018525
{
@@ -8578,6 +8602,9 @@ uint16_t LoadBinary( const char * acApp, const char * acAppArgs, uint8_t lenAppA
85788602
}
85798603
else // EXE
85808604
{
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+
85818608
uint32_t file_size = (uint32_t) portable_filelen( file.get() );
85828609

85838610
ExeHeader head = { 0 };
@@ -9056,20 +9083,18 @@ void ValidateStateLooksOK()
90569083
cpu.trace_state();
90579084
tracer.Trace( "ntvdmCodeLo %04x, ntvdmCodeHi %04x, curCode %04x\n", ntvdmCodeLo, ntvdmCodeHi, curCode );
90589085

9059-
90609086
printf( "ntvdmCodeLo %04x, ntvdmCodeHi %04x, curCode %04x\n", ntvdmCodeLo, ntvdmCodeHi, curCode );
90619087
printf( " cs %04x, ip %04x\n", cpu.get_cs(), cpu.get_ip() );
90629088

90639089
for ( size_t i = 0; i < g_allocEntries.size(); i++ )
90649090
{
90659091
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 );
90689092
printf( " da %zd: process %04x, seg %04x, length %04x\n", i, da.seg_process, da.segment, da.para_length );
90699093
}
90709094

90719095
i8086_hard_exit( "ERROR: instruction pointer isn't in the OS or the app's memory\n" );
90729096
}
9097+
90739098
if ( !stackInRange )
90749099
{
90759100
trace_all_allocations();
@@ -9455,6 +9480,10 @@ int main( int argc, char * argv[] )
94559480
// Tick tock interrupt 0x1c just does an iret for performance.
94569481
// 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.
94579482
// 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.
94589487

94599488
uint32_t * pVectors = (uint32_t *) cpu.flat_address( 0, 0 );
94609489
uint8_t * pRoutines = cpu.flat_address8( InterruptRoutineSegment, 0 );
@@ -9471,7 +9500,8 @@ int main( int argc, char * argv[] )
94719500
routine[ 2 ] = 0xcf; // iret
94729501

94739502
// 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.
94759505
routine[ 3 ] = 0xcf; // iret
94769506

94779507
codeOffset += 4;
@@ -9566,9 +9596,9 @@ int main( int argc, char * argv[] )
95669596

95679597
unique_ptr<CSimpleThread> peekKbdThread( g_UseOneThread ? 0 : new CSimpleThread( PeekKeyboardThreadProc ) );
95689598

9569-
uint64_t total_cycles = 0; // this will be inaccurate if I8086_TRACK_CYCLES isn't defined
95709599
CPUCycleDelay delay( clockrate );
95719600
g_tAppStart = high_resolution_clock::now();
9601+
uint64_t total_cycles = 0; // this will be inaccurate if I8086_TRACK_CYCLES isn't defined
95729602

95739603
do
95749604
{
@@ -9619,10 +9649,11 @@ int main( int argc, char * argv[] )
96199649
continue;
96209650
}
96219651

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,
96239653
// 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 )
96269657
{
96279658
// on my machine this is invoked about every 72 million total_cycles if no throttle sleeping happened (tens of thousands if so)
96289659

0 commit comments

Comments
 (0)