TIA Der S7-1200 unter den Rock geschaut

Beiträge
9.189
Reaktionspunkte
2.934
Zuviel Werbung?
-> Hier kostenlos registrieren
Durch die "ruhigen Tage" und viel Spekulatius (hilft beim Spekulieren) habe ich mal etwas Zeit gefunden und versucht herauszufinden wie ein Programm für die S7-1200 übersetzt und abgearbeitet wird.
Dazu habe ich diverse kurze Programme mit einfachen Anweisungen in die SPS hochgeladenen, die übertragenen Daten am Netzwerk abgegriffen und auseinandergepflückt.

Mir ging es nicht darum alles komplett zu entschlüsseln, sondern nur einen groben Überblick über die Funktionsweise zu bekommen. Ich finde es hilft ungemein wenn man wenigstens grob weiß was da im Hintergrund abläuft, aber Siemens rückt ja keine detaillierten Beschreibungen raus.

Hinweis:
Dieses ist alles meine eigene Interpretation, kann sein dass ich in einigen Dingen falsch liege!

Ein im TIA-Portal übersetztes Programm wird in eine Art Zwischencode (oder Intermediate Language - IL) übersetzt.
Dieser Code wird in die SPS hochgeladen und dort ausgeführt. Ob der Code dort direkt interpretiert oder mittels eines dort befindlichen
Just-in-Time Compilers in Maschinencode übersetzt wird, kann nicht nachvollzogen werden, da dieser Teil in der Firmware der SPS
implementiert ist. Die Vorgehensweise mit IL und JIT ist z.B. von den .Net-Sprachen her bekannt.

Ich habe nun versucht den Aufbau dieser IL herauszufinden, und aus den Bytes eine symbolische Assemblersprache (1200-IL) zurückzuentwickeln.

Im Gegensatz zur bekannten S7-AWL-Syntax mit dem Aufbau einer Zeile "Funktion Operand", kann eine Anweisung in 1200-IL mehrere Operanden besitzen (z.B. "Funktion Ziel, Operand1, Operand2").

Beispiele:

S7-AWL:
Code:
L  123
T  MW10

entspräche in 1200-IL:
Code:
MOVW MW10, 123

S7-AWL:
Code:
L  123
L  456
+I
T  MW10

entspräche in 1200-IL:
Code:
ADDW MW10, 123, 456

Die Mnemonics wie MOVW und ADDW sind dabei meine Interpretation der 1200-IL, die Siemens-Bezeichnung sieht garantiert anders aus.

Ein paar weitere Funktionen der 1200-IL:
Code:
ADDW dst, op1, op2	// Addition 2 Byte (INT) und transferiere
MULR dst, op1, op2	// Real Multiplikation und transferiere
ANDW dst, op		// Bitweise AND-Verknüpfung 2 Byte (WORD)
JMP lbl			// Springe unbedingt zu Label
JNvke lbl		// Springe wenn VKE=false zu Label

Da die Sprungziele in dieser Sprache noch über Labels und nicht über Codeadressen / Sprungweiten laufen,
kann es sich meiner Meinung nach nicht direkt um Maschinensprache handeln sondern eben um eine Zwischenschicht.

Die Operanden (op1, op2) und Ziel einer Operation (dst) bestehen aus einem Byte Bereichs-/Längen-Kennung und dem eigentlichen Operandenwert.
Eine Bereichs-Kennung ist z.B. Merker-, Eingangs- oder Ausgangsbereich, oder Konstantenkennung (8/16/24/32 Bit).

FUP und SCL erzeugen prinzipiell beide Code dieses Befehlssatzes.
Es existieren aber erweiterte Anweisungen um die Weitergabe von EN und ENO an gestapelte FUP-Boxen zu ermöglichen.
Ähnliche Anweisungen werden auch in SCL erzeugt wenn man das Bausteinattribut "ENO automatisch setzen" aktiviert (was die Codegröße in etwa verdoppelt).
Wobei eine ADD-Box in FUP über den EN-Eingang nicht wie bei S7-AWL in einen Sprung übersetzt wird, sondern es dafür eine extra Anweisung gibt.
(in der Art einer "ADDcc" - Add conditional - Funktion).

Ein Beispiel wie SCL in 1200-IL übersetzt wird:

SCL:
Code:
"MW2" := 26214;
"MW4" := ("MW0" + "MW6") * ("MW6" - 4369);
"MW6" := 17476;

1200-IL:
Code:
MOVW MW2, 0x6666
ADDW @iTemp1, MW0, MW6
ADDW @iTemp2, MW6, 0x1111
MULW MW6, @iTemp1, @iTemp2

Die @-Variablen sind ein Ablageplatz für Zwischenberechnungen, welche nach Größen der Datentypen sortiert sind (Bool/Int/Dint/Real/...).
Auf IL-Ebene gibt es auch keine Symbole mehr, sondern es wird direkt z.B. "Merkerbereich, Wort 6" oder "Eingang, Byte 100" angesprochen.
Bei Datenbausteinen sieht das etwas anders aus, dort scheint per Basispointer und Offset gearbeitet zu werden. Das habe ich noch nicht ganz auseinandergenommen.

Mit dem Befehlssatz dürfte auch klar sein warum bei der 1200 kein AWL möglich ist, denn demgegenüber ist die IL eine "höhere" Programmiersprache.

Da wäre interessant wie sich die 1500 verhält. D.h. ob diese prinzipiell die gleiche IL wie die 1200 spricht, erweitert um Ein-Adress-Anweisungen für AWL, oder ob das eine komplett eigenständige Entwicklungslinie ist.


Vielleicht hat ja jemand irgendwelche offiziellen Infos die das Bild komplettieren, z.B. bezüglich Unterschiede 1200/1500 und Abarbeitung in der SPS.
Ich glaube bei Siemens heißt diese Zwischensprache MC7plus wenn ich mich nicht irre.
 
Pass meine Arbeit im Moment vielleicht auch dazu:

Ich hab Angefangen direkten support für das TIA File Format in meine ToolBox einzubauen. Den Code dazu gibts hier https://github.com/jogibear9988/DotNetSiemensPLCToolBoxLibrary. Scheint aber recht komplex, weiss noch nicht was da erreichbar ist...

Wer Interesse hat der Code liegt in der Step7ProjectV11.cs, im Moment wird dort aber noch der Code verwendet welcher die TIA DLLs nutzt, daraus XML erzeugt und dann das Projekt parsed! Ganz soweit das Ich das wegschmeißen kann hab Ich das File Format noch nicht verstanden!
 
Zuviel Werbung?
-> Hier kostenlos registrieren
entspräche in 1200-IL:
Code:
MOVW MW10, 123
Das sieht ja aus, wie AWL bei der S7-200, auch wenn dies
entspräche in 1200-IL:
Code:
ADDW MW10, 123, 456
getrennt
Code:
[FONT=courier new]MOVW MW10, 123
ADDW MW10, 456[/FONT]
ausgeführt wurde.

Dann ist die S7-1200 wohl wirklich 'ne Weiterentwicklung der S7-200?
 
hucki, das ist hier der Befehlscode der S7-1200 in der Interpredation von Thomas.
Also das was TIA über die Schnittstelle zur CPU schickt.
Wie weit da Verwandtschaft besteht, kann man nach so ein paar Codestücken nicht sagen.
Aber Thomas und Jochen werden das Schwein schon schlachten :p

Gruß
Dieter
 
Die Namen sind rein von mir. Meine erste Vermutung war ja dass es sich um einen ARM-Prozessor handelt, darum habe ich mich dabei an dessen Syntax orientiert.
Mit der S7-200 hat das nichts zu tun, eben weil diese imho keine Drei-Adress-Befehle kann.

Das Prinzip bei der 1200 ist eigentlich genauso wie auch bei den alten CPUs. Bei den alten CPUs war eben MC7 der Zwischencode (bzw. bei speziellen Prozessoren wie dem Speed7 sogar der native Maschinencode). Bei den 300/400er Reihen gab es ebenso Unterschiede zwschen Compiler- und Interpretermaschinen.
Alter Wein in neuen Schläuchen sozusagen.

Bei Codesys wird ja wirklich direkt Maschinencode für die Zielarchitektur erzeugt, zumindest für x86 ist das so. Irgendwo hat Siemens doch beim TIA-Portal auch damit geworben dass alles sehr schnell ist weil es direkt in Maschinencode und nicht AWL übersetzt wird. Sieht mir bei dem was ich bei der 1200er so sehe aber anders aus - außer das wird wirklich auf der SPS kompiliert und nicht interpretiert. Die schlechte Performance der 1500er spricht aber für letzteres.

Irgendwann muss man den Schritt der Übersetzung für den Zielprozessor machen. Ich persönlich hätte das ja alles auf den PC verlagert, denn da sind ausreichend Ressourcen für sowas vorhanden.
 
Zuviel Werbung?
-> Hier kostenlos registrieren
Irgendwo hat Siemens doch beim TIA-Portal auch damit geworben dass alles sehr schnell ist weil es direkt in Maschinencode und nicht AWL übersetzt wird. Sieht mir bei dem was ich bei der 1200er so sehe aber anders aus - außer das wird wirklich auf der SPS kompiliert und nicht interpretiert. Die schlechte Performance der 1500er spricht aber für letzteres.

Die Aussage kenne ich nur in Zusammenhang mit SCL und Graph.
Deine Einschätzung zur Performance der 1500 teile ich, da wäre wirklich mehr zu erwarten gewesen.
Ich vermute mal, dass Siemens beim Interpreter Vorteile beim Einspielen von Änderungen und der Speicherverwaltung sieht.

Offtopic:
Was mich aber bei der 1500 positiv überrascht hat, ist der Netzwerk-CP.
Der hat den Sicherheitsscan unserer IT ohne Makel bestanden.

Gruß
Dieter
 
@Thomas_v2.1

Wie sieht es mit der 1500er aus ????? Hast Du da auch schon mal etwas Zeit gefunden und versucht herauszufinden wie ein Programm für die S7-1500 übersetzt und abgearbeitet wird ???
 
@Thomas_v2.1

Wie sieht es mit der 1500er aus ????? Hast Du da auch schon mal etwas Zeit gefunden und versucht herauszufinden wie ein Programm für die S7-1500 übersetzt und abgearbeitet wird ???

Heute um 19:01 geschrieben von Thomas_v2.1:
Da wäre interessant wie sich die 1500 verhält. D.h. ob diese prinzipiell die gleiche IL wie die 1200 spricht, erweitert um Ein-Adress-Anweisungen für AWL, oder ob das eine komplett eigenständige Entwicklungslinie ist.

Ich glaube nicht, daß er innerhalb 2,5 Std. da schon eine Lösung bereitstellen kann....;)
 
Zuviel Werbung?
-> Hier kostenlos registrieren
Wie sieht es mit der 1500er aus ????? Hast Du da auch schon mal etwas Zeit gefunden und versucht herauszufinden wie ein Programm für die S7-1500 übersetzt und abgearbeitet wird ???

Nein, hab auch keine 1500er zum Testen da.
Hellebarde schrieb mir mal, dass man bei der 1500er die Kommunikation auch nicht so einfach am Netzwerk abgreifen kann. D.h. er hat zweimal den gleichen Baustein hochgeladen, und auf dem Netzwerk waren die Daten komplett unterschiedlich. Vlt. wird dazu SSL-Verbindung aufgebaut, sowas in der Art habe ich zumindest mal gelesen.
Selbst in der plf-Datei in der sich die kompletten Daten eines Projekts befinden, findet man irgendwelche markanten Konstanten die man vorher im Programm platziert nicht wieder. Ist also nicht ganz so einfach, wenn auch nicht unmöglich das auch bei der 1500er rauszufinden.
 
Passt zwar nicht ganz zur 1200, aber das TIA-Portal hat in der Version V11 noch eine versteckte Funktion.

Bei TIA V11 lässt sich ein Compilerlogfile bei Übersetzungsvorgängen eines SCL-Bausteins für eine S7-300/400 aktivieren. Bei der 1200 wird das leider nicht gemacht, oder ich habe nur noch nicht gefunden wie man das auch dort aktivieren kann.

Dazu muss nur ein Ordner angelegt werden.
Unter Windows 7 ist das (bei meinem Benutzer) der komplette Pfad:
C:\Users\Thomas\AppData\Local\Siemens\Automation\Portal V11\SCLPlus_Logs

Wenn man dann einen SCL-Baustein übersetzt, landen im Ordner SCLPlus_Logs diverse Dateien des Übersetzungsprozesses, wie Logfiles und auch der generierte AWL-Code.

Unter anderem werden auch noch diverse *.Dot Dateien für die Optimierungsschritte des Compilers erzeugt. Mit diesen Dateien lassen sich Syntaxbäume z.B. mit Graphviz (http://www.graphviz.org/) erstellen. Das Format ist 1:1 kompatibel.

Ein kleines Beispiel:
Code:
#intVar1 := 100;
#intVar2 := #intVar1 * 3;
Mit dem nun erstellten .dot File kann man sich mit Graphviz folgenden Syntaxbaum (Ausschnitt) erstellen lassen:

Beispiel-AST-Graphviz.png

Ob die Funktion bei der V12 komplett entfernt wurde, oder ob man sie auf andere Weise wieder aktivieren kann habe ich noch nicht entdecken können.
Die Vorgehensweise wie für die V11 funktioniert zumindest nicht mehr.
 
Zuviel Werbung?
-> Hier kostenlos registrieren
Bei TIA V11 lässt sich ein Compilerlogfile bei Übersetzungsvorgängen eines SCL-Bausteins für eine S7-300/400 aktivieren. Bei der 1200 wird das leider nicht gemacht, oder ich habe nur noch nicht gefunden wie man das auch dort aktivieren kann.

Bei der V11 gibt es doch für die 1200 gar kein SCL.
 
Zuviel Werbung?
-> Hier kostenlos registrieren
Hi all

kaum bin ich Schifoan -- eigentlich war das mehr Wiese rutschen :((((, schon habt ihr hier die geilen Themen.

Ich hab Thomas schon meine Erkenntnisse gegeben. Mal sehen was Jürgen da noch draus machen kann

'n schön' Tach auch
HB
 
Ich habe mal son bißchen weitergeforscht. Ein paar Dinge hat Siemens sich bei der MSIL abgeguckt, z.B. die Ein- und Zwei-Byte Opcodes, nur entscheidet sich das bei Siemens anhand eines anderen Wertes des ersten Bytes des Opcodes.

Parameterübergabe zu Funktionen läuft wohl über eine Art Stack, zumindest habe ich es so getauft, da es der gleiche Bereich zu sein scheint auf den auch Zwischenergebnisse für Berechnungen abgelegt werden. .

Ein FC-Inhalt (FC Nummer 1) mit:
Code:
MW0 := 17476;
MW2 := #IN1;
MW4 := #IN2;
#OUT := "MW114_Int";
MW0 := 17476;

#IN1, #IN2 und #OUT1 sind vom Typ Int.

Wird nach meiner Interpretation zu:
Code:
SET                #BoolStack@01                                                // Setze Bit <1:OUT>
MOVE               [WORD] M0, 0x4444                                            // Lade und transferiere <1:OUT, 2:IN>
MOVE               [WORD] M2, #IntStack@02                                      // Lade und transferiere <1:OUT, 2:IN>
MOVE               [WORD] M4, #IntStack@06                                      // Lade und transferiere <1:OUT, 2:IN>
MOVE               [WORD] #IntStack@10, M114                                    // Lade und transferiere <1:OUT, 2:IN>
MOVE               [WORD] M0, 0x4444                                            // Lade und transferiere <1:OUT, 2:IN>
RET                #BoolStack@01                                                // Bausteinende <1:IN>

Wenn man den FC aufruft mit:
Code:
MW0 := 17476;
TestFcAddInt(
  IN1:=12,
  IN2:=34,
  OUT=>"MW2"
);
MW0 := 17476;

wird daraus
Code:
SET                #BoolStack@01                                                // Setze Bit <1:OUT>
MOVE               [WORD] M0, 0x4444                                            // Lade und transferiere <1:OUT, 2:IN>
CALL               Params#0x0002, 1                                             // Call Befehl einleiten <1:PARAM, 2:NR>
MOVE               [WORD] #IntStack@03, 0x0C                                    // Lade und transferiere <1:OUT, 2:IN>
MOVE               [WORD] #IntStack@07, 0x22                                    // Lade und transferiere <1:OUT, 2:IN>
CALL_EXECUTE                                                                    // Call Befehl ausführen
MOVE               [WORD] M2, #IntStack@11                                      // Lade und transferiere <1:OUT, 2:IN>
CALL_FRAME_CLEAR                                                                // Call Befehl abschließen
MOVE               [WORD] M0, 0x4444                                            // Lade und transferiere <1:OUT, 2:IN>
RET                #BoolStack@01                                                // Bausteinende <1:IN>

Die Bausteinnummern sind immer noch tief im System verankert, zumindest auf Ebene der IL.
Was die SPS damit anstellt wird einem wohl weitestgehend verborgen bleiben.
 
Zuviel Werbung?
-> Hier kostenlos registrieren
wie ist denn der aktuelle Stand der Analayse - und was ist das Ziel?
willst du einen freien SCL-Compiler fuer die 1200 bauen?

Der Sinn dahinter ist rauszufinden wie es funktioniert. Das ist ergiebiger als tausende Seiten halbgare Siemens-Doku zu lesen.

Es fehlt noch etwas am Grundverständnis, wie DB-Zugriffe und Zugriffe auf Temp-Variablen realisiert sind, bzw. wie man sich die interne Speicherablage überhaupt vorstellen kann. Ansonsten muss noch jede verfügbare Anweisung einmal in allen verschiedenen Varianten aufrufen und den Aufbau dokumentieren. Das ist mehr oder weniger Fleißarbeit, es gesellen sich mit jedem SP auch neue Anweisungen dem bisherigen Zoo hinzu.
Da ich bisher den Code nur beim Download über Wireshark abgreifen kann ist das etwas aufwändig, da auch der Wireshark-Dissector noch nicht vollständig ist. Darum mache ich erstmal da weiter - je nach Lust und Laune.
 
Alles für eine S7-1200.

SCL-Quelle OB1:
Code:
IF NOT("M10.0_Bool") THEN
  "MW0" := 30583;
END_IF;
MW0 := 26214;
IF "MW114_Int" < 100 THEN
  "MW0" := 30583;
ELSIF "MW114_Int" <= 200 THEN
  "MW0" := 26214;
ELSIF "MW114_Int" = 300 THEN
  "MW0" := 30583;
ELSIF "MW114_Int" >= 400 THEN
  "MW0" := 26214;
ELSIF "MW114_Int" > 500 THEN
  "MW0" := 30583;
ELSE
  "MW0" := 26214;
END_IF;

Im angehängten Logfile beginnt der Bausteinupload in Paket Nr. 142. Bei dieser Bausteingröße ist alles in einer PDU.
Der Code beginnt bei den drei Bytes 0x18 0x40 0x01

Laut meiner Interpretation ergibt das in MC7plus-IL:
Code:
SET                #BoolStack@01                                                // Setze Bit <1:OUT>
LDN_BOOL           M10.0                                                        // Lade Bit und negiere <1:IN>
ST                 #BoolStack@05                                                // Speichere VKE auf <1:OUT>
JMPC               0                                                            // Springe bei VKE=1 <1:LBL>
MOVE               [WORD] M0, 0x7777                                            // Lade und transferiere <1:OUT, 2:IN>
JMP                0                                                            // Springe unbedingt <1:LBL>
LABEL              0                                                            // Label <1:LBL>
MOVE               [WORD] M0, 0x6666                                            // Lade und transferiere <1:OUT, 2:IN>
LD_CMP             LT, [INT] M114, 0x64                                         // Lade und vergleiche <1:TYP 2:IN1, 3:IN2>
ST                 #BoolStack@05                                                // Speichere VKE auf <1:OUT>
JMPC               1                                                            // Springe bei VKE=1 <1:LBL>
MOVE               [WORD] M0, 0x7777                                            // Lade und transferiere <1:OUT, 2:IN>
JMP                2                                                            // Springe unbedingt <1:LBL>
LABEL              1                                                            // Label <1:LBL>
LD_CMP             LE, [INT] M114, 0xC8                                         // Lade und vergleiche <1:TYP 2:IN1, 3:IN2>
ST                 #BoolStack@05                                                // Speichere VKE auf <1:OUT>
JMPC               0x03                                                         // Springe bei VKE=1 <1:LBL>
MOVE               [WORD] M0, 0x6666                                            // Lade und transferiere <1:OUT, 2:IN>
JMP                2                                                            // Springe unbedingt <1:LBL>
LABEL              0x03                                                         // Label <1:LBL>
LD_CMP             EQ, [INT] M114, 0x012C                                       // Lade und vergleiche <1:TYP 2:IN1, 3:IN2>
ST                 #BoolStack@05                                                // Speichere VKE auf <1:OUT>
JMPC               0x04                                                         // Springe bei VKE=1 <1:LBL>
MOVE               [WORD] M0, 0x7777                                            // Lade und transferiere <1:OUT, 2:IN>
JMP                2                                                            // Springe unbedingt <1:LBL>
LABEL              0x04                                                         // Label <1:LBL>
LD_CMP             GE, [INT] M114, 0x0190                                       // Lade und vergleiche <1:TYP 2:IN1, 3:IN2>
ST                 #BoolStack@05                                                // Speichere VKE auf <1:OUT>
JMPC               0x05                                                         // Springe bei VKE=1 <1:LBL>
MOVE               [WORD] M0, 0x6666                                            // Lade und transferiere <1:OUT, 2:IN>
JMP                2                                                            // Springe unbedingt <1:LBL>
LABEL              0x05                                                         // Label <1:LBL>
LD_CMP             GT, [INT] M114, 0x01F4                                       // Lade und vergleiche <1:TYP 2:IN1, 3:IN2>
ST                 #BoolStack@05                                                // Speichere VKE auf <1:OUT>
JMPC               0x06                                                         // Springe bei VKE=1 <1:LBL>
MOVE               [WORD] M0, 0x7777                                            // Lade und transferiere <1:OUT, 2:IN>
JMP                2                                                            // Springe unbedingt <1:LBL>
LABEL              0x06                                                         // Label <1:LBL>
MOVE               [WORD] M0, 0x6666                                            // Lade und transferiere <1:OUT, 2:IN>
LABEL              2                                                            // Label <1:LBL>
RET                #BoolStack@01                                                // Bausteinende <1:IN>

#BoolStackXY ist meine Interpretation, das dient zumindest als eine Art Zwischenspeicher.
 

Anhänge

  • Upload9-1.zip
    9,7 KB · Aufrufe: 6
Zurück
Oben