AVR simulator as aid tool for debugging code

@ivankravets I’ve started to work on that, meanwhile you could try the other workaround without having to recompile anything. That way we can start testing on OSX before the patch is ready.

simavr -g -f <freq> -m <model> firmware.elf

@ivankravets @valeros I’ve just uploaded a patch for issue #371 to my fork, please test if it works on OSX.

The fix consists of faking the memory map reporting the Flash section as RAM which forces avg-gdb to use only the M command instead of vFlashErase, vFlashWrite and vFlashDone.

I’ve tested with both my versions of avr-gdb and the load command doesn’t trigger that error anymore.

Nevertheless, I will keep working on this and also on the issue #370, so the commands monitor reset and monitor halt get supported too.

2 Likes

@ivankravets @valeros
Done, I’ve just added a fix for issue #370. Now pio_reset_halt_target and pio_reset_run_target can be populated like this:

  define pio_reset_halt_target
      monitor reset
  end
  define pio_reset_run_target
      monitor halt
  end
  target remote :$DEBUG_PORT
  $LOAD_CMDS
  $INIT_BREAK

About the fix, monitor reset will put the processor to a initial state and pause, while monitor halt will gracefully terminate all execution and close the program.

Please, rebuild from my fork and test if there are any other bugs.

1 Like

@msquirogac! Many thanks for such a great contribution, it works much better on OS X, so we’ve used your fork as the source for tool-simavr package! One thing though, it looks like GDB is not able to produce disassembly for the uploaded firmware, on OS X GDB complains:

Unable to disassemble: Cannot access memory at address 0x800900 (from data-disassemble -s 0x000008b8 -e 0x0000091c -- 2)

on other platforms it mostly returns NOPes:

0x008007d2: 00 00           	nop
0x008007d4: 00 00           	nop

Any ideas?

Anyway, thanks again for efforts!

@valeros Maybe another gdb command without support.
What are the commands related to the disassemble function or at least the ones that vscode tries to use?

For example you can run in the CLI mode:

disassemble main

in my case the output is :

(gdb) disassemble main
Dump of assembler code for function main:
   0x000008ca <+0>:     .word   0xffff  ; ????
   0x000008cc <+2>:     .word   0xffff  ; ????
   0x000008ce <+4>:     .word   0xffff  ; ????
   0x000008d0 <+6>:     .word   0xffff  ; ????
   0x000008d2 <+8>:     .word   0xffff  ; ????
   0x000008d4 <+10>:    .word   0xffff  ; ????
   0x000008d6 <+12>:    .word   0xffff  ; ????
   0x000008d8 <+14>:    .word   0xffff  ; ????
   0x000008da <+16>:    .word   0xffff  ; ????

or, for example, GDB MI command:

data-disassemble -s 0x000008ca -e 0x000008dc -- 2

@valeros
This is the procedure to launch the debug session on CLI mode, right?
Could you upload the source code you are using? My demo returns this for disassemble main

0x0000044c < 0>: call 0x220 ; 0x220
0x00000450 < 4>: call 0x38c ; 0x38c
0x00000454 < 8>: call 0x358 ; 0x358
0x00000458 < 12>: ldi r24, 0x00 ; 0
0x0000045a < 14>: ldi r25, 0x00 ; 0
0x0000045c < 16>: or r24, r25
0x0000045e < 18>: breq .-12 ; 0x454 <main 8>
0x00000460 < 20>: call 0 ; 0x0 <__vectors>
0x00000464 < 24>: rjmp .-18 ; 0x454 <main 8>

This is the procedure to launch the debug session on CLI mode, right?

Yes, that’s one of the options or you can type commands directly in the debug console.

My demo returns this for disassemble main

I see, it looks like the load command is mandatory even if the firmware didn’t change.

But what’s interesting is that when I specify a memory range it still returns NOPes, e.g.:

(gdb) disassemble main
Dump of assembler code for function main:
=> 0x000008ca <+0>:     call    0x24e   ;  0x24e <init>
   0x000008ce <+4>:     call    0x74a   ;  0x74a <setup>
   0x000008d2 <+8>:     call    0x7d2   ;  0x7d2 <loop>
   0x000008d6 <+12>:    call    0x732   ;  0x732 <serialEventRun>
   0x000008da <+16>:    rjmp    .-10            ;  0x8d2 <main+8>
End of assembler dump.
(gdb) disassemble 0x000008ca,0x000008da
Dump of assembler code from 0x8008ca to 0x8008da:
   0x008008ca:  nop
   0x008008cc:  nop
   0x008008ce:  nop
   0x008008d0:  nop
   0x008008d2:  nop
   0x008008d4:  nop
   0x008008d6:  nop
   0x008008d8:  nop
End of assembler dump

@valeros
Look at the addresses. While you ask for 0x000008ca, which is in the flash section so is good, GDB returns 0x8008ca. That 8 shouldn’t be there and the returned memory is outside the program region. By default that memory is empty, that’s why we have nop.
I haven’t figured why GDB is doing that yet. Do you have any idea?

Edit: I’ve found and old comment from the author himself related to this problem. That lead me to research about the memory mapping in avr-gdb and looks like there is a very well known bug. I found a patch but it never got into avr-gdb mainline.

@ivankravets
We need that fix to get the disassemble command working properly. Would it be possible to recompile avr-gdb with that patch? As this only affects avg-gdb it shouldn’t have any effect over avr-gcc, so Arduino compatibility should stay the same.

2 Likes

Could someone helps us with updated binaries?

This patch definitely works. I compiled GDB 9.1 from Index of /gnu/gdb, applied the source change, compiled (./configure --target=avr --prefix=<some_local_install_dir> && make && make install procedure) and it goes from previously

disassemble main
Dump of assembler code for function main:
   0x00000496 <+0>:     call    0x280   ;  0x280 <init>
   0x0000049a <+4>:     call    0x3d6   ;  0x3d6 <setup>
   0x0000049e <+8>:     call    0x3b8   ;  0x3b8 <loop>
   0x000004a2 <+12>:    ldi     r24, 0x00       ; 0
   0x000004a4 <+14>:    ldi     r25, 0x00       ; 0
   0x000004a6 <+16>:    or      r24, r25
   0x000004a8 <+18>:    breq    .-12            ;  0x49e <main+8>
   0x000004aa <+20>:    call    0       ;  0x0 <__vectors>
   0x000004ae <+24>:    rjmp    .-18            ;  0x49e <main+8>
End of assembler dump.
{"token":55,"outOfBandRecord":[],"resultRecords":{"resultClass":"done","results":[]}}
disassemble 0x496,0x4ae
Dump of assembler code from 0x800496 to 0x8004ae:
   0x00800496:  nop
   0x00800498:  nop
   0x0080049a:  nop
   0x0080049c:  nop
   0x0080049e:  nop
   0x008004a0:  nop
   0x008004a2:  nop
   0x008004a4:  nop
   0x008004a6:  nop
   0x008004a8:  nop
   0x008004aa:  nop
   0x008004ac:  nop
End of assembler dump.

to

disassemble 0x496,0x4ae
Dump of assembler code from 0x496 to 0x4ae:
   0x00000496 <main+0>:	call	0x280	;  0x280 <init>
   0x0000049a <main+4>:	call	0x3d6	;  0x3d6 <setup>
   0x0000049e <main+8>:	call	0x3b8	;  0x3b8 <loop>
   0x000004a2 <main+12>:	ldi	r24, 0x00	; 0
   0x000004a4 <main+14>:	ldi	r25, 0x00	; 0
   0x000004a6 <main+16>:	or	r24, r25
   0x000004a8 <main+18>:	breq	.-12     	;  0x49e <main+8>
   0x000004aa <main+20>:	call	0	;  0x0 <__vectors>
End of assembler dump.

Though I seem to have made some mistake while compiling regarding python because GDB keeps complaining about

Python Exception <type 'exceptions.NameError'> Installation error: gdb._execute_unwinders function is missing: 

so it doesn’t make sense to share the binary. But the patch is correct.

2 Likes

@maxgerhardt
Great! Really baffling that they never fixed that bug after all this time. Looks like avr-gdb really is a very low priority for them.

@valeros
I’ve found an interesting workaround. It would help to test things until we get a correct binary.
Instead of this:

disassemble 0x000008ca,0x000008da

Use this:

disassemble (void (*)())0x000008ca, (void (*)())0x000008da

It should return the data on Flash memory instead of SRAM.

1 Like

@valeros @maxgerhardt
Any news regarding that binary?

I’ve started to eval another method to debug AVR microcontrollers, this time using the real hardware and a library without requiring any external tools. Works pretty well, almost as good as simavr, but disassemble has the same problem. I will open a new threat explaining how to use it soon.

1 Like

@msquirogac Still in progress, turns out it’s not quite easy to statically compile a proper self-sufficient GDB that doesn’t depend on external shared libraries.

Hi, I got some news.

I’ve been thinking about the capabilities of simavr that aren’t still supported, like the VCD dumps. I found that not only gtkwave support those files, but also the sigrok suite, which is an amazing tool for debugging electronic systems by looking at the signals they generate.

Now, to make simavr generate those VCD files, some parameters must be passed first, and right now there are only two ways to do it.

The first and most documented way requires passing the elf file directly to simavr as this:

simavr -m atmega328p -f 16000000 .pio/build/nanoatmega328new/firmware.elf

Then, add a block like this example into source code:

#include <avr_mcu_section.h>
AVR_MCU(F_CPU, "atmega328");
const struct avr_mmcu_vcd_trace_t _mytrace[]  _MMCU_ = {
	{ AVR_MCU_VCD_SYMBOL("TX"), .mask = (1 <<2), .what = (void*)&PORTD, }
};

Also the following must be added to platformio.ini

build_flags =
        -Wl,--undefined=_mmcu,--section-start=.mmcu=0x910000

Once done a file with the vcd extension should appear which can be open with this command:

pulseview -I vcd -i capture.vcd

The second method requires neither the library or the build_flags and is easier to set up, but
like the previous one a parameter that must be added to the simavr call. Said command isn’t documented and I just found about it by reviewing its source code. It goes this way:

simavr -m atmega328p -f 16000000 --add-vcd-trace name=kind@addr/mask

Where:
name is a tag that will be used to identify the trace.
kind is related to the AVR_MMCU_TAG_VCD_IRQ, AVR_MMCU_TAG_VCD_TRACE and AVR_MMCU_TAG_VCD_PORTPIN macros and should be trace for what matters.
addr and mask are the memory region and bit mask for the data being traced, and they are expressed in hex, being the former 16bits and the latter 8bits long.

So the call equivalent to the first example would be like this:

simavr -m atmega328p -f 16000000 --add-vcd-trace TX=trace@0x002B/0x04

It should be noticed that both ways give the simulator the same kind of information about the memory that we want to be traced, and both of them require extra parameters for the simavr call, yet only the first one requires the extra compilation flags and code modification.

Now, what is so amazing about sigrok and that VCD file that might be worth the hassle of setting all of that?

The VCD dump has information of all the transitions or changes in the traced memory region as if a tool like a logic analyzer or oscilloscope was connected, but as is a simulated system it isn’t limited to just an IO pin, but it can trace everything even regular variables.

On the other hand, sigrok not only can show those changes but also decode them, so you can see all the bits, their timing, and meaning. A picture is a better way to show this :slight_smile:

@valeros @ivankravets
Is there any way to pass additional arguments to the simavr call?
If not, would it be possible to create an option in the platformio.ini file? Something like debug_extra_args maybe.

1 Like

Some news,
I’ve coded a python script that can be called through the extra_scripts parameter, like this:

platformio.ini

[env:nanoatmega328new]
platform = atmelavr
board = nanoatmega328new
extra_scripts = capture_target.py
custom_capture =
    --add-vcd-trace
    LED=trace@0x0025/0x20

Said script automatizes most of the procedure described before, like adding the linker flags, or passing the parameters to the simulator. It also adds a new parameter called custom_capture which can be used to pass arbitrary arguments to simavr in capture mode, included the --add-vcd-trace parameter, making it possible to use both methods of describing the memory to be traced.

Must be noticed that this doesn’t replace the debug target, but creates a new one that can be invoked with:

pio run -t capture

Once done the file capture.vcd should appear with all the traces previously defined. Right now the only way to stop the capture is to CTRL+C it, and I wouldn’t advise to keep it recording for too long, especially for signals that change very fast, KHz and above range, because the file size can grow very quickly to tens or hundreds of MBytes.

Finally, I created a new repository for this tool and others related to enhance the integration of simavr into platformio. Right now, said script is available together with a modified version of the header avr_mcu_section.h, which forces the compiler not to drop the .mmcu section required for the simulator, and of course an example to make it easier to this idea.

Any feedback would be welcome, I would like to make this platformio-simavr integration even more amazing than what already is.

2 Likes

@valeros
Great news!!
The patch we talked before has just been accepted. mailing list
So now that its official, I suppose it’s just a matter of time until Microchip releases a new version with the fix included. :slight_smile:

2 Likes

lol… it only took …um, well, … 9 years, and that’s with the fix provided 5 years ago… :open_mouth: :laughing:

So there’s hope for other bugs yet! :bug:

Hi all, I just wanted to follow-up on the current status :slight_smile: As a not-very-familiar-with-platformio hobby dev notoriously setting up far-too-complex-for-my-skill-projects, I’m in desparate need for a working debugger solution (I would have taken my Atmel ICE if it worked with Arduino Nano…) While I can build my solutions with simavr tool in platformio, I would have expected to see live DDRx and PORTx registers (and almost all other periperal registers like serial buffer etc.) implemented off the shelf which is not the case.
So here my questions: What is the current status of implementation? How does platformio connect with simavr to handle these things?

Post 33 from @msquirogac was somewhat enlightening but this just creates a side-simavr solution which again I couldn’t get “live”.

1 Like

Hm good question. Usually the internal register map of a microcontroller is specified in SVD files which the PlatformIO unified debugger can load and then display the current value of it. This is indeed working already working, see screenshot and that little section “PERIPHERALS” – that info was decoded from the .svd file and the current value of that was captured using the gdb client.

But SVD files are mostly an ARM microcontroller type thing, and Atmel provides atdf that describe their hardware registers (per this). This is a project that can convert these files to SVD files though, and it supports the atmega328p – haven’t tried it yet or seen the corresponding SVD file though.

The way it internally works for the SVD files is that the board definition declares the path to the file

which maps e.g. to the file found in platform-atmelsam/misc/svd at develop · platformio/platform-atmelsam · GitHub in this case. But GitHub - platformio/platform-atmelavr: Atmel AVR: development platform for PlatformIO has no such SVD files.

If that file is obtained and referenced in the e.g. uno.json file, it’s half the story. The other is whether the current version of the PlatformIO Unified debugger will load the SVD file correctly, and whether avr-gdb will spit out the correct value for a certain register, if asked for. AVRs have a different memory architecture (harvard, I/O registers live in data memory and not in program memory), if the debugger asks GDB the wrong way for the value of a register address, it might get garbage or crash.

Also, I wanted to try out dwire since reports on that in VSCode PIO debugger for AVR - #9 by fedex03 of it partially working, I’m gonna give this a spin in combination with SVD files.

If it works, it means we have live-debugging of a running chip via debug-wire (the NRST line abused as bi-directional data line for debugging) with a decoded register view.

Note that currently also avr-stub works, but it inherently needs the main serial to communicate and it also needs flash space. But a working live-example of that can be seen in How to debug on Arduino mega 2560 - #2 by maxgerhardt.

Edit:

But that does work with avarice as a debugging server, doesn’t it? So, you can get live-debugging with that, too. See CLion / Avarice debug not working

1 Like