Skip to content

Conversation

@stefanrueger
Copy link
Collaborator

The new terminal command disasm disassembles the selected memory section. It knows about the register file of the part, so you get these symbols for free. You can also provide an ASCII tag file (and generate one via tools/elf2tag from an .elf file).

A big shoutout to @johndoe31415 whose 17 year old avrdisas provides the base for this PR. It is well written, relatively easy to port, and it was relatively easy to add 8 opcodes that had cropped up in the meantime and to add some 20 or so unallocated opcodes that are known to behave in a certain way (the famous 0xffff that behaves like sbrs r31, 7). Option -d shows the unallocated opcodes as u/, so, eg, u/sbrs r31, 7. Option -D (the default) switches back to only disassembling the opcodes that the architecture of the part knows.

Here an example: use the files in urclock_cs8_ad.zip

$ avrdude -qq -p m328p -c dryrun -U urclock_cs8_ad.hex -t
avrdude> disasm flash 0x7e00 32
.equ PORTB_DDRB, 0x4
.equ PORTB_PORTB, 0x5
.equ SPI_SPCR, 0x2c
.equ CPU_MCUSR, 0x34
7e00:   11 24           clr     r1
7e02:   c4 b7           in      r28, CPU_MCUSR
7e04:   2c 2e           mov     r2, r28
7e06:   14 be           out     CPU_MCUSR, r1
7e08:   80 e0           ldi     r24, 0x00       ; 0
7e0a:   bd d0           rcall   watchdogConfig
7e0c:   c1 fd           sbrc    r28, 1          ; 0x02 = 2
7e0e:   3e c0           rjmp    ldi_wdto
7e10:   c3 ff           sbrs    r28, 3          ; 0x08 = 8
7e12:   3b c0           rjmp    rjmp_application
7e14:   85 e1           ldi     r24, 0x15       ; 21
7e16:   85 b9           out     PORTB_PORTB, r24
7e18:   8d e2           ldi     r24, 0x2d       ; 45
7e1a:   84 b9           out     PORTB_DDRB, r24
7e1c:   80 e5           ldi     r24, 0x50       ; 80
7e1e:   8c bd           out     SPI_SPCR, r24

avrdude> disasm flash 0x7e00 32 -t=urclock_cs8_ad.tag 
.equ PORTB_DDRB, 0x4
.equ PORTB_PORTB, 0x5
.equ SPI_SPCR, 0x2c
.equ CPU_MCUSR, 0x34
7e00:   11 24           clr     r1
7e02:   c4 b7           in      r28, CPU_MCUSR
7e04:   2c 2e           mov     r2, r28
7e06:   14 be           out     CPU_MCUSR, r1
7e08:   80 e0           ldi     r24, 0x00       ; 0
7e0a:   bd d0           rcall   watchdogConfig
7e0c:   c1 fd           sbrc    r28, 1          ; 0x02 = 2
7e0e:   3e c0           rjmp    ldi_wdto
7e10:   c3 ff           sbrs    r28, 3          ; 0x08 = 8
7e12:   3b c0           rjmp    rjmp_application
7e14:   85 e1           ldi     r24, 0x15       ; 21
7e16:   85 b9           out     PORTB_PORTB, r24
7e18:   8d e2           ldi     r24, 0x2d       ; 45
7e1a:   84 b9           out     PORTB_DDRB, r24
7e1c:   80 e5           ldi     r24, 0x50       ; 80
7e1e:   8c bd           out     SPI_SPCR, r24

vrdude> dis -d fl 0x7ff0 20
7ff0:   08 95           ret

7ff2:   ff ff           u/sbrs  r31, 7          ; 0x80 = 128
7ff4:   ff ff           u/sbrs  r31, 7          ; 0x80 = 128
7ff6:   ff ff           u/sbrs  r31, 7          ; 0x80 = 128
7ff8:   ff ff           u/sbrs  r31, 7          ; 0x80 = 128
7ffa:   04 00           u/nop
7ffc:   d2 cf           rjmp    pgm_write_page
7ffe:   f1 40           sbci    r31, 0x01       ; 1
   0:   ff ff           u/sbrs  r31, 7          ; 0x80 = 128
   2:   ff ff           u/sbrs  r31, 7          ; 0x80 = 128

avrdude> di -?
Syntax: disasm [<opts>] <mem> <addr> <len> # Entire region
        disasm [<opts>] <mem> <addr>       # Start at <addr>
        disasm [<opts>] <mem>              # Continue with memory where left off
        disasm [<opts>]                    # Continue with most recently shown <mem>
Function: disassemble memory section
Options:
    -a        show addresses (default), -A don't show addresses
    -o        show opcode bytes (default), -O don't show opcode bytes
    -c        show comments (default), -C don't show comments
    -q        show call cycles, -Q don't show call cycles (default)
    -s        use avr-gcc code style (default), -S use AVR inst set code style
    -l        preprocess jump/call (default), -L don't preprocess jump/call
    -d        decode all (even unallocated) opcodes; -D only those of the part
    -z        zap list of jumps/calls before disassembly
    -t=<file> set the tagfile (zaps old tagfile contents)

Both the <addr> and <len> can be negative numbers; a negative <addr> starts
an interval from that many bytes below the memory size; a negative <len> ends
the interval at that many bytes below the memory size.

The latter two versions of the command page through the memory with a page
size of the last used effective length (256 bytes default)

Anyway, have fun.

@stefanrueger
Copy link
Collaborator Author

The AVRDUDE terminal disasm command is now pretty much as far as I can/want to take it in terms of functionality. Style and formatting is likely to be a personal choice. I have tried to make it crisp and clear but am happy to consider suggestions.

@johndoe31415 Johannes, please have a look at the copyright, reference and acknowledgement of your work and let me know whether you want changes, eg, a different email address (or none for that matter).

@MCUdude, your little test example should now be formatted as below. Not sure it can be made cripser.

       .equ    io.ddrb,  0x17
       .equ    io.portb, 0x18

       .text
main:
L000:  rjmp    Label1               ; L016
L002:  rjmp    Label0               ; L014
L004:  rjmp    Label0               ; L014
L006:  rjmp    Label0               ; L014
L008:  rjmp    Label0               ; L014
L00a:  rjmp    Label0               ; L014
L00c:  rjmp    Label0               ; L014
L00e:  rjmp    Label0               ; L014
L010:  rjmp    Label0               ; L014
L012:  rjmp    .+0                  ; L014

; Rjmp from L002, L004, L006, L008, L00a, L00c, L00e, L010
Label0:
L014:  reti

Label1:                             ; Rjmp from L000
L016:  clr     r1
L018:  ldi     r24, 0x04            ; 4
L01a:  out     io.ddrb, r24

Label2:                             ; Rjmp from L020
L01c:  out     io.portb, r24
L01e:  out     io.portb, r1
L020:  rjmp    Label2               ; L01c

L022:  .fill   495, 2, 0xffff

@mcuee, @MCUdude Here some tips for testing: Clone this PR and copy an arbitrary .hex file of yours for any MCU you can think of into the directory of the PR. If you have the .elf file of when you created it, then copy that as well. Then call this bash script dis-test with the MCU name and application name. This should create a temporary test directory /tmp/<app>-dis-test/ with the disassembled .S source. If that can be compiled to the same flash content you should see something like this:

$ dis-test atmega328p blink
ALL OK: have a look at blink.S file in /tmp/blink-dis-test

The part name must be the full name in lower case. You may need to modify the avr-cc line for modern parts (as they need a service pack argument), but disassembly should still be OK,.

@mcuee
Copy link
Collaborator

mcuee commented Jul 28, 2024

Test using picoboot.
https://github.com/nerdralph/picoboot

Build with the default Makefile.

MINGW64 /c/work/avr/picoboot
$ make
Makefile:51: warning: overriding recipe for target 'picoboot.bin'
Makefile:42: warning: ignoring old recipe for target 'picoboot.bin'
avr-gcc -mmcu=atmega328 -DF_CPU=16000000 -nostdlib -x assembler-with-cpp -c picobootSerial.S -o picobootSerial.o
avr-gcc -mmcu=atmega328 -DF_CPU=16000000 -nostdlib -Wl,-section-start=.bootloader=0xbbe -o picobootSerial.elf picobootSerial.o
avr-objcopy -O ihex picobootSerial.elf picobootSerial.hex

MINGW64 /c/work/avr/avrdude_test/avrdude_sr/build_mingw64_nt-10.0-22631/src
$ cat picobootSerial.hex
:10000000DFC5229A1A9AFEE1F8D53197E9F7FACFBF
:100BBE0021CA489BFDCF0027519A0DD0E62F0BD0AE
:100BCE00F62F09D0599A07D00213FFCF67BFE895C9
:100BDE000F015998F2CF60E82CE04899FECF06D06D
:100BEE0028E0489908946795D0F706272150F1F729
:020BFE00089558
:00000001FF

$ ./dis-test atmega328 picobootSerial
./dis-test: line 52: tools/elf2tag: No such file or directory
avrdude error: (cmd) command disasm is invalid

(Copy tools/elf2tag)

$ ./dis-test atmega328 picobootSerial
avrdude error: (cmd) command disasm is invalid

$ git log
commit c51d50ce0952c1a9a1a820511978a0248fb2f70b (HEAD -> disasm, origin/disasm)
Author: Stefan Rueger <[email protected]>
Date:   Sun Jul 28 00:24:39 2024 +0100

    Add sram.start and sram.end to known symbols

@mcuee
Copy link
Collaborator

mcuee commented Jul 28, 2024

@stefanrueger

Maybe the above error is due to some bash compatibility thingy between MSYS2 bash and Linux bash.

@mcuee
Copy link
Collaborator

mcuee commented Jul 28, 2024

The disasm feature itself seems to work fine.

Original source code in assembly.
https://github.com/nerdralph/picoboot/blob/master/picobootSerial.S

$ ./avrdude -qq -p m328 -c dryrun -U ./picobootSerial.hex -t
avrdude> dump flash 0x00 0x10
0000  df c5 22 9a 1a 9a fe e1  f8 d5 31 97 e9 f7 fa cf  |..".......1.....|
avrdude> dump flash 0x0bbe 0x50
0bbe  21 ca 48 9b fd cf 00 27  51 9a 0d d0 e6 2f 0b d0  |!.H....'Q. ../ .|
0bce  f6 2f 09 d0 59 9a 07 d0  02 13 ff cf 67 bf e8 95  |./ .Y.......g...|
0bde  0f 01 59 98 f2 cf 60 e8  2c e0 48 99 fe cf 06 d0  |..Y...`.,.H.....|
0bee  28 e0 48 99 08 94 67 95  d0 f7 06 27 21 50 f1 f7  |(.H...g....'!P..|
0bfe  08 95 ff ff ff ff ff ff  ff ff ff ff ff ff ff ff  |................|

avrdude> disasm flash 0x0bbe 0x42
                    .equ    io.pind,   0x09
                    .equ    io.ddrd,   0x0a
                    .equ    io.portd,  0x0b
                    .equ    io.spmcsr, 0x37

Label0:                                          ; Rjmp from L0bc2
L0bbe: 21 ca        rjmp    .-3006               ; L0002
L0bc0: 48 9b        sbis    io.pind, 0           ; Bit 0 = 0x01
L0bc2: fd cf        rjmp    Label0               ; L0bbe
L0bc4: 00 27        clr     r16
L0bc6: 51 9a        sbi     io.ddrd, 1           ; Bit 1 = 0x02

Label1:                                          ; Rjmp from L0be2
L0bc8: 0d d0        rcall   Subroutine0          ; L0be4
L0bca: e6 2f        mov     r30, r22
L0bcc: 0b d0        rcall   Subroutine0          ; L0be4
L0bce: f6 2f        mov     r31, r22
L0bd0: 09 d0        rcall   Subroutine0          ; L0be4
L0bd2: 59 9a        sbi     io.portd, 1          ; Bit 1 = 0x02
L0bd4: 07 d0        rcall   Subroutine0          ; L0be4
L0bd6: 02 13        cpse    r16, r18

Label2:                                          ; Rjmp from L0bd8
L0bd8: ff cf        rjmp    Label2               ; L0bd8
L0bda: 67 bf        out     io.spmcsr, r22
L0bdc: e8 95        spm
L0bde: 0f 01        movw    r0, r30
L0be0: 59 98        cbi     io.portd, 1          ; Bit 1 = 0x02
L0be2: f2 cf        rjmp    Label1               ; L0bc8

; Rcall from L0bc8, L0bcc, L0bd0, L0bd4
Subroutine0:
L0be4: 60 e8        ldi     r22, 0x80            ; 128
L0be6: 2c e0        ldi     r18, 0x0c            ; 12

Label3:                                          ; Rjmp from L0bea
L0be8: 48 99        sbic    io.pind, 0           ; Bit 0 = 0x01
L0bea: fe cf        rjmp    Label3               ; L0be8

Label4:                                          ; Brcc from L0bf6
L0bec: 06 d0        rcall   Subroutine1          ; L0bfa
L0bee: 28 e0        ldi     r18, 0x08            ; 8
L0bf0: 48 99        sbic    io.pind, 0           ; Bit 0 = 0x01
L0bf2: 08 94        sec
L0bf4: 67 95        ror     r22
L0bf6: d0 f7        brcc    Label4               ; L0bec
L0bf8: 06 27        eor     r16, r22

; Rcall from L0bec
; Brne from L0bfc
Subroutine1:
L0bfa: 21 50        subi    r18, 0x01            ; 1
L0bfc: f1 f7        brne    Subroutine1          ; L0bfa
L0bfe: 08 95        ret
avrdude> quit

@stefanrueger
Copy link
Collaborator Author

stefanrueger commented Jul 28, 2024

@mcuee dis-test assumes avrdude to be the one of this PR, but it seems to call a different one. Maybe change avrdude in dis-test to ./avrdude? Also, it assumes to be running in the directory of a clone of the PR. tools/elf2tag was not found, so either it's not executable or dis-test was called from elsewhere. Can you execute the new tool elf2tag and does it work under Windows?

Anyway it's the last three lines of the dis-test file that are the core of it.

  • Create an x.S file from the terminal disasm -g flash 0 -1 command (note it should be > not >> in dis-test)
  • Compile the x.S file to x.elf using avr-gcc
  • Compare the x.elf file with the contents of the flash the was used for creating the x.S file

I am sure you can write your own, but it's the disassemble, compile, compare cycle that proves the disassembly correct.

@mcuee
Copy link
Collaborator

mcuee commented Jul 28, 2024

Msys2 shell under Windows is indeed a bit strange. The following bash script works.

  1. /tmp directory problem
  2. need to use "../avrdude.exe".
#!/usr/bin/env bash

# Published under GNU General Public License, version 2 (GPL-2.0)
# Copyright (C) 2024 Stefan Rueger <[email protected]>

# Tests AVRDUDE's terminal disasm -g command on app.hex

mcu=atmega328p
app=blink
tag=tools/elf2tag
tmp=.
start=0x0000

progname=$(basename "$0")

Usage() {
cat <<END
$progname [mcu|-] [app]
Function: tests AVRDUDE's terminal disasm -g command on app.hex
Options: none
Notes:
  - Test files are created in /tmp/[app]-$progname
  - Also uses optional app.elf to create a tag file app.tag
Examples:
$ $progname (tests disasm on default blink.hex for the default ATmega328P MCU)
$ $progname attiny13a crocodile-gate
$ $progname - crocodile-gate (assumes default ATmega328P MCU)
END
}

if [[ $# -eq 1 && $1 == "-h" ]]; then
  Usage
  exit
fi
if [[ $# -gt 0 && $1 != "-" ]]; then
  mcu=$1
fi
if [[ $# -gt 1 ]]; then
  app=$2
fi

if [ ! -r $app.hex ]; then
echo $progname: cannot read $app.hex
exit
fi  

tmpdir="$tmp/$app-$progname"
rm -rf "$tmpdir"; mkdir -p "$tmpdir"

top=
if [ -r "$app.elf" ]; then
$tag $app.elf >"$tmpdir/$app.tag"
top="-t=$app.tag"
fi

cp -a "$app.hex" "$tmpdir/"
cd "$tmpdir"

######
# Generate $app.S by loading $app.hex into the dryrun MCU and running disasm -g
# Then compile $app.S to $app.elf and compare with original $app.hex
#
../avrdude.exe -qq -p "$mcu" -c dryrun -U "$app.hex:I" -T "disasm -g "$top" flash $start -1" >> "$app.S" || exit 1
avr-gcc -mmcu="$mcu" -nostdlib -Wl,--section-start=.text=$start "$app.S" -o "$app.elf"
../avrdude.exe -qq -p "$mcu" -c dryrun -U "$app.hex:I" -U flash:v:"$app.elf" && echo "ALL OK: have a look at $app.S file in $tmpdir"

Run log:

MINGW64 /c/work/avr/avrdude_test/avrdude_sr/build_mingw64_nt-10.0-22631/src
$ ls
ac_cfg.h      avrdude.conf.in  avrdude.spec         CMakeFiles  libavrdude.a      picobootSerial.elf  picobootSerial-dis-test  windows.rc
avrdude.conf  avrdude.exe      cmake_install.cmake  dis-test    libavrdude.dll.a  picobootSerial.hex  tools

$ ./dis-test atmega328 picobootSerial
ALL OK: have a look at picobootSerial.S file in ./picobootSerial-dis-test

$ ls ./picobootSerial-dis-test/
picobootSerial.elf  picobootSerial.hex  picobootSerial.S  picobootSerial.tag

$ cat ./picobootSerial-dis-test/picobootSerial.S
        .equ    io.pinb,   0x03
        .equ    io.ddrb,   0x04
        .equ    io.pind,   0x09
        .equ    io.ddrd,   0x0a
        .equ    io.portd,  0x0b
        .equ    io.spmcsr, 0x37

        .text
main:
L0000:  rjmp    BootStart            ; L0bc0

BlinkLED:                            ; Rjmp from L0bbe
L0002:  sbi     io.ddrb, 2           ; Bit 2 = 0x04

Blink:                               ; Rjmp from L000e
L0004:  sbi     io.pinb, 2           ; Bit 2 = 0x04
L0006:  ldi     r31, 0x1e            ; 30

DelayLoop:                           ; Brne from L000c
L0008:  rcall   Delay3Cycle          ; L0bfa
L000a:  sbiw    r30, 0x01            ; 1
L000c:  brne    DelayLoop            ; L0008
L000e:  rjmp    Blink                ; L0004

L0010:  .fill   1495, 2, 0xffff

VirtualReset:                        ; Rjmp from L0bc2
L0bbe:  rjmp    BlinkLED             ; L0002

BootStart:                           ; Rjmp from L0000
L0bc0:  sbis    io.pind, 0           ; Bit 0 = 0x01
L0bc2:  rjmp    VirtualReset         ; L0bbe
L0bc4:  clr     r16
L0bc6:  sbi     io.ddrd, 1           ; Bit 1 = 0x02

CommandLoop:                         ; Rjmp from L0be2
L0bc8:  rcall   RxByte               ; L0be4
L0bca:  mov     r30, r22
L0bcc:  rcall   RxByte               ; L0be4
L0bce:  mov     r31, r22
L0bd0:  rcall   RxByte               ; L0be4
L0bd2:  sbi     io.portd, 1          ; Bit 1 = 0x02
L0bd4:  rcall   RxByte               ; L0be4
L0bd6:  cpse    r16, r18

Halt:                                ; Rjmp from L0bd8
L0bd8:  rjmp    Halt                 ; L0bd8
L0bda:  out     io.spmcsr, r22
L0bdc:  spm
L0bde:  movw    r0, r30
L0be0:  cbi     io.portd, 1          ; Bit 1 = 0x02
L0be2:  rjmp    CommandLoop          ; L0bc8

RxByte:                              ; Rcall from L0bc8, L0bcc, L0bd0, L0bd4
L0be4:  ldi     r22, 0x80            ; 128
L0be6:  ldi     r18, 0x0c            ; 12

WaitStart:                           ; Rjmp from L0bea
L0be8:  sbic    io.pind, 0           ; Bit 0 = 0x01
L0bea:  rjmp    WaitStart            ; L0be8

RxBit:                               ; Brcc from L0bf6
L0bec:  rcall   Delay3Cycle          ; L0bfa
L0bee:  ldi     r18, 0x08            ; 8
L0bf0:  sbic    io.pind, 0           ; Bit 0 = 0x01
L0bf2:  sec
L0bf4:  ror     r22
L0bf6:  brcc    RxBit                ; L0bec
L0bf8:  eor     r16, r22

; Rcall from L0008, L0bec
; Brne from L0bfc
Delay3Cycle:
L0bfa:  subi    r18, 0x01            ; 1
L0bfc:  brne    Delay3Cycle          ; L0bfa
L0bfe:  ret

L0c00:  .fill   14848, 2, 0xffff

@mcuee
Copy link
Collaborator

mcuee commented Jul 29, 2024

Using the urclock example from the first post.

$ ./dis-test atmega328p urclock_cs8_ad
ALL OK: have a look at urclock_cs8_ad.S file in ./urclock_cs8_ad-dis-test

$ cat ./urclock_cs8_ad-dis-test/urclock_cs8_ad.S
        .equ    io.ddrb,    0x04
        .equ    io.portb,   0x05
        .equ    io.pind,    0x09
        .equ    io.eecr,    0x1f
        .equ    io.eedr,    0x20
        .equ    io.eearl,   0x21
        .equ    io.eearh,   0x22
        .equ    io.spcr,    0x2c
        .equ    io.spsr,    0x2d
        .equ    io.spdr,    0x2e
        .equ    io.mcusr,   0x34
        .equ    io.spmcsr,  0x37
        .equ    mem.wdtcsr, 0x60
        .equ    mem.ucsr0a, 0xc0
        .equ    mem.udr0,   0xc6
        .equ    sram.start, 0x100

        .text
main:
L0000:  .fill   16128, 2, 0xffff

L7e00:  clr     r1
L7e02:  in      r28, io.mcusr
L7e04:  mov     r2, r28
L7e06:  out     io.mcusr, r1
L7e08:  ldi     r24, 0x00            ; 0
L7e0a:  rcall   Subroutine4          ; L7f86
L7e0c:  sbrc    r28, 1               ; Bit 1 = 0x02
L7e0e:  rjmp    Label7               ; L7e8c
L7e10:  sbrs    r28, 3               ; Bit 3 = 0x08
L7e12:  rjmp    Label6               ; L7e8a
L7e14:  ldi     r24, 0x15            ; 21
L7e16:  out     io.portb, r24
L7e18:  ldi     r24, 0x2d            ; 45
L7e1a:  out     io.ddrb, r24
L7e1c:  ldi     r24, 0x50            ; 80
L7e1e:  out     io.spcr, r24
L7e20:  ldi     r31, 0x00            ; 0
L7e22:  ldi     r30, 0x00            ; 0
L7e24:  ldi     r28, 0xfc            ; 252

Label0:                              ; Brne from L7e64
L7e26:  cbi     io.portb, 0          ; Bit 0 = 0x01
L7e28:  ldi     r24, 0x03            ; 3
L7e2a:  rcall   Subroutine0          ; L7f48
L7e2c:  ldi     r24, 0x00            ; 0
L7e2e:  rcall   Subroutine0          ; L7f48
L7e30:  mov     r24, r31
L7e32:  rcall   Subroutine0          ; L7f48
L7e34:  mov     r24, r30
L7e36:  rcall   Subroutine0          ; L7f48
L7e38:  ldi     r26, lo8(sram.start) ; 0x00 = 0
L7e3a:  ldi     r27, hi8(sram.start) ; 0x01 = 1

Label1:                              ; Brne from L7e44
L7e3c:  ldi     r24, 0x00            ; 0
L7e3e:  rcall   Subroutine0          ; L7f48
L7e40:  st      X+, r24
L7e42:  cpi     r26, 0x80            ; 128
L7e44:  brne    Label1               ; L7e3c
L7e46:  ldi     r26, 0x00            ; 0
L7e48:  sbi     io.portb, 0          ; Bit 0 = 0x01
L7e4a:  sbiw    r30, 0x00            ; 0
L7e4c:  brne    Label2               ; L7e5c
L7e4e:  lds     r24, 0x101
L7e52:  cpi     r24, 0x94            ; 148
L7e54:  breq    Label2               ; L7e5c
L7e56:  cbr     r24, 0x0f            ; 15
L7e58:  cpi     r24, 0xc0            ; 192
L7e5a:  brne    Label5               ; L7e84

; Breq from L7e54
; Brne from L7e4c
Label2:
L7e5c:  rcall   Subroutine6          ; L7fa6
L7e5e:  subi    r30, 0x80            ; 128
L7e60:  sbci    r31, 0xff            ; 255
L7e62:  subi    r28, 0x01            ; 1
L7e64:  brne    Label0               ; L7e26
L7e66:  cbi     io.portb, 0          ; Bit 0 = 0x01
L7e68:  ldi     r24, 0x06            ; 6
L7e6a:  rcall   Subroutine0          ; L7f48
L7e6c:  sbi     io.portb, 0          ; Bit 0 = 0x01
L7e6e:  cbi     io.portb, 0          ; Bit 0 = 0x01
L7e70:  ldi     r16, 0x04            ; 4
L7e72:  ldi     r24, 0x20            ; 32

Label3:                              ; Brne from L7e7a
L7e74:  rcall   Subroutine0          ; L7f48
L7e76:  ldi     r24, 0x00            ; 0
L7e78:  subi    r16, 0x01            ; 1
L7e7a:  brne    Label3               ; L7e74
L7e7c:  sbi     io.portb, 0          ; Bit 0 = 0x01
L7e7e:  ldi     r24, 0x0e            ; 14
L7e80:  rcall   Subroutine4          ; L7f86

Label4:                              ; Rjmp from L7e82
L7e82:  rjmp    Label4               ; L7e82

Label5:                              ; Brne from L7e5a
L7e84:  out     io.spcr, r1
L7e86:  out     io.ddrb, r1
L7e88:  out     io.portb, r1

Label6:                              ; Rjmp from L7e12
L7e8a:  rjmp    .+372                ; L0000

Label7:                              ; Rjmp from L7e0e
L7e8c:  ldi     r24, 0x0e            ; 14
L7e8e:  rcall   Subroutine4          ; L7f86
L7e90:  ldi     r30, lo8(mem.ucsr0a) ; 0xc0 = 192
L7e92:  ldi     r31, hi8(mem.ucsr0a) ; 0x00 = 0
L7e94:  ldi     r26, 0x7f            ; 127
L7e96:  ser     r27

Label8:                              ; Rjmp from L7e9a
L7e98:  sbic    io.pind, 0           ; Bit 0 = 0x01
L7e9a:  rjmp    Label8               ; L7e98

Label9:                              ; Rjmp from L7ea0
L7e9c:  adiw    r26, 0x20            ; 32
L7e9e:  sbis    io.pind, 0           ; Bit 0 = 0x01
L7ea0:  rjmp    Label9               ; L7e9c
L7ea2:  std     Z+4, r27

Label10:                             ; Brne from L7ea6
L7ea4:  sbiw    r26, 0x01            ; 1
L7ea6:  brne    Label10              ; L7ea4
L7ea8:  ldi     r27, 0x02            ; 2
L7eaa:  st      Z, r27
L7eac:  ldi     r27, 0x18            ; 24
L7eae:  std     Z+1, r27

Label11:                             ; Rjmp from L7ed0
L7eb0:  rcall   Subroutine2          ; L7f62
L7eb2:  mov     r28, r24
L7eb4:  cpi     r24, 0x52            ; 82
L7eb6:  brne    Label14              ; L7ed2
L7eb8:  rcall   Subroutine5          ; L7f92
L7eba:  ldi     r30, 0x80            ; 128
L7ebc:  ldi     r31, 0x7d            ; 125

Label12:                             ; Brcc from L7eca
L7ebe:  wdr
L7ec0:  ldi     r24, 0x03            ; 3
L7ec2:  rcall   Subroutine8          ; L7fe4
L7ec4:  rcall   Subroutine7          ; L7fe2
L7ec6:  subi    r30, 0x80            ; 128
L7ec8:  sbc     r31, r1
L7eca:  brcc    Label12              ; L7ebe

Label13:                             ; Rjmp from L7f06, L7f0a, L7f1e, L7f3a, L7f46
L7ecc:  ldi     r24, 0x77            ; 119
L7ece:  rcall   Subroutine1          ; L7f54
L7ed0:  rjmp    Label11              ; L7eb0

Label14:                             ; Brne from L7eb6
L7ed2:  cbr     r24, 0x02            ; 2
L7ed4:  brne    Label19              ; L7f0c
L7ed6:  rcall   Subroutine3          ; L7f7c
L7ed8:  mov     r29, r24
L7eda:  ldi     r26, lo8(sram.start) ; 0x00 = 0
L7edc:  ldi     r27, hi8(sram.start) ; 0x01 = 1

Label15:                             ; Rjmp from L7ee4
L7ede:  rcall   Subroutine2          ; L7f62
L7ee0:  st      X+, r24
L7ee2:  cpse    r29, r26
L7ee4:  rjmp    Label15              ; L7ede
L7ee6:  ldi     r26, lo8(sram.start) ; 0x00 = 0
L7ee8:  ldi     r27, hi8(sram.start) ; 0x01 = 1
L7eea:  rcall   Subroutine5          ; L7f92
L7eec:  cpse    r28, r1
L7eee:  rjmp    Label18              ; L7f08

Label16:                             ; Brne from L7f04
L7ef0:  out     io.eearh, r31
L7ef2:  out     io.eearl, r30
L7ef4:  ld      r24, X+
L7ef6:  out     io.eedr, r24
L7ef8:  sbi     io.eecr, 2           ; Bit 2 = 0x04
L7efa:  sbi     io.eecr, 1           ; Bit 1 = 0x02
L7efc:  adiw    r30, 0x01            ; 1

Label17:                             ; Rjmp from L7f00
L7efe:  sbic    io.eecr, 1           ; Bit 1 = 0x02
L7f00:  rjmp    Label17              ; L7efe
L7f02:  subi    r29, 0x01            ; 1
L7f04:  brne    Label16              ; L7ef0
L7f06:  rjmp    Label13              ; L7ecc

Label18:                             ; Rjmp from L7eee
L7f08:  rcall   Subroutine6          ; L7fa6
L7f0a:  rjmp    Label13              ; L7ecc

Label19:                             ; Brne from L7ed4
L7f0c:  cpi     r28, 0x03            ; 3
L7f0e:  brne    Label21              ; L7f20
L7f10:  rcall   Subroutine3          ; L7f7c
L7f12:  mov     r28, r24
L7f14:  rcall   Subroutine5          ; L7f92

Label20:                             ; Brne from L7f1c
L7f16:  lpm     r24, Z+
L7f18:  rcall   Subroutine1          ; L7f54
L7f1a:  subi    r28, 0x01            ; 1
L7f1c:  brne    Label20              ; L7f16
L7f1e:  rjmp    Label13              ; L7ecc

Label21:                             ; Brne from L7f0e
L7f20:  cpi     r28, 0x01            ; 1
L7f22:  brne    Label23              ; L7f3c
L7f24:  rcall   Subroutine3          ; L7f7c
L7f26:  mov     r28, r24
L7f28:  rcall   Subroutine5          ; L7f92

Label22:                             ; Brne from L7f38
L7f2a:  out     io.eearh, r31
L7f2c:  out     io.eearl, r30
L7f2e:  sbi     io.eecr, 0           ; Bit 0 = 0x01
L7f30:  in      r24, io.eedr
L7f32:  rcall   Subroutine1          ; L7f54
L7f34:  adiw    r30, 0x01            ; 1
L7f36:  subi    r28, 0x01            ; 1
L7f38:  brne    Label22              ; L7f2a
L7f3a:  rjmp    Label13              ; L7ecc

Label23:                             ; Brne from L7f22
L7f3c:  cpi     r28, 0x51            ; 81
L7f3e:  brne    Label24              ; L7f44
L7f40:  ldi     r24, 0x08            ; 8
L7f42:  rcall   Subroutine4          ; L7f86

Label24:                             ; Brne from L7f3e
L7f44:  rcall   Subroutine5          ; L7f92
L7f46:  rjmp    Label13              ; L7ecc

; Rcall from L7e2a, L7e2e, L7e32, L7e36, L7e3e, L7e6a, L7e74
Subroutine0:
L7f48:  out     io.spdr, r24

Label25:                             ; Rjmp from L7f4e
L7f4a:  in      r24, io.spsr
L7f4c:  sbrs    r24, 7               ; Bit 7 = 0x80
L7f4e:  rjmp    Label25              ; L7f4a
L7f50:  in      r24, io.spdr
L7f52:  ret

; Rjmp from L7f5a, L7fa0
; Rcall from L7ece, L7f18, L7f32
Subroutine1:
L7f54:  lds     r25, mem.ucsr0a
L7f58:  sbrs    r25, 5               ; Bit 5 = 0x20
L7f5a:  rjmp    Subroutine1          ; L7f54
L7f5c:  sts     mem.udr0, r24
L7f60:  ret

; Rjmp from L7f84
; Rcall from L7eb0, L7ede, L7f7c, L7f80, L7f92
Subroutine2:
L7f62:  sbi     io.portb, 1          ; Bit 1 = 0x02
L7f64:  sbi     io.ddrb, 1           ; Bit 1 = 0x02

Label26:                             ; Rjmp from L7f6c
L7f66:  lds     r24, mem.ucsr0a
L7f6a:  sbrs    r24, 7               ; Bit 7 = 0x80
L7f6c:  rjmp    Label26              ; L7f66
L7f6e:  sbrc    r24, 4               ; Bit 4 = 0x10
L7f70:  rjmp    Label27              ; L7f98
L7f72:  wdr
L7f74:  lds     r24, mem.udr0
L7f78:  cbi     io.portb, 1          ; Bit 1 = 0x02
L7f7a:  ret

Subroutine3:                         ; Rcall from L7ed6, L7f10, L7f24
L7f7c:  rcall   Subroutine2          ; L7f62
L7f7e:  mov     r30, r24
L7f80:  rcall   Subroutine2          ; L7f62
L7f82:  mov     r31, r24
L7f84:  rjmp    Subroutine2          ; L7f62

Subroutine4:                         ; Rcall from L7e0a, L7e80, L7e8e, L7f42, L7f9a
L7f86:  ldi     r25, 0x18            ; 24
L7f88:  sts     mem.wdtcsr, r25
L7f8c:  sts     mem.wdtcsr, r24
L7f90:  ret

Subroutine5:                         ; Rcall from L7eb8, L7eea, L7f14, L7f28, L7f44
L7f92:  rcall   Subroutine2          ; L7f62
L7f94:  cpi     r24, 0x20            ; 32
L7f96:  breq    Label29              ; L7f9e

Label27:                             ; Rjmp from L7f70
L7f98:  ldi     r24, 0x08            ; 8
L7f9a:  rcall   Subroutine4          ; L7f86

Label28:                             ; Rjmp from L7f9c
L7f9c:  rjmp    Label28              ; L7f9c

Label29:                             ; Breq from L7f96
L7f9e:  ldi     r24, 0xa0            ; 160
L7fa0:  rjmp    Subroutine1          ; L7f54

Label30:                             ; Rjmp from L7ffc
L7fa2:  movw    r30, r22
L7fa4:  movw    r26, r24

Subroutine6:                         ; Rcall from L7e5c, L7f08
L7fa6:  andi    r30, 0x80            ; 128
L7fa8:  ldi     r23, 0x80            ; 128
L7faa:  add     r23, r26
L7fac:  movw    r18, r30
L7fae:  cpi     r31, 0x7e            ; 126
L7fb0:  brcc    Label34              ; L7fee
L7fb2:  ldi     r25, 0x00            ; 0

Label31:                             ; Rjmp from L7fbe
L7fb4:  lpm     r20, Z+
L7fb6:  ld      r21, X+
L7fb8:  cpse    r20, r21
L7fba:  ori     r25, 0x01            ; 1
L7fbc:  cpse    r26, r23
L7fbe:  rjmp    Label31              ; L7fb4
L7fc0:  movw    r30, r18
L7fc2:  subi    r26, 0x80            ; 128
L7fc4:  sbc     r27, r1
L7fc6:  sbrs    r25, 0               ; Bit 0 = 0x01
L7fc8:  rjmp    Label34              ; L7fee
L7fca:  ldi     r24, 0x03            ; 3
L7fcc:  rcall   Subroutine8          ; L7fe4
L7fce:  ldi     r24, 0x01            ; 1

Label32:                             ; Rjmp from L7fda
L7fd0:  ld      r0, X+
L7fd2:  ld      r1, X+
L7fd4:  rcall   Subroutine8          ; L7fe4
L7fd6:  adiw    r30, 0x02            ; 2
L7fd8:  cpse    r26, r23
L7fda:  rjmp    Label32              ; L7fd0
L7fdc:  movw    r30, r18
L7fde:  ldi     r24, 0x05            ; 5
L7fe0:  rcall   Subroutine8          ; L7fe4

Subroutine7:                         ; Rcall from L7ec4
L7fe2:  ldi     r24, 0x11            ; 17

Subroutine8:                         ; Rcall from L7ec2, L7fcc, L7fd4, L7fe0
L7fe4:  out     io.spmcsr, r24
L7fe6:  spm

Label33:                             ; Rjmp from L7fec
L7fe8:  in      r1, io.spmcsr
L7fea:  sbrc    r1, 0                ; Bit 0 = 0x01
L7fec:  rjmp    Label33              ; L7fe8

; Rjmp from L7fc8
; Brcc from L7fb0
Label34:
L7fee:  clr     r1
L7ff0:  ret

L7ff2:  .fill   4, 2, 0xffff

L7ffa:  .word   0x0004               ; Invalid opcode
L7ffc:  rjmp    Label30              ; L7fa2
L7ffe:  sbci    r31, 0x01            ; 1

@MCUdude
Copy link
Collaborator

MCUdude commented Jul 30, 2024

@SpenceKonde I know you've been using inline assembly in some of your Arduino cores. Would you like to give this PR a try?

@stefanrueger I'm not really an assembly guy myself (I wish I was though), so I'm not qualified to point out any errors the disassembler might output. However, I'm really impressed with your effort! This is truly an 8.0 feature worthy!

@MCUdude Nor am I. But whenever I use asm volatile() snippets in C I wish I could see and test the output in situ. There are a lot of subtleties with the AVR assembler (inc and dec don't produce carries, lsr does) and it is often hard to keep one's brbs apart from one's sbrs.

@stefanrueger
Copy link
Collaborator Author

@ndim I would like to install the bash script elf2tag in the same bin directory as avrdude; it should be installed/uninstalled the same way avrdude is. Which changes do CMakeLists.txt and Makefile.am need to undergo so this happens? Thanks!

@SpenceKonde
Copy link

This looks really cool, as I spend a lot of time staring at dissassebled AVR code, and what you get out of gcc itself, we all know the result is pretty lousy....

Only question I have is why does it even make sense as part of avrdude instead of a separate tool?

(My dream form of a dissembler tool is something I could drag and drop the input file as .elf or .hex onto, and have it create a .asm or .S or something with the asm. But I guess as long as it;s commandlinable I can get that easy enough no matter what)

@stefanrueger
Copy link
Collaborator Author

separate tool?

@SpenceKonde -c dryrun is your friend: drop the following line into a bash script and replace the part, the file, the start address and the length with $1, ..., $4 and you have your stand-alone command line disasm command:

avrdude -qq -c dryrun -p attiny13a -U blink.elf -T "disasm -g flash 0 -1"

does it even make sense as part of avrdude

AVRDUDE knows 300+ parts, how big flash is, where sram sits, where the io region is, whether or not it has a 0x20 offset, the register names, the MCU architecture (and by implication, which opcodes a part has), ... So the whole infrastructure for a disassembler that knows its shit is already there. That's the USP of this approach: the disassembly is part-specific. When you use disasm -q to analyse the cycle times of a loop it knows which part it is, which of the four timing tables to use (AVRe, AVRxm, AVRxt and AVRrc) and whether or not it is a 22-bit PC (calls and returns take longer) or a 16-bit PC. It knows the I/O registers and displays them as symbols. It knows about I/O register offsets applies them correctly etc.

And AVRDUDE already has an interactive terminal that gives you access to an interactive disassembly session for free.

So, for me, there is no better infrastructure than AVRDUDE to code a disassembler that sucks less (and, yes, they all suck).

@ndim
Copy link
Contributor

ndim commented Jul 30, 2024

@ndim I would like to install the bash script elf2tag in the same bin directory as avrdude; it should be installed/uninstalled the same way avrdude is. Which changes do CMakeLists.txt and Makefile.am need to undergo so this happens? Thanks!

@stefanrueger I see a problem here: The build system files you mention are /src/CMakeLists.txt and /src/Makefile.am, but the script path is /tools/elf2tag which is not inside /src/.

Adding something to the top level /CMakeLists.txt would make more sense. I am 100% certain this is very easy, but I am too new to cmake to know the specifics off the top of my head.

On the Automake side, if we had a top-level /Makefile.am, this would would be trivial (bin_SCRIPTS = tools/elf2tag). Unfortunately, someone has put configure.ac into /src/configure.ac, and therefore the Automake build system can only deal with files inside /src/. I could write a bit of a hack which copies /tools/elf2tag somewhere inside /src/ and has the Automake buildsystem then deal with that file. That is not non-trivial but relatively straightforward for the common cases, but should eventually work even in edge cases. But it will still be a hack. The GNU build system (as generated by Automake) is specifically designed to only deal with the files inside its release tarball.

@MCUdude
Copy link
Collaborator

MCUdude commented Jul 30, 2024

I was just about to reply when @stefanrueger provided his excellent answer. Basically what he said. The fact that Avrdude has a dryrun mode makes it a very powerful tool that's really neat to have as an AVR developer.

@SpenceKonde Avrdude has recently gotten lots of new and powerful features since the 6.3 version you're probably the most familiar with. And @stefanrueger has implemented incredible features we could just dream of a few years ago. There is a lot to be excited about here!

  • Outstanding hardware support, even the latest and greatest AVR-EA, EB, and DU.
  • The Urboot bootloader combined with the Avrdude Urclock implementation. A true game-changer!
  • Support for multi-memory hex and srec files
    • This makes it possible to back up the entire microcontroller content into a single file and write to all memories using a single file. Fuses and flash content in a single file? No problem!
  • Vastly improved terminal mode (-t, -T)
    • config lets you easily configure the fuse bits. It's so good that I don't bother to use an online fuse calculator anymore!
    • backup and restore commands let you easily backup and restore the entire microcontroller to and from a single file
    • regfile lets you play around with registers. Read and write to IO for instance
  • And very soon, a built-in disassembler as well!

@stefanrueger
Copy link
Collaborator Author

@ndim Thanks for looking into this! I'd be vvv happy to move elf2tag into the src directory (in fact, I just have); and it makes sense, too, as it is actually not a development tool, but something users would run. And, yes, I am not welded to using the CMakeList.txt in src (I have simply overlooked the one in the root of the repo). So anything that works is cool.

I have a related question. It occurred to me that the avrdude.pdf documentation is installed somewhere on the user's destination system, but (as far as I am aware!) it isn't available in the avrdude repository for reference. So, when discussing issues it is not possible for us maintainers to refer to the documentation of the current git-main avrdude other than saying sth like Find out where avrdude.pdf is installed on your system and look at Section 3.2... This is bizarre. What would be a good way to keep a current avrdude.pdf document (ideally created automatically when commits/merges happen to git main) in the repository? Ideally this would reside in a top-level directory such as documentation. Thoughts? @ndim @mcuee @dl8dtl @MCUdude

@ndim
Copy link
Contributor

ndim commented Jul 30, 2024

@ndim Thanks for looking into this! I'd be vvv happy to move elf2tag into the src directory (in fact, I just have); and it makes sense, too, as it is actually not a development tool, but something users would run. And, yes, I am not welded to using the CMakeList.txt in src (I have simply overlooked the one in the root of the repo). So anything that works is cool.

Here is what appears to work. You can add that to your branch yourself.

  • /src/CMakeLists.txt

    Add the following line somewhere, probably best grouped with all the other install(...) lines

    install(PROGRAMS "elf2tag" DESTINATION bin)
    

    According to https://cmake.org/cmake/help/book/mastering-cmake/chapter/Install.html, PROGRAMS is (confusingly) for installing scripts, in this case into the same bin directory to where install(TARGETS avrdude DESTINATION bin) already installs the built avrdude executable.

    Tested with

    $ ./build.sh
    [...]
    $ env DESTDIR=$PWD/_d cmake --build build_linux --target install
    [...]
    $ ls -l _d/usr/local/bin
    total 5056
    -rwxr-xr-x. 1 user user 5170704 Jul 30 15:03 avrdude
    -rwxr-xr-x. 1 user user    1442 Jul 30 14:36 elf2tag
    
  • /src/Makefile.am

    Add the following line anywhere, e.g. just above to the man1_MANS line

    dist_bin_SCRIPTS = elf2tag
    

    The magic _SCRIPTS suffix tells Automake to process this definition in a special way, and to generate install rules installing the script with the x bit set. The bin part installs into bindir, i.e. the same place where bin_PROGRAMS installs the avrdude executable. The dist_ prefix makes sure that elf2tag is included in the make dist tarball.

    Tested with

    $ cd src
    $ autoreconf -i && ./configure
    [...]
    $ make -j$(nproc) -l$(nproc)
    [...]
    $ make DESTDIR=$PWD/_d install
    [...]
    $ ls -l _d/usr/local/bin
    total 5052
    -rwxr-xr-x. 1 ndim ndim 5165920 Jul 30 15:08 avrdude
    -rwxr-xr-x. 1 ndim ndim    1442 Jul 30 15:08 elf2tag
    $ make -j$(nproc) -l$(nproc) distcheck
    [...]
    

I have a related question. It occurred to me that the avrdude.pdf documentation […]

This is very much unrelated, so I have put this into a separate issue: #1847

@stefanrueger stefanrueger merged commit 7272151 into avrdudes:main Jul 31, 2024
@stefanrueger stefanrueger deleted the disasm branch July 31, 2024 15:58
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

6 participants