RoboterNetz.de Foren-▄bersicht               
 RN-Wissen Home  -  Community Home  -  Alle Artikel -  Mitglieder -  Moderatoren  -  Bilderliste  -  Letzte ├änderungen
 Kategorien  -  Beliebte Seiten  -  Sackgassenartikel  -  Artikel ohne Kategorie  -  Neue Artikel  -  Anmelden

Avr-gcc/Interna

aus RN-Wissen, der freien Wissensdatenbank

Dieser Artikel fasst einige Interna von avr-gcc zusammen.

Inhaltsverzeichnis

Ablauf der Codegenerierung

Die Code-Erzeugung durch avr-gcc geschieht in mehreren, voneinander unabh├Ąngigen Schritten. Diese Schritte sind f├╝r den Anwender nicht immer erkennbar, und es auch nicht unbedingt notwendig, sie zu kennen. F├╝r ein besseres Verst├Ąndnis der Code-Generierung und zur Einordnung von Fehlermeldungen und Warnungen ist eine Kenntnis aber durchaus hilfreich.

├ťbersichts-Grafik

Zusammenspiel zwischen avr-gcc und binutils

Schritte der Codegenerierung

Ohne die Angabe spezieller Optionen legt avr-gcc die Zwischenformate nur als tempor├Ąre Dateien an, und nach Beenden des Compilers werden diese wieder gel├Âscht. Dadurch f├Ąllt die Aufgliederung in Unterschritte nicht auf. In diesem Falle m├╝ssen Assembler und Linker/Locator auch nicht extra aufgerufen werden, sondern die Aufrufe werden durch avr-gcc verwaltet.

Precompileren
Alle Preprozessor-Direktiven werden aufgel├Âst. Dazu geh├Âren Direktiven wie
#include <avr/io.h>
#include "meinzeug.h"

#define MAKRONAME ERSATZTEXT

#if !defined(__AVR__)
#error einen Fehler ausgeben und abbrechen
#else
/* Alles klar, wir koennen loslegen mit C-Code fuer AVR */
#endif 

MAKRONAME
Precompilieren besteht also nur aus reinem Textersatz: Aufl├Âsen von Makros, kopieren von anderen Dateien in die Quelle, etc.
Compilieren
In diesem Schritt geschieht der eigentliche Compilier-Vorgang: avr-gcc ├╝bersetzt die reine, precompilierte C-Quelle (*.i): Die Quelle wird auf Syntax-Fehler gepr├╝ft, es werden Optimierungen gemacht, und das ├╝bersetzte C-Programm als Assembler-Datei in (*.s) gespeichert.
Assemblieren
Der Assembler (avr-as) ├╝bersetzt den Assembler-Code (*.s) in das AVR-eigene Objektformat elf32-avr (*.o). Das Objekt enth├Ąlt schon Maschinen-Code. Zus├Ątzlich gibt es aber noch L├╝cken, die erst sp├Ąter gef├╝llt werden und Debug-Informationen und ganz viel anderes Zeug.
Linken und Lokatieren
Der Linker (avr-ld) bindet die angegebenen Objekte (*.o) zusammen und l├Âst externe Referenzen auf. Der Linker entscheidet anhand der Beschreibung im Linker-Script, in welchen Speicheradressen und Sections die Daten landen: er lokatiert (von location, locate (en)). Module aus Bibliotheken (*.a) werden hinzugebunden (z.B. printf) und die elf32-avr Ausgabedatei (├╝blicherweise *.elf) erzeugt.
Umwandeln ins gew├╝nschte Objekt-Format
Ohne Angabe spezieller Optionen erzeugen Linker und Assembler ihre Ausgabe im Objektformat elf32-avr. Wird ein anderes Objektformat wie Intel-HEX (*.hex), binary (*.bin) oder srec (*.srec) ben├Âtigt, kann avr-objcopy dazu verwendet werden, um diese zu erstellen. Der Inhalt einzelner Sections kann gezielt umkopiert oder ausgeblendet werden, so da├č Dateien erstellt werden k├Ânnen, die nur den Inhalt des Flashs (Section .text) oder des EEPROMs (Section .eeprom) repr├Ąsentieren. Durch das Umwandeln in ein anderes Objektformat gehen ├╝blicherweise Informationen wie Debug-Informationen verloren.
Es ist auch m├Âglich, den Linker mit der Optione --oformat=... zu starten, damit er direkt das gew├╝nschte Ausgabeformat erzeugt. Diese Option l├Ąsst man von avr-gcc an den Linker weiterreichen, etwa wenn man direkt eine Intel-HEX-Datei erstellen will und keine elf-Datei braucht, wie sie z.b. beim Debuggen ben├Âtigt wird:
> avr-gcc ... -Wl,--oformat=ihex

Dabei ist daf├╝r zu sorgen, da├č keine Debug-Informationen etc. in der hex-Datei landen! Generell ist es vorzuziehen, die hex-Datei aus einer elf-Datei zu erzeugen.


Allgemeine Charakteristika von avr-gcc

Ôćĺ avr-gcc: Application Binary Interface (ABI) im GCC Wiki
Gro├č- und Kleinschreibung
C unterscheidet generell zwischen Gro├č- und Kleinschreibung, sowohl bei Variablen- und Funktionsnamen, bei Sprungmarken als auch bei Makros, und je nach Betriebssystem auch bei Pfad- und Dateinamen/Dateierweiterungen.
Gr├Â├če des Typs int
Der Standard-Typ int ist 16 Bit gro├č
Gr├Â├če von Pointern
Ein Pointer (Zeiger) ist 16 Bit gro├č
Endianess
avr-gcc implementiert Datentypen als little-endian, d.h. bei Datentypen, die mehrere Bytes gro├č sind, wird das niederwertigste Byte an der niedrigsten Adresse gespeichert. Dies gilt auch f├╝r Adressen und deren Ablage auf dem Stack sowie die Ablage von Werten, die mehrere Register belegen.
size_t
size_t ist unsigned und immer 16 Bit gro├č, unabh├Ąngig davon , ob mit -mint8 ├╝bersetzt wird oder nicht.

Bin├Ąre Konstanten

Einige Versionen von avr-gcc erm├Âglichen die Verwendung bin├Ąrer Konstanten f├╝r Ganzzahl-Werte:

unsigned char value = 0b00000010;

Davon sollte man absehen, denn zum einen hat man schnell eine 0 zu wenig oder zu viel getippselt, es ist kein Standard-C und man hat die leserlichere Alternative

unsigned char value = (1<<1);

Registerverwendung

Ôćĺ avr-gcc: Register Layout im GCC Wiki

avr-gcc verwendet die Prozessor-Register GPRs auf eine definierte Art und Weise. Wie die Register verwendet werden, muss man wissen, wenn man Assembler-Funktionen schreibt, die mit durch avr-gcc ├╝bersetztem C-Code zusammenpassen sollen. Der "reinen" C-Programmierer muss sich keine Gedanken um die Register-Verwendung machen.

R0
ein tempor├Ąres Register, in dem man rumwutzen darf. Der Compiler verwendet dieses Register als Scratch-Register.
T-Flag
Das T-Flag im SREG wird analog wie R0 verwendet
R1
enth├Ąlt den Wert 0. Wird der Wert zerst├Ârt, zum Beispiel durch einen MUL-Befehl, muss das Register wieder auf 0 gesetzt werden.
R1 – R17, R28, R29
allgemeine Register, die durch einen Funktionsaufruf nicht ver├Ąndert bzw. wieder auf den urspr├╝nglichen Wert restauriert werden
R0, R18 – R27, R30, R31
k├Ânnen durch Funktionsaufrufe ver├Ąndert werden, ohne restauriert zu werden
Framepointer
R28 – R29 (Y-Reg) enth├Ąlt den Framepointer, sofern ben├Âtigt
Argument-Register
Die Register R8 bis R25 finden Verwendung zur ├ťbergabe von Funktionsparametern. F├╝r den Ort, an dem das Argument ├╝bergeben wird, gilt:
  • Die Gr├Â├če in Bytes wird zur n├Ąchsten geraden Zahl aufgerundet, falls die Argumentgr├Â├če ungerade ist.
  • Ist in den verbleibenden ├ťbergaberegistern kein Platz mehr oder ist das Argument namenlos (varargs), wird es im Stack oder im Speicher ├╝bergeben und die Adresse als implizites erstes Argument.
  • Der Registerort f├Ąngt mit 26 an.
  • Von dem Registerort wird die berechete Gr├Â├če abgezogen und (falls Platz) das Argument in diesen Registern ├╝bergeben. Ist das erste Argument z.B. ein long, dann erfolgt die ├ťbergabe in den Registern R22, R23, R24 und R25 (LSB zuerst). Danach wird die gerundete Gr├Â├če des Arguments vom Registerort abgezogen, dieser also auf 22 gesetzt. Ist das n├Ąchste Argument ein char, wird dessen Gr├Â├če auf 2 aufgerundet und vom Ort abgezogen. Dieses Argument wird also in R20 ├╝bergeben und der Ort auf 20 gesetzt, etc.
Return-Register
Ein Return-Wert wird in den gleichen Registern zur├╝ckgegeben, die auch f├╝r ein gleichgrosses erstes Funktionsargument genommen w├╝rden. Liefert eine Funktion ein long, dann erfolgt die R├╝ckgabe also in den Registern R22-R25 (LSB zuerst). Bei einem short sind es die Register R24 und R25.

Builtins

Zur bedingten Codeerzeugung und zur Erkennung, welcher Compiler sich an der Quelle zu schaffen macht, sind folgende Builtins hilfreich.

Siehe auch: Maschinenspezifische Optionen

Builtin Defines

GCC

__GNUC__
X wenn GCC-Version X.Y.Z
__GNUC_MINOR__
Y wenn GCC-Version X.Y.Z
__GNUC_PATCHLEVEL__
Z wenn GCC-Version X.Y.Z
__VERSION__
"X.Y.Z" wenn GCC-Version X.Y.Z
__GXX_ABI_VERSION
Version der ABI (Application Binary Interface)
__STDC__
Ist 1, wenn Standard-C ├╝bersetzt wird
__OPTIMIZE__
Optimierung ist aktiviert
__NO_INLINE__
Ohne Schalter -finline resp. -finline-all-functions etc.
__ASSEMBLER__
Definiert, falls GCC die Eingabe als Assembler-Code betrachtet und nicht compiliert. Weiterleitung an den Assembler.
__cplusplus
Es wird C++ ├╝bersetzt (Quell-Endung *.cpp, *.c++ oder Option -x c++).
__FILE__
L├Âst auf zum Dateinamen der Quelldatei, in der das __FILE__ steht.
__LINE__
L├Âst auf zur Zeilennummer der Quelldatei, in der das __LINE__ steht.
__DATE__
L├Âst auf zum Datum (precompile-date)
__TIME__
L├Âst auf zur Zeit (precompile-time)

avr-gcc

__AVR
Definiert f├╝r Target avr, d.h. avr-gcc ist am Werk
__AVR__
dito
__AVR_ARCH__
codiert den AVR-Kern, f├╝r den Code erzeugt wird (Classic, Mega, ...).
__AVR_XXXX__
Gesetzt, wenn -mmcu=xxxx.

Builtin Variablen

__func__
Eine magische Variable, die den aktuellen Funktionsnamen enth├Ąlt. Gerade so, als h├Ątte man ihn selbst mit
static const char __func__[] = "main";
definiert.

Speicherverwaltung

Sections

Sections sind mit F├Ąchern vergleichbar, in die Daten, Code, Debug-Informationen usw. einsortiert werden. Zur Section .text geh├Ârt z.B. der ausf├╝hrbare Code, welcher letztendlich im Flash landet. Wo genau das ist, braucht man nicht zu wissen und es spielt auch keine Rolle, wo eine bestimmte Funktion landet.

F├╝r 'normalen' Code und 'normale' Daten braucht man sich nicht um die Sections zu k├╝mmern, sie werden von avr-gcc automatisch richtig zugeordnet. F├╝r spezielle Anwendungen kann es aber notwendig sein, die Ablage in eine andere Section zu machen; etwa wenn man Daten im EEPROM lesen/schreiben will. Wie das genau gemacht wird, steht im Abschnitt "Attribute" und es gibt Beispiele in den Abschnitten "Datenablage am Beispiel Strings" und "Zufall".

Tabelle: Bedeutung der Sections bei avr-gcc
Section Ablage Betrifft Beschreibung
.text Flash Code normaler Programm-Code
.data SRAM Daten wird vom Startup-Code initialisiert
.rodataSRAM Daten f├╝r AVR analog zu .data, enth├Ąlt readonly-Daten und wird im RAM abgelegt, nicht im Flash
.bss SRAM Daten wird vom Startup-Code zu 0 initialisiert
.progmem.data Flash Daten Daten im Flash, die dort mit PROGMEM bzw. __attribute__((progmem)) abgelegt wurden und mit pgm_read_*-Funktionen gelesen werden k├Ânnen
.eeprom EEPROM Daten Daten im EEPROM, die dort z.B. mit EEMEM hingelegt werden
.noinit SRAM Daten wird nicht initialisiert
.initn Flash Code wird vor main ausgef├╝hrt, n = 0...9
.finin Flash Code wird nach main ausgef├╝hrt, n = 9...0
.vectors Flash Code Vektor-Tabelle: Tabelle mit Spr├╝ngen zur jeweiligen ISR
.bootloader FlashCode f├╝r den Bootloader
.stab*
.debug*
Debug-Info wird nicht geladen

Der Anfang einer Section kann auch dem Linker mitgegeben werden, etwa wenn wie ├╝blich avr-gcc als Treiber f├╝r den Linker verwendet wird:

avr-gcc ...  -Wl,--section-start=.eeprom=0x810001

Damit beginnt Section .eeprom nicht an der (virtuellen) Adresse 0x810000, sondern ein Byte sp├Ąter. Manche AVRs haben einen Silicon-Bug, der bei Verwendung der EEPROM-Adresse 0 zu Fehlern f├╝hrt. Mit der obigen Linker-Option wird diese Adresse nicht mehr verwendet.


Dynamische Speicherallokierung

RAM-Layout f├╝r ein AVR mit 1kByte SRAM
vergr├Â├čern
RAM-Layout f├╝r ein AVR mit 1kByte SRAM

Zur dynamischen Speicherallokierung stehen Standard-Funktionen wie malloc zur Verf├╝gung. Damit kann man zur Laufzeit Speicher anfordern und wenn man ihn nicht mehr ben├Âtigt, wieder freigeben. Funktionen wie malloc und calloc sind jedoch recht aufw├Ąndig. Die allokierten Speicherst├╝cke werden intern in einer verketteten Liste verwaltet, und das verbraucht wertvollen Platz im Flash und im SRAM sowie Laufzeit.

Resourcen-schonendere M├Âglichkeiten, zur Laufzeit an Speicher zu kommen, bieteten __builtin_alloca und dynamische Arrays. Der Speicher, der damit belegt wird, wird nicht auf dem Heap angelegt, sondern im Frame der Funktion. Das ist wesentlich effektiver als die Standard-Methoden, denn es muss nur ein Wert zum Framepointer addiert werden. Den so erhaltenen Speicher braucht man auch nicht freizugeben. Das geschieht automatisch beim Verlassen der Funktion in deren Epilog, indem der Wert wieder vom Framepointer subtrahiert wird.

Von der Verwendung ist der mittels __builtin_alloca und dynamischer Arrays erhaltene Speicher also wie eine lokale Variable, mitsamt den bekannten Regeln f├╝r den G├╝ltigkeitsbereich.

Insbesondere darf dieser Speicher nicht mit return an die dar├╝berliegende Funktion zur├╝ckgegeben werden, weil er dann nicht mehr g├╝ltig ist und ein Zugriff darauf zu einem Laufzeitfehler f├╝hrt!

Der Speicherbereich ist dort g├╝ltig, wo auch die Adresse einer 'normalen' lokalen Variablen g├╝ltig w├Ąre, wenn diese an der gleichen Stelle definiert w├╝rde.

Das Programm/der Algorithmus muss daher beim Beschreiten dieses Wegs darauf angepasst sein.

Verwendung:

void function (size_t num_data)
{
   /* data_t hat man irgendwo selber definiert, oder es ist ein elementarer Typ */
   data_t * const p = (data_t * const) __builtin_alloca (num_data * sizeof (data_t));

   /* Mach was mit p[0] ... p[num_data-1] */
   ...
}

oder mittels eines dynamischen Arrays:

void function (size_t num_data)
{
   /* data_t hat man irgendwo selber definiert, oder es ist ein elementarer Typ */
   data_t p[num_data];

   /* Mach was mit p[0] ... p[num_data-1] */
   ...
}

Attribute

Mit Attributen kann man die Codeerzeugung beeinflussen. Es gibt verschiedene Attribute, die auf Daten, Typen, und/oder Funktionen anwendbar sind.

Syntax:

__attribute__ ((<name>))
__attribute__ ((<name1>, <name2>, ...))
__attribute__ ((<name> ("<wert>")))

N├╝tzliche Attribute von GCC

Tabelle: Attribute von GCC (Auszug)
Attribut Funktionen Daten Typen Beschreibung
section ("<name>") (x) (x) (x) Lokatiert nach Section <name>
noreturn x Die Funktion wird nie zur├╝ckkehren. Erm├Âglicht weitere Optimierungen.
inline x Funktion wird geinlinet falls m├Âglich
always_inline x Funktion wird geinlinet
noinline x Funktion wird keinesfalls geinlinet
const x Diese Funktion h├Ąngt nur von ihren Parametrn ab und hat keine Nebenwirkungen, ausser den R├╝ckgabewert zu liefern. Dieses Wissen erm├Âglicht Optimierungen.
pure x Diese Funktion h├Ąngt nur von ihren Parametern und globalen Variablen ab und hat keine Nebenwirkungen, ausser den R├╝ckgabewert zu liefern. Dieses Wissen erm├Âglicht Optimierungen.
deprecated x x H├Ąngt dieses Attribut an einem Prototypen, so bekommt man eine Warnung, wenn das dazugeh├Ârige Objekt angefasst wird. Sehr praktisch, um alle Verwendungen eines Datums/einer Funktion anzeigen zu lassen.
packed x x Datenablage in Strukturen erfolgt dicht, also ohne eventuelle F├╝llbytes
unused x Bei Funktionsparametern, die nicht gebraucht werden. Vermeidet entsprechende Warnungen bei Funktionen die ein bestimmtes Interface implementieren (m├╝ssen).
constructor x Die Funktion wird vor main automatisch aufgerufen, um Initialisierungen zu erledigen. Die Reihenfolge der Aufrufe mehrerer so attributierter Funktionen ist nicht spezifiziert. Die Funktion darf keine Parameter haben. Der Aufruf erfolgt nach dem Initialiseren der globalen und statischen Variablen.

Attribute von avr-gcc

Tabelle: Attribute von avr-gcc
Attribut Funktionen Daten Typen Beschreibung
progmem x Lokatiert read-only Daten ins Flash. progmem in typedef wird nicht unterst├╝tzt!
naked x Funktion wird ohne Prolog/Epilog erzeugt
interrupt x Hier nur wegen der Vollst├Ąndigkeit erw├Ąhnt. Verwendet AVR Libc in ISR(┬Ě,ISR_NOBLOCK)
signal x Hier nur wegen der Vollst├Ąndigkeit erw├Ąhnt. Verwendet AVR Libc in ISR(┬Ě)
OS_main x Ab Version 4.4. Die Funktion sichert keine Register, hat im Gegensatz zu einem naked Funktion jedoch eine return-Instruktion und initialisiert einen Framepointer falls ben├Âtigt.
OS_task x Ab Version 4.4. Wie OS_main, jedoch Interrupt-sicher: Falls der Stackpointer ver├Ąndert werden muss, dann geschiegt das unter IRQ-Abschaltung, was etwas gr├Â├čeren Code wie bei OS_main bedeuten kann.
Beispiele
void __attribute__ ((constructor))
foo (void)
{
   /* Code */
}

Fr├╝he Codeausf├╝hrung vor main()

Mitunter ist es notwendig, Code unmittelbar nach dem Reset auszuf├╝hren, noch bevor man in main() mit der eigentlichen Programmausf├╝hrung beginnt. Das kann zB. zur Bedienung eines Watchdog-Timers erforderlich sein.

Nach einen Reset und vor Aufruf von main werden Initialisierungen ausgef├╝hrt wie

  • Setzen des Stackpointers
  • Vorbelegung globaler Datenobjekte: Daten ohne Initializer werden zu 0 initialisert (Section .bss). F├╝r Daten mit Initializer (Section .data) werden die Werte aus dem Flash ins SRAM kopiert.
  • Initialisierung von Registern wie R1, in dem bei avr-gcc immer die Konstante 0 gehalten wird.

init-Sections

Im Linker-Script werden Sections von .init0 bis .init9 definiert, die nacheinander abgearbeitet werden. Erst danach wird main betreten. Um Code fr├╝h auszuf├╝hren, legt man die Funktion in eine dieser Sections:

void code_init3(void) __attribute__ ((naked, used, section (".init3")));

/* !!! never call this function !!! */
void code_init3 (void)
{
   /* Code */
}

Der Code in den .initn-Sections wird sequenziell ausgef├╝hrt, daher darf in diese Sections nur der nackte Funktions-Code, ohne Prolog und ohne Epilog (also auch ohne ret-Instruktion!). Da diese nackten Funktionen kein ret haben, d├╝rfen sie nicht explizit aufgerufen werden! Ihr Aufruf erfolgt implizit dadurch, da├č ihr Code in einer init-Section steht.

Zu beachten ist dabei

  • Eine so definierte Funktion darf keinesfalls aufgerufen werden!
  • Zuweisungen wie i=0; ergeben vor .init3 inkorrekten Code, da vor Ende von .init2 Register R1 noch nicht mit 0 besetzt ist, avr-gcc aber davon ausgeht, da├č es eben diesen Wert enth├Ąlt.
  • Lokale Variablen m├╝ssen in Registern liegen, denn vor Ende von .init2 ist der Stackpointer noch nicht initialisiert. Zudem ist die Funktion naked, hat also insbesondere keinen Prolog, der den Framepointer (Y-Register) setzen k├Ânnte, falls er ben├Âtigt wird.
  • Gegebenenfalls ist daher die Verwendung von inline-Assembler angezeigt oder die Implementierung in einem eigenen Assembler-Modul, das dazu gelinkt wird. Der erzeugte Code ist im List-File zu ├╝berpr├╝fen.
  • Werden mehrere Funktionen in die selbe init-Section gelegt, ist die Reihenfolge ihrer Ausf├╝hrung nicht spezifiziert und i.A. nicht die gleiche wie in der Quelle.

Unbenutzte init-Sections haben die Nummern 0, 1, 3 und 5 bis 8. Die verbleibenden werden vom Startup-Code verwendet:

.init2
Initialisieren von R1 mit 0 und Setzen des Stackpointers
.init4
Kopieren der Daten vom Flash ins SRAM (.data, .rodata) und L├Âschen von .bss
.init6
Ausf├╝hrung von globalen Konstruktoren
.init9
Aufruf von main

Ein Programmbeispiel f├╝r Code in einer init-Section ist in "Speicherverbrauch bestimmen mit avr-gcc".

Konstruktoren

In GNU-C ist es m├Âglich, auch in C Konstruktoren zu definieren. Diese werden automatisch vor main ausgef├╝hrt:

void myinit(void) __attribute__ ((constructor, used));

void myinit (void)
{
   /* Code */
}

Im Vergleich zu init-Sections ist diese Methode einfacher, da keine Einschr├Ąnkungen wie bei den init-Sections bestehen: Es sind lediglich parameterlose void-Funktionen zu definieren. Konstruktoren m├╝ssen voneinander unabh├Ąngig sein, denn bei mehreren Konstruktoren ist deren Aufrufreihenfolge nicht festgelegt.

'Pers├Ânliche Werkzeuge

USB-RS232 Modul
Controller einfach
mit USB nachr├╝sten
robotikhardware.de


Lichtprofi.de
LED Shop
www.lichtprofi.de