Monday, October 19, 2015

Breakpoints Starting to Work

I'm finally getting breakpoints to start working!

RST 20H (0xe7) is what I use to generate breakpoints. Since the code copies itself to RAM banks and executes from RAM, the breakpoint code can swap in e7 for whatever instruction is at a certain point. This causes the execution to run off to 0x0020.

Once there, it pushes all registers. It also pushes sp (i.e. ld hl, 0; add hl, sp; push hl) and then calls a function that loads hl with a pointer to a function and jumps to it. The default function just returns. But if the pointer is modified, execution can go someplace else.

And that place is bpBreak.

The bpBreak function handles loading a pointer to the registers contents struct over the stack where the the registers are stored. That struct pointer can be used to display and change register contents. It then it replaces all the RST 20H opcodes with the original contents. While doing that, it updates the breakpoint ignore counter, deletes delete-on-break breakpoints, and deletes break-on-hit breakpoints if the pc is where the break occurred. Finally, if any breakpoint had a count of 0, it will start the break prompt, otherwise skip straight to continuing.

The break prompt is where user can interactively set, delete, and change breakpoints, view and change register contents, and view and change memory.

Step is done with temporary breakpoints that delete on break. A breakpoint usually goes at the beginning of the next instruction. The exceptions are ret, jp, and jr instructions since execution never gets beyond. Except, of course, when they are conditional. In that case, the breakpoint must be set at the destination of the jump. There are 6 possibilities of which I deal with 4;
call, jp: place breakpoint at *(uint16_t*)(bpRegs->pc+1)
jr: place breakpoint at pc+*(uint8_t*)(bpRegs->pc+1)
ret: place breakpoint at *(uint16_t*)(bpRegs->sp)
jp (hl): place breakpoint at bpRegs->hl
rst: this is not done but one could place breakpoints at 00, 08, 10, 18, 20, 28, 30, or 38
halt: this is not done but one could place a breakpoint at 66

Next is like step except if there is a call (i.e. breakpoint uses absolute addressing, may reach next instruction, and is not conditional), then the breakpoint is not placed at the destination. This fails to take into account that calls can be conditional, but the SDCC compiler is not generating conditional calls, at least at the optimization level I'm using.

Continue is a little tricky. It is assumed that the current instruction is a breakpoint. If it isn't this is overkill, but if it is, we can't just load the breakpoints and run--we'd break at the same point again. Rather, the breakpoint has be temporarily disabled, a step has to take place, and the breakpoint must be restored once past it. This is done by never loading any breakpoint at the current pc, and having the code that unloads the breakpoint determine if a break is necessary. If a deletion-break breakpoint is hit and has an ignore count greater than 0, it will not generate a break.

Here are two breakpoint files. The first is the basic breakpoint function:


// __asm__("rst 20h") == 0xe7
#define RST_20H ((uint8_t)(0xe7))

struct Regs* bpRegs;
struct BP bpInfo[BREAKPOINT_COUNT];


// declare fnRST_20H for telling bank 0 how to get to bpBreak
extern void(*fnRST_20H)();

// Initialize breakpoint system
void bpInit()
{
    memset(bpInfo, 0, sizeof(bpInfo));
    fnRST_20H = bpBreak;
}

// crt1 calls this instead of main. This calls main
void bpStart(void(*initBP)())
{
    bpSet((uint8_t*)initBP, (1 << BP_FLAG_ENABLED) | (1 << BP_FLAG_DELETE_ON_HIT), 0);
    bpLoadBreakPoints(NULL);
}

// Sets a breakpoint at location with behavior flags and an ignore count
uint8_t bpSet(uint8_t* location, uint8_t flags, uint8_t count)
{
    uint8_t bp;

    // get unusued and look for same
    for (bp = 0; bp < BREAKPOINT_COUNT; bp++)
    {
        struct BP* b = &bpInfo[bp];
        if ((b->flags & (1 << BP_FLAG_ASSIGNED)) == 0)
        {
            b->flags |= (1 << BP_FLAG_ASSIGNED);
            break;
        }
        else if (b->addr == location)
        {
            if (((b->flags ^ flags) & ~0x03) == 0)
            {
                // such a breakpoint already exists
                return bp;
            }
        }
    }

    // Set the breakpoint
    if (bp < BREAKPOINT_COUNT)
    {
        struct BP* b = &bpInfo[bp];
        b->flags = flags | (1 << BP_FLAG_ASSIGNED);
        b->addr = location;
        b->count = count;
        printf("Set BP%x @ %p (%x-%d)\r\n", bp, location, flags, count);
    }
    return bp;
}

// Delete a breakpoint
void bpUnset(uint8_t bp)
{
    struct BP* b = &bpInfo[bp];
    b->flags = 0x00;
    printf("Unset BP%x @ %p (%x-%d)\r\n", bp, b->addr, b->flags, b->count);
}

// Enable or disable a breakpoint
void bpEnable(uint8_t bp, bool enable)
{
    if (enable)
    {
        bpInfo[bp].flags |= (1 << BP_FLAG_ENABLED);
        bpInfo[bp].count = 0;
    }
    else
    {
        bpInfo[bp].flags &= ~(1 << BP_FLAG_ENABLED);
    }
}

// Find if a breakpoint is enabled
bool bpIsEnabled(uint8_t bp)
{
    return (bpInfo[bp].flags & (1 << BP_FLAG_ENABLED)) != 0;
}

// Gets the first breakpoint at addr of 0xff
uint8_t bpAt(uint8_t* addr)
{
    uint8_t bp;
    for (bp = 0; bp < BREAKPOINT_COUNT; bp++)
    {
        if (bpInfo[bp].addr == addr)
        {
            return bp;
        }
    }
    return 0xff;
}

// Swaps in the RST 20H into the locations with breakpoints
// Breakpoints are not swapped at 'pc'
void bpLoadBreakPoints(uint8_t* pc)
{
    uint8_t bp = 0;
    for (bp = 0; bp < BREAKPOINT_COUNT; bp++)
    {
        struct BP* b = &bpInfo[bp];
        if ((b->flags & 0x03) == 0x03 && b->addr != pc) // assigned and enabled and not at PC
        {
            uint8_t swap = *(b->addr);
            if (swap == RST_20H)
            {
                // already a breakpoint there
            }
            else
            {
                b->swap = swap;
                *(b->addr) = RST_20H;
                printf("RST 20 to %02x @ %p\r\n", b->swap, b->addr);
            }
        }
    }
}

// Restores RST 20H with the original opcodes
// decrements ignore count for breakpoints who addr is 'pc'
// Returns whether any breakpoints at 'pc' were hit and had an ignore count of 0
bool bpUnloadBreakPoints(uint8_t* pc)
{
    uint8_t bp = 0;
    bool doBreak = false;
    for (bp = 0; bp < BREAKPOINT_COUNT; bp++)
    {
        struct BP* b = &bpInfo[bp];
        if ((b->flags & (1 << BP_FLAG_ASSIGNED)) != 0)
        {
            if ((b->flags & (1 << BP_FLAG_ENABLED)) != 0)
            {
                uint8_t swap = *(b->addr);
                if (swap == RST_20H)
                {
                    printf("RST 20 from %02x @ %p\r\n", b->swap, b->addr);
                    *(b->addr) = b->swap;
                }
                if (b->addr == pc)
                {
                    if ((b->count > 0))
                    {
                        b->count--;
                    }
                    else
                    {
                        doBreak = true;
                    }

                    // delete on hit
                    if ((b->flags & (1 << BP_FLAG_DELETE_ON_HIT)) != 0)
                    {
                        b->flags = 0;
                    }
                }
            }
            // delete on break BP
            if ((b->flags & (1 << BP_FLAG_DELETE_ON_BREAK)) != 0)
            {
                b->flags = 0;
            }
        }
    }
    return doBreak;
}

// This set breakpoints for certain types: 0-step 1-next 2-continue
// Step/Next: if !BRANCH_NO_STEP set bp @ pc+il
// Step: if BRANCH_ABS set @ (pc+1)
// Step: if BRANCH_REL set @ pc + (pc+1)
// Step: if BRANCH_RET set @ (sp-2)
// Step: if BRANCH_HL set @ hl
void bpStep(uint8_t type)
{
    uint8_t* pc;
    uint8_t branchInfo;
    uint8_t instrLen;
    bool breakOnNextInstr;
    bool breakOnDest;
    uint8_t count;
    uint8_t nextFlags;

    // get PC
    pc = (uint8_t*)bpRegs->pc;

    // get next instruction address
    instrLen = z80InstructionLength(pc);
    branchInfo = instrLen & 0xf8;
    instrLen = instrLen & 0x07;

    printf("branch %02x\r\n", branchInfo);

    // figure out what to do
    // if not branching away permanently, set a breakpoint there
    breakOnNextInstr = (branchInfo & BRANCH_ALWAYS) == 0;

    // count is 0 unless type is continue
    // If count is 1, will not return to break point prompt--will just continue
    count = (type == 2) ? 1 : 0;

    // How flags work
    // step: delete on hit for next and delete on break dest
    // next: delete on hit for next
    // continue: always delete on any break--restart immediately
    nextFlags = (type == 2) ? (1 << BP_FLAG_DELETE_ON_BREAK) : (1 << BP_FLAG_DELETE_ON_HIT);
    nextFlags |= (1 << BP_FLAG_ENABLED);
    //nextFlags = (1 << BP_FLAG_DELETE_ON_BREAK) | (1 << BP_FLAG_ENABLED);

    // place breakpoint if not NO_STEP branch (jp **, jp (hl), ret/i/n, jr)
    if (breakOnNextInstr)
    {
        bpSet(pc + instrLen, nextFlags, count);
    }

    // BP on branch destination
    breakOnDest = ((branchInfo & 0xf8) != 0);
    if ((type == 1) && ((branchInfo & BRANCH_CALL) != 0))
    {
        breakOnDest = false; // if user is using 'next' on a call
    }

    if (breakOnDest)
    {
        uint8_t branchType = branchInfo & 0x30;
        uint16_t target = 0;
        // get branch addr (rel, abs, (hl), (sp-2)
        if (branchType == BRANCH_ABS)
        {
            target = *(uint16_t*)(pc + 1);
        }
        else if (branchType == BRANCH_REL)
        {
            target = (uint16_t)pc + (int8_t)(pc[1]);
        }
        else if (branchType == BRANCH_RET)
        {
            target = *(uint16_t*)(bpRegs->sp);
        }
        else// if (branchType == BRANCH_HL)
        {
            target = bpRegs->hl;
        }

        // place breakpoint
        bpSet((uint8_t*)target, (1 << BP_FLAG_DELETE_ON_BREAK) | (1 << BP_FLAG_ENABLED), count);
    }
}

// RST 20H calls this after 
void bpBreak() __naked
{
    //RST_20H all registers pushed
    __asm__("di");
    __asm__("ld hl, #2");
    __asm__("add hl, sp");
    __asm__("push hl");
    __asm__("ld (_bpRegs), hl");
    __asm__("call _bpPrompt");
    __asm__("pop hl ; toss");
    __asm__("ret");
}

This file snippet is for breakpoint prompt features:


struct BpCmd {
    const char* cmd;
    const char* desc;
    uint8_t(*fn)(const char* line);
};

void showMem(uint16_t address, uint16_t length)
{
    uint8_t i;
    char* ph;
    char* pa;
    char line[75];
    w_bb shift;
    address = address & 0xfff0;
    length = (length + 15) & 0xfff0;
    memset(line, ' ', sizeof(line) - 1);
    line[sizeof(line) - 1] = 0;
    line[4] = ':';
    while (length)
    {
        shift.uw = address;
        ph = &line[0];
        pa = &line[4 + 1 + 1 + 1 + 16 * 3 + 1];
        *ph++ = __ascii[shift.ubb.h >> 4];
        *ph++ = __ascii[shift.ubb.h & 0x0f];
        *ph++ = __ascii[shift.ubb.l >> 4];
        *ph++ = __ascii[shift.ubb.l & 0x0f];
        ph++;
        ph++;
        // 16 bytes
        for (i = 0; i < 16; i++)
        {
            uint8_t b;
            b = *(char*)(address + i);
            *ph++ = __ascii[b >> 4];
            *ph++ = __ascii[b & 0x0f];
            ph++;

            // unprintables to .
            if (b < ' ' || b > 0x7e)
            {
                b = '.';
            }
            *pa++ = b;

            // gap
            if (i == 7)
            {
                ph++;
                pa++;
            }

        }
        puts(line);
        address += 16;
        length -= 16;
    }
}

static void showRegs()
{
    printf("af: %04x  bc: %04x  de: %04x  hl=%04x\r\n", bpRegs->af, bpRegs->bc, bpRegs->de, bpRegs->hl);
    printf("pc: %04x  sp: %04x  ix: %04x  iy=%04x\r\n", bpRegs->pc, bpRegs->sp, bpRegs->ix, bpRegs->iy);
}

static void showBreakpoint(uint8_t bp)
{
    struct BP* b = &bpInfo[bp];
    printf("%x: addr=%04x en=%x count=%02x\r\n", bp, b->addr, (b->flags & (1 << BP_FLAG_ENABLED)) != 0, b->count);
}
static void showBreakpoints()
{
    uint8_t i;
    for (i = 0; i < BREAKPOINT_COUNT; i++)
    {
        struct BP* b = &bpInfo[i];
        if ((b->flags & (1 << BP_FLAG_ASSIGNED)) != 0)
        {
            showBreakpoint(i);
        }
    }
}

static const struct BpCmd commands[] =
{
    {
        "?",
        "list",
        bpIntHelp
    },
    {
        "l",
        "list bp's",
        bpIntList
    },
    {
        "s",
        "step",
        bpIntStep
    },
    {
        "n",
        "next",
        bpIntNext
    },
    {
        "c",
        "continue",
        bpIntContinue
    },
    {
        "b",
        "set bp @ addr",
        bpIntCreate
    },
    {
        "d",
        "delete bp # or all",
        bpIntDelete
    },
    {
        "t",
        "toggle bp",
        bpIntToggle
    },
    {
        "r",
        "see regs or set rr v",
        bpIntRegs
    },
    {
        "m",
        "see RAM",
        bpIntMem
    },
};

const static uint8_t commandLength = sizeof(commands) / sizeof(commands[0]);


static uint8_t bpIntList(const char* line)
{
    line; // suppress warning
    showBreakpoints();
    return 0;
}

static uint8_t bpIntHelp(const char* line)
{
    uint8_t i;
    line; // suppress warning
    for (i = 0; i < commandLength; i++)
    {
        printf("%s - %s\r\n", commands[i].cmd, commands[i].desc);
    }
    return 0;
}

static uint8_t bpIntContinue(const char* line)
{
    line; // suppress warning
    bpStep(2);
    return 0xff; // continue
}

static uint8_t bpIntStep(const char* line)
{
    line; // suppress warning
    bpStep(0);
    return 0xff; // continue
}

static uint8_t bpIntNext(const char* line)
{
    line; // suppress warning
    bpStep(1);
    return 0xff; // continue
}

static uint8_t bpIntCreate(const char* line)
{
    if (*line++ == ' ')
    {
        uint16_t addr = extractHex(&line);
        uint8_t count = 0;
        if (*line == ' ')
        {
            line++;
            count = extractHex(&line);
        }
        if (*line == 0)
        {
            uint8_t i = bpSet((uint8_t*)addr, (1<<BP_FLAG_ENABLED), count);
            if (i == 0xff)
            {
                puts("BP full");
                return 1;
            }
            else
            {
                showBreakpoint(i);
            }
        }
        else
        {
            return 2;
        }
    }
    else
    {
        return 3;
    }
    return 0;
}

static uint8_t bpIntDelete(const char* line)
{
    uint8_t status = 0;
    if (*line == ' ')
    {
        uint8_t i;
        line++;
        i = extractHex(&line);
        if (*line == 0 && i < BREAKPOINT_COUNT)
        {
            bpUnset(i);
        }
    }
    return 0;
}

static uint8_t bpIntToggle(const char* line)
{
    //uint8_t status = 0;
    uint8_t bp;
    if (*line == ' ')
    {
        bp = extractHex(&line);
    }
    else if (*line == 0)
    {
        bp = bpAt((uint8_t*)bpRegs->pc);
    }
    else
    {
        return 1;
    }
    if (bp >= BREAKPOINT_COUNT)
    {
        return 2;
    }
    bpEnable(bp, !bpIsEnabled(bp));
    showBreakpoint(bp);
    return 0;
}

static const char* const regs = "SPiyixhldebcafpc";

static uint8_t bpIntRegs(const char* line)
{
    //uint8_t status = 0;
    if (*line == 0)
    {
        showRegs();
        return 0;
    }
    else if (*line == ' ')
    {
        uint8_t i;
        uint16_t search;
        const uint16_t* reg = (const uint16_t*)regs;

        line++;
        search = *(uint16_t*)line;

        for (i = 0; i < 7; i++)
        {
            if (search == *reg)
            {
                uint16_t val;
                line += 3;
                val = extractHex(&line);
                if (*line == 0)
                {
                    ((uint16_t*)bpRegs)[i] = val;
                    showRegs();
                }
                else
                {
                    puts("?");
                }
            }
            reg++;
        }
    }
    return 0;
}

static uint8_t bpIntMem(const char* line)
{
    uint16_t addr = 0;
    uint16_t len = 16;
    if (*line++ == ' ')
    {
        addr = extractHex(&line);
        if (*line++ == ' ')
        {
            len = extractHex(&line);
        }
        if (*line == 0)
        {
            showMem(addr, len);
            return 0;
        }
    }
    return 1;
}

// do prompt for breaks
//  ?           list commands
//  l           list breakpoints
//  c           continue
//  s           step
//  n           next
//  b xxxx (cc) break @ xxxx (optional countdown)
//  d ##        delete ##
//  t ##        toggle ## enable
//  r           show regs
//  r rr xxxx   set reg (except sp)
//  m xxxx xxxx show mem
void bpPrompt()
{
    char buffer[32];
    char* p;
    uint8_t i;
    uint16_t pc = bpRegs->pc - 1; // source of RST_20
    uint8_t interruptSave;
    bool doBreak;

    bpRegs->pc = pc; // restore to the instruction that was supposed to be there
    doBreak = bpUnloadBreakPoints((uint8_t*)pc);
    interruptSave = ei_save();

    while (doBreak)
    {
        uint8_t result = 0;
        char c;
        printf("(brk @ %p)> ", pc);
        gets(buffer);
        p = &buffer[0];
        c = *p;
        for (i = 0; i < commandLength; i++)
        {
            const char* cmd = commands[i].cmd;
            uint8_t len = strlen(cmd);
            if (strncmp(commands[i].cmd, p, len) == 0)
            {
                result = commands[i].fn(&p[len]);
            }
        }

        if (result == 0xff)
        {
            break;
        }
        else if (result != 0)
        {
            printf("error %d\r\n", result);
        }
    }
    bpLoadBreakPoints((uint8_t*)pc);
    di_restore(interruptSave);
}


This is the part of interrupt.c that is relevant:


static void ret() __naked
{
    __asm__("ret");
}
// This is preset to 'ret' If the code runs frojmj flash, this is necessary.
// If the code runs from RAM, this can be overridden.
void(*fnRST_20H)() = ret;
static void call_hl() __naked
{
    __asm__("jp (hl)");
}
void do_RST_20H() __naked
{
    __asm__("push af");
    __asm__("push bc");
    __asm__("push de");
    __asm__("push hl");
    __asm__("push ix");
    __asm__("push iy");
    // push sp
    __asm__("ld hl, #14");
    __asm__("add hl, sp");
    __asm__("push hl");

    __asm__("ld hl, (_fnRST_20H)");
    __asm__("call _call_hl");

    __asm__("pop iy"); //  toss  SP
    __asm__("pop iy");
    __asm__("pop ix");
    __asm__("pop hl");
    __asm__("pop de");
    __asm__("pop bc");
    __asm__("pop af");
    __asm__("ret");
}

No comments:

Post a Comment