I write mostly using the z88dk and debugging can be a little bit more involved as I can't always be sure where the compiled code ends up at, so adding in breakpoints at an absolute addresses can be a bit of a faff. I can work around this by adding some inline assembly at the point I want to enter the debugger by going into an endless loop (jr -2) and then running the code and manually breaking into the debugger when I hit the endless loop. This allows me to inspect registers and memory at this point but I then have to 'nop' out the endless loop to let the code carry on executing.

What would be useful is something like an int 03 opcode on the x86 which when executed would break into the debugger automatically at that point.

SpecEmu lets you run until you hit certain opcodes (which all seem to have side effects if you were to add them in to your code just to cause the debugger to pop up) or when you read / write to a port. I can sort of achieve an int 03 by doing a read to a specific port from my code but I'm not sure this information is saved between sessions, so when I restart SpecEmu I have to manually add the port again, It also seems that after seeing the port read, the debugger no longer breaks on subsequent reads to that port.

Would this be doable with a custom 'rst' command or another opcode combination that doesn't effect other registers / modes on the processor? Or maybe an option to treat 'jr -2' as a debug break / int 03?

You can't really have a "custom RST" because of the way the opcode is encoded. Indeed all possible encodings are valid on a Z80, which makes it trickier. You could possibly treat one of the alternate NOP encodings as a breakpoint (which would be less problematic than JR -2)

A tidier solution, however, would be for z88dk to generate breakpoint information that emulators could either import directly or that could be converted to whatever format they usually use to persist breakpoints. I've no idea how easy that would be to add (or indeed if it could be built on info that already exists). I'd assume knowing the entry points of functions and generating breakpoints there should at least be doable.

Zeus has some great expression-based breakpoints and expression-based logging commands. It's not really geared up to running z88dk (although it can load taps and tzxs in a fastish but not flash mode) but it's perfect for asm work.

For example I have set breakpoint expressions to track values from multiple routines and FRAMES in a kind of state machine, logging interesting register and memory values as it goes (and even writing to memory from the expression), then break right before the problem time. With recorded keystrokes so the test case can be replayed over and over again, while you examine and change the problem area of code. It only takes a few zeusdatabreakpoint directives to set up something quite elaborate that persists across multiple debugging sessions.

A tidier solution, however, would be for z88dk to generate breakpoint information that emulators could either import directly or that could be converted to whatever format they usually use to persist breakpoints. I've no idea how easy that would be to add (or indeed if it could be built on info that already exists). I'd assume knowing the entry points of functions and generating breakpoints there should at least be doable.

You can run zcc, one of the compilers in the z88dk with the flag -m which will generates a map file which lists all the finalised locations of the global variables, locals and functions, so yes working out breakpoints at function entry points would be possible. z88dk to the best of my knowledge doesn't implement a breakpoint as such, so I'm not sure how you'd get the compiler to emit a list of them without making changes to do this. I thought the opcode approach would be the easier way to achieve this rather than asking emulator developers to add support for z88dk listing and map files and turning their existing debugger frontends into a mini-IDE just for z88dk developed code.

Well I was thinking more of a solution which might not require emulator changes at all. You'd have a text file containing functions you wanted breakpoints in, the map file produced by z88dk and from that generate a suitable set of breakpoints for the emulator. That is making the assumption that the emulator you're using persists breakpoints in a known (or easily decipherable) format - which may or may not be the case (I tried in Spin but couldn't figure out how to create a breakpoint in the assembler)

After some inadvertent searching, I found out that the ZX Spectrum Next emulator CSpect implements this functionality.

If you run it with the command line -brk it breaks into the debugger when it encounters the opcode sequence 'dd 01'. I managed to test this and it does indeed break into the debugger when it encounters that opcode. If you run it with the command line -exit, I'm guessing it exits the emulator when it hits 'dd 00'. This could be handy too during testing as you could close down the emulator from inside the code.

z88dk does generate a map file but only for the functions - so far as I can tell. If I wanted to find out the absolute address for a particular line in my source file, I'd have to get the generated assembly offset from the .lis file z88dk produces and then add that onto the offset from the function in the map file. I'd then need to start the emulator, go into the debugger and add my breakpoint(s) in manually and then load my code in. Any time I made a change to the code I would have to recalculate the breakpoint address. Everytime I restarted the emulator, I would need to re-input my breakpoints as breakpoints do not appear to be persistent - we are talking about emulators here not fully blown C IDEs.

From my perspective I don't see how this is easier for me or less work for an emulator developer to provide me with the option to treat a specific opcode sequence as a command to break into the debugger than it would be to have to load in and parse generated compiler symbol files to achieve the same thing?