empathicqubit/vscode-cc65-debugger

Debugging with AppleWin

audetto opened this issue ยท 55 comments

As of this https://github.com/audetto/AppleWin/tree/229eac9dc73a0797eb9b89b0847cc8b516ac3711 the branch is in a beta-working state.

Caveats:

  1. AppleWin cannot run a .apple2 executable, but needs a disk. So I had to add a new supported extension .dsk. It can be created with this extension to the Makefile
$(PROGRAM).apple2.dsk: $(PROGRAM).apple2
	cp $(IP65)/build/prodos.dsk $@
	java -jar $(AC) -as $@ $(PROGRAM)            < $<
	java -jar $(AC) -p  $@ $(PROGRAM).system sys < $(shell cl65 --print-target-path)/apple2enh/util/loader.system
  1. Disable run ahead
  2. Even with a .dsk, I currently don't know how to start it automatically, so autostart only inserts the disk. You will have to manually start the code in the emulator. Will see if there is a way round it
  3. Video: works. Indexed greyscale at the moment.
  4. Breakpoints: this is something that needs some work.
  5. I had to disable memory breakpoints as they constantly hit. The extensions sets breakpoints at
BREAKPOINT [0841-0841] src:  3 op: 2 enabled: 0 temp: 0    3 = exec
BREAKPOINT [0841-0841] src: 17 op: 2 enabled: 0 temp: 0   17 = write mem    explicitly disabled
BREAKPOINT [08AB-08AB] src: 17 op: 2 enabled: 0 temp: 0   17 = write mem    explicitly disabled

I don't know why, but maybe the OS uses that area for some other reason.
7. I don't really understand the meaning of "stop" and "enabled" in the checkpoint API. If stop = false, why do we even set it?
8. I am not sure why, but after running the program a few times, it finally hits. I see the extensions creating some exec breakpoints, but in a disabled state, but after a while they are enabled, and it finally stops.
9. If a "load" breakpoint is set at location X, is it supposed to trigger when the PC gets there as it needs to read the opcode? or memory is just memory operands?

TODO:

  • Jump to raster line to support runahead without conditions
  • Allow choosing a different frontend (sa2, qapple, etc)
  • Command line switches to select monitor port / default configuration

stop = false is for tracepoints. It's a way to get information on the execution state without stopping the emulation. It's used to keep track of the call stack. I'd like breakpoints to be able to execute other commands automatically to make this more useful, similar to the way you can attach commands to breakpoints in the VICE text monitor, but it get the job done for now. I think the least complex way would be to have an array of commands with full headers, and just ignore or modify the header fields that don't make sense on the server side.
enabled = false allows to completely turn off the breakpoint but still keep it on the emulator side. This is because if you delete and recreate a breakpoint, it will get a new id. In some places I need to flip off the breakpoints to do some other complicated operation, and then turn them back on.
I'm not sure about load also being triggered by PC, but I think so. I will look at the VICE documentation.

It looks like the load checkpoints do not break for execution load:

(C:$e5cf) break load $fce2
WATCH: 1  C:$fce2  (Stop on load)
(C:$e5cf) reset
(C:$e5d4) 
(C:$e5d4) 
(C:$e5d4) 
(C:$e5d4) 
(C:$e5d4) 
(C:$e5d4) 
(C:$e5d4) break load $e5d4
WATCH: 2  C:$e5d4  (Stop on load)
(C:$e5d4) x
(C:$e5d1) reg
ERROR -- Wrong syntax:
  reg
     ^
(C:$e5d1) r
  ADDR A  X  Y  SP 00 01 NV-BDIZC LIN CYC  STOPWATCH
.;e5d1 00 00 0a f3 2f 37 00100010 000 000   18987696
(C:$e5d1) break load $e5d1
WATCH: 3  C:$e5d1  (Stop on load)
(C:$e5d1) x
(C:$e5cd) break $e5d1
BREAK: 4  C:$e5d1  (Stop on exec)
(C:$e5cd) x
+ #4 (Stop on  exec e5d1)   60/$03c,   7/$07
+ .C:e5d1  8D 92 02    STA $0292      - A:00 X:00 Y:0A SP:f3 ..-...Z.   26814571
(C:$e5d1) 

It would be best if AppleWin could handle disk creation. VICE does this with c1541. Relying on java in the Makefile is definitely not good. What is $(AC)? https://vice-emu.sourceforge.io/vice_14.html

It would be best if AppleWin could handle disk creation. VICE does this with c1541. Relying on java in the Makefile is definitely not good.

I disagree. Everybody using cc65 for an Apple ][ is already using it to create a disk (or maybe a hard disk) (and in various formats). (other people use CiderPress)
This becomes a problem if you want to ship an entire toolchain with the debugger, but everybody already has it.

stop = false is for tracepoints.

So what happens when one is hit?
Increment hit count? Report to the vscode extension? None of it?
I guess it does not send a "stopped event", but only a "checkpoint info"?

And what about the ignore count? when it is incremented?

I need to add a good viewer for breakpoints as I am getting very confused reading the moving log.

Another thing: is there a way to refresh the display? I have not understood exactly when it refreshes via a DisplayGet? I was looking for a button to force a refresh?

Any breakpoint which is hit should have the hit count incremented, I think. I do not remember how the ignore count works. Look at the VICE source.

Can you see how complicated it would be to allow running the apple2 file directly? As it currently stands it would add some complexity to the unit tests. Also c1541 isn't currently in the Makefile, because it doesn't need to be. I'd like to avoid deviating too much from the one that CC65 provides.

The display refreshes once per second when running, and not at all when stopped. There isn't a refresh button at the moment.

To run an apple2 files one needs

  1. insert and boot a ProDos disk
  2. copy the file to the correct location (normally 0803) (there is a header in the file)
  3. transfer control to 0803 (probably inserting the text CALL xyz\n in the keyboard buffer)

Except step 1, it is probably the same vice does.

I have another question about the implementation of the Binary Monitor protocol (https://vice-emu.sourceforge.io/vice_13.html#SEC301)

The transmission of any command causes the emulator to stop, similar to the regular monitor. This causes the server to respond with a list of register values.

Is this 100% correct? Even for PING? It generates a lot of (unnedded?) traffic: ping, then the emulator sends registers, sends stopped, the server re-sends starts, the emulator sends resumed.

When should exactly the emulator send "suspended" and "resumed"?

Yes, this is correct. Especially ping, because it gives the client the ability to stop the emulation without doing anything. All commands or even unrecognized traffic should cause the emulation to stop. Suspended should be sent when a breakpoint is hit or the client sends data. I think the suspend comes after the breakpoint, mainly because VICE processes the breakpoint before entering the monitor. Please double check though. Resumed should be sent when the client resumes the emulation. Tracepoints do not stop the emulation, so they should not send a suspend.

A resume is also sent on instruction(s) step, and the suspend when the step finishes. Resume and suspend are basically for, "I'm leaving the monitor and resuming execution. If you send another command I will stop again."

If you send a command type which causes a resume, the response to that command should be sent before the resume event. For example, the continue response 0xaa, gets sent before the resume 0x63. See https://vice-emu.sourceforge.io/vice_13.html#SEC301 and also https://sourceforge.net/p/vice-emu/code/HEAD/tree/testprogs/remotemonitor/binmontest/main.c#l1256

Traffic is cheap. Debugging over the internet isn't what this was designed for.

I wanted the monitor to be able to do async, but there were people who wanted the monitor to keep working synchronously. I understand because it does help to reduce problems, though it can be cumbersome at times.

I also wanted a separate connection to stream the screen as video which would reduce the stopping and starting to get the screen, but I didn't get there yet. They wanted to use ZMBV format in VICE, instead of ffmpeg, but I'm not sure if that ever went anywhere. I wanted to help out with that conversion but I kind of dropped the ball there. If you want to implement something like that, you should probably run it by the VICE peoples. Some of the people there have a very specific vision of how things should be. Aggressive minimalism, typically, hence wanting to remove ffmpeg.

Is there anything else I can help with?

Hi,

got distracted...

I have reached a point where "some of it works", but I need to improve the start / stop / breakpoint logic because sometimes I feel the 2 ends loose sync.
But I was able to put breakpoints, in C and asm and see the debugger stopping.
BTW: have a look at cc65/cc65#1736, I often get the warning "missing main.s" from the extension.

I had a question for you: since I attach / detach multiple times, I wonder if there is some implicit moment when all existing breakpoints are cleared?
This cannot be on a new tcp connection, as there are many, so for now, if I detect a duplicate breakpoint request, I reuse the previous id, but I was thinking a "session" should start with some "clear" command.

And I noticed that when I remove a breakpoint, the extension seems to delete and resend them. Nothing wrong with it, but it causes confusion when I try (in my mind) to keep track of what happens. Why is it doing it?

if I detect a duplicate breakpoint request, I reuse the previous id

This could be problematic depending on how you detect it, but I like your idea of clearing everything at the beginning. I didn't think through the attach case with multiple attachments in mind

It is only temporary until a better solution is found, otherwise I get a lot of repetition.

You could list and delete all the existing breakpoints (if any) when the session starts.

And I noticed that when I remove a breakpoint, the extension seems to delete and resend them. Nothing wrong with it, but it causes confusion when I try (in my mind) to keep track of what happens

I don't remember why it does this. Maybe it's to keep the user breakpoints grouped together or something, so if you list them on the text monitor it's easier to find them, instead of having them mixed in with the others. I used to add a do-nothing marker condition to them to make them look different, but that's no longer possible because the condition expressions aren't used anymore since they were too specific to VICE

Okay, I changed it to clean up its breakpoints on disconnect. I might have missed something. It doesn't clear everything so it can theoretically preserve anything the user might have added outside the debugger, such as in the text monitor.

Can you add the command line switches to the different UIs so that it can start up without any hacks?

I have added --binary-monitor, --binary-monitor-address and --default.

About --default, I am not sure it should be a default argument, as one would normally configure the emulator (adding cards etc..) rather than debugging on an empty machine.

Anyway, I could not test them as, for some reason, when I change attach to launch, the extensions tried to compile and fails. Can I tell it not to compile anything, and leave it to me?

I still need to work on the suspend / resume / breakpoint logic.

--default isn't a default argument, except in the version checking case. Normally it starts up without it. This is used to temporarily clear out any garbage settings which might prevent a successful startup, since it only gets the version, but on the actual start it should use the saved settings.

skip: Should we skip building before execution?

Ok, so the implementation is not quite right then, because as a side effect, it will zero the saved configuration.

I can change it to "don't load and don't save the configuration", but I don't really know what purpose it will serve.

might prevent a successful startup

I have 1 observation. If the actual start does not use --default and it cannot start, what did you achieve by the first start, where you only got the version? The garbage settings will anyway prevent the debug session from running.

Different options have to be passed to VICE depending on which version you have. --default guarantees that the options are appropriate for the version, then on the second startup it can pass the correct ones. This is needed for the -directory option, which points to the ROM data folders, but the format changed between versions because the structure of the folder changed. The debugger overrides the ROM with a slightly hacked version which boots faster.

Ok, I have implemented in a way that will not read nor write a configuration file.
And I have changed the version info to the real one.

Okay, thanks. I added AppleWin to the test suite and it starts now, but doesn't do anything yet. Is it possible to get an sa2 build on Windows? I tried using the VC2019 sln but it seemed to only generate the standard interface

I think I figured out why the cc65 build was breaking for you. Something with the way the arguments get interpreted with npm-run-all changed. I'm not sure if it's an issue with npm-run-all, or pnpm, or what, but I changed the scripts to use yargs so it's less likely to break.

Hi,

Windows compilation will be for later. I develop in linux. If this works, I will see if there is any interest to upstream it.

I am at a point where I need your help.
I made some changes but the behaviour is not as clean as I would like.

I have added something to the GUI to allow you to see what happens.
If you click System -> Debugger and select Breakpoints you should see a lot, and not require to debug my code.

So what I do is

  1. start ./sa2
  2. set some breakpoints attach the debugger
  3. autostart
  4. Apple ][: at the DOS prompt type: BYE and then navigate to PROGRAM.SYSTEM and hit enter
  5. execution does not stop, even if I see the emulator telling the extension that we have hit a preakpoint
  6. go back to 4). This time the behaviour will be different with many breakpoints being added (a lot of duplicates) and a big one (ID 1) which hits all the times.

I do not understand why the extension behaves differently in the 1st and 2nd run and why so many duplicate breakpoints are added.

image

There is a little UI issue if the Enhanced speed is enabled while debugging

image

It seems like it hangs, but it is only the UI not refreshed. For now, make sure you disable it.

I was then able to test better and I still have this issue that the extensions sends many (duplicate) breakpoints, including the ID=1 which triggers continuously. You can manually delete it and you can then stop correctly inside the c source code.

Moreover the first way round, the extension does not block, but you you rerun the program a few times, you eventually can stop.

I'd appreciate if you could tell me if the monitor is not behaving correctly.

I haven't had the energy to dive into this recently, but I'll look at it this weekend

I should note that the attach situation is not the primary one, and there are not as many integration tests related to it. It would probably be better to focus your energy on getting the regular startup working first.

Except that I am still fighting against the whole compilation step. attach works, launch not.
Can I tell the extension to completely forget about cc65?

image

Downgrade to vscode 1.66 and update to master.

To tell the extension to skip building use build.skip = true in the launch.json

Please also see #114 . I fixed this on Windows but I do not have a fix for Linux yet.

Please feel free to contribute a fix if you have an idea of what's happening. I was going to save the entire command line to a temporary shell script and execute from there. Microsoft changed the command line escaping in 1.67.

Launching works a bit better, but there are still issues:

  1. it stops the first time I run the program, rather than 2nd or 3rd
  2. still many duplicate breakpoints
  3. and the long memory write breakpoint, which I need to disable to continue

The long memory write breakpoint is to protect the code segment, which shouldn't be written to unless something is won't. It should only be hit once and causes a warning to appear.

Duplicate breakpoints aren't necessarily an issue. There may be different breakpoints at the same location which have different responsibilities. All of them should emit when the address gets hit, provided they are enabled, and then the debugger should stop.

Have you seen this section? It looks like there are a few different linker configurations. Do you know which one is best? https://cc65.github.io/doc/apple2.html#s4

If you could, please send me the cc65 project you are using to test this. I cannot autostart with the standard project.

Try with this

https://github.com/audetto/vscode-cc65-debugger/blob/2aad24d42c6a1d83eb9b32950e85ea3044ca7c83/src/__tests__/c-project-template/dsk.mk#L3

I put there all the changes I currently carry to make this work.

do this too make TARGETS=apple2 program.apple2.dsk

You can set the target to apple2enh to get a few more features, but to reduce which machines can run it: https://cc65.github.io/doc/apple2enh.html

Hi, I start seeing some decent behaviour, with various caveats

  1. launch is problematic because the memory around 0840 is used by PRODOS during startup. Is this a problem? It keeps stopping during boot.
  2. attach still only stops the 2nd time round and I need to disable the long write breakpoint
  3. I have a bit of an issue with the stop flag, so for now a breakpoint stops if is it both enabled and stops
  4. temporary breakpoints will cause me some issues as they are deleted by AppleWin debugger before I can report they have been hit
  5. i cannot see the value of local variables in the extension debugger window

But with all the above I can now put a few breakpoints in c and they actually stop properly.

I'm glad things are progressing for you! I was wondering if you would push the monitor into a deeper layer at some point, so that the other UIs could benefit from it. I'm having a problem getting the open file dialog to appear in vscode, so I haven't gotten much farther with things yet. I tried erased my .vscode and .config/Code, but that didn't help

What are $(IP65) and $(AC) ?

$$ \begin{align} twas^\frac{brillig}{and} the_\frac{slithy}{toves} did^\frac{gyre}{and} gimble_\frac{in}{the} wabe.^\frac{All}{mimsy} were_\frac{the}{borogoves} and^\frac{the}{mome} wraths_{outgrabe.} \end{align} $$

Sorry, I noticed that Github could do formulas and got carried away

I pushed a new commit to https://github.com/audetto/vscode-cc65-debugger and together with your new code it works a lot better.

I get the variables too.
I removed $(IP65) and explained what is $(AC)

https://github.com/audetto/vscode-cc65-debugger/blob/abeef2b20c5d8409592616121eba98e1edc68237/src/__tests__/c-project-template/dsk.mk#L5

Sorry it took me a while to get back to you. I had to cannibalize your script but I was able to produce a disk image. Do you know how I get it to autostart after that?

autostart will simply insert the disk in the first slot.

if you have the ] prompt (if not press F2), then one of these will do

  1. BRUN PROGRAM
  2. -PROGRAM.SYSTEM
  3. BYE and select either in the list

What I could do to ease autostart is to force a reboot and insert BRUN PROGRAM in the keyboard buffer.

Just wanted to mention that I didn't forget this, just been busy with summer planning. Hope you're doing well.

Can you make autostart fully work? That would help me a lot

I have been really bad recently, I have to try to upstream the breakpoint changes, otherwise this will be hard to maintain.

Autostart: I made 2 changes

  1. audetto/AppleWin@0ede8ea after the disk is inserted, the machine is reset

  2. audetto@16803d9 if a program is called startup it will be automatically started by BASIC.SYSTEM

With the 2 together, I get autostart to work.

Okay, I think it's working for me now. I'm a little confused about why it stops at particular addresses. Could you change the SP to be one byte? I don't think it's causing the startup problems, but it could cause problems with other parts of the extension.

I don't know if it gives any clues, but I set a breakpoint at the entry address to know when the program has loaded it (maybe). Then I check the memory to see if it's actually there, and continue if it's not. It looks like it hits it a few times, but not when the program is actually loaded. Maybe it gets unset somehow? If I open the internal debugger, it will cause the execution to stop, which gives it another chance to check if the program is loaded, and then everything is good.

I got some of the assembly project tests to run now. Please see if you can find anything else in your implementation of the debug protocol that needs tweaking.

You can change the TEST_PROGRAM and APPLEWIN_DIRECTORY variables in the build.env to choose which AppleWin and apple2 file to use for the tests. Look at the notes in build.env.sample

https://github.com/empathicqubit/vscode-cc65-debugger/actions/runs/3460247894/jobs/5777562802

SP 8 bit.

I have added some extra information to the breakpoint info dialog to include the hit count.
And I think I had a small issue where it would report breakpoints being triggered wrongly.

It does behave a bit better but still not perfect.

Have you tried using the VICE binary monitor test suite against AppleWin? You will probably have to change some stuff, but that might help you verify there's nothing else wrong.

https://sourceforge.net/p/vice-emu/code/HEAD/tree/testprogs/remotemonitor/binmontest/

The tests should compile without any special libraries or setup, but only on Linux.

I did something dumb and partially broke the startup process. I noticed this when I tried to launch one of my slightly more complex projects. It's working now.

You're probably already capable of doing this yourself, but here is a dump of the traffic between VICE and the debugger. This is with starting up and stepping over a few lines. maybe it will help you figure out what's missing in your implementation.
dump.txt

I know: very little progress recently, but I have finally proposed the PR for the breakpoint enhancements to AppleWin : AppleWin/AppleWin#1191

If successful, this will definitely facilitate the whole thing, removing custom modification.

I think we can close this one. It works a lot better now.

For some reasons launch works better than attach and I think some breakpoints are duplicate.
We can address in more specific issues.

I will move to clean the monitor branch and merge to master.