Beispiel #1
0
// Handle an interrupt from the VM.
void DebuggerProxy::interrupt(CmdInterrupt &cmd) {
  TRACE_RB(2, "DebuggerProxy::interrupt\n");
  // Make any breakpoints that have passed breakable again.
  setBreakableForBreakpointsNotMatching(cmd);

  // At this point we have an interrupt, but we don't know if we're on the
  // thread the proxy considers "current".
  // NB: BreakPointReached really means we've got control of a VM thread from
  // the opcode hook. This could be for a breakpoint, stepping, etc.

  // Wait until this thread is the one this proxy wants to debug.
  if (!blockUntilOwn(cmd, true)) return;

  // We know we're on the "current" thread, so we can process any active flow
  // command, stop if we're at a breakpoint, handle other interrupts, etc.
  if (checkFlowBreak(cmd)) {
    // We've hit a breakpoint and now need to make sure that breakpoints
    // won't be hit again for this site until control leaves this site.
    // (Breakpoints can still get hit if control reaches this site during
    // a call that is part of this site because the flags are stacked.)
    unsetBreakableForBreakpointsMatching(cmd);
    while (true) {
      try {
        // We're about to send the client an interrupt and start
        // waiting for commands back from it. Disable signal polling
        // during this time, since our protocol requires that only one
        // thread talk to the client at a time.
        disableSignalPolling();
        SCOPE_EXIT { enableSignalPolling(); };
        processInterrupt(cmd);
      } catch (const DebuggerException &e) {
        TRACE(2, "DebuggerException from processInterrupt!\n");
        switchThreadMode(Normal);
        throw;
      } catch (...) {
        TRACE(2, "Unknown exception from processInterrupt!\n");
        assertx(false); // no other exceptions should be seen here
        switchThreadMode(Normal);
        throw;
      }
      if (cmd.getInterruptType() == PSPEnded) break;
      if (!m_newThread) break; // we're not switching threads
      switchThreadMode(Normal, m_newThread->m_id);
      m_newThread.reset();
      blockUntilOwn(cmd, false);
    }
  }

  if ((m_threadMode == Normal) || (cmd.getInterruptType() == PSPEnded)) {
    // If the thread mode is Normal we let other threads with
    // interrupts go ahead and process them. We also do this when the
    // thread is at PSPEnded because the thread is done.
    switchThreadMode(Normal);
  }
}
Beispiel #2
0
static void runScreenSimulation(int16_t *synthBuffer, uint32_t cyclesPerScreen, uint16_t samplesPerCall, int16_t **synthTraceBufs) {
	setupVolumeHack();

	// cpu cycles used during processing
	int32_t irqCycles= 0, nmiCycles= 0, mainProgCycles= 0;	// only mainProgCycles can become negative..

	// reminder: timings of IRQ and NMI are calculated as if these where independent (which is
	// of course flawed). A proper premptive scheduling is not simulated.

	int32_t availableIrqCycles= cyclesPerScreen;	// cpu cycles still available for processing (limitation: only the waiting time is currently considered..)
	int32_t availableNmiCycles= cyclesPerScreen;

	uint32_t synthPos= 0;	// start time for the samples synthesized from SID settings (measured in cycles)						

	/*
	* process NMI and IRQ interrupts in their order of appearance - and as long as they fit into this screen
	*/	
	uint32_t currentIrqTimer= forwardToNextInterrupt(getIrqTimerMode(CIA1), CIA1, availableIrqCycles);
	
	// FIXME: the assumption that only RSID uses NMI meanwhile proved to be wrong (see MicroProse_Soccer_V1.sid 
	// tracks >4) .. also an IRQ run may also update the NMI schedule! flawed: ciaForwardToNextInterrupt 
	// calculation does NOT consider the time when the timer is actually started but only the time that some 
	// interrupt returns (which will lead to distortions)
	
	uint32_t currentNmiTimer= envIsRSID() ? ciaForwardToNextInterrupt(CIA2, availableNmiCycles) : NO_INT;	
	
	// KNOWN LIMITATION: ideally all 4 timers should be kept in sync - not just A/B within the same unit! 
	// however progs that actually rely on multiple timers (like Vicious_SID_2-Carmina_Burana.sid) probably 
	// need timer info that is continuously updated - so that effort would only make sense in a full fledged 
	// cycle-by-cycle emulator.
	
	uint8_t hack= vicIsIrqActive() && isCiaActive(CIA1);
	
	uint32_t tNmi= currentNmiTimer;		// NMI simulation progress in cycles
	uint32_t tIrq= currentIrqTimer;		// IRQ simulation progress in cycles
	uint32_t tDone= 0;
	
	uint16_t mainProgStep= 100;	// periodically give the main prog a chance to run.. 
	uint16_t mainProgStart= mainProgStep;

	if (0 || (envIsRSID() && (currentIrqTimer == NO_INT) && (currentNmiTimer == NO_INT))) {
		_mainLoopOnlyMode= 1;
		mainProgStart= 0; 	// this might be the better choice in any case.. but to avoid regression testing lets use it here for now..
	} else {
		_mainLoopOnlyMode= 0;

		// Wonderland_XII-Digi_part_4 has high interrupt rate..
		#define ABORT_FUSE 400		// better safe than sorry.. no need to crash the player with an endless loop

		uint16_t fuse;
		for (fuse= 0; (fuse<ABORT_FUSE) && ((currentIrqTimer != NO_INT) || (currentNmiTimer != NO_INT)) ; fuse++) {
			// periodically give unused cycles to main program (needed, e.g. by THCM's stuff)	
			
			if (tDone > mainProgStart) {
				// todo: the handing of main prog cycles is rather flawed (timing for main code that would normally run before 
				// 'mainProgStep'). In an alternative "improved" impl I ran "main" directly before "processInterrupt" calls below - letting 
				// "main" use all cycles unused up to that moment. also I had added interruption for "LDA#00; BEQ .." based endless main-loop 
				// logic (which would require IRQ logic to break out) to avoid unnecessary cycle usage.. while those changes work fine for 
				// THCM's recent stuff they broke stuff like "Arkanoid", etc (so I rolled them back for now..) 
				// -> now that the D012 handling has been improved I might give it another try..
				
				int32_t availableMainCycles= tDone - (nmiCycles + irqCycles + mainProgCycles);
				
				if (availableMainCycles > 0) {
					processMain(cyclesPerScreen, mainProgStart, availableMainCycles);

					mainProgCycles+= availableMainCycles;				
				}			
				mainProgStart= tDone + mainProgStep;
			}
			
			// FIXME: better allow NMI to interrupt IRQ, i.e. set respective cycle-limit
			// accordingly - and then resume the IRQ later. see Ferrari_Formula_One.sid
			
			if (!isIrqNext(currentIrqTimer, currentNmiTimer, tIrq, tNmi)) {		// handle next NMI
				tDone= tNmi;
				// problem: songs like Arcade_Classics re-set the d418 volume nowhere else but in their
				// NMI digi-player: when only used for the short intervals between NMIs the respective changing
				// settings do not seem to pose any problems for the rendering of the regular SID output 
				// (or are even intentionally creating some kind of tremolo) but when SID output rendering 
				// is performed for longer intervals (e.g. like is done here) then "wrong" settings are also 
				// used for these longer intervals which may cause audible clicking/pauses in the output.

				// if the handled NMIs were preemtively scheduled/properly timed, then renderSynth() could 
				// be performed also for these shorter intervals.. but unfortunately they are not and respective 
				// rendering cannot be done here, i.e. the IRQ based rendering then has no clue regarding the 
				// validity/duration of respectice NMI-volume infos..
				
				nmiCycles+= processInterrupt(NMI_OFFSET_MASK, getNmiVector(), tNmi, cyclesPerScreen);
					
				availableNmiCycles-= currentNmiTimer;		

				if (availableNmiCycles <= 0) {
					availableNmiCycles= 0;		// see unsigned use below
				}
				currentNmiTimer= ciaForwardToNextInterrupt(CIA2, availableNmiCycles);
				
				tNmi+= currentNmiTimer;		// will be garbage on last run..		
			} else {															// handle next IRQ
				tDone= tIrq;
				renderSynth(synthBuffer, cyclesPerScreen, samplesPerCall, synthPos, tIrq, synthTraceBufs);
				synthPos= tIrq;
				
				if (hack) {
					// in case CIA1 has also been configured to trigger IRQs, our current "raster IRQ or timer IRQ" impl 
					// is obviously flawed.. this hack will fix simple scenarios like Cycles.sid
					ciaSignalUnderflow(CIA1, TIMER_A); 
				}
				
				// FIXME: multi-frame IRQ handling would be necessary to deal with songs like: Musik_Run_Stop.sid	
				uint32_t usedCycles= processInterrupt(IRQ_OFFSET_MASK, getIrqVector(), tIrq, _irqTimeout);
				// flaw: IRQ might have switched off NMI, e.g. Vicious_SID_2-Blood_Money.sid
				if (usedCycles >=_irqTimeout) { // IRQ gets aborted due to a timeout
					if (cpuIrqFlag()) {
						// this IRQ handler does not want to be interrupted.. (either the code got stuck due to some impossible
						// condition - e.g. waiting for a timer which we don't update - or it is an endless IRQ routine that needs to 
						// continue..)						
					} else {
						// this IRQ handler was intentionally waiting to get interrupted - and it would normally not have burned
						// the same amount of cycles (we have no chance to get the timing right here..)
						usedCycles= 1;
					}					
				}
				
				irqCycles+= usedCycles;
				availableIrqCycles-= currentIrqTimer;
			
				if (availableIrqCycles <= 0) {
					availableIrqCycles= 0;		// see unsigned use below
				}
				
				currentIrqTimer= forwardToNextInterrupt(getIrqTimerMode(CIA1), CIA1, availableIrqCycles);
				tIrq+= currentIrqTimer;		// will be garbage on last run..
				
				if (envIsTimerDrivenPSID() && (currentIrqTimer != NO_INT))  {
					if(usedCycles > currentIrqTimer) {
						currentIrqTimer= NO_INT;	// hack for Eye_of_the_Tiger
					}
				}
				// some dumbshit timer-PSIDs do not ackn IRQ (MasterComposer rips & Automatas.sid)
				if(envIsTimerDrivenPSID())  { ciaReadMem(0xdc0d); }
			}
		}

#ifdef DEBUG		
		if (fuse >= ABORT_FUSE) {
			EM_ASM_({ console.log('fuse blown: ' + $0); }, fuse);
			EM_ASM_({ console.log('last IRQ timer: ' + $0); }, currentIrqTimer);
Beispiel #3
0
/*----------------------------------------------------------------------------*/
void PIN_INT7_ISR(void)
{
  processInterrupt(7);
}
Beispiel #4
0
/*----------------------------------------------------------------------------*/
void PIN_INT0_ISR(void)
{
  processInterrupt(0);
}
Beispiel #5
0
/*----------------------------------------------------------------------------*/
void PIN_INT5_ISR(void)
{
  processInterrupt(5);
}
Beispiel #6
0
/*----------------------------------------------------------------------------*/
void PIN_INT6_ISR(void)
{
  processInterrupt(6);
}
Beispiel #7
0
/*----------------------------------------------------------------------------*/
void PIN_INT4_ISR(void)
{
  processInterrupt(4);
}
Beispiel #8
0
/*----------------------------------------------------------------------------*/
void PIN_INT3_ISR(void)
{
  processInterrupt(3);
}
Beispiel #9
0
/*----------------------------------------------------------------------------*/
void PIN_INT2_ISR(void)
{
  processInterrupt(2);
}
Beispiel #10
0
/*----------------------------------------------------------------------------*/
void PIN_INT1_ISR(void)
{
  processInterrupt(1);
}
// Handle an interrupt from the VM.
void DebuggerProxy::interrupt(CmdInterrupt &cmd) {
  TRACE(2, "DebuggerProxy::interrupt\n");
  changeBreakPointDepth(cmd);

  if (cmd.getInterruptType() == BreakPointReached) {
    if (!needInterrupt()) return;

    // NB: stepping is represented as a BreakPointReached interrupt.

    // Modify m_lastLocFilter to save current location. This will short-circuit
    // the work done up in phpDebuggerOpcodeHook() and ensure we don't break on
    // this line until we're completely off of it.
    InterruptSite *site = cmd.getSite();
    if (g_vmContext->m_lastLocFilter) {
      g_vmContext->m_lastLocFilter->clear();
    } else {
      g_vmContext->m_lastLocFilter = new VM::PCFilter();
    }
    if (debug && Trace::moduleEnabled(Trace::bcinterp, 5)) {
      Trace::trace("prepare source loc filter\n");
      const VM::OffsetRangeVec& offsets = site->getCurOffsetRange();
      for (VM::OffsetRangeVec::const_iterator it = offsets.begin();
           it != offsets.end(); ++it) {
        Trace::trace("block source loc in %s:%d: unit %p offset [%d, %d)\n",
                     site->getFile(), site->getLine0(),
                     site->getUnit(), it->m_base, it->m_past);
      }
    }
    g_vmContext->m_lastLocFilter->addRanges(site->getUnit(),
                                            site->getCurOffsetRange());
    // If the breakpoint is not to be processed, we should continue execution.
    BreakPointInfoPtr bp = getBreakPointAtCmd(cmd);
    if (bp) {
      if (!bp->breakable(getRealStackDepth())) {
        return;
      } else {
        bp->unsetBreakable(getRealStackDepth());
      }
    }
  }

  // Wait until this thread is the thread this proxy wants to debug.
  // NB: breakpoints and control flow checks happen here, too, and return false
  // if we're not done with the flow, or not at a breakpoint, etc.
  if (!blockUntilOwn(cmd, true)) {
    return;
  }
  if (processFlowBreak(cmd)) {
    while (true) {
      try {
        Lock lock(m_signalMutex);
        m_signum = CmdSignal::SignalNone;
        processInterrupt(cmd);
      } catch (const DebuggerException &e) {
        switchThreadMode(Normal);
        throw;
      } catch (...) {
        assert(false); // no other exceptions should be seen here
        switchThreadMode(Normal);
        throw;
      }
      if (cmd.getInterruptType() == PSPEnded) {
        switchThreadMode(Normal);
        return; // we are done with this thread
      }
      if (!m_newThread) {
        break; // we're not switching threads
      }

      switchThreadMode(Normal, m_newThread->m_id);
      blockUntilOwn(cmd, false);
    }
  }

  if (m_threadMode == Normal) {
    Lock lock(this);
    m_thread = 0;
    notify();
  } else if (cmd.getInterruptType() == PSPEnded) {
    switchThreadMode(Normal); // we are done with this thread
  }
}