Forum: Mikrocontroller und Digitale Elektronik Bootloader + Int.Vektor


von Nali (Gast)


Lesenswert?

Hallo zusammen!

Habe ein Verständnisproblem und ich hoffe ihr könnt mir helfen.
Bisher stand immer am Anfang meiner FW der Interrupt Vektor.
Die ersten 4 Bytes davon sind der Stack Pointer, die weiteren 4 Bytes 
die Startadresse.
Nun wollte ich "Meta Daten" meiner FW hinzufügen, die im Bootloader 
ausgewertet werden soll. Deshalb müssen diese Informationen immer an der 
selben Adresse stehen. Mittendrin ist blöd. Am Ende auch, da die Länge 
der FW ja sich leicht verändert. "Also warum nicht am Anfang!?", dachte 
ich mir.
Linker File angepasst: d.h. an den Anfang meine MetaDaten (mit fester 
und bekannter größe gelegt), den Interrupt Vektor nach hinten geschoben.
Ohne Fehler kompiliert, gelinkt und geflasht.
Meine Metadaten standen da, wo sie sein sollten, deren Inhalt war auch 
richtig. Auch die Adressen für den StackPointer und meiner Anwendung 
stimmten.
Beim Starten der Applikation lande ich jedoch immer im 
"DebugMon_Handler".

Kann man bitter jemand sagen, was ich falsch mache bzw. warum das nicht 
so funktioniert wie ich es mir gedacht habe?

Danke & Grüße
Nali

von Peter II (Gast)


Lesenswert?

Nali schrieb:
> Kann man bitter jemand sagen, was ich falsch mache bzw. warum das nicht
> so funktioniert wie ich es mir gedacht habe?

ohne den µC zu kennen, wird es verdammt schwer.

von Nali (Gast)


Lesenswert?

Achso ... Sorry! Habe gedacht das wäre allgemein (controllerunabhängig) 
gültig. :-O
Es ist ein STM32F407.

Grüße
Nali

Beitrag #5098161 wurde vom Autor gelöscht.
von Jim M. (turboj)


Lesenswert?

Nali schrieb:
> Linker File angepasst: d.h. an den Anfang meine MetaDaten (mit fester
> und bekannter größe gelegt), den Interrupt Vektor nach hinten geschoben.

Blöd nur dass die Hardware den an einer bestimmten Stelle erwartet. 
Man lese sich mal die Beschreibung des Cortex-M4 durch, im Zweifel auf 
der infocenter.arm.com Webseite.

Ein guter Platz für Firmware Infos ist hinter der Vektortabelle.

von Nali (Gast)


Lesenswert?

Hallo Rüdiger,

vielen dank für deine Antwort.

Das ist so nicht richtig zusammengefasst.
Am Anfang des gesamten Flashs (0x08000000) sitzt ja noch immer mein 
Bootloader, der seinen eigenen SP (Offset: 0 Byte) und PC (Offset: 4 
Byte) hat.
Der Bootloader weiß ja, wo sich das Hauptimage (0x08010000) befindet. 
Bei diesem Image war bisher ebenfalls der Offset für den SP 0 Byte, also 
0x08010000 und 4 Byte (respektive 0x08010004) für den PC.
Nun sind alle ein paar Bytes durch Anpassung des Linkerfiles nach hinten 
geschoben worden. An 0x08010000 sitzen also nun meine Metadaten. Der SP 
steht bei 0x0801000C der PC entsprechend bei 0x08010010. Habe extra 
drauf geachtet, dass meine Metadaten vielfaches von 32bit/4 Byte sind, 
damit es mit dem Padding keine Probleme gibt.

Der PC stimmt auch mit der im Linkeroutputfile angegebenen Adresse der 
Startfunktion überein. Daher wundert mich, dass augenscheinlich alles 
passt und harmoniert und es dennoch nicht funktioniert...

Viele Grüße
Nali

von Nali (Gast)


Lesenswert?

Äh ... warum hat Rüdiger nun seinen Post gelöscht!? Egal ...
Die Antwort passt auch auf die Antwort von dir, Jim. ;)

von Adam P. (adamap)


Lesenswert?

Nali schrieb:
> Der Bootloader weiß ja, wo sich das Hauptimage (0x08010000) befindet.

Hast du dem Bootloader denn auch mitgeteilt, dass SP & PC nun an anderer 
Position liegen?

Du schreibst, der Bootloader weiß es ja...also er wusste & nun mit neuem 
Aufbau?

von Adam P. (adamap)


Lesenswert?

Nali schrieb:
> ... warum hat Rüdiger nun seinen Post gelöscht!?

Werbung ist nicht erlaubt!

von Nali (Gast)


Lesenswert?

Adam P. schrieb:
> Du schreibst, der Bootloader weiß es ja...also er wusste & nun mit neuem
> Aufbau?

Natürlich! SP und PC werden richtig geladen. - Und zuvor werden auch 
richtig die Metadaten ausgelesen ...

von Mike R. (thesealion)


Lesenswert?

Moin,

"eigentlich" sollte deine Lösung dann auch funktionieren. Nur das du 
halt beim Wechsel vom Bootloader zur Application die Adresse 0x0801000C 
als Startadresse angeben musst (und nicht die 0x08010000 wie vorher).

(Und zusätzlich musst du den µC natürlich noch sagen, wo er die 
Vectortabelle findet (bevor du irgendwelche Interrupts nutzt).

von Nali (Gast)


Lesenswert?

Mike R. schrieb:
> Application die Adresse 0x0801000C
> als Startadresse angeben musst (und nicht die 0x08010000 wie vorher).

Ähm ... ist das jetzt ein Test? Ich setze als Startadresse die Adresse, 
die an der Adresse 0x08010010 liegt/abgespeichert ist.

Mike R. schrieb:
> (Und zusätzlich musst du den µC natürlich noch sagen, wo er die
> Vectortabelle findet (bevor du irgendwelche Interrupts nutzt).

Nochmal ... ähm ... Was? Wo? Wie?
Vermutlich liegt hier der Wurm begraben!

von Adam P. (adamap)


Lesenswert?

Nali schrieb:
> Ähm ... ist das jetzt ein Test?

Zur Klarstellung:

1. SP wird mit dem Wert geladen der an 0x0801000C liegt
2. PC wird mit dem Wert geladen der an 0x08010010 liegt

richtig?

von Nali (Gast)


Angehängte Dateien:

Lesenswert?

Adam P. schrieb:
> Zur Klarstellung:
>
> 1. SP wird mit dem Wert geladen der an 0x0801000C liegt
> 2. PC wird mit dem Wert geladen der an 0x08010010 liegt
>
> richtig?

Ja, richtig!

Aber deine Vermutung mit der Vektortabelle geht in die richtige 
Richtung!
Wie soll ich es sagen? - Das Register VTOR nimmt den zusätzlichen Offset 
nicht an!
Wie man im angefügten Screenshot sehen kann setze ich in den Int.Vektor 
Adresse auf 0x08010010 und im Register selbt steht jedoch 0x08010000 .
Als Gegentest habe ich VTOR auf 0x08020010 gesetzt; im Register steht 
aber "nur" 0x08020000.
Damit ist wohl die Ursache gefunden! - Nur der Grund ist mir nicht klar! 
?:-O

Gruß
Nali

von Sowieso (Gast)


Lesenswert?

Ist das nicht eine 32-bit CPU? Ginge 0x08020020?

Oder bin ich jetzt völlig blau. Iss nur geraten:-)

von Jim M. (turboj)


Lesenswert?

Nali schrieb:
> . An 0x08010000 sitzen also nun meine Metadaten. Der SP
> steht bei 0x0801000C der PC entsprechend bei 0x08010010. Habe extra
> drauf geachtet, dass meine Metadaten vielfaches von 32bit/4 Byte sind,
> damit es mit dem Padding keine Probleme gibt.

Klappt nicht. Bitte Doku zu Cortex M lesen, die Vektortabelle hat 
strengeres Alignment - IIRC 128 oder 256 Byte. Siehe VTOR.

von Sowieso (Gast)


Lesenswert?

When setting TBLOFF, you must align the offset to the number of 
exception entries in the vector table. The minimum alignment is 32 
words, enough for up to 16 interrupts. For more interrupts, adjust the 
alignment by rounding up to the next power of two. For example, if you 
require 21 interrupts, the alignment must be on a 64-word boundary 
because the required table size is 37 words, and the next power of two 
is 64. See your vendor documentation for the alignment details for your 
device.


aus

http://infocenter.arm.com/help/index.jsp?topic=/com.arm.doc.dui0552a/Ciheijba.html

von Adam P. (adamap)


Lesenswert?

Wenn das ein M4 Core ist, dann sind die Bits [6:0] reserviert.
Der Wert der nach VTOR geschrieben wird ist im Reset_Handler auch 
maskiert:
1
/* Set the vector table base address */
2
pSrc = (uint32_t *) & _sfixed;
3
SCB->VTOR = ((uint32_t) pSrc & SCB_VTOR_TBLOFF_Msk);

Deshalb nimmt er die unteren Bit-Änderungen nicht an.

Siehe Cortex M4 UserGuide 4.3.4 Vector Table Offset Register.

von Ruediger A. (Firma: keine) (rac)


Lesenswert?

Nali schrieb:
> Äh ... warum hat Rüdiger nun seinen Post gelöscht!? Egal ...
> Die Antwort passt auch auf die Antwort von dir, Jim. ;)

Ich hatte deinen Post nochmal etwas genauer gelesen, und dabei war mir 
aufgefallen, dass meine Interpretation der Sache (wie auch Jim's 
ursprüngliche) falsch und damit meine Antwort nicht sachdienlich war.

Sorry 4 the noise!

Ich denke auch, dass die alignment Sache den Kern trifft.

von Mike R. (thesealion)


Lesenswert?

Zusätzlich würde ich noch einmal darüber nachdenken, die Metadaten 
hinter die Vektortabelle zu legen.
Ansonsten musst du das nächste mal, wenn die Metadaten größer werden, 
Bootloader und Programm anpassen und bist nicht mehr mit alten Versionen 
kompatibel.

von Nali (Gast)


Lesenswert?

Jim M. schrieb:
> Klappt nicht. Bitte Doku zu Cortex M lesen, die Vektortabelle hat
> strengeres Alignment - IIRC 128 oder 256 Byte. Siehe VTOR.

Kommt hin ...
Alles was kleiner als 0x80 ist wird auf 0x00 "abgerundet".
Alles zwischen als 0x80 und 0xFF wird auf 0x80 "abgerundet".

Aus 0x08010037F wird also 0x080100300.
Aus 0x08020019F wird also 0x080200180.
0x080200500 bleibt bei 0x080200500 und 0x080200880 bleibt bei 
0x080200880.

Mike R. schrieb:
> Bootloader und Programm anpassen und bist nicht mehr mit alten Versionen
> kompatibel.

Werde ich tun!

@ALL:
Vielen Dank für eure konstruktive Hilfe!
Hat Spaß gemacht! ;)

Grüße
Nali

von Adam P. (adamap)


Lesenswert?

Nur noch aus Neugier: Wie fügst du die Meta-Daten in deine FW ein?
Im Source (als const) oder erst nachdem die FW erstellt wurde?

von Mike R. (thesealion)


Lesenswert?

Ich hab den Thread zwar nicht erstellt, aber da ich auch gerne 
entsprechende Metadaten in der Firmware unterbringe:
Sowohl als auch.

Die Meta Daten sind ein const struct, das an der vorgegebenen Stelle 
(hinter der Vectortabelle) platziert wird.
Zusätzlich habe ich einige Tools, die die erstellte FW durchlaufen und 
einige Marker in diesem Struct ersetzen. (z.B. eine Checksumme einfügen)
Und drittens kann auch der Bootloader nach dem flashen noch bestimmt 
Felder eintragen (Wenn die mit 0xFF initialisiert sind, geht das ohne 
die Page noch einmal löschen zu müssen).

: Bearbeitet durch User
von Nali (Gast)


Lesenswert?

Mike R. schrieb:
> Ich hab den Thread zwar nicht erstellt, aber da ich auch gerne
> entsprechende Metadaten in der Firmware unterbringe:
> Sowohl als auch.
>
> Die Meta Daten sind ein const struct, das an der vorgegebenen Stelle
> (hinter der Vectortabelle) platziert wird.
> Zusätzlich habe ich einige Tools, die die erstellte FW durchlaufen und
> einige Marker in diesem Struct ersetzen. (z.B. eine Checksumme einfügen)
> Und drittens kann auch der Bootloader nach dem flashen noch bestimmt
> Felder eintragen (Wenn die mit 0xFF initialisiert sind, geht das ohne
> die Page noch einmal löschen zu müssen).

Ich bin der TO und der Text könnte genau so gut auch von mir sein! :-D

von Nali (Gast)


Lesenswert?

Anmerkung:
Zu den ganzen Links, die ihr gefunden und gepostet habt, habe ich auch 
diesen hier gefunden:
https://community.st.com/thread/41729-how-do-execute-a-program-from-a-location-other-than-the-beginning-of-flash-memory

von Adam P. (adamap)


Lesenswert?

Alles klar, danke.

Und was für Infos schreibt ihr da rein?
Version, Datum?

Habe sowas ähnliches, jedoch überleg ich grad, ob euer Ansatz eleganter 
ist :-)

Meine erstellte FW läuft durch ein Tool und der FW wird ein Header 
vorangestellt. Dieser wird durch den Bootloader extrahiert, geprüft etc. 
und die FW (ohne Header) in den Flash kopiert.

von Nali (Gast)


Lesenswert?

@Mike
Platzierst du deine Metadaten immer direkt hinter den Int.Vektor, oder 
irgendwo mit sicherem Abstand dahinter?
Ersteres hat ja die Gefahr, dass wenn man was am Int.Vektor ändert, z.B. 
neue Interrupts hinzufügt und verwendet auch die Endadresse nicht mehr 
stimmt.
Eine bestimmte Speicherstelle "irgendwo im sicheren Abstand dahinter", 
fürh dazu, dass evtl. irgendwelche Codefragmente entstehen, da der 
Linker eben an der Speicherstelle die Metadaten einfügen muss ...

von Nali (Gast)


Lesenswert?

Adam P. schrieb:
> Version, Datum?

+Länge des Bin-Files für Prüfsumme, Prüfsumme selbst, Statusflags, ...

von Mike R. (thesealion)


Lesenswert?

Da ich den IAR Compiler incl. Start-Up files benutze, hat meine 
Vetortabelle eine feste Länge (solange ich nicht den Controller 
wechsel).

Beim STM32F415 liegt der Block z.B. an Adresse 0x08008188. Das ist das 
erste Byte hinter das maximalen Vector Tabelle.

@Adam: Die Metadaten mit in den Flash zu schreiben hat einfach den 
Vorteil, das der Bootloader bei jedem Start die Application prüfen kann 
und es so nahezu unmöglich ist das gerät zu bricken.
Wenn das flashen fehlschlägt, bleibt das Programm auch nach einem 
Neustart im Bootloader und man kann es noch einmal probieren.

von Mike R. (thesealion)


Lesenswert?

Nali schrieb:
> Adam P. schrieb:
>> Version, Datum?
>
> +Länge des Bin-Files für Prüfsumme, Prüfsumme selbst, Statusflags, ...

+Firmware ID (um bei mehreren Geräten ein vertauschen der Firmware zu 
verhindern), Version der Metadaten

von Ruediger A. (Firma: keine) (rac)


Lesenswert?

Nali schrieb:
> @Mike
> Platzierst du deine Metadaten immer direkt hinter den Int.Vektor, oder
> irgendwo mit sicherem Abstand dahinter?
> Ersteres hat ja die Gefahr, dass wenn man was am Int.Vektor ändert, z.B.
> neue Interrupts hinzufügt und verwendet auch die Endadresse nicht mehr
> stimmt.
> Eine bestimmte Speicherstelle "irgendwo im sicheren Abstand dahinter",
> fürh dazu, dass evtl. irgendwelche Codefragmente entstehen, da der
> Linker eben an der Speicherstelle die Metadaten einfügen muss ...

Bin zwar nicht Mike, aber hier meine Antwort:

Ich lege den Anker i.d.Regel direkt hinter das Ende der IVT.

Wer's am Rande der Legalität mag: Du kannst einen Zeiger auf die 
Struktur auch an Vector 0 (initial SP) legen und nach dem Hochfahren den 
SP explizit umsetzen (damit kannst Du Dir bis zur ersten stackbasierten 
Operation Zeit lassen, natürlich aber so füh wie möglich). Aber Achtung: 
FreeRTOS (und zweifellos auch Andere OS und Softwaren) trifft Annahmen 
über das IVT layout, d.h. beim Hochstarten von FreeRTOS wird der SP über 
Vector 0 der über VTOR zugreifabren IVT nochmals umgesetzt, d.h der 
Startup des OS müsste manuell umgebogen werden.

Du kannst auch einen beliebigen immer unbenutzten Vektor als Zeiger auf 
den Anker umbiegen oder deine Struktur mit einer Signatur absichern, die 
niemals eine gültige Adresse sein kann und dann beim Hochfahren von 0 
bis zu dieser Signatur suchen. Dann können deine Metadaten auch variabel 
liegen (aber natürlich niemals hinter Codebeginn). All solche Sachen 
sind aber tendentiell eher fehleranfällig. In meiner Philosophie ist 
small=beautiful.

Ich habe im Beispielcode meines Buches Kapitel 9 eine 
Beispielimplementatio n, die viel von dem abdeckt, was hier beschrieben 
wurde (das ist keine Werbung, weil der Code kostenfrei und frei 
zugänglich ist).

: Bearbeitet durch User
von Nali (Gast)


Lesenswert?

Mike R. schrieb:
> @Adam: Die Metadaten mit in den Flash zu schreiben hat einfach den
> Vorteil, das der Bootloader bei jedem Start die Application prüfen kann
> und es so nahezu unmöglich ist das gerät zu bricken.
> Wenn das flashen fehlschlägt, bleibt das Programm auch nach einem
> Neustart im Bootloader und man kann es noch einmal probieren.

Wieso muss man das Rad immer wieder neu erfinden!?
Auch genau das sind meine Intensionen!

Mike R. schrieb:
> +Firmware ID (um bei mehreren Geräten ein vertauschen der Firmware zu
> verhindern), Version der Metadaten

"Ja" und "gute Idee"! ;-)

von Nali (Gast)


Lesenswert?

@Mike:
Nutzt du auch zusätzlich einen festen und eindeutigen Startidentifier 
deiner Metadaten?

von Nali (Gast)


Lesenswert?

Ruediger A. schrieb:
> Wer's am Rande der Legalität mag: Du kannst einen Zeiger auf die
> Struktur auch an Vector 0 (initial SP) legen und nach dem Hochfahren den
> SP explizit umsetzen (damit kannst Du Dir bis zur ersten stackbasierten
> Operation Zeit lassen, natürlich aber so füh wie möglich). Aber Achtung:
> FreeRTOS (und zweifellos auch Andere OS und Softwaren) trifft Annahmen
> über das IVT layout, d.h. beim Hochstarten von FreeRTOS wird der SP über
> Vector 0 der über VTOR zugreifabren IVT nochmals umgesetzt, d.h der
> Startup des OS müsste manuell umgebogen werden.
>
> Du kannst auch einen beliebigen immer unbenutzten Vektor als Zeiger auf
> den Anker umbiegen oder deine Struktur mit einer Signatur absichern, die
> niemals eine gültige Adresse sein kann und dann beim Hochfahren von 0
> bis zu dieser Signatur suchen. Dann können deine Metadaten auch variabel
> liegen (aber natürlich niemals hinter Codebeginn). All solche Sachen
> sind aber tendentiell eher fehleranfällig. In meiner Philosophie ist
> small=beautiful.

Ähm ... wird mir zu kompliziert ... Da kommt dann schnell "Error in 
Layer 8" hinzu!

Ruediger A. schrieb:
> (das ist keine Werbung, weil der Code kostenfrei und frei
> zugänglich ist).

Von deinem Buch ist ja nicht einmal die Leseprobe kostenlos! :-P
(Habe dich schon gegoogelt. :-D)

von Mike R. (thesealion)


Lesenswert?

Nali schrieb:
> @Mike:
> Nutzt du auch zusätzlich einen festen und eindeutigen Startidentifier
> deiner Metadaten?

Nein, ich verlasse mich darauf, das das strcut immer an der Adresse 
liegt, an der ich es erwarte.

von Mike R. (thesealion)


Lesenswert?

Das ist jetzt zwar eine alte Version, aber so sieht das ganze ungefähr 
aus

header
1
  typedef const struct _SystemInformation  {
2
      struct {
3
        uint32_t    Major;
4
        uint32_t    Minor;
5
        uint32_t    Patch;
6
        char      PreRelease[8];
7
        char      Meta[8];
8
      } Version;
9
10
      char        Type[8];
11
      char        Description[32];
12
13
      uint32_t      Checksum;
14
15
      const char      CompileDate[12];
16
      const char      CompileTime[12];
17
18
  } SysInformation_s;

code
1
  __root const SysInformation_s AppInfo  @ ".appInformation"  = {
2
      {
3
        0,          //Major
4
.
5
.
6
.

von Ruediger A. (Firma: keine) (rac)


Lesenswert?

Nali schrieb:
>
> Ruediger A. schrieb:
>> (das ist keine Werbung, weil der Code kostenfrei und frei
>> zugänglich ist).
>
> Von deinem Buch ist ja nicht einmal die Leseprobe kostenlos! :-P
> (Habe dich schon gegoogelt. :-D)


uhm, falsch:

https://books.google.de/books?id=8ra8DQAAQBAJ&pg=PR3#v=onepage&q&f=false

der Code ist in jedem Fall frei zugänglich:

http://www.springer.com/de/book/9783658148492

(ca. halbe Seite runter, "Zusatzmaterial")

von Nali (Gast)


Lesenswert?

Mike R. schrieb:
> Das ist jetzt zwar eine alte Version, aber so sieht das ganze ungefähr
> aus

Diese Ähnlichkeit! - Verblüffend! :-D

Nur bei der Positionierung unterscheiden wir uns!
Ich verwende den Pragma-Operator:
1
#pragma location = ".metadata"
Und im Linker file habe ich dann ein
1
address mem: __ICFEDIT_metadata_start__ { readonly section .metadata };

Sollte man nicht die Meta-Versionsnummer ganz an den Anfang legen?

Ruediger A. schrieb:
> uhm, falsch:
>
> https://books.google.de/books?id=8ra8DQAAQBAJ&pg=PR3#v=onepage&q&f=false

Ui! Das ein Autor Google-Books referenziert!?

Der Verlag tut es auf jeden Fall nicht:
http://www.springer.com/de/book/9783658148492#aboutBook

von Nali (Gast)


Lesenswert?

Ruediger A. schrieb:
> uhm, falsch:
>
> https://books.google.de/books?id=8ra8DQAAQBAJ&pg=PR3#v=onepage&q&f=false

Hmmm ... Bin mal kurz über das Buch rüber gescrollt. Schein ganz nett zu 
sein! ;-)
Was mir auffällt, trotz Cortex-spezifika sehe ich nirgends eine 
Beschreibung der HW-Faults (HardwareFault, Bus-Fault, Usage Fault), 
obwohl du "Faults" thematisierst.

von Mike R. (thesealion)


Lesenswert?

Nali schrieb:
> Sollte man nicht die Meta-Versionsnummer ganz an den Anfang legen?

Sollte man auf jeden Fall tun.

Wie gesagt, das ist eine alte Version.
Die hat noch keine Versionsnummer für die Metadaten.

Das Meta in der von mir genannten Struktur gehört zur Firmware Versions 
Nummer.

von Nali (Gast)


Lesenswert?

Mike R. schrieb:
> Das Meta in der von mir genannten Struktur gehört zur Firmware Versions
> Nummer.

Achso ...

von Ruediger A. (Firma: keine) (rac)


Lesenswert?

Nali schrieb:
> Ruediger A. schrieb:
>> uhm, falsch:
>>
>> https://books.google.de/books?id=8ra8DQAAQBAJ&pg=PR3#v=onepage&q&f=false
>
> Hmmm ... Bin mal kurz über das Buch rüber gescrollt. Schein ganz nett zu
> sein! ;-)
> Was mir auffällt, trotz Cortex-spezifika sehe ich nirgends eine
> Beschreibung der HW-Faults (HardwareFault, Bus-Fault, Usage Fault),
> obwohl du "Faults" thematisierst.

Hi Nali,

beantworte ich gerne, aber lieber offline, sonst hat das Ganze zu sehr 
ein Geschmäckle von Hijacken des threads für Werbezwecke... schick mir 
am Besten eine PM, dann können wir das Offline weiterführen!

Bitte melde dich an um einen Beitrag zu schreiben. Anmeldung ist kostenlos und dauert nur eine Minute.
Bestehender Account
Schon ein Account bei Google/GoogleMail? Keine Anmeldung erforderlich!
Mit Google-Account einloggen
Noch kein Account? Hier anmelden.