Ungenutzte Bits in einem Programmzähler sollten eigentlich Null sein. In der Praxis sind sie das jedoch manchmal nicht. Da diese Bits ungenutzt sind, spielt ihr Wert eigentlich keine Rolle – solange niemand von außen, etwa ein Debugger, sie ausliest. Können solche Bits bei AVR Mikrocontrollern zu Problemen führen?

Ungenutzte und reservierte Bits

In der Embedded-Programmierung sind ungenutzte und reservierte Bits in Status- und Kontrollregistern eher die Regel als die Ausnahme. Reservierte Bits sind solche, für die der Chip-Hersteller möglicherweise Pläne für zukünftige Chip-Generationen hat. Für reservierte Bits gibt das Datenblatt in der Regel an, welchen Wert man hineinschreiben soll. Im Gegensatz dazu sind ungenutzte Bits schlicht „Don’t Cares“. Man kann in sie schreiben, was man möchte, und der ausgelesene Wert ist meist konstant, sehr oft Null.

Ungenutzte Bits im Programmzähler (PC) sind aus mehreren Gründen etwas Besonderes. Erstens sind in den meisten Fällen einige Bits im PC ungenutzt, weil der physische Speicher meist kleiner ist als der durch den Programmzähler adressierbare Speicherbereich. Zweitens ist eine direkte Manipulation des Programmzählers in der Regel nicht möglich. Aus diesen Gründen ist es für ein auf dem Mikrocontroller laufendem Programm völlig egal, welchen Wert diese Bits annehmen. Es kann also durchaus sein, dass ein Programm, das eigentlich bei Adresse 0x0000 starten soll, mit einem Programmzähler von 0x8000 beginnt – vorausgesetzt, Bit 15 des PC ist ungenutzt. Trotzdem funktioniert alles so, als würde der Mikrocontroller bei Adresse 0x0000 mit der Ausführung beginnen.

Während gesetzte ungenutzte Bits intern keine Probleme verursachen, geht die Außenwelt davon aus, dass sie Null sind. Ein Compiler, der Code für den Mikrocontroller erzeugt, generiert beispielsweise einen Sprung zu Adresse 0x0000 statt zu 0x8000.

Einen Debugger verwirren

Ahnungslose Debugger wie GDB geraten in Schwierigkeiten, wenn einige ungenutzte Bits im Programmzähler nicht Null sind. Wie der Compiler geht auch der Debugger davon aus, dass alle ungenutzten Bits im Progammzähler Null sind. Außerdem stellt er eine enge Verbindung zwischen Flash-Adressen und dem Quelltext her. Erhält GDB eine Flash-Adresse, die keinem Quelltext zugeordnet ist, ist er verloren. Entsprechend leidet das Debugging-Erlebnis erheblich.

Wie gravierend ist dieses Problem also? Gibt es viele Mikrocontroller, die dieses Verhalten zeigen? Bei meiner Arbeit am GDB-Server PyAvrOCD habe ich systematische End-to-End-Tests mit (fast) allen Mikrocontrollern durchgeführt, die der GDB-Server unterstützen soll. Bisher habe ich folgende problematische Chips gefunden:

  • ATmega16, ATmega16A und ATmega64A
  • ATmega329P und ATmega3250P
  • ATmega48 und ATmega88 (ohne A- oder P-Suffix)

Die letzten beiden MCUs sind mir bereits beim Bau von dw-link aufgefallen, einem debugWIRE-Debugger mit RSP-Interface. Damals habe ich gelernt, dass man bei der Debug-Schnittstelle von AVR-Chips nicht verallgemeinern kann. Nur weil es bei einem Chip einer Familie funktioniert, heißt das nicht, dass es auch bei nahen Verwandten funktioniert. Besonders extrem ist der Fall der ATmega48s und ATmega88s, da die Chips mit A-Suffix, die dieselbe Chip-Signatur haben, völlig problemlos funktionieren.

Wie schlimm kann es werden?

Interessanterweise gibt es unterschiedliche Schweregrade. Bei den ATmega16, 16A und 64A ist nicht sofort erkennbar, ob der Programmzähler verfälscht ist. Erst wenn man sich die auf dem Stack gespeicherten Rücksprungadressen ansieht, wird deutlich, dass einige ungenutzte Bits im PC nicht Null sind. Auch das mag GDB nicht. Stack-Backtraces sind leer, und beim Verwenden des next-Befehls kann man nicht über einen Funktionsaufruf hinweg springen, sondern landet stattdessen in der Funktion.

Obwohl ich den ATmega64 nicht explizit getestet habe, vermute ich stark, dass er dasselbe Verhalten zeigt, da im Datenblatt derselbe Hinweis steht wie bei seinem Cousin mit A-Suffix:

If software reads the Program Counter from the Stack after a call or an interrupt, unused bits (bit 15) should be masked out.

Als Nächstes kommen die ATmega329P- und ATmega3250P-Chips. Sie verbergen nicht, dass der PC ungenutzte Bits enthält, die nicht Null sind, sondern melden dies offen, wenn der Zustand des Programmzählers abgefragt wird. Im Datenblatt findet sich dazu jedoch kein Hinweis. Ungenutzte PC-Bits, die nicht Null sind, verwirren GDB noch stärker als verfälschte Rücksprungadressen. Der Debugger hat dann keine Ahnung, wo er nach dem Erreichen eines Breakpoints gelandet ist.

Schließlich gibt es noch den ATmega48 und ATmega88. Diese sind besonders problematisch, weil sie im debugWIRE-Modus in Kombination mit Open-Source-Software und Microchip-Debuggern praktisch unbrauchbar sind. Der einzige Weg, sie wiederzubeleben, ist entweder MPLAB X oder Microchip Studio zusammen mit einem Microchip-Debugger/Programmer zu verwenden oder den oben erwähnten DIY-Debugger dw-link einzusetzen.

Umgang mit PC-Bits auf Abwegen

Da GDB mit ungenutzten PC-Bits, die nicht Null sind, überhaupt nicht zurechtkommt, sind hier Gegenmaßnahmen erforderlich.

Umgang mit dem Hotel-California-Syndrom

ATmega48 und ATmega88 stellen das größte Problem dar. Da diese Chips unter dem sogenannten Hotel-California-Syndrom leiden („You can check out any time you like, but you can never leave!“), ist es am besten, sie zu identifizieren, bevor sie in den debugWIRE-Modus wechseln. Da sie jedoch denselben Signaturcode wie ATmega48A bzw. ATmega88A haben, kann man dies anhand der Signatur nicht entscheiden.

Ein Unterschied zwischen den Chips ohne und mit A-Suffix besteht darin, dass die Chips ohne A-Suffix den Signaturcode nicht im Boot-Signatur-Bereich speichern. Leider lässt sich das über die SPI-Programmierschnittstelle nicht feststellen. Man kann jedoch ein kurzes Programm auf den Chip laden, das nach Untersuchung des Boot-Signatur-Bereichs die Lock-Bits ensprechend setzt. Für den ATmega48 sieht ein solches Programm wie folgt aus:

#include <avr/boot.h>
#define SIGRD 5
int main(void)
{
  if (boot_signature_byte_get(0) != 0x1E) {
    boot_lock_bits_set (_BV (0));
  }
}

Dieses Programm kann vor Beginn des Debuggings hochgeladen werden, und anschließend können die gesetzten Lock-Bits überprüft werden. Für den ATmega88 ist das Programm etwas komplizierter, da die Lock-Bits im Boot-Bereich programmiert werden müssen. Insgesamt sorgt diese Methode zuverlässig dafür, dass diese Chips nicht versehentlich in den debugWIRE Modus gebracht werden.

Umgang mit offen sichtbaren Bits auf Abwegen

Wenn der Mikrocontroller, die ungenutzten Bits, die nicht Null sind, nicht verbirgt, ist es relativ einfach, diese Bits zu maskieren, sodass die Außenwelt (und der Rest von PyAvrOCD) nur die tatsächliche Flash-Adresse sieht. Eine Sache ist jedoch zu beachten: Beim Setzen von Hardware-Breakpoints muss dieses Bit gesetzt werden, da es beim Vergleich des Hardware-Registers mit dem PC relevant ist.

Umgang mit gesetzten ungenutzten Bits in Rücksprungadressen

Die zusätzlichen gesetzten Bits in Rücksprungadressen, die vom Stack gelesen werden, könnte man ignorieren. Das hätte jedoch defekte Stack-Backtraces und fehlerhafte Single-Step-Operationen zur Folge.

Langfristig möchte man diese Bits natürlich korrekt behandeln. Die Lösung liegt auf der Hand: Man muss die ungenutzten Bits bei der Kommunikation mit GDB maskieren. Allerdings ist nicht immer klar, wann Werte vom Stack tatsächlich Rücksprungadressen sind und wann es sich um lokale Variablen handelt.

Die sauberste Lösung ist daher, GDB selbst das Maskieren übernehmen zu lassen. GDB weiß, wann etwas eine Rücksprungadresse ist und wann es sich um einen normalen Variablenwert handelt. Als ersten Schritt habe ich einen Bug-Report eingereicht. Beim Durchsehen anderer Bug-Reports zur AVR-Architektur fiel mir jedoch auf, dass die Aktivität in diesem Bereich eher gering ist. Ein bestimmter Bug-Report, der zu defekten Stack-Backtraces führt, liegt dort bereits seit 12 Jahren. Zudem war er einfach zu korrigieren, was ich auch getan habe.

Das hatte meinen Ehrgeiz geweckt. Nach dem Studium des AVR-spezifischen Codes und Gesprächen mit einem der GDB-Maintainer kam ich zu einer Lösung, die funktioniert, solange der GDB-Server eine Memory-Map an GDB liefert. Das ist der Fall, wenn GDB mit der Expat-Bibliothek kompiliert wurde. Man kann dann in allen Fällen, in denen ein Wert als Adresse im Codebereich verstanden werden soll, die ungenutzten PC-Bits maskieren.

Hoffentlich wird der Patch in die nächste GDB-Version aufgenommen. Bis dahin werde ich eine gepatchte GDB Version bereitstellen. Damit können wir künftig wieder vergessen, dass es AVR-MCUs mit ungenutzten PC-Bits gibt, die nicht Null sind.

Zusammenfassung

Wie wir gesehen haben, können gesetzte ungenutzte PC-Bits beim Embedded-Debugging sehr störend sein. Wenn der Debugger die Situation nicht erkennt, fühlt sich das Debugging zumindest merkwürdig an. Glücklicherweise lassen sich diese Bits sowohl auf der Server- als auch auf der GDB-Seite maskieren, sodass wir sie letztlich vollständig ignorieren können.

Views: 3