Debug firmware avr con simavr e avr-gdb

Condividi:

Gli strumenti che utilizzerò per il debug del firmware sono simavr e avr-gdb. simavr è un emulatore modulare di mcu che permette anche collegare virtualmente alla mcu dell’hardware supplementare come pulsanti ecc. E’ possibile iniettare segnali, alterare lo stato dei pin e loggare su file .vcd lo stato dei registri interni. In questo articolo utilizzeremo simavr solo come simulatore. avr-gdb è il debugger gcc che utilizzeremo per l’esecuzione del firmware e per ispezionare i dati che ci interessano durante il debug.

Installazione di avr-gcc

Nel caso non fosse già stato installato

# apt install gdb-avr
Installazione di simavr

Scarichiamo i sorgenti dal repo git

# git clone https://github.com/buserror/simavr.git

Per la compilazione dei sorgenti avremo bisogno di alcune librerie

# apt install avr-libc libelf-dev freeglut3-dev libncurses-dev

Ora compiliamo

# cd simavr
# make
# make install
Firmware di esempio

Compiliamo questo semplice firmware con le opzioni per l’Atmega328p. Per prima cosa compileremo il listato per ottenere il sorgente da assemblare, inseriremo il primo break ed compileremo il file .elf da eseguire in emulazione. Analizzeremo le informazioni che ci interessano dopodichè sposteremo il break, ricompileremo e rieseguiremo il firmware analizzando i registri che sono stati moficati dalle istruzioni che prima non erano state eseguite.

#include <avr/io.h>

int main(void){
  char c;
  while(1){
    c++;
  }
}
CC=avr-gcc
MCU=atmega328p
CFLAGS=-mmcu=${MCU}
TARGET=main
OBJCOPY=avr-objcopy

OBJS=main.o

all: ${TARGET}.hex

%.s: %.c
        ${CC} ${CFLAGS} -S $< -o $@

%.o: %.s
        ${CC} ${CFLAGS} -c $< -o $@

${TARGET}.elf: ${OBJS}
        ${CC} ${CFLAGS} -o $@ ${OBJS}

${TARGET}.hex: ${TARGET}.elf
        ${OBJCOPY} -j .text -j .data -O ihex $^ $@

clean:
        rm -f *.elf *.s *.o *.hex

Otteniamo il listato assembler da editare

$ make main.s
avr-gcc -mmcu=atmega328p -S main.c -o main.s
        .file   "main.c"
__SP_H__ = 0x3e
__SP_L__ = 0x3d
__SREG__ = 0x3f
__tmp_reg__ = 0
__zero_reg__ = 1
        .text
.global main
        .type   main, @function
main:
        push r28
        push r29
        push __zero_reg__
        in r28,__SP_L__
        in r29,__SP_H__
/* prologue: function */
/* frame size = 1 */
/* stack size = 3 */
.L__stack_usage = 3
.L2:
        ldd r24,Y+1
        subi r24,lo8(-(1))
        std Y+1,r24
        rjmp .L2
        .size   main, .-main
        .ident  "GCC: (GNU) 4.9.2"

Inseriamo un comando break all’interno del codice, prima dell’ incremento della variabile all’interno del ciclo while identificato dalla label .L2
In questo listato notiamo che la variabile c di tipo char viene indirizzata dal valore del registro Y incrementato di 1. Viene copiata nel registro r24, a cui viene sottratto -1 e poi il valore del registro viene ricopiato all’indirizzo puntato dal registro Y incrementato di 1.

..
.L2:
        break
        ldd r24,Y+1
        subi r24,lo8(-(1))
        std Y+1,r24
        rjmp .L2
        .size   main, .-main
        .ident  "GCC: (GNU) 4.9.2"

Ora compiliamo il file elf

$ make
avr-gcc -mmcu=atmega328p -c main.s -o main.o
avr-gcc -mmcu=atmega328p -o main.elf main.o
avr-objcopy -j .text -j .data -O ihex main.elf main.hex

In una console avviamo simavr

$ sudo simavr -f 16000000 -m atmega328p -t -g main.elf
Loaded 152 .text at address 0x0
Loaded 0 .data
avr_gdb_init listening on port 1234

In un’altra console avviamo avr-gdb

$ avr-gdb main.elf
GNU gdb (GDB) 7.12.0.20161007-git
Copyright (C) 2016 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.  Type "show copying"
and "show warranty" for details.
This GDB was configured as "--host=x86_64-linux-gnu --target=avr".
Type "show configuration" for configuration details.
For bug reporting instructions, please see:
<http://www.gnu.org/software/gdb/bugs/>.
Find the GDB manual and other documentation resources online at:
<http://www.gnu.org/software/gdb/documentation/>.
For help, type "help".
Type "apropos word" to search for commands related to "word"...
Reading symbols from main.elf...(no debugging symbols found)...done.
(gdb)

avr-gdb ci avvisa che non ha trovato simboli per il debug, ed è corretto perchè abbiamo compilato senza l’opzione -ggdb. Infondo non ci interessa il codice C in questo momento.
Da avr-gdb colleghiamoci al processo simavr

(gdb) target remote localhost:1234
Remote debugging using localhost:1234
0x00000000 in __vectors ()
gdb_network_handler connection opened

I comandi che utilizzeremo in avr-gbd sono

  • disassemble
  • x/xb
  • info reg
  • continue
  • next
(gdb) disassemble
Dump of assembler code for function __vectors:
=> 0x00000000 <+0>:     jmp     0x68    ;  0x68 <__trampolines_start>

Ci troviamo all’indirizzo 0x00, dove si trova il RESET interrupt vector. Avviamo l’esecuzione con continue che si fermerà in corrispondenza del comando break

(gdb) continue
Continuing.

Program received signal SIGTRAP, Trace/breakpoint trap.
0x0000008a in main ()

Il programma si è fermato sulla break che abbiamo inserito

(gdb) disassemble
Dump of assembler code for function main:
   0x00000080 <+0>:     push    r28
   0x00000082 <+2>:     push    r29
   0x00000084 <+4>:     push    r1
   0x00000086 <+6>:     in      r28, 0x3d       ; 61
   0x00000088 <+8>:     in      r29, 0x3e       ; 62
=> 0x0000008a <+10>:    break
   0x0000008c <+12>:    ldd     r24, Y+1        ; 0x01
   0x0000008e <+14>:    subi    r24, 0xFF       ; 255
   0x00000090 <+16>:    std     Y+1, r24        ; 0x01
   0x00000092 <+18>:    rjmp    .-10            ;  0x8a <main+10>
End of assembler dump.

Analizziamo lo stato dei registri

(gdb) info registers
...
r24            0x0      0
...
r28            0xfa     250
r29            0x8      8
...
SREG           0x0      0
SP             0x8008fa 0x8008fa
PC2            0x8a     138
pc             0x8a     0x8a <main+10>

Osserviamo che il registro r24 contiene il valore 0, lo Stack Pointer ed il registro Y ( formato da r28 ed r29 ) puntano all’indirizzo 0x800fa  che rappresenta la base della memoria delle variabili locali della funzione main. Visualizziamo il contenuto degli indirizzi puntatu da Y e da Y+1

(gdb) x/xb 0x8008fa
0x8008fa:       0x00
(gdb) x/xb 0x8008fb
0x8008fb:       0x00

Ora usciamo da simavr ed editiamo il file main.s spostando la break dopo l’istruzione subi

.L2:
        ldd r24,Y+1
        subi r24,lo8(-(1))
        break
        std Y+1,r24
        rjmp .L2
        .size   main, .-main
        .ident  "GCC: (GNU) 4.9.2"

Ricompiliamo il main.elf

$ make
avr-gcc -mmcu=atmega328p -c main.s -o main.o
avr-gcc -mmcu=atmega328p -o main.elf main.o
avr-objcopy -j .text -j .data -O ihex main.elf main.hex

Riavviamo simavr

$ sudo simavr -f 16000000 -m atmega328p -t -g main.elf
Loaded 152 .text at address 0x0
Loaded 0 .data
avr_gdb_init listening on port 1234

E nell’altra console ricolleghiamoci a simavr

(gdb) target remote localhost:1234
A program is being debugged already.  Kill it? (y or n) y
Remote connection closed
(gdb) target remote localhost:1234
`/home/stefano/src/avr/test-001/main.elf' has changed; re-reading symbols.
(no debugging symbols found)
Remote debugging using localhost:1234

Program received signal SIGTRAP, Trace/breakpoint trap.
0x00000000 in __vectors ()

Eseguiamo il programma

(gdb) continue
Continuing.

Program received signal SIGTRAP, Trace/breakpoint trap.
0x0000008e in main ()

Disassembliamo

(gdb) disassemble
Dump of assembler code for function main:
   0x00000080 <+0>:     push    r28
   0x00000082 <+2>:     push    r29
   0x00000084 <+4>:     push    r1
   0x00000086 <+6>:     in      r28, 0x3d       ; 61
   0x00000088 <+8>:     in      r29, 0x3e       ; 62
   0x0000008a <+10>:    ldd     r24, Y+1        ; 0x01
   0x0000008c <+12>:    subi    r24, 0xFF       ; 255
=> 0x0000008e <+14>:    break
   0x00000090 <+16>:    std     Y+1, r24        ; 0x01
   0x00000092 <+18>:    rjmp    .-10            ;  0x8a <main+10>
End of assembler dump.

E visualizziamo lo stato dei registri

(gdb) info registers
...
r24            0x1      1
...
r28            0xfa     250
r29            0x8      8
...
SREG           0x21     33
SP             0x8008fa 0x8008fa
PC2            0x90     144
pc             0x90     0x90 <main+16>

Notiamo che il registro r24 ora contiene un uno.

(gdb) x/xb 0x8008fa
0x8008fa:       0x00
(gdb) x/xb 0x8008fb
0x8008fb:       0x00

Il valore degli indirizzi puntati da Y ed Y+1 è ancora lo stesso

Ripetiamo gli ultimi passaggi spostando la break dopo std, ricompiliamo, rieseguiamo simavr e ricolleghiamoci da avr-gdb. Dopo aver impartito i comandi continue e disassemble dovremo trovarci qui

(gdb) disassemble
Dump of assembler code for function main:
   0x00000080 <+0>:     push    r28
   0x00000082 <+2>:     push    r29
   0x00000084 <+4>:     push    r1
   0x00000086 <+6>:     in      r28, 0x3d       ; 61
   0x00000088 <+8>:     in      r29, 0x3e       ; 62
   0x0000008a <+10>:    ldd     r24, Y+1        ; 0x01
   0x0000008c <+12>:    subi    r24, 0xFF       ; 255
   0x0000008e <+14>:    std     Y+1, r24        ; 0x01
=> 0x00000090 <+16>:    break
   0x00000092 <+18>:    rjmp    .-10            ;  0x8a <main+10>
End of assembler dump.

Lo stato de dei registri non è cambiato

(gdb) info registers
...
r24            0x1      1
...
r28            0xfa     250
r29            0x8      8
...
SREG           0x21     33
SP             0x8008fa 0x8008fa
PC2            0x90     144
pc             0x90     0x90 <main+16>

Invece il valore che è contenuto in r24 è stato copiato nella nostra variabile locale c rappresentata da Y+1

(gdb) x/xb 0x8008fa
0x8008fa:       0x00
(gdb) x/xb 0x8008fb
0x8008fb:       0x01

Il valore di 0x8008fb quindi è stato incrementato di 1