Step 7 Bereichslängenfehler bei einem Multiinstanz-FC

Pimp.my.PC

Level-1
Beiträge
16
Reaktionspunkte
0
Zuviel Werbung?
-> Hier kostenlos registrieren
Guten Morgen,

Ich habe hier einen Bereichlängenfehler in einem FC, der im Programm sehr oft aufgerufen wird.
Es wird eine DB-Adresse berechnet, anhand extern angelegter Daten beim Aufruf.
Die Adresse ist weit außerhalb des DB-Bereichs, das habe ich schon gesehen, normalerweise ist ein Bereichslängenfehler auch kein Problem für mich.

Jetzt das große ABER:
Ich weiß nicht, wie ich nun herausfinden kann, in welchem Aufruf der Fehler passiert.
Der Baustein wird ca. 500 mal aufgerufen (sehr großes Programm) und nur an ein, zwei stellen tritt der Fehler auf.
Wie kann ich jetzt herausfinden, in welchem Aufruf das passiert?

LG
Sebastian

PS: Die SPS läuft im Produktivbetrieb, ich kann sie nicht stoppen lassen, indem ich OB121 lösche und dann in die Stacks schaue.
 
Dafür hat siemens z.B. den Simulator erfunden Da kannst du das dann einfach in Stop laufen lassen.

Aber es gäbe ja noch die Möglichkeit den OB121 den du ja geladen hast auch auszuwerten. Die sind nicht nur dazu da die CPU vorm Stop zu bewahren.

Code:
ORGANIZATION_BLOCK "Programming error"
{ S7_Optimized_Access := 'TRUE' }
VERSION : 0.1
   VAR_TEMP 
      index : Int;
   END_VAR




BEGIN
    FOR #index := 100 TO 1 BY -1 DO
        "FehlerSpeicher".OB121[#index] := "FehlerSpeicher".OB121[#index - 1];
    END_FOR;
    
    "FehlerSpeicher".OB121[0].BlockNr := #BlockNr;
    "FehlerSpeicher".OB121[0].Reaction := #Reaction;
    "FehlerSpeicher".OB121[0].Fault_ID := #Fault_ID;
    "FehlerSpeicher".OB121[0].BlockType := #BlockType;
    "FehlerSpeicher".OB121[0].Area := #Area;
    "FehlerSpeicher".OB121[0].DBNr := #DBNr;
    "FehlerSpeicher".OB121[0].Csg_OBNr := #Csg_OBNr;
    "FehlerSpeicher".OB121[0].Width := #Width;
END_ORGANIZATION_BLOCK

Es sollte noch ein anwenderdefinierte Datentyp definiert werden, kann man ebenfalls als Quelle laden:
Code:
TYPE "OB121_Struct"
VERSION : 0.1
   STRUCT
      BlockNr { S7_HMI_Accessible := 'False'; S7_HMI_Visible := 'False'} : UInt;   // Number of causing block
      Reaction { S7_HMI_Accessible := 'False'; S7_HMI_Visible := 'False'} : USInt;   // 0:ignore, 1:substitute, 2:skip
      Fault_ID { S7_HMI_Accessible := 'False'; S7_HMI_Visible := 'False'} : Byte;   // Fault identifier
      BlockType { S7_HMI_Accessible := 'False'; S7_HMI_Visible := 'False'} : USInt;   // 16#01:OB, 16#02:FC, 16#03:FB, 16#04:SFC, 16#05:SFB, 16#06:DB: Type of block causing the error
      Area { S7_HMI_Accessible := 'False'; S7_HMI_Visible := 'False'} : USInt;   // Area where the error occured
      DBNr { S7_HMI_Accessible := 'False'; S7_HMI_Visible := 'False'} : DB_ANY;   // DB number if Area = DB or DI
      Csg_OBNr { S7_HMI_Accessible := 'False'; S7_HMI_Visible := 'False'} : OB_ANY;   // Number of OB causing the error
      Csg_Prio { S7_HMI_Accessible := 'False'; S7_HMI_Visible := 'False'} : USInt;   // Priority of OB causing the error
      Width { S7_HMI_Accessible := 'False'; S7_HMI_Visible := 'False'} : USInt;   // 0:bit, 1:byte, 2:word, 3:dword, 4:lword
   END_STRUCT;


END_TYPE

in einem DB (in meinem Beispiel heisst der "FehlerSpeicher") dadrin ein Array [0..100] of Struct bzw "OB121_Struct" erstellen. Der OB121 sollte also dann nicht unbedingt auf undefinierte Speicherbereiche zugreifen. Also vorher mal testen.

mfG René

edit: kleines Beispiel zugefügt
edit2: weil einige nicht wissen wie das mit Anwenderdefinierten Datentypen funktioniert
 
Zuletzt bearbeitet:
Auf die Schnelle grenze ich bei so einem Fehler die Fehlerstelle durch Einfügen von BEA oder durch Auskommentieren von Bausteinaufrufen ein.
 
Das Auswerten der OB121-Informationen wird bei diesem Fehler vermutlich garnichts bringen.
Die Info, welche Art Fehler auftrat und auf welche fehlerhafte Adresse zugegriffen werden soll, findet man ja bereits im Diagnosepuffer. (Der Code von vollmi ist im Grunde nichts anderes als ein auf Programmierfehler gefilterter zusätzlicher Diagnosepuffer. Mehr Infos als der CPU-Diagnosepuffer liefert vollmis Puffer nicht.)
Die Programmadresse wo der Fehler auftrat, wird bei S7-300 nicht mitgeteilt. Und selbst wenn man die Programmstelle wüßte, dann weiß man immer noch nicht, bei welchem Aufruf der Fehler passiert.

@Pimp.my.PC
Woher weißt Du, in welchem FC der Zugriffsfehler auftritt?
Tritt der Fehler in jedem OB1-Zyklus auf oder nur selten/sporadisch?

500 Aufrufstellen sind schon mal ganz schön viele. Da bräuchte man schon sehr viel Glück und Intuition oder Ausdauer, um den fehlerhaften Aufruf direkt zu finden.

Bei so vielen potentiellen Fehlerstellen würde ich wahrscheinlich so vorgehen:
- am Anfang des OB1 eine globale Variable (z.B. Merkerwort MW100) auf 0 schreiben
- im OB121 das Merkerwort inkrementieren
- am Ende des OB1 ein "L MW100" einfügen und beobachten --> da sehe ich schon mal, ob und wieviele fehlerhafte Aufrufe es gab
- beginnend im OB1 ca. in der Mitte des Programms ein "L MW100" einfügen und beobachten --> ist der Wert da noch 0, dann liegt die Fehlerstelle weiter hinten im Programm, ist der Wert <> 0 dann weiter vorn
- nun in ca. der Mitte des verbleibenden Programmbereichs ein "L MW100" einfügen und beobachten --> und so weiter...

Auf diese Art sollte nach Einfügen von 10 bis 15 Teststellen der erste fehlerhafte Aufruf zu finden sein. Falls der Fehler nicht in jedem OB1-Zyklus auftritt, dann kann das Testlesen auf ein Umspeichern auf verschiedene MW oder DBW bei <>0 erweitert werden. Dieses Vorgehen kann man in der laufenden Anlage machen.


Irgendwie ist der Fehler aber auch ein Bug im FC, daß er (unzulässige?) Eingangsparameter (ungeprüft?) verarbeitet und fehlerhafte Zugriffsadressen erzeugt. (oder ist das so ein Baustein zum absolut adressierten 'rumpoken im Datenspeicher, der prinzipiell auf jede denkbare Adresse zugreifen können soll?) Einen Bug könnte man beseitigen und dabei vielleicht eine Fehlerrückgabe programmieren. Zuerstmal prüfen, ob der Fehler vielleicht nur im FC liegt (z.B. falsche Verwendung von TEMP, falsche Verarbeitung von Datentypen, falsche Operationen, falsche Logik) oder ob es wirklich ein fehlerhafter Aufruf ist.

Harald
 
Zuviel Werbung?
-> Hier kostenlos registrieren
Ich mach dafür einen zusätzlichen speicherplatz in die Struktur.

zu beginn von OB1 lade ich dann 0
Code:
"Auslösestelle" := 0;

Dann kann man an verschiedenen neuralgischen Punkten die Zuordnungsnummern laden
z.B. vor dem 1. Aufruf FB11
Code:
"Ausloesestelle" := 2020;
vor dem 2. Aufruf FB11
Code:
"Ausloesestelle" := 2021;

Im OB121 muss dann einfach noch zusätzlich eingefügt werden
Code:
"FehlerSpeicher".OB121[0].Ausloesestelle:= "Ausloesestelle";

Dann hat man zum Fehler auch immer die letzte Ausloesestelle. 0 Wenn die erste Auslösestelle noch nicht erreicht wurde.

Das kann man übrigens bei kritisch zu parametrierenden Bausteinen, auch fest einplanen. Und an der Schnittstelle eine AufrufID übergeben welche unbedingt auf "Ausloesestelle" kopiert wird (Und zwar vor dem eigentlichen Bausteincode).

mfG René
 
Wenn man jetzt schon den Baustein 500mal aufruft. am Start des Bausteins z.B.
"Ausloesestelle" := "Ausloesestelle" + 1;
Einfügen. Dann weiss man zumindest der wievielte Aufruf den Fehler ausgelöst hat.

mfG René
 
Ein Übergabeparameter "AufrufID" ist eine gute Variante, nur leider nachträglich nur noch sehr aufwendig einzubauen, weil dadurch alle Aufrufe geändert werden müssen.

Harald
 
Zuviel Werbung?
-> Hier kostenlos registrieren
Hey,

Ein Paar Fakten vorab:
Es ist eine S7-416F
Das Programm ist nicht von mir, ich betreue lediglich die Anlage.
Die Anlage läuft und ich kann leider keine einzelnen Aufrufe auskommentieren, oder größere Änderungen vornehmen, da die Produktion gesichert sein muss.

Es handelt sich hier um einen FC , welcher Daten der zu fördernden Teile auf einen nächsten Förderer (DB) kopiert. Die Förderstrecke ist mehrfach verzweigt und bedient mehrere Anlagen.
Es ist im Prinzip ne Datenverfolgung. Der FC wird immer von dem jeweiligen Förderer-FC aufgerufen, wenn dieser an den nächsten übergibt.
Somit ergibt sich kein zyklischer Aufruf.

@PN/DP: Es ist definitiv ein Bug im FC, da dieser die Eingangsvariable ungeprüft von iQuelle nach iZiel umkopiert. Das weiß ich.
Aber wenn man ihn überall ordentlich beschreibt, passiert halt nix, deswegen war es nicht notwendig eine Prüfung einzubauen.

Ich weiß aus dem Diagnosepuffer, welcher FC das is, ich weiß nur nicht, wo er seine falschen Daten herbekommt.

Ereignis 1 von 120: Ereignis-ID 16# 2523
Bereichslängenfehler beim Schreiben
Global -DB , Wortzugriff, Zugriffsadresse: 53536
FC-Nummer: 800
Bausteinadresse: 620
Angeforderter OB: Programmierfehler-OB (OB 121)
Prioritätsklasse: 1
interner Fehler, kommendes Ereignis
08:04:58.651 21.04.2016
(Kodierung: 16# 2523 0179 8C24 D120 0320 026C)


Das mit der Aufruf-ID ist auch ne sehr gute Idee, allerdings müsste ich auch dann an jeden aufrufenden Baustein ran, oder an den Baustein selbst.

LG und danke für die guten Tipps.
Sebastian
 
du könntest eben im entsprechenden Baustein mal den Code einfügen
L MW100
L 1
+I
T MW100

Oder halt entsprechend der Programmiersprache die der Baustein hat.
zu begin des OB1
L 0
T MW100

Im OB121 muss dann MW100 (oder die entsprechende Variable die du verwendet hast) weggesichert werden.
Damit hast du dann den n-ten Bausteinaufruf, dieser verursacht den Zugriffsfehler effektiv. Wenn du ihn in die Obengenannte Struktur einpflegst hast du den n-ten Bausteinaufruf zum Fehlereintrag im OB121.

Es muss also nur der fehlerbehaftete Baustein einmal geladen werden, der DB der als Fehlerspeicher fungiert, plus der geänderte OB121. Achte darauf der OB121 sauber läuft und keine Zugriffsfehler verursacht.
Schnell im Simulator laden das FIFO und so sauber sind und einen programmfehler verursachen.

mfG René
 
Hmm, ein Aufrufzähler im FC wäre eine schöne schnelle Lösung, wenn alle FC-Aufrufe immer ausgeführt werden würden. (Die Idee ist gut: Der FC produziert unique Diagnosewerte und der OB121 sichert die weg - so'rum funktioniert das auch bei sporadisch auftretenden Fehlern.) Doch da der FC nur bei Bedarf aufgerufen wird hilft der Aufrufzähler nicht (liefert vermutlich immer 1). Das Wegsichern eines Diagnosewertes im OB121 kann man aber schonmal nutzen (*).

Sind eigentlich bei den FC-Aufrufen jeweils andere DB/IDB geöffnet? Dann könnte man auch die DBNO/DINO wegsichern und damit die Aufrufstelle einkreisen.

Du mußt da wohl die bisher genannten Varianten zur Erzeugung von Diagnosewerten passend zu Deinem Programm etwas flexibel kombinieren um die Fehlerstellen einzukreisen. z.B. am Anfang der übergeordneten Förderer-FC setzt Du MW100 auf 1000, 1100, 1200, 1300 ... und hast dadurch schon mal den FC, in dem der fehlerhafte FC-Aufruf liegt. Dann in diesem Förderer-FC (schrittweise) vor jedem FC-Aufruf den MW100 individuell setzen zu 1201, 1202, 1203 ... oder zunächst jeden zehnten Aufruf zu 1210, 1220, 1230, ...

Wie häufig kommen die Fehler eigentlich?
Magst Du uns den Code des FC vielleicht mal zeigen? Vielleicht sehen wir da den Fehler oder weitere Ansatzpunkte zum Finden der individuellen Instanz. (Vielleicht ist da ja einfach nur eine 16-Bit-Operation verwendet wo eine 32-Bit-Operation hingehört?)
Welches Step7 verwendest Du eigentlich?


(*) Allerdings würde ich jetzt nicht den FIFO in den OB121 programmieren (im OB121 darf man keinen Programmierfehler machen!). Ein Kopieren des MW100 auf ein zweites MW reicht zum finden jeweils einer Stelle im laufenden Programm.

Harald
 
Zuviel Werbung?
-> Hier kostenlos registrieren
Wie häufig kommen die Fehler eigentlich?
Magst Du uns den Code des FC vielleicht mal zeigen? Vielleicht sehen wir da den Fehler oder weitere Ansatzpunkte zum Finden der individuellen Instanz.
Den Fehler kenne ich ja bereits.
Also, ich erkläre mal kurz den Ablauf (Vereinfacht mit 10 Förderern):

- DB1 enthält 100 STRUCTs in denen Teileinformationen enthalten sind: Charge, Menge, Produktionsdatum, Artikelnummer, usw.
- DB2 enthält 10 STRUCTs, die wiederum zu jedem Förder einen INT enthält, dessen Inhalt der Zeiger zu dem jeweiligen STRUCT in DB1 ist (den sog. Index).
- FC100 ist der oft aufgerufene Kopierbaustein

Jeder Förderer hat seinen eigenen FC, der sich um die Ansteuerung und deren Logik kümmert.

Wenn jetzt das Teil von Förderer 1 (FC1) auf Förderer 2 (FC2) fährt, ruft FC1 den den FC100 auf und übergibt als Quelle 1 und als Ziel 2.
FC100 kopiert dann den Inhalt des Quell-INTs in den Ziel-INT im DB2.

Der Vorteil hierbei ist, dass immer nur der Verweis umkopiert werden muss und die Daten immer am selben Ort liegen bleiben.

Das Problem scheint zu sein, dass der FC100 in irgendeinem Fördererbaustein (ein Nebenabzweig, der nicht oft befahren wird) eine ungültige Zahl als Quell- oder Ziel bekommt (in unserem Beispiel, sagen wir 13).
Das ganze ist aber zu komplex um das einfach zu suchen, da hier auch nur mit Verweisen gearbeitet wird, da sich ein Förderer ja auch andersrum drehen kann und dann ein anderes Ziel hat. Zumal sind auch Abzweigungen enthalten.

Als ich meine Frage gestellt hab, dachte ich, STEP7 bringt eine Funktion mit, mit der ich nachsehen kann, aus welchem Aufruf der Fehler kommt und dass ich diese Funktion einfach nicht finde.
Ich werde mir nun aus den hier gegebenen Tipps eine Lösung zusammenbauen.
Vielen Dank an euch für die sehr guten und hilfreichen Tipps.

LG
Sebastian

PS: Ich denke, ich verwende die neueste Version. Lade regelmäßig Updates. Bin schon aufm Heimweg, kann nicht mehr nachsehen.
 
Bei meiner Frage nach Deiner Step7-Version meinte ich, ob Du Step7 "classic" V5.5 oder Step7 TIA V13.x benutzt.
In Step7 V5.5 gibt es meines Wissens keine Möglichkeit, den Aufrufpfad eines FC zur Laufzeit herauszubekommen (vermutlich im B-Stack). Höchstens wenn man die CPU in Stop gehen läßt, dann kann man in den Baustein-Stack, den L-Stack und den U-Stack schauen.
In Step7 TIA weiß ich nicht, vermutlich gibt es da aber auch nicht mehr Diagnosemöglichkeiten.


Werden die Daten nur so "aus Spaß" mitgereicht oder bleibt das Fördergut irgendwo nach dem Fehler stehen? Gibt es Folgefehler?
Kann der Fehler nicht logisch gesucht werden, indem man sich die Transfer-DB ansieht, wo denn Daten fehlen?
Kann anhand des Fehler-Zeitstempels mit einer anderen Datenbank verglichen werden, wo ein Fördergut zu der Zeit unterwegs war?

Harald
 
Kannst du uns den code des FC100 zeigen?
Kann man die Zweige die zum Fehlerzeitpunkt genutzt wurden irgendwie nachvollziehen? Damit könnte man die Fehlersuche mit der AufrufID etwas einschränken.
Unbedingt PN/DPs Hinweis beachten. Ein Fehler im OB121 in einer Produktiven Anlage ist schlecht. Mach darin nur einfache Anweisungen z.B die AufrufID wegsichern.
Solltest du den FIFO da machen wollen, unbedingt vorgängig im Simulator testen OB121 auslösen und schauen ob er einen Eintrag macht.
Dann erst den FehlerDB in die produktive CPU laden.

Da deine FC100 teilweise übersprungen werden, nützt dir das Inkrementieren der ID im FC100 nichts.
Da wirst du nicht darumherum kommen die aufrufpfade erstmal einzuschränken, das kann recht lange dauern wenn der fehler sehr selten auftritt.

Am besten die IDkopierung beim ersten Zweig der nicht ausgeschlossen werden kann vor die FC100 in die er verzeigt setzen. Damit kannst du dir den Pfad bis zum Fehler ebnen.
ggf wird die Verzweigung ja mit einem CASE oder so gemacht, dann die Casevariable als AussprungID hernehmen spart Tiparbeit.

Es sieht so aus. Als wirst du nicht drum herumkommen den Fehler systematisch einzukreisen.

mfG René
 
Zuviel Werbung?
-> Hier kostenlos registrieren
Was mir auffällt:

Dein FC Kopiert nur INT... somit ist dort meiner Meinung nach kein Bereichslängenfehler möglich -> Außer du verlässt den Struct in DB2.
Nehmen wir mal an Du bekommst nen falschen INT Wert (Warum auch immer) könnte der Fehler wo anders auftreten als dort wo er verursacht wird.

Beispiel: Der FC kopiert eine falsche ID, und erst bei Verwendung in einem anderen Baustein gibt es einen Bereichslängenfehler, da dort etwa so aufgerufen wird "STRUCT_IM_DB1[FEHLERHAFTER_INDEX].CHARGENNUMMER := 1;"
Was könnte die Ursache für ne falsche ID sein? Kopierst Du sie wirklich? Ich schätze eher Du verschiebst!

Also etwa so:

Code:
ID_ZIEL := ID_QUELLE;
ID_QUELLE := LEER;

Kann es Dir ggf. passieren, dass du diese aktion zweimal machst (Sensor flackert, etc) und Leer dann zufällig 0 ist, dein Ziel wird 0 und dein Struct von 1..100 geht... dann hast Du einen Bereichlängenfehler.
(Kann das noch jemand nachvollziehen? ich versteh mich selbst nur halb)

Alternativ:

Du bindest irgendwo deinen DB1 als INOUT an, und ein Baustein bildet das gleiche Array of Struct intern ab.
Die länge der Arrays unterscheidet sich, und keiner merkts (weil ggf. gepointert wird o.ä.).

Nur mal zwei Ansätze zum Suchen.

Grüße

Marcel
 
Da wir ja nicht wissen wie Ziel und Quelle am FC definiert werden. Ich mein um INT auf INT zu kopieren braucht man keinen Baustein. Habe ich jetzt mal angenommen das da irgendwas gepointert wird

mfG René
 
Ja, da wird ja nur der Pointer verschoben und noch eine vorherige ID gesichert.
Der DB besitzt (in meinem Beispiel) ja nur 10 "Datenfächer" (Einen für jede RoBa). Es wird hier aber ein nicht vorhandener Eintrag gepointet.
Die Nummern der Förder sind z.B. 92421 oder 97122.

Eine Förderervariable sieht dann so aus:
DB10."92421.Index" = 15

Wenn ich mir nun die Artikelnummer eines Teils auf Förderer 92421 ansehen will, muss ich folgende Variable anschauen:
DB1."Eintrag"[DB10."92421.Index"]."Artikelnummer"

Ich vermute irgendwo ist eine Variable tippfehlerbehaftet und er will in (z.B.) 924212 einen Index kopieren, den Förderer gibt es aber nicht, der DB geht nur bis 99999
und 99999 steht irgendwo bei Byte 44.000 im DB und im Diagnosepuffer steht, er will auf Byteadresse 52636 zugreifen.

Aber ich hab noch ne Idee:
Ich werde im FC100 einen Bereichsfehler abfangen und im Fehlerfall die Lokaldaten wegsichern. Dann weiß ich, welcher Förderer übergeben wird und finde so bestimmt den Tippfehler.

Das mit den INT war jetzt hier von mir so geschrieben, ich bin mir nicht sicher ob es wirklich nen INT ist.
Ich nutze das klassische Step7. Mit TIA-Portal werde ich nicht warm.

Ich habe auch leider nicht die Möglichkeit in einer anderen Datenbank o.ä. nachzusehen und einen Vergleich zu ziehen.
Mir ist auch noch nie aufgefallen, dass die Daten irendwo fehlen, da alle paar RoBas ein RFID-Chip ausgelesen wird, auf dem die Daten gesichert sind, und zurückgeholt werden.

Ich scheue mich auch nicht, den Fehler "manuell" zu suchen, wollte halt nur wissen, was es sonst noch so an Möglichkeiten gibt.

LG
 
Zuviel Werbung?
-> Hier kostenlos registrieren
Aber ich hab noch ne Idee:
Ich werde im FC100 einen Bereichsfehler abfangen und im Fehlerfall die Lokaldaten wegsichern. Dann weiß ich, welcher Förderer übergeben wird und finde so bestimmt den Tippfehler.
Ja, wenn Du in dem FC100 außer den fehlerhaften Quell-/Ziel-Angaben auch übergeben bekommst (oder errechnen kannst), zu welchem Förderer die Daten gehören, dann kannst Du mit diesen Angaben auch gezielt auf die Suche nach der problematischen Aufrufstelle gehen. Die Förderernummer könnte so eine Signatur ähnlich einer Aufruf-ID sein, mit der Du den Fehler einem oder nur wenigen FC-Aufrufen zuordnen kannst. Wo welcher Förderer bearbeitet wird sollte in dem strukturierten Programm ja leicht zu finden sein.

Merkwürdig finde ich nur, daß die nicht geschriebenen (weil falsch adressierten) Daten anscheinend nirgends vermisst werden oder bisher unbemerkt Datensätze mehrfach vorkommen. Da kann man ja fast froh sein, daß die Fehler Diagnosepuffereinträge verursachen, sonst würden sie wohl gar nicht auffallen.

Harald
 
Merkwürdig finde ich nur, daß die nicht geschriebenen (weil falsch adressierten) Daten anscheinend nirgends vermisst werden oder bisher unbemerkt Datensätze mehrfach vorkommen. Da kann man ja fast froh sein, daß die Fehler Diagnosepuffereinträge verursachen, sonst würden sie wohl gar nicht auffallen.

Das wundert mich auch sehr an der ganzen Sache, aber das kommt halt daher, dass das System so gebaut wurde, dass keine Daten verloren gehen können...
Naja, ich werde mich am Montag mal an den FC geben.

Nochmal vielen Dank an alle für die guten und hilfreichen Tipps!

Schönes Wochenende,
Sebastian
 
Zurück
Oben