dirkwhoffmann / Moira

A Motorola 68000 emulator written in C++
https://dirkwhoffmann.github.io/Moira
Other
113 stars 13 forks source link

Breakpoints at the beginning of exception handlers are ignored #1

Closed dirkwhoffmann closed 4 years ago

dirkwhoffmann commented 4 years ago

Posted by Elmer:

I actually run into one issue concerning breakpoints at the start of exception handlers: It is possible (of course) to add a breakpoint at the specific address, but the system is unable to detect it The reason seems to be that the execute() function is atomic: First it checks for changed ipl levels, if necessary initiates the exception (during which an address is loaded from the auto-vector table). Subsequently, in one go, it executes the first instruction of the exception code, after which the address of the next instruction will be checked for a breakpoint.

dirkwhoffmann commented 4 years ago

I need to think about it in more detail, but the most obvious solution might be to simply skip the exec call in Moira::execute() if a trace or interrupt exception has occurred.

void
Moira::execute()
{
    // Process execution flags (if any)
    if (flags) {

        // Process pending trace exception (if any)
        if (flags & CPU_TRACE_EXCEPTION) {
            execTraceException();
        }

        // Check if the T flag is set inside the status register
        if (flags & CPU_TRACE_FLAG) {
            flags |= CPU_TRACE_EXCEPTION;

>>> ADD LINE:        goto exit;
        }

        // Process pending interrupt (if any)
        if (flags & CPU_CHECK_IRQ) {
            checkForIrq();

>>> ADD LINE:       IF "IRQ HAS TRIGGERED" goto exit;

        }

        // If the CPU is stopped, poll the IPL lines and return
        if (flags & CPU_IS_STOPPED) {
            pollIrq();
            sync(MIMIC_MUSASHI ? 1 : 2);
            return;
        }

        // If logging is enabled, record the executed instruction
        if (flags & CPU_LOG_INSTRUCTION) {
            debugger.logInstruction();
        }
    }

    // Execute the instruction
    reg.pc += 2;
    (this->*exec[queue.ird])(queue.ird);

>>> ADD LABEL:  exit:

    // Check if a breakpoint has been reached
    if (debugger.breakpoints.needsCheck)
        if (debugger.breakpointMatches(reg.pc)) breakpointReached(reg.pc);
}
dirkwhoffmann commented 4 years ago

@elmerucr: I think I have a decent fix for the breakpoint issue. My plan is to change the execution function as follows:

void
Moira::execute()
{
    //
    // The quick execution path: Call the instruction handler and return
    //

    if (!flags) {

        reg.pc += 2;
        (this->*exec[queue.ird])(queue.ird);
        return;
    }

    //
    // The slow execution path: Process flags one by one
    //

    // Process pending trace exception (if any)
    if (flags & CPU_TRACE_EXCEPTION) {
        execTraceException();
        goto done;
    }

    // Check if the T flag is set inside the status register
    if (flags & CPU_TRACE_FLAG) {
        flags |= CPU_TRACE_EXCEPTION;
    }

    // Process pending interrupt (if any)
    if (flags & CPU_CHECK_IRQ) {
        if (checkForIrq()) goto done;
    }

    // If the CPU is stopped, poll the IPL lines and return
    if (flags & CPU_IS_STOPPED) {
        pollIrq();
        sync(MIMIC_MUSASHI ? 1 : 2);
        return;
    }

    // If logging is enabled, record the executed instruction
    if (flags & CPU_LOG_INSTRUCTION) {
        debugger.logInstruction();
    }

    // Execute the instruction
    reg.pc += 2;
    (this->*exec[queue.ird])(queue.ird);

done:

    // Check if a breakpoint has been reached
    if (flags & CPU_CHECK_BP)
        if (debugger.breakpointMatches(reg.pc)) breakpointReached(reg.pc);
} 

In addition to returning directly from this function after executing an exception condition, I have moved breakpoint checking into the "slow execution path". Now, the "quick execution path" only consists of a single call to the instruction handler which should slightly speed up emulation (I guess, the quick execution path is called 99% of the time under normal operating conditions).

I haven't checked in the code yet, but I did some experiments with it in vAmiga. With the new code, I was successful to interrupt the CPU right at the beginning of the VBLANK IRQ handler.

Bildschirmfoto 2020-01-18 um 13 09 44
elmerucr commented 4 years ago

Looks like a good solution! Are you going to commit the changes to the repository?

dirkwhoffmann commented 4 years ago

New code has been checked in and uploaded.

elmerucr commented 4 years ago

Thanks, code works great so far!