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
1 |
# apt install gdb-avr |
Installazione di simavr
Scarichiamo i sorgenti dal repo git
1 |
# git clone https://github.com/buserror/simavr.git |
Per la compilazione dei sorgenti avremo bisogno di alcune librerie
1 |
# apt install avr-libc libelf-dev freeglut3-dev libncurses-dev |
Ora compiliamo
1 2 3 |
# 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.
1 2 3 4 5 6 7 8 |
#include <avr/io.h> int main(void){ char c; while(1){ c++; } } |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 |
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
1 2 |
$ make main.s avr-gcc -mmcu=atmega328p -S main.c -o main.s |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 |
.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.
1 2 3 4 5 6 7 8 9 |
.. .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
1 2 3 4 |
$ 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
1 2 3 4 |
$ 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
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
$ 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
1 2 3 |
(gdb) target remote localhost:1234 Remote debugging using localhost:1234 0x00000000 in __vectors () |
1 |
gdb_network_handler connection opened |
I comandi che utilizzeremo in avr-gbd sono
- disassemble
- x/xb
- info reg
- continue
- next
1 2 3 |
(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
1 2 3 4 5 |
(gdb) continue Continuing. Program received signal SIGTRAP, Trace/breakpoint trap. 0x0000008a in main () |
Il programma si è fermato sulla break che abbiamo inserito
1 2 3 4 5 6 7 8 9 10 11 12 13 |
(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
1 2 3 4 5 6 7 8 9 10 11 |
(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
1 2 3 4 |
(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
1 2 3 4 5 6 7 8 |
.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
1 2 3 4 |
$ 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
1 2 3 4 |
$ 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
1 2 3 4 5 6 7 8 9 10 |
(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
1 2 3 4 5 |
(gdb) continue Continuing. Program received signal SIGTRAP, Trace/breakpoint trap. 0x0000008e in main () |
Disassembliamo
1 2 3 4 5 6 7 8 9 10 11 12 13 |
(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
1 2 3 4 5 6 7 8 9 10 11 |
(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.
1 2 3 4 |
(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
1 2 3 4 5 6 7 8 9 10 11 12 13 |
(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
1 2 3 4 5 6 7 8 9 10 11 |
(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
1 2 3 4 |
(gdb) x/xb 0x8008fa 0x8008fa: 0x00 (gdb) x/xb 0x8008fb 0x8008fb: 0x01 |
Il valore di 0x8008fb quindi è stato incrementato di 1